llm-cli-gateway 2.16.0 → 2.17.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/least-cost-routing/SKILL.md +123 -0
- package/CHANGELOG.md +37 -0
- package/README.md +12 -12
- package/dist/acp/client.js +5 -0
- package/dist/acp/flight-redaction.d.ts +2 -0
- package/dist/acp/flight-redaction.js +2 -0
- package/dist/acp/runtime.d.ts +1 -0
- package/dist/acp/runtime.js +20 -0
- package/dist/acp/types.d.ts +52 -0
- package/dist/acp/types.js +8 -0
- package/dist/api-provider.d.ts +1 -0
- package/dist/api-provider.js +1 -0
- package/dist/async-job-manager.d.ts +16 -0
- package/dist/async-job-manager.js +70 -22
- package/dist/claude-mcp-config.js +1 -1
- package/dist/compressor/transforms/ansi.js +12 -2
- package/dist/config.d.ts +31 -0
- package/dist/config.js +142 -7
- package/dist/db.js +4 -4
- package/dist/doctor.d.ts +34 -1
- package/dist/doctor.js +85 -3
- package/dist/executor.d.ts +5 -0
- package/dist/executor.js +11 -1
- package/dist/flight-recorder.d.ts +10 -0
- package/dist/flight-recorder.js +68 -2
- package/dist/http-transport.js +19 -21
- package/dist/index.d.ts +42 -3
- package/dist/index.js +666 -41
- package/dist/job-store.d.ts +10 -2
- package/dist/job-store.js +154 -43
- package/dist/lcr-priors.d.ts +60 -0
- package/dist/lcr-priors.js +190 -0
- package/dist/lcr-router-env.d.ts +20 -0
- package/dist/lcr-router-env.js +133 -0
- package/dist/lcr-telemetry.d.ts +2 -0
- package/dist/lcr-telemetry.js +17 -0
- package/dist/least-cost-router.d.ts +86 -0
- package/dist/least-cost-router.js +296 -0
- package/dist/least-cost-types.d.ts +34 -0
- package/dist/least-cost-types.js +1 -0
- package/dist/migrate-sessions.js +1 -1
- package/dist/migrate.js +1 -1
- package/dist/model-registry.js +1 -1
- package/dist/postgres-job-store-worker.js +56 -13
- package/dist/pricing.d.ts +17 -0
- package/dist/pricing.js +167 -0
- package/dist/provider-admin-tools.js +12 -4
- package/dist/provider-definitions.d.ts +4 -0
- package/dist/provider-definitions.js +33 -5
- package/dist/provider-tool-capabilities.js +6 -9
- package/dist/request-helpers.d.ts +3 -4
- package/dist/request-helpers.js +9 -10
- package/dist/resources.d.ts +37 -2
- package/dist/resources.js +96 -1
- package/dist/retry.d.ts +1 -0
- package/dist/retry.js +1 -1
- package/dist/token-estimator.d.ts +6 -0
- package/dist/token-estimator.js +59 -0
- package/dist/upstream-contracts.d.ts +4 -1
- package/dist/upstream-contracts.js +255 -71
- package/dist/validation-receipt.js +1 -1
- package/dist/validation-tools.d.ts +6 -0
- package/dist/validation-tools.js +174 -54
- package/npm-shrinkwrap.json +2 -2
- package/package.json +14 -5
- package/setup/status.schema.json +68 -0
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
|
+
}
|
|
@@ -122,6 +122,13 @@ export function familyBaseRisk(provider, fam) {
|
|
|
122
122
|
return contract.risk;
|
|
123
123
|
return fam.safety === "read-only" ? "read_only" : "writes_local_config";
|
|
124
124
|
}
|
|
125
|
+
function contractAdminProjectionCeiling(provider, commandPath) {
|
|
126
|
+
const exactContract = getCliSubcommandContract(provider, commandPath);
|
|
127
|
+
return exactContract?.adminProjection === "not_exposed" ? "not_exposed" : null;
|
|
128
|
+
}
|
|
129
|
+
function projectedAdminExposure(provider, commandPath, risk) {
|
|
130
|
+
return contractAdminProjectionCeiling(provider, commandPath) ?? adminRiskToExposure(risk);
|
|
131
|
+
}
|
|
125
132
|
function rootAdvertises(discovered, family) {
|
|
126
133
|
const wanted = family.toLowerCase();
|
|
127
134
|
return discovered.rootHelp.subcommands.some(s => s.name.toLowerCase() === wanted);
|
|
@@ -142,6 +149,7 @@ export function projectProviderAdminOperations(def, discovered) {
|
|
|
142
149
|
if (adminSurfaceKind(fam) !== "cli-subcommand")
|
|
143
150
|
continue;
|
|
144
151
|
const baseRisk = familyBaseRisk(def.id, fam);
|
|
152
|
+
const familyExposure = projectedAdminExposure(def.id, [fam.family], baseRisk);
|
|
145
153
|
if (!discovered) {
|
|
146
154
|
ops.push({
|
|
147
155
|
provider: def.id,
|
|
@@ -149,8 +157,8 @@ export function projectProviderAdminOperations(def, discovered) {
|
|
|
149
157
|
operationId: fam.family,
|
|
150
158
|
argv: [fam.family],
|
|
151
159
|
risk: baseRisk,
|
|
152
|
-
exposure:
|
|
153
|
-
mutating:
|
|
160
|
+
exposure: familyExposure,
|
|
161
|
+
mutating: familyExposure === "mcp_requires_approval",
|
|
154
162
|
available: false,
|
|
155
163
|
discoverySource: "no-discovery",
|
|
156
164
|
summary: fam.evidence,
|
|
@@ -165,7 +173,7 @@ export function projectProviderAdminOperations(def, discovered) {
|
|
|
165
173
|
if (!isSafeAdminToken(sub.name))
|
|
166
174
|
continue;
|
|
167
175
|
const risk = classifyOperationRisk(sub.name, baseRisk, { isSubcommand: true });
|
|
168
|
-
const exposure =
|
|
176
|
+
const exposure = projectedAdminExposure(def.id, [fam.family, sub.name], risk);
|
|
169
177
|
ops.push({
|
|
170
178
|
provider: def.id,
|
|
171
179
|
family: fam.family,
|
|
@@ -183,7 +191,7 @@ export function projectProviderAdminOperations(def, discovered) {
|
|
|
183
191
|
}
|
|
184
192
|
const baseExposure = adminRiskToExposure(baseRisk);
|
|
185
193
|
const risk = baseExposure === "not_exposed" ? baseRisk : classifyOperationRisk(fam.family, baseRisk);
|
|
186
|
-
const exposure =
|
|
194
|
+
const exposure = projectedAdminExposure(def.id, [fam.family], risk);
|
|
187
195
|
ops.push({
|
|
188
196
|
provider: def.id,
|
|
189
197
|
family: fam.family,
|
|
@@ -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;
|
|
@@ -5,13 +5,13 @@ export function adminSurfaceKind(family) {
|
|
|
5
5
|
}
|
|
6
6
|
export const DEVIN_ACP_AGENT_TYPES = ["summarizer", "review"];
|
|
7
7
|
export const PROVIDER_TARGET_VERSIONS = {
|
|
8
|
-
claude: "claude 2.1.
|
|
9
|
-
codex: "codex-cli 0.144.
|
|
10
|
-
gemini: "agy 1.1.
|
|
11
|
-
grok: "grok 0.2.
|
|
8
|
+
claude: "claude 2.1.207",
|
|
9
|
+
codex: "codex-cli 0.144.3",
|
|
10
|
+
gemini: "agy 1.1.1",
|
|
11
|
+
grok: "grok 0.2.99 (b1b49ccb71)",
|
|
12
12
|
mistral: "vibe 2.19.1",
|
|
13
13
|
devin: "devin 3000.1.27 (0d4bf12e)",
|
|
14
|
-
cursor: "cursor-agent 2026.07.09-
|
|
14
|
+
cursor: "cursor-agent 2026.07.09-a3815c0",
|
|
15
15
|
};
|
|
16
16
|
const PROVIDER_DEFINITIONS = {
|
|
17
17
|
claude: {
|
|
@@ -28,6 +28,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
28
28
|
acpCapable: false,
|
|
29
29
|
syncToolName: "claude_request",
|
|
30
30
|
asyncToolName: "claude_request_async",
|
|
31
|
+
acceptsImages: false,
|
|
32
|
+
acceptsAttachments: false,
|
|
33
|
+
toolCalling: true,
|
|
34
|
+
jsonSchema: true,
|
|
31
35
|
},
|
|
32
36
|
docs: { primary: ["https://code.claude.com/docs/en/cli-reference"] },
|
|
33
37
|
discovery: {
|
|
@@ -127,6 +131,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
127
131
|
acpCapable: false,
|
|
128
132
|
syncToolName: "codex_request",
|
|
129
133
|
asyncToolName: "codex_request_async",
|
|
134
|
+
acceptsImages: true,
|
|
135
|
+
acceptsAttachments: true,
|
|
136
|
+
toolCalling: true,
|
|
137
|
+
jsonSchema: true,
|
|
130
138
|
},
|
|
131
139
|
docs: {
|
|
132
140
|
primary: [
|
|
@@ -264,6 +272,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
264
272
|
acpCapable: false,
|
|
265
273
|
syncToolName: "gemini_request",
|
|
266
274
|
asyncToolName: "gemini_request_async",
|
|
275
|
+
acceptsImages: false,
|
|
276
|
+
acceptsAttachments: false,
|
|
277
|
+
toolCalling: true,
|
|
278
|
+
jsonSchema: false,
|
|
267
279
|
},
|
|
268
280
|
docs: {
|
|
269
281
|
primary: [
|
|
@@ -363,6 +375,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
363
375
|
acpCapable: true,
|
|
364
376
|
syncToolName: "grok_request",
|
|
365
377
|
asyncToolName: "grok_request_async",
|
|
378
|
+
acceptsImages: false,
|
|
379
|
+
acceptsAttachments: false,
|
|
380
|
+
toolCalling: true,
|
|
381
|
+
jsonSchema: true,
|
|
366
382
|
},
|
|
367
383
|
docs: {
|
|
368
384
|
primary: [
|
|
@@ -470,6 +486,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
470
486
|
acpCapable: true,
|
|
471
487
|
syncToolName: "mistral_request",
|
|
472
488
|
asyncToolName: "mistral_request_async",
|
|
489
|
+
acceptsImages: false,
|
|
490
|
+
acceptsAttachments: false,
|
|
491
|
+
toolCalling: true,
|
|
492
|
+
jsonSchema: false,
|
|
473
493
|
},
|
|
474
494
|
docs: { primary: ["https://github.com/mistralai/mistral-vibe"] },
|
|
475
495
|
discovery: {
|
|
@@ -586,6 +606,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
586
606
|
acpCapable: true,
|
|
587
607
|
syncToolName: "devin_request",
|
|
588
608
|
asyncToolName: "devin_request_async",
|
|
609
|
+
acceptsImages: false,
|
|
610
|
+
acceptsAttachments: false,
|
|
611
|
+
toolCalling: true,
|
|
612
|
+
jsonSchema: false,
|
|
589
613
|
},
|
|
590
614
|
docs: { primary: ["https://docs.devin.ai/cli/reference/commands"] },
|
|
591
615
|
discovery: {
|
|
@@ -697,6 +721,10 @@ const PROVIDER_DEFINITIONS = {
|
|
|
697
721
|
acpCapable: true,
|
|
698
722
|
syncToolName: "cursor_request",
|
|
699
723
|
asyncToolName: "cursor_request_async",
|
|
724
|
+
acceptsImages: false,
|
|
725
|
+
acceptsAttachments: false,
|
|
726
|
+
toolCalling: true,
|
|
727
|
+
jsonSchema: false,
|
|
700
728
|
},
|
|
701
729
|
docs: { primary: ["https://docs.cursor.com/en/cli/overview"] },
|
|
702
730
|
discovery: {
|
|
@@ -615,7 +615,7 @@ const TOOL_CONTROLS = {
|
|
|
615
615
|
mistral: {
|
|
616
616
|
providerKind: "cli",
|
|
617
617
|
gatewayRequestTools: ["mistral_request", "mistral_request_async"],
|
|
618
|
-
summary: "Mistral Vibe owns its runtime tool catalog; the gateway can pass Vibe enabled-tool controls and reports local skills if present.",
|
|
618
|
+
summary: "Mistral Vibe owns its runtime tool catalog; the gateway can pass Vibe enabled- and disabled-tool controls and reports local skills if present.",
|
|
619
619
|
controls: {
|
|
620
620
|
allowlist: {
|
|
621
621
|
supported: true,
|
|
@@ -624,9 +624,10 @@ const TOOL_CONTROLS = {
|
|
|
624
624
|
behavior: "Each entry is emitted as a separate Vibe enabled-tool flag.",
|
|
625
625
|
},
|
|
626
626
|
denylist: {
|
|
627
|
-
supported:
|
|
627
|
+
supported: true,
|
|
628
628
|
requestField: "disallowedTools",
|
|
629
|
-
|
|
629
|
+
cliFlag: "--disabled-tools",
|
|
630
|
+
behavior: "Each entry is emitted as a separate Vibe disabled-tool flag.",
|
|
630
631
|
},
|
|
631
632
|
mcpServers: {
|
|
632
633
|
supported: false,
|
|
@@ -675,15 +676,11 @@ const TOOL_CONTROLS = {
|
|
|
675
676
|
approvalAndSandboxControls: true,
|
|
676
677
|
costAndLoopControls: true,
|
|
677
678
|
workspaceAndWorktreeControls: true,
|
|
679
|
+
toolAllowDenyControls: true,
|
|
678
680
|
enabledToolAllowlist: true,
|
|
679
681
|
trustControl: true,
|
|
680
682
|
}),
|
|
681
683
|
unsupportedInputs: [
|
|
682
|
-
{
|
|
683
|
-
input: "disallowedTools",
|
|
684
|
-
behavior: "ignored",
|
|
685
|
-
details: "Accepted for caller parity but ignored because Vibe has no deny-list flag.",
|
|
686
|
-
},
|
|
687
684
|
{
|
|
688
685
|
input: "mcpServers",
|
|
689
686
|
behavior: "approval_tracking_only",
|
|
@@ -798,7 +795,7 @@ const TOOL_CONTROLS = {
|
|
|
798
795
|
supported: true,
|
|
799
796
|
requestField: "permissionMode",
|
|
800
797
|
cliFlag: "--permission-mode",
|
|
801
|
-
behavior: "Maps to Devin CLI --permission-mode: auto auto-approves read-only tools; smart additionally auto-runs actions a fast model judges safe; dangerous auto-approves all.",
|
|
798
|
+
behavior: "Maps to Devin CLI --permission-mode: auto auto-approves read-only tools; accept-edits also auto-approves workspace edits; smart additionally auto-runs actions a fast model judges safe; dangerous auto-approves all.",
|
|
802
799
|
},
|
|
803
800
|
promptControl: {
|
|
804
801
|
supported: true,
|
|
@@ -52,10 +52,9 @@ export interface PrepareMistralRequestInput {
|
|
|
52
52
|
export interface PrepareMistralRequestResult {
|
|
53
53
|
args: string[];
|
|
54
54
|
env: Record<string, string>;
|
|
55
|
-
ignoredDisallowedTools: boolean;
|
|
56
55
|
}
|
|
57
56
|
export declare function prepareMistralRequest(input: PrepareMistralRequestInput): PrepareMistralRequestResult;
|
|
58
|
-
export declare const CLAUDE_PERMISSION_MODES: readonly ["default", "acceptEdits", "
|
|
57
|
+
export declare const CLAUDE_PERMISSION_MODES: readonly ["default", "acceptEdits", "auto", "bypassPermissions", "manual", "dontAsk", "plan"];
|
|
59
58
|
export type ClaudePermissionMode = (typeof CLAUDE_PERMISSION_MODES)[number];
|
|
60
59
|
export interface ClaudePermissionFlagsInput {
|
|
61
60
|
permissionMode?: ClaudePermissionMode;
|
|
@@ -229,8 +228,8 @@ export declare const CODEX_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodObject<{
|
|
|
229
228
|
disable: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
230
229
|
}, "strip", z.ZodTypeAny, {
|
|
231
230
|
search?: boolean | undefined;
|
|
232
|
-
outputSchema?: string | Record<string, unknown> | undefined;
|
|
233
231
|
profile?: string | undefined;
|
|
232
|
+
outputSchema?: string | Record<string, unknown> | undefined;
|
|
234
233
|
configOverrides?: Record<string, string> | undefined;
|
|
235
234
|
ephemeral?: boolean | undefined;
|
|
236
235
|
images?: string[] | undefined;
|
|
@@ -240,8 +239,8 @@ export declare const CODEX_HIGH_IMPACT_PARAMS_SCHEMA: z.ZodObject<{
|
|
|
240
239
|
disable?: string[] | undefined;
|
|
241
240
|
}, {
|
|
242
241
|
search?: boolean | undefined;
|
|
243
|
-
outputSchema?: string | Record<string, unknown> | undefined;
|
|
244
242
|
profile?: string | undefined;
|
|
243
|
+
outputSchema?: string | Record<string, unknown> | undefined;
|
|
245
244
|
configOverrides?: Record<string, string> | undefined;
|
|
246
245
|
ephemeral?: boolean | undefined;
|
|
247
246
|
images?: string[] | undefined;
|
package/dist/request-helpers.js
CHANGED
|
@@ -3,6 +3,7 @@ import { tmpdir } from "os";
|
|
|
3
3
|
import { join, isAbsolute } from "path";
|
|
4
4
|
import { randomUUID } from "crypto";
|
|
5
5
|
import { z } from "zod/v3";
|
|
6
|
+
import { CLAUDE_WIRE_PERMISSION_MODES } from "./upstream-contracts.js";
|
|
6
7
|
export const GATEWAY_SESSION_PREFIX = "gw-";
|
|
7
8
|
export function validateSessionId(sessionId) {
|
|
8
9
|
if (sessionId.startsWith(GATEWAY_SESSION_PREFIX)) {
|
|
@@ -117,6 +118,12 @@ export function prepareMistralRequest(input) {
|
|
|
117
118
|
args.push("--enabled-tools", tool);
|
|
118
119
|
}
|
|
119
120
|
}
|
|
121
|
+
if (input.disallowedTools && input.disallowedTools.length > 0) {
|
|
122
|
+
sanitizeCliArgValues(input.disallowedTools, "disallowedTools");
|
|
123
|
+
for (const tool of input.disallowedTools) {
|
|
124
|
+
args.push("--disabled-tools", tool);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
120
127
|
if (input.trust) {
|
|
121
128
|
args.push("--trust");
|
|
122
129
|
}
|
|
@@ -137,8 +144,7 @@ export function prepareMistralRequest(input) {
|
|
|
137
144
|
args.push("--add-dir", dir);
|
|
138
145
|
}
|
|
139
146
|
}
|
|
140
|
-
|
|
141
|
-
return { args, env, ignoredDisallowedTools };
|
|
147
|
+
return { args, env };
|
|
142
148
|
}
|
|
143
149
|
function normalizeMistralOutputFormat(format) {
|
|
144
150
|
if (format === "plain")
|
|
@@ -147,14 +153,7 @@ function normalizeMistralOutputFormat(format) {
|
|
|
147
153
|
return "streaming";
|
|
148
154
|
return format;
|
|
149
155
|
}
|
|
150
|
-
export const CLAUDE_PERMISSION_MODES = [
|
|
151
|
-
"default",
|
|
152
|
-
"acceptEdits",
|
|
153
|
-
"plan",
|
|
154
|
-
"auto",
|
|
155
|
-
"dontAsk",
|
|
156
|
-
"bypassPermissions",
|
|
157
|
-
];
|
|
156
|
+
export const CLAUDE_PERMISSION_MODES = ["default", ...CLAUDE_WIRE_PERMISSION_MODES];
|
|
158
157
|
export function resolveClaudePermissionFlags(input) {
|
|
159
158
|
const { permissionMode, dangerouslySkipPermissions } = input;
|
|
160
159
|
let warning;
|
package/dist/resources.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
+
}
|
package/dist/resources.js
CHANGED
|
@@ -4,6 +4,9 @@ import { getRequestContext, principalCanAccess, resolveOwnerPrincipal } from "./
|
|
|
4
4
|
import { getAvailableCliInfo } from "./model-registry.js";
|
|
5
5
|
import { computeGlobalCacheStats, computePrefixCacheStats, computeSessionCacheStats, computeTtlRemaining, } from "./cache-stats.js";
|
|
6
6
|
import { enabledApiProviders, } from "./config.js";
|
|
7
|
+
import { computeLcrPriorsFromDb } from "./lcr-priors.js";
|
|
8
|
+
import { telemetryTierFor } from "./lcr-telemetry.js";
|
|
9
|
+
import { PRICING_AS_OF, API_CATALOG_AS_OF } from "./pricing.js";
|
|
7
10
|
import { apiContinuityForKind } from "./api-provider.js";
|
|
8
11
|
import { apiProviderCatalogEntry } from "./api-request.js";
|
|
9
12
|
import { buildProviderSubcommandsCompactCatalog, getCliSubcommandContract, serializeCliSubcommandContract, } from "./upstream-contracts.js";
|
|
@@ -20,7 +23,8 @@ export class ResourceProvider {
|
|
|
20
23
|
providers;
|
|
21
24
|
capabilityPeek;
|
|
22
25
|
acpConfig;
|
|
23
|
-
|
|
26
|
+
leastCost;
|
|
27
|
+
constructor(sessionManager, performanceMetrics, flightRecorder = { queryRequests: () => [] }, cacheAwareness = null, providers = null, capabilityPeek = peekProviderCapabilitySet, acpConfig = null, leastCost = undefined) {
|
|
24
28
|
this.sessionManager = sessionManager;
|
|
25
29
|
this.performanceMetrics = performanceMetrics;
|
|
26
30
|
this.flightRecorder = flightRecorder;
|
|
@@ -28,6 +32,10 @@ export class ResourceProvider {
|
|
|
28
32
|
this.providers = providers;
|
|
29
33
|
this.capabilityPeek = capabilityPeek;
|
|
30
34
|
this.acpConfig = acpConfig;
|
|
35
|
+
this.leastCost = leastCost;
|
|
36
|
+
}
|
|
37
|
+
routingResourcesEnabled() {
|
|
38
|
+
return this.leastCost?.enabled === true;
|
|
31
39
|
}
|
|
32
40
|
getFlightRecorderQuery() {
|
|
33
41
|
return this.flightRecorder;
|
|
@@ -172,8 +180,79 @@ export class ResourceProvider {
|
|
|
172
180
|
priority: 0.7,
|
|
173
181
|
},
|
|
174
182
|
})),
|
|
183
|
+
...(this.routingResourcesEnabled()
|
|
184
|
+
? [
|
|
185
|
+
{
|
|
186
|
+
uri: "routing://decisions",
|
|
187
|
+
name: "Routing Decisions",
|
|
188
|
+
title: "Least-Cost Routing Decisions",
|
|
189
|
+
description: "Recent redacted least-cost routing decisions (provider/model/tier/est cost/confidence)",
|
|
190
|
+
mimeType: "application/json",
|
|
191
|
+
annotations: {
|
|
192
|
+
audience: ["user", "assistant"],
|
|
193
|
+
priority: 0.7,
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
uri: "routing://priors",
|
|
198
|
+
name: "Routing Priors",
|
|
199
|
+
title: "Least-Cost Routing Priors",
|
|
200
|
+
description: "Learned output-token priors + input-token calibration (k, samples, quality) + price asOf",
|
|
201
|
+
mimeType: "application/json",
|
|
202
|
+
annotations: {
|
|
203
|
+
audience: ["user", "assistant"],
|
|
204
|
+
priority: 0.7,
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
]
|
|
208
|
+
: []),
|
|
175
209
|
];
|
|
176
210
|
}
|
|
211
|
+
readRoutingDecisions() {
|
|
212
|
+
const rows = this.flightRecorder.queryRequests(`SELECT r.cli, r.model, r.datetime_utc, r.cost_basis,
|
|
213
|
+
m.route_est_cost_usd, m.route_est_confidence, m.route_reason,
|
|
214
|
+
m.route_considered, m.route_reroutes
|
|
215
|
+
FROM requests r
|
|
216
|
+
LEFT JOIN gateway_metadata m ON m.request_id = r.id
|
|
217
|
+
WHERE m.routed = 1
|
|
218
|
+
ORDER BY r.datetime_utc DESC
|
|
219
|
+
LIMIT 50`);
|
|
220
|
+
return rows.map(row => ({
|
|
221
|
+
provider: row.cli,
|
|
222
|
+
model: row.model,
|
|
223
|
+
tier: telemetryTierFor(row.cli),
|
|
224
|
+
estCostUsd: row.route_est_cost_usd ?? null,
|
|
225
|
+
costBasis: row.cost_basis ?? null,
|
|
226
|
+
confidence: row.route_est_confidence ?? null,
|
|
227
|
+
reason: row.route_reason ?? null,
|
|
228
|
+
considered: row.route_considered ?? null,
|
|
229
|
+
reroutes: row.route_reroutes ?? null,
|
|
230
|
+
at: row.datetime_utc,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
readRoutingPriors() {
|
|
234
|
+
const scope = this.leastCost?.priorsScope ?? "off";
|
|
235
|
+
const priors = computeLcrPriorsFromDb(this.flightRecorder, {
|
|
236
|
+
priorsScope: scope,
|
|
237
|
+
ownerPrincipal: resolveOwnerPrincipal(getRequestContext()),
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
priorsScope: scope,
|
|
241
|
+
priceAsOf: { table: PRICING_AS_OF, apiCatalog: API_CATALOG_AS_OF },
|
|
242
|
+
outputPriors: Array.from(priors.outputPriors, ([key, prior]) => ({
|
|
243
|
+
candidate: key,
|
|
244
|
+
median: prior.median,
|
|
245
|
+
p90: prior.p90,
|
|
246
|
+
samples: prior.samples,
|
|
247
|
+
})),
|
|
248
|
+
calibration: Array.from(priors.calibration, ([key, bucket]) => ({
|
|
249
|
+
bucket: key,
|
|
250
|
+
k: bucket.k,
|
|
251
|
+
samples: bucket.samples,
|
|
252
|
+
confidence: bucket.confidence,
|
|
253
|
+
})),
|
|
254
|
+
};
|
|
255
|
+
}
|
|
177
256
|
ownedSessions(sessions) {
|
|
178
257
|
const caller = resolveOwnerPrincipal(getRequestContext());
|
|
179
258
|
const owned = sessions.filter(s => principalCanAccess(s.ownerPrincipal, caller));
|
|
@@ -256,6 +335,22 @@ export class ResourceProvider {
|
|
|
256
335
|
};
|
|
257
336
|
}
|
|
258
337
|
}
|
|
338
|
+
if (this.routingResourcesEnabled()) {
|
|
339
|
+
if (uri === "routing://decisions") {
|
|
340
|
+
return {
|
|
341
|
+
uri,
|
|
342
|
+
mimeType: "application/json",
|
|
343
|
+
text: JSON.stringify({ decisions: this.readRoutingDecisions() }, null, 2),
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
if (uri === "routing://priors") {
|
|
347
|
+
return {
|
|
348
|
+
uri,
|
|
349
|
+
mimeType: "application/json",
|
|
350
|
+
text: JSON.stringify(this.readRoutingPriors(), null, 2),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
}
|
|
259
354
|
if (uri === "metrics://performance") {
|
|
260
355
|
return {
|
|
261
356
|
uri,
|
package/dist/retry.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export interface RetryOptions {
|
|
|
19
19
|
isTransient: (error: any) => boolean;
|
|
20
20
|
onRetry: (error: any, attempt: number, delay: number) => void;
|
|
21
21
|
}
|
|
22
|
+
export declare const isDefaultTransient: (error: any) => boolean;
|
|
22
23
|
export declare function createCircuitBreaker(options?: {
|
|
23
24
|
resetTimeout?: number;
|
|
24
25
|
failureThreshold?: number;
|
package/dist/retry.js
CHANGED
|
@@ -4,7 +4,7 @@ export var CircuitBreakerState;
|
|
|
4
4
|
CircuitBreakerState["OPEN"] = "OPEN";
|
|
5
5
|
CircuitBreakerState["HALF_OPEN"] = "HALF_OPEN";
|
|
6
6
|
})(CircuitBreakerState || (CircuitBreakerState = {}));
|
|
7
|
-
const isDefaultTransient = (error) => {
|
|
7
|
+
export const isDefaultTransient = (error) => {
|
|
8
8
|
if (!error) {
|
|
9
9
|
return false;
|
|
10
10
|
}
|