From 0d394297d1f5ff0927e29d25aaa6ff35ba772833 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 17 Jul 2026 03:43:38 +0000 Subject: [PATCH 1/3] Add Moonshot Kimi K3 computer-use provider Expose kimi-k3 as moonshotai:kimi-k3 (alias moonshot:), streaming through pi-ai's builtin OpenAI-compatible chat completions transport with MOONSHOT_API_KEY. Kimi grounding emits 0-1 width/height fractions, so the provider declares a fractional normalized coordinate contract and disables parallel tool calls via payload middleware. Bump pi packages to 0.80.10 for the Kimi K3 registry entry, adapting to the Models auth surface and the sessionAffinityFormat compat rename, and accept the new max thinking level in the CLI. --- .agents/skills/update-models/SKILL.md | 17 ++- .../skills/update-models/reference/README.md | 3 +- .../reference/audit-official-examples.ts | 3 +- .../reference/discover-models.ts | 101 +++++++++++++++++- .../reference/provider-doc-drift.ts | 16 ++- .../update-models/reference/report-schema.md | 1 + .github/workflows/ci.yml | 2 + README.md | 4 + package-lock.json | 56 +++++----- packages/agent/README.md | 1 + packages/agent/package.json | 4 +- packages/agent/src/agent.ts | 6 +- packages/agent/src/provider-retry.ts | 6 +- packages/agent/test/e2e.live.test.ts | 14 +++ packages/ai/README.md | 11 +- packages/ai/docs/supported-models.md | 19 ++++ packages/ai/examples/quickstart.ts | 1 + packages/ai/package.json | 2 +- packages/ai/src/api-keys.ts | 9 +- packages/ai/src/index.ts | 1 + packages/ai/src/models.ts | 26 +++-- packages/ai/src/providers.ts | 2 + packages/ai/src/providers/moonshot/index.ts | 63 +++++++++++ packages/ai/src/runtime-spec.ts | 2 + packages/ai/test/api-keys.test.ts | 5 + .../ai/test/computer-tool.integration.test.ts | 12 +++ packages/ai/test/models.test.ts | 25 ++++- packages/ai/test/provider-module.test.ts | 5 +- packages/ai/test/providers.test.ts | 5 +- packages/ai/test/runtime-spec.test.ts | 10 ++ packages/cli/README.md | 8 +- packages/cli/package.json | 4 +- packages/cli/src/cli-harness.ts | 8 +- packages/cli/src/cli.ts | 9 +- packages/cli/src/harness-models.ts | 7 +- packages/cli/test/cli-executor.test.ts | 1 + packages/cli/test/harness-models.test.ts | 5 + 37 files changed, 399 insertions(+), 75 deletions(-) create mode 100644 packages/ai/src/providers/moonshot/index.ts diff --git a/.agents/skills/update-models/SKILL.md b/.agents/skills/update-models/SKILL.md index f02b894a..f09e343b 100644 --- a/.agents/skills/update-models/SKILL.md +++ b/.agents/skills/update-models/SKILL.md @@ -1,6 +1,6 @@ --- name: update-models -description: Discover latest OpenAI, Anthropic, Google/Gemini, Meta, xAI, Tzafon, and Yutori models and verify computer-use support. Use when updating CUA model defaults, checking new model releases, auditing provider-native computer tool actions, or comparing provider metadata, official examples, and smoke-test results. +description: Discover latest OpenAI, Anthropic, Google/Gemini, Meta, xAI, Moonshot, Tzafon, and Yutori models and verify computer-use support. Use when updating CUA model defaults, checking new model releases, auditing provider-native computer tool actions, or comparing provider metadata, official examples, and smoke-test results. --- # Update Models @@ -9,14 +9,14 @@ Use this workflow to keep CUA current with provider model releases and computer- ## Quick Start -1. Verify credentials are available: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY` or `GEMINI_API_KEY`, `META_API_KEY`, `XAI_API_KEY`, `TZAFON_API_KEY`, and `YUTORI_API_KEY`. +1. Verify credentials are available: `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY` or `GEMINI_API_KEY`, `META_API_KEY`, `XAI_API_KEY`, `MOONSHOT_API_KEY`, `TZAFON_API_KEY`, and `YUTORI_API_KEY`. 2. If credentials live in `~/AGENTS.md`, load them into the current shell without printing them: ```bash eval "$(python3 - <<'PY' import pathlib, re, shlex text = pathlib.Path('~/AGENTS.md').expanduser().read_text() -for key in ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_API_KEY', 'META_API_KEY', 'XAI_API_KEY', 'TZAFON_API_KEY', 'YUTORI_API_KEY']: +for key in ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_API_KEY', 'META_API_KEY', 'XAI_API_KEY', 'MOONSHOT_API_KEY', 'TZAFON_API_KEY', 'YUTORI_API_KEY']: m = re.search(r'export\s+' + re.escape(key) + r'=(?:"([^"]+)"|([^\s\n]+))', text) if m: print(f'export {key}={shlex.quote(m.group(1) or m.group(2))}') @@ -114,6 +114,16 @@ xAI: - Use CUA's normalized 0-1000 coordinate instructions, `parallel_tool_calls: false`, `store: true`, and `previous_response_id` for browser loops. xAI accepts encrypted reasoning replay in these requests. - Use `reasoning: { effort: "low" }` for low-latency smoke tests; Grok 4.5 also supports `medium` and `high`, cannot disable reasoning, and defaults to `high`. +Moonshot: + +- Discover with the OpenAI SDK against `https://api.moonshot.ai/v1` using `MOONSHOT_API_KEY`. +- Smoke-test the chat completions API with screenshot input and explicit function tools matching CUA's canonical actions. +- Pass condition: response `choices[0].message.tool_calls[]` contains one of the supplied browser actions. +- Treat Kimi computer use as custom-function-tool support, not a provider-native computer tool. Moonshot documents vision input and function calling but no coordinate protocol. +- Kimi grounding emits width/height fractions from 0 to 1 regardless of prompt or schema wording; keep CUA's fractional coordinate contract and verify emitted values stay in 0-1. +- Set `parallel_tool_calls: false` because browser actions mutate shared state. There is no response threading; the full context replays each turn. +- Kimi K3 launched with max-only thinking effort. pi-ai's registry entry clamps other levels away; re-check `thinkingLevelMap` when Moonshot ships low/high modes. + Tzafon: - Discover with `new Lightcone({ apiKey }).models.list()` from `@tzafon/lightcone` when available. @@ -187,6 +197,7 @@ All CUA model and adapter support lives in `packages/ai` (`@onkernel/cua-ai`). W - Anthropic: update the `ANTHROPIC_CUA_ACTION_TYPES` set in `packages/ai/src/providers/anthropic/actions.ts` and `index.ts`. The computer tool version and `computer-use-*` beta header are selected by `pi-ai` per model, so a new dated tool version usually means bumping `@earendil-works/pi-ai`, not editing this package. - Gemini: update `packages/ai/src/providers/gemini/index.ts`, including coordinate handling if needed. - xAI: update `packages/ai/src/providers/xai/index.ts` and `provider.ts`, including normalized coordinate instructions, Responses threading, and reasoning compatibility. + - Moonshot: update `packages/ai/src/providers/moonshot/index.ts`, including the fractional coordinate instructions and payload middleware. Streaming rides pi-ai's builtin `openai-completions` transport, so wire-format changes usually mean bumping `@earendil-works/pi-ai`. - Tzafon: update `packages/ai/src/providers/tzafon/index.ts` and `provider.ts`, including coordinate/action handling. - Yutori: update `packages/ai/src/providers/yutori/actions.ts`, `index.ts`, and `provider.ts`, including payload filtering and coordinate/action handling. - Shared canonical action semantics go in `packages/ai/src/providers/common.ts`. diff --git a/.agents/skills/update-models/reference/README.md b/.agents/skills/update-models/reference/README.md index 7bb813dc..d2e6c099 100644 --- a/.agents/skills/update-models/reference/README.md +++ b/.agents/skills/update-models/reference/README.md @@ -13,10 +13,11 @@ These scripts support the `update-models` skill. Run them from the repository ro - `GOOGLE_API_KEY` or `GEMINI_API_KEY` - `META_API_KEY` - `XAI_API_KEY` + - `MOONSHOT_API_KEY` - `TZAFON_API_KEY` - `YUTORI_API_KEY` -The scripts never print API keys. Smoke tests are non-destructive: they ask each model to emit a computer-use tool call, then inspect the response without executing the action. Meta and xAI use supplied function tools; other providers may use provider-native computer tools. +The scripts never print API keys. Smoke tests are non-destructive: they ask each model to emit a computer-use tool call, then inspect the response without executing the action. Meta, xAI, and Moonshot use supplied function tools; other providers may use provider-native computer tools. ## Common Commands diff --git a/.agents/skills/update-models/reference/audit-official-examples.ts b/.agents/skills/update-models/reference/audit-official-examples.ts index cc6a4b07..1005b43a 100644 --- a/.agents/skills/update-models/reference/audit-official-examples.ts +++ b/.agents/skills/update-models/reference/audit-official-examples.ts @@ -5,7 +5,7 @@ import { basename, join, resolve } from "node:path"; import { spawnSync } from "node:child_process"; import process from "node:process"; -type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "moonshot" | "yutori"; interface ExampleRepo { provider: Provider; @@ -83,6 +83,7 @@ const ACTION_REGEXES: Record = { gemini: [/\b(open_web_browser|open_web|wait_5_seconds|go_back|go_forward|search|navigate|click_at|hover_at|type_text_at|key_combination|scroll_document|scroll_at|drag_and_drop)\b/g], meta: [/\b(screenshot|left_click|right_click|middle_click|double_click|triple_click|left_click_drag|mouse_move|key|type|scroll|hold_key|wait|left_mouse_down|left_mouse_up)\b/g], xai: [/\b(screenshot|click|double_click|mouse_down|mouse_up|scroll|type|keypress|drag|move|wait)\b/g], + moonshot: [/\b(screenshot|click|double_click|mouse_down|mouse_up|scroll|type|keypress|drag|move|wait)\b/g], yutori: [/\b(left_click|double_click|triple_click|right_click|scroll|type|key_press|hover|drag|wait|refresh|go_back|goto_url|mouse_move|middle_click|mouse_down|mouse_up|go_forward|hold_key|extract_elements|find|set_element_value|execute_js)\b/g], }; diff --git a/.agents/skills/update-models/reference/discover-models.ts b/.agents/skills/update-models/reference/discover-models.ts index 097c87f4..f243e6cd 100644 --- a/.agents/skills/update-models/reference/discover-models.ts +++ b/.agents/skills/update-models/reference/discover-models.ts @@ -6,7 +6,7 @@ import { join } from "node:path"; import process from "node:process"; import { parse as parseToml } from "smol-toml"; -type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "tzafon" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "moonshot" | "tzafon" | "yutori"; interface Args { provider: Provider | "all"; @@ -39,7 +39,7 @@ interface ModelResult { cua?: Record; } -const PROVIDERS: Provider[] = ["openai", "anthropic", "gemini", "meta", "xai", "tzafon", "yutori"]; +const PROVIDERS: Provider[] = ["openai", "anthropic", "gemini", "meta", "xai", "moonshot", "tzafon", "yutori"]; const TZAFON_KNOWN_MODELS = [ "tzafon.northstar-cua-fast", ]; @@ -110,7 +110,7 @@ function usage(): never { npx tsx .agents/skills/update-models/reference/discover-models.ts --provider openai --models gpt-5.5,gpt-5.4 Options: - --provider + --provider --models Smoke-test explicit models instead of inferred candidates. --candidate-limit Max inferred candidates per provider. Default: 20. --no-smoke Only list metadata. @@ -140,6 +140,7 @@ async function runProvider(provider: Provider, args: Args): Promise> { + const OpenAI = await importDefault("openai", "OpenAI"); + const client = new OpenAI({ apiKey: process.env.MOONSHOT_API_KEY, baseURL: "https://api.moonshot.ai/v1" }); + const rawModels = await collectAsync(client.models.list()); + const models: ModelResult[] = rawModels.map((m) => ({ + id: String(m.id), + display_name: String(m.id), + created_at: typeof m.created === "number" && m.created > 0 ? new Date(m.created * 1000).toISOString() : null, + raw: m, + supports_generation: String(m.id).toLowerCase().startsWith("kimi-"), + model_docs: { + url: "https://platform.kimi.ai/docs/api/tool-use", + chat_completions_endpoint: "supported", + function_calling: "supported", + image_input: "verify-per-model", + coordinate_space: "CUA-defined 0-1 fractions", + }, + })); + const candidates = explicitOrCandidates( + args, + models + .filter((model) => model.supports_generation) + .sort(compareMoonshotCandidates) + .map((model) => model.id), + ); + if (args.smoke) { + await Promise.all(candidates.map(async (id) => { + const model = models.find((candidate) => candidate.id === id) ?? { id, display_name: id, supports_generation: true }; + model.computer_use = await smokeMoonshot(client, id); + if (!models.find((candidate) => candidate.id === id)) models.unshift(model); + })); + } + await annotateCuaSupport("moonshot", models); + return { provider: "moonshot", metadata_source: "Moonshot models.list()", models, candidates }; +} + +async function smokeMoonshot(client: any, model: string): Promise { + try { + const screenshot = await readFile(fixtureScreenshotPath()); + const response = await client.chat.completions.create({ + model, + parallel_tool_calls: false, + max_tokens: 8192, + messages: [ + { + role: "system", + content: "Coordinates are fractions of the screenshot, normalized from 0 to 1.", + }, + { + role: "user", + content: [ + { type: "text", text: "Call the click tool for the sign in link. Do not answer only in text." }, + { type: "image_url", image_url: { url: `data:image/png;base64,${screenshot.toString("base64")}` } }, + ], + }, + ], + tools: [{ + type: "function", + function: { + name: "click", + description: "Click at coordinates given as 0-1 fractions of the screenshot.", + parameters: { + type: "object", + properties: { x: { type: "number" }, y: { type: "number" } }, + required: ["x", "y"], + additionalProperties: false, + }, + }, + }], + }); + const calls: any[] = response.choices?.[0]?.message?.tool_calls ?? []; + return { + status: calls.length > 0 ? "pass" : "inconclusive", + tool_name: "function_tools", + tool_version: null, + beta_header: null, + observed_actions: unique(calls.map((call) => call?.function?.name).filter(Boolean)), + response_item_types: unique([response.choices?.[0]?.finish_reason].filter(Boolean)), + coordinate_space: "0-1 fractions", + error: null, + }; + } catch (err) { + return smokeError(err, { tool_name: "function_tools" }); + } +} + +function compareMoonshotCandidates(a: ModelResult, b: ModelResult): number { + if (a.id === "kimi-k3") return -1; + if (b.id === "kimi-k3") return 1; + return String(b.created_at ?? "").localeCompare(String(a.created_at ?? "")); +} + function compareXaiCandidates(a: ModelResult, b: ModelResult): number { if (a.id === "grok-4.5") return -1; if (b.id === "grok-4.5") return 1; @@ -839,7 +932,7 @@ const TZAFON_FUNCTION_TOOLS = [ ]; async function annotateCuaSupport(provider: Provider, models: ModelResult[]): Promise { - const piProvider = provider === "gemini" ? "google" : provider; + const piProvider = provider === "gemini" ? "google" : provider === "moonshot" ? "moonshotai" : provider; const getBuiltinModel = await import("@earendil-works/pi-ai/providers/all").then((mod) => mod.getBuiltinModel).catch(() => undefined); for (const model of models) { const inRegistry = getBuiltinModel ? !!getBuiltinModel(piProvider as never, model.id as never) : false; diff --git a/.agents/skills/update-models/reference/provider-doc-drift.ts b/.agents/skills/update-models/reference/provider-doc-drift.ts index 46b153c0..95ea4be4 100644 --- a/.agents/skills/update-models/reference/provider-doc-drift.ts +++ b/.agents/skills/update-models/reference/provider-doc-drift.ts @@ -2,7 +2,7 @@ import { readFile, writeFile } from "node:fs/promises"; import process from "node:process"; -type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "moonshot" | "yutori"; interface Args { examples: string; @@ -40,6 +40,11 @@ const DOCS: Record = { "https://docs.x.ai/developers/tools/function-calling", "https://docs.x.ai/developers/model-capabilities/images/understanding", ], + moonshot: [ + "https://www.kimi.com/blog/kimi-k3", + "https://platform.kimi.ai/docs/api/tool-use", + "https://platform.kimi.ai/docs/guide/use-kimi-vision-model", + ], yutori: [ "https://docs.yutori.com/reference/navigator", "https://docs.yutori.com/reference/n1", @@ -54,6 +59,7 @@ const LOCAL_FILES: Record = { gemini: "packages/ai/src/providers/gemini/index.ts", meta: "packages/ai/src/providers/meta/index.ts", xai: "packages/ai/src/providers/xai/index.ts", + moonshot: "packages/ai/src/providers/moonshot/index.ts", yutori: "packages/ai/src/providers/yutori/actions.ts", }; @@ -63,6 +69,7 @@ const ACTION_REGEXES: Record = { gemini: /\b(open_web_browser|open_web|wait_5_seconds|go_back|go_forward|search|navigate|click_at|hover_at|type_text_at|key_combination|scroll_document|scroll_at|drag_and_drop)\b/g, meta: /\b(click|double_click|mouse_down|mouse_up|scroll|type|wait|keypress|drag|move|screenshot|goto|back|forward|url|cursor_position|left_click|right_click|middle_click|triple_click|left_click_drag|mouse_move|key|hold_key|left_mouse_down|left_mouse_up)\b/g, xai: /\b(click|double_click|mouse_down|mouse_up|scroll|type|wait|keypress|drag|move|screenshot|goto|back|forward|url|cursor_position)\b/g, + moonshot: /\b(click|double_click|mouse_down|mouse_up|scroll|type|wait|keypress|drag|move|screenshot|goto|back|forward|url|cursor_position)\b/g, yutori: /\b(left_click|double_click|triple_click|right_click|scroll|type|key_press|hover|drag|wait|refresh|go_back|go_forward|goto_url|mouse_move|middle_click|mouse_down|mouse_up|hold_key|extract_elements|find|set_element_value|execute_js)\b/g, }; @@ -137,8 +144,8 @@ async function checkProvider(provider: Provider, examples: any): Promise models.getModels(provider), getModel: (provider, id) => models.getModel(provider, id), refresh: (provider) => models.refresh(provider), - getAuth: (model) => models.getAuth(model), + getAuth: (input, overrides) => models.getAuth(input as never, overrides), + checkAuth: (providerId) => models.checkAuth(providerId), + getAvailable: (providerId) => models.getAvailable(providerId), + login: (providerId, type, interaction) => models.login(providerId, type, interaction), + logout: (providerId) => models.logout(providerId), stream, complete, streamSimple, diff --git a/packages/agent/src/provider-retry.ts b/packages/agent/src/provider-retry.ts index 3bfa848d..bfb27f19 100644 --- a/packages/agent/src/provider-retry.ts +++ b/packages/agent/src/provider-retry.ts @@ -88,7 +88,11 @@ export function withProviderRetryModels(models: Models, policy: ResolvedRetryPol getModels: (provider) => models.getModels(provider), getModel: (provider, id) => models.getModel(provider, id), refresh: (provider) => models.refresh(provider), - getAuth: (model) => models.getAuth(model), + getAuth: (input, overrides) => models.getAuth(input as never, overrides), + checkAuth: (providerId) => models.checkAuth(providerId), + getAvailable: (providerId) => models.getAvailable(providerId), + login: (providerId, type, interaction) => models.login(providerId, type, interaction), + logout: (providerId) => models.logout(providerId), stream: (model, context, options) => models.stream(model, context, options), complete: (model, context, options) => models.complete(model, context, options), streamSimple, diff --git a/packages/agent/test/e2e.live.test.ts b/packages/agent/test/e2e.live.test.ts index b3fe4953..2b51c6ca 100644 --- a/packages/agent/test/e2e.live.test.ts +++ b/packages/agent/test/e2e.live.test.ts @@ -22,6 +22,7 @@ type ProviderCase = { | "google:gemini-3-flash-preview" | "meta:muse-spark-1.1" | "xai:grok-4.5" + | "moonshotai:kimi-k3" | "tzafon:tzafon.northstar-cua-fast" | "yutori:n1.5-latest"; prompt: string; @@ -107,6 +108,19 @@ const cases: ProviderCase[] = [ expectToolCalls: true, timeoutMs: 180_000, }, + { + name: "moonshotai", + apiKeyEnvVar: "MOONSHOT_API_KEY", + modelRef: "moonshotai:kimi-k3", + prompt: [ + "Use the tool named `screenshot` exactly once to inspect the browser.", + "Pass empty arguments (`{}`).", + "Do not call any other tools.", + "After the tool result, provide a one-sentence summary.", + ].join("\n"), + expectToolCalls: true, + timeoutMs: 180_000, + }, { name: "tzafon", apiKeyEnvVar: "TZAFON_API_KEY", diff --git a/packages/ai/README.md b/packages/ai/README.md index 219df162..6b630d18 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -22,6 +22,7 @@ check these environment variables, in order: | `google` | `GOOGLE_API_KEY`, `GEMINI_API_KEY` | | `meta` | `META_API_KEY` | | `xai` | `XAI_API_KEY` | +| `moonshotai` | `MOONSHOT_API_KEY` | | `tzafon` | `TZAFON_API_KEY` | | `yutori` | `YUTORI_API_KEY` | @@ -214,6 +215,7 @@ getCuaModel("anthropic:claude-opus-4-7"); getCuaModel("google:gemini-3-flash-preview"); getCuaModel("meta:muse-spark-1.1"); getCuaModel("xai:grok-4.5"); +getCuaModel("moonshotai:kimi-k3"); // "moonshot:" is accepted as an alias getCuaModel("tzafon:tzafon.northstar-cua-fast"); getCuaModel("yutori:n1.5-latest"); ``` @@ -230,7 +232,7 @@ list of CUA-supporting models per provider. `CuaProvider` is the string union of provider IDs this package targets: ```ts -type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "tzafon" | "yutori"; +type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "moonshotai" | "tzafon" | "yutori"; ``` The IDs match pi-ai's `Model.provider` values exactly. `providerForModel(model)` @@ -377,6 +379,7 @@ Per-provider canonical action subsets (each namespace exports its list as | `gemini` | all 16 | | `meta` | all 16 | | `xai` | all 16 | +| `moonshot` | all 16 | | `tzafon` | all 16 (replaced on the wire by Tzafon's native `computer_use` tool) | | `yutori` | 13 — everything except `screenshot`, `url`, `cursor_position` (local mirrors only) | @@ -413,6 +416,7 @@ Current coordinate contracts: - `gemini`: normalized coordinates in the 0-999 range ([source](https://ai.google.dev/gemini-api/docs/computer-use)) - `meta`: normalized coordinates in the 0-1000 range ([source](https://dev.meta.ai/docs/getting-started/cookbook/computer-use-macos)) - `xai`: CUA-defined normalized coordinates in the 0-1000 range; Grok exposes image input and function calling rather than a native coordinate protocol ([source](https://docs.x.ai/developers/grok-4-5)) +- `moonshot`: CUA-defined normalized coordinates as 0-1 fractions; Kimi's visual grounding natively emits width/height fractions and Moonshot documents function calling and image input rather than a coordinate protocol ([source](https://platform.kimi.ai/docs/api/tool-use)) - `yutori`: normalized coordinates in the 0-1000 range ([source](https://docs.yutori.com/reference/navigator), [SDK helper](https://github.com/yutori-ai/yutori-sdk-python/blob/main/yutori/navigator/coordinates.py)) - `tzafon`: normalized coordinates in the 0-999 range ([source](https://docs.lightcone.ai/guides/coordinates/), [model card](https://huggingface.co/Tzafon/Northstar-CUA-Fast)) @@ -490,7 +494,7 @@ type CuaNavigationInput = { ## Provider Namespaces Every provider namespace (`openai`, `anthropic`, `gemini`, `meta`, `xai`, -`tzafon`, `yutori`) follows one convention: +`moonshot`, `tzafon`, `yutori`) follows one convention: - `computerTools(options?)` and `computerToolExecutors(options?)` - `createActionSchema(actions?)` — TypeBox schema for the provider's subset @@ -520,6 +524,9 @@ Provider-specific extras: `streamXaiResponses`, `streamSimpleXaiResponses`, `XaiResponsesOptions`), with serial function tools, encrypted reasoning replay, and `previous_response_id` threading +- `moonshot`: no stream adapter — Kimi flows through pi's builtin + `openai-completions` transport; the provider module's payload middleware + disables parallel tool calls - `tzafon`: the `tzafon-responses` stream adapter (`TZAFON_RESPONSES_API`, `streamTzafonResponses`, `streamSimpleTzafonResponses`, `TzafonResponsesOptions` with `keepToolNames`), `tzafonComputerUseOnPayload` diff --git a/packages/ai/docs/supported-models.md b/packages/ai/docs/supported-models.md index 2151cc7f..b7be7aab 100644 --- a/packages/ai/docs/supported-models.md +++ b/packages/ai/docs/supported-models.md @@ -88,6 +88,25 @@ Grok 4.5 model metadata. Source: [Grok 4.5 docs](https://docs.x.ai/developers/grok-4-5), [function calling](https://docs.x.ai/developers/tools/function-calling), and [image understanding](https://docs.x.ai/developers/model-capabilities/images/understanding). +## `moonshotai` + +Coordinates: normalized 0–1 fractions (CUA function-tool contract) + +Model refs use the `moonshotai:` prefix; `moonshot:` is accepted as an alias. + +Exact IDs: + +- `kimi-k3` + +Kimi K3 uses Moonshot's OpenAI-compatible chat completions API with screenshot +input and ordinary function tools. Moonshot does not define a native computer +tool or coordinate protocol; Kimi's visual grounding emits width/height +fractions from 0 to 1, so CUA declares that contract and scales to viewport +pixels at execution. K3 launched with max-only thinking effort — other levels +are clamped away until Moonshot ships them. + +Source: [Kimi K3 announcement](https://www.kimi.com/blog/kimi-k3), [tool use](https://platform.kimi.ai/docs/api/tool-use), and [vision input](https://platform.kimi.ai/docs/guide/use-kimi-vision-model). + ## `tzafon` Coordinates: normalized 0–999 diff --git a/packages/ai/examples/quickstart.ts b/packages/ai/examples/quickstart.ts index 0ac928df..7423a2ae 100644 --- a/packages/ai/examples/quickstart.ts +++ b/packages/ai/examples/quickstart.ts @@ -12,6 +12,7 @@ import { // google:gemini-3-flash-preview GOOGLE_API_KEY // meta:muse-spark-1.1 META_API_KEY // xai:grok-4.5 XAI_API_KEY +// moonshotai:kimi-k3 MOONSHOT_API_KEY // tzafon:tzafon.northstar-cua-fast TZAFON_API_KEY // yutori:n1.5-latest YUTORI_API_KEY const modelRef = (process.env.CUA_MODEL ?? "openai:gpt-5.5") as CuaModelRef; diff --git a/packages/ai/package.json b/packages/ai/package.json index e349b655..8adec3e3 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -41,7 +41,7 @@ "test:integration": "vitest --run --config vitest.integration.config.ts" }, "dependencies": { - "@earendil-works/pi-ai": "0.80.6", + "@earendil-works/pi-ai": "0.80.10", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, diff --git a/packages/ai/src/api-keys.ts b/packages/ai/src/api-keys.ts index 443ff13a..41db3109 100644 --- a/packages/ai/src/api-keys.ts +++ b/packages/ai/src/api-keys.ts @@ -14,19 +14,24 @@ const CUA_PROVIDER_API_KEY_ENV_VARS: Record = { google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"], meta: ["META_API_KEY"], xai: ["XAI_API_KEY"], + moonshotai: ["MOONSHOT_API_KEY"], tzafon: ["TZAFON_API_KEY"], yutori: ["YUTORI_API_KEY"], }; /** * List the environment variables checked for a provider's API key, in - * precedence order. Accepts `"gemini"` as an alias for `"google"`; returns an - * empty list for unknown providers. + * precedence order. Accepts `"gemini"` as an alias for `"google"` and + * `"moonshot"` as an alias for `"moonshotai"`; returns an empty list for + * unknown providers. */ export function cuaApiKeyEnvVarsForProvider(provider: string): readonly string[] { if (provider === "gemini") { return CUA_PROVIDER_API_KEY_ENV_VARS.google; } + if (provider === "moonshot") { + return CUA_PROVIDER_API_KEY_ENV_VARS.moonshotai; + } return CUA_PROVIDER_API_KEY_ENV_VARS[provider as keyof typeof CUA_PROVIDER_API_KEY_ENV_VARS] ?? []; } diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index 61864104..eafd77f7 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -8,6 +8,7 @@ export * from "./providers/common"; export * as anthropic from "./providers/anthropic/index"; export * as gemini from "./providers/gemini/index"; export * as meta from "./providers/meta/index"; +export * as moonshot from "./providers/moonshot/index"; export * as openai from "./providers/openai/index"; export * as tzafon from "./providers/tzafon/index"; export * as xai from "./providers/xai/index"; diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index 320569dd..194a1fef 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -5,7 +5,7 @@ import { OPENAI_CUA_RESPONSES_API } from "./providers/openai/provider"; import { XAI_CUA_RESPONSES_API } from "./providers/xai/provider"; /** Providers with curated computer-use model support. */ -export type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "tzafon" | "yutori"; +export type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "moonshotai" | "tzafon" | "yutori"; /** Provider-qualified model reference, e.g. `"openai:gpt-5.5"` or `"google:gemini-3-flash-preview"`. */ export type CuaModelRef = `${CuaProvider}:${string}`; @@ -22,7 +22,7 @@ export interface CuaModelInfo { } /** All providers this package curates computer-use models for. */ -export const CUA_PROVIDERS: readonly CuaProvider[] = ["openai", "anthropic", "google", "meta", "xai", "tzafon", "yutori"]; +export const CUA_PROVIDERS: readonly CuaProvider[] = ["openai", "anthropic", "google", "meta", "xai", "moonshotai", "tzafon", "yutori"]; /** * How a {@link CuaModelAnnotation} matches model ids. @@ -86,6 +86,12 @@ export const CUA_MODEL_ANNOTATIONS: Record[]> = { openai: [], anthropic: [], google: [], - // pi-ai 0.80.6 predates Meta's models.dev catalog entry. + // pi-ai 0.80.10 still lacks Meta's models.dev catalog entry. meta: [cuaModel("meta", "muse-spark-1.1", "Muse Spark 1.1")], xai: [], + moonshotai: [], tzafon: [ cuaModel("tzafon", "tzafon.northstar-cua-fast", "Tzafon Northstar CUA Fast"), cuaModel("tzafon", "tzafon.northstar-cua-fast-1.6", "Tzafon Northstar CUA Fast 1.6"), @@ -133,8 +140,9 @@ export function cuaOverrideModels(provider: CuaProvider): readonly Model[] * Split a provider-qualified ref like `"openai:gpt-5.5"` into its parts. * * `"gemini:"` is accepted as an alias for the canonical `"google:"` prefix - * and normalizes to provider `"google"`. Throws when the ref is unqualified - * or names an unsupported provider. + * and normalizes to provider `"google"`; `"moonshot:"` likewise normalizes + * to `"moonshotai"`. Throws when the ref is unqualified or names an + * unsupported provider. */ export function parseCuaModelRef(ref: string): { provider: CuaProvider; model: string } { const idx = ref.indexOf(":"); @@ -142,7 +150,7 @@ export function parseCuaModelRef(ref: string): { provider: CuaProvider; model: s throw new Error(`CUA model ref must be provider-qualified as ":"; got "${ref}"`); } const prefix = ref.slice(0, idx); - const provider = prefix === "gemini" ? "google" : prefix; + const provider = prefix === "gemini" ? "google" : prefix === "moonshot" ? "moonshotai" : prefix; const model = ref.slice(idx + 1); if (!isCuaProvider(provider)) { throw new Error(`unsupported CUA provider "${prefix}" (expected one of: ${CUA_PROVIDERS.join(", ")})`); @@ -223,7 +231,7 @@ export function routeCuaApi(model: Model): Model { ...model.cost, tiers: [{ inputTokensAbove: 200_000, input: 4, output: 12, cacheRead: 1, cacheWrite: 0 }], }, - compat: { supportsDeveloperRole: false, sendSessionIdHeader: false, supportsLongCacheRetention: false }, + compat: { supportsDeveloperRole: false, sessionAffinityFormat: "openai-nosession", supportsLongCacheRetention: false }, }; } return model; @@ -273,7 +281,7 @@ function isCuaFamilyMatch(id: string, family: string): boolean { .every((segment) => /^\d+$/.test(segment)); } -function cuaModel(provider: Exclude, id: string, name: string): Model { +function cuaModel(provider: Exclude, id: string, name: string): Model { const base = { id, name, @@ -301,7 +309,7 @@ function cuaModel(provider: Exclude, id: string, name: strin cost: { input: 1.25, output: 4.25, cacheRead: 0.15, cacheWrite: 0 }, contextWindow: 1_048_576, maxTokens: 128_000, - compat: { supportsDeveloperRole: true, sendSessionIdHeader: false, supportsLongCacheRetention: true }, + compat: { supportsDeveloperRole: true, sessionAffinityFormat: "openai-nosession", supportsLongCacheRetention: true }, } as Model; case "tzafon": return { ...base, api: "tzafon-responses", baseUrl: "https://api.lightcone.ai", contextWindow: 128_000, maxTokens: 4_096 } as Model; diff --git a/packages/ai/src/providers.ts b/packages/ai/src/providers.ts index 4eafc3a8..0dc3904e 100644 --- a/packages/ai/src/providers.ts +++ b/packages/ai/src/providers.ts @@ -36,6 +36,8 @@ import { streamSimpleYutori, streamYutori, YUTORI_CHAT_COMPLETIONS_API } from ". * (pi's builtin only reads `GEMINI_API_KEY`). * - `xai` intercepts `xai-cua-responses` so Grok can use stateful Responses * tool loops while preserving pi's builtin xAI auth and catalog. + * - `moonshotai` is pi's builtin provider untouched: Kimi streams through the + * plain OpenAI-compatible chat completions transport with `MOONSHOT_API_KEY`. * - `meta`, `tzafon`, and `yutori` are CUA-only providers pi does not ship. * * Each call returns an independent collection; register additional providers diff --git a/packages/ai/src/providers/moonshot/index.ts b/packages/ai/src/providers/moonshot/index.ts new file mode 100644 index 00000000..fe5b37d1 --- /dev/null +++ b/packages/ai/src/providers/moonshot/index.ts @@ -0,0 +1,63 @@ +import { assertComputerModeOnly, computerToolExecutors, computerTools } from "../common"; +import type { ComputerToolCoordinateSystem, ComputerToolsOptions, CuaPayloadHook, CuaProviderModule } from "../common"; + +export { + CUA_ACTION_TYPES as MOONSHOT_CUA_ACTION_TYPES, + computerToolExecutors, + computerTools, + createCuaActionSchema as createActionSchema, +} from "../common"; +export type { + CuaAction as MoonshotAction, + ComputerToolsOptions, +} from "../common"; + +/** + * Instructions for driving Kimi through CUA's custom function tools. + * + * Moonshot documents {@link https://platform.moonshot.ai/docs/guide/use-kimi-vision-model | image input} + * and {@link https://platform.moonshot.ai/docs/api/tool-use | tool calling} on its + * OpenAI-compatible API, but does not define a native computer tool or + * coordinate space. + */ +export const MOONSHOT_COMPUTER_INSTRUCTIONS = `You control a Kernel cloud browser through individual browser tools. Coordinates are fractions of the most recent screenshot, normalized from 0 to 1, with (0, 0) at the top left and (1, 1) at the bottom right. Base each action on the latest observed state and request a screenshot when you need a fresh view.`; + +/** + * Build Moonshot's default system prompt for computer-use runs. + */ +export function buildMoonshotSystemPrompt(opts: { suffix?: string } = {}): string { + return [MOONSHOT_COMPUTER_INSTRUCTIONS, opts.suffix].filter(Boolean).join("\n\n"); +} + +/** + * Return the normalized coordinate contract CUA supplies to Kimi function tools. + * + * Kimi's visual grounding natively emits width/height fractions: probed with + * real screenshots, Kimi answers 0-1 fractional coordinates regardless of + * whether the prompt or tool schema asks for pixels or a 0-1000 grid (e.g. + * (0.927, 0.293) for a link whose center is at that exact fraction). CUA + * declares the contract the model actually honors and scales at execution. + */ +export function coordinateSystem(): ComputerToolCoordinateSystem { + return { type: "normalized", range: [0, 1] }; +} + +// Browser actions mutate shared state, so ask the API for serial tool calls. +const onPayload: CuaPayloadHook = (payload) => { + if (!payload || typeof payload !== "object") return undefined; + return { ...(payload as Record), parallel_tool_calls: false }; +}; + +export const providerModule = { + toolDefinitions: (options?: ComputerToolsOptions) => { + assertComputerModeOnly("moonshotai", options); + return computerTools(options); + }, + toolExecutors: (options?: ComputerToolsOptions) => { + assertComputerModeOnly("moonshotai", options); + return computerToolExecutors(options); + }, + coordinateSystem, + buildSystemPrompt: buildMoonshotSystemPrompt, + onPayload, +} satisfies CuaProviderModule; diff --git a/packages/ai/src/runtime-spec.ts b/packages/ai/src/runtime-spec.ts index 25742ae1..2777ac81 100644 --- a/packages/ai/src/runtime-spec.ts +++ b/packages/ai/src/runtime-spec.ts @@ -6,6 +6,7 @@ import { providerModule as anthropic } from "./providers/anthropic/index"; import { createNativeToolOnPayload, nativeApiForToolType, nativeToolExecutors } from "./providers/anthropic/native"; import { providerModule as gemini } from "./providers/gemini/index"; import { providerModule as meta } from "./providers/meta/index"; +import { providerModule as moonshot } from "./providers/moonshot/index"; import { providerModule as openai } from "./providers/openai/index"; import { providerModule as tzafon } from "./providers/tzafon/index"; import { providerModule as xai } from "./providers/xai/index"; @@ -24,6 +25,7 @@ const PROVIDERS = { google: gemini, meta, xai, + moonshotai: moonshot, tzafon, yutori, } satisfies Record; diff --git a/packages/ai/test/api-keys.test.ts b/packages/ai/test/api-keys.test.ts index c40bf3e3..bdbd7ec9 100644 --- a/packages/ai/test/api-keys.test.ts +++ b/packages/ai/test/api-keys.test.ts @@ -14,6 +14,7 @@ const ENV_KEYS = [ "GEMINI_API_KEY", "META_API_KEY", "XAI_API_KEY", + "MOONSHOT_API_KEY", "TZAFON_API_KEY", "YUTORI_API_KEY", ] as const; @@ -35,6 +36,8 @@ describe("cua api key helpers", () => { expect(cuaApiKeyEnvVarsForProvider("gemini")).toEqual(["GOOGLE_API_KEY", "GEMINI_API_KEY"]); expect(cuaApiKeyEnvVarsForProvider("meta")).toEqual(["META_API_KEY"]); expect(cuaApiKeyEnvVarsForProvider("xai")).toEqual(["XAI_API_KEY"]); + expect(cuaApiKeyEnvVarsForProvider("moonshotai")).toEqual(["MOONSHOT_API_KEY"]); + expect(cuaApiKeyEnvVarsForProvider("moonshot")).toEqual(["MOONSHOT_API_KEY"]); expect(cuaApiKeyEnvVarsForProvider("unknown")).toEqual([]); }); @@ -53,6 +56,8 @@ describe("cua api key helpers", () => { expect(getCuaEnvApiKeyForModel("meta:muse-spark-1.1")).toBe("meta"); process.env.XAI_API_KEY = "xai"; expect(getCuaEnvApiKeyForModel("xai:grok-4.5")).toBe("xai"); + process.env.MOONSHOT_API_KEY = "moonshot"; + expect(getCuaEnvApiKeyForModel("moonshotai:kimi-k3")).toBe("moonshot"); }); it("throws readable errors when missing", () => { diff --git a/packages/ai/test/computer-tool.integration.test.ts b/packages/ai/test/computer-tool.integration.test.ts index 88b63adb..96b8ca79 100644 --- a/packages/ai/test/computer-tool.integration.test.ts +++ b/packages/ai/test/computer-tool.integration.test.ts @@ -13,6 +13,7 @@ import { getCuaEnvApiKey, getCuaModel, meta, + moonshot, openai, tzafon, xai, @@ -74,6 +75,17 @@ const cases: ProviderCase[] = [ systemPrompt: "Coordinates are normalized from 0 to 1000 relative to the screenshot.", extraOptions: { reasoningEffort: "off" }, }, + { + provider: "moonshotai", + envVar: "MOONSHOT_API_KEY", + modelRef: "moonshotai:kimi-k3", + tools: () => moonshot.computerTools({ actions: ["click"] }), + coordinateRange: [0, 1], + requireToolCalls: true, + systemPrompt: "Coordinates are fractions of the screenshot, normalized from 0 to 1.", + // K3 launched max-thinking-only; leave headroom for reasoning tokens. + extraOptions: { maxTokens: 8192 }, + }, { provider: "tzafon", envVar: "TZAFON_API_KEY", diff --git a/packages/ai/test/models.test.ts b/packages/ai/test/models.test.ts index 0f9e8f08..30865ed6 100644 --- a/packages/ai/test/models.test.ts +++ b/packages/ai/test/models.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest"; import { CUA_MODEL_ANNOTATIONS, CUA_PROVIDERS, + type CuaModelRef, cuaOverrideModels, findCuaAnnotation, formatCuaModelRef, @@ -27,7 +28,7 @@ describe("CUA model refs", () => { it("names the valid providers in the unsupported-provider error", () => { expect(() => parseCuaModelRef("bogus:model")).toThrow( - 'unsupported CUA provider "bogus" (expected one of: openai, anthropic, google, meta, xai, tzafon, yutori)', + 'unsupported CUA provider "bogus" (expected one of: openai, anthropic, google, meta, xai, moonshotai, tzafon, yutori)', ); }); @@ -77,6 +78,25 @@ describe("CUA model refs", () => { ]); }); + it("uses pi-ai's Kimi catalog entry without CUA routing overrides", () => { + expect(cuaOverrideModels("moonshotai")).toEqual([]); + const kimi = getCuaModel("moonshotai:kimi-k3"); + expect(kimi.provider).toBe("moonshotai"); + expect(kimi.api).toBe("openai-completions"); + expect(kimi.baseUrl).toBe("https://api.moonshot.ai/v1"); + expect(kimi.input).toContain("image"); + expect(kimi.contextWindow).toBe(1_048_576); + expect(kimi.maxTokens).toBe(131_072); + // K3 launched with max-only thinking effort; pi-ai clamps the rest away. + expect(kimi.thinkingLevelMap?.max).toBe("max"); + expect(kimi.thinkingLevelMap?.high).toBeNull(); + }); + + it("accepts the moonshot: alias for moonshotai refs", () => { + expect(parseCuaModelRef("moonshot:kimi-k3")).toEqual({ provider: "moonshotai", model: "kimi-k3" }); + expect(getCuaModel("moonshot:kimi-k3" as CuaModelRef).id).toBe("kimi-k3"); + }); + it("loads supported custom provider models without explicit registration", () => { expect(getCuaModel("tzafon:tzafon.northstar-cua-fast").api).toBe("tzafon-responses"); expect(getCuaModel("yutori:n1.5-latest").api).toBe("yutori-chat-completions"); @@ -146,6 +166,9 @@ describe("CUA support annotations", () => { expect(findCuaAnnotation("xai", "grok-4.5")).toBeDefined(); expect(findCuaAnnotation("xai", "grok-4.5-latest")).toBeUndefined(); expect(findCuaAnnotation("xai", "grok-4.3")).toBeUndefined(); + expect(findCuaAnnotation("moonshotai", "kimi-k3")).toBeDefined(); + expect(findCuaAnnotation("moonshotai", "kimi-k2.5")).toBeUndefined(); + expect(findCuaAnnotation("moonshotai", "kimi-latest")).toBeUndefined(); expect(findCuaAnnotation("google", "gemini-3.1-flash-lite")).toBeDefined(); expect(findCuaAnnotation("yutori", "n1.5-latest")).toBeDefined(); expect(findCuaAnnotation("tzafon", "tzafon.northstar-cua-fast")).toBeDefined(); diff --git a/packages/ai/test/provider-module.test.ts b/packages/ai/test/provider-module.test.ts index b92a0f00..d9b33071 100644 --- a/packages/ai/test/provider-module.test.ts +++ b/packages/ai/test/provider-module.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { anthropic, CUA_COMPUTER_ACTION_TYPES, CUA_PROVIDERS, type CuaProvider, gemini, meta, openai, tzafon, xai, yutori } from "../src/index"; +import { anthropic, CUA_COMPUTER_ACTION_TYPES, CUA_PROVIDERS, type CuaProvider, gemini, meta, moonshot, openai, tzafon, xai, yutori } from "../src/index"; import type { CuaProviderModule } from "../src/providers/common"; const MODULES: Record = { @@ -8,6 +8,7 @@ const MODULES: Record = { google: gemini, meta, xai, + moonshotai: moonshot, tzafon, yutori, }; @@ -18,6 +19,7 @@ const NAMESPACES: Record; pref google: { namespace: gemini, prefix: "GEMINI" }, meta: { namespace: meta, prefix: "META" }, xai: { namespace: xai, prefix: "XAI" }, + moonshotai: { namespace: moonshot, prefix: "MOONSHOT" }, tzafon: { namespace: tzafon, prefix: "TZAFON" }, yutori: { namespace: yutori, prefix: "YUTORI" }, }; @@ -59,6 +61,7 @@ describe("provider modules satisfy the uniform contract", () => { it.each([ ["meta", meta.providerModule], ["xai", xai.providerModule], + ["moonshotai", moonshot.providerModule], ])("%s restricts its normalized coordinate contract to computer mode", (_provider, providerModule) => { expect(() => providerModule.toolDefinitions({ mode: "browser" })).toThrow(/computer only/); expect(() => providerModule.toolDefinitions({ mode: "hybrid" })).toThrow(/computer only/); diff --git a/packages/ai/test/providers.test.ts b/packages/ai/test/providers.test.ts index 4afd551c..8c261a5e 100644 --- a/packages/ai/test/providers.test.ts +++ b/packages/ai/test/providers.test.ts @@ -8,7 +8,7 @@ const YUTORI_CHAT_COMPLETIONS_API = yutori.YUTORI_CHAT_COMPLETIONS_API; describe("createCuaModels", () => { it("registers the CUA-only providers alongside pi's builtins", () => { const models = createCuaModels(); - for (const id of ["openai", "anthropic", "google", "meta", "xai", "tzafon", "yutori"]) { + for (const id of ["openai", "anthropic", "google", "meta", "xai", "moonshotai", "tzafon", "yutori"]) { const provider = models.getProvider(id); expect(provider, id).toBeDefined(); expect(provider?.stream).toBeTypeOf("function"); @@ -19,7 +19,8 @@ describe("createCuaModels", () => { it("lists CUA provider catalogs", () => { const models = createCuaModels(); expect(models.getModel("meta", "muse-spark-1.1")?.api).toBe("meta-responses"); - expect(models.getModel("xai", "grok-4.5")?.api).toBe("openai-completions"); + expect(models.getModel("xai", "grok-4.5")?.api).toBe("openai-responses"); + expect(models.getModel("moonshotai", "kimi-k3")?.api).toBe("openai-completions"); const tzafonIds = models.getModels("tzafon").map((m) => m.id); expect(tzafonIds).toContain("tzafon.northstar-cua-fast"); const yutoriIds = models.getModels("yutori").map((m) => m.id); diff --git a/packages/ai/test/runtime-spec.test.ts b/packages/ai/test/runtime-spec.test.ts index b5c8e75c..459ea6a9 100644 --- a/packages/ai/test/runtime-spec.test.ts +++ b/packages/ai/test/runtime-spec.test.ts @@ -45,6 +45,16 @@ describe("resolveCuaRuntimeSpec", () => { expect(openaiSpec.onPayload).toBeUndefined(); expect(xaiSpec.onPayload).toBeUndefined(); expect(anthropicSpec.onPayload).toBeTypeOf("function"); + // Kimi streams through pi's plain chat-completions transport, so its + // serial tool-call constraint rides on runtime payload middleware. + const moonshotSpec = resolveCuaRuntimeSpec("moonshotai:kimi-k3"); + expect(moonshotSpec.onPayload).toBeTypeOf("function"); + }); + + it("disables parallel tool calls on Kimi payloads", async () => { + const spec = resolveCuaRuntimeSpec("moonshotai:kimi-k3"); + const payload = await spec.onPayload?.({ model: "kimi-k3", messages: [] }, spec.model); + expect(payload).toMatchObject({ model: "kimi-k3", parallel_tool_calls: false }); }); it("routes concrete Responses models to provider-specific APIs", () => { diff --git a/packages/cli/README.md b/packages/cli/README.md index 4e0d3bd6..95d9db13 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -47,6 +47,7 @@ cua --print --model anthropic:claude-opus-4-7 "..." cua --print --model google:gemini-3-flash-preview "..." cua --print --model meta:muse-spark-1.1 "..." cua --print --model xai:grok-4.5 "..." +cua --print --model moonshotai:kimi-k3 "..." cua --print --model yutori:n1.5-latest "..." # Named sessions (browser stays alive across calls): @@ -70,7 +71,8 @@ cua --session abc12345 # by id prefix Run `cua models` to list every supported `-m` / `--model` value and the provider it routes to. Filter by provider with `cua models -p openai`, `cua models -p anthropic`, `cua models -p google` (alias: `gemini`), -`cua models -p meta`, `cua models -p xai`, or `cua models -p yutori`. +`cua models -p meta`, `cua models -p xai`, `cua models -p moonshotai` +(alias: `moonshot`), or `cua models -p yutori`. `-m` / `--model` accepts a provider-qualified `provider:model` ref (e.g. `openai:gpt-5.5`) or a bare model id when it matches exactly one catalog @@ -89,6 +91,7 @@ Configuration is by environment variable. There is no config file. | `GEMINI_API_KEY` | alias of `GOOGLE_API_KEY` | | `META_API_KEY` | Meta Model API key (required when `-m meta:…`) | | `XAI_API_KEY` | xAI API key (required when `-m xai:…`) | +| `MOONSHOT_API_KEY` | Moonshot AI API key (required when `-m moonshotai:…`) | | `TZAFON_API_KEY` | Tzafon API key (required when `-m tzafon:…`) | | `YUTORI_API_KEY` | Yutori API key (required when `-m yutori:…`) | | `KERNEL_BASE_URL` | override Kernel base URL | @@ -97,12 +100,13 @@ Configuration is by environment variable. There is no config file. | `GOOGLE_BASE_URL` | override Google base URL | | `META_BASE_URL` | override Meta Model API base URL | | `XAI_BASE_URL` | override xAI API base URL | +| `MOONSHOTAI_BASE_URL` | override Moonshot AI base URL | | `TZAFON_BASE_URL` | override Tzafon base URL | | `YUTORI_BASE_URL` | override Yutori base URL | | `XDG_DATA_HOME` | sessions dir base (defaults to `~/.local/share`) | | `CUA_IMAGE_PROTOCOL` | force inline image protocol (`kitty`/`iterm2`/`none`/`auto`) | -Use `--thinking ` (`off | minimal | low | medium | high | xhigh`, +Use `--thinking ` (`off | minimal | low | medium | high | xhigh | max`, default `low`) for providers that support reasoning effort. ## Playwright escape hatch diff --git a/packages/cli/package.json b/packages/cli/package.json index 3eeee438..f9a37d4d 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,8 +34,8 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.6", - "@earendil-works/pi-tui": "0.80.6", + "@earendil-works/pi-coding-agent": "0.80.10", + "@earendil-works/pi-tui": "0.80.10", "@onkernel/cua-agent": "0.6.0", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0" diff --git a/packages/cli/src/cli-harness.ts b/packages/cli/src/cli-harness.ts index a9283522..628cf558 100644 --- a/packages/cli/src/cli-harness.ts +++ b/packages/cli/src/cli-harness.ts @@ -60,7 +60,7 @@ Usage: cua models --json Options: - -p, --provider Filter by provider: openai | anthropic | google | gemini | meta | xai | tzafon | yutori + -p, --provider Filter by provider: openai | anthropic | google | gemini | meta | xai | moonshotai | tzafon | yutori --json Output JSON -h, --help Show this help `; @@ -526,7 +526,7 @@ export function parseNativeTool(raw: string | undefined): CuaNativeToolSpec | un throw new Error(`invalid --native-tool value "${raw}"; expected one of: computer_20260701 | browser_20260701`); } -function mapThinkingLevel(raw: string | undefined): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" { +function mapThinkingLevel(raw: string | undefined): "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max" { const v = (raw ?? "low").trim().toLowerCase(); switch (v) { case "off": @@ -540,12 +540,14 @@ function mapThinkingLevel(raw: string | undefined): "off" | "minimal" | "low" | return "high"; case "xhigh": return "xhigh"; + case "max": + return "max"; case "low": case "": return "low"; default: throw new Error( - `invalid --thinking value "${raw}"; expected one of: off | minimal | low | medium | high | xhigh`, + `invalid --thinking value "${raw}"; expected one of: off | minimal | low | medium | high | xhigh | max`, ); } } diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 2da5b820..41cd1b9d 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -55,9 +55,10 @@ Options: google: google:gemini-3-flash-preview meta: meta:muse-spark-1.1 xai: xai:grok-4.5 + moonshot: moonshotai:kimi-k3 tzafon: tzafon:tzafon.northstar-cua-fast yutori: yutori:n1.5-latest - --thinking Thinking level: off | minimal | low | medium | high | xhigh + --thinking Thinking level: off | minimal | low | medium | high | xhigh | max (default: low; applies to providers that support it) --profile Kernel browser profile to load --proxy Kernel proxy to route the browser through @@ -105,6 +106,7 @@ Environment: GEMINI_API_KEY Alias for GOOGLE_API_KEY META_API_KEY Meta Model API key (required when -m meta:…) XAI_API_KEY xAI API key (required when -m xai:…) + MOONSHOT_API_KEY Moonshot AI API key (required when -m moonshotai:…) TZAFON_API_KEY Tzafon API key (required when -m tzafon:…) YUTORI_API_KEY Yutori API key (required when -m yutori:…) KERNEL_BASE_URL Override Kernel base URL @@ -113,6 +115,7 @@ Environment: GOOGLE_BASE_URL Override Google base URL META_BASE_URL Override Meta Model API base URL XAI_BASE_URL Override xAI API base URL + MOONSHOTAI_BASE_URL Override Moonshot AI base URL TZAFON_BASE_URL Override Tzafon base URL YUTORI_BASE_URL Override Yutori base URL XDG_DATA_HOME Sessions are stored under \$XDG_DATA_HOME/cua/sessions @@ -204,10 +207,10 @@ function parseCliArgs(argv: string[]): CliFlags { const maxSteps = maxStepsRaw ? Number(maxStepsRaw) : undefined; const thinkingRaw = parsed.values.thinking as string | undefined; if (thinkingRaw !== undefined) { - const allowed = new Set(["off", "none", "minimal", "low", "medium", "high", "xhigh"]); + const allowed = new Set(["off", "none", "minimal", "low", "medium", "high", "xhigh", "max"]); if (!allowed.has(thinkingRaw.trim().toLowerCase())) { throw new Error( - `invalid --thinking value "${thinkingRaw}"; expected one of: off | minimal | low | medium | high | xhigh`, + `invalid --thinking value "${thinkingRaw}"; expected one of: off | minimal | low | medium | high | xhigh | max`, ); } } diff --git a/packages/cli/src/harness-models.ts b/packages/cli/src/harness-models.ts index 68d358af..18f1d696 100644 --- a/packages/cli/src/harness-models.ts +++ b/packages/cli/src/harness-models.ts @@ -39,12 +39,13 @@ export function resolveCuaModelRef(input: string | undefined): CuaModelRef { } /** - * List supported models, optionally filtered to a provider. Accepts either - * the canonical `"google"` or the CLI-friendly `"gemini"` alias. + * List supported models, optionally filtered to a provider. Accepts the + * canonical `"google"`/`"moonshotai"` ids or the CLI-friendly `"gemini"`/ + * `"moonshot"` aliases. */ export function listSupportedModels(provider?: string): CuaModelInfo[] { if (!provider) return listCuaModels(); - const normalized = provider === "gemini" ? "google" : provider; + const normalized = provider === "gemini" ? "google" : provider === "moonshot" ? "moonshotai" : provider; if (!isCuaProvider(normalized)) { throw new Error(`unknown provider "${provider}"`); } diff --git a/packages/cli/test/cli-executor.test.ts b/packages/cli/test/cli-executor.test.ts index b47e39da..ef34fd62 100644 --- a/packages/cli/test/cli-executor.test.ts +++ b/packages/cli/test/cli-executor.test.ts @@ -24,6 +24,7 @@ const PROVIDER_ENV_KEYS = [ "GEMINI_API_KEY", "META_API_KEY", "XAI_API_KEY", + "MOONSHOT_API_KEY", "TZAFON_API_KEY", "YUTORI_API_KEY", ]; diff --git a/packages/cli/test/harness-models.test.ts b/packages/cli/test/harness-models.test.ts index eb7c012a..a9a9cf85 100644 --- a/packages/cli/test/harness-models.test.ts +++ b/packages/cli/test/harness-models.test.ts @@ -11,12 +11,15 @@ describe("resolveCuaModelRef", () => { expect(resolveCuaModelRef("openai:gpt-5.5")).toBe("openai:gpt-5.5"); expect(resolveCuaModelRef("meta:muse-spark-1.1")).toBe("meta:muse-spark-1.1"); expect(resolveCuaModelRef("xai:grok-4.5")).toBe("xai:grok-4.5"); + expect(resolveCuaModelRef("moonshotai:kimi-k3")).toBe("moonshotai:kimi-k3"); + expect(resolveCuaModelRef("moonshot:kimi-k3")).toBe("moonshotai:kimi-k3"); }); it("accepts bare ids when they match exactly one catalog entry", () => { expect(resolveCuaModelRef("gpt-5.5")).toBe("openai:gpt-5.5"); expect(resolveCuaModelRef("muse-spark-1.1")).toBe("meta:muse-spark-1.1"); expect(resolveCuaModelRef("grok-4.5")).toBe("xai:grok-4.5"); + expect(resolveCuaModelRef("kimi-k3")).toBe("moonshotai:kimi-k3"); }); it("throws on unknown bare ids", () => { @@ -26,6 +29,8 @@ describe("resolveCuaModelRef", () => { it("filters custom provider catalogs", () => { expect(listSupportedModels("meta").map((model) => model.ref)).toEqual(["meta:muse-spark-1.1"]); expect(listSupportedModels("xai").map((model) => model.ref)).toEqual(["xai:grok-4.5"]); + expect(listSupportedModels("moonshotai").map((model) => model.ref)).toEqual(["moonshotai:kimi-k3"]); + expect(listSupportedModels("moonshot").map((model) => model.ref)).toEqual(["moonshotai:kimi-k3"]); }); it("treats 'gemini' as an alias for google when filtering", () => { From fa316198a8556c3c730baf873cf32aafaae58b89 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:04:49 +0000 Subject: [PATCH 2/3] Trigger CI for ready-for-review run From 70404a8460b505fd303d72b4a9e3e9b0a13ebb32 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Fri, 17 Jul 2026 18:39:23 +0000 Subject: [PATCH 3/3] Bump cua-ai/cua-agent to 0.7.0 and cua-cli to 0.5.0 for release --- package-lock.json | 12 ++++++------ packages/agent/CHANGELOG.md | 10 ++++++++++ packages/agent/package.json | 4 ++-- packages/ai/CHANGELOG.md | 15 +++++++++++++++ packages/ai/package.json | 2 +- packages/cli/package.json | 6 +++--- 6 files changed, 37 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3418cecf..40eb258e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6070,12 +6070,12 @@ }, "packages/agent": { "name": "@onkernel/cua-agent", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@earendil-works/pi-agent-core": "0.80.10", "@earendil-works/pi-ai": "0.80.10", - "@onkernel/cua-ai": "0.6.0", + "@onkernel/cua-ai": "0.7.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" }, @@ -6086,7 +6086,7 @@ }, "packages/ai": { "name": "@onkernel/cua-ai", - "version": "0.6.0", + "version": "0.7.0", "license": "MIT", "dependencies": { "@earendil-works/pi-ai": "0.80.10", @@ -6100,13 +6100,13 @@ }, "packages/cli": { "name": "@onkernel/cua-cli", - "version": "0.4.0", + "version": "0.5.0", "license": "MIT", "dependencies": { "@earendil-works/pi-coding-agent": "0.80.10", "@earendil-works/pi-tui": "0.80.10", - "@onkernel/cua-agent": "0.6.0", - "@onkernel/cua-ai": "0.6.0", + "@onkernel/cua-agent": "0.7.0", + "@onkernel/cua-ai": "0.7.0", "@onkernel/sdk": "0.49.0" }, "bin": { diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md index 47e12b21..6229b498 100644 --- a/packages/agent/CHANGELOG.md +++ b/packages/agent/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.7.0 - 2026-07-17 + +- `CuaAgent` and `CuaAgentHarness` support Moonshot Kimi K3 + (`moonshotai:kimi-k3`) via `@onkernel/cua-ai` 0.7.0, resolving auth from + `MOONSHOT_API_KEY`. Kimi's fractional coordinates are scaled to viewport + pixels by the existing translator. +- Bumped `@earendil-works/pi-ai` and `@earendil-works/pi-agent-core` to + 0.80.10; the wrapped `Models` collections forward the new + `checkAuth`/`getAvailable`/`login`/`logout` methods. + ## 0.6.0 - 2026-07-10 Adds explicit request-recovery and context-management policies while keeping diff --git a/packages/agent/package.json b/packages/agent/package.json index 6ac07a88..60e4b27a 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-agent", - "version": "0.6.0", + "version": "0.7.0", "description": "Kernel browser computer-use Agent and AgentHarness classes built on pi-agent-core", "license": "MIT", "type": "module", @@ -42,7 +42,7 @@ "dependencies": { "@earendil-works/pi-agent-core": "0.80.10", "@earendil-works/pi-ai": "0.80.10", - "@onkernel/cua-ai": "0.6.0", + "@onkernel/cua-ai": "0.7.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" }, diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index b76207b1..e635e1c9 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +## 0.7.0 - 2026-07-17 + +- Added Moonshot Kimi K3 computer-use support: `moonshotai:kimi-k3` + (`moonshot:` accepted as a ref alias), authenticated with + `MOONSHOT_API_KEY`. Kimi streams through the OpenAI-compatible chat + completions transport with ordinary function tools. +- New `moonshot` provider namespace following the standard conventions + (`computerTools`, `coordinateSystem`, `buildMoonshotSystemPrompt`, + `providerModule`, …). Kimi's coordinate contract is normalized 0–1 + width/height fractions, matching the model's native visual grounding; + payload middleware disables parallel tool calls. +- Bumped `@earendil-works/pi-ai` to 0.80.10 (carries the Kimi K3 registry + entry). Grok 4.5's registry api id is now `openai-responses`; CUA routing + behavior is unchanged. + ## 0.6.0 - 2026-07-10 Breaking: response threading is now configured per request instead of through diff --git a/packages/ai/package.json b/packages/ai/package.json index 8adec3e3..6b299e18 100644 --- a/packages/ai/package.json +++ b/packages/ai/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-ai", - "version": "0.6.0", + "version": "0.7.0", "description": "Kernel-curated computer-use model access built on pi-ai", "license": "MIT", "type": "module", diff --git a/packages/cli/package.json b/packages/cli/package.json index f9a37d4d..f41846fd 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@onkernel/cua-cli", - "version": "0.4.0", + "version": "0.5.0", "description": "Kernel-cloud-browser computer-use TUI built on @onkernel/cua-agent and pi-tui", "license": "MIT", "type": "module", @@ -36,8 +36,8 @@ "dependencies": { "@earendil-works/pi-coding-agent": "0.80.10", "@earendil-works/pi-tui": "0.80.10", - "@onkernel/cua-agent": "0.6.0", - "@onkernel/cua-ai": "0.6.0", + "@onkernel/cua-agent": "0.7.0", + "@onkernel/cua-ai": "0.7.0", "@onkernel/sdk": "0.49.0" }, "devDependencies": {