llm-cli-gateway 2.15.0 → 2.17.0

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.
Files changed (80) hide show
  1. package/.agents/skills/least-cost-routing/SKILL.md +123 -0
  2. package/CHANGELOG.md +78 -1
  3. package/dist/acp/client.js +5 -0
  4. package/dist/acp/flight-redaction.d.ts +2 -0
  5. package/dist/acp/flight-redaction.js +2 -0
  6. package/dist/acp/process-manager.js +2 -1
  7. package/dist/acp/provider-registry.js +8 -8
  8. package/dist/acp/runtime.d.ts +1 -0
  9. package/dist/acp/runtime.js +20 -0
  10. package/dist/acp/types.d.ts +52 -0
  11. package/dist/acp/types.js +8 -0
  12. package/dist/api-provider.d.ts +1 -0
  13. package/dist/api-provider.js +1 -0
  14. package/dist/async-job-manager.d.ts +20 -1
  15. package/dist/async-job-manager.js +85 -28
  16. package/dist/claude-mcp-config.js +1 -1
  17. package/dist/compressor/estimate.d.ts +5 -0
  18. package/dist/compressor/estimate.js +21 -0
  19. package/dist/compressor/index.d.ts +23 -0
  20. package/dist/compressor/index.js +77 -0
  21. package/dist/compressor/router.d.ts +2 -0
  22. package/dist/compressor/router.js +57 -0
  23. package/dist/compressor/transforms/ansi.d.ts +3 -0
  24. package/dist/compressor/transforms/ansi.js +99 -0
  25. package/dist/compressor/transforms/json.d.ts +1 -0
  26. package/dist/compressor/transforms/json.js +156 -0
  27. package/dist/compressor/transforms/log.d.ts +12 -0
  28. package/dist/compressor/transforms/log.js +55 -0
  29. package/dist/compressor/transforms/whitespace.d.ts +8 -0
  30. package/dist/compressor/transforms/whitespace.js +79 -0
  31. package/dist/config.d.ts +39 -0
  32. package/dist/config.js +166 -6
  33. package/dist/db.js +4 -4
  34. package/dist/doctor.d.ts +34 -1
  35. package/dist/doctor.js +85 -3
  36. package/dist/executor.d.ts +6 -0
  37. package/dist/executor.js +16 -3
  38. package/dist/flight-recorder.d.ts +19 -0
  39. package/dist/flight-recorder.js +110 -2
  40. package/dist/http-transport.js +19 -21
  41. package/dist/index.d.ts +58 -2
  42. package/dist/index.js +929 -94
  43. package/dist/job-store.d.ts +14 -2
  44. package/dist/job-store.js +170 -43
  45. package/dist/lcr-priors.d.ts +60 -0
  46. package/dist/lcr-priors.js +190 -0
  47. package/dist/lcr-router-env.d.ts +20 -0
  48. package/dist/lcr-router-env.js +133 -0
  49. package/dist/lcr-telemetry.d.ts +2 -0
  50. package/dist/lcr-telemetry.js +17 -0
  51. package/dist/least-cost-router.d.ts +86 -0
  52. package/dist/least-cost-router.js +296 -0
  53. package/dist/least-cost-types.d.ts +34 -0
  54. package/dist/least-cost-types.js +1 -0
  55. package/dist/migrate-sessions.js +1 -1
  56. package/dist/migrate.js +1 -1
  57. package/dist/model-registry.js +1 -1
  58. package/dist/postgres-job-store-worker.js +56 -13
  59. package/dist/pricing.d.ts +17 -0
  60. package/dist/pricing.js +167 -0
  61. package/dist/provider-definitions.d.ts +5 -0
  62. package/dist/provider-definitions.js +56 -10
  63. package/dist/provider-tool-capabilities.js +3 -3
  64. package/dist/request-helpers.d.ts +2 -2
  65. package/dist/request-helpers.js +1 -1
  66. package/dist/resources.d.ts +37 -2
  67. package/dist/resources.js +96 -1
  68. package/dist/retry.d.ts +1 -0
  69. package/dist/retry.js +1 -1
  70. package/dist/spawn-env-isolation.d.ts +10 -0
  71. package/dist/spawn-env-isolation.js +55 -0
  72. package/dist/token-estimator.d.ts +6 -0
  73. package/dist/token-estimator.js +59 -0
  74. package/dist/upstream-contracts.js +48 -28
  75. package/dist/validation-receipt.js +1 -1
  76. package/dist/validation-tools.d.ts +6 -0
  77. package/dist/validation-tools.js +174 -54
  78. package/npm-shrinkwrap.json +2 -2
  79. package/package.json +15 -6
  80. package/setup/status.schema.json +68 -0
@@ -1,7 +1,21 @@
1
- import { writeFileSync } from "node:fs";
2
1
  import { parentPort, workerData } from "node:worker_threads";
3
2
  let pool = null;
4
3
  const PG_NOW_MS = "(EXTRACT(EPOCH FROM now()) * 1000)::bigint";
4
+ const PG_BOOTSTRAP_LOCK_KEY = 13_920_260_713;
5
+ const PG_POOL_MAX = 1;
6
+ const PG_STATEMENT_TIMEOUT_MS = 25_000;
7
+ const PG_LOCK_TIMEOUT_MS = 5_000;
8
+ const PG_QUERY_TIMEOUT_MS = 27_000;
9
+ function reportDiagnostic(kind, error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ const stack = error instanceof Error ? error.stack : undefined;
12
+ parentPort?.postMessage({
13
+ type: "postgres-job-store-diagnostic",
14
+ kind,
15
+ message,
16
+ stack,
17
+ });
18
+ }
5
19
  function signal(shared, status) {
6
20
  const view = new Int32Array(shared);
7
21
  Atomics.store(view, 0, status);
@@ -48,9 +62,22 @@ async function init() {
48
62
  pool = new pg.Pool({
49
63
  connectionString: workerData.dsn,
50
64
  connectionTimeoutMillis: workerData.connectionTimeoutMillis,
65
+ max: PG_POOL_MAX,
66
+ idleTimeoutMillis: 30_000,
67
+ statement_timeout: PG_STATEMENT_TIMEOUT_MS,
68
+ lock_timeout: PG_LOCK_TIMEOUT_MS,
69
+ query_timeout: PG_QUERY_TIMEOUT_MS,
70
+ application_name: "llm-cli-gateway-job-store",
51
71
  });
72
+ getPool().on("error", error => reportDiagnostic("pool_error", error));
52
73
  await getPool().query("SELECT 1");
53
- await getPool().query(`
74
+ await withClient(async (client) => {
75
+ await client.query("BEGIN");
76
+ try {
77
+ await client.query("SET LOCAL lock_timeout = 0");
78
+ await client.query("SELECT pg_advisory_xact_lock($1::bigint)", [PG_BOOTSTRAP_LOCK_KEY]);
79
+ await client.query(`SET LOCAL lock_timeout = '${PG_LOCK_TIMEOUT_MS}ms'`);
80
+ await client.query(`
54
81
  CREATE TABLE IF NOT EXISTS jobs (
55
82
  id TEXT PRIMARY KEY,
56
83
  correlation_id TEXT NOT NULL,
@@ -125,14 +152,26 @@ async function init() {
125
152
  confidence TEXT NOT NULL
126
153
  );
127
154
  CREATE INDEX IF NOT EXISTS idx_validation_receipts_owner ON validation_receipts(owner_principal);
128
- `);
129
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_principal TEXT");
130
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS transport TEXT NOT NULL DEFAULT 'process'");
131
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS http_status INTEGER");
132
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS payload_json TEXT");
133
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_instance TEXT");
134
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS lease_deadline BIGINT");
135
- await getPool().query("CREATE INDEX IF NOT EXISTS idx_jobs_owner_status ON jobs(owner_instance, status)");
155
+ `);
156
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_principal TEXT");
157
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS transport TEXT NOT NULL DEFAULT 'process'");
158
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS http_status INTEGER");
159
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS payload_json TEXT");
160
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_instance TEXT");
161
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS lease_deadline BIGINT");
162
+ await client.query("CREATE INDEX IF NOT EXISTS idx_jobs_owner_status ON jobs(owner_instance, status)");
163
+ await client.query("COMMIT");
164
+ }
165
+ catch (error) {
166
+ try {
167
+ await client.query("ROLLBACK");
168
+ }
169
+ catch (rollbackError) {
170
+ reportDiagnostic("bootstrap_rollback_error", rollbackError);
171
+ }
172
+ throw error;
173
+ }
174
+ });
136
175
  }
137
176
  async function op(method, args) {
138
177
  switch (method) {
@@ -426,7 +465,7 @@ async function op(method, args) {
426
465
  }
427
466
  }
428
467
  parentPort?.on("message", async (message) => {
429
- const { method, args, resultPath, shared } = message;
468
+ const { method, args, shared, responsePort } = message;
430
469
  let payload;
431
470
  try {
432
471
  payload = ok(await op(method, args));
@@ -435,10 +474,14 @@ parentPort?.on("message", async (message) => {
435
474
  payload = fail(error);
436
475
  }
437
476
  try {
438
- writeFileSync(resultPath, JSON.stringify(payload));
477
+ responsePort.postMessage(payload);
439
478
  signal(shared, 1);
440
479
  }
441
- catch {
480
+ catch (error) {
481
+ reportDiagnostic("response_transport_error", error);
442
482
  signal(shared, 2);
443
483
  }
484
+ finally {
485
+ responsePort.close();
486
+ }
444
487
  });
package/dist/pricing.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { CliType } from "./provider-types.js";
2
+ import type { ModelCost, TokenCounts, TokenEstimate, CostResult, AccountingMode } from "./least-cost-types.js";
2
3
  export interface PricePerMillion {
3
4
  inputUsd: number;
4
5
  outputUsd: number;
@@ -7,3 +8,19 @@ export interface PricePerMillion {
7
8
  export declare const PRICING_AS_OF = "2026-06-13";
8
9
  export declare function getPricing(cli: CliType, model: string): PricePerMillion;
9
10
  export declare function estimateCacheSavingsUsd(cli: CliType, model: string, cacheReadTokens: number): number;
11
+ export declare function modelIdToFamily(model: string): string;
12
+ export declare const API_CATALOG_AS_OF = "2026-07-11";
13
+ export interface ApiCatalogEntry {
14
+ inputUsdPerMTok: number;
15
+ outputUsdPerMTok: number;
16
+ cacheReadMultiplier: number;
17
+ accountingMode?: AccountingMode;
18
+ family?: string;
19
+ }
20
+ export type ApiCatalog = ReadonlyMap<string, ApiCatalogEntry>;
21
+ export declare const API_CATALOG: ApiCatalog;
22
+ export declare function getModelCost(provider: string, model: string, opts?: {
23
+ catalog?: ApiCatalog;
24
+ preferCatalog?: boolean;
25
+ }): ModelCost;
26
+ export declare function composeCost(counts: TokenCounts | null, estimate: TokenEstimate, modelCost: ModelCost): CostResult;
package/dist/pricing.js CHANGED
@@ -116,3 +116,170 @@ export function estimateCacheSavingsUsd(cli, model, cacheReadTokens) {
116
116
  const savedPerToken = (p.inputUsd * (1 - p.cacheReadMultiplier)) / 1_000_000;
117
117
  return cacheReadTokens * savedPerToken;
118
118
  }
119
+ export function modelIdToFamily(model) {
120
+ const lower = model.toLowerCase();
121
+ if (lower.includes("sonnet"))
122
+ return "claude-sonnet";
123
+ if (lower.includes("opus"))
124
+ return "claude-opus";
125
+ if (lower.includes("haiku"))
126
+ return "claude-haiku";
127
+ if (lower.includes("gpt-5") || lower.includes("o3"))
128
+ return "openai-gpt5";
129
+ const geminiSpecialty = lower.includes("lite") ||
130
+ lower.includes("image") ||
131
+ lower.includes("audio") ||
132
+ lower.includes("tts");
133
+ if (!geminiSpecialty) {
134
+ if (lower.includes("2.5-flash"))
135
+ return "gemini-2.5-flash";
136
+ if (lower.includes("2.5-pro"))
137
+ return "gemini-2.5-pro";
138
+ if (lower.includes("gemini-3") && lower.includes("pro"))
139
+ return "gemini-3-pro";
140
+ }
141
+ if (lower.includes("grok-build") || lower.includes("grok-code"))
142
+ return "grok-build";
143
+ if (lower.includes("grok-4") || lower.includes("grok-3") || lower.includes("grok-latest")) {
144
+ return "grok-4";
145
+ }
146
+ if (lower.includes("devstral-small"))
147
+ return "mistral-devstral";
148
+ if (lower.includes("mistral-medium-3.5"))
149
+ return "mistral-medium";
150
+ return "unknown";
151
+ }
152
+ const FAMILY_PRICING = new Map([
153
+ ["claude-sonnet", ANTHROPIC_SONNET],
154
+ ["claude-opus", ANTHROPIC_OPUS],
155
+ ["claude-haiku", ANTHROPIC_HAIKU],
156
+ ["openai-gpt5", OPENAI_GPT5],
157
+ ["gemini-2.5-pro", GEMINI_25_PRO],
158
+ ["gemini-2.5-flash", GEMINI_FLASH],
159
+ ["gemini-3-pro", GEMINI_3_PRO],
160
+ ["grok-4", GROK_4],
161
+ ["grok-build", GROK_BUILD],
162
+ ["mistral-medium", MISTRAL_MEDIUM],
163
+ ["mistral-devstral", MISTRAL_DEVSTRAL],
164
+ ]);
165
+ export const API_CATALOG_AS_OF = "2026-07-11";
166
+ export const API_CATALOG = new Map([
167
+ [
168
+ "openai/gpt-5.5",
169
+ {
170
+ inputUsdPerMTok: 1.25,
171
+ outputUsdPerMTok: 10,
172
+ cacheReadMultiplier: 0.5,
173
+ family: "openai-gpt5",
174
+ },
175
+ ],
176
+ [
177
+ "anthropic/claude-sonnet-4.5",
178
+ {
179
+ inputUsdPerMTok: 3,
180
+ outputUsdPerMTok: 15,
181
+ cacheReadMultiplier: 0.1,
182
+ accountingMode: "disjoint",
183
+ family: "claude-sonnet",
184
+ },
185
+ ],
186
+ [
187
+ "google/gemini-2.5-flash",
188
+ {
189
+ inputUsdPerMTok: 0.3,
190
+ outputUsdPerMTok: 2.5,
191
+ cacheReadMultiplier: 0.1,
192
+ family: "gemini-2.5-flash",
193
+ },
194
+ ],
195
+ [
196
+ "grok-build-0.1",
197
+ { inputUsdPerMTok: 1, outputUsdPerMTok: 2, cacheReadMultiplier: 0.2, family: "grok-build" },
198
+ ],
199
+ [
200
+ "grok-4",
201
+ { inputUsdPerMTok: 1.25, outputUsdPerMTok: 2.5, cacheReadMultiplier: 0.16, family: "grok-4" },
202
+ ],
203
+ ]);
204
+ function tableModelCost(family, rate) {
205
+ const accountingMode = family.startsWith("claude-") ? "disjoint" : "inclusive";
206
+ return {
207
+ inputUsdPerMTok: rate.inputUsd,
208
+ outputUsdPerMTok: rate.outputUsd,
209
+ cacheReadMultiplier: rate.cacheReadMultiplier,
210
+ cacheWriteUsdPerMTok: rate.inputUsd,
211
+ accountingMode,
212
+ family,
213
+ source: "table",
214
+ asOf: PRICING_AS_OF,
215
+ };
216
+ }
217
+ function catalogModelCost(model, entry) {
218
+ const family = entry.family ?? modelIdToFamily(model);
219
+ return {
220
+ inputUsdPerMTok: entry.inputUsdPerMTok,
221
+ outputUsdPerMTok: entry.outputUsdPerMTok,
222
+ cacheReadMultiplier: entry.cacheReadMultiplier,
223
+ cacheWriteUsdPerMTok: entry.inputUsdPerMTok,
224
+ accountingMode: entry.accountingMode ?? "inclusive",
225
+ family: family === "unknown" ? model.toLowerCase() : family,
226
+ source: "api-catalog",
227
+ asOf: API_CATALOG_AS_OF,
228
+ };
229
+ }
230
+ const UNKNOWN_MODEL_COST = {
231
+ inputUsdPerMTok: 0,
232
+ outputUsdPerMTok: 0,
233
+ cacheReadMultiplier: 0,
234
+ cacheWriteUsdPerMTok: 0,
235
+ accountingMode: "inclusive",
236
+ family: "unknown",
237
+ source: "unknown",
238
+ asOf: PRICING_AS_OF,
239
+ };
240
+ export function getModelCost(provider, model, opts) {
241
+ const catalog = opts?.catalog ?? API_CATALOG;
242
+ const preferCatalog = opts?.preferCatalog ?? true;
243
+ const catalogEntry = catalog.get(model.toLowerCase());
244
+ const family = modelIdToFamily(model);
245
+ const familyRate = FAMILY_PRICING.get(family);
246
+ if (catalogEntry !== undefined && (preferCatalog || familyRate === undefined)) {
247
+ return catalogModelCost(model, catalogEntry);
248
+ }
249
+ if (familyRate !== undefined) {
250
+ return tableModelCost(family, familyRate);
251
+ }
252
+ return { ...UNKNOWN_MODEL_COST };
253
+ }
254
+ function composeTotalUsd(inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, reasoningTokens, modelCost, inputIsWholePrompt) {
255
+ const M = 1_000_000;
256
+ const { inputUsdPerMTok, outputUsdPerMTok, cacheWriteUsdPerMTok, cacheReadMultiplier } = modelCost;
257
+ const outputCost = ((outputTokens + reasoningTokens) * outputUsdPerMTok) / M;
258
+ const cacheWriteCost = (cacheWriteTokens * cacheWriteUsdPerMTok) / M;
259
+ if (modelCost.accountingMode === "disjoint") {
260
+ const freshTokens = inputIsWholePrompt
261
+ ? inputTokens - cacheReadTokens - cacheWriteTokens
262
+ : inputTokens;
263
+ const freshCost = (freshTokens * inputUsdPerMTok) / M;
264
+ const cacheReadCost = (cacheReadTokens * inputUsdPerMTok * cacheReadMultiplier) / M;
265
+ return freshCost + cacheWriteCost + cacheReadCost + outputCost;
266
+ }
267
+ const baseCost = (inputTokens * inputUsdPerMTok) / M;
268
+ const cacheReadDiscount = (cacheReadTokens * inputUsdPerMTok * (1 - cacheReadMultiplier)) / M;
269
+ return baseCost + cacheWriteCost + outputCost - cacheReadDiscount;
270
+ }
271
+ export function composeCost(counts, estimate, modelCost) {
272
+ if (counts?.reportedCostUsd != null) {
273
+ return {
274
+ costUsd: counts.reportedCostUsd,
275
+ cost_basis: "provider-reported",
276
+ confidence: "high",
277
+ };
278
+ }
279
+ if (counts != null && modelCost.source !== "unknown") {
280
+ const costUsd = composeTotalUsd(counts.inputTokens, counts.outputTokens, counts.cacheReadTokens ?? 0, counts.cacheCreationTokens ?? 0, counts.reasoningTokens ?? 0, modelCost, false);
281
+ return { costUsd, cost_basis: "derived-from-tokens", confidence: "high" };
282
+ }
283
+ const costUsd = composeTotalUsd(estimate.estInputTokens, estimate.estOutputTokens, estimate.estCacheReadTokens ?? 0, estimate.estCacheWriteTokens ?? 0, 0, modelCost, true);
284
+ return { costUsd, cost_basis: "pre-flight-estimate", confidence: "low" };
285
+ }
@@ -97,6 +97,10 @@ export interface ProviderRequestSurface {
97
97
  readonly acpCapable: boolean;
98
98
  readonly syncToolName: string;
99
99
  readonly asyncToolName: string;
100
+ readonly acceptsImages: boolean;
101
+ readonly acceptsAttachments: boolean;
102
+ readonly toolCalling: boolean;
103
+ readonly jsonSchema: boolean;
100
104
  }
101
105
  export interface ProviderDefinition {
102
106
  readonly id: ProviderId;
@@ -119,6 +123,7 @@ export interface ProviderDefinition {
119
123
  readonly upstreamContract: ProviderUpstreamLinkage;
120
124
  readonly capabilityScope: CapabilityScope;
121
125
  }
126
+ export declare const PROVIDER_TARGET_VERSIONS: Record<CliType, string>;
122
127
  export declare const PROVIDER_DEFINITIONS_BY_ID: Readonly<Record<CliType, ProviderDefinition>>;
123
128
  export declare function getAllProviderDefinitions(): readonly ProviderDefinition[];
124
129
  export declare function getProviderDefinition(id: CliType): ProviderDefinition;
@@ -4,6 +4,15 @@ export function adminSurfaceKind(family) {
4
4
  return family.kind ?? "cli-subcommand";
5
5
  }
6
6
  export const DEVIN_ACP_AGENT_TYPES = ["summarizer", "review"];
7
+ export const PROVIDER_TARGET_VERSIONS = {
8
+ claude: "claude 2.1.206",
9
+ codex: "codex-cli 0.144.1",
10
+ gemini: "agy 1.1.0",
11
+ grok: "grok 0.2.93 (f00f96316d)",
12
+ mistral: "vibe 2.19.1",
13
+ devin: "devin 3000.1.27 (0d4bf12e)",
14
+ cursor: "cursor-agent 2026.07.09-c59fd9a",
15
+ };
7
16
  const PROVIDER_DEFINITIONS = {
8
17
  claude: {
9
18
  id: "claude",
@@ -19,6 +28,10 @@ const PROVIDER_DEFINITIONS = {
19
28
  acpCapable: false,
20
29
  syncToolName: "claude_request",
21
30
  asyncToolName: "claude_request_async",
31
+ acceptsImages: false,
32
+ acceptsAttachments: false,
33
+ toolCalling: true,
34
+ jsonSchema: true,
22
35
  },
23
36
  docs: { primary: ["https://code.claude.com/docs/en/cli-reference"] },
24
37
  discovery: {
@@ -86,7 +99,7 @@ const PROVIDER_DEFINITIONS = {
86
99
  nativeEntrypoint: null,
87
100
  entrypoint: null,
88
101
  probeArgv: [],
89
- evidence: "No native Claude Code ACP subcommand or flag in installed help (claude 2.1.198) or the official CLI reference. Coverage is CLI-first; ACP reporting says no native entrypoint is advertised.",
102
+ evidence: `No native Claude Code ACP subcommand or flag in installed help (${PROVIDER_TARGET_VERSIONS.claude}) or the official CLI reference. Coverage is CLI-first; ACP reporting says no native entrypoint is advertised.`,
90
103
  },
91
104
  safetyModes: {
92
105
  sandbox: false,
@@ -98,7 +111,10 @@ const PROVIDER_DEFINITIONS = {
98
111
  outputFormats: ["text", "json", "stream-json"],
99
112
  streamingFormats: ["stream-json"],
100
113
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
101
- upstreamContract: { targetVersion: "Claude Code 2.1.198", helpChecksumRef: "claude--help.txt" },
114
+ upstreamContract: {
115
+ targetVersion: PROVIDER_TARGET_VERSIONS.claude,
116
+ helpChecksumRef: "claude--help.txt",
117
+ },
102
118
  capabilityScope: "full",
103
119
  },
104
120
  codex: {
@@ -115,6 +131,10 @@ const PROVIDER_DEFINITIONS = {
115
131
  acpCapable: false,
116
132
  syncToolName: "codex_request",
117
133
  asyncToolName: "codex_request_async",
134
+ acceptsImages: true,
135
+ acceptsAttachments: true,
136
+ toolCalling: true,
137
+ jsonSchema: true,
118
138
  },
119
139
  docs: {
120
140
  primary: [
@@ -215,7 +235,7 @@ const PROVIDER_DEFINITIONS = {
215
235
  nativeEntrypoint: null,
216
236
  entrypoint: null,
217
237
  probeArgv: [],
218
- evidence: "codex-cli 0.142.4 advertises mcp-server and app-server transports, not a native ACP agent entrypoint. Third-party adapters exist but are documentation only and are never treated as native gateway ACP.",
238
+ evidence: `${PROVIDER_TARGET_VERSIONS.codex} advertises mcp-server and app-server transports, not a native ACP agent entrypoint. Third-party adapters exist but are documentation only and are never treated as native gateway ACP.`,
219
239
  },
220
240
  safetyModes: {
221
241
  sandbox: true,
@@ -233,7 +253,7 @@ const PROVIDER_DEFINITIONS = {
233
253
  streamingFormats: ["jsonl"],
234
254
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
235
255
  upstreamContract: {
236
- targetVersion: "codex-cli 0.142.4",
256
+ targetVersion: PROVIDER_TARGET_VERSIONS.codex,
237
257
  helpChecksumRef: "codex-exec--help.txt",
238
258
  },
239
259
  capabilityScope: "full",
@@ -252,6 +272,10 @@ const PROVIDER_DEFINITIONS = {
252
272
  acpCapable: false,
253
273
  syncToolName: "gemini_request",
254
274
  asyncToolName: "gemini_request_async",
275
+ acceptsImages: false,
276
+ acceptsAttachments: false,
277
+ toolCalling: true,
278
+ jsonSchema: false,
255
279
  },
256
280
  docs: {
257
281
  primary: [
@@ -319,7 +343,7 @@ const PROVIDER_DEFINITIONS = {
319
343
  nativeEntrypoint: null,
320
344
  entrypoint: null,
321
345
  probeArgv: [],
322
- evidence: "agy 1.0.14 has no ACP flag or subcommand in installed help or the Antigravity CLI docs. Legacy Gemini CLI ACP evidence does not transfer. No native entrypoint advertised.",
346
+ evidence: `${PROVIDER_TARGET_VERSIONS.gemini} has no ACP flag or subcommand in installed help or the Antigravity CLI docs. Legacy Gemini CLI ACP evidence does not transfer. No native entrypoint advertised.`,
323
347
  },
324
348
  safetyModes: {
325
349
  sandbox: true,
@@ -331,7 +355,10 @@ const PROVIDER_DEFINITIONS = {
331
355
  outputFormats: ["text"],
332
356
  streamingFormats: [],
333
357
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
334
- upstreamContract: { targetVersion: "agy 1.0.14", helpChecksumRef: "agy--help.txt" },
358
+ upstreamContract: {
359
+ targetVersion: PROVIDER_TARGET_VERSIONS.gemini,
360
+ helpChecksumRef: "agy--help.txt",
361
+ },
335
362
  capabilityScope: "full",
336
363
  },
337
364
  grok: {
@@ -348,6 +375,10 @@ const PROVIDER_DEFINITIONS = {
348
375
  acpCapable: true,
349
376
  syncToolName: "grok_request",
350
377
  asyncToolName: "grok_request_async",
378
+ acceptsImages: false,
379
+ acceptsAttachments: false,
380
+ toolCalling: true,
381
+ jsonSchema: true,
351
382
  },
352
383
  docs: {
353
384
  primary: [
@@ -436,7 +467,7 @@ const PROVIDER_DEFINITIONS = {
436
467
  streamingFormats: ["json"],
437
468
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
438
469
  upstreamContract: {
439
- targetVersion: "grok 0.2.77 (44e77bec3a)",
470
+ targetVersion: PROVIDER_TARGET_VERSIONS.grok,
440
471
  helpChecksumRef: "grok--help.txt",
441
472
  },
442
473
  capabilityScope: "full",
@@ -455,6 +486,10 @@ const PROVIDER_DEFINITIONS = {
455
486
  acpCapable: true,
456
487
  syncToolName: "mistral_request",
457
488
  asyncToolName: "mistral_request_async",
489
+ acceptsImages: false,
490
+ acceptsAttachments: false,
491
+ toolCalling: true,
492
+ jsonSchema: false,
458
493
  },
459
494
  docs: { primary: ["https://github.com/mistralai/mistral-vibe"] },
460
495
  discovery: {
@@ -551,7 +586,10 @@ const PROVIDER_DEFINITIONS = {
551
586
  outputFormats: ["text", "json"],
552
587
  streamingFormats: [],
553
588
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
554
- upstreamContract: { targetVersion: "vibe 2.18.3", helpChecksumRef: "vibe--help.txt" },
589
+ upstreamContract: {
590
+ targetVersion: PROVIDER_TARGET_VERSIONS.mistral,
591
+ helpChecksumRef: "vibe--help.txt",
592
+ },
555
593
  capabilityScope: "full",
556
594
  },
557
595
  devin: {
@@ -568,6 +606,10 @@ const PROVIDER_DEFINITIONS = {
568
606
  acpCapable: true,
569
607
  syncToolName: "devin_request",
570
608
  asyncToolName: "devin_request_async",
609
+ acceptsImages: false,
610
+ acceptsAttachments: false,
611
+ toolCalling: true,
612
+ jsonSchema: false,
571
613
  },
572
614
  docs: { primary: ["https://docs.devin.ai/cli/reference/commands"] },
573
615
  discovery: {
@@ -660,7 +702,7 @@ const PROVIDER_DEFINITIONS = {
660
702
  streamingFormats: [],
661
703
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
662
704
  upstreamContract: {
663
- targetVersion: "devin 2026.8.18 (16737566)",
705
+ targetVersion: PROVIDER_TARGET_VERSIONS.devin,
664
706
  helpChecksumRef: "devin--help.txt",
665
707
  },
666
708
  capabilityScope: "full",
@@ -679,6 +721,10 @@ const PROVIDER_DEFINITIONS = {
679
721
  acpCapable: true,
680
722
  syncToolName: "cursor_request",
681
723
  asyncToolName: "cursor_request_async",
724
+ acceptsImages: false,
725
+ acceptsAttachments: false,
726
+ toolCalling: true,
727
+ jsonSchema: false,
682
728
  },
683
729
  docs: { primary: ["https://docs.cursor.com/en/cli/overview"] },
684
730
  discovery: {
@@ -734,7 +780,7 @@ const PROVIDER_DEFINITIONS = {
734
780
  streamingFormats: ["stream-json"],
735
781
  resourcePolicy: { exposesModelsResource: true, exposesSessionsResource: true },
736
782
  upstreamContract: {
737
- targetVersion: "cursor-agent 2026.06.29-2ad2186",
783
+ targetVersion: PROVIDER_TARGET_VERSIONS.cursor,
738
784
  helpChecksumRef: "cursor-agent--help.txt",
739
785
  },
740
786
  capabilityScope: "maintain-only",
@@ -4,7 +4,7 @@ import path from "path";
4
4
  import { parse as parseToml } from "smol-toml";
5
5
  import { CLAUDE_MCP_SERVER_NAMES } from "./claude-mcp-config.js";
6
6
  import { getAvailableCliInfo } from "./model-registry.js";
7
- import { CLI_TYPES, getProviderDefinition } from "./provider-definitions.js";
7
+ import { CLI_TYPES, getProviderDefinition, PROVIDER_TARGET_VERSIONS, } from "./provider-definitions.js";
8
8
  import { enabledApiProviders, isXaiProviderEnabled, loadProvidersConfig, } from "./config.js";
9
9
  import { apiContinuityForKind } from "./api-provider.js";
10
10
  const MAX_SKILLS_PER_DIR = 100;
@@ -50,7 +50,7 @@ export const ACP_CONTRACT = {
50
50
  },
51
51
  gemini: {
52
52
  classification: "absent_watchlist",
53
- summary: "Google Antigravity agy 1.0.14 has no ACP surface; watchlist item only.",
53
+ summary: `Google Antigravity ${PROVIDER_TARGET_VERSIONS.gemini} has no ACP surface; watchlist item only.`,
54
54
  },
55
55
  grok_api: {
56
56
  classification: "absent_watchlist",
@@ -110,7 +110,7 @@ const ACP_RESIDUAL = {
110
110
  smokeSupported: false,
111
111
  smokeStatus: "unsupported",
112
112
  caveats: [
113
- "Antigravity agy 1.0.14 has no ACP flag or subcommand.",
113
+ `Antigravity ${PROVIDER_TARGET_VERSIONS.gemini} has no ACP flag or subcommand.`,
114
114
  "Legacy Gemini CLI ACP evidence does not transfer to agy; kept on the upstream drift watchlist.",
115
115
  ],
116
116
  },
@@ -229,8 +229,8 @@ export declare const CODEX_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodObject<{
229
229
  disable: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
230
230
  }, "strip", z.ZodTypeAny, {
231
231
  search?: boolean | undefined;
232
- outputSchema?: string | Record<string, unknown> | undefined;
233
232
  profile?: string | undefined;
233
+ outputSchema?: string | Record<string, unknown> | undefined;
234
234
  configOverrides?: Record<string, string> | undefined;
235
235
  ephemeral?: boolean | undefined;
236
236
  images?: string[] | undefined;
@@ -240,8 +240,8 @@ export declare const CODEX_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodObject<{
240
240
  disable?: string[] | undefined;
241
241
  }, {
242
242
  search?: boolean | undefined;
243
- outputSchema?: string | Record<string, unknown> | undefined;
244
243
  profile?: string | undefined;
244
+ outputSchema?: string | Record<string, unknown> | undefined;
245
245
  configOverrides?: Record<string, string> | undefined;
246
246
  ephemeral?: boolean | undefined;
247
247
  images?: string[] | undefined;
@@ -99,7 +99,7 @@ export const MISTRAL_BUILTIN_AGENT_MODES = [
99
99
  "accept-edits",
100
100
  "auto-approve",
101
101
  ];
102
- export const MISTRAL_DEFAULT_AGENT_MODE = "auto-approve";
102
+ export const MISTRAL_DEFAULT_AGENT_MODE = "accept-edits";
103
103
  export function prepareMistralRequest(input) {
104
104
  const args = ["-p", input.prompt];
105
105
  const env = {};
@@ -3,7 +3,7 @@ import { type CliType } from "./session-manager.js";
3
3
  import { PerformanceMetrics } from "./metrics.js";
4
4
  import { FlightRecorderQuery } from "./flight-recorder.js";
5
5
  import { type GlobalCacheStats, type PrefixCacheStats, type SessionCacheStats } from "./cache-stats.js";
6
- import { type CacheAwarenessConfig, type ProvidersConfig } from "./config.js";
6
+ import { type CacheAwarenessConfig, type LeastCostConfig, type ProvidersConfig } from "./config.js";
7
7
  import type { AcpConfig } from "./config.js";
8
8
  import { type ResolvedProviderCapability } from "./provider-capability-resolver.js";
9
9
  export interface ResourceDefinition {
@@ -31,7 +31,9 @@ export declare class ResourceProvider {
31
31
  private providers;
32
32
  private capabilityPeek;
33
33
  private acpConfig;
34
- constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null, providers?: ProvidersConfig | null, capabilityPeek?: (id: CliType) => ResolvedProviderCapability | null, acpConfig?: AcpConfig | null);
34
+ private leastCost;
35
+ constructor(sessionManager: ISessionManager, performanceMetrics: PerformanceMetrics, flightRecorder?: FlightRecorderQuery, cacheAwareness?: CacheAwarenessConfig | null, providers?: ProvidersConfig | null, capabilityPeek?: (id: CliType) => ResolvedProviderCapability | null, acpConfig?: AcpConfig | null, leastCost?: LeastCostConfig | undefined);
36
+ private routingResourcesEnabled;
35
37
  getFlightRecorderQuery(): FlightRecorderQuery;
36
38
  private apiRuntimes;
37
39
  private continuityTrackedApiRuntimes;
@@ -42,7 +44,40 @@ export declare class ResourceProvider {
42
44
  readCacheStateSession(sessionId: string): SessionCacheStats;
43
45
  readCacheStateForPrefix(stablePrefixHash: string): PrefixCacheStats;
44
46
  listResources(): ResourceDefinition[];
47
+ private readRoutingDecisions;
48
+ private readRoutingPriors;
45
49
  private ownedSessions;
46
50
  private ownedActiveId;
47
51
  readResource(uri: string): Promise<ResourceContents | null>;
48
52
  }
53
+ export interface RoutingDecision {
54
+ provider: string;
55
+ model: string;
56
+ tier: string;
57
+ estCostUsd: number | null;
58
+ costBasis: string | null;
59
+ confidence: string | null;
60
+ reason: string | null;
61
+ considered: number | null;
62
+ reroutes: number | null;
63
+ at: string;
64
+ }
65
+ export interface RoutingPriorsPayload {
66
+ priorsScope: string;
67
+ priceAsOf: {
68
+ table: string;
69
+ apiCatalog: string;
70
+ };
71
+ outputPriors: Array<{
72
+ candidate: string;
73
+ median: number;
74
+ p90: number;
75
+ samples: number;
76
+ }>;
77
+ calibration: Array<{
78
+ bucket: string;
79
+ k: number;
80
+ samples: number;
81
+ confidence: string;
82
+ }>;
83
+ }