import {z} from 'zod' export type GetApiV1ModelsOptions = {baseUrl?: string | undefined, apiKey?: string | undefined, fetch?: typeof fetch | undefined}; export const modelInfo = z.object({id: z.string(), description: z.string()}); export const cUAVersionInfo = z.object({id: z.string(), description: z.string(), avg_step_time: z.string(), features: z.array(z.string()), managed_stateless_predict_mode: z.enum(["full_pipeline", "shared_fast_planner"]), latest: z.boolean()}); export const creditsByCuaVersion = z.object({v1: z.number().int().gte(0), v3: z.number().int().gte(0), v4: z.number().int().gte(0), v5: z.number().int().gte(0)}); export const scheduleExecutionPricingInfo = z.object({balance_source: z.literal("subscription_credits"), credits_per_minute: z.number().int().gte(1), minimum_balance_credits: z.number().int().gte(0), billing_rounding: z.literal("ceil_billable_minutes"), billing_session_max_duration_seconds: z.number().int().gte(1), scheduler_timeout_seconds: z.number().int().gte(1), credit_meter_applies_to: z.literal("non_unlimited_accounts"), unlimited_credit_meter_bypass: z.literal(true)}); export const schedulePricingInfo = z.object({per_fire_credits: z.literal(0), minimum_wallet_balance_cents: z.number().int().gte(0), webhook_minimum_wallet_balance_cents: z.number().int().gte(0), webhook_default_rate_limit_per_minute: z.number().int().gte(1), webhook_replay_window_seconds: z.number().int().gte(1), webhook_dedup_window_seconds: z.number().int().gte(1), webhook_max_body_bytes: z.number().int().gte(1), execution: scheduleExecutionPricingInfo}); export const runtimePricingInfo = z.object({value_kind: z.literal("effective_runtime"), published_defaults_path: z.literal("/api/pricing"), currency: z.literal("usd"), credit_usd_cents: z.literal(1), base_fees_credits: z.object({predict: z.number().int().gte(0), session_create: z.number().int().gte(0), session_predict: z.number().int().gte(0), ground: z.number().int().gte(0), parse: z.number().int().gte(0)}), surcharges_credits: z.object({trajectory_screenshot_each: z.number().int().gte(0), high_resolution_image_each: z.number().int().gte(0), v1_engine_per_request: z.number().int().gte(0), long_system_prompt_per_request: z.number().int().gte(0)}), long_system_prompt_threshold_chars: z.number().int().gte(0), run_step_credits: creditsByCuaVersion, workflow_task_step_credits: creditsByCuaVersion, schedules: schedulePricingInfo, test_keys_bill: z.literal(false)}); export const modelsResponse = z.object({models: z.array(modelInfo), cua_versions: z.array(cUAVersionInfo), action_types: z.array(z.string()), machine_action_commands: z.array(z.enum(["screenshot", "click", "double_click", "click_with_modifiers", "type", "key_press", "key_combo", "scroll", "drag", "move", "list_windows", "switch_to_window", "close_window", "minimize_window", "maximize_window", "restore_window", "arrange_windows", "move_window", "terminal_connect", "terminal_execute", "terminal_read", "terminal_clear", "terminal_close", "file_read", "file_exists", "directory_list", "file_write", "file_edit", "file_append", "file_delete", "directory_delete", "file_download", "file_list_downloads", "browser_open", "browser_navigate", "browser_click", "browser_type", "browser_get_dom", "browser_get_clickables", "browser_state", "browser_info", "browser_scroll", "browser_close", "browser_screenshot", "browser_wait", "browser_list_tabs", "browser_open_tab", "browser_close_tab", "browser_switch_tab", "browser_execute"])), pricing: runtimePricingInfo}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * List models, CUA versions, action types, and effective runtime pricing * * GET /v1/models * * Also accepts auth: bearerAuth */ export const getApiV1Models = async (options?: GetApiV1ModelsOptions) => { const {baseUrl = "https://coasty.ai", fetch: fetchFn = fetch, apiKey} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (apiKey !== undefined) headers.set("X-API-Key", apiKey) const url = `${baseUrl.replace(/\/+$/, '')}/v1/models${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(modelsResponse, await res.json()) };