From dc717004b3731c7f850d67134d57836062afc0de Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 13 Jul 2026 21:47:10 +0000 Subject: [PATCH 1/4] Add Grok 4.5 computer-use provider --- .agents/skills/update-models/SKILL.md | 23 +++- .../skills/update-models/reference/README.md | 4 +- .../reference/audit-official-examples.ts | 13 +- .../reference/discover-models.ts | 117 ++++++++++++++++-- .../reference/native-action-probe.ts | 62 +++++++++- .../reference/provider-doc-drift.ts | 16 ++- .../update-models/reference/report-schema.md | 1 + .github/workflows/ci.yml | 2 + README.md | 6 + packages/agent/README.md | 11 +- packages/agent/src/agent.ts | 4 +- packages/agent/test/e2e.live.test.ts | 14 +++ packages/ai/README.md | 19 ++- packages/ai/docs/supported-models.md | 17 +++ packages/ai/examples/quickstart.ts | 5 +- packages/ai/src/api-keys.ts | 1 + packages/ai/src/index.ts | 1 + packages/ai/src/models.ts | 31 ++++- packages/ai/src/providers.ts | 25 ++++ packages/ai/src/providers/common.ts | 2 +- packages/ai/src/providers/xai/index.ts | 49 ++++++++ packages/ai/src/providers/xai/provider.ts | 57 +++++++++ packages/ai/src/runtime-spec.ts | 2 + packages/ai/test/api-keys.test.ts | 4 + .../ai/test/computer-tool.integration.test.ts | 81 +++++++++++- packages/ai/test/models.test.ts | 17 ++- packages/ai/test/provider-module.test.ts | 13 +- packages/ai/test/providers.test.ts | 11 +- packages/ai/test/runtime-spec.test.ts | 13 +- packages/ai/test/xai-threading.test.ts | 87 +++++++++++++ packages/cli/README.md | 5 +- packages/cli/src/cli-harness.ts | 2 +- packages/cli/src/cli.ts | 3 + packages/cli/src/harness.ts | 2 +- packages/cli/test/cli-executor.test.ts | 1 + packages/cli/test/harness-models.test.ts | 5 +- 36 files changed, 663 insertions(+), 63 deletions(-) create mode 100644 packages/ai/src/providers/xai/index.ts create mode 100644 packages/ai/src/providers/xai/provider.ts create mode 100644 packages/ai/test/xai-threading.test.ts diff --git a/.agents/skills/update-models/SKILL.md b/.agents/skills/update-models/SKILL.md index fe6ff729..f02b894a 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, 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, 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`, `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`, `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', 'TZAFON_API_KEY', 'YUTORI_API_KEY']: +for key in ['OPENAI_API_KEY', 'ANTHROPIC_API_KEY', 'GOOGLE_API_KEY', 'META_API_KEY', 'XAI_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))}') @@ -61,7 +61,7 @@ Treat example repos as strongest when they are provider-owned or linked from off There are two enumeration layers: -- Live provider availability: `reference/discover-models.ts` uses provider APIs and docs (`OpenAI().models.list()`, `Anthropic().models.list({ limit: 1000 })`, `GoogleGenAI().models.list()` / documented Gemini computer-use IDs, Tzafon's `Lightcone().models.list()` with known-model fallback, and Yutori OpenAPI/docs model enums) to discover what the current API key can access. +- Live provider availability: `reference/discover-models.ts` uses provider APIs and docs (`OpenAI().models.list()`, `Anthropic().models.list({ limit: 1000 })`, `GoogleGenAI().models.list()` / documented Gemini computer-use IDs, xAI's OpenAI-compatible `models.list()`, Tzafon's `Lightcone().models.list()` with known-model fallback, and Yutori OpenAPI/docs model enums) to discover what the current API key can access. - CUA-supported refs: `listCuaModels(provider?)` from `@onkernel/cua-ai` reads `packages/ai/src/models.ts` and returns the provider-qualified refs CUA accepts (e.g. `anthropic:claude-opus-4-7`). The `CUA_MODEL_ANNOTATIONS` table there is also what `getCuaModel()` and runtime provider routing use. When live discovery finds a new model with passing smoke tests, update `packages/ai/src/models.ts`; then verify it appears in `listCuaModels("")`. @@ -104,6 +104,16 @@ Google/Gemini: - Pass condition: response contains provider-native `functionCall.name` values such as `open_web_browser`, `click_at`, or `type_text_at`. - Do not infer official computer-use support from CUA's custom Gemini `functionDeclarations`; those are a separate compatibility path. +xAI: + +- Discover with the OpenAI SDK against `https://api.x.ai/v1` using `XAI_API_KEY`. +- Record aliases, context length, standard and long-context token prices, and the 200k long-context threshold returned by `models.list()`. +- Smoke-test the Responses API with screenshot input and explicit function tools matching CUA's canonical actions. +- Pass condition: response output contains a `function_call` for one of the supplied browser actions. +- Treat Grok computer use as custom-function-tool support, not a provider-native computer tool. xAI currently documents image understanding and function calling but no native coordinate protocol. +- 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`. + Tzafon: - Discover with `new Lightcone({ apiKey }).models.list()` from `@tzafon/lightcone` when available. @@ -126,9 +136,11 @@ Run action probes when updating adapters or when docs/examples show drift: ```bash npx tsx .agents/skills/update-models/reference/discover-models.ts --provider meta --models muse-spark-1.1 +npx tsx .agents/skills/update-models/reference/discover-models.ts --provider xai --models grok-4.5 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider openai --model gpt-5.5 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider anthropic --model claude-opus-4-7 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider gemini --model gemini-3-flash-preview +npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider xai --model grok-4.5 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider tzafon --model tzafon.northstar-cua-fast npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider yutori --model n1.5-latest ``` @@ -147,7 +159,7 @@ Recommend a model as CUA-supported only if: - It appears in the provider metadata API for the available key. - Its model-specific docs do not rule out required CUA runtime features such as streaming. -- Its provider-native computer-use smoke test passes. +- Its provider-appropriate computer-use smoke test passes: native tools where offered, or supplied function tools for Meta and xAI. - Its local cua-ai smoke test emits a computer tool call: `CUA_MODEL=: npm run example:quickstart --workspace @onkernel/cua-ai` returns a `toolCall` block. - Official docs or examples support the same tool mechanism, or the smoke result clearly supersedes stale docs. - The model is annotated in `CUA_MODEL_ANNOTATIONS` in `packages/ai/src/models.ts`, resolved from `pi-ai`'s registry or backed by a `CUA_MODEL_OVERRIDES` entry. @@ -174,6 +186,7 @@ All CUA model and adapter support lives in `packages/ai` (`@onkernel/cua-ai`). W - OpenAI: update `packages/ai/src/providers/openai/index.ts` and its action vocabulary, plus the shared canonical types in `packages/ai/src/providers/common.ts` if the action set changes. - 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. - 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 686fa65f..7bb813dc 100644 --- a/.agents/skills/update-models/reference/README.md +++ b/.agents/skills/update-models/reference/README.md @@ -12,10 +12,11 @@ These scripts support the `update-models` skill. Run them from the repository ro - `ANTHROPIC_API_KEY` - `GOOGLE_API_KEY` or `GEMINI_API_KEY` - `META_API_KEY` + - `XAI_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 uses 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 and xAI use supplied function tools; other providers may use provider-native computer tools. ## Common Commands @@ -29,6 +30,7 @@ Probe native action vocabularies for a specific provider/model: ```bash npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider openai --model gpt-5.5 --out /tmp/openai-actions.json +npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider xai --model grok-4.5 --out /tmp/xai-actions.json npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider tzafon --model tzafon.northstar-cua-fast --out /tmp/tzafon-actions.json npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider yutori --model n1.5-latest --out /tmp/yutori-actions.json ``` diff --git a/.agents/skills/update-models/reference/audit-official-examples.ts b/.agents/skills/update-models/reference/audit-official-examples.ts index 247d295a..cc6a4b07 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" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "yutori"; interface ExampleRepo { provider: Provider; @@ -59,6 +59,14 @@ const EXAMPLES: ExampleRepo[] = [ pathHint: "03_use_cases/13_macos_cua", patterns: ["muse-spark-1.1", "function_call", "function_call_output", "previous_response_id", "reasoning.encrypted_content", "parallel_tool_calls"], }, + { + provider: "xai", + name: "xai-sdk-python", + repo: "https://github.com/xai-org/xai-sdk-python.git", + confidence: "provider-owned", + pathHint: "examples", + patterns: ["grok-4.5", "function_call", "tool_call", "previous_response_id", "reasoning_effort", "parallel_tool_calls"], + }, { provider: "yutori", name: "kernel-cli-yutori-template", @@ -74,6 +82,7 @@ const ACTION_REGEXES: Record = { anthropic: [/\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|cursor_position|zoom)\b/g], 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], 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], }; @@ -156,7 +165,7 @@ async function auditRepo(example: ExampleRepo, cacheDir: string, args: Args): Pr for (const regex of ACTION_REGEXES[example.provider] ?? []) { extractAll(text, regex).forEach((v) => actionNames.add(v)); } - for (const field of ["computer_call", "actions", "action", "pending_safety_checks", "tool_use", "tool_result", "tool_calls", "function_call", "functionCall", "FunctionResponse", "safety_decision"]) { + for (const field of ["computer_call", "actions", "action", "pending_safety_checks", "tool_use", "tool_result", "tool_calls", "function_call", "functionCall", "FunctionResponse", "safety_decision", "previous_response_id", "parallel_tool_calls", "reasoning_effort"]) { if (text.includes(field)) responseFields.add(field); } diff --git a/.agents/skills/update-models/reference/discover-models.ts b/.agents/skills/update-models/reference/discover-models.ts index 8984a138..dc0d0259 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" | "tzafon" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "meta" | "xai" | "tzafon" | "yutori"; interface Args { provider: Provider | "all"; @@ -39,7 +39,7 @@ interface ModelResult { cua?: Record; } -const PROVIDERS: Provider[] = ["openai", "anthropic", "gemini", "meta", "tzafon", "yutori"]; +const PROVIDERS: Provider[] = ["openai", "anthropic", "gemini", "meta", "xai", "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. @@ -139,6 +139,7 @@ async function runProvider(provider: Provider, args: Args): Promise> { async function smokeMeta(client: any, model: string): Promise { try { - const screenshotPath = [ - join(process.cwd(), "examples", "screenshot.png"), - join(process.cwd(), "packages", "ai", "examples", "screenshot.png"), - ].find(existsSync); - if (!screenshotPath) throw new Error("could not find packages/ai/examples/screenshot.png"); - const screenshot = await readFile(screenshotPath); + const screenshot = await readFile(fixtureScreenshotPath()); const response = await client.responses.create({ model, store: false, @@ -250,6 +246,100 @@ async function smokeMeta(client: any, model: string): Promise { } } +async function discoverXai(args: Args): Promise> { + const OpenAI = await importDefault("openai", "OpenAI"); + const client = new OpenAI({ apiKey: process.env.XAI_API_KEY, baseURL: "https://api.x.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: likelyXaiGenerationModel(String(m.id)), + model_docs: { + url: String(m.id) === "grok-4.5" ? "https://docs.x.ai/developers/grok-4-5" : "https://docs.x.ai/developers/models", + responses_endpoint: "supported", + function_calling: "supported", + image_input: "verify-per-model", + coordinate_space: "CUA-defined 0-1000", + }, + })); + const candidates = explicitOrCandidates( + args, + models + .filter((model) => model.supports_generation) + .sort(compareXaiCandidates) + .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 smokeXai(client, id); + if (!models.find((candidate) => candidate.id === id)) models.unshift(model); + })); + } + await annotateCuaSupport("xai", models); + return { provider: "xai", metadata_source: "xAI models.list()", models, candidates }; +} + +async function smokeXai(client: any, model: string): Promise { + try { + const screenshot = await readFile(fixtureScreenshotPath()); + const response = await client.responses.create({ + model, + store: true, + parallel_tool_calls: false, + max_output_tokens: 768, + reasoning: { effort: "low" }, + instructions: "Coordinates are normalized from 0 to 1000 relative to the screenshot.", + input: [{ + role: "user", + content: [ + { type: "input_text", text: "Call the click tool for the sign in link. Do not answer only in text." }, + { type: "input_image", image_url: `data:image/png;base64,${screenshot.toString("base64")}`, detail: "high" }, + ], + }], + tools: [{ + type: "function", + name: "click", + description: "Click at normalized 0-1000 screen coordinates.", + parameters: { + type: "object", + properties: { x: { type: "number" }, y: { type: "number" } }, + required: ["x", "y"], + additionalProperties: false, + }, + }], + }); + const output: any[] = response.output ?? []; + const calls = output.filter((item) => item?.type === "function_call"); + return { + status: calls.length > 0 ? "pass" : "inconclusive", + tool_name: "function_tools", + tool_version: null, + beta_header: null, + observed_actions: unique(calls.map((call) => call?.name).filter(Boolean)), + response_item_types: unique(output.map((item) => item?.type).filter(Boolean)), + reasoning_effort: "low", + coordinate_space: "0-1000", + error: null, + }; + } catch (err) { + return smokeError(err, { tool_name: "function_tools" }); + } +} + +function likelyXaiGenerationModel(id: string): boolean { + const lower = id.toLowerCase(); + return lower.startsWith("grok-") && !lower.includes("imagine"); +} + +function compareXaiCandidates(a: ModelResult, b: ModelResult): number { + if (a.id === "grok-4.5") return -1; + if (b.id === "grok-4.5") return 1; + return String(b.created_at ?? "").localeCompare(String(a.created_at ?? "")); +} + function likelyOpenAIGenerationModel(id: string): boolean { const lower = id.toLowerCase(); if (OPENAI_EXCLUDE.some((needle) => lower.includes(needle))) return false; @@ -889,6 +979,15 @@ function unique(values: T[]): T[] { return [...new Set(values)]; } +function fixtureScreenshotPath(): string { + const path = [ + join(process.cwd(), "examples", "screenshot.png"), + join(process.cwd(), "packages", "ai", "examples", "screenshot.png"), + ].find(existsSync); + if (!path) throw new Error("could not find packages/ai/examples/screenshot.png"); + return path; +} + async function emitJson(value: unknown, outPath: string): Promise { const text = `${JSON.stringify(value, null, 2)}\n`; if (outPath) await writeFile(outPath, text); diff --git a/.agents/skills/update-models/reference/native-action-probe.ts b/.agents/skills/update-models/reference/native-action-probe.ts index d6fd433d..4c712551 100644 --- a/.agents/skills/update-models/reference/native-action-probe.ts +++ b/.agents/skills/update-models/reference/native-action-probe.ts @@ -1,11 +1,12 @@ #!/usr/bin/env tsx +import { existsSync } from "node:fs"; import { readFile, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { join } from "node:path"; import process from "node:process"; import { parse as parseToml } from "smol-toml"; -type Provider = "openai" | "anthropic" | "gemini" | "tzafon" | "yutori"; +type Provider = "openai" | "anthropic" | "gemini" | "xai" | "tzafon" | "yutori"; interface ProbePrompt { id: string; @@ -63,8 +64,8 @@ function parseArgs(argv: string[]): Args { throw new Error(`unknown argument: ${arg}`); } } - if (!["openai", "anthropic", "gemini", "tzafon", "yutori"].includes(out.provider)) { - throw new Error("--provider is required: openai | anthropic | gemini | tzafon | yutori"); + if (!["openai", "anthropic", "gemini", "xai", "tzafon", "yutori"].includes(out.provider)) { + throw new Error("--provider is required: openai | anthropic | gemini | xai | tzafon | yutori"); } if (!out.model) throw new Error("--model is required"); return out; @@ -74,6 +75,7 @@ function usage(): never { console.log(`Usage: npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider openai --model gpt-5.5 --out /tmp/actions.json npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider anthropic --model claude-opus-4-7 --limit 3 + npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider xai --model grok-4.5 --limit 3 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider tzafon --model tzafon.northstar-cua-fast --limit 3 npx tsx .agents/skills/update-models/reference/native-action-probe.ts --provider yutori --model n1.5-latest --limit 3 `); @@ -102,6 +104,7 @@ async function runProbe(provider: Provider, model: string, prompt: ProbePrompt): if (provider === "openai") return await probeOpenAI(model, prompt); if (provider === "anthropic") return await probeAnthropic(model, prompt); if (provider === "gemini") return await probeGemini(model, prompt); + if (provider === "xai") return await probeXai(model, prompt); if (provider === "tzafon") return await probeTzafon(model, prompt); if (provider === "yutori") return await probeYutori(model, prompt); throw new Error(`unknown provider ${provider satisfies never}`); @@ -215,6 +218,46 @@ async function probeGemini(model: string, prompt: ProbePrompt): Promise { + const OpenAI = await importDefault("openai", "OpenAI"); + const client = new OpenAI({ apiKey: process.env.XAI_API_KEY, baseURL: "https://api.x.ai/v1" }); + const screenshot = await readFixtureScreenshot(); + const response = await client.responses.create({ + model, + store: true, + parallel_tool_calls: false, + max_output_tokens: 768, + reasoning: { effort: "low" }, + instructions: "The available browser tools use normalized 0-1000 screenshot coordinates. Call one tool instead of answering in text.", + input: [{ + role: "user", + content: [ + { type: "input_text", text: prompt.text }, + { type: "input_image", image_url: `data:image/png;base64,${screenshot.toString("base64")}`, detail: "high" }, + ], + }], + tools: XAI_FUNCTION_TOOLS, + }); + const output: any[] = response.output ?? []; + const calls = output.filter((item) => item?.type === "function_call"); + return { + id: prompt.id, + status: calls.length ? "pass" : "inconclusive", + actions: unique(calls.map((call) => call?.name).filter(Boolean)), + item_types: unique(output.map((item) => item?.type).filter(Boolean)), + raw_tool_calls: calls.map(redactLargeFields), + }; +} + +async function readFixtureScreenshot(): Promise { + const path = [ + join(process.cwd(), "examples", "screenshot.png"), + join(process.cwd(), "packages", "ai", "examples", "screenshot.png"), + ].find(existsSync); + if (!path) throw new Error("could not find packages/ai/examples/screenshot.png"); + return readFile(path); +} + async function probeYutori(model: string, prompt: ProbePrompt): Promise { const OpenAI = await importDefault("openai", "OpenAI"); const client = new OpenAI({ @@ -296,6 +339,19 @@ async function probeTzafon(model: string, prompt: ProbePrompt): Promise = { "https://dev.meta.ai/docs/features/responses", "https://dev.meta.ai/docs/features/tool-calling", ], + xai: [ + "https://docs.x.ai/developers/grok-4-5", + "https://docs.x.ai/developers/tools/function-calling", + "https://docs.x.ai/developers/model-capabilities/images/understanding", + ], yutori: [ "https://docs.yutori.com/reference/navigator", "https://docs.yutori.com/reference/n1", @@ -48,6 +53,7 @@ const LOCAL_FILES: Record = { anthropic: "packages/ai/src/providers/anthropic/actions.ts", gemini: "packages/ai/src/providers/gemini/index.ts", meta: "packages/ai/src/providers/meta/index.ts", + xai: "packages/ai/src/providers/xai/index.ts", yutori: "packages/ai/src/providers/yutori/actions.ts", }; @@ -56,6 +62,7 @@ const ACTION_REGEXES: Record = { anthropic: /\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|cursor_position|zoom)\b/g, 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, 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, }; @@ -130,8 +137,8 @@ async function checkProvider(provider: Provider, examples: any): Promise & { /** Maximum tool-result images included from message history per provider request. Defaults to 4; false disables projection. */ toolResultImageReplayLimit?: ToolResultImageReplayLimit; /** - * Chain OpenAI, Meta, and Tzafon requests through provider-stored response state. Defaults to true. + * Chain OpenAI, Meta, xAI, and Tzafon requests through provider-stored response state. Defaults to true. */ responseThreading?: boolean; /** Optional CUA-level retries around each provider request. Disabled by default. */ @@ -205,7 +205,7 @@ export type CuaAgentHarnessOptions< /** Maximum tool-result images included from message history per provider request. Defaults to 4; false disables projection. */ toolResultImageReplayLimit?: ToolResultImageReplayLimit; /** - * Chain OpenAI, Meta, and Tzafon requests through provider-stored response state. Defaults to true. + * Chain OpenAI, Meta, xAI, and Tzafon requests through provider-stored response state. Defaults to true. */ responseThreading?: boolean; /** Optional CUA-level retries around each provider request. Disabled by default. */ diff --git a/packages/agent/test/e2e.live.test.ts b/packages/agent/test/e2e.live.test.ts index 55fbc1d6..b3fe4953 100644 --- a/packages/agent/test/e2e.live.test.ts +++ b/packages/agent/test/e2e.live.test.ts @@ -21,6 +21,7 @@ type ProviderCase = { | "anthropic:claude-opus-4-7" | "google:gemini-3-flash-preview" | "meta:muse-spark-1.1" + | "xai:grok-4.5" | "tzafon:tzafon.northstar-cua-fast" | "yutori:n1.5-latest"; prompt: string; @@ -93,6 +94,19 @@ const cases: ProviderCase[] = [ expectToolCalls: true, timeoutMs: 180_000, }, + { + name: "xai", + apiKeyEnvVar: "XAI_API_KEY", + modelRef: "xai:grok-4.5", + 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 f8c9739c..219df162 100644 --- a/packages/ai/README.md +++ b/packages/ai/README.md @@ -21,6 +21,7 @@ check these environment variables, in order: | `anthropic` | `ANTHROPIC_OAUTH_TOKEN`, `ANTHROPIC_API_KEY` | | `google` | `GOOGLE_API_KEY`, `GEMINI_API_KEY` | | `meta` | `META_API_KEY` | +| `xai` | `XAI_API_KEY` | | `tzafon` | `TZAFON_API_KEY` | | `yutori` | `YUTORI_API_KEY` | @@ -212,6 +213,7 @@ getCuaModel("openai:gpt-5.5"); getCuaModel("anthropic:claude-opus-4-7"); getCuaModel("google:gemini-3-flash-preview"); getCuaModel("meta:muse-spark-1.1"); +getCuaModel("xai:grok-4.5"); getCuaModel("tzafon:tzafon.northstar-cua-fast"); getCuaModel("yutori:n1.5-latest"); ``` @@ -228,7 +230,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" | "tzafon" | "yutori"; +type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "tzafon" | "yutori"; ``` The IDs match pi-ai's `Model.provider` values exactly. `providerForModel(model)` @@ -334,8 +336,9 @@ definitions and executors; it is forwarded to the provider module's with pi's builtin providers plus CUA's adjustments: OpenAI models route through cua's `openai-cua-responses` stream (threads `previous_response_id`), Google resolves `GOOGLE_API_KEY` or - `GEMINI_API_KEY`, and the Meta/Tzafon/Yutori providers are registered. Each call - returns an independent collection. + `GEMINI_API_KEY`, xAI Grok 4.5 routes through a stateful Responses stream, + and the Meta/Tzafon/Yutori providers are registered. Each call returns an + independent collection. - `cuaModels(): MutableModels` — the shared default collection, created on first use. `CuaAgent` and `CuaAgentHarness` stream through it unless given another one. @@ -373,6 +376,7 @@ Per-provider canonical action subsets (each namespace exports its list as | `anthropic` | 13 — everything except `back`, `forward`, `url`; adds `computer_batch` by default | | `gemini` | all 16 | | `meta` | all 16 | +| `xai` | 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) | @@ -408,6 +412,7 @@ Current coordinate contracts: - `anthropic`: pixel coordinates, matching Anthropic's computer-use quickstart - `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)) - `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)) @@ -484,8 +489,8 @@ type CuaNavigationInput = { ## Provider Namespaces -Every provider namespace (`openai`, `anthropic`, `gemini`, `meta`, `tzafon`, -`yutori`) follows one convention: +Every provider namespace (`openai`, `anthropic`, `gemini`, `meta`, `xai`, +`tzafon`, `yutori`) follows one convention: - `computerTools(options?)` and `computerToolExecutors(options?)` - `createActionSchema(actions?)` — TypeBox schema for the provider's subset @@ -511,6 +516,10 @@ Provider-specific extras: - `meta`: the `meta-responses` stream adapter (`META_RESPONSES_API`, `streamMetaResponses`, `streamSimpleMetaResponses`, `MetaResponsesOptions`), with `previous_response_id` threading and Meta-compatible reasoning payloads +- `xai`: the `xai-cua-responses` stream adapter (`XAI_CUA_RESPONSES_API`, + `streamXaiResponses`, `streamSimpleXaiResponses`, `XaiResponsesOptions`), + with serial function tools, encrypted reasoning replay, and + `previous_response_id` threading - `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 0812cfff..c6a494d3 100644 --- a/packages/ai/docs/supported-models.md +++ b/packages/ai/docs/supported-models.md @@ -71,6 +71,23 @@ tools. CUA continues tool loops through `previous_response_id`. Source: [Meta computer-use cookbook](https://dev.meta.ai/docs/getting-started/cookbook/computer-use-macos). +## `xai` + +Coordinates: normalized 0–1000 (CUA function-tool contract) + +Exact IDs: + +- `grok-4.5` + +Grok 4.5 uses xAI's OpenAI-compatible Responses API with screenshot input and +ordinary function tools. xAI does not define a native computer tool or +coordinate protocol, so CUA supplies the browser actions and normalized +coordinate instructions. Tool loops continue through `previous_response_id`. +The catalog records xAI's standard-context prices; xAI doubles token prices +when input exceeds 200k tokens. + +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). + ## `tzafon` Coordinates: normalized 0–999 diff --git a/packages/ai/examples/quickstart.ts b/packages/ai/examples/quickstart.ts index fed43c6f..0ac928df 100644 --- a/packages/ai/examples/quickstart.ts +++ b/packages/ai/examples/quickstart.ts @@ -11,6 +11,7 @@ import { // anthropic:claude-opus-4-7 ANTHROPIC_API_KEY // google:gemini-3-flash-preview GOOGLE_API_KEY // meta:muse-spark-1.1 META_API_KEY +// xai:grok-4.5 XAI_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; @@ -34,9 +35,9 @@ const response = await models.complete( model, { systemPrompt: [ - "You are controlling a browser from a screenshot.", + spec.defaultSystemPrompt, "Call the computer tool with the coordinates of the target. Do not describe the click in prose unless you cannot identify the target.", - ].join("\n"), + ].filter(Boolean).join("\n"), messages: [ { role: "user", diff --git a/packages/ai/src/api-keys.ts b/packages/ai/src/api-keys.ts index 769599f3..443ff13a 100644 --- a/packages/ai/src/api-keys.ts +++ b/packages/ai/src/api-keys.ts @@ -13,6 +13,7 @@ const CUA_PROVIDER_API_KEY_ENV_VARS: Record = { anthropic: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"], google: ["GOOGLE_API_KEY", "GEMINI_API_KEY"], meta: ["META_API_KEY"], + xai: ["XAI_API_KEY"], tzafon: ["TZAFON_API_KEY"], yutori: ["YUTORI_API_KEY"], }; diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index 54e05158..61864104 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -10,4 +10,5 @@ export * as gemini from "./providers/gemini/index"; export * as meta from "./providers/meta/index"; export * as openai from "./providers/openai/index"; export * as tzafon from "./providers/tzafon/index"; +export * as xai from "./providers/xai/index"; export * as yutori from "./providers/yutori/index"; diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index df574b44..5c63e338 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -2,9 +2,10 @@ import type { Api, Model } from "@earendil-works/pi-ai"; import { getBuiltinModel, getBuiltinModels } from "@earendil-works/pi-ai/providers/all"; import { META_RESPONSES_API } from "./providers/meta/provider"; 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" | "tzafon" | "yutori"; +export type CuaProvider = "openai" | "anthropic" | "google" | "meta" | "xai" | "tzafon" | "yutori"; /** Provider-qualified model reference, e.g. `"openai:gpt-5.5"` or `"google:gemini-3-flash-preview"`. */ export type CuaModelRef = `${CuaProvider}:${string}`; @@ -21,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", "tzafon", "yutori"]; +export const CUA_PROVIDERS: readonly CuaProvider[] = ["openai", "anthropic", "google", "meta", "xai", "tzafon", "yutori"]; /** * How a {@link CuaModelAnnotation} matches model ids. @@ -82,6 +83,9 @@ export const CUA_MODEL_ANNOTATIONS: Record[]> = { openai: [], anthropic: [], google: [], - // pi-ai 0.80.3 predates Meta's models.dev catalog entry. + // pi-ai 0.80.3 predates these providers' current models.dev catalog entries. meta: [cuaModel("meta", "muse-spark-1.1", "Muse Spark 1.1")], + xai: [cuaModel("xai", "grok-4.5", "Grok 4.5")], 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"), @@ -201,7 +206,7 @@ export function getCuaModel(ref: CuaModelRef): Model { // Route OpenAI-compatible CUA models to provider-specific Responses transports // that thread previous_response_id. Registry-resolved models otherwise carry -// pi-ai's builtin "openai-responses" api. +// pi-ai's builtin API ids. export function routeCuaApi(model: Model): Model { if (model.provider === "openai" && model.api !== OPENAI_CUA_RESPONSES_API) { return { ...model, api: OPENAI_CUA_RESPONSES_API }; @@ -209,6 +214,9 @@ export function routeCuaApi(model: Model): Model { if (model.provider === "meta" && model.api !== META_RESPONSES_API) { return { ...model, api: META_RESPONSES_API }; } + if (model.provider === "xai" && model.api !== XAI_CUA_RESPONSES_API) { + return { ...model, api: XAI_CUA_RESPONSES_API }; + } return model; } @@ -261,7 +269,7 @@ function cuaModel(provider: CuaProvider, id: string, name: string): Model { id, name, provider, - reasoning: provider === "openai" || provider === "anthropic" || provider === "google" || provider === "meta", + reasoning: provider === "openai" || provider === "anthropic" || provider === "google" || provider === "meta" || provider === "xai", input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, } satisfies Partial>; @@ -286,6 +294,19 @@ function cuaModel(provider: CuaProvider, id: string, name: string): Model { maxTokens: 128_000, compat: { supportsDeveloperRole: true, sendSessionIdHeader: false, supportsLongCacheRetention: true }, } as Model; + case "xai": + // xAI doubles token prices above 200k input tokens; pi's flat Model + // cost fields record the standard-context rates. + return { + ...base, + api: XAI_CUA_RESPONSES_API, + baseUrl: "https://api.x.ai/v1", + thinkingLevelMap: { off: null, minimal: "low", xhigh: "high" }, + cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 }, + contextWindow: 500_000, + maxTokens: 500_000, + compat: { supportsDeveloperRole: false, sendSessionIdHeader: false, supportsLongCacheRetention: false }, + } as Model; case "tzafon": return { ...base, api: "tzafon-responses", baseUrl: "https://api.lightcone.ai", contextWindow: 128_000, maxTokens: 4_096 } as Model; case "yutori": diff --git a/packages/ai/src/providers.ts b/packages/ai/src/providers.ts index cdd8b225..2cfbc824 100644 --- a/packages/ai/src/providers.ts +++ b/packages/ai/src/providers.ts @@ -17,6 +17,7 @@ import { ANTHROPIC_NATIVE_API_BETA_HEADERS, withAnthropicBetaHeader } from "./pr import { META_RESPONSES_API, streamMetaResponses, streamSimpleMetaResponses } from "./providers/meta/provider"; import { OPENAI_CUA_RESPONSES_API, streamOpenAIResponses, streamSimpleOpenAIResponses } from "./providers/openai/provider"; import { streamSimpleTzafonResponses, streamTzafonResponses, TZAFON_RESPONSES_API } from "./providers/tzafon/provider"; +import { streamSimpleXaiResponses, streamXaiResponses, XAI_CUA_RESPONSES_API } from "./providers/xai/provider"; import { streamSimpleYutori, streamYutori, YUTORI_CHAT_COMPLETIONS_API } from "./providers/yutori/provider"; /** @@ -33,6 +34,8 @@ import { streamSimpleYutori, streamYutori, YUTORI_CHAT_COMPLETIONS_API } from ". * `anthropic-beta` header merged in. * - `google` resolves its API key from `GOOGLE_API_KEY` or `GEMINI_API_KEY` * (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. * - `meta`, `tzafon`, and `yutori` are CUA-only providers pi does not ship. * * Each call returns an independent collection; register additional providers @@ -46,6 +49,8 @@ export function createCuaModels(options?: CreateModelsOptions): MutableModels { if (anthropic) models.setProvider(withAnthropicNativeTools(anthropic)); const google = models.getProvider("google"); if (google) models.setProvider(withGoogleEnvKeys(google)); + const xai = models.getProvider("xai"); + if (xai) models.setProvider(withXaiCuaResponses(xai)); models.setProvider(metaProvider()); models.setProvider(tzafonProvider()); models.setProvider(yutoriProvider()); @@ -108,6 +113,25 @@ function withGoogleEnvKeys(base: Provider): Provider { return { ...base, auth: { ...base.auth, apiKey: envApiKeyAuth("Google API key", cuaApiKeyEnvVarsForProvider("google")) } }; } +function withXaiCuaResponses(base: Provider): Provider { + return { + ...base, + getModels: () => { + const models = new Map(base.getModels().map((model) => [model.id, model])); + for (const model of cuaOverrideModels("xai")) models.set(model.id, model); + return [...models.values()]; + }, + stream: (model: Model, context: Context, options?: StreamOptions) => + model.api === XAI_CUA_RESPONSES_API + ? streamXaiResponses(model as never, context, options) + : base.stream(model, context, options), + streamSimple: (model: Model, context: Context, options?: SimpleStreamOptions) => + model.api === XAI_CUA_RESPONSES_API + ? streamSimpleXaiResponses(model as never, context, options) + : base.streamSimple(model, context, options), + }; +} + function metaProvider(): Provider { return createProvider({ id: "meta", @@ -144,4 +168,5 @@ function yutoriProvider(): Provider { export { META_RESPONSES_API, streamMetaResponses, streamSimpleMetaResponses }; export { OPENAI_CUA_RESPONSES_API, streamOpenAIResponses, streamSimpleOpenAIResponses }; export { TZAFON_RESPONSES_API, streamSimpleTzafonResponses, streamTzafonResponses }; +export { XAI_CUA_RESPONSES_API, streamSimpleXaiResponses, streamXaiResponses }; export { YUTORI_CHAT_COMPLETIONS_API, streamSimpleYutori, streamYutori }; diff --git a/packages/ai/src/providers/common.ts b/packages/ai/src/providers/common.ts index dc54a620..d55f1567 100644 --- a/packages/ai/src/providers/common.ts +++ b/packages/ai/src/providers/common.ts @@ -274,7 +274,7 @@ export type CuaPayloadHook = (payload: unknown, model: Model, context?: Cua * pi-ai `SimpleStreamOptions` plus CUA request controls consumed by provider * adapters. Pass `keepToolNames` for caller tools that must survive native * tool-set substitution; use `disableResponseThreading` for a full-context - * OpenAI, Meta, or Tzafon request. + * OpenAI, Meta, xAI, or Tzafon request. */ export interface CuaSimpleStreamOptions extends SimpleStreamOptions, ResponseThreadingOptions { keepToolNames?: readonly string[]; diff --git a/packages/ai/src/providers/xai/index.ts b/packages/ai/src/providers/xai/index.ts new file mode 100644 index 00000000..77349ccc --- /dev/null +++ b/packages/ai/src/providers/xai/index.ts @@ -0,0 +1,49 @@ +import { assertComputerModeOnly, computerToolExecutors, computerTools } from "../common"; +import type { ComputerToolCoordinateSystem, ComputerToolsOptions, CuaProviderModule } from "../common"; + +export { + CUA_ACTION_TYPES as XAI_CUA_ACTION_TYPES, + computerToolExecutors, + computerTools, + createCuaActionSchema as createActionSchema, +} from "../common"; +export type { + CuaAction as XaiAction, + ComputerToolsOptions, +} from "../common"; +export { + streamSimpleXaiResponses, + streamXaiResponses, + threadXaiRequest, + XAI_CUA_RESPONSES_API, +} from "./provider"; +export type { XaiResponsesOptions } from "./provider"; + +export const XAI_COMPUTER_INSTRUCTIONS = `You control a Kernel cloud browser through individual browser tools. Coordinates are normalized from 0 to 1000 relative to the most recent screenshot, with (0, 0) at the top left and (1000, 1000) at the bottom right. Base each action on the latest observed state and request a screenshot when you need a fresh view.`; + +/** + * Build xAI's default system prompt for computer-use runs. + */ +export function buildXaiSystemPrompt(opts: { suffix?: string } = {}): string { + return [XAI_COMPUTER_INSTRUCTIONS, opts.suffix].filter(Boolean).join("\n\n"); +} + +/** + * Return the normalized coordinate contract CUA supplies to Grok function tools. + */ +export function coordinateSystem(): ComputerToolCoordinateSystem { + return { type: "normalized", range: [0, 1000] }; +} + +export const providerModule = { + toolDefinitions: (options?: ComputerToolsOptions) => { + assertComputerModeOnly("xai", options); + return computerTools(options); + }, + toolExecutors: (options?: ComputerToolsOptions) => { + assertComputerModeOnly("xai", options); + return computerToolExecutors(options); + }, + coordinateSystem, + buildSystemPrompt: buildXaiSystemPrompt, +} satisfies CuaProviderModule; diff --git a/packages/ai/src/providers/xai/provider.ts b/packages/ai/src/providers/xai/provider.ts new file mode 100644 index 00000000..24b64828 --- /dev/null +++ b/packages/ai/src/providers/xai/provider.ts @@ -0,0 +1,57 @@ +import { + type Context, + type OpenAIResponsesOptions as PiOpenAIResponsesOptions, + type StreamFunction, +} from "@earendil-works/pi-ai"; +import { + stream as piStreamOpenAIResponses, + streamSimple as piStreamSimpleOpenAIResponses, +} from "@earendil-works/pi-ai/api/openai-responses"; +import { + type CuaSimpleStreamOptions, + type ResponsesThreadingOptions, + threadResponsesRequest, +} from "../common"; + +export const XAI_CUA_RESPONSES_API = "xai-cua-responses"; + +/** + * Stream options for xAI's OpenAI-compatible Responses API. + */ +export interface XaiResponsesOptions extends PiOpenAIResponsesOptions, ResponsesThreadingOptions {} + +/** + * Apply xAI's serial computer-use constraints on top of shared Responses threading. + */ +export function threadXaiRequest(context: Context, options: ResponsesThreadingOptions | undefined) { + const callerOnPayload = options?.onPayload; + const threaded = threadResponsesRequest(context, XAI_CUA_RESPONSES_API, { + ...options, + onPayload: async (payload, model) => { + const constrained = { ...(payload as Record), parallel_tool_calls: false }; + return callerOnPayload ? ((await callerOnPayload(constrained, model)) ?? constrained) : constrained; + }, + }); + const onPayload: typeof threaded.onPayload = async (payload, model) => { + const prepared = await threaded.onPayload(payload, model); + const constrained: Record = { + ...(prepared as Record), + store: true, + parallel_tool_calls: false, + }; + delete constrained.previous_response_id; + if (threaded.previousResponseId) constrained.previous_response_id = threaded.previousResponseId; + return constrained; + }; + return { context: threaded.context, onPayload }; +} + +export const streamXaiResponses: StreamFunction = (model, context, options) => { + const threaded = threadXaiRequest(context, options); + return piStreamOpenAIResponses(model as never, threaded.context, { ...options, onPayload: threaded.onPayload }); +}; + +export const streamSimpleXaiResponses: StreamFunction = (model, context, options) => { + const threaded = threadXaiRequest(context, options); + return piStreamSimpleOpenAIResponses(model as never, threaded.context, { ...options, onPayload: threaded.onPayload }); +}; diff --git a/packages/ai/src/runtime-spec.ts b/packages/ai/src/runtime-spec.ts index 867d4df7..25742ae1 100644 --- a/packages/ai/src/runtime-spec.ts +++ b/packages/ai/src/runtime-spec.ts @@ -8,6 +8,7 @@ import { providerModule as gemini } from "./providers/gemini/index"; import { providerModule as meta } from "./providers/meta/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"; import { providerModule as yutori } from "./providers/yutori/index"; import type { ComputerToolsOptions, @@ -22,6 +23,7 @@ const PROVIDERS = { anthropic, google: gemini, meta, + xai, tzafon, yutori, } satisfies Record; diff --git a/packages/ai/test/api-keys.test.ts b/packages/ai/test/api-keys.test.ts index 3ab7c86f..c40bf3e3 100644 --- a/packages/ai/test/api-keys.test.ts +++ b/packages/ai/test/api-keys.test.ts @@ -13,6 +13,7 @@ const ENV_KEYS = [ "GOOGLE_API_KEY", "GEMINI_API_KEY", "META_API_KEY", + "XAI_API_KEY", "TZAFON_API_KEY", "YUTORI_API_KEY", ] as const; @@ -33,6 +34,7 @@ describe("cua api key helpers", () => { expect(cuaApiKeyEnvVarsForProvider("google")).toEqual(["GOOGLE_API_KEY", "GEMINI_API_KEY"]); 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("unknown")).toEqual([]); }); @@ -49,6 +51,8 @@ describe("cua api key helpers", () => { expect(getCuaEnvApiKeyForModel("openai:gpt-5.5")).toBe("openai"); process.env.META_API_KEY = "meta"; expect(getCuaEnvApiKeyForModel("meta:muse-spark-1.1")).toBe("meta"); + process.env.XAI_API_KEY = "xai"; + expect(getCuaEnvApiKeyForModel("xai:grok-4.5")).toBe("xai"); }); 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 dcb59097..e020f006 100644 --- a/packages/ai/test/computer-tool.integration.test.ts +++ b/packages/ai/test/computer-tool.integration.test.ts @@ -15,6 +15,7 @@ import { meta, openai, tzafon, + xai, yutori, } from "../src/index"; @@ -32,6 +33,7 @@ interface ProviderCase { tools: () => ReturnType; coordinateRange: readonly [number, number]; requireToolCalls: boolean; + systemPrompt?: string; ciOptInEnvVar?: string; extraOptions?: Record; } @@ -62,6 +64,16 @@ const cases: ProviderCase[] = [ coordinateRange: [0, 999], requireToolCalls: true, }, + { + provider: "xai", + envVar: "XAI_API_KEY", + modelRef: "xai:grok-4.5", + tools: () => xai.computerTools({ actions: ["click"] }), + coordinateRange: [0, 1000], + requireToolCalls: true, + systemPrompt: "Coordinates are normalized from 0 to 1000 relative to the screenshot.", + extraOptions: { reasoningEffort: "low" }, + }, { provider: "tzafon", envVar: "TZAFON_API_KEY", @@ -73,13 +85,14 @@ const cases: ProviderCase[] = [ }, ]; -async function buildContext(tools: ProviderCase["tools"]): Promise { +async function buildContext(c: ProviderCase): Promise { const screenshot = await readFile(screenshotPath); return { systemPrompt: [ "You are controlling a browser from a screenshot.", "Call the available click tool for the sign in / up link.", - ].join("\n"), + c.systemPrompt, + ].filter(Boolean).join("\n"), messages: [ { role: "user", @@ -90,7 +103,7 @@ async function buildContext(tools: ProviderCase["tools"]): Promise { timestamp: Date.now(), }, ], - tools: tools(), + tools: c.tools(), }; } @@ -123,7 +136,7 @@ describe("individual computer action integration", () => { (ciEnabled ? test : it.skip)(`${c.provider} returns a canonical click tool call`, async () => { const model = getCuaModel(c.modelRef as never); - const context = await buildContext(c.tools); + const context = await buildContext(c); const response = await cuaModels().complete(model, context, { apiKey: apiKeyForCase(c), maxTokens: 1024, @@ -216,6 +229,66 @@ describe("individual computer action integration", () => { 90_000, ); + const xaiApiKey = getCuaEnvApiKey("xai"); + (xaiApiKey ? it : it.skip)( + "xai continues a screenshot tool loop with previous_response_id", + async () => { + const screenshot = await readFile(screenshotPath); + const model = getCuaModel("xai:grok-4.5"); + const tools = xai.computerTools({ actions: ["click"] }); + const context: Context = { + systemPrompt: "Use normalized 0-1000 coordinates. Call click exactly once, then answer in text after its result.", + messages: [ + { + role: "user", + content: [ + { type: "text", text: "Click the sign in / up link. After the tool result, report that the click completed without calling another tool." }, + { type: "image", data: screenshot.toString("base64"), mimeType: "image/png" }, + ], + timestamp: Date.now(), + }, + ], + tools, + }; + const first = await cuaModels().complete(model, context, { + apiKey: xaiApiKey, + maxTokens: 1024, + reasoningEffort: "low", + }); + const click = first.content.find((part) => part.type === "toolCall" && part.name === "click"); + expect(click).toBeDefined(); + expect(first.responseId).toBeTruthy(); + context.messages.push(first, { + role: "toolResult", + toolCallId: click!.id, + toolName: click!.name, + content: [ + { type: "text", text: "Click executed successfully." }, + { type: "image", data: screenshot.toString("base64"), mimeType: "image/png" }, + ], + isError: false, + timestamp: Date.now(), + }); + + let payload: Record | undefined; + const second = await cuaModels().complete(model, context, { + apiKey: xaiApiKey, + maxTokens: 1024, + reasoningEffort: "low", + onPayload: (value) => { + payload = value as Record; + }, + }); + expect(second.stopReason).not.toBe("error"); + expect(second.responseId).toBeTruthy(); + expect(payload?.previous_response_id).toBe(first.responseId); + expect(payload?.store).toBe(true); + expect(payload?.parallel_tool_calls).toBe(false); + expect(payload?.include).toEqual(["reasoning.encrypted_content"]); + }, + 90_000, + ); + const yutoriHasKey = !!process.env.YUTORI_API_KEY; (yutoriHasKey ? it : it.skip)( "yutori translates native tool calls into canonical individual actions", diff --git a/packages/ai/test/models.test.ts b/packages/ai/test/models.test.ts index 622d73df..03478d8d 100644 --- a/packages/ai/test/models.test.ts +++ b/packages/ai/test/models.test.ts @@ -9,6 +9,7 @@ import { meta, openai, parseCuaModelRef, + xai, } from "../src/index"; describe("CUA model refs", () => { @@ -25,7 +26,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, tzafon, yutori)', + 'unsupported CUA provider "bogus" (expected one of: openai, anthropic, google, meta, xai, tzafon, yutori)', ); }); @@ -59,6 +60,16 @@ describe("CUA model refs", () => { expect(muse.contextWindow).toBe(1_048_576); expect(muse.maxTokens).toBe(128_000); expect(muse.thinkingLevelMap?.off).toBeNull(); + + const grok = getCuaModel("xai:grok-4.5"); + expect(grok.provider).toBe("xai"); + expect(grok.api).toBe(xai.XAI_CUA_RESPONSES_API); + expect(grok.baseUrl).toBe("https://api.x.ai/v1"); + expect(grok.contextWindow).toBe(500_000); + expect(grok.maxTokens).toBe(500_000); + expect(grok.thinkingLevelMap?.off).toBeNull(); + expect(grok.thinkingLevelMap?.minimal).toBe("low"); + expect(grok.thinkingLevelMap?.xhigh).toBe("high"); }); it("loads supported custom provider models without explicit registration", () => { @@ -72,6 +83,7 @@ describe("CUA model refs", () => { expect(getCuaModel("openai:gpt-5.5").api).toBe(openai.OPENAI_CUA_RESPONSES_API); expect(getCuaModel("openai:gpt-5.4-mini").api).toBe(openai.OPENAI_CUA_RESPONSES_API); expect(getCuaModel("meta:muse-spark-1.1").api).toBe(meta.META_RESPONSES_API); + expect(getCuaModel("xai:grok-4.5").api).toBe(xai.XAI_CUA_RESPONSES_API); }); it("rejects supported model IDs that are not in pi-ai or overrides", () => { @@ -126,6 +138,9 @@ describe("CUA support annotations", () => { it("matches exact-id annotations", () => { expect(findCuaAnnotation("google", "gemini-3-flash-preview")).toBeDefined(); expect(findCuaAnnotation("meta", "muse-spark-1.1")).toBeDefined(); + expect(findCuaAnnotation("xai", "grok-4.5")).toBeDefined(); + expect(findCuaAnnotation("xai", "grok-4.5-latest")).toBeUndefined(); + expect(findCuaAnnotation("xai", "grok-4.3")).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 b3f48106..b92a0f00 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, yutori } from "../src/index"; +import { anthropic, CUA_COMPUTER_ACTION_TYPES, CUA_PROVIDERS, type CuaProvider, gemini, meta, openai, tzafon, xai, yutori } from "../src/index"; import type { CuaProviderModule } from "../src/providers/common"; const MODULES: Record = { @@ -7,6 +7,7 @@ const MODULES: Record = { anthropic, google: gemini, meta, + xai, tzafon, yutori, }; @@ -16,6 +17,7 @@ const NAMESPACES: Record; pref anthropic: { namespace: anthropic, prefix: "ANTHROPIC" }, google: { namespace: gemini, prefix: "GEMINI" }, meta: { namespace: meta, prefix: "META" }, + xai: { namespace: xai, prefix: "XAI" }, tzafon: { namespace: tzafon, prefix: "TZAFON" }, yutori: { namespace: yutori, prefix: "YUTORI" }, }; @@ -54,9 +56,12 @@ describe("provider modules satisfy the uniform contract", () => { expect(yutori.providerModule.toolDefinitions()).toEqual([]); }); - it("meta currently restricts its normalized coordinate contract to computer mode", () => { - expect(() => meta.providerModule.toolDefinitions({ mode: "browser" })).toThrow(/computer only/); - expect(() => meta.providerModule.toolDefinitions({ mode: "hybrid" })).toThrow(/computer only/); + it.each([ + ["meta", meta.providerModule], + ["xai", xai.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 8eb90faa..fad6e887 100644 --- a/packages/ai/test/providers.test.ts +++ b/packages/ai/test/providers.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { createCuaModels, cuaModels, tzafon, yutori } from "../src/index"; +import { createCuaModels, cuaModels, tzafon, xai, yutori } from "../src/index"; import { OPENAI_CUA_RESPONSES_API } from "../src/providers/openai/provider"; const TZAFON_RESPONSES_API = tzafon.TZAFON_RESPONSES_API; @@ -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", "tzafon", "yutori"]) { + for (const id of ["openai", "anthropic", "google", "meta", "xai", "tzafon", "yutori"]) { const provider = models.getProvider(id); expect(provider, id).toBeDefined(); expect(provider?.stream).toBeTypeOf("function"); @@ -19,6 +19,7 @@ describe("createCuaModels", () => { it("lists the CUA-only 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(xai.XAI_CUA_RESPONSES_API); 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); @@ -27,13 +28,17 @@ describe("createCuaModels", () => { expect(models.getModel("yutori", "n1.5-latest")?.api).toBe(YUTORI_CHAT_COMPLETIONS_API); }); - it("keeps pi's builtin openai catalog on the wrapped openai provider", () => { + it("keeps builtin catalogs on wrapped providers", () => { const models = createCuaModels(); const openaiIds = models.getModels("openai").map((m) => m.id); expect(openaiIds).toContain("gpt-5.4"); // The catalog keeps pi's api ids; getCuaModel() routes to // openai-cua-responses, which the wrapped provider dispatches. expect(models.getModel("openai", "gpt-5.4")?.api).not.toBe(OPENAI_CUA_RESPONSES_API); + + const xaiIds = models.getModels("xai").map((m) => m.id); + expect(xaiIds).toContain("grok-4.3"); + expect(xaiIds).toContain("grok-4.5"); }); it("returns independent collections", () => { diff --git a/packages/ai/test/runtime-spec.test.ts b/packages/ai/test/runtime-spec.test.ts index 4233339a..b5c8e75c 100644 --- a/packages/ai/test/runtime-spec.test.ts +++ b/packages/ai/test/runtime-spec.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { CUA_NAVIGATION_TOOL_NAME, CUA_PROVIDERS, getCuaModel, listCuaModels, meta, openai, resolveCuaRuntimeSpec } from "../src/index"; +import { CUA_NAVIGATION_TOOL_NAME, CUA_PROVIDERS, getCuaModel, listCuaModels, meta, openai, resolveCuaRuntimeSpec, xai } from "../src/index"; describe("resolveCuaRuntimeSpec", () => { it("resolves a runtime spec for every CUA provider", () => { @@ -34,14 +34,16 @@ describe("resolveCuaRuntimeSpec", () => { it("only sets payload middleware for providers that need it", () => { const yutoriSpec = resolveCuaRuntimeSpec("yutori:n1.5-latest"); const openaiSpec = resolveCuaRuntimeSpec("openai:gpt-5.5"); + const xaiSpec = resolveCuaRuntimeSpec("xai:grok-4.5"); const tzafonSpec = resolveCuaRuntimeSpec("tzafon:tzafon.northstar-cua-fast"); const anthropicSpec = resolveCuaRuntimeSpec("anthropic:claude-opus-4-7"); expect(yutoriSpec.onPayload).toBeTypeOf("function"); expect(tzafonSpec.onPayload).toBeTypeOf("function"); - // OpenAI needs no payload middleware: openai-cua-responses sets store:true - // in its own request builder. Anthropic uses middleware to adapt newer - // adaptive-thinking models without changing callers. + // OpenAI and xAI need no runtime payload middleware: their Responses + // transports apply threading constraints. Anthropic uses middleware to + // adapt newer adaptive-thinking models without changing callers. expect(openaiSpec.onPayload).toBeUndefined(); + expect(xaiSpec.onPayload).toBeUndefined(); expect(anthropicSpec.onPayload).toBeTypeOf("function"); }); @@ -51,6 +53,9 @@ describe("resolveCuaRuntimeSpec", () => { const metaModel = { ...getCuaModel("meta:muse-spark-1.1"), api: "openai-responses" } as Parameters[0]; expect(resolveCuaRuntimeSpec(metaModel).model.api).toBe(meta.META_RESPONSES_API); + + const xaiModel = { ...getCuaModel("xai:grok-4.5"), api: "openai-completions" } as Parameters[0]; + expect(resolveCuaRuntimeSpec(xaiModel).model.api).toBe(xai.XAI_CUA_RESPONSES_API); }); it("threads tool options through to the provider module", () => { diff --git a/packages/ai/test/xai-threading.test.ts b/packages/ai/test/xai-threading.test.ts new file mode 100644 index 00000000..1907aca6 --- /dev/null +++ b/packages/ai/test/xai-threading.test.ts @@ -0,0 +1,87 @@ +import type { Context, Message, Model } from "@earendil-works/pi-ai"; +import { describe, expect, it } from "vitest"; +import { threadXaiRequest, XAI_CUA_RESPONSES_API } from "../src/providers/xai/provider"; + +const model = {} as Model; + +function multiTurnContext(): Context { + const messages: Message[] = [ + { role: "user", content: "inspect the browser", timestamp: 0 }, + { + role: "assistant", + content: [{ type: "toolCall", id: "call_1|fc_1", name: "screenshot", arguments: {} }], + api: XAI_CUA_RESPONSES_API, + provider: "xai", + model: "grok-4.5", + responseId: "response_xai_1", + usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, totalTokens: 0, cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 } }, + stopReason: "toolUse", + timestamp: 0, + }, + { + role: "toolResult", + toolCallId: "call_1|fc_1", + toolName: "screenshot", + content: [{ type: "image", mimeType: "image/png", data: "screenshot" }], + isError: false, + timestamp: 0, + }, + ]; + return { messages, tools: [], systemPrompt: "control the browser" }; +} + +describe("xAI Responses threading", () => { + it("threads the latest response and applies serial computer-use defaults", async () => { + const { context, onPayload } = threadXaiRequest(multiTurnContext(), undefined); + expect(context.messages).toHaveLength(1); + expect(await onPayload({ input: [] }, model)).toEqual({ + input: [], + store: true, + parallel_tool_calls: false, + previous_response_id: "response_xai_1", + }); + }); + + it("preserves encrypted reasoning replay", async () => { + const context: Context = { messages: [{ role: "user", content: "hello", timestamp: 0 }] }; + const { onPayload } = threadXaiRequest(context, undefined); + expect(await onPayload({ input: [], include: ["reasoning.encrypted_content"] }, model)).toEqual({ + input: [], + include: ["reasoning.encrypted_content"], + store: true, + parallel_tool_calls: false, + }); + }); + + it("sends full history without previous_response_id when threading is disabled", async () => { + const context = multiTurnContext(); + const prepared = threadXaiRequest(context, { + disableResponseThreading: true, + onPayload: (payload) => ({ ...(payload as object), previous_response_id: "foreign-response" }), + }); + expect(prepared.context).toBe(context); + expect(await prepared.onPayload({ input: [] }, model)).toEqual({ + input: [], + store: true, + parallel_tool_calls: false, + }); + }); + + it("reapplies xAI constraints after caller payload hooks", async () => { + const { onPayload } = threadXaiRequest(multiTurnContext(), { + onPayload: (payload) => ({ + ...(payload as object), + store: false, + parallel_tool_calls: true, + previous_response_id: "wrong-response", + include: ["reasoning.encrypted_content"], + }), + }); + expect(await onPayload({}, model)).toEqual({ + store: true, + parallel_tool_calls: false, + previous_response_id: "response_xai_1", + include: ["reasoning.encrypted_content"], + }); + }); +}); diff --git a/packages/cli/README.md b/packages/cli/README.md index 7debfac9..4e0d3bd6 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -46,6 +46,7 @@ cua --print --model openai:gpt-5.5 "..." 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 yutori:n1.5-latest "..." # Named sessions (browser stays alive across calls): @@ -69,7 +70,7 @@ 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`, or `cua models -p yutori`. +`cua models -p meta`, `cua models -p xai`, 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 @@ -87,6 +88,7 @@ Configuration is by environment variable. There is no config file. | `GOOGLE_API_KEY` | Google API key (required when `-m google:…`) | | `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:…`) | | `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 | @@ -94,6 +96,7 @@ Configuration is by environment variable. There is no config file. | `ANTHROPIC_BASE_URL` | override Anthropic base URL | | `GOOGLE_BASE_URL` | override Google base URL | | `META_BASE_URL` | override Meta Model API base URL | +| `XAI_BASE_URL` | override xAI API 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`) | diff --git a/packages/cli/src/cli-harness.ts b/packages/cli/src/cli-harness.ts index 64d2d2eb..a9283522 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 | tzafon | yutori + -p, --provider Filter by provider: openai | anthropic | google | gemini | meta | xai | tzafon | yutori --json Output JSON -h, --help Show this help `; diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 5ed45148..2da5b820 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -54,6 +54,7 @@ Options: anthropic: anthropic:claude-opus-4-7 google: google:gemini-3-flash-preview meta: meta:muse-spark-1.1 + xai: xai:grok-4.5 tzafon: tzafon:tzafon.northstar-cua-fast yutori: yutori:n1.5-latest --thinking Thinking level: off | minimal | low | medium | high | xhigh @@ -103,6 +104,7 @@ Environment: GOOGLE_API_KEY Google API key (required when -m google:…) 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:…) 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 @@ -110,6 +112,7 @@ Environment: ANTHROPIC_BASE_URL Override Anthropic base URL GOOGLE_BASE_URL Override Google base URL META_BASE_URL Override Meta Model API base URL + XAI_BASE_URL Override xAI API 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 diff --git a/packages/cli/src/harness.ts b/packages/cli/src/harness.ts index ac1dbb6a..7a21a4e2 100644 --- a/packages/cli/src/harness.ts +++ b/packages/cli/src/harness.ts @@ -46,7 +46,7 @@ export interface BuildCuaHarnessOptions { /** Maximum tool-result images included from message history per provider request. */ toolResultImageReplayLimit?: CuaAgentHarnessOptions["toolResultImageReplayLimit"]; /** - * Chain OpenAI, Meta, and Tzafon requests through provider-stored response state. Defaults to true. + * Chain OpenAI, Meta, xAI, and Tzafon requests through provider-stored response state. Defaults to true. */ responseThreading?: CuaAgentHarnessOptions["responseThreading"]; /** Optional CUA-level retries around each provider request. Disabled by default. */ diff --git a/packages/cli/test/cli-executor.test.ts b/packages/cli/test/cli-executor.test.ts index 1063b6a8..b47e39da 100644 --- a/packages/cli/test/cli-executor.test.ts +++ b/packages/cli/test/cli-executor.test.ts @@ -23,6 +23,7 @@ const PROVIDER_ENV_KEYS = [ "GOOGLE_API_KEY", "GEMINI_API_KEY", "META_API_KEY", + "XAI_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 d58e852e..eb7c012a 100644 --- a/packages/cli/test/harness-models.test.ts +++ b/packages/cli/test/harness-models.test.ts @@ -10,19 +10,22 @@ describe("resolveCuaModelRef", () => { it("passes provider-qualified refs through", () => { 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"); }); 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"); }); it("throws on unknown bare ids", () => { expect(() => resolveCuaModelRef("does-not-exist")).toThrow(/unknown model/); }); - it("filters the Meta catalog", () => { + 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"]); }); it("treats 'gemini' as an alias for google when filtering", () => { From 34ee9da2e65c7d78db8cb5c0bb9e70d92747c081 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:39:59 +0000 Subject: [PATCH 2/4] Map disabled Grok reasoning to low --- packages/ai/src/models.ts | 2 +- packages/ai/test/computer-tool.integration.test.ts | 7 ++++--- packages/ai/test/models.test.ts | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index 5c63e338..0af26cec 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -301,7 +301,7 @@ function cuaModel(provider: CuaProvider, id: string, name: string): Model { ...base, api: XAI_CUA_RESPONSES_API, baseUrl: "https://api.x.ai/v1", - thinkingLevelMap: { off: null, minimal: "low", xhigh: "high" }, + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high" }, cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 }, contextWindow: 500_000, maxTokens: 500_000, diff --git a/packages/ai/test/computer-tool.integration.test.ts b/packages/ai/test/computer-tool.integration.test.ts index e020f006..88b63adb 100644 --- a/packages/ai/test/computer-tool.integration.test.ts +++ b/packages/ai/test/computer-tool.integration.test.ts @@ -72,7 +72,7 @@ const cases: ProviderCase[] = [ coordinateRange: [0, 1000], requireToolCalls: true, systemPrompt: "Coordinates are normalized from 0 to 1000 relative to the screenshot.", - extraOptions: { reasoningEffort: "low" }, + extraOptions: { reasoningEffort: "off" }, }, { provider: "tzafon", @@ -253,7 +253,7 @@ describe("individual computer action integration", () => { const first = await cuaModels().complete(model, context, { apiKey: xaiApiKey, maxTokens: 1024, - reasoningEffort: "low", + reasoningEffort: "off", }); const click = first.content.find((part) => part.type === "toolCall" && part.name === "click"); expect(click).toBeDefined(); @@ -274,7 +274,7 @@ describe("individual computer action integration", () => { const second = await cuaModels().complete(model, context, { apiKey: xaiApiKey, maxTokens: 1024, - reasoningEffort: "low", + reasoningEffort: "off", onPayload: (value) => { payload = value as Record; }, @@ -284,6 +284,7 @@ describe("individual computer action integration", () => { expect(payload?.previous_response_id).toBe(first.responseId); expect(payload?.store).toBe(true); expect(payload?.parallel_tool_calls).toBe(false); + expect(payload?.reasoning).toEqual({ effort: "low", summary: "auto" }); expect(payload?.include).toEqual(["reasoning.encrypted_content"]); }, 90_000, diff --git a/packages/ai/test/models.test.ts b/packages/ai/test/models.test.ts index 03478d8d..c7992213 100644 --- a/packages/ai/test/models.test.ts +++ b/packages/ai/test/models.test.ts @@ -67,7 +67,7 @@ describe("CUA model refs", () => { expect(grok.baseUrl).toBe("https://api.x.ai/v1"); expect(grok.contextWindow).toBe(500_000); expect(grok.maxTokens).toBe(500_000); - expect(grok.thinkingLevelMap?.off).toBeNull(); + expect(grok.thinkingLevelMap?.off).toBe("low"); expect(grok.thinkingLevelMap?.minimal).toBe("low"); expect(grok.thinkingLevelMap?.xhigh).toBe("high"); }); From 42b057b1165978484f51d086e3248339929e02b4 Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Mon, 13 Jul 2026 23:54:53 +0000 Subject: [PATCH 3/4] Use pi-ai Grok model metadata --- .../reference/discover-models.ts | 4 +- package-lock.json | 645 ++++++++---------- packages/agent/package.json | 4 +- packages/ai/docs/supported-models.md | 4 +- packages/ai/package.json | 2 +- packages/ai/src/models.ts | 34 +- packages/ai/src/providers.ts | 5 - packages/ai/test/models.test.ts | 11 +- packages/ai/test/providers.test.ts | 6 +- packages/cli/package.json | 4 +- 10 files changed, 330 insertions(+), 389 deletions(-) diff --git a/.agents/skills/update-models/reference/discover-models.ts b/.agents/skills/update-models/reference/discover-models.ts index dc0d0259..097c87f4 100644 --- a/.agents/skills/update-models/reference/discover-models.ts +++ b/.agents/skills/update-models/reference/discover-models.ts @@ -840,9 +840,9 @@ const TZAFON_FUNCTION_TOOLS = [ async function annotateCuaSupport(provider: Provider, models: ModelResult[]): Promise { const piProvider = provider === "gemini" ? "google" : provider; - const getModel = await import("@earendil-works/pi-ai").then((mod) => mod.getModel).catch(() => undefined); + const getBuiltinModel = await import("@earendil-works/pi-ai/providers/all").then((mod) => mod.getBuiltinModel).catch(() => undefined); for (const model of models) { - const inRegistry = getModel ? !!getModel(piProvider as never, model.id as never) : false; + const inRegistry = getBuiltinModel ? !!getBuiltinModel(piProvider as never, model.id as never) : false; const localAdapterSupport = localAdapterSupportStatus(provider, model); model.cua = { provider_inference: provider, diff --git a/package-lock.json b/package-lock.json index fdcf3859..15d27b7d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,20 +43,6 @@ } } }, - "node_modules/@aws-crypto/crc32": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-5.2.0.tgz", - "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -132,17 +118,17 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.974.20", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.974.20.tgz", - "integrity": "sha512-7sDi2B2N3mc3nf1nz6FyEx/FCrJ1N1QnBmraHHQNabFaeAh2IaOOLml48/rHOD1bICHgTRkbBgNTvUzEr5Z35g==", + "version": "3.975.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.975.1.tgz", + "integrity": "sha512-8qh/6EYb7hl/ZwVfQufhbMEZs1gQIc7GbdrIf4eprQJ7cv042+74nE6l3YDfyWNzb9iPXb8fRyYSHkNIk5eE6Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@aws-sdk/xml-builder": "^3.972.29", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/core": "^3.24.6", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@aws-sdk/xml-builder": "^3.972.34", + "@aws/lambda-invoke-store": "^0.3.0", + "@smithy/core": "^3.29.2", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "bowser": "^2.11.0", "tslib": "^2.6.2" }, @@ -151,15 +137,15 @@ } }, "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.46.tgz", - "integrity": "sha512-+GPXVS2srMOlH74S+SmC1gVuP2TvUZ0siuC0onKO93q+udP+M72dmY8wJfVQ5CX9z/9X5A1HHwz5yRIGBtskvQ==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.57.tgz", + "integrity": "sha512-1RfJaF7SW1TOnvNGU7kaYjwUf5H3sfm+synGH1bHhRlqcnxCt3szebH3dmKEyY4tuGcbQ6ffzUT89cRitBV8OQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -167,17 +153,17 @@ } }, "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.48", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.48.tgz", - "integrity": "sha512-fA5loSdlocacRxyUXtpoHSMuk5rsIKRDzQYVMnMxjcmFeZshaJlJ8lymy/hYKji6sne/UmNGj5pxuEs6kq/Qcg==", + "version": "3.972.59", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.59.tgz", + "integrity": "sha512-sRCkpTiFnCdQvuaRVjQ6SVoHu6i7RUpurVo1c4F81HWhPvUJ7Wdp5MNtSdX1O29CNXc8em3O5m52hCjVtAD9SA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/node-http-handler": "^4.7.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -185,13 +171,13 @@ } }, "node_modules/@aws-sdk/credential-provider-http/node_modules/@smithy/node-http-handler": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", - "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -199,23 +185,23 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.52", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.52.tgz", - "integrity": "sha512-szg1nnebqC+Svv6Vfsdf6P/QK8x5g/ghG2CKa/1WkHifRnq0BBmDELj2Qnqk9nPsUvEu/OEcYic97CPLpKqF9g==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.973.1.tgz", + "integrity": "sha512-6d8H6ZAh3ZPKZ6fe1nG2OWeZEZPtt9ravoD1dezPdPtsSkJRoxGAnFSHwKT3E/Te6fHE30zRzjV6TD12rvF6yQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/credential-provider-env": "^3.972.46", - "@aws-sdk/credential-provider-http": "^3.972.48", - "@aws-sdk/credential-provider-login": "^3.972.51", - "@aws-sdk/credential-provider-process": "^3.972.46", - "@aws-sdk/credential-provider-sso": "^3.972.51", - "@aws-sdk/credential-provider-web-identity": "^3.972.51", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/credential-provider-imds": "^4.3.7", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-login": "^3.972.63", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -223,16 +209,16 @@ } }, "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.51.tgz", - "integrity": "sha512-csHFsH+/VjnI40oqm1l1OqMY4B4kza36DbfcbHcgcbobgjebasqUbTU34xvwUkvtoNGGizbfyMSlMzJWUPv3dQ==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.63.tgz", + "integrity": "sha512-GREWRrMj0XnNKMaVa/Mauoaui26qBEHu71WWqXbwZOu/jFQOnPZjTf7u0KtGKC8VGa6VUs9kDWGgocrKNLS9vw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -240,21 +226,21 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.54", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.54.tgz", - "integrity": "sha512-vinTSQtziNHxi2nqXF+76jr2sO44q88Ind1qFFVaotNgBaC1rcWDjBug8yoE8n0ov33s21xks9WY5XDHH9SENw==", + "version": "3.972.67", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.67.tgz", + "integrity": "sha512-oYlzWst56rlhhjbYnexwv5hVLYe1cW4liLObhDfxDLI4RAQzleMVHQgQgx7XsC4HKj4e3kjT8v9DId+Pi/dndw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.46", - "@aws-sdk/credential-provider-http": "^3.972.48", - "@aws-sdk/credential-provider-ini": "^3.972.52", - "@aws-sdk/credential-provider-process": "^3.972.46", - "@aws-sdk/credential-provider-sso": "^3.972.51", - "@aws-sdk/credential-provider-web-identity": "^3.972.51", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/credential-provider-imds": "^4.3.7", - "@smithy/types": "^4.14.3", + "@aws-sdk/credential-provider-env": "^3.972.57", + "@aws-sdk/credential-provider-http": "^3.972.59", + "@aws-sdk/credential-provider-ini": "^3.973.1", + "@aws-sdk/credential-provider-process": "^3.972.57", + "@aws-sdk/credential-provider-sso": "^3.973.1", + "@aws-sdk/credential-provider-web-identity": "^3.972.63", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/credential-provider-imds": "^4.4.7", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -262,15 +248,15 @@ } }, "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.46", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.46.tgz", - "integrity": "sha512-VUoNFBIjWrUN8NbFiQiuxQEgFjvziAlBRPK+ddh27aj65gk0BYu6bLZnrdrNZwpW6vAihtSUtEMQ1PUJ32QRPA==", + "version": "3.972.57", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.57.tgz", + "integrity": "sha512-TiVQhuU0pbhIZAUZacbPHMyzrIdiH+lnx+PMY/Pu/b93dJrq3wdZwzUJ0TPpvNxaqbHsxJvQZW3/h/beLiKq7Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -278,17 +264,17 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.51.tgz", - "integrity": "sha512-60qhpQcSDIKIr0AuBlmJezKX0b5nbJPCINiR49N9yJXrEI5tTRwsXVBr0IdSvvsNJyqgiINyoBd++Ed0yvggbw==", + "version": "3.973.1", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.973.1.tgz", + "integrity": "sha512-3foTZUJ4821Ij60X7K3NJroygiZLnbBmarN+T//O2cjkISan90zElN3NBmgSlDrTQ7Gs6z/yO8V7h60QNcDZHQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/token-providers": "3.1065.0", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/token-providers": "3.1083.0", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -296,16 +282,16 @@ } }, "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/token-providers": { - "version": "3.1065.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1065.0.tgz", - "integrity": "sha512-qdHQntq82gMqG6Tf8xrgmhJxacaYkxW4PEeDg/ISMVJ84EWe7iD6JyCTgbyox3uNDH6vqEJ8GUiTaXCq307zVw==", + "version": "3.1083.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.1083.0.tgz", + "integrity": "sha512-s0woKnxuHrExLc5L2ArIH5BMkbonHPtt+5hSBM8oknp9M6QTuUmmAmJ2E0EdzCGONrO+8+ADPqvv6UX0nNcc7A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -313,16 +299,16 @@ } }, "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.51", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.51.tgz", - "integrity": "sha512-0X5eWsUIp8ItRJeJBBrhQAPzc9AQelDetRTVTsycCAISCCzM17R4hs/vFAPeQ0o0B35sciLiqe/Pwmml909cZA==", + "version": "3.972.63", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.63.tgz", + "integrity": "sha512-8qZLFhM69eKcS37m459ctPR05Qimycm/74OPVioe6wNZabMT54GYhwBju0+J656RkMasNSawWQu+c8CmBe3TUQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/nested-clients": "^3.997.19", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/nested-clients": "^3.997.31", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -330,14 +316,14 @@ } }, "node_modules/@aws-sdk/eventstream-handler-node": { - "version": "3.972.21", - "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.21.tgz", - "integrity": "sha512-mVC0hOmwGJmNFezZ+wM8Sqfap/LjsMavEf2Evl0YWrLAcrdZOEdjnY8nRvgakVViWJSGm2eJxLuPVHGdeV06kA==", + "version": "3.972.26", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-handler-node/-/eventstream-handler-node-3.972.26.tgz", + "integrity": "sha512-RE1fu7Nn05vG0EUJM+8Sde2GFecC658WGaC/asPzLF6K4x3H5ZaDBcQtHRE67Gdgb1VZpyUUliYejHFK1qt0Uw==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -345,14 +331,14 @@ } }, "node_modules/@aws-sdk/middleware-eventstream": { - "version": "3.972.17", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.17.tgz", - "integrity": "sha512-tdbnXbw73ww62ABWP0G0Z/euvFowEEvAoi/zG4NaZo7HJFpfGho/Z65HyVzkJLT1cMsUregr4pTyxljlarT0wA==", + "version": "3.972.22", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-eventstream/-/middleware-eventstream-3.972.22.tgz", + "integrity": "sha512-jtkgmhevnpzC1WeS+Y/sgymYbaQ6qg7pVOUl5cUT/8MiLptqrtnXQlNV80m+j2WIx5MIL7kVHIZNxxcK2tfUEQ==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -360,17 +346,17 @@ } }, "node_modules/@aws-sdk/middleware-websocket": { - "version": "3.972.28", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.28.tgz", - "integrity": "sha512-SCW06Zjugn86pq7+dxGnFcyWJuEWHT753HTU/Vj/OzVxP+NoShwdAr4ynxAcvWL883OgRVbSqW3ohnjIxwXjjw==", + "version": "3.972.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-websocket/-/middleware-websocket-3.972.39.tgz", + "integrity": "sha512-CS1spxRSezmTmI3PD+3Xrnp6KryTSEz0EefA8u6uGd0s2I0uXseWHALDI/03Wi0IUczXNWo2QrZEaHDuJNby/Q==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -378,20 +364,18 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.997.19", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.19.tgz", - "integrity": "sha512-P2Otgf15GBJMKzG6j5Ddf7w+Kz6z2jvesMy874TD3jlMfDWNK7clJeUd7hgigdeVOotjoUP4emcTWVdS9sfZDw==", + "version": "3.997.31", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.997.31.tgz", + "integrity": "sha512-BDHTpwcsZHEBNEJzOg/B1BkFYJxAXY50dau/NyVWs3d51F0WgIUGSWZot/Os+N3KpDhXeaXnz37mWffAvduREw==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.974.20", - "@aws-sdk/signature-v4-multi-region": "^3.996.33", - "@aws-sdk/types": "^3.973.12", - "@smithy/core": "^3.24.6", - "@smithy/fetch-http-handler": "^5.4.6", - "@smithy/node-http-handler": "^4.7.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/core": "^3.975.1", + "@aws-sdk/signature-v4-multi-region": "^3.996.39", + "@aws-sdk/types": "^3.974.0", + "@smithy/core": "^3.29.2", + "@smithy/fetch-http-handler": "^5.6.4", + "@smithy/node-http-handler": "^4.9.4", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -399,13 +383,13 @@ } }, "node_modules/@aws-sdk/nested-clients/node_modules/@smithy/node-http-handler": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.7.7.tgz", - "integrity": "sha512-ZAFvHXrEk6K180EVhmZVg8GU5pUH5BSFqRs27JW3j1qEFx9YyYwWFx17x/MHcjALYimGAji7qEOlF1++be+G5A==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.9.5.tgz", + "integrity": "sha512-bNqdxTQTxmLbomSmlkZFz8L6B/feQ2HHzw4L2zY7Ecp2XffYAZq2uzdWDdxJHJFbEvqd+SRuluJso0P8+xPdbw==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -413,14 +397,14 @@ } }, "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.996.33", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.33.tgz", - "integrity": "sha512-Hn0RThJEbyOZWV2PV9Z4YD3nitGPxybmyU17dSe9b61WOBcKnqS0WTtM3c1zyZq9WnGiyrfi/i+UBPUk7cM8Ug==", + "version": "3.996.39", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.996.39.tgz", + "integrity": "sha512-8+srXqYIF8KYMLC4FxMLEM5Ek7kUNibJu1R4m8/fUhhNYIZZz26oGtKkCr8I/HiG2fFQxBvaGgQZT4/mqRCSnA==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/types": "^3.973.12", - "@smithy/signature-v4": "^5.4.6", - "@smithy/types": "^4.14.3", + "@aws-sdk/types": "^3.974.0", + "@smithy/signature-v4": "^5.6.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -445,12 +429,12 @@ } }, "node_modules/@aws-sdk/types": { - "version": "3.973.12", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.12.tgz", - "integrity": "sha512-43ajd1NF0RMgX5k0hxCNUyEdrtFUsb2aHT2QvpktSC/2Eyb2Jr/JPVqdp0XIoaHWikZJq5tNWSLO6kB5q2eMCA==", + "version": "3.974.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.974.0.tgz", + "integrity": "sha512-QIBrw90CDm4O0UaIIzkU6DrFdeJzEb2Va5EPEVpyldj6sHJxB6cshhStJuhZxk3wR3PmjJlYsjPmY1kNb+KGBg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -458,9 +442,9 @@ } }, "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.7", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.7.tgz", - "integrity": "sha512-M0D6oIpohdNHjc7udzTHEQyot0+0iuA36jc2I9Hps+f/GtKi2HO/pyijQnCnNcwZqLB5+rtn81z3eZK/GyjAmA==", + "version": "3.965.8", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.8.tgz", + "integrity": "sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -470,13 +454,12 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.29", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.29.tgz", - "integrity": "sha512-fk0niuGFxfi8yIJuMVM4mhwObkiQSuwZFj3tAPrLVx64Pk3BkrEIpqjzHKY4hKoEBUD6Jg/S74Zj9jy+5F3DnQ==", + "version": "3.972.34", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.34.tgz", + "integrity": "sha512-wHhWL1y7sN3enBA8POrPpQM5jCcmu2ozyhbRei4c8OjVcEaEs6yLucLa/pla457ggS/ysuy7bosagz3HaJkZXA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.3", - "fast-xml-parser": "5.7.3", + "@smithy/types": "^4.16.0", "tslib": "^2.6.2" }, "engines": { @@ -484,9 +467,9 @@ } }, "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", - "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.3.0.tgz", + "integrity": "sha512-sl4Bm6yiMNYrZKkqqDFWN0UfnWhlS8ivKxrYl+6t0gCLrqr8y3B2IqZZbFRkfaVVp7C/baApyh71P+LeE1A2sQ==", "license": "Apache-2.0", "engines": { "node": ">=18.0.0" @@ -570,12 +553,12 @@ } }, "node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.3.tgz", - "integrity": "sha512-3qw0/GeRQBU/nlGjDe5Yb7ePKTmoxefx2YxyKMFAviFUMXpFexBG/hS7mBtwFahFvzrrTPPoRT6sFIDjwoDWPQ==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", + "integrity": "sha512-Lvn89ko42h5ETUb6Z0Ku6ldskEqXaTdQBYvSa0+7bdG9V6rUEpXptv5e0OVZ1HDcvi8s6/2lGCQWsxKX+DFHNw==", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.80.3", + "@earendil-works/pi-ai": "^0.80.6", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" @@ -585,9 +568,9 @@ } }, "node_modules/@earendil-works/pi-ai": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.3.tgz", - "integrity": "sha512-jPZLMeGL5kkMSEAwAklfXTMHqZvfhsJtCCpKGIr5Duk7mc0n4skjB1dugk7y0z3z8ZHIUCmPAWHdyDqgUz5vdA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", + "integrity": "sha512-7xfLk8sANBp+bpPEbjoOZTbPxsa+++b1JXAoSJsNa3vbs9AHHEclmvg54XLQcxH+fuwaeti/g2jeIfJ+mVYLpA==", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", @@ -610,15 +593,15 @@ } }, "node_modules/@earendil-works/pi-coding-agent": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.3.tgz", - "integrity": "sha512-TIggw9gCXpA+Ph7OjdTA7ka2NPwTVuPmy39KDSyUzaKq8VvHfMGR7vtRz4JB7Um/RMRblmzhu4p9tUCk6MTgGA==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-coding-agent/-/pi-coding-agent-0.80.6.tgz", + "integrity": "sha512-vcfD6tOk402isLl3Cm/qbn2O10TvgroMp1+/fEGM24ZdvETFCdOYv5VZ7m59EI5fPsjfSJh+CpQ5bhBrhfOg7g==", "hasShrinkwrap": true, "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "^0.80.3", - "@earendil-works/pi-ai": "^0.80.3", - "@earendil-works/pi-tui": "^0.80.3", + "@earendil-works/pi-agent-core": "^0.80.6", + "@earendil-works/pi-ai": "^0.80.6", + "@earendil-works/pi-tui": "^0.80.6", "@silvia-odwyer/photon-node": "0.3.4", "chalk": "5.6.2", "cross-spawn": "7.0.6", @@ -1081,11 +1064,11 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-agent-core": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.3.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-agent-core/-/pi-agent-core-0.80.6.tgz", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "^0.80.3", + "@earendil-works/pi-ai": "^0.80.6", "ignore": "7.0.5", "typebox": "1.1.38", "yaml": "2.9.0" @@ -1095,8 +1078,8 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.3.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-ai/-/pi-ai-0.80.6.tgz", "license": "MIT", "dependencies": { "@anthropic-ai/sdk": "0.91.1", @@ -1119,8 +1102,8 @@ } }, "node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-tui": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -2428,9 +2411,9 @@ } }, "node_modules/@earendil-works/pi-tui": { - "version": "0.80.3", - "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.3.tgz", - "integrity": "sha512-2BJI6qwRQfnM0Q7seL1+SbacU/jRRjBnN7Hu3n9BjAn7/s5FaBNnvdD1qBQYRsFTHfjqMaDsjYqanPyqwXj99w==", + "version": "0.80.6", + "resolved": "https://registry.npmjs.org/@earendil-works/pi-tui/-/pi-tui-0.80.6.tgz", + "integrity": "sha512-bSuzS4EVSqEPj/Qr/p9eqCESfKsGuDNbl77EGci8Iaqqt/C/XCBZL1MjXaxSWW1NsT5afjp/Cb0NTPzOLv/aPA==", "license": "MIT", "dependencies": { "get-east-asian-width": "1.6.0", @@ -3449,18 +3432,6 @@ "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@nodable/entities": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@nodable/entities/-/entities-2.1.1.tgz", - "integrity": "sha512-Pig3HxDIoMgjdEH8OCf/dkcTmLFjJRjWuq8jSnklu284/TKOPibSRERmOykiwmyXTtv61mP+44f3GMx0tLAyjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/nodable" - } - ], - "license": "MIT" - }, "node_modules/@onkernel/cua-agent": { "resolved": "packages/agent", "link": true @@ -3491,9 +3462,9 @@ } }, "node_modules/@opentelemetry/semantic-conventions": { - "version": "1.42.0", - "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.42.0.tgz", - "integrity": "sha512-icc5xCzndZfhuJMy5oqk5AvloWquR7jtae74qzpkKkhGp8BivK+oCcEXgGnjCdTfp8hA44l+w8gE8yYJbocJJw==", + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.43.0.tgz", + "integrity": "sha512-eSYWTm620tTk45EKSedaUL8MFYI8hW164hIXsgIHyxu3VobUB3fFCu5t0hQby6OoWRPsG1KkKUG2M5UadiLiVg==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -3511,46 +3482,59 @@ }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/base64": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/codegen": { - "version": "2.0.4", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/eventemitter": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/fetch": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", "license": "BSD-3-Clause", "dependencies": { - "@protobufjs/aspromise": "^1.1.1", - "@protobufjs/inquire": "^1.1.0" + "@protobufjs/aspromise": "^1.1.1" } }, "node_modules/@protobufjs/float": { "version": "1.0.2", - "license": "BSD-3-Clause" - }, - "node_modules/@protobufjs/inquire": { - "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/path": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/pool": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", "license": "BSD-3-Clause" }, "node_modules/@protobufjs/utf8": { - "version": "1.1.0", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", "license": "BSD-3-Clause" }, "node_modules/@quansync/fs": { @@ -4181,13 +4165,12 @@ ] }, "node_modules/@smithy/core": { - "version": "3.24.6", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.24.6.tgz", - "integrity": "sha512-wBXDRup6UU97VKyaiRo8AssnfStPtG0oAAfpq/bC0a1YYau8pM86YB4kM6ccoVi1mS8l/UHbn9oDM+7uozr/ug==", + "version": "3.29.3", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.29.3.tgz", + "integrity": "sha512-L+Ys6ecjk5vwPMAKHBpPKlJ3DkqwNcnfEISXBZIsVvWG/XKXfsAP8mwIYlTeLcd2ElHdesPI8OuOmJSFAPhm6A==", "license": "Apache-2.0", "dependencies": { - "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4195,13 +4178,13 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.8.tgz", - "integrity": "sha512-5cAM+KZC02sTqDt6NaLXyu50M/GNMd1eTzDVR8Lb0BBsVtu7RWHo47VPPEEv1vt3Yub6uzr+M5FHC+GtoT0USg==", + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.4.8.tgz", + "integrity": "sha512-q9J7JTiXrAhB8sDp4px97uEPT7CwKH61Co78grdNQvU8QZAdiuaSRhP0tUVf2ogy36RZTrlMU1rBmDEH+cnkiA==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4209,13 +4192,13 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.6.tgz", - "integrity": "sha512-FEwEYJ1jlBKdhe9TPzfghEi1bP55ZeEImlDkEa62bBBYzUcnB6RUCyuiS2mqKt6ZVjUbBgcNhzfIctH+Hevx9g==", + "version": "5.6.5", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.6.5.tgz", + "integrity": "sha512-SuqeisTyPoiIPtIYru/sGxGyXzmZ+8nnFOhC+qRPglt06Ebd1yH//CDltZB2J/3WBNVhwfUaZ0EtHB3cm2X32g==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4249,13 +4232,13 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.4.6.tgz", - "integrity": "sha512-Ojg4B6oIDlIr1R86xCDJt1zJWnYa0VINmqdjfe9qxWjdRivHalZ3iSlQgVqYbW0MdpFOC5XfHEWsnbmdnpIILQ==", + "version": "5.6.4", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.6.4.tgz", + "integrity": "sha512-B89bpf2t/y/wia6LZ+4JfHXYQT9PnVftsH05rgJKKIStS7r/4XSs9HOjtPoLtgcA6HCW9jVqX5DBbq7E0PAkiQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.24.6", - "@smithy/types": "^4.14.3", + "@smithy/core": "^3.29.3", + "@smithy/types": "^4.16.1", "tslib": "^2.6.2" }, "engines": { @@ -4263,9 +4246,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.3", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.3.tgz", - "integrity": "sha512-YupL0ZWmFtJexUN2cHzkvvF/b9pKrtAIfT1o7/oY/Ppu8IYeZ+lDPM5vZdQJaSeA132dJCqojjGC9NhXeF71VQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.16.1.tgz", + "integrity": "sha512-0JFs3V2y2M9tKW5na/qxe69Zv+uxLMO7QBbhxF/FHu/Gp2NFZAAL9tWl9PU02xxo07pb3G9FTyjNc6D5uZrJIg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -4352,6 +4335,8 @@ }, "node_modules/@types/retry": { "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "license": "MIT" }, "node_modules/@tzafon/lightcone": { @@ -4477,6 +4462,8 @@ }, "node_modules/agent-base": { "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -4492,18 +4479,6 @@ "node": ">=14" } }, - "node_modules/anynum": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/anynum/-/anynum-1.0.0.tgz", - "integrity": "sha512-xjR9/zBVnUOP6ztMIIgShjsxui80nQUQH+5xJnvrYLs+90bF25/KJqaAi8mk+B4RDtX1Nspi6fmp4YTEts8SfA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -4534,6 +4509,8 @@ }, "node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -4552,6 +4529,8 @@ }, "node_modules/bignumber.js": { "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", "engines": { "node": "*" @@ -4575,6 +4554,8 @@ }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, "node_modules/cac": { @@ -4616,6 +4597,8 @@ }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "license": "MIT", "engines": { "node": ">= 12" @@ -4685,6 +4668,8 @@ }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { "safe-buffer": "^5.0.1" @@ -4771,45 +4756,10 @@ }, "node_modules/extend": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/fast-xml-builder": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.2.0.tgz", - "integrity": "sha512-00aAWieqff+ZJhsXA4g1g7M8k+7AYoMUUHF+/zFb5U6Uv/P0Vl4QZo84/IcufzYalLuEj9928bXN9PbbFzMF0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "path-expression-matcher": "^1.5.0", - "xml-naming": "^0.1.0" - } - }, - "node_modules/fast-xml-parser": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.7.3.tgz", - "integrity": "sha512-C0AaNuC+mscy6vrAQKAc/rMq+zAPHodfHGZu4sGVehvAQt/JLG1O5zEcYcXSY5zSqr4YVgxsB+pHXTq0i7eDlg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "@nodable/entities": "^2.1.0", - "fast-xml-builder": "^1.1.7", - "path-expression-matcher": "^1.5.0", - "strnum": "^2.2.3" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -4830,6 +4780,8 @@ }, "node_modules/fetch-blob": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", "funding": [ { "type": "github", @@ -4851,6 +4803,8 @@ }, "node_modules/formdata-polyfill": { "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -4875,7 +4829,9 @@ } }, "node_modules/gaxios": { - "version": "7.1.4", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.2.0.tgz", + "integrity": "sha512-CUVb4wcYe+771XevyH6HtGmXFAGGKkIC3kswAP8Z1JCe0j80JMaTPZH930DWFrvo0atjh18Arc0pEyUCWa5bfg==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", @@ -4888,6 +4844,8 @@ }, "node_modules/gcp-metadata": { "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { "gaxios": "^7.0.0", @@ -4924,7 +4882,9 @@ } }, "node_modules/google-auth-library": { - "version": "10.6.2", + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.9.0.tgz", + "integrity": "sha512-xtvUqvINPhTaBm7nXqlYPcrMHJPm1lCNdSovxnKKhTm+4JsvQ+KGVYJViLoH9Yxu8w+T0Qv5HubzYT9BLrppJg==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", @@ -4940,6 +4900,8 @@ }, "node_modules/google-logging-utils": { "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -4954,6 +4916,8 @@ }, "node_modules/http-proxy-agent": { "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "license": "MIT", "dependencies": { "agent-base": "^7.1.0", @@ -4965,6 +4929,8 @@ }, "node_modules/https-proxy-agent": { "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -5016,6 +4982,8 @@ }, "node_modules/json-bigint": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", "license": "MIT", "dependencies": { "bignumber.js": "^9.0.0" @@ -5036,6 +5004,8 @@ }, "node_modules/jwa": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", @@ -5045,6 +5015,8 @@ }, "node_modules/jws": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", "license": "MIT", "dependencies": { "jwa": "^2.0.1", @@ -5053,6 +5025,8 @@ }, "node_modules/long": { "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", "license": "Apache-2.0" }, "node_modules/loupe": { @@ -5118,6 +5092,9 @@ }, "node_modules/node-domexception": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", "funding": [ { "type": "github", @@ -5135,6 +5112,8 @@ }, "node_modules/node-fetch": { "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -5200,6 +5179,8 @@ }, "node_modules/p-retry": { "version": "4.6.2", + "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-4.6.2.tgz", + "integrity": "sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==", "license": "MIT", "dependencies": { "@types/retry": "0.12.0", @@ -5211,23 +5192,10 @@ }, "node_modules/partial-json": { "version": "0.1.7", + "resolved": "https://registry.npmjs.org/partial-json/-/partial-json-0.1.7.tgz", + "integrity": "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA==", "license": "MIT" }, - "node_modules/path-expression-matcher": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.5.0.tgz", - "integrity": "sha512-cbrerZV+6rvdQrrD+iGMcZFEiiSrbv9Tfdkvnusy6y0x0GKBXREFg/Y65GhIfm0tnLntThhzCnfKwp1WRjeCyQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", @@ -5295,22 +5263,23 @@ } }, "node_modules/protobufjs": { - "version": "7.5.5", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz", + "integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==", "hasInstallScript": true, "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", - "@protobufjs/codegen": "^2.0.4", - "@protobufjs/eventemitter": "^1.1.0", - "@protobufjs/fetch": "^1.1.0", + "@protobufjs/codegen": "^2.0.5", + "@protobufjs/eventemitter": "^1.1.1", + "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", - "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", - "@protobufjs/utf8": "^1.1.0", + "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", - "long": "^5.0.0" + "long": "^5.3.2" }, "engines": { "node": ">=12.0.0" @@ -5345,6 +5314,8 @@ }, "node_modules/retry": { "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", "license": "MIT", "engines": { "node": ">= 4" @@ -5491,6 +5462,8 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "funding": [ { "type": "github", @@ -5618,21 +5591,6 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/strnum": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.4.0.tgz", - "integrity": "sha512-sHrVyWWdq28RbhjuJdZsA1SnGRJV6NiXbk6AXBxDOsgAcA+lmpUZCYjOdLBxkXMwis6RRe7dlZt4VlIWFVzkmg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "anynum": "^1.0.0" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -6036,6 +5994,8 @@ }, "node_modules/web-streams-polyfill": { "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", "license": "MIT", "engines": { "node": ">= 8" @@ -6077,21 +6037,6 @@ } } }, - "node_modules/xml-naming": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/xml-naming/-/xml-naming-0.1.0.tgz", - "integrity": "sha512-k8KO9hrMyNk6tUWqUfkTEZbezRRpONVOzUTnc97VnCvyj6Tf9lyUR9EDAIeiVLv56jsMcoXEwjW8Kv5yPY52lw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", @@ -6128,8 +6073,8 @@ "version": "0.6.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-agent-core": "0.80.3", - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-agent-core": "0.80.6", + "@earendil-works/pi-ai": "0.80.6", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" @@ -6144,7 +6089,7 @@ "version": "0.6.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-ai": "0.80.6", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, @@ -6158,8 +6103,8 @@ "version": "0.4.0", "license": "MIT", "dependencies": { - "@earendil-works/pi-coding-agent": "0.80.3", - "@earendil-works/pi-tui": "0.80.3", + "@earendil-works/pi-coding-agent": "0.80.6", + "@earendil-works/pi-tui": "0.80.6", "@onkernel/cua-agent": "0.6.0", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0" diff --git a/packages/agent/package.json b/packages/agent/package.json index 6086cc38..1b332e04 100644 --- a/packages/agent/package.json +++ b/packages/agent/package.json @@ -40,8 +40,8 @@ "typecheck": "tsc -b" }, "dependencies": { - "@earendil-works/pi-agent-core": "0.80.3", - "@earendil-works/pi-ai": "0.80.3", + "@earendil-works/pi-agent-core": "0.80.6", + "@earendil-works/pi-ai": "0.80.6", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0", "sharp": "^0.34.5" diff --git a/packages/ai/docs/supported-models.md b/packages/ai/docs/supported-models.md index c6a494d3..2151cc7f 100644 --- a/packages/ai/docs/supported-models.md +++ b/packages/ai/docs/supported-models.md @@ -83,8 +83,8 @@ Grok 4.5 uses xAI's OpenAI-compatible Responses API with screenshot input and ordinary function tools. xAI does not define a native computer tool or coordinate protocol, so CUA supplies the browser actions and normalized coordinate instructions. Tool loops continue through `previous_response_id`. -The catalog records xAI's standard-context prices; xAI doubles token prices -when input exceeds 200k tokens. +CUA adds xAI's doubled token-price tier above 200k input tokens to pi-ai's +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). diff --git a/packages/ai/package.json b/packages/ai/package.json index 49e9ce3c..e349b655 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.3", + "@earendil-works/pi-ai": "0.80.6", "@tzafon/lightcone": "^0.7.0", "openai": "^6.26.0" }, diff --git a/packages/ai/src/models.ts b/packages/ai/src/models.ts index 0af26cec..320569dd 100644 --- a/packages/ai/src/models.ts +++ b/packages/ai/src/models.ts @@ -108,9 +108,9 @@ const CUA_MODEL_OVERRIDES: Record[]> = { openai: [], anthropic: [], google: [], - // pi-ai 0.80.3 predates these providers' current models.dev catalog entries. + // pi-ai 0.80.6 predates Meta's models.dev catalog entry. meta: [cuaModel("meta", "muse-spark-1.1", "Muse Spark 1.1")], - xai: [cuaModel("xai", "grok-4.5", "Grok 4.5")], + xai: [], 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"), @@ -214,8 +214,17 @@ export function routeCuaApi(model: Model): Model { if (model.provider === "meta" && model.api !== META_RESPONSES_API) { return { ...model, api: META_RESPONSES_API }; } - if (model.provider === "xai" && model.api !== XAI_CUA_RESPONSES_API) { - return { ...model, api: XAI_CUA_RESPONSES_API }; + if (model.provider === "xai" && model.id === "grok-4.5") { + return { + ...model, + api: XAI_CUA_RESPONSES_API, + thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high" }, + cost: { + ...model.cost, + tiers: [{ inputTokensAbove: 200_000, input: 4, output: 12, cacheRead: 1, cacheWrite: 0 }], + }, + compat: { supportsDeveloperRole: false, sendSessionIdHeader: false, supportsLongCacheRetention: false }, + }; } return model; } @@ -264,12 +273,12 @@ function isCuaFamilyMatch(id: string, family: string): boolean { .every((segment) => /^\d+$/.test(segment)); } -function cuaModel(provider: CuaProvider, id: string, name: string): Model { +function cuaModel(provider: Exclude, id: string, name: string): Model { const base = { id, name, provider, - reasoning: provider === "openai" || provider === "anthropic" || provider === "google" || provider === "meta" || provider === "xai", + reasoning: provider === "openai" || provider === "anthropic" || provider === "google" || provider === "meta", input: ["text", "image"], cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 }, } satisfies Partial>; @@ -294,19 +303,6 @@ function cuaModel(provider: CuaProvider, id: string, name: string): Model { maxTokens: 128_000, compat: { supportsDeveloperRole: true, sendSessionIdHeader: false, supportsLongCacheRetention: true }, } as Model; - case "xai": - // xAI doubles token prices above 200k input tokens; pi's flat Model - // cost fields record the standard-context rates. - return { - ...base, - api: XAI_CUA_RESPONSES_API, - baseUrl: "https://api.x.ai/v1", - thinkingLevelMap: { off: "low", minimal: "low", xhigh: "high" }, - cost: { input: 2, output: 6, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 500_000, - maxTokens: 500_000, - compat: { supportsDeveloperRole: false, sendSessionIdHeader: false, supportsLongCacheRetention: false }, - } as Model; case "tzafon": return { ...base, api: "tzafon-responses", baseUrl: "https://api.lightcone.ai", contextWindow: 128_000, maxTokens: 4_096 } as Model; case "yutori": diff --git a/packages/ai/src/providers.ts b/packages/ai/src/providers.ts index 2cfbc824..4eafc3a8 100644 --- a/packages/ai/src/providers.ts +++ b/packages/ai/src/providers.ts @@ -116,11 +116,6 @@ function withGoogleEnvKeys(base: Provider): Provider { function withXaiCuaResponses(base: Provider): Provider { return { ...base, - getModels: () => { - const models = new Map(base.getModels().map((model) => [model.id, model])); - for (const model of cuaOverrideModels("xai")) models.set(model.id, model); - return [...models.values()]; - }, stream: (model: Model, context: Context, options?: StreamOptions) => model.api === XAI_CUA_RESPONSES_API ? streamXaiResponses(model as never, context, options) diff --git a/packages/ai/test/models.test.ts b/packages/ai/test/models.test.ts index c7992213..0f9e8f08 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, + cuaOverrideModels, findCuaAnnotation, formatCuaModelRef, getCuaModel, @@ -60,16 +61,20 @@ describe("CUA model refs", () => { expect(muse.contextWindow).toBe(1_048_576); expect(muse.maxTokens).toBe(128_000); expect(muse.thinkingLevelMap?.off).toBeNull(); + }); + it("uses pi-ai's Grok catalog entry with CUA routing overrides", () => { + expect(cuaOverrideModels("xai")).toEqual([]); const grok = getCuaModel("xai:grok-4.5"); expect(grok.provider).toBe("xai"); expect(grok.api).toBe(xai.XAI_CUA_RESPONSES_API); expect(grok.baseUrl).toBe("https://api.x.ai/v1"); expect(grok.contextWindow).toBe(500_000); expect(grok.maxTokens).toBe(500_000); - expect(grok.thinkingLevelMap?.off).toBe("low"); - expect(grok.thinkingLevelMap?.minimal).toBe("low"); - expect(grok.thinkingLevelMap?.xhigh).toBe("high"); + expect(grok.thinkingLevelMap).toEqual({ off: "low", minimal: "low", xhigh: "high" }); + expect(grok.cost.tiers).toEqual([ + { inputTokensAbove: 200_000, input: 4, output: 12, cacheRead: 1, cacheWrite: 0 }, + ]); }); it("loads supported custom provider models without explicit registration", () => { diff --git a/packages/ai/test/providers.test.ts b/packages/ai/test/providers.test.ts index fad6e887..4afd551c 100644 --- a/packages/ai/test/providers.test.ts +++ b/packages/ai/test/providers.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { createCuaModels, cuaModels, tzafon, xai, yutori } from "../src/index"; +import { createCuaModels, cuaModels, tzafon, yutori } from "../src/index"; import { OPENAI_CUA_RESPONSES_API } from "../src/providers/openai/provider"; const TZAFON_RESPONSES_API = tzafon.TZAFON_RESPONSES_API; @@ -16,10 +16,10 @@ describe("createCuaModels", () => { } }); - it("lists the CUA-only provider catalogs", () => { + 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(xai.XAI_CUA_RESPONSES_API); + expect(models.getModel("xai", "grok-4.5")?.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/cli/package.json b/packages/cli/package.json index 8a5caf4e..3eeee438 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.3", - "@earendil-works/pi-tui": "0.80.3", + "@earendil-works/pi-coding-agent": "0.80.6", + "@earendil-works/pi-tui": "0.80.6", "@onkernel/cua-agent": "0.6.0", "@onkernel/cua-ai": "0.6.0", "@onkernel/sdk": "0.49.0" From 4be716817a0a994c532f2a830571413a35f44f2c Mon Sep 17 00:00:00 2001 From: rgarcia <72655+rgarcia@users.noreply.github.com> Date: Tue, 14 Jul 2026 00:04:17 +0000 Subject: [PATCH 4/4] Document xAI tool contracts --- packages/ai/src/providers/xai/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/ai/src/providers/xai/index.ts b/packages/ai/src/providers/xai/index.ts index 77349ccc..f98ecab6 100644 --- a/packages/ai/src/providers/xai/index.ts +++ b/packages/ai/src/providers/xai/index.ts @@ -19,6 +19,13 @@ export { } from "./provider"; export type { XaiResponsesOptions } from "./provider"; +/** + * Instructions for driving Grok through CUA's custom function tools. + * + * xAI documents {@link https://docs.x.ai/developers/model-capabilities/images/understanding | image input} + * and {@link https://docs.x.ai/developers/tools/function-calling | function calling}, + * but does not define a native computer tool or coordinate space. + */ export const XAI_COMPUTER_INSTRUCTIONS = `You control a Kernel cloud browser through individual browser tools. Coordinates are normalized from 0 to 1000 relative to the most recent screenshot, with (0, 0) at the top left and (1000, 1000) at the bottom right. Base each action on the latest observed state and request a screenshot when you need a fresh view.`; /** @@ -30,6 +37,10 @@ export function buildXaiSystemPrompt(opts: { suffix?: string } = {}): string { /** * Return the normalized coordinate contract CUA supplies to Grok function tools. + * + * This is a CUA-defined contract because xAI's documented + * {@link https://docs.x.ai/developers/tools/function-calling | function tools} + * do not prescribe browser coordinates. */ export function coordinateSystem(): ComputerToolCoordinateSystem { return { type: "normalized", range: [0, 1000] };