llm-cli-gateway 2.11.1 → 2.12.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/async-job-orchestration/SKILL.md +288 -0
- package/.agents/skills/implement-review-fix/SKILL.md +154 -0
- package/.agents/skills/multi-llm-review/SKILL.md +174 -0
- package/.agents/skills/public-demo-session/SKILL.md +100 -0
- package/.agents/skills/secure-orchestration/SKILL.md +227 -0
- package/.agents/skills/session-workflow/SKILL.md +271 -0
- package/CHANGELOG.md +46 -0
- package/README.md +48 -19
- package/dist/acp/provider-registry.js +5 -5
- package/dist/api-provider.d.ts +7 -0
- package/dist/api-provider.js +7 -0
- package/dist/api-request.js +1 -0
- package/dist/async-job-manager.d.ts +11 -1
- package/dist/async-job-manager.js +44 -3
- package/dist/config.d.ts +2 -0
- package/dist/config.js +3 -0
- package/dist/index.d.ts +38 -2
- package/dist/index.js +609 -156
- package/dist/job-store.d.ts +48 -1
- package/dist/job-store.js +184 -0
- package/dist/provider-codegen.js +3 -0
- package/dist/provider-tool-capabilities.js +7 -7
- package/dist/upstream-contracts.d.ts +1 -0
- package/dist/upstream-contracts.js +128 -21
- package/dist/validation-orchestrator.d.ts +5 -2
- package/dist/validation-orchestrator.js +71 -5
- package/dist/validation-receipt.d.ts +68 -0
- package/dist/validation-receipt.js +245 -0
- package/dist/validation-report.d.ts +4 -2
- package/dist/validation-report.js +18 -1
- package/dist/validation-tools.js +58 -9
- package/npm-shrinkwrap.json +8 -8
- package/package.json +11 -4
- package/dist/xai-api-provider.d.ts +0 -43
- package/dist/xai-api-provider.js +0 -191
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
> _"Without consultation, plans are frustrated, but with many counselors they succeed."_
|
|
10
10
|
> — Proverbs 15:22 (LSB)
|
|
11
11
|
|
|
12
|
-
A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok,
|
|
12
|
+
A Model Context Protocol (MCP) gateway for running Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and Devin CLIs from one MCP endpoint, with durable async jobs, session continuity, cache-aware prompting, observability, and personal-appliance setup tooling.
|
|
13
13
|
|
|
14
|
-
**Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across
|
|
14
|
+
**Why developers try it:** one local MCP endpoint for cross-LLM validation, multi-agent coding workflows, and repeatable assistant-led setup across six provider CLIs.
|
|
15
15
|
|
|
16
16
|
**Current signals:** CI and security workflows pass on `main`, OpenSSF Scorecard is published, OpenSSF Best Practices is passing, releases use Sigstore signing, and the package is MIT licensed.
|
|
17
17
|
|
|
@@ -38,7 +38,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
38
38
|
|
|
39
39
|
`llm-cli-gateway` is a single-user MCP gateway for cross-LLM validation and multi-agent coding workflows. It is more than a thin CLI wrapper:
|
|
40
40
|
|
|
41
|
-
- Runs
|
|
41
|
+
- Runs six provider CLIs through consistent sync and async MCP tools.
|
|
42
42
|
- Persists long-running jobs, supports restart-safe result collection, deduplication, cancellation, and sync-to-async deferral.
|
|
43
43
|
- Tracks sessions, real CLI resume paths, structured response metadata, and cache telemetry.
|
|
44
44
|
- Supports cache-aware `promptParts`, including explicit Claude `cache_control` when opted in.
|
|
@@ -49,7 +49,7 @@ Or use directly with `npx` from an MCP client:
|
|
|
49
49
|
|
|
50
50
|
## Workflow Assets
|
|
51
51
|
|
|
52
|
-
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, and secure approval-gated dispatch. Machine-readable DAG-TOML plans live under [`docs/plans`](docs/plans) and [`setup/install-plan.dag.toml`](setup/install-plan.dag.toml) for workflows that need deterministic sequencing and verification gates.
|
|
52
|
+
The repo ships agent-ready workflow skills under [`.agents/skills`](.agents/skills) for async orchestration, session continuity, multi-LLM review, implement-review-fix loops, and secure approval-gated dispatch. Six caller-facing skills are bundled in the published npm package: `async-job-orchestration`, `multi-llm-review`, `session-workflow`, `secure-orchestration`, `implement-review-fix`, and `public-demo-session`. Machine-readable DAG-TOML plans live under [`docs/plans`](docs/plans) and [`setup/install-plan.dag.toml`](setup/install-plan.dag.toml) for workflows that need deterministic sequencing and verification gates.
|
|
53
53
|
|
|
54
54
|
The next documentation focus is provider-specific skill and DAG-TOML pairs for each outbound CLI: Claude, Codex, Gemini, Grok, and Mistral Vibe. The implementation plan is tracked in [`docs/plans/provider-workflow-assets.dag.toml`](docs/plans/provider-workflow-assets.dag.toml), with each provider asset expected to cover install/login checks, session behavior, approval modes, cache/telemetry surfaces, failure modes, and a smoke-test gate.
|
|
55
55
|
|
|
@@ -84,7 +84,7 @@ Current personal-appliance artifacts include:
|
|
|
84
84
|
- Docker Compose fallback: [docker/personal.compose.yml](docker/personal.compose.yml) + [docker/Dockerfile.personal](docker/Dockerfile.personal) for users who already manage containers.
|
|
85
85
|
- Local setup UI artifact: [setup/ui/index.html](setup/ui/index.html)
|
|
86
86
|
- Provider setup snippets: [setup/providers/](setup/providers/)
|
|
87
|
-
- Cross-validation tools: `validate_with_models`, `second_opinion`, `compare_answers`, `red_team_review`, `consensus_check`, `ask_model`, `synthesize_validation`, `job_status`, and `
|
|
87
|
+
- Cross-validation tools: `validate_with_models`, `second_opinion`, `compare_answers`, `red_team_review`, `consensus_check`, `ask_model`, `synthesize_validation`, `job_status`, `job_result`, and `validation_receipt` (plus the `validation-receipt://{validationId}` resource).
|
|
88
88
|
|
|
89
89
|
### Install / Upgrade / Uninstall (single binary)
|
|
90
90
|
|
|
@@ -157,7 +157,7 @@ docker compose -f docker/personal.compose.yml run --rm doctor
|
|
|
157
157
|
|
|
158
158
|
### Core Capabilities
|
|
159
159
|
|
|
160
|
-
- **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok,
|
|
160
|
+
- **Multi-LLM Orchestration**: Unified interface for Claude Code, Codex, Gemini, Grok, Mistral (Vibe), and Devin CLIs
|
|
161
161
|
- **Session Management**: Track and resume conversations across all CLIs with persistent storage
|
|
162
162
|
- **Gateway-owned worktrees**: Run any sync or async provider request inside a managed git worktree, with per-session reuse and cleanup
|
|
163
163
|
- **Token Optimization**: Automatic 44% reduction on prompts, 37% on responses (opt-in)
|
|
@@ -169,11 +169,11 @@ docker compose -f docker/personal.compose.yml run --rm doctor
|
|
|
169
169
|
- **SQLite Flight Recorder**: Every request/response logged to `~/.llm-cli-gateway/logs.db` with correlation IDs, token usage, duration, retry counts, and circuit breaker state. Browse with [Datasette](https://datasette.io/): `datasette ~/.llm-cli-gateway/logs.db`
|
|
170
170
|
- **Structured Metadata**: Tool responses include machine-readable `structuredContent` (model, cli, correlationId, sessionId, durationMs, token counts)
|
|
171
171
|
- **Cache observability resources**: `cache-state://global`, `cache-state://session/{id}`, and `cache-state://prefix/{hash}` MCP resources return aggregate cache hit/miss/savings — tokens and hashes only, no prompt text. `session_get` includes a `cacheState` block when the session has prior requests.
|
|
172
|
-
- **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, and
|
|
172
|
+
- **Provider capability inventory**: `provider_tool_capabilities` and `provider-tools://catalog` expose the gateway request fields, supported/degraded provider controls, local skill/tool discovery, and safe config-surface hints for Claude Code, Codex CLI, Gemini/Antigravity, Grok CLI/API, Mistral Vibe, and Cognition Devin. `doctor --json` includes a compact `provider_capabilities` summary for setup assistants.
|
|
173
173
|
|
|
174
174
|
### Cache-aware operation
|
|
175
175
|
|
|
176
|
-
Every `*_request` and `*_request_async` tool accepts an optional `promptParts` field that structures the prompt for better cache hit rates. The gateway concatenates the parts in canonical order (`system → tools → context → task`) so that the stable prefix bytes precede the volatile task tail unchanged across calls, letting each provider's automatic prompt-caching land on the same content hash each time.
|
|
176
|
+
Every `*_request` and `*_request_async` tool except `devin_request` / `devin_request_async` accepts an optional `promptParts` field that structures the prompt for better cache hit rates (the Devin headless path takes a plain `prompt` only). The gateway concatenates the parts in canonical order (`system → tools → context → task`) so that the stable prefix bytes precede the volatile task tail unchanged across calls, letting each provider's automatic prompt-caching land on the same content hash each time.
|
|
177
177
|
|
|
178
178
|
```json
|
|
179
179
|
{
|
|
@@ -188,7 +188,7 @@ Every `*_request` and `*_request_async` tool accepts an optional `promptParts` f
|
|
|
188
188
|
|
|
189
189
|
`prompt` and `promptParts` are mutually exclusive — pass exactly one.
|
|
190
190
|
|
|
191
|
-
Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all; explicit levers are provider-specific):
|
|
191
|
+
Per-CLI capability matrix (prefix discipline is automatic via `promptParts` for all providers except Devin, which has no `promptParts` surface; explicit levers are provider-specific):
|
|
192
192
|
|
|
193
193
|
| CLI | Prefix discipline | Explicit lever(s) |
|
|
194
194
|
| ------- | ----------------- | ----------------- |
|
|
@@ -240,7 +240,7 @@ Opt-in flags (all default off) live under `[cache_awareness]` in `~/.llm-cli-gat
|
|
|
240
240
|
|
|
241
241
|
### Security & Quality
|
|
242
242
|
|
|
243
|
-
- **Comprehensive Testing**: 1,
|
|
243
|
+
- **Comprehensive Testing**: 1,700+ tests covering unit, integration, and regression scenarios with real CLI execution
|
|
244
244
|
- **Input Validation**: Zod schemas prevent injection attacks
|
|
245
245
|
- **No Secret Leakage**: Generic session descriptions only (file permissions 0o600)
|
|
246
246
|
- **No ReDoS**: Bounded regex patterns prevent catastrophic backtracking
|
|
@@ -387,6 +387,9 @@ The personal-appliance surface exposes simplified validation tools for non-devel
|
|
|
387
387
|
- `synthesize_validation`: run an explicit judge model after provider results have been collected.
|
|
388
388
|
- `list_available_models`: list the models each provider CLI exposes through the simplified surface.
|
|
389
389
|
- `job_status` and `job_result`: poll and collect validation job outputs.
|
|
390
|
+
- `validation_receipt`: retrieve the immutable receipt of a terminal cross-LLM validation run by `validationId` (returns `minted | pending | expired_unminted | not_found`, own-or-not-found). `format: "markdown"` renders a human-readable report; `includeRawResponses` inlines provider answer text. Registered only under the durable persistence gate (`backend = "sqlite"` with an attached validation-run store).
|
|
391
|
+
|
|
392
|
+
The same receipt is also exposed as the `validation-receipt://{validationId}` MCP resource (same durable gate and own-or-not-found owner scoping).
|
|
390
393
|
|
|
391
394
|
The validation report preserves per-provider disagreement. Optional judge synthesis is explicit about which provider produced the judge job.
|
|
392
395
|
|
|
@@ -531,6 +534,8 @@ Execute a Google Antigravity CLI (`agy`) request with session support.
|
|
|
531
534
|
- `approvalStrategy` (string, optional): `"legacy"` (default) or `"mcp_managed"`
|
|
532
535
|
- `approvalPolicy` (string, optional): `"strict"`, `"balanced"`, or `"permissive"`
|
|
533
536
|
- `includeDirs` (string[], optional): Additional workspace directories (passed as `--add-dir`)
|
|
537
|
+
- `project` (string, optional): Select the Antigravity project for this session (`--project <ID>`); mutually exclusive with `newProject`
|
|
538
|
+
- `newProject` (boolean, optional): Create a new Antigravity project for this session (`--new-project`); mutually exclusive with `project`
|
|
534
539
|
- `sandbox` (boolean, optional): Run Antigravity in sandbox mode (`--sandbox`)
|
|
535
540
|
- `outputFormat` (string, optional): `text` only. Antigravity print mode emits text; `json` and `stream-json` are rejected.
|
|
536
541
|
- `mcpServers`, `allowedTools`, `policyFiles`, `adminPolicyFiles`, `attachments` (string[], optional) and `skipTrust` (boolean, optional): **Unsupported by Antigravity CLI** — non-empty values (or `skipTrust: true`) are rejected with an explanatory error. Retained in the schema for caller parity.
|
|
@@ -602,6 +607,9 @@ Execute a Grok CLI (xAI) request with session support.
|
|
|
602
607
|
- `restoreCode` (boolean, optional): Check out the original session commit when resuming
|
|
603
608
|
- `leaderSocket` (string, optional): Custom leader socket path (`--leader-socket`, Grok 0.2.32+; default `~/.grok/leader.sock`) — targets an isolated leader process, e.g. a local/branch Grok build
|
|
604
609
|
- `nativeWorktree` (boolean|string, optional): Grok's own `--worktree` flag (`true` → bare, string → named); distinct from the gateway `worktree` option
|
|
610
|
+
- `worktreeRef` (string, optional): Branch/tag/commit to base the native worktree on (`--worktree-ref`); requires `nativeWorktree`
|
|
611
|
+
- `forkSession` (boolean, optional): Fork the resumed session into a new branch instead of appending to it
|
|
612
|
+
- `jsonSchema` (string|object, optional): JSON Schema (string or object) constraining structured output (`--json-schema`)
|
|
605
613
|
- `worktree` (boolean|object, optional): Run inside a gateway-owned git worktree (slice λ)
|
|
606
614
|
- `promptParts` (object, optional): Cache-aware structured prompt `{ system?, tools?, context?, task }`; mutually exclusive with `prompt`
|
|
607
615
|
- `optimizePrompt` (boolean, optional): Optimize prompt for token efficiency, default: false
|
|
@@ -878,9 +886,28 @@ Run a Mistral Vibe agentic coding request. Like `grok_request` in shape, but wit
|
|
|
878
886
|
- `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
|
|
879
887
|
- `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
|
|
880
888
|
|
|
881
|
-
##### `
|
|
889
|
+
##### `devin_request`
|
|
890
|
+
|
|
891
|
+
Run a Cognition Devin CLI request synchronously (headless print mode, `devin -p`). Auto-defers to a pollable job past the sync deadline when async jobs are enabled.
|
|
892
|
+
|
|
893
|
+
**Parameters:**
|
|
894
|
+
|
|
895
|
+
- `prompt` (string, optional*): Prompt text for Devin CLI (1-100,000 chars). Required in practice; `promptFile` is additive
|
|
896
|
+
- `model` (string, optional): Model name or alias (e.g. `opus`, `latest`)
|
|
897
|
+
- `transport` (string, optional): `"cli"` (default) runs the Devin CLI; `"acp"` routes through `devin acp` when `[acp].enabled` and the provider's `runtime_enabled` are set (fails closed otherwise)
|
|
898
|
+
- `permissionMode` (string, optional): Devin CLI permission mode (`--permission-mode`): `auto` (auto-approves read-only tools), `smart` (also auto-runs actions a fast model judges safe), `dangerous` (auto-approves all). Omit to use Devin's headless default
|
|
899
|
+
- `promptFile` (string, optional): Load the initial prompt from a file (`--prompt-file`)
|
|
900
|
+
- `sessionId` (string, optional): Devin session ID to resume (`--resume <id>`). The `gw-*` id minted for a brand-new session is not resumable via `sessionId`; continue with `resumeLatest: true`
|
|
901
|
+
- `resumeLatest` (boolean, optional): Resume the most recent Devin session in cwd (`--continue`)
|
|
902
|
+
- `createNewSession` (boolean, optional): Force a new session
|
|
903
|
+
- `optimizePrompt` / `optimizeResponse` (boolean, optional): Token-efficiency optimisation, default: false
|
|
904
|
+
- `correlationId` (string, optional): Request trace ID (auto-generated if omitted)
|
|
905
|
+
- `idleTimeoutMs` (integer, optional): Kill a stuck process after output inactivity; 30,000 to 3,600,000 ms
|
|
906
|
+
- `forceRefresh` (boolean, optional): Bypass dedup and force a fresh CLI run, default: false
|
|
907
|
+
|
|
908
|
+
##### `claude_request_async` / `codex_request_async` / `gemini_request_async` / `grok_request_async` / `mistral_request_async` / `devin_request_async`
|
|
882
909
|
|
|
883
|
-
Start a long-running Claude, Codex, Gemini, Grok, or
|
|
910
|
+
Start a long-running Claude, Codex, Gemini, Grok, Mistral, or Devin request without waiting for completion in the same MCP call.
|
|
884
911
|
|
|
885
912
|
Use this flow when analysis/runtime can exceed client tool-call limits:
|
|
886
913
|
|
|
@@ -939,7 +966,7 @@ Return the gateway's declared provider CLI contracts, optionally probing the ins
|
|
|
939
966
|
|
|
940
967
|
**Parameters:**
|
|
941
968
|
|
|
942
|
-
- `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral`)
|
|
969
|
+
- `cli` (string, optional): Filter (`claude|codex|gemini|grok|mistral|devin`)
|
|
943
970
|
- `probeInstalled` (boolean, optional, default `false`): Run local `--help` probes and compare advertised flags against the declared contract — strongly recommended after any provider CLI upgrade. The probe reports `missingFlags`, `extraFlags`, `acknowledgedExtraFlags` (known upstream-only flags filtered from `extraFlags`), `discoveredFlags`, and stale-marker `warnings`.
|
|
944
971
|
|
|
945
972
|
#### Session Management Tools
|
|
@@ -950,7 +977,7 @@ Create a new session for a specific CLI.
|
|
|
950
977
|
|
|
951
978
|
**Parameters:**
|
|
952
979
|
|
|
953
|
-
- `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral")
|
|
980
|
+
- `cli` (string, required): CLI to create session for ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
954
981
|
- `description` (string, optional): Description for the session
|
|
955
982
|
- `setAsActive` (boolean, optional): Set as active session, default: true
|
|
956
983
|
|
|
@@ -970,7 +997,7 @@ List all sessions, optionally filtered by CLI.
|
|
|
970
997
|
|
|
971
998
|
**Parameters:**
|
|
972
999
|
|
|
973
|
-
- `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral")
|
|
1000
|
+
- `cli` (string, optional): Filter by CLI ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
974
1001
|
|
|
975
1002
|
**Response includes:**
|
|
976
1003
|
|
|
@@ -1019,7 +1046,7 @@ List available models for each CLI.
|
|
|
1019
1046
|
|
|
1020
1047
|
**Parameters:**
|
|
1021
1048
|
|
|
1022
|
-
- `cli` (string, optional): Specific CLI to list models for ("claude", "codex", "gemini", "grok", "mistral")
|
|
1049
|
+
- `cli` (string, optional): Specific CLI to list models for ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1023
1050
|
|
|
1024
1051
|
**Response includes:**
|
|
1025
1052
|
|
|
@@ -1067,7 +1094,7 @@ inputs.
|
|
|
1067
1094
|
|
|
1068
1095
|
**Parameters:**
|
|
1069
1096
|
|
|
1070
|
-
- `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"
|
|
1097
|
+
- `cli` (string, optional): Provider filter (`"claude"`, `"codex"`, `"gemini"`, `"grok"`, `"mistral"`, `"devin"`, or `"grok_api"`)
|
|
1071
1098
|
- `includeSkills` (boolean, default `true`): Include bounded local skill discovery
|
|
1072
1099
|
- `includeProviderTools` (boolean, default `true`): Include provider-native tools extracted from discovered skills
|
|
1073
1100
|
- `includeUnsupported` (boolean, default `true`): Include explicit unsupported/degraded input records
|
|
@@ -1087,6 +1114,7 @@ Equivalent MCP resources:
|
|
|
1087
1114
|
- `provider-tools://grok`
|
|
1088
1115
|
- `provider-tools://grok_api`
|
|
1089
1116
|
- `provider-tools://mistral`
|
|
1117
|
+
- `provider-tools://devin`
|
|
1090
1118
|
|
|
1091
1119
|
`doctor --json` also emits a compact `provider_capabilities` block with the
|
|
1092
1120
|
same schema version, per-provider request tool names, supported feature names,
|
|
@@ -1100,7 +1128,7 @@ Report installed CLI versions.
|
|
|
1100
1128
|
|
|
1101
1129
|
**Parameters:**
|
|
1102
1130
|
|
|
1103
|
-
- `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral")
|
|
1131
|
+
- `cli` (string, optional): Specific CLI to inspect ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1104
1132
|
|
|
1105
1133
|
##### `cli_upgrade`
|
|
1106
1134
|
|
|
@@ -1108,7 +1136,7 @@ Plan or run an upgrade for one CLI.
|
|
|
1108
1136
|
|
|
1109
1137
|
**Parameters:**
|
|
1110
1138
|
|
|
1111
|
-
- `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral")
|
|
1139
|
+
- `cli` (string, required): CLI to upgrade ("claude", "codex", "gemini", "grok", "mistral", "devin")
|
|
1112
1140
|
- `target` (string, optional): Package tag/version/target, default: `latest`
|
|
1113
1141
|
- `dryRun` (boolean, optional): Return the upgrade plan without running it, default: `true`
|
|
1114
1142
|
- `timeoutMs` (number, optional): Upgrade timeout when `dryRun=false`
|
|
@@ -1123,6 +1151,7 @@ Plan or run an upgrade for one CLI.
|
|
|
1123
1151
|
- Grok latest: `grok update`
|
|
1124
1152
|
- Grok explicit target: `grok update --version <target>`
|
|
1125
1153
|
- Mistral (Vibe): dispatches to the detected installer (`pip`/`uv`/`brew`); errors with guidance when none is detected (Vibe ships no self-update command)
|
|
1154
|
+
- Devin latest: `devin update` (self-update; explicit version targets are unsupported)
|
|
1126
1155
|
|
|
1127
1156
|
**Example dry run:**
|
|
1128
1157
|
|
|
@@ -17,7 +17,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
17
17
|
displayName: "xAI Grok CLI",
|
|
18
18
|
status: "native_smoke_passed",
|
|
19
19
|
supportKind: "native",
|
|
20
|
-
targetVersion: "grok 0.2.
|
|
20
|
+
targetVersion: "grok 0.2.73 (9ff14c43bb)",
|
|
21
21
|
entrypoint: Object.freeze({ command: "grok", args: Object.freeze(["agent", "stdio"]) }),
|
|
22
22
|
runtimeEnabledDefault: false,
|
|
23
23
|
shipRuntimePilot: true,
|
|
@@ -30,7 +30,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
30
30
|
displayName: "OpenAI Codex CLI",
|
|
31
31
|
status: "adapter_mediated_deferred",
|
|
32
32
|
supportKind: "adapter_mediated",
|
|
33
|
-
targetVersion: "codex-cli 0.
|
|
33
|
+
targetVersion: "codex-cli 0.142.4",
|
|
34
34
|
entrypoint: null,
|
|
35
35
|
runtimeEnabledDefault: false,
|
|
36
36
|
shipRuntimePilot: false,
|
|
@@ -43,7 +43,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
43
43
|
displayName: "Anthropic Claude Code",
|
|
44
44
|
status: "adapter_mediated_deferred",
|
|
45
45
|
supportKind: "adapter_mediated",
|
|
46
|
-
targetVersion: "claude 2.1.
|
|
46
|
+
targetVersion: "claude 2.1.195",
|
|
47
47
|
entrypoint: null,
|
|
48
48
|
runtimeEnabledDefault: false,
|
|
49
49
|
shipRuntimePilot: false,
|
|
@@ -56,7 +56,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
56
56
|
displayName: "Google Antigravity",
|
|
57
57
|
status: "absent_watchlist",
|
|
58
58
|
supportKind: "none",
|
|
59
|
-
targetVersion: "agy 1.0.
|
|
59
|
+
targetVersion: "agy 1.0.13",
|
|
60
60
|
entrypoint: null,
|
|
61
61
|
runtimeEnabledDefault: false,
|
|
62
62
|
shipRuntimePilot: false,
|
|
@@ -69,7 +69,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
|
|
|
69
69
|
displayName: "Cognition Devin CLI",
|
|
70
70
|
status: "native_smoke_passed",
|
|
71
71
|
supportKind: "native",
|
|
72
|
-
targetVersion: "devin 2026.
|
|
72
|
+
targetVersion: "devin 2026.8.18 (16737566)",
|
|
73
73
|
entrypoint: Object.freeze({ command: "devin", args: Object.freeze(["acp"]) }),
|
|
74
74
|
runtimeEnabledDefault: false,
|
|
75
75
|
shipRuntimePilot: true,
|
package/dist/api-provider.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { URL } from "node:url";
|
|
|
2
2
|
import type { Logger } from "./logger.js";
|
|
3
3
|
import { ApiHttpError } from "./api-http.js";
|
|
4
4
|
export type ApiProviderKind = "openai-compatible" | "anthropic" | "xai-responses";
|
|
5
|
+
export type ApiContinuity = "server-side-id" | "stateless-resend" | "none";
|
|
5
6
|
export interface ApiChatMessage {
|
|
6
7
|
role: "system" | "user" | "assistant";
|
|
7
8
|
content: string;
|
|
@@ -17,6 +18,7 @@ export interface ApiRequest {
|
|
|
17
18
|
reasoningEffort?: "none" | "low" | "medium" | "high";
|
|
18
19
|
timeoutMs?: number;
|
|
19
20
|
previousResponseId?: string;
|
|
21
|
+
usageInclude?: boolean;
|
|
20
22
|
}
|
|
21
23
|
export interface ApiUsage {
|
|
22
24
|
inputTokens?: number;
|
|
@@ -32,10 +34,12 @@ export interface ApiResult {
|
|
|
32
34
|
raw: unknown;
|
|
33
35
|
httpStatus: number;
|
|
34
36
|
responseId?: string | null;
|
|
37
|
+
status?: string | null;
|
|
35
38
|
}
|
|
36
39
|
export interface ApiProvider {
|
|
37
40
|
readonly name: string;
|
|
38
41
|
readonly kind: ApiProviderKind;
|
|
42
|
+
readonly continuity: ApiContinuity;
|
|
39
43
|
endpointUrl(baseUrl: string): URL;
|
|
40
44
|
buildBody(req: ApiRequest): Record<string, unknown>;
|
|
41
45
|
parseResult(httpStatus: number, body: string): ApiResult;
|
|
@@ -45,6 +49,7 @@ export interface ApiProvider {
|
|
|
45
49
|
export declare class OpenAiCompatibleProvider implements ApiProvider {
|
|
46
50
|
readonly name: string;
|
|
47
51
|
readonly kind: "openai-compatible";
|
|
52
|
+
readonly continuity: "stateless-resend";
|
|
48
53
|
constructor(name: string);
|
|
49
54
|
endpointUrl(baseUrl: string): URL;
|
|
50
55
|
buildBody(req: ApiRequest): Record<string, unknown>;
|
|
@@ -57,6 +62,7 @@ export declare class AnthropicProvider implements ApiProvider {
|
|
|
57
62
|
readonly name: string;
|
|
58
63
|
private readonly anthropicVersion;
|
|
59
64
|
readonly kind: "anthropic";
|
|
65
|
+
readonly continuity: "stateless-resend";
|
|
60
66
|
constructor(name: string, anthropicVersion?: string);
|
|
61
67
|
endpointUrl(baseUrl: string): URL;
|
|
62
68
|
buildBody(req: ApiRequest): Record<string, unknown>;
|
|
@@ -67,6 +73,7 @@ export declare class AnthropicProvider implements ApiProvider {
|
|
|
67
73
|
export declare class XaiResponsesProvider implements ApiProvider {
|
|
68
74
|
readonly name: string;
|
|
69
75
|
readonly kind: "xai-responses";
|
|
76
|
+
readonly continuity: "server-side-id";
|
|
70
77
|
constructor(name: string);
|
|
71
78
|
endpointUrl(baseUrl: string): URL;
|
|
72
79
|
buildBody(req: ApiRequest): Record<string, unknown>;
|
package/dist/api-provider.js
CHANGED
|
@@ -15,6 +15,7 @@ function firstNumber(...candidates) {
|
|
|
15
15
|
export class OpenAiCompatibleProvider {
|
|
16
16
|
name;
|
|
17
17
|
kind = "openai-compatible";
|
|
18
|
+
continuity = "stateless-resend";
|
|
18
19
|
constructor(name) {
|
|
19
20
|
this.name = name;
|
|
20
21
|
}
|
|
@@ -32,6 +33,8 @@ export class OpenAiCompatibleProvider {
|
|
|
32
33
|
body.temperature = req.temperature;
|
|
33
34
|
if (req.topP !== undefined)
|
|
34
35
|
body.top_p = req.topP;
|
|
36
|
+
if (req.usageInclude)
|
|
37
|
+
body.usage = { include: true };
|
|
35
38
|
return body;
|
|
36
39
|
}
|
|
37
40
|
parseResult(httpStatus, body) {
|
|
@@ -46,6 +49,7 @@ export class OpenAiCompatibleProvider {
|
|
|
46
49
|
inputTokens: firstNumber(usage.prompt_tokens, usage.input_tokens),
|
|
47
50
|
outputTokens: firstNumber(usage.completion_tokens, usage.output_tokens),
|
|
48
51
|
cacheReadTokens: firstNumber(usage?.prompt_tokens_details?.cached_tokens),
|
|
52
|
+
costUsd: firstNumber(usage.cost),
|
|
49
53
|
raw: usage,
|
|
50
54
|
},
|
|
51
55
|
raw: parsed,
|
|
@@ -65,6 +69,7 @@ export class AnthropicProvider {
|
|
|
65
69
|
name;
|
|
66
70
|
anthropicVersion;
|
|
67
71
|
kind = "anthropic";
|
|
72
|
+
continuity = "stateless-resend";
|
|
68
73
|
constructor(name, anthropicVersion = DEFAULT_ANTHROPIC_VERSION) {
|
|
69
74
|
this.name = name;
|
|
70
75
|
this.anthropicVersion = anthropicVersion;
|
|
@@ -152,6 +157,7 @@ function extractXaiResponseText(parsed) {
|
|
|
152
157
|
export class XaiResponsesProvider {
|
|
153
158
|
name;
|
|
154
159
|
kind = "xai-responses";
|
|
160
|
+
continuity = "server-side-id";
|
|
155
161
|
constructor(name) {
|
|
156
162
|
this.name = name;
|
|
157
163
|
}
|
|
@@ -186,6 +192,7 @@ export class XaiResponsesProvider {
|
|
|
186
192
|
const usage = parsed?.usage ?? {};
|
|
187
193
|
return {
|
|
188
194
|
responseId: typeof parsed?.id === "string" ? parsed.id : null,
|
|
195
|
+
status: typeof parsed?.status === "string" ? parsed.status : null,
|
|
189
196
|
model: typeof parsed?.model === "string" ? parsed.model : "unknown",
|
|
190
197
|
text: extractXaiResponseText(parsed),
|
|
191
198
|
usage: {
|
package/dist/api-request.js
CHANGED
|
@@ -2,7 +2,10 @@ import type { Logger } from "./logger.js";
|
|
|
2
2
|
import { type JobHealth } from "./process-monitor.js";
|
|
3
3
|
import { JobStore } from "./job-store.js";
|
|
4
4
|
import { type FlightRecorderLike } from "./flight-recorder.js";
|
|
5
|
-
import
|
|
5
|
+
import type { ValidationRunStore } from "./job-store.js";
|
|
6
|
+
import { type ApiProvider, type ApiRequest, type ApiUsage } from "./api-provider.js";
|
|
7
|
+
export declare function extractApiHttpStatus(error: unknown): number | null;
|
|
8
|
+
export declare function extractApiErrorBody(error: unknown): string | undefined;
|
|
6
9
|
export type LlmCli = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
|
|
7
10
|
export type JobProvider = LlmCli | (string & {});
|
|
8
11
|
export type AsyncJobStatus = "running" | "completed" | "failed" | "canceled" | "orphaned";
|
|
@@ -41,6 +44,11 @@ export interface AsyncJobResult extends AsyncJobSnapshot {
|
|
|
41
44
|
stderr: string;
|
|
42
45
|
stdoutTruncated: boolean;
|
|
43
46
|
stderrTruncated: boolean;
|
|
47
|
+
apiUsage?: ApiUsage;
|
|
48
|
+
httpStatus?: number | null;
|
|
49
|
+
responseId?: string | null;
|
|
50
|
+
model?: string;
|
|
51
|
+
errorBody?: string;
|
|
44
52
|
}
|
|
45
53
|
export interface StartJobOptions {
|
|
46
54
|
cwd?: string;
|
|
@@ -72,6 +80,7 @@ export declare class AsyncJobManager {
|
|
|
72
80
|
private buildOrphanFlightResult;
|
|
73
81
|
checkStalledJobs(now?: number): void;
|
|
74
82
|
hasStore(): boolean;
|
|
83
|
+
getValidationRunStore(): ValidationRunStore | null;
|
|
75
84
|
private emitMetrics;
|
|
76
85
|
private evictCompletedJobs;
|
|
77
86
|
private buildRequestKey;
|
|
@@ -91,6 +100,7 @@ export declare class AsyncJobManager {
|
|
|
91
100
|
private fireOnComplete;
|
|
92
101
|
private writeFlightComplete;
|
|
93
102
|
private safeExtractUsage;
|
|
103
|
+
private httpUsage;
|
|
94
104
|
armFlightCompleteForDeferral(jobId: string): void;
|
|
95
105
|
private safeStoreCall;
|
|
96
106
|
private maybeFlushOutput;
|
|
@@ -2,12 +2,12 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import { envWithExtendedPath, getExtendedPath, killProcessGroup, providerCommandName, spawnCliProcess, unregisterProcessGroup, } from "./executor.js";
|
|
3
3
|
import { noopLogger, logWarn } from "./logger.js";
|
|
4
4
|
import { ProcessMonitor } from "./process-monitor.js";
|
|
5
|
-
import { computeRequestKey } from "./job-store.js";
|
|
5
|
+
import { computeRequestKey, isValidationRunStore } from "./job-store.js";
|
|
6
6
|
import { NoopFlightRecorder, } from "./flight-recorder.js";
|
|
7
7
|
import { codexFrResponse } from "./codex-json-parser.js";
|
|
8
8
|
import { getRequestContext, resolveOwnerPrincipal } from "./request-context.js";
|
|
9
9
|
import { runApiRequest, ApiHttpError, } from "./api-provider.js";
|
|
10
|
-
function extractApiHttpStatus(error) {
|
|
10
|
+
export function extractApiHttpStatus(error) {
|
|
11
11
|
for (const candidate of [error, error?.cause]) {
|
|
12
12
|
if (candidate instanceof ApiHttpError && typeof candidate.status === "number") {
|
|
13
13
|
return candidate.status;
|
|
@@ -18,6 +18,14 @@ function extractApiHttpStatus(error) {
|
|
|
18
18
|
}
|
|
19
19
|
return null;
|
|
20
20
|
}
|
|
21
|
+
export function extractApiErrorBody(error) {
|
|
22
|
+
for (const candidate of [error, error?.cause]) {
|
|
23
|
+
if (candidate instanceof ApiHttpError && candidate.responseText) {
|
|
24
|
+
return candidate.responseText;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
21
29
|
const MAX_OUTPUT_SIZE = 50 * 1024 * 1024;
|
|
22
30
|
const JOB_TTL_MS = 60 * 60 * 1000;
|
|
23
31
|
const EVICTION_INTERVAL_MS = 5 * 60 * 1000;
|
|
@@ -173,6 +181,9 @@ export class AsyncJobManager {
|
|
|
173
181
|
hasStore() {
|
|
174
182
|
return this.store !== null;
|
|
175
183
|
}
|
|
184
|
+
getValidationRunStore() {
|
|
185
|
+
return this.store && isValidationRunStore(this.store) ? this.store : null;
|
|
186
|
+
}
|
|
176
187
|
emitMetrics(job) {
|
|
177
188
|
if (job.metricsRecorded)
|
|
178
189
|
return;
|
|
@@ -404,6 +415,9 @@ export class AsyncJobManager {
|
|
|
404
415
|
job.stdout = result.text;
|
|
405
416
|
job.httpStatus = result.httpStatus;
|
|
406
417
|
job.exitCode = 0;
|
|
418
|
+
job.apiUsage = result.usage;
|
|
419
|
+
job.apiResponseId = result.responseId ?? null;
|
|
420
|
+
job.apiModel = result.model;
|
|
407
421
|
}
|
|
408
422
|
else {
|
|
409
423
|
job.status = "failed";
|
|
@@ -413,6 +427,7 @@ export class AsyncJobManager {
|
|
|
413
427
|
job.stderr = message;
|
|
414
428
|
job.error = message;
|
|
415
429
|
job.exitCode = 1;
|
|
430
|
+
job.apiErrorBody = extractApiErrorBody(error);
|
|
416
431
|
}
|
|
417
432
|
job.finishedAt = new Date().toISOString();
|
|
418
433
|
job.exited = true;
|
|
@@ -443,7 +458,13 @@ export class AsyncJobManager {
|
|
|
443
458
|
if (job.flightRecorderComplete)
|
|
444
459
|
return;
|
|
445
460
|
const durationMs = Math.max(0, Date.now() - new Date(job.startedAt).getTime());
|
|
446
|
-
const usage = finalStatus
|
|
461
|
+
const usage = finalStatus !== "completed"
|
|
462
|
+
? {}
|
|
463
|
+
: job.transport === "http"
|
|
464
|
+
? this.httpUsage(job)
|
|
465
|
+
: job.extractUsage
|
|
466
|
+
? this.safeExtractUsage(job)
|
|
467
|
+
: {};
|
|
447
468
|
const isFailure = finalStatus === "failed";
|
|
448
469
|
let response;
|
|
449
470
|
if (job.transport === "process" && job.cli === "codex") {
|
|
@@ -491,6 +512,17 @@ export class AsyncJobManager {
|
|
|
491
512
|
return {};
|
|
492
513
|
}
|
|
493
514
|
}
|
|
515
|
+
httpUsage(job) {
|
|
516
|
+
const u = job.apiUsage;
|
|
517
|
+
if (!u)
|
|
518
|
+
return {};
|
|
519
|
+
return {
|
|
520
|
+
inputTokens: u.inputTokens,
|
|
521
|
+
outputTokens: u.outputTokens,
|
|
522
|
+
cacheReadTokens: u.cacheReadTokens,
|
|
523
|
+
costUsd: u.costUsd,
|
|
524
|
+
};
|
|
525
|
+
}
|
|
494
526
|
armFlightCompleteForDeferral(jobId) {
|
|
495
527
|
const job = this.jobs.get(jobId);
|
|
496
528
|
if (!job)
|
|
@@ -846,6 +878,15 @@ export class AsyncJobManager {
|
|
|
846
878
|
stderr: stderr.text,
|
|
847
879
|
stdoutTruncated: stdout.truncated,
|
|
848
880
|
stderrTruncated: stderr.truncated,
|
|
881
|
+
...(job.transport === "http"
|
|
882
|
+
? {
|
|
883
|
+
apiUsage: job.apiUsage,
|
|
884
|
+
httpStatus: job.httpStatus,
|
|
885
|
+
responseId: job.apiResponseId,
|
|
886
|
+
model: job.apiModel,
|
|
887
|
+
errorBody: job.apiErrorBody,
|
|
888
|
+
}
|
|
889
|
+
: {}),
|
|
849
890
|
};
|
|
850
891
|
}
|
|
851
892
|
cancelJob(jobId) {
|
package/dist/config.d.ts
CHANGED
|
@@ -76,6 +76,7 @@ export interface ApiProviderConfig {
|
|
|
76
76
|
baseUrl: string;
|
|
77
77
|
defaultModel: string;
|
|
78
78
|
models?: string[];
|
|
79
|
+
usageInclude?: boolean;
|
|
79
80
|
}
|
|
80
81
|
export interface ApiProviderRuntime {
|
|
81
82
|
name: string;
|
|
@@ -83,6 +84,7 @@ export interface ApiProviderRuntime {
|
|
|
83
84
|
baseUrl: string;
|
|
84
85
|
defaultModel: string;
|
|
85
86
|
models?: string[];
|
|
87
|
+
usageInclude?: boolean;
|
|
86
88
|
apiKey: string;
|
|
87
89
|
}
|
|
88
90
|
export interface ProvidersConfig {
|
package/dist/config.js
CHANGED
|
@@ -301,6 +301,7 @@ const ApiProviderSchema = z
|
|
|
301
301
|
api_key_env: z.string().min(1).optional(),
|
|
302
302
|
default_model: z.string().min(1),
|
|
303
303
|
models: z.array(z.string().min(1)).nonempty().optional(),
|
|
304
|
+
usage_include: z.boolean().optional(),
|
|
304
305
|
})
|
|
305
306
|
.strict();
|
|
306
307
|
function readProvidersFile(configPath, logger) {
|
|
@@ -369,6 +370,7 @@ export function loadProvidersConfig(logger = noopLogger) {
|
|
|
369
370
|
baseUrl: parsed.data.base_url,
|
|
370
371
|
defaultModel: parsed.data.default_model,
|
|
371
372
|
models: parsed.data.models ? [...parsed.data.models] : undefined,
|
|
373
|
+
usageInclude: parsed.data.usage_include,
|
|
372
374
|
};
|
|
373
375
|
}
|
|
374
376
|
return { xai, providers, sources: { configFile: sourcePath } };
|
|
@@ -395,6 +397,7 @@ export function enabledApiProviders(config, env = process.env) {
|
|
|
395
397
|
baseUrl: provider.baseUrl,
|
|
396
398
|
defaultModel: provider.defaultModel,
|
|
397
399
|
models: provider.models,
|
|
400
|
+
usageInclude: provider.usageInclude,
|
|
398
401
|
apiKey: resolveProviderKey(provider.apiKeyEnv, env) ?? "",
|
|
399
402
|
});
|
|
400
403
|
}
|