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.
- package/.agents/skills/least-cost-routing/SKILL.md +123 -0
- package/CHANGELOG.md +78 -1
- 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/process-manager.js +2 -1
- package/dist/acp/provider-registry.js +8 -8
- 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 +20 -1
- package/dist/async-job-manager.js +85 -28
- package/dist/claude-mcp-config.js +1 -1
- package/dist/compressor/estimate.d.ts +5 -0
- package/dist/compressor/estimate.js +21 -0
- package/dist/compressor/index.d.ts +23 -0
- package/dist/compressor/index.js +77 -0
- package/dist/compressor/router.d.ts +2 -0
- package/dist/compressor/router.js +57 -0
- package/dist/compressor/transforms/ansi.d.ts +3 -0
- package/dist/compressor/transforms/ansi.js +99 -0
- package/dist/compressor/transforms/json.d.ts +1 -0
- package/dist/compressor/transforms/json.js +156 -0
- package/dist/compressor/transforms/log.d.ts +12 -0
- package/dist/compressor/transforms/log.js +55 -0
- package/dist/compressor/transforms/whitespace.d.ts +8 -0
- package/dist/compressor/transforms/whitespace.js +79 -0
- package/dist/config.d.ts +39 -0
- package/dist/config.js +166 -6
- package/dist/db.js +4 -4
- package/dist/doctor.d.ts +34 -1
- package/dist/doctor.js +85 -3
- package/dist/executor.d.ts +6 -0
- package/dist/executor.js +16 -3
- package/dist/flight-recorder.d.ts +19 -0
- package/dist/flight-recorder.js +110 -2
- package/dist/http-transport.js +19 -21
- package/dist/index.d.ts +58 -2
- package/dist/index.js +929 -94
- package/dist/job-store.d.ts +14 -2
- package/dist/job-store.js +170 -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-definitions.d.ts +5 -0
- package/dist/provider-definitions.js +56 -10
- package/dist/provider-tool-capabilities.js +3 -3
- package/dist/request-helpers.d.ts +2 -2
- package/dist/request-helpers.js +1 -1
- 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/spawn-env-isolation.d.ts +10 -0
- package/dist/spawn-env-isolation.js +55 -0
- package/dist/token-estimator.d.ts +6 -0
- package/dist/token-estimator.js +59 -0
- package/dist/upstream-contracts.js +48 -28
- 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 +15 -6
- package/setup/status.schema.json +68 -0
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@ import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileS
|
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative } from "path";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
9
|
import { z } from "zod/v3";
|
|
10
|
-
import { executeCli, killAllProcessGroups, providerCommandName } from "./executor.js";
|
|
10
|
+
import { executeCli, killAllProcessGroups, providerCommandName, cliBreakerState, } from "./executor.js";
|
|
11
|
+
import { isDefaultTransient } from "./retry.js";
|
|
11
12
|
import { parseStreamJson } from "./stream-json-parser.js";
|
|
12
13
|
import { parseCodexJsonStream, codexDisplayText, codexFrResponse } from "./codex-json-parser.js";
|
|
13
14
|
import { grokDisplayText } from "./grok-json-parser.js";
|
|
@@ -21,7 +22,12 @@ import { ResourceProvider } from "./resources.js";
|
|
|
21
22
|
import { generateResourceDescriptors, generateProviderAcpDescriptors, } from "./provider-surface-generator.js";
|
|
22
23
|
import { PerformanceMetrics } from "./metrics.js";
|
|
23
24
|
import { estimateTokens, optimizePrompt as optimizePromptText, optimizeResponse as optimizeResponseText, } from "./optimizer.js";
|
|
24
|
-
import {
|
|
25
|
+
import { compressDisplayText } from "./compressor/index.js";
|
|
26
|
+
import { loadConfig, loadPersistenceConfig, loadCacheAwarenessConfig, loadProvidersConfig, loadAcpConfig, loadAdminConfig, loadLimitsConfig, loadCompressionConfig, loadLeastCostConfig, loadSkillsConfig, defaultGatewayConfigPath, isXaiProviderEnabled, enabledApiProviders, minStableTokensForModel, } from "./config.js";
|
|
27
|
+
import { buildRouterEnv, toRouterConfig } from "./lcr-router-env.js";
|
|
28
|
+
import { getModelCost, composeCost, modelIdToFamily } from "./pricing.js";
|
|
29
|
+
import { telemetryTierFor } from "./lcr-telemetry.js";
|
|
30
|
+
import { selectCandidate, } from "./least-cost-router.js";
|
|
25
31
|
import { loadGatewaySkills } from "./skill-loader.js";
|
|
26
32
|
import { runAcpRequest } from "./acp/runtime.js";
|
|
27
33
|
import { isAcpError } from "./acp/errors.js";
|
|
@@ -228,6 +234,8 @@ let resourceProvider;
|
|
|
228
234
|
let flightRecorder = null;
|
|
229
235
|
let persistenceConfig = null;
|
|
230
236
|
let cacheAwarenessConfig = null;
|
|
237
|
+
let compressionConfig = null;
|
|
238
|
+
let leastCostConfig = null;
|
|
231
239
|
let providersConfig = null;
|
|
232
240
|
let acpConfig = null;
|
|
233
241
|
let adminConfig = null;
|
|
@@ -260,6 +268,14 @@ function getCacheAwarenessConfig(runtimeLogger = logger) {
|
|
|
260
268
|
cacheAwarenessConfig ??= loadCacheAwarenessConfig(runtimeLogger);
|
|
261
269
|
return cacheAwarenessConfig;
|
|
262
270
|
}
|
|
271
|
+
function getCompressionConfig(runtimeLogger = logger) {
|
|
272
|
+
compressionConfig ??= loadCompressionConfig(runtimeLogger);
|
|
273
|
+
return compressionConfig;
|
|
274
|
+
}
|
|
275
|
+
function getLeastCostConfig(runtimeLogger = logger) {
|
|
276
|
+
leastCostConfig ??= loadLeastCostConfig(runtimeLogger);
|
|
277
|
+
return leastCostConfig;
|
|
278
|
+
}
|
|
263
279
|
function getProvidersConfig(runtimeLogger = logger) {
|
|
264
280
|
providersConfig ??= loadProvidersConfig(runtimeLogger);
|
|
265
281
|
return providersConfig;
|
|
@@ -272,7 +288,7 @@ function getJobStore(runtimeLogger = logger) {
|
|
|
272
288
|
jobStore = createJobStore(getPersistenceConfig(runtimeLogger), runtimeLogger);
|
|
273
289
|
}
|
|
274
290
|
catch (err) {
|
|
275
|
-
runtimeLogger.error("Failed to open durable job store
|
|
291
|
+
runtimeLogger.error("Failed to open configured durable job store", err);
|
|
276
292
|
jobStore = null;
|
|
277
293
|
}
|
|
278
294
|
return jobStore;
|
|
@@ -458,7 +474,7 @@ export function resolveGatewayServerRuntime(deps = {}, options = {}) {
|
|
|
458
474
|
sessionManager: runtimeSessionManager,
|
|
459
475
|
resourceProvider: deps.resourceProvider ??
|
|
460
476
|
(options.isolateState
|
|
461
|
-
? new ResourceProvider(runtimeSessionManager, runtimePerformanceMetrics, runtimeFlightRecorder, deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger), deps.providers ?? getProvidersConfig(runtimeLogger), undefined, deps.acpConfig ?? getAcpConfig(runtimeLogger))
|
|
477
|
+
? new ResourceProvider(runtimeSessionManager, runtimePerformanceMetrics, runtimeFlightRecorder, deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger), deps.providers ?? getProvidersConfig(runtimeLogger), undefined, deps.acpConfig ?? getAcpConfig(runtimeLogger), deps.leastCost ?? getLeastCostConfig(runtimeLogger))
|
|
462
478
|
: resourceProvider),
|
|
463
479
|
db: "db" in deps ? (deps.db ?? null) : db,
|
|
464
480
|
performanceMetrics: runtimePerformanceMetrics,
|
|
@@ -468,10 +484,12 @@ export function resolveGatewayServerRuntime(deps = {}, options = {}) {
|
|
|
468
484
|
logger: runtimeLogger,
|
|
469
485
|
persistence: deps.persistence ?? getPersistenceConfig(runtimeLogger),
|
|
470
486
|
cacheAwareness: deps.cacheAwareness ?? getCacheAwarenessConfig(runtimeLogger),
|
|
487
|
+
compression: deps.compression ?? getCompressionConfig(runtimeLogger),
|
|
471
488
|
providers: deps.providers ?? getProvidersConfig(runtimeLogger),
|
|
472
489
|
acpConfig: deps.acpConfig ?? getAcpConfig(runtimeLogger),
|
|
473
490
|
adminConfig: deps.adminConfig ?? getAdminConfig(runtimeLogger),
|
|
474
491
|
workspaces: deps.workspaces ?? loadWorkspaceRegistry(runtimeLogger),
|
|
492
|
+
leastCost: deps.leastCost ?? getLeastCostConfig(runtimeLogger),
|
|
475
493
|
};
|
|
476
494
|
}
|
|
477
495
|
export function shouldRegisterGrokApiTools(providers) {
|
|
@@ -545,7 +563,7 @@ export async function runAcpTransport(deps, params) {
|
|
|
545
563
|
}
|
|
546
564
|
}
|
|
547
565
|
const SYNC_POLL_INTERVAL_MS = 1_000;
|
|
548
|
-
async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, forceRefresh, runtime = resolveGatewayServerRuntime(), env, onComplete, flightRecorderEntry, extractUsage, stdin, cwd) {
|
|
566
|
+
async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, forceRefresh, runtime = resolveGatewayServerRuntime(), env, onComplete, flightRecorderEntry, extractUsage, stdin, cwd, compressResponse) {
|
|
549
567
|
let onCompleteOwnedByCaller = onComplete !== undefined;
|
|
550
568
|
const consumeOnComplete = () => {
|
|
551
569
|
if (!onCompleteOwnedByCaller || !onComplete)
|
|
@@ -605,6 +623,7 @@ async function awaitJobOrDefer(cli, args, corrId, idleTimeoutMs, outputFormat, f
|
|
|
605
623
|
onComplete,
|
|
606
624
|
flightRecorderEntry,
|
|
607
625
|
extractUsage,
|
|
626
|
+
compressResponse,
|
|
608
627
|
});
|
|
609
628
|
onCompleteOwnedByCaller = false;
|
|
610
629
|
}
|
|
@@ -1166,22 +1185,28 @@ export function extractUsageAndCost(cli, output, outputFormat, ctx) {
|
|
|
1166
1185
|
}
|
|
1167
1186
|
return {};
|
|
1168
1187
|
}
|
|
1169
|
-
function buildAsyncFlightRecorderHandoff(cliName, prep, sessionId, outputFormat) {
|
|
1188
|
+
function buildAsyncFlightRecorderHandoff(cliName, prep, sessionId, outputFormat, optimizationApplied = false) {
|
|
1170
1189
|
const cli = cliName;
|
|
1171
1190
|
const fmt = outputFormat;
|
|
1172
1191
|
const sid = sessionId;
|
|
1173
1192
|
const home = homedir();
|
|
1193
|
+
const model = prep.resolvedModel || "default";
|
|
1174
1194
|
return {
|
|
1175
1195
|
flightRecorderEntry: {
|
|
1176
|
-
model
|
|
1196
|
+
model,
|
|
1177
1197
|
prompt: prep.effectivePrompt,
|
|
1178
1198
|
sessionId,
|
|
1199
|
+
optimizationApplied,
|
|
1179
1200
|
stablePrefixHash: prep.stablePrefixHash ?? undefined,
|
|
1180
1201
|
stablePrefixTokens: prep.stablePrefixTokens ?? undefined,
|
|
1181
1202
|
cacheControlBlocks: prep.cacheControlBlocks,
|
|
1182
1203
|
cacheControlTtlSeconds: prep.cacheControlTtlSeconds,
|
|
1183
1204
|
},
|
|
1184
|
-
extractUsage: (stdout) =>
|
|
1205
|
+
extractUsage: (stdout) => {
|
|
1206
|
+
const usage = extractUsageAndCost(cli, stdout, fmt, { sessionId: sid, home });
|
|
1207
|
+
const { costUsd, costBasis } = deriveCostBasis(cli, model, usage);
|
|
1208
|
+
return { ...usage, costUsd, costBasis };
|
|
1209
|
+
},
|
|
1185
1210
|
};
|
|
1186
1211
|
}
|
|
1187
1212
|
function safeFlightStart(entry, runtime = resolveGatewayServerRuntime()) {
|
|
@@ -1200,6 +1225,40 @@ function safeFlightComplete(correlationId, result, runtime = resolveGatewayServe
|
|
|
1200
1225
|
runtime.logger.error("Flight recorder logComplete failed", error);
|
|
1201
1226
|
}
|
|
1202
1227
|
}
|
|
1228
|
+
export function resolveEffectiveCompression(compression, input) {
|
|
1229
|
+
const requested = input.compressResponse ?? compression.enabled;
|
|
1230
|
+
if (!requested)
|
|
1231
|
+
return false;
|
|
1232
|
+
if (input.outputFormat === "json")
|
|
1233
|
+
return false;
|
|
1234
|
+
if (input.outputSchemaDeclared)
|
|
1235
|
+
return false;
|
|
1236
|
+
return true;
|
|
1237
|
+
}
|
|
1238
|
+
function safeRecordCompression(correlationId, compression, runtime) {
|
|
1239
|
+
if (!compression)
|
|
1240
|
+
return;
|
|
1241
|
+
try {
|
|
1242
|
+
runtime.flightRecorder.recordCompressionTelemetry(correlationId, {
|
|
1243
|
+
route: compression.route,
|
|
1244
|
+
transforms: compression.transforms,
|
|
1245
|
+
originalChars: compression.originalChars,
|
|
1246
|
+
compressedChars: compression.compressedChars,
|
|
1247
|
+
estimatedTokensSaved: compression.estimatedTokensSaved,
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
runtime.logger.error("Flight recorder recordCompressionTelemetry failed", error);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
function safeRecordRouting(correlationId, routing, runtime) {
|
|
1255
|
+
try {
|
|
1256
|
+
runtime.flightRecorder.recordRouting(correlationId, routing);
|
|
1257
|
+
}
|
|
1258
|
+
catch (error) {
|
|
1259
|
+
runtime.logger.error("Flight recorder recordRouting failed", error);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1203
1262
|
function createApprovalDeniedResponse(operation, decision) {
|
|
1204
1263
|
return {
|
|
1205
1264
|
content: [
|
|
@@ -2142,7 +2201,7 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
|
|
|
2142
2201
|
args.push("--permission-mode", "acceptEdits");
|
|
2143
2202
|
}
|
|
2144
2203
|
}
|
|
2145
|
-
else if (
|
|
2204
|
+
else if (params.alwaysApprove) {
|
|
2146
2205
|
args.push("--always-approve");
|
|
2147
2206
|
}
|
|
2148
2207
|
else if (params.permissionMode) {
|
|
@@ -2210,6 +2269,14 @@ export function prepareGrokRequest(params, runtime = resolveGatewayServerRuntime
|
|
|
2210
2269
|
stablePrefixTokens,
|
|
2211
2270
|
};
|
|
2212
2271
|
}
|
|
2272
|
+
export function resolveMistralAgentMode(approvalStrategy, callerMode) {
|
|
2273
|
+
if (approvalStrategy === "mcp_managed") {
|
|
2274
|
+
return bypassAllowedByOperator() ? "auto-approve" : "accept-edits";
|
|
2275
|
+
}
|
|
2276
|
+
if (callerMode !== undefined)
|
|
2277
|
+
return callerMode;
|
|
2278
|
+
return bypassAllowedByOperator() ? "auto-approve" : "accept-edits";
|
|
2279
|
+
}
|
|
2213
2280
|
export function prepareMistralRequest(params, runtime = resolveGatewayServerRuntime()) {
|
|
2214
2281
|
const corrId = params.correlationId || randomUUID();
|
|
2215
2282
|
const cliInfo = getCliInfo();
|
|
@@ -2266,11 +2333,7 @@ export function prepareMistralRequest(params, runtime = resolveGatewayServerRunt
|
|
|
2266
2333
|
return createApprovalDeniedResponse(params.operation, approvalDecision);
|
|
2267
2334
|
}
|
|
2268
2335
|
}
|
|
2269
|
-
const effectivePermissionMode = params.approvalStrategy
|
|
2270
|
-
? bypassAllowedByOperator()
|
|
2271
|
-
? "auto-approve"
|
|
2272
|
-
: "accept-edits"
|
|
2273
|
-
: (params.permissionMode ?? "auto-approve");
|
|
2336
|
+
const effectivePermissionMode = resolveMistralAgentMode(params.approvalStrategy, params.permissionMode);
|
|
2274
2337
|
const prep = buildMistralCliInvocation({
|
|
2275
2338
|
prompt: effectivePrompt,
|
|
2276
2339
|
resolvedModel,
|
|
@@ -2319,11 +2382,7 @@ export function buildMistralRetryPrep(params, recoveryModel) {
|
|
|
2319
2382
|
prompt: params.effectivePrompt,
|
|
2320
2383
|
resolvedModel: recoveryModel,
|
|
2321
2384
|
outputFormat: params.outputFormat,
|
|
2322
|
-
permissionMode: params.approvalStrategy
|
|
2323
|
-
? bypassAllowedByOperator()
|
|
2324
|
-
? "auto-approve"
|
|
2325
|
-
: "accept-edits"
|
|
2326
|
-
: (params.permissionMode ?? "auto-approve"),
|
|
2385
|
+
permissionMode: resolveMistralAgentMode(params.approvalStrategy, params.permissionMode),
|
|
2327
2386
|
allowedTools: params.allowedTools,
|
|
2328
2387
|
disallowedTools: params.disallowedTools,
|
|
2329
2388
|
trust: params.trust,
|
|
@@ -2334,7 +2393,7 @@ export function buildMistralRetryPrep(params, recoveryModel) {
|
|
|
2334
2393
|
addDir: params.addDir,
|
|
2335
2394
|
});
|
|
2336
2395
|
}
|
|
2337
|
-
export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings) {
|
|
2396
|
+
export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionId, prep, durationMs, resumable, outputFormat, warnings, compressResponse = false) {
|
|
2338
2397
|
let finalStdout = stdout;
|
|
2339
2398
|
if (cli === "codex" && outputFormat !== "json") {
|
|
2340
2399
|
finalStdout = codexDisplayText(stdout);
|
|
@@ -2347,6 +2406,20 @@ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionI
|
|
|
2347
2406
|
logOptimizationTokens("response", corrId, finalStdout, optimized);
|
|
2348
2407
|
finalStdout = optimized;
|
|
2349
2408
|
}
|
|
2409
|
+
let compression;
|
|
2410
|
+
if (compressResponse && outputFormat !== "json") {
|
|
2411
|
+
const compressed = compressDisplayText(finalStdout, {
|
|
2412
|
+
provider: cli,
|
|
2413
|
+
direction: "outbound",
|
|
2414
|
+
outputFormat,
|
|
2415
|
+
outputSchemaDeclared: false,
|
|
2416
|
+
lossless: true,
|
|
2417
|
+
});
|
|
2418
|
+
if (compressed.text !== finalStdout) {
|
|
2419
|
+
compression = compressed;
|
|
2420
|
+
finalStdout = compressed.text;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2350
2423
|
if (prep.reviewIntegrity &&
|
|
2351
2424
|
prep.reviewIntegrity.violations.length > 0 &&
|
|
2352
2425
|
outputFormat !== "json") {
|
|
@@ -2422,6 +2495,7 @@ export function buildCliResponse(cli, stdout, optimizeResponse, corrId, sessionI
|
|
|
2422
2495
|
missing: prep.mcpConfig.missing,
|
|
2423
2496
|
}
|
|
2424
2497
|
: { requested: prep.requestedMcpServers },
|
|
2498
|
+
...(compression ? { compression } : {}),
|
|
2425
2499
|
};
|
|
2426
2500
|
if (sessionId) {
|
|
2427
2501
|
response.sessionId = sessionId;
|
|
@@ -2931,6 +3005,7 @@ export async function handleApiProviderRequest(runtimeArg, providerRuntime, para
|
|
|
2931
3005
|
inputTokens: result.usage?.inputTokens,
|
|
2932
3006
|
outputTokens: result.usage?.outputTokens,
|
|
2933
3007
|
cacheReadTokens: result.usage?.cacheReadTokens,
|
|
3008
|
+
cacheCreationTokens: result.usage?.cacheCreationTokens,
|
|
2934
3009
|
costUsd: result.usage?.costUsd,
|
|
2935
3010
|
}, runtimeArg);
|
|
2936
3011
|
return buildApiSuccessResponse(text, corrId, providerRuntime.name, {
|
|
@@ -3175,8 +3250,13 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
3175
3250
|
catch (err) {
|
|
3176
3251
|
return createErrorResponse("gemini_request", 1, "", corrId, err);
|
|
3177
3252
|
}
|
|
3178
|
-
const
|
|
3179
|
-
|
|
3253
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3254
|
+
compressResponse: params.compressResponse,
|
|
3255
|
+
outputFormat: params.outputFormat,
|
|
3256
|
+
outputSchemaDeclared: false,
|
|
3257
|
+
});
|
|
3258
|
+
const geminiFrHandoff = buildAsyncFlightRecorderHandoff("gemini", prep, params.sessionId, params.outputFormat, params.optimizePrompt);
|
|
3259
|
+
const result = await awaitJobOrDefer("gemini", args, corrId, resolveIdleTimeout("gemini", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, geminiFrHandoff.flightRecorderEntry, geminiFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
3180
3260
|
if (isDeferredResponse(result)) {
|
|
3181
3261
|
return buildDeferredToolResponse(result, effectiveSessionIdHint);
|
|
3182
3262
|
}
|
|
@@ -3197,7 +3277,7 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
3197
3277
|
return createErrorResponse("gemini", code, stderr, corrId);
|
|
3198
3278
|
}
|
|
3199
3279
|
wasSuccessful = true;
|
|
3200
|
-
|
|
3280
|
+
const effectiveSessionId = effectiveSessionIdHint;
|
|
3201
3281
|
if (effectiveSessionId) {
|
|
3202
3282
|
const existing = await deps.sessionManager.getSession(effectiveSessionId);
|
|
3203
3283
|
if (!existing) {
|
|
@@ -3213,7 +3293,8 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
3213
3293
|
await deps.sessionManager.updateSessionUsage(effectiveSessionId);
|
|
3214
3294
|
}
|
|
3215
3295
|
deps.logger.info(`[${corrId}] gemini_request completed successfully in ${durationMs}ms`);
|
|
3216
|
-
const response = buildCliResponse("gemini", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, userProvidedSession, params.outputFormat);
|
|
3296
|
+
const response = buildCliResponse("gemini", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, userProvidedSession, params.outputFormat, undefined, effectiveCompress);
|
|
3297
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
3217
3298
|
if (worktreeResolution.worktreePath) {
|
|
3218
3299
|
const first = response.content[0];
|
|
3219
3300
|
if (first && first.type === "text") {
|
|
@@ -3221,6 +3302,7 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
3221
3302
|
}
|
|
3222
3303
|
}
|
|
3223
3304
|
const geminiUsage = extractUsageAndCost("gemini", stdout, params.outputFormat);
|
|
3305
|
+
const { costUsd: geminiCostUsd, costBasis: geminiCostBasis } = deriveCostBasis("gemini", prep.resolvedModel || "default", geminiUsage);
|
|
3224
3306
|
const geminiMeta = extractProviderOutputMetadata("gemini", stdout, params.outputFormat);
|
|
3225
3307
|
safeFlightComplete(corrId, {
|
|
3226
3308
|
response: stdout,
|
|
@@ -3235,7 +3317,8 @@ export async function handleGeminiRequest(deps, params) {
|
|
|
3235
3317
|
outputTokens: geminiUsage.outputTokens,
|
|
3236
3318
|
cacheReadTokens: geminiUsage.cacheReadTokens,
|
|
3237
3319
|
cacheCreationTokens: geminiUsage.cacheCreationTokens,
|
|
3238
|
-
costUsd:
|
|
3320
|
+
costUsd: geminiCostUsd,
|
|
3321
|
+
costBasis: geminiCostBasis,
|
|
3239
3322
|
providerSessionId: geminiMeta.sessionId,
|
|
3240
3323
|
stopReason: geminiMeta.stopReason,
|
|
3241
3324
|
}, runtime);
|
|
@@ -3296,7 +3379,7 @@ export async function handleGeminiRequestAsync(deps, params) {
|
|
|
3296
3379
|
resumeLatest: params.resumeLatest,
|
|
3297
3380
|
createNewSession: params.createNewSession,
|
|
3298
3381
|
});
|
|
3299
|
-
|
|
3382
|
+
const effectiveSessionId = sessionPlan.resumed ? params.sessionId : undefined;
|
|
3300
3383
|
const existingSession = await getExistingSessionForProvider(deps.sessionManager, effectiveSessionId, "gemini");
|
|
3301
3384
|
args.push(...sessionPlan.args);
|
|
3302
3385
|
if (effectiveSessionId) {
|
|
@@ -3328,8 +3411,13 @@ export async function handleGeminiRequestAsync(deps, params) {
|
|
|
3328
3411
|
}
|
|
3329
3412
|
assertUpstreamCliArgs("gemini", args);
|
|
3330
3413
|
assertUpstreamCliEnv("gemini", undefined);
|
|
3331
|
-
const
|
|
3332
|
-
|
|
3414
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3415
|
+
compressResponse: params.compressResponse,
|
|
3416
|
+
outputFormat: params.outputFormat,
|
|
3417
|
+
outputSchemaDeclared: false,
|
|
3418
|
+
});
|
|
3419
|
+
const geminiAsyncFrHandoff = buildAsyncFlightRecorderHandoff("gemini", prep, effectiveSessionId, params.outputFormat, params.optimizePrompt);
|
|
3420
|
+
const job = deps.asyncJobManager.startJob("gemini", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("gemini", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, undefined, undefined, geminiAsyncFrHandoff.flightRecorderEntry, geminiAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
3333
3421
|
deps.logger.info(`[${corrId}] gemini_request_async started job ${job.id}`);
|
|
3334
3422
|
const asyncResponse = {
|
|
3335
3423
|
success: true,
|
|
@@ -3458,8 +3546,13 @@ export async function handleGrokRequest(deps, params) {
|
|
|
3458
3546
|
catch (err) {
|
|
3459
3547
|
return createErrorResponse("grok_request", 1, "", corrId, err);
|
|
3460
3548
|
}
|
|
3461
|
-
const
|
|
3462
|
-
|
|
3549
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3550
|
+
compressResponse: params.compressResponse,
|
|
3551
|
+
outputFormat: params.outputFormat,
|
|
3552
|
+
outputSchemaDeclared: false,
|
|
3553
|
+
});
|
|
3554
|
+
const grokFrHandoff = buildAsyncFlightRecorderHandoff("grok", prep, params.sessionId, params.outputFormat, params.optimizePrompt);
|
|
3555
|
+
const result = await awaitJobOrDefer("grok", args, corrId, resolveIdleTimeout("grok", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, grokFrHandoff.flightRecorderEntry, grokFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
3463
3556
|
if (isDeferredResponse(result)) {
|
|
3464
3557
|
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
3465
3558
|
}
|
|
@@ -3500,7 +3593,8 @@ export async function handleGrokRequest(deps, params) {
|
|
|
3500
3593
|
effectiveSessionId = newSession.id;
|
|
3501
3594
|
}
|
|
3502
3595
|
deps.logger.info(`[${corrId}] grok_request completed successfully in ${durationMs}ms`);
|
|
3503
|
-
const response = buildCliResponse("grok", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat);
|
|
3596
|
+
const response = buildCliResponse("grok", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat, undefined, effectiveCompress);
|
|
3597
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
3504
3598
|
if (worktreeResolution.worktreePath) {
|
|
3505
3599
|
const first = response.content[0];
|
|
3506
3600
|
if (first && first.type === "text") {
|
|
@@ -3641,8 +3735,13 @@ export async function handleGrokRequestAsync(deps, params) {
|
|
|
3641
3735
|
}
|
|
3642
3736
|
assertUpstreamCliArgs("grok", args);
|
|
3643
3737
|
assertUpstreamCliEnv("grok", undefined);
|
|
3644
|
-
const
|
|
3645
|
-
|
|
3738
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3739
|
+
compressResponse: params.compressResponse,
|
|
3740
|
+
outputFormat: params.outputFormat,
|
|
3741
|
+
outputSchemaDeclared: false,
|
|
3742
|
+
});
|
|
3743
|
+
const grokAsyncFrHandoff = buildAsyncFlightRecorderHandoff("grok", prep, effectiveSessionId, params.outputFormat, params.optimizePrompt);
|
|
3744
|
+
const job = deps.asyncJobManager.startJob("grok", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("grok", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, undefined, undefined, grokAsyncFrHandoff.flightRecorderEntry, grokAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
3646
3745
|
deps.logger.info(`[${corrId}] grok_request_async started job ${job.id}`);
|
|
3647
3746
|
const asyncResponse = {
|
|
3648
3747
|
success: true,
|
|
@@ -3770,8 +3869,13 @@ export async function handleDevinRequest(deps, params) {
|
|
|
3770
3869
|
await getExistingSessionForProvider(deps.sessionManager, sessionResult.effectiveSessionId, "devin");
|
|
3771
3870
|
}
|
|
3772
3871
|
args.push(...sessionResult.resumeArgs);
|
|
3773
|
-
const
|
|
3774
|
-
|
|
3872
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3873
|
+
compressResponse: params.compressResponse,
|
|
3874
|
+
outputFormat: undefined,
|
|
3875
|
+
outputSchemaDeclared: false,
|
|
3876
|
+
});
|
|
3877
|
+
const devinFrHandoff = buildAsyncFlightRecorderHandoff("devin", prep, params.sessionId, undefined, params.optimizePrompt);
|
|
3878
|
+
const result = await awaitJobOrDefer("devin", args, corrId, resolveIdleTimeout("devin", params.idleTimeoutMs), undefined, params.forceRefresh, runtime, undefined, undefined, devinFrHandoff.flightRecorderEntry, devinFrHandoff.extractUsage, undefined, undefined, effectiveCompress);
|
|
3775
3879
|
if (isDeferredResponse(result)) {
|
|
3776
3880
|
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
3777
3881
|
}
|
|
@@ -3810,7 +3914,8 @@ export async function handleDevinRequest(deps, params) {
|
|
|
3810
3914
|
const newSession = await deps.sessionManager.createSession("devin", "Devin Session", `${GATEWAY_SESSION_PREFIX}${randomUUID()}`);
|
|
3811
3915
|
effectiveSessionId = newSession.id;
|
|
3812
3916
|
}
|
|
3813
|
-
const response = buildCliResponse("devin", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession);
|
|
3917
|
+
const response = buildCliResponse("devin", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, undefined, undefined, effectiveCompress);
|
|
3918
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
3814
3919
|
safeFlightComplete(corrId, {
|
|
3815
3920
|
response: stdout,
|
|
3816
3921
|
durationMs,
|
|
@@ -3890,8 +3995,13 @@ export async function handleDevinRequestAsync(deps, params) {
|
|
|
3890
3995
|
}
|
|
3891
3996
|
assertUpstreamCliArgs("devin", args);
|
|
3892
3997
|
assertUpstreamCliEnv("devin", undefined);
|
|
3893
|
-
const
|
|
3894
|
-
|
|
3998
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
3999
|
+
compressResponse: params.compressResponse,
|
|
4000
|
+
outputFormat: undefined,
|
|
4001
|
+
outputSchemaDeclared: false,
|
|
4002
|
+
});
|
|
4003
|
+
const devinAsyncFrHandoff = buildAsyncFlightRecorderHandoff("devin", prep, effectiveSessionId, undefined, params.optimizePrompt);
|
|
4004
|
+
const job = deps.asyncJobManager.startJob("devin", args, corrId, undefined, resolveIdleTimeout("devin", params.idleTimeoutMs), undefined, params.forceRefresh, undefined, undefined, devinAsyncFrHandoff.flightRecorderEntry, devinAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
3895
4005
|
deps.logger.info(`[${corrId}] devin_request_async started job ${job.id}`);
|
|
3896
4006
|
return {
|
|
3897
4007
|
content: [
|
|
@@ -4089,8 +4199,13 @@ export async function handleCursorRequest(deps, params) {
|
|
|
4089
4199
|
args[workspaceFlagIndex + 1] = worktreeResolution.workspace.cwd;
|
|
4090
4200
|
}
|
|
4091
4201
|
}
|
|
4092
|
-
const
|
|
4093
|
-
|
|
4202
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4203
|
+
compressResponse: params.compressResponse,
|
|
4204
|
+
outputFormat: params.outputFormat,
|
|
4205
|
+
outputSchemaDeclared: false,
|
|
4206
|
+
});
|
|
4207
|
+
const cursorFrHandoff = buildAsyncFlightRecorderHandoff("cursor", prep, params.sessionId, params.outputFormat, params.optimizePrompt);
|
|
4208
|
+
const result = await awaitJobOrDefer("cursor", args, corrId, resolveIdleTimeout("cursor", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, undefined, undefined, cursorFrHandoff.flightRecorderEntry, cursorFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
4094
4209
|
if (isDeferredResponse(result)) {
|
|
4095
4210
|
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
4096
4211
|
}
|
|
@@ -4129,7 +4244,8 @@ export async function handleCursorRequest(deps, params) {
|
|
|
4129
4244
|
const newSession = await deps.sessionManager.createSession("cursor", "Cursor Session", `${GATEWAY_SESSION_PREFIX}${randomUUID()}`);
|
|
4130
4245
|
effectiveSessionId = newSession.id;
|
|
4131
4246
|
}
|
|
4132
|
-
const response = buildCliResponse("cursor", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat);
|
|
4247
|
+
const response = buildCliResponse("cursor", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat, undefined, effectiveCompress);
|
|
4248
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
4133
4249
|
safeFlightComplete(corrId, {
|
|
4134
4250
|
response: stdout,
|
|
4135
4251
|
durationMs,
|
|
@@ -4231,8 +4347,13 @@ export async function handleCursorRequestAsync(deps, params) {
|
|
|
4231
4347
|
}
|
|
4232
4348
|
assertUpstreamCliArgs("cursor", args);
|
|
4233
4349
|
assertUpstreamCliEnv("cursor", undefined);
|
|
4234
|
-
const
|
|
4235
|
-
|
|
4350
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4351
|
+
compressResponse: params.compressResponse,
|
|
4352
|
+
outputFormat: params.outputFormat,
|
|
4353
|
+
outputSchemaDeclared: false,
|
|
4354
|
+
});
|
|
4355
|
+
const cursorAsyncFrHandoff = buildAsyncFlightRecorderHandoff("cursor", prep, effectiveSessionId, params.outputFormat, params.optimizePrompt);
|
|
4356
|
+
const job = deps.asyncJobManager.startJob("cursor", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("cursor", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, undefined, undefined, cursorAsyncFrHandoff.flightRecorderEntry, cursorAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
4236
4357
|
deps.logger.info(`[${corrId}] cursor_request_async started job ${job.id}`);
|
|
4237
4358
|
return {
|
|
4238
4359
|
content: [
|
|
@@ -4299,7 +4420,7 @@ export async function handleMistralRequest(deps, params) {
|
|
|
4299
4420
|
stablePrefixHash: prep.stablePrefixHash ?? undefined,
|
|
4300
4421
|
stablePrefixTokens: prep.stablePrefixTokens ?? undefined,
|
|
4301
4422
|
}, runtime);
|
|
4302
|
-
deps.logger.info(`[${corrId}] mistral_request invoked with model=${prep.resolvedModel || "default"}, permissionMode=${params.permissionMode
|
|
4423
|
+
deps.logger.info(`[${corrId}] mistral_request invoked with model=${prep.resolvedModel || "default"}, permissionMode=${resolveMistralAgentMode(params.approvalStrategy, params.permissionMode)}, prompt length=${prep.effectivePrompt.length}`);
|
|
4303
4424
|
try {
|
|
4304
4425
|
const sessionResult = resolveMistralSessionArgs({
|
|
4305
4426
|
sessionId: params.sessionId,
|
|
@@ -4325,8 +4446,13 @@ export async function handleMistralRequest(deps, params) {
|
|
|
4325
4446
|
catch (err) {
|
|
4326
4447
|
return createErrorResponse("mistral_request", 1, "", corrId, err);
|
|
4327
4448
|
}
|
|
4328
|
-
const
|
|
4329
|
-
|
|
4449
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4450
|
+
compressResponse: params.compressResponse,
|
|
4451
|
+
outputFormat: params.outputFormat,
|
|
4452
|
+
outputSchemaDeclared: false,
|
|
4453
|
+
});
|
|
4454
|
+
const mistralFrHandoff = buildAsyncFlightRecorderHandoff("mistral", prep, params.sessionId, params.outputFormat, params.optimizePrompt);
|
|
4455
|
+
let result = await awaitJobOrDefer("mistral", args, corrId, resolveIdleTimeout("mistral", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, runtime, mistralEnv, undefined, mistralFrHandoff.flightRecorderEntry, mistralFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
4330
4456
|
if (isDeferredResponse(result)) {
|
|
4331
4457
|
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
4332
4458
|
}
|
|
@@ -4336,7 +4462,7 @@ export async function handleMistralRequest(deps, params) {
|
|
|
4336
4462
|
deps.logger.info(`[${corrId}] mistral_request detected stale Vibe model selection; retrying once with ${recoveryModel}`);
|
|
4337
4463
|
const retryPrep = buildMistralRetryPrep({ ...params, effectivePrompt: prep.effectivePrompt }, recoveryModel);
|
|
4338
4464
|
const retryArgs = [...retryPrep.args, ...sessionResult.resumeArgs];
|
|
4339
|
-
result = await awaitJobOrDefer("mistral", retryArgs, corrId, resolveIdleTimeout("mistral", params.idleTimeoutMs), params.outputFormat, true, runtime, retryPrep.env, undefined, mistralFrHandoff.flightRecorderEntry, mistralFrHandoff.extractUsage, undefined, worktreeResolution.cwd);
|
|
4465
|
+
result = await awaitJobOrDefer("mistral", retryArgs, corrId, resolveIdleTimeout("mistral", params.idleTimeoutMs), params.outputFormat, true, runtime, retryPrep.env, undefined, mistralFrHandoff.flightRecorderEntry, mistralFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
4340
4466
|
if (isDeferredResponse(result)) {
|
|
4341
4467
|
return buildDeferredToolResponse(result, sessionResult.effectiveSessionId);
|
|
4342
4468
|
}
|
|
@@ -4381,7 +4507,8 @@ export async function handleMistralRequest(deps, params) {
|
|
|
4381
4507
|
effectiveSessionId = newSession.id;
|
|
4382
4508
|
}
|
|
4383
4509
|
deps.logger.info(`[${corrId}] mistral_request completed successfully in ${durationMs}ms`);
|
|
4384
|
-
const response = buildCliResponse("mistral", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat);
|
|
4510
|
+
const response = buildCliResponse("mistral", stdout, params.optimizeResponse ?? false, corrId, effectiveSessionId, prep, durationMs, sessionResult.userProvidedSession, params.outputFormat, undefined, effectiveCompress);
|
|
4511
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
4385
4512
|
if (worktreeResolution.worktreePath) {
|
|
4386
4513
|
const first = response.content[0];
|
|
4387
4514
|
if (first && first.type === "text") {
|
|
@@ -4489,8 +4616,13 @@ export async function handleMistralRequestAsync(deps, params) {
|
|
|
4489
4616
|
}
|
|
4490
4617
|
assertUpstreamCliArgs("mistral", args);
|
|
4491
4618
|
assertUpstreamCliEnv("mistral", mistralEnv);
|
|
4492
|
-
const
|
|
4493
|
-
|
|
4619
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4620
|
+
compressResponse: params.compressResponse,
|
|
4621
|
+
outputFormat: params.outputFormat,
|
|
4622
|
+
outputSchemaDeclared: false,
|
|
4623
|
+
});
|
|
4624
|
+
const mistralAsyncFrHandoff = buildAsyncFlightRecorderHandoff("mistral", prep, effectiveSessionId, params.outputFormat, params.optimizePrompt);
|
|
4625
|
+
const job = deps.asyncJobManager.startJob("mistral", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("mistral", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, mistralEnv, undefined, mistralAsyncFrHandoff.flightRecorderEntry, mistralAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
4494
4626
|
deps.logger.info(`[${corrId}] mistral_request_async started job ${job.id}`);
|
|
4495
4627
|
const asyncResponse = {
|
|
4496
4628
|
success: true,
|
|
@@ -4619,15 +4751,14 @@ export async function handleCodexRequestAsync(deps, params) {
|
|
|
4619
4751
|
}
|
|
4620
4752
|
assertUpstreamCliArgs("codex", args);
|
|
4621
4753
|
assertUpstreamCliEnv("codex", undefined);
|
|
4622
|
-
const
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
}
|
|
4754
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4755
|
+
compressResponse: params.compressResponse,
|
|
4756
|
+
outputFormat: params.outputFormat,
|
|
4757
|
+
outputSchemaDeclared: params.outputSchema !== undefined,
|
|
4758
|
+
});
|
|
4759
|
+
const codexAsyncFrHandoff = buildAsyncFlightRecorderHandoff("codex", prep, effectiveSessionId, params.outputFormat, params.optimizePrompt);
|
|
4760
|
+
const job = deps.asyncJobManager.startJob("codex", args, corrId, worktreeResolution.cwd, resolveIdleTimeout("codex", params.idleTimeoutMs), params.outputFormat, params.forceRefresh, undefined, prepCleanup, codexAsyncFrHandoff.flightRecorderEntry, codexAsyncFrHandoff.extractUsage, true, undefined, effectiveCompress);
|
|
4761
|
+
prepCleanupOwnedHere = false;
|
|
4631
4762
|
deps.logger.info(`[${corrId}] codex_request_async started job ${job.id}`);
|
|
4632
4763
|
const asyncResponse = {
|
|
4633
4764
|
success: true,
|
|
@@ -4656,6 +4787,439 @@ export async function handleCodexRequestAsync(deps, params) {
|
|
|
4656
4787
|
return createErrorResponse("codex_request_async", 1, "", corrId, error);
|
|
4657
4788
|
}
|
|
4658
4789
|
}
|
|
4790
|
+
function routedOutputFormat(provider) {
|
|
4791
|
+
return provider === "claude" || provider === "gemini" ? "stream-json" : "text";
|
|
4792
|
+
}
|
|
4793
|
+
function prepareRoutedCli(runtime, cli, params, outputFormat) {
|
|
4794
|
+
const base = {
|
|
4795
|
+
prompt: params.prompt,
|
|
4796
|
+
promptParts: params.promptParts,
|
|
4797
|
+
model: params.model,
|
|
4798
|
+
correlationId: params.correlationId,
|
|
4799
|
+
optimizePrompt: params.optimizePrompt,
|
|
4800
|
+
operation: "route_request",
|
|
4801
|
+
outputFormat,
|
|
4802
|
+
};
|
|
4803
|
+
switch (cli) {
|
|
4804
|
+
case "claude": {
|
|
4805
|
+
const p = {
|
|
4806
|
+
...base,
|
|
4807
|
+
dangerouslySkipPermissions: false,
|
|
4808
|
+
approvalStrategy: "legacy",
|
|
4809
|
+
strictMcpConfig: false,
|
|
4810
|
+
};
|
|
4811
|
+
return prepareClaudeRequest(p, runtime);
|
|
4812
|
+
}
|
|
4813
|
+
case "codex": {
|
|
4814
|
+
const p = {
|
|
4815
|
+
...base,
|
|
4816
|
+
outputFormat: outputFormat,
|
|
4817
|
+
fullAuto: false,
|
|
4818
|
+
dangerouslyBypassApprovalsAndSandbox: false,
|
|
4819
|
+
approvalStrategy: "legacy",
|
|
4820
|
+
};
|
|
4821
|
+
return prepareCodexRequest(p, runtime);
|
|
4822
|
+
}
|
|
4823
|
+
case "gemini": {
|
|
4824
|
+
const p = { ...base, approvalStrategy: "legacy" };
|
|
4825
|
+
return prepareGeminiRequest(p, runtime);
|
|
4826
|
+
}
|
|
4827
|
+
case "grok": {
|
|
4828
|
+
const p = { ...base, approvalStrategy: "legacy" };
|
|
4829
|
+
return prepareGrokRequest(p, runtime);
|
|
4830
|
+
}
|
|
4831
|
+
case "mistral": {
|
|
4832
|
+
const p = { ...base, approvalStrategy: "legacy" };
|
|
4833
|
+
return prepareMistralRequest(p, runtime);
|
|
4834
|
+
}
|
|
4835
|
+
case "devin": {
|
|
4836
|
+
const p = { ...base };
|
|
4837
|
+
return prepareDevinRequest(p, runtime);
|
|
4838
|
+
}
|
|
4839
|
+
case "cursor": {
|
|
4840
|
+
const p = { ...base };
|
|
4841
|
+
return prepareCursorRequest(p, runtime);
|
|
4842
|
+
}
|
|
4843
|
+
default: {
|
|
4844
|
+
const exhaustive = cli;
|
|
4845
|
+
return exhaustive;
|
|
4846
|
+
}
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
async function dispatchRoutedCli(runtime, cli, params) {
|
|
4850
|
+
const startTime = Date.now();
|
|
4851
|
+
let durationMs = 0;
|
|
4852
|
+
let wasSuccessful = false;
|
|
4853
|
+
const outputFormat = routedOutputFormat(cli);
|
|
4854
|
+
const prep = prepareRoutedCli(runtime, cli, params, outputFormat);
|
|
4855
|
+
if (!("args" in prep))
|
|
4856
|
+
return prep;
|
|
4857
|
+
const { corrId, args } = prep;
|
|
4858
|
+
const prepCleanup = "cleanup" in prep && typeof prep.cleanup === "function"
|
|
4859
|
+
? prep.cleanup
|
|
4860
|
+
: undefined;
|
|
4861
|
+
safeFlightStart({
|
|
4862
|
+
correlationId: corrId,
|
|
4863
|
+
cli,
|
|
4864
|
+
model: prep.resolvedModel || "default",
|
|
4865
|
+
prompt: prep.effectivePrompt,
|
|
4866
|
+
stablePrefixHash: prep.stablePrefixHash ?? undefined,
|
|
4867
|
+
stablePrefixTokens: prep.stablePrefixTokens ?? undefined,
|
|
4868
|
+
}, runtime);
|
|
4869
|
+
try {
|
|
4870
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
4871
|
+
compressResponse: params.compressResponse,
|
|
4872
|
+
outputFormat,
|
|
4873
|
+
outputSchemaDeclared: false,
|
|
4874
|
+
});
|
|
4875
|
+
const frHandoff = buildAsyncFlightRecorderHandoff(cli, prep, undefined, outputFormat, params.optimizePrompt);
|
|
4876
|
+
const result = await awaitJobOrDefer(cli, args, corrId, resolveIdleTimeout(cli, undefined), outputFormat, params.forceRefresh, runtime, undefined, prepCleanup, frHandoff.flightRecorderEntry, frHandoff.extractUsage, prep.stdinPayload, undefined, effectiveCompress);
|
|
4877
|
+
if (isDeferredResponse(result)) {
|
|
4878
|
+
return buildDeferredToolResponse(result, undefined);
|
|
4879
|
+
}
|
|
4880
|
+
const { stdout, stderr, code } = result;
|
|
4881
|
+
durationMs = Math.max(0, Date.now() - startTime);
|
|
4882
|
+
if (code !== 0) {
|
|
4883
|
+
safeFlightComplete(corrId, {
|
|
4884
|
+
response: stderr || "",
|
|
4885
|
+
durationMs,
|
|
4886
|
+
retryCount: 0,
|
|
4887
|
+
circuitBreakerState: "closed",
|
|
4888
|
+
optimizationApplied: params.optimizePrompt || params.optimizeResponse,
|
|
4889
|
+
exitCode: code,
|
|
4890
|
+
errorMessage: stderr || `Exit code ${code}`,
|
|
4891
|
+
status: "failed",
|
|
4892
|
+
}, runtime);
|
|
4893
|
+
return createErrorResponse(cli, code, stderr, corrId);
|
|
4894
|
+
}
|
|
4895
|
+
wasSuccessful = true;
|
|
4896
|
+
const claudeStream = cli === "claude" && outputFormat === "stream-json";
|
|
4897
|
+
const parsedClaude = claudeStream ? parseStreamJson(stdout) : null;
|
|
4898
|
+
const responseText = parsedClaude ? parsedClaude.text : stdout;
|
|
4899
|
+
const usage = parsedClaude
|
|
4900
|
+
? {
|
|
4901
|
+
inputTokens: parsedClaude.usage?.inputTokens,
|
|
4902
|
+
outputTokens: parsedClaude.usage?.outputTokens,
|
|
4903
|
+
cacheReadTokens: parsedClaude.usage?.cacheReadInputTokens || undefined,
|
|
4904
|
+
cacheCreationTokens: parsedClaude.usage?.cacheCreationInputTokens || undefined,
|
|
4905
|
+
costUsd: parsedClaude.costUsd ?? undefined,
|
|
4906
|
+
}
|
|
4907
|
+
: extractUsageAndCost(cli, stdout, outputFormat);
|
|
4908
|
+
const meta = extractProviderOutputMetadata(cli, stdout, outputFormat);
|
|
4909
|
+
const { costUsd: recordedCostUsd, costBasis } = deriveCostBasis(cli, prep.resolvedModel || "default", usage);
|
|
4910
|
+
safeFlightComplete(corrId, {
|
|
4911
|
+
response: cli === "codex" ? codexFrResponse(outputFormat, stdout) : responseText,
|
|
4912
|
+
durationMs,
|
|
4913
|
+
retryCount: 0,
|
|
4914
|
+
circuitBreakerState: "closed",
|
|
4915
|
+
optimizationApplied: params.optimizePrompt || params.optimizeResponse,
|
|
4916
|
+
exitCode: 0,
|
|
4917
|
+
status: "completed",
|
|
4918
|
+
inputTokens: usage.inputTokens,
|
|
4919
|
+
outputTokens: usage.outputTokens,
|
|
4920
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
4921
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
4922
|
+
costUsd: recordedCostUsd,
|
|
4923
|
+
costBasis,
|
|
4924
|
+
providerSessionId: parsedClaude ? (parsedClaude.sessionId ?? undefined) : meta.sessionId,
|
|
4925
|
+
stopReason: parsedClaude ? (parsedClaude.stopReason ?? undefined) : meta.stopReason,
|
|
4926
|
+
}, runtime);
|
|
4927
|
+
const response = buildCliResponse(cli, responseText, params.optimizeResponse, corrId, undefined, prep, durationMs, undefined, outputFormat, undefined, effectiveCompress);
|
|
4928
|
+
safeRecordCompression(corrId, response.compression, runtime);
|
|
4929
|
+
return response;
|
|
4930
|
+
}
|
|
4931
|
+
catch (error) {
|
|
4932
|
+
const elapsedMs = Math.max(0, Date.now() - startTime);
|
|
4933
|
+
safeFlightComplete(corrId, {
|
|
4934
|
+
response: "",
|
|
4935
|
+
durationMs: elapsedMs,
|
|
4936
|
+
retryCount: 0,
|
|
4937
|
+
circuitBreakerState: "closed",
|
|
4938
|
+
optimizationApplied: params.optimizePrompt || params.optimizeResponse,
|
|
4939
|
+
exitCode: 1,
|
|
4940
|
+
errorMessage: error.message,
|
|
4941
|
+
status: "failed",
|
|
4942
|
+
}, runtime);
|
|
4943
|
+
return createErrorResponse(cli, 1, "", corrId, error);
|
|
4944
|
+
}
|
|
4945
|
+
finally {
|
|
4946
|
+
const finalizedDurationMs = Math.max(0, durationMs || Date.now() - startTime);
|
|
4947
|
+
runtime.performanceMetrics.recordRequest(cli, finalizedDurationMs, wasSuccessful);
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
async function dispatchRoutedRequest(runtime, chosen, params) {
|
|
4951
|
+
if (isCliTypeValue(chosen.provider)) {
|
|
4952
|
+
return dispatchRoutedCli(runtime, chosen.provider, params);
|
|
4953
|
+
}
|
|
4954
|
+
const api = enabledApiProviders(runtime.providers).find(p => p.name === chosen.provider);
|
|
4955
|
+
if (!api) {
|
|
4956
|
+
return createErrorResponse("route_request", 1, `Routed provider '${chosen.provider}' is not an enabled API provider`, params.correlationId);
|
|
4957
|
+
}
|
|
4958
|
+
const apiParams = {
|
|
4959
|
+
prompt: params.prompt,
|
|
4960
|
+
promptParts: params.promptParts,
|
|
4961
|
+
model: chosen.model,
|
|
4962
|
+
correlationId: params.correlationId,
|
|
4963
|
+
optimizePrompt: params.optimizePrompt,
|
|
4964
|
+
optimizeResponse: params.optimizeResponse,
|
|
4965
|
+
compressResponse: params.compressResponse,
|
|
4966
|
+
};
|
|
4967
|
+
return handleApiProviderRequest(runtime, api, apiParams);
|
|
4968
|
+
}
|
|
4969
|
+
function isCliTypeValue(provider) {
|
|
4970
|
+
return CLI_TYPES.includes(provider);
|
|
4971
|
+
}
|
|
4972
|
+
function routeCandidateKey(c) {
|
|
4973
|
+
return `${c.provider}:${c.model}`;
|
|
4974
|
+
}
|
|
4975
|
+
export function isTransientRouteFailure(provider, response) {
|
|
4976
|
+
const breakerOpen = isCliTypeValue(provider)
|
|
4977
|
+
? cliBreakerState(provider) !== "CLOSED"
|
|
4978
|
+
: apiProviderBreakerState(provider) !== "CLOSED";
|
|
4979
|
+
if (breakerOpen)
|
|
4980
|
+
return true;
|
|
4981
|
+
const sc = response.structuredContent;
|
|
4982
|
+
if (isDefaultTransient({ code: sc?.exitCode }))
|
|
4983
|
+
return true;
|
|
4984
|
+
const haystack = `${response.content.map(c => c.text).join(" ")} ${sc?.errorCategory ?? ""}`;
|
|
4985
|
+
return /\b(ETIMEDOUT|ECONNRESET|ECONNREFUSED|EPIPE)\b/i.test(haystack);
|
|
4986
|
+
}
|
|
4987
|
+
function attachRouting(response, block) {
|
|
4988
|
+
response.routing = block;
|
|
4989
|
+
response.structuredContent = { ...(response.structuredContent ?? {}), routing: block };
|
|
4990
|
+
const chosen = block.chosen ? `${block.chosen.provider}/${block.chosen.model}` : "none";
|
|
4991
|
+
const cost = block.estCostUsd !== undefined ? `~$${block.estCostUsd.toFixed(6)}` : "unpriced";
|
|
4992
|
+
const basis = block.costBasis ? ` (${block.costBasis}, ${block.confidence})` : "";
|
|
4993
|
+
const banner = `[routing] chosen=${chosen} est=${cost}${basis} considered=${block.consideredCount} reroutes=${block.reroutes}`;
|
|
4994
|
+
const first = response.content[0];
|
|
4995
|
+
if (first && first.type === "text") {
|
|
4996
|
+
first.text = `${banner}\n${first.text}`;
|
|
4997
|
+
}
|
|
4998
|
+
else {
|
|
4999
|
+
response.content.unshift({ type: "text", text: banner });
|
|
5000
|
+
}
|
|
5001
|
+
return response;
|
|
5002
|
+
}
|
|
5003
|
+
function buildRoutingBlock(decision, reroutes) {
|
|
5004
|
+
return {
|
|
5005
|
+
chosen: decision.chosen ? { ...decision.chosen } : null,
|
|
5006
|
+
tier: decision.tier,
|
|
5007
|
+
estCostUsd: decision.estCostUsd,
|
|
5008
|
+
costBasis: decision.costBasis,
|
|
5009
|
+
confidence: decision.confidence,
|
|
5010
|
+
nearTie: decision.nearTie,
|
|
5011
|
+
estInputTokens: decision.estInputTokens,
|
|
5012
|
+
estOutputTokens: decision.estOutputTokens,
|
|
5013
|
+
priceAsOf: decision.priceAsOf,
|
|
5014
|
+
priceSource: decision.priceSource,
|
|
5015
|
+
consideredCount: decision.consideredCount,
|
|
5016
|
+
rejected: decision.rejected.map(r => ({ candidate: { ...r.candidate }, reason: r.reason })),
|
|
5017
|
+
reroutes,
|
|
5018
|
+
error: decision.error,
|
|
5019
|
+
};
|
|
5020
|
+
}
|
|
5021
|
+
export function deriveCostBasis(provider, resolvedModel, usage) {
|
|
5022
|
+
if (usage.costUsd != null) {
|
|
5023
|
+
return { costUsd: usage.costUsd, costBasis: "provider-reported" };
|
|
5024
|
+
}
|
|
5025
|
+
const hasCounts = usage.inputTokens != null || usage.outputTokens != null;
|
|
5026
|
+
if (hasCounts) {
|
|
5027
|
+
const modelCost = getModelCost(provider, resolvedModel);
|
|
5028
|
+
if (modelCost.source !== "unknown") {
|
|
5029
|
+
const counts = {
|
|
5030
|
+
inputTokens: usage.inputTokens ?? 0,
|
|
5031
|
+
outputTokens: usage.outputTokens ?? 0,
|
|
5032
|
+
cacheReadTokens: usage.cacheReadTokens,
|
|
5033
|
+
cacheCreationTokens: usage.cacheCreationTokens,
|
|
5034
|
+
};
|
|
5035
|
+
const composed = composeCost(counts, { estInputTokens: 0, estOutputTokens: 0 }, modelCost);
|
|
5036
|
+
return { costUsd: composed.costUsd, costBasis: composed.cost_basis };
|
|
5037
|
+
}
|
|
5038
|
+
}
|
|
5039
|
+
return {};
|
|
5040
|
+
}
|
|
5041
|
+
function recordRoutingDecision(runtime, correlationId, decision, reroutes) {
|
|
5042
|
+
const reason = decision.chosen
|
|
5043
|
+
? `cheapest${decision.nearTie ? ":near-tie" : ""}`
|
|
5044
|
+
: (decision.error ?? "no-eligible");
|
|
5045
|
+
safeRecordRouting(correlationId, {
|
|
5046
|
+
estCostUsd: decision.estCostUsd ?? null,
|
|
5047
|
+
estConfidence: decision.confidence ?? null,
|
|
5048
|
+
reason,
|
|
5049
|
+
considered: decision.consideredCount,
|
|
5050
|
+
reroutes,
|
|
5051
|
+
}, runtime);
|
|
5052
|
+
}
|
|
5053
|
+
function withDispatchRejections(decision, dispatchRejections) {
|
|
5054
|
+
if (dispatchRejections.length === 0)
|
|
5055
|
+
return decision;
|
|
5056
|
+
const seen = new Set(decision.rejected.map(r => routeCandidateKey(r.candidate)));
|
|
5057
|
+
const extra = dispatchRejections.filter(r => !seen.has(routeCandidateKey(r.candidate)));
|
|
5058
|
+
return { ...decision, rejected: [...decision.rejected, ...extra] };
|
|
5059
|
+
}
|
|
5060
|
+
function routeErrorResponse(decision, reroutes, corrId, lastFailure) {
|
|
5061
|
+
const reasons = decision.rejected
|
|
5062
|
+
.map(r => `${routeCandidateKey(r.candidate)} (${r.reason})`)
|
|
5063
|
+
.join(", ");
|
|
5064
|
+
const kind = decision.error ?? "NoEligibleCandidate";
|
|
5065
|
+
const base = kind === "BudgetExceeded"
|
|
5066
|
+
? `route_request: no candidate fits the budget. Rejected: ${reasons || "none eligible"}.`
|
|
5067
|
+
: `route_request: no eligible candidate. Rejected: ${reasons || "empty pool"}.`;
|
|
5068
|
+
const diag = lastFailure?.content?.[0]?.text
|
|
5069
|
+
? `\nLast dispatch failure: ${lastFailure.content[0].text}`
|
|
5070
|
+
: "";
|
|
5071
|
+
const resp = createErrorResponse("route_request", 1, `${base}${diag}`, corrId);
|
|
5072
|
+
return attachRouting(resp, buildRoutingBlock({ ...decision, error: kind }, reroutes));
|
|
5073
|
+
}
|
|
5074
|
+
async function runRouteRequest(runtime, params) {
|
|
5075
|
+
const corrId = params.correlationId ?? randomUUID();
|
|
5076
|
+
const cfg = runtime.leastCost;
|
|
5077
|
+
const { env, routerConfig, req } = buildRouteContext(runtime, params);
|
|
5078
|
+
const excluded = new Set();
|
|
5079
|
+
const dispatchRejections = [];
|
|
5080
|
+
let reroutes = 0;
|
|
5081
|
+
let transientReroutes = 0;
|
|
5082
|
+
let lastFailure = null;
|
|
5083
|
+
let lastDecision = null;
|
|
5084
|
+
const maxIterations = env.providers().length * 8 + cfg.maxReroutes + 2;
|
|
5085
|
+
for (let i = 0; i < maxIterations; i++) {
|
|
5086
|
+
const decision = selectCandidate(req, env, routerConfig, excluded);
|
|
5087
|
+
lastDecision = decision;
|
|
5088
|
+
if (!decision.chosen) {
|
|
5089
|
+
return routeErrorResponse(withDispatchRejections(decision, dispatchRejections), reroutes, corrId, lastFailure);
|
|
5090
|
+
}
|
|
5091
|
+
const chosen = decision.chosen;
|
|
5092
|
+
const attemptCorrId = i === 0 ? corrId : randomUUID();
|
|
5093
|
+
const dispatchParams = {
|
|
5094
|
+
prompt: params.prompt,
|
|
5095
|
+
promptParts: params.promptParts,
|
|
5096
|
+
model: chosen.model,
|
|
5097
|
+
correlationId: attemptCorrId,
|
|
5098
|
+
optimizePrompt: params.optimizePrompt ?? false,
|
|
5099
|
+
optimizeResponse: params.optimizeResponse ?? false,
|
|
5100
|
+
compressResponse: params.compressResponse,
|
|
5101
|
+
forceRefresh: params.forceRefresh,
|
|
5102
|
+
};
|
|
5103
|
+
const result = await dispatchRoutedRequest(runtime, chosen, dispatchParams);
|
|
5104
|
+
if (!result.isError) {
|
|
5105
|
+
recordRoutingDecision(runtime, attemptCorrId, decision, reroutes);
|
|
5106
|
+
return attachRouting(result, buildRoutingBlock(decision, reroutes));
|
|
5107
|
+
}
|
|
5108
|
+
lastFailure = result;
|
|
5109
|
+
const transient = isTransientRouteFailure(chosen.provider, result);
|
|
5110
|
+
excluded.add(routeCandidateKey(chosen));
|
|
5111
|
+
dispatchRejections.push({
|
|
5112
|
+
candidate: chosen,
|
|
5113
|
+
reason: transient ? "dispatch-failed:transient" : "dispatch-failed:non-transient",
|
|
5114
|
+
});
|
|
5115
|
+
reroutes++;
|
|
5116
|
+
if (transient) {
|
|
5117
|
+
transientReroutes++;
|
|
5118
|
+
if (transientReroutes > cfg.maxReroutes) {
|
|
5119
|
+
return attachRouting(result, buildRoutingBlock(decision, reroutes));
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
if (lastDecision) {
|
|
5124
|
+
return routeErrorResponse(withDispatchRejections(lastDecision, dispatchRejections), reroutes, corrId, lastFailure);
|
|
5125
|
+
}
|
|
5126
|
+
return createErrorResponse("route_request", 1, "route_request: routing did not converge", corrId);
|
|
5127
|
+
}
|
|
5128
|
+
function buildRouteContext(runtime, params) {
|
|
5129
|
+
return {
|
|
5130
|
+
env: buildRouterEnv({
|
|
5131
|
+
performanceMetrics: runtime.performanceMetrics,
|
|
5132
|
+
limiterSnapshot: runtime.asyncJobManager.getLimiterSnapshot(),
|
|
5133
|
+
apiProviders: enabledApiProviders(runtime.providers),
|
|
5134
|
+
preferCatalogPrice: runtime.leastCost.preferCatalogPrice,
|
|
5135
|
+
flightRecorder: runtime.flightRecorder,
|
|
5136
|
+
priorsScope: runtime.leastCost.priorsScope,
|
|
5137
|
+
ownerPrincipal: resolveOwnerPrincipal(getRequestContext()),
|
|
5138
|
+
}),
|
|
5139
|
+
routerConfig: toRouterConfig(runtime.leastCost),
|
|
5140
|
+
req: {
|
|
5141
|
+
prompt: params.prompt ?? "",
|
|
5142
|
+
candidates: params.candidates,
|
|
5143
|
+
minTier: params.minTier,
|
|
5144
|
+
maxCostUsd: params.maxCostUsd,
|
|
5145
|
+
expectedOutputTokens: params.expectedOutputTokens,
|
|
5146
|
+
maxOutputTokens: params.maxOutputTokens,
|
|
5147
|
+
requiredCapabilities: params.requiredCapabilities,
|
|
5148
|
+
allowUnpriced: params.allowUnpriced,
|
|
5149
|
+
budgetWaiver: params.budgetWaiver,
|
|
5150
|
+
fallback: params.fallback,
|
|
5151
|
+
},
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
async function dispatchRoutedCliAsync(runtime, cli, params) {
|
|
5155
|
+
const outputFormat = routedOutputFormat(cli);
|
|
5156
|
+
const prep = prepareRoutedCli(runtime, cli, params, outputFormat);
|
|
5157
|
+
if (!("args" in prep))
|
|
5158
|
+
return prep;
|
|
5159
|
+
const { corrId, args } = prep;
|
|
5160
|
+
const prepCleanup = "cleanup" in prep && typeof prep.cleanup === "function"
|
|
5161
|
+
? prep.cleanup
|
|
5162
|
+
: undefined;
|
|
5163
|
+
try {
|
|
5164
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
5165
|
+
compressResponse: params.compressResponse,
|
|
5166
|
+
outputFormat,
|
|
5167
|
+
outputSchemaDeclared: false,
|
|
5168
|
+
});
|
|
5169
|
+
const frHandoff = buildAsyncFlightRecorderHandoff(cli, prep, undefined, outputFormat, params.optimizePrompt);
|
|
5170
|
+
const job = runtime.asyncJobManager.startJob(cli, args, corrId, undefined, resolveIdleTimeout(cli, undefined), outputFormat, params.forceRefresh, undefined, prepCleanup, frHandoff.flightRecorderEntry, frHandoff.extractUsage, true, prep.stdinPayload, effectiveCompress);
|
|
5171
|
+
return {
|
|
5172
|
+
content: [
|
|
5173
|
+
{
|
|
5174
|
+
type: "text",
|
|
5175
|
+
text: JSON.stringify({ success: true, job, sessionId: null }, null, 2),
|
|
5176
|
+
},
|
|
5177
|
+
],
|
|
5178
|
+
};
|
|
5179
|
+
}
|
|
5180
|
+
catch (error) {
|
|
5181
|
+
return createErrorResponse("route_request_async", 1, "", corrId, error);
|
|
5182
|
+
}
|
|
5183
|
+
}
|
|
5184
|
+
async function runRouteRequestAsync(runtime, params) {
|
|
5185
|
+
const corrId = params.correlationId ?? randomUUID();
|
|
5186
|
+
const { env, routerConfig, req } = buildRouteContext(runtime, params);
|
|
5187
|
+
const decision = selectCandidate(req, env, routerConfig);
|
|
5188
|
+
if (!decision.chosen) {
|
|
5189
|
+
return routeErrorResponse(decision, 0, corrId);
|
|
5190
|
+
}
|
|
5191
|
+
const chosen = decision.chosen;
|
|
5192
|
+
const dispatchParams = {
|
|
5193
|
+
prompt: params.prompt,
|
|
5194
|
+
promptParts: params.promptParts,
|
|
5195
|
+
model: chosen.model,
|
|
5196
|
+
correlationId: corrId,
|
|
5197
|
+
optimizePrompt: params.optimizePrompt ?? false,
|
|
5198
|
+
optimizeResponse: params.optimizeResponse ?? false,
|
|
5199
|
+
compressResponse: params.compressResponse,
|
|
5200
|
+
forceRefresh: params.forceRefresh,
|
|
5201
|
+
};
|
|
5202
|
+
let result;
|
|
5203
|
+
if (isCliTypeValue(chosen.provider)) {
|
|
5204
|
+
result = await dispatchRoutedCliAsync(runtime, chosen.provider, dispatchParams);
|
|
5205
|
+
}
|
|
5206
|
+
else {
|
|
5207
|
+
const api = enabledApiProviders(runtime.providers).find(p => p.name === chosen.provider);
|
|
5208
|
+
if (!api) {
|
|
5209
|
+
return createErrorResponse("route_request_async", 1, `Routed provider '${chosen.provider}' is not an enabled API provider`, corrId);
|
|
5210
|
+
}
|
|
5211
|
+
result = await handleApiProviderRequestAsync(runtime, api, {
|
|
5212
|
+
prompt: params.prompt,
|
|
5213
|
+
promptParts: params.promptParts,
|
|
5214
|
+
model: chosen.model,
|
|
5215
|
+
correlationId: corrId,
|
|
5216
|
+
optimizePrompt: params.optimizePrompt ?? false,
|
|
5217
|
+
optimizeResponse: params.optimizeResponse ?? false,
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
recordRoutingDecision(runtime, corrId, decision, 0);
|
|
5221
|
+
return attachRouting(result, buildRoutingBlock(decision, 0));
|
|
5222
|
+
}
|
|
4659
5223
|
export function createGatewayServer(deps = {}) {
|
|
4660
5224
|
const runtime = resolveGatewayServerRuntime(deps, { isolateState: true });
|
|
4661
5225
|
const { sessionManager, asyncJobManager, approvalManager, performanceMetrics, logger, persistence, flightRecorder, cacheAwareness, providers, } = runtime;
|
|
@@ -4669,6 +5233,9 @@ export function createGatewayServer(deps = {}) {
|
|
|
4669
5233
|
asyncJobManager,
|
|
4670
5234
|
apiProviders: enabledApiProviders(providers),
|
|
4671
5235
|
validationRunStore: asyncJobManager.getValidationRunStore(),
|
|
5236
|
+
leastCost: runtime.leastCost,
|
|
5237
|
+
performanceMetrics: runtime.performanceMetrics,
|
|
5238
|
+
flightRecorder: runtime.flightRecorder,
|
|
4672
5239
|
});
|
|
4673
5240
|
registerWorkspaceTools(server, runtime);
|
|
4674
5241
|
registerProviderAdminTools(server, {
|
|
@@ -4680,6 +5247,93 @@ export function createGatewayServer(deps = {}) {
|
|
|
4680
5247
|
if (apiProviderTools.length > 0) {
|
|
4681
5248
|
runtime.logger.info(`Registered API provider tools: ${apiProviderTools.join(", ")}`);
|
|
4682
5249
|
}
|
|
5250
|
+
if (runtime.leastCost.enabled) {
|
|
5251
|
+
const RouteCandidateShape = z.object({
|
|
5252
|
+
provider: z.string().min(1).describe("A CLI_TYPES member or an enabled API-provider name."),
|
|
5253
|
+
model: z.string().min(1).describe("A concrete model id/alias that provider can serve."),
|
|
5254
|
+
});
|
|
5255
|
+
const RouteRequiredCapabilitiesShape = z
|
|
5256
|
+
.object({
|
|
5257
|
+
images: z.boolean().optional(),
|
|
5258
|
+
attachments: z.boolean().optional(),
|
|
5259
|
+
toolCalling: z.boolean().optional(),
|
|
5260
|
+
jsonSchema: z.boolean().optional(),
|
|
5261
|
+
outputFormat: z.string().optional(),
|
|
5262
|
+
effort: z.string().optional(),
|
|
5263
|
+
})
|
|
5264
|
+
.describe("Hard capability constraints; a candidate missing any required capability is excluded.");
|
|
5265
|
+
const routeInputShape = {
|
|
5266
|
+
prompt: z
|
|
5267
|
+
.string()
|
|
5268
|
+
.min(1)
|
|
5269
|
+
.max(100000)
|
|
5270
|
+
.optional()
|
|
5271
|
+
.describe("Prompt text to route (mutually exclusive with promptParts)."),
|
|
5272
|
+
promptParts: PromptPartsSchema.optional().describe("Cache-aware structured prompt (mutually exclusive with prompt)."),
|
|
5273
|
+
candidates: z
|
|
5274
|
+
.array(RouteCandidateShape)
|
|
5275
|
+
.optional()
|
|
5276
|
+
.describe("Explicit (provider, model) pool to restrict routing to; also whitelists untiered / maintain-only candidates. Empty/omitted = all eligible."),
|
|
5277
|
+
minTier: z
|
|
5278
|
+
.enum(["economy", "standard", "frontier"])
|
|
5279
|
+
.optional()
|
|
5280
|
+
.describe("Minimum quality tier floor (default from config, usually 'standard'). No silent downgrade below it."),
|
|
5281
|
+
maxCostUsd: z
|
|
5282
|
+
.number()
|
|
5283
|
+
.positive()
|
|
5284
|
+
.optional()
|
|
5285
|
+
.describe("Per-request budget cap (USD). Over-budget fails closed unless waived via an explicit fallback + budgetWaiver."),
|
|
5286
|
+
maxOutputTokens: z
|
|
5287
|
+
.number()
|
|
5288
|
+
.int()
|
|
5289
|
+
.positive()
|
|
5290
|
+
.optional()
|
|
5291
|
+
.describe("Caller output cap; bounds the conservative budget estimate."),
|
|
5292
|
+
expectedOutputTokens: z
|
|
5293
|
+
.number()
|
|
5294
|
+
.int()
|
|
5295
|
+
.positive()
|
|
5296
|
+
.optional()
|
|
5297
|
+
.describe("Expected output tokens for the ranking estimate (default from config)."),
|
|
5298
|
+
requiredCapabilities: RouteRequiredCapabilitiesShape.optional(),
|
|
5299
|
+
allowUnpriced: z
|
|
5300
|
+
.boolean()
|
|
5301
|
+
.optional()
|
|
5302
|
+
.describe("Admit unpriced (source 'unknown') candidates. They still rank strictly last and cannot pass the budget gate without budgetWaiver."),
|
|
5303
|
+
budgetWaiver: z
|
|
5304
|
+
.boolean()
|
|
5305
|
+
.optional()
|
|
5306
|
+
.describe("Explicit acknowledgment to admit an unpriced or over-budget fallback candidate (with allowUnpriced / fallback)."),
|
|
5307
|
+
fallback: RouteCandidateShape.optional().describe("A pinned (provider, model) used only when the eligible pool is empty; bypasses cost ranking but still passes eligibility + budget (unless budgetWaiver)."),
|
|
5308
|
+
optimizePrompt: z
|
|
5309
|
+
.boolean()
|
|
5310
|
+
.optional()
|
|
5311
|
+
.describe("Apply prompt token optimization before dispatch."),
|
|
5312
|
+
optimizeResponse: z.boolean().optional().describe("Apply response token optimization."),
|
|
5313
|
+
compressResponse: z
|
|
5314
|
+
.boolean()
|
|
5315
|
+
.optional()
|
|
5316
|
+
.describe("Apply the native display-text compressor to the response."),
|
|
5317
|
+
correlationId: z.string().optional().describe("Caller-supplied correlation id for tracing."),
|
|
5318
|
+
forceRefresh: z
|
|
5319
|
+
.boolean()
|
|
5320
|
+
.optional()
|
|
5321
|
+
.describe("Bypass the async dedup window for this request."),
|
|
5322
|
+
};
|
|
5323
|
+
const routeAnnotations = {
|
|
5324
|
+
readOnlyHint: false,
|
|
5325
|
+
destructiveHint: true,
|
|
5326
|
+
idempotentHint: false,
|
|
5327
|
+
openWorldHint: true,
|
|
5328
|
+
};
|
|
5329
|
+
server.tool("route_request", "Least-cost routing: pick the cheapest eligible (provider, model) candidate that meets the caller's quality tier, capability, and budget constraints, then dispatch it through the normal provider path. Returns the provider response plus a `routing` block (chosen candidate, cost estimate + basis/confidence, rejections, reroutes). Registered only when [least_cost].enabled is true.", routeInputShape, { title: "Least-cost route (sync)", ...routeAnnotations }, async (params) => runRouteRequest(runtime, params));
|
|
5330
|
+
let registeredRouteTools = "route_request";
|
|
5331
|
+
if (asyncJobsEnabled) {
|
|
5332
|
+
server.tool("route_request_async", "Async least-cost routing: select the cheapest eligible (provider, model) candidate synchronously, enqueue it for background execution, and return a pollable job handle plus the `routing` block. Poll with llm_job_status, collect with llm_job_result. Registered only when [least_cost].enabled is true and async jobs are enabled.", routeInputShape, { title: "Least-cost route (async)", ...routeAnnotations }, async (params) => runRouteRequestAsync(runtime, params));
|
|
5333
|
+
registeredRouteTools += ", route_request_async";
|
|
5334
|
+
}
|
|
5335
|
+
runtime.logger.info(`Registered least-cost routing tools: ${registeredRouteTools}`);
|
|
5336
|
+
}
|
|
4683
5337
|
if (grokApiToolsEnabled) {
|
|
4684
5338
|
server.tool("grok_api_request", "Run an xAI Grok API request synchronously through the Responses API. Requires exactly one of prompt or promptParts. Registered only when [providers.xai] is configured and its API-key env var is present.", {
|
|
4685
5339
|
prompt: z
|
|
@@ -4883,6 +5537,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
4883
5537
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
4884
5538
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
4885
5539
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
5540
|
+
compressResponse: z
|
|
5541
|
+
.boolean()
|
|
5542
|
+
.optional()
|
|
5543
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
4886
5544
|
idleTimeoutMs: z
|
|
4887
5545
|
.number()
|
|
4888
5546
|
.int()
|
|
@@ -4900,7 +5558,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
4900
5558
|
destructiveHint: true,
|
|
4901
5559
|
idempotentHint: false,
|
|
4902
5560
|
openWorldHint: true,
|
|
4903
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
5561
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, optimizeResponse, compressResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
4904
5562
|
const startTime = Date.now();
|
|
4905
5563
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
4906
5564
|
return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
|
|
@@ -5020,8 +5678,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
5020
5678
|
return createErrorResponse("claude_request", 1, "", corrId, err);
|
|
5021
5679
|
}
|
|
5022
5680
|
const effectiveIdleTimeout = outputFormat === "stream-json" ? resolveIdleTimeout("claude", idleTimeoutMs) : undefined;
|
|
5023
|
-
const
|
|
5024
|
-
|
|
5681
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
5682
|
+
compressResponse,
|
|
5683
|
+
outputFormat,
|
|
5684
|
+
outputSchemaDeclared: false,
|
|
5685
|
+
});
|
|
5686
|
+
const claudeSyncFrHandoff = buildAsyncFlightRecorderHandoff("claude", prep, effectiveSessionId, outputFormat, optimizePrompt);
|
|
5687
|
+
const result = await awaitJobOrDefer("claude", args, corrId, effectiveIdleTimeout, outputFormat, forceRefresh, runtime, undefined, undefined, claudeSyncFrHandoff.flightRecorderEntry, claudeSyncFrHandoff.extractUsage, prep.stdinPayload, worktreeResolution.cwd, effectiveCompress);
|
|
5025
5688
|
if (isDeferredResponse(result)) {
|
|
5026
5689
|
const deferred = buildDeferredToolResponse(result, effectiveSessionId);
|
|
5027
5690
|
if (warnings.length > 0) {
|
|
@@ -5062,6 +5725,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
5062
5725
|
if (parsed.costUsd !== null) {
|
|
5063
5726
|
logger.debug(`[${corrId}] stream-json cost=$${parsed.costUsd}, model=${parsed.model}, turns=${parsed.numTurns}`);
|
|
5064
5727
|
}
|
|
5728
|
+
const { costUsd: claudeCostUsd, costBasis: claudeCostBasis } = deriveCostBasis("claude", prep.resolvedModel || "default", {
|
|
5729
|
+
inputTokens: parsed.usage?.inputTokens,
|
|
5730
|
+
outputTokens: parsed.usage?.outputTokens,
|
|
5731
|
+
cacheReadTokens: parsed.usage?.cacheReadInputTokens || undefined,
|
|
5732
|
+
cacheCreationTokens: parsed.usage?.cacheCreationInputTokens || undefined,
|
|
5733
|
+
costUsd: parsed.costUsd ?? undefined,
|
|
5734
|
+
});
|
|
5065
5735
|
safeFlightComplete(corrId, {
|
|
5066
5736
|
response: parsed.text,
|
|
5067
5737
|
inputTokens: parsed.usage?.inputTokens,
|
|
@@ -5071,14 +5741,16 @@ export function createGatewayServer(deps = {}) {
|
|
|
5071
5741
|
durationMs,
|
|
5072
5742
|
retryCount: 0,
|
|
5073
5743
|
circuitBreakerState: "closed",
|
|
5074
|
-
costUsd:
|
|
5744
|
+
costUsd: claudeCostUsd,
|
|
5745
|
+
costBasis: claudeCostBasis,
|
|
5075
5746
|
optimizationApplied: optimizePrompt || optimizeResponse,
|
|
5076
5747
|
exitCode: 0,
|
|
5077
5748
|
status: "completed",
|
|
5078
5749
|
providerSessionId: parsed.sessionId ?? undefined,
|
|
5079
5750
|
stopReason: parsed.stopReason ?? undefined,
|
|
5080
5751
|
}, runtime);
|
|
5081
|
-
const streamResponse = buildCliResponse("claude", parsed.text, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings);
|
|
5752
|
+
const streamResponse = buildCliResponse("claude", parsed.text, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings, effectiveCompress);
|
|
5753
|
+
safeRecordCompression(corrId, streamResponse.compression, runtime);
|
|
5082
5754
|
if (worktreeResolution.worktreePath) {
|
|
5083
5755
|
const first = streamResponse.content[0];
|
|
5084
5756
|
if (first && first.type === "text") {
|
|
@@ -5099,7 +5771,8 @@ export function createGatewayServer(deps = {}) {
|
|
|
5099
5771
|
providerSessionId: claudeMeta.sessionId,
|
|
5100
5772
|
stopReason: claudeMeta.stopReason,
|
|
5101
5773
|
}, runtime);
|
|
5102
|
-
const nonStreamResponse = buildCliResponse("claude", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings);
|
|
5774
|
+
const nonStreamResponse = buildCliResponse("claude", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, warnings, effectiveCompress);
|
|
5775
|
+
safeRecordCompression(corrId, nonStreamResponse.compression, runtime);
|
|
5103
5776
|
if (worktreeResolution.worktreePath) {
|
|
5104
5777
|
const first = nonStreamResponse.content[0];
|
|
5105
5778
|
if (first && first.type === "text") {
|
|
@@ -5181,6 +5854,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
5181
5854
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5182
5855
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5183
5856
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
5857
|
+
compressResponse: z
|
|
5858
|
+
.boolean()
|
|
5859
|
+
.optional()
|
|
5860
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
5184
5861
|
idleTimeoutMs: z
|
|
5185
5862
|
.number()
|
|
5186
5863
|
.int()
|
|
@@ -5245,7 +5922,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
5245
5922
|
destructiveHint: true,
|
|
5246
5923
|
idempotentHint: false,
|
|
5247
5924
|
openWorldHint: true,
|
|
5248
|
-
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
|
|
5925
|
+
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, optimizeResponse, compressResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
|
|
5249
5926
|
const startTime = Date.now();
|
|
5250
5927
|
const prep = prepareCodexRequest({
|
|
5251
5928
|
prompt,
|
|
@@ -5307,7 +5984,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
5307
5984
|
}, runtime);
|
|
5308
5985
|
logger.info(`[${corrId}] codex_request invoked with model=${prep.resolvedModel || "default"}, fullAuto=${fullAuto}, prompt length=${prep.effectivePrompt.length}`);
|
|
5309
5986
|
const prepCleanup = "cleanup" in prep && typeof prep.cleanup === "function" ? prep.cleanup : undefined;
|
|
5310
|
-
let worktreeResolution
|
|
5987
|
+
let worktreeResolution;
|
|
5311
5988
|
try {
|
|
5312
5989
|
worktreeResolution = await resolveWorkspaceAndWorktreeForRequest({
|
|
5313
5990
|
provider: "codex",
|
|
@@ -5323,8 +6000,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
5323
6000
|
return createErrorResponse("codex_request", 1, "", corrId, err);
|
|
5324
6001
|
}
|
|
5325
6002
|
try {
|
|
5326
|
-
const
|
|
5327
|
-
|
|
6003
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
6004
|
+
compressResponse,
|
|
6005
|
+
outputFormat,
|
|
6006
|
+
outputSchemaDeclared: outputSchema !== undefined,
|
|
6007
|
+
});
|
|
6008
|
+
const codexSyncFrHandoff = buildAsyncFlightRecorderHandoff("codex", prep, sessionId, outputFormat, optimizePrompt);
|
|
6009
|
+
const result = await awaitJobOrDefer("codex", args, corrId, resolveIdleTimeout("codex", idleTimeoutMs), outputFormat, forceRefresh, runtime, undefined, prepCleanup, codexSyncFrHandoff.flightRecorderEntry, codexSyncFrHandoff.extractUsage, undefined, worktreeResolution.cwd, effectiveCompress);
|
|
5328
6010
|
if (isDeferredResponse(result)) {
|
|
5329
6011
|
return buildDeferredToolResponse(result, sessionId);
|
|
5330
6012
|
}
|
|
@@ -5375,6 +6057,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
5375
6057
|
}
|
|
5376
6058
|
logger.info(`[${corrId}] codex_request completed successfully in ${durationMs}ms`);
|
|
5377
6059
|
const codexUsage = extractUsageAndCost("codex", stdout, outputFormat);
|
|
6060
|
+
const { costUsd: codexCostUsd, costBasis: codexCostBasis } = deriveCostBasis("codex", prep.resolvedModel || "default", codexUsage);
|
|
5378
6061
|
const codexMeta = extractProviderOutputMetadata("codex", stdout, outputFormat);
|
|
5379
6062
|
safeFlightComplete(corrId, {
|
|
5380
6063
|
response: codexFrResponse(outputFormat, stdout),
|
|
@@ -5388,11 +6071,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
5388
6071
|
outputTokens: codexUsage.outputTokens,
|
|
5389
6072
|
cacheReadTokens: codexUsage.cacheReadTokens,
|
|
5390
6073
|
cacheCreationTokens: codexUsage.cacheCreationTokens,
|
|
5391
|
-
costUsd:
|
|
6074
|
+
costUsd: codexCostUsd,
|
|
6075
|
+
costBasis: codexCostBasis,
|
|
5392
6076
|
providerSessionId: codexMeta.sessionId,
|
|
5393
6077
|
stopReason: codexMeta.stopReason,
|
|
5394
6078
|
}, runtime);
|
|
5395
|
-
const codexResponse = buildCliResponse("codex", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat);
|
|
6079
|
+
const codexResponse = buildCliResponse("codex", stdout, optimizeResponse, corrId, effectiveSessionId, prep, durationMs, undefined, outputFormat, undefined, effectiveCompress);
|
|
6080
|
+
safeRecordCompression(corrId, codexResponse.compression, runtime);
|
|
5396
6081
|
if (worktreeResolution.worktreePath) {
|
|
5397
6082
|
const first = codexResponse.content[0];
|
|
5398
6083
|
if (first && first.type === "text") {
|
|
@@ -5585,6 +6270,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
5585
6270
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5586
6271
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5587
6272
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
6273
|
+
compressResponse: z
|
|
6274
|
+
.boolean()
|
|
6275
|
+
.optional()
|
|
6276
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
5588
6277
|
idleTimeoutMs: z
|
|
5589
6278
|
.number()
|
|
5590
6279
|
.int()
|
|
@@ -5713,6 +6402,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
5713
6402
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5714
6403
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5715
6404
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
6405
|
+
compressResponse: z
|
|
6406
|
+
.boolean()
|
|
6407
|
+
.optional()
|
|
6408
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
5716
6409
|
idleTimeoutMs: z
|
|
5717
6410
|
.number()
|
|
5718
6411
|
.int()
|
|
@@ -5869,6 +6562,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
5869
6562
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5870
6563
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5871
6564
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
6565
|
+
compressResponse: z
|
|
6566
|
+
.boolean()
|
|
6567
|
+
.optional()
|
|
6568
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
5872
6569
|
idleTimeoutMs: z
|
|
5873
6570
|
.number()
|
|
5874
6571
|
.int()
|
|
@@ -5969,6 +6666,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
5969
6666
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
5970
6667
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
5971
6668
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
6669
|
+
compressResponse: z
|
|
6670
|
+
.boolean()
|
|
6671
|
+
.optional()
|
|
6672
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
5972
6673
|
idleTimeoutMs: z
|
|
5973
6674
|
.number()
|
|
5974
6675
|
.int()
|
|
@@ -6011,7 +6712,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6011
6712
|
forceRefresh,
|
|
6012
6713
|
});
|
|
6013
6714
|
});
|
|
6014
|
-
server.tool("mistral_request", "Run a Mistral Vibe CLI request synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts. Defaults to --agent
|
|
6715
|
+
server.tool("mistral_request", "Run a Mistral Vibe CLI request synchronously (when async jobs are enabled, auto-defers to a pollable job past the sync deadline; otherwise runs to completion). Requires exactly one of prompt or promptParts. Defaults to --agent accept-edits (auto-accepts file edits; dangerous ops such as shell stay gated); pass permissionMode auto-approve (or set LLM_GATEWAY_APPROVAL_ALLOW_BYPASS) for unattended tool execution.", {
|
|
6015
6716
|
prompt: z
|
|
6016
6717
|
.string()
|
|
6017
6718
|
.min(1, "Prompt cannot be empty")
|
|
@@ -6043,7 +6744,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6043
6744
|
permissionMode: z
|
|
6044
6745
|
.string()
|
|
6045
6746
|
.optional()
|
|
6046
|
-
.describe("Vibe --agent name. Builtins: default|plan|accept-edits|auto-approve; Vibe also accepts install-gated builtins (e.g. lean) and custom agents from ~/.vibe/agents, so any name is passed through. Defaults to auto-approve for
|
|
6747
|
+
.describe("Vibe --agent name. Builtins: default|plan|accept-edits|auto-approve; Vibe also accepts install-gated builtins (e.g. lean) and custom agents from ~/.vibe/agents, so any name is passed through. Defaults to accept-edits; pass auto-approve (or set LLM_GATEWAY_APPROVAL_ALLOW_BYPASS) for unattended tool execution."),
|
|
6047
6748
|
approvalStrategy: z
|
|
6048
6749
|
.enum(["legacy", "mcp_managed"])
|
|
6049
6750
|
.default("legacy")
|
|
@@ -6067,6 +6768,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6067
6768
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6068
6769
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
6069
6770
|
optimizeResponse: z.boolean().default(false).describe("Optimize response output"),
|
|
6771
|
+
compressResponse: z
|
|
6772
|
+
.boolean()
|
|
6773
|
+
.optional()
|
|
6774
|
+
.describe("Compress the response display text via the native compressor (default: [compression].enabled in config.toml, off unless opted in). Skipped for structured output."),
|
|
6070
6775
|
idleTimeoutMs: z
|
|
6071
6776
|
.number()
|
|
6072
6777
|
.int()
|
|
@@ -6263,6 +6968,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6263
6968
|
.describe("Restrict Claude to provided MCP config only"),
|
|
6264
6969
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6265
6970
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
6971
|
+
compressResponse: z
|
|
6972
|
+
.boolean()
|
|
6973
|
+
.optional()
|
|
6974
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
6266
6975
|
idleTimeoutMs: z
|
|
6267
6976
|
.number()
|
|
6268
6977
|
.int()
|
|
@@ -6280,7 +6989,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6280
6989
|
destructiveHint: true,
|
|
6281
6990
|
idempotentHint: false,
|
|
6282
6991
|
openWorldHint: true,
|
|
6283
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
6992
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, continueSession, createNewSession, allowedTools, disallowedTools, dangerouslySkipPermissions, permissionMode, agent, agents, forkSession, systemPrompt, appendSystemPrompt, maxBudgetUsd, maxTurns, effort, excludeDynamicSystemPromptSections, fallbackModel, jsonSchema, addDir, noSessionPersistence, settingSources, settings, tools, includeHookEvents, replayUserMessages, systemPromptFile, appendSystemPromptFile, name, pluginDir, pluginUrl, safeMode, bare, debug, debugFile, workspace, worktree, approvalStrategy, approvalPolicy, mcpServers, strictMcpConfig, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
6284
6993
|
if (systemPrompt !== undefined && appendSystemPrompt !== undefined) {
|
|
6285
6994
|
return createErrorResponse("claude", 1, "", correlationId, new Error("systemPrompt and appendSystemPrompt are mutually exclusive; use one or the other (not both)."));
|
|
6286
6995
|
}
|
|
@@ -6379,8 +7088,13 @@ export function createGatewayServer(deps = {}) {
|
|
|
6379
7088
|
: undefined;
|
|
6380
7089
|
assertUpstreamCliArgs("claude", args);
|
|
6381
7090
|
assertUpstreamCliEnv("claude", undefined);
|
|
6382
|
-
const
|
|
6383
|
-
|
|
7091
|
+
const effectiveCompress = resolveEffectiveCompression(runtime.compression, {
|
|
7092
|
+
compressResponse,
|
|
7093
|
+
outputFormat,
|
|
7094
|
+
outputSchemaDeclared: false,
|
|
7095
|
+
});
|
|
7096
|
+
const claudeAsyncFrHandoff = buildAsyncFlightRecorderHandoff("claude", prep, effectiveSessionId, outputFormat, optimizePrompt);
|
|
7097
|
+
const job = asyncJobManager.startJob("claude", args, corrId, worktreeResolution.cwd, effectiveIdleTimeout, outputFormat, forceRefresh, undefined, undefined, claudeAsyncFrHandoff.flightRecorderEntry, claudeAsyncFrHandoff.extractUsage, true, prep.stdinPayload, effectiveCompress);
|
|
6384
7098
|
logger.info(`[${corrId}] claude_request_async started job ${job.id}, outputFormat=${outputFormat}`);
|
|
6385
7099
|
const asyncResponse = {
|
|
6386
7100
|
success: true,
|
|
@@ -6474,6 +7188,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6474
7188
|
createNewSession: z.boolean().default(false).describe("Force a fresh session (no resume)"),
|
|
6475
7189
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6476
7190
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7191
|
+
compressResponse: z
|
|
7192
|
+
.boolean()
|
|
7193
|
+
.optional()
|
|
7194
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
6477
7195
|
idleTimeoutMs: z
|
|
6478
7196
|
.number()
|
|
6479
7197
|
.int()
|
|
@@ -6523,7 +7241,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6523
7241
|
destructiveHint: true,
|
|
6524
7242
|
idempotentHint: false,
|
|
6525
7243
|
openWorldHint: true,
|
|
6526
|
-
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
|
|
7244
|
+
}, async ({ prompt, promptParts, model, fullAuto, sandboxMode, askForApproval, useLegacyFullAutoFlag, dangerouslyBypassApprovalsAndSandbox, approvalStrategy, approvalPolicy, mcpServers, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, outputFormat, outputSchema, search, profile, configOverrides, ephemeral, images, ignoreUserConfig, ignoreRules, workingDir, addDir, enable, disable, strictConfig, oss, localProvider, color, outputLastMessage, dangerouslyBypassHookTrust, workspace, worktree, }) => {
|
|
6527
7245
|
return handleCodexRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
6528
7246
|
prompt,
|
|
6529
7247
|
promptParts,
|
|
@@ -6541,6 +7259,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6541
7259
|
createNewSession,
|
|
6542
7260
|
correlationId,
|
|
6543
7261
|
optimizePrompt,
|
|
7262
|
+
compressResponse,
|
|
6544
7263
|
idleTimeoutMs,
|
|
6545
7264
|
forceRefresh,
|
|
6546
7265
|
outputFormat,
|
|
@@ -6614,6 +7333,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6614
7333
|
LOCAL_INCLUDE_DIRS_FIELD_SUFFIX),
|
|
6615
7334
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6616
7335
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7336
|
+
compressResponse: z
|
|
7337
|
+
.boolean()
|
|
7338
|
+
.optional()
|
|
7339
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
6617
7340
|
idleTimeoutMs: z
|
|
6618
7341
|
.number()
|
|
6619
7342
|
.int()
|
|
@@ -6663,7 +7386,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6663
7386
|
destructiveHint: true,
|
|
6664
7387
|
idempotentHint: false,
|
|
6665
7388
|
openWorldHint: true,
|
|
6666
|
-
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, project, newProject, printTimeout, workspace, worktree, }) => {
|
|
7389
|
+
}, async ({ prompt, promptParts, model, sessionId, resumeLatest, createNewSession, approvalMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, includeDirs, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, outputFormat, sandbox, policyFiles, adminPolicyFiles, attachments, skipTrust, yolo, project, newProject, printTimeout, workspace, worktree, }) => {
|
|
6667
7390
|
return handleGeminiRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
6668
7391
|
prompt,
|
|
6669
7392
|
promptParts,
|
|
@@ -6679,6 +7402,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6679
7402
|
includeDirs,
|
|
6680
7403
|
correlationId,
|
|
6681
7404
|
optimizePrompt,
|
|
7405
|
+
compressResponse,
|
|
6682
7406
|
idleTimeoutMs,
|
|
6683
7407
|
forceRefresh,
|
|
6684
7408
|
outputFormat,
|
|
@@ -6752,6 +7476,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6752
7476
|
.describe("Disallowed built-in tools (passed as --disallowed-tools comma list)"),
|
|
6753
7477
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6754
7478
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7479
|
+
compressResponse: z
|
|
7480
|
+
.boolean()
|
|
7481
|
+
.optional()
|
|
7482
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
6755
7483
|
idleTimeoutMs: z
|
|
6756
7484
|
.number()
|
|
6757
7485
|
.int()
|
|
@@ -6893,7 +7621,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6893
7621
|
destructiveHint: true,
|
|
6894
7622
|
idempotentHint: false,
|
|
6895
7623
|
openWorldHint: true,
|
|
6896
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktreeRef, forkSession, jsonSchema, workspace, worktree, }) => {
|
|
7624
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, alwaysApprove, permissionMode, effort, reasoningEffort, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, maxTurns, workingDir, sandbox, rules, systemPromptOverride, allow, deny, compactionMode, compactionDetail, agent, bestOfN, check, disableWebSearch, todoGate, verbatim, agents, promptFile, promptJson, single, experimentalMemory, noAltScreen, noMemory, noPlan, noSubagents, oauth, restoreCode, leaderSocket, nativeWorktree, worktreeRef, forkSession, jsonSchema, workspace, worktree, }) => {
|
|
6897
7625
|
return handleGrokRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
6898
7626
|
prompt,
|
|
6899
7627
|
promptParts,
|
|
@@ -6913,6 +7641,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
6913
7641
|
disallowedTools,
|
|
6914
7642
|
correlationId,
|
|
6915
7643
|
optimizePrompt,
|
|
7644
|
+
compressResponse,
|
|
6916
7645
|
idleTimeoutMs,
|
|
6917
7646
|
forceRefresh,
|
|
6918
7647
|
maxTurns,
|
|
@@ -6994,6 +7723,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
6994
7723
|
createNewSession: z.boolean().default(false).describe("Force a new session"),
|
|
6995
7724
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
6996
7725
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7726
|
+
compressResponse: z
|
|
7727
|
+
.boolean()
|
|
7728
|
+
.optional()
|
|
7729
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
6997
7730
|
idleTimeoutMs: z
|
|
6998
7731
|
.number()
|
|
6999
7732
|
.int()
|
|
@@ -7011,7 +7744,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7011
7744
|
destructiveHint: true,
|
|
7012
7745
|
idempotentHint: false,
|
|
7013
7746
|
openWorldHint: true,
|
|
7014
|
-
}, async ({ prompt, model, permissionMode, promptFile, config, sandbox, exportSession, respectWorkspaceTrust, agentConfig, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
7747
|
+
}, async ({ prompt, model, permissionMode, promptFile, config, sandbox, exportSession, respectWorkspaceTrust, agentConfig, sessionId, resumeLatest, createNewSession, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
7015
7748
|
return handleDevinRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
7016
7749
|
prompt,
|
|
7017
7750
|
model,
|
|
@@ -7027,6 +7760,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7027
7760
|
createNewSession,
|
|
7028
7761
|
correlationId,
|
|
7029
7762
|
optimizePrompt,
|
|
7763
|
+
compressResponse,
|
|
7030
7764
|
idleTimeoutMs,
|
|
7031
7765
|
forceRefresh,
|
|
7032
7766
|
});
|
|
@@ -7086,6 +7820,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
7086
7820
|
.describe("Approval policy when approvalStrategy is mcp_managed: strict|balanced|permissive (default balanced). Ignored under legacy strategy."),
|
|
7087
7821
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
7088
7822
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7823
|
+
compressResponse: z
|
|
7824
|
+
.boolean()
|
|
7825
|
+
.optional()
|
|
7826
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
7089
7827
|
idleTimeoutMs: z
|
|
7090
7828
|
.number()
|
|
7091
7829
|
.int()
|
|
@@ -7103,7 +7841,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7103
7841
|
destructiveHint: true,
|
|
7104
7842
|
idempotentHint: false,
|
|
7105
7843
|
openWorldHint: true,
|
|
7106
|
-
}, async ({ prompt, model, mode, outputFormat, force, autoReview, sandbox, trust, workspace, addDir, sessionId, resumeLatest, createNewSession, approvalStrategy, approvalPolicy, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, }) => {
|
|
7844
|
+
}, async ({ prompt, model, mode, outputFormat, force, autoReview, sandbox, trust, workspace, addDir, sessionId, resumeLatest, createNewSession, approvalStrategy, approvalPolicy, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, }) => {
|
|
7107
7845
|
return handleCursorRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
7108
7846
|
prompt,
|
|
7109
7847
|
model,
|
|
@@ -7122,6 +7860,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7122
7860
|
approvalPolicy,
|
|
7123
7861
|
correlationId,
|
|
7124
7862
|
optimizePrompt,
|
|
7863
|
+
compressResponse,
|
|
7125
7864
|
idleTimeoutMs,
|
|
7126
7865
|
forceRefresh,
|
|
7127
7866
|
});
|
|
@@ -7154,7 +7893,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7154
7893
|
permissionMode: z
|
|
7155
7894
|
.string()
|
|
7156
7895
|
.optional()
|
|
7157
|
-
.describe("Vibe --agent name. Builtins: default|plan|accept-edits|auto-approve; Vibe also accepts install-gated builtins (e.g. lean) and custom agents from ~/.vibe/agents, so any name is passed through. Defaults to auto-approve for
|
|
7896
|
+
.describe("Vibe --agent name. Builtins: default|plan|accept-edits|auto-approve; Vibe also accepts install-gated builtins (e.g. lean) and custom agents from ~/.vibe/agents, so any name is passed through. Defaults to accept-edits; pass auto-approve (or set LLM_GATEWAY_APPROVAL_ALLOW_BYPASS) for unattended tool execution."),
|
|
7158
7897
|
approvalStrategy: z
|
|
7159
7898
|
.enum(["legacy", "mcp_managed"])
|
|
7160
7899
|
.default("legacy")
|
|
@@ -7177,6 +7916,10 @@ export function createGatewayServer(deps = {}) {
|
|
|
7177
7916
|
.describe("Accepted for caller parity; Vibe has no deny-list flag, so values are ignored (a warning is logged)."),
|
|
7178
7917
|
correlationId: z.string().optional().describe("Request trace ID (auto if omitted)"),
|
|
7179
7918
|
optimizePrompt: z.boolean().default(false).describe("Optimize prompt before execution"),
|
|
7919
|
+
compressResponse: z
|
|
7920
|
+
.boolean()
|
|
7921
|
+
.optional()
|
|
7922
|
+
.describe("Compress the response display text when collected via llm_job_result (native compressor; default: [compression].enabled)."),
|
|
7180
7923
|
idleTimeoutMs: z
|
|
7181
7924
|
.number()
|
|
7182
7925
|
.int()
|
|
@@ -7214,7 +7957,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7214
7957
|
destructiveHint: true,
|
|
7215
7958
|
idempotentHint: false,
|
|
7216
7959
|
openWorldHint: true,
|
|
7217
|
-
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, workspace, worktree, }) => {
|
|
7960
|
+
}, async ({ prompt, promptParts, model, outputFormat, sessionId, resumeLatest, createNewSession, permissionMode, approvalStrategy, approvalPolicy, mcpServers, allowedTools, disallowedTools, correlationId, optimizePrompt, compressResponse, idleTimeoutMs, forceRefresh, trust, maxTurns, maxPrice, maxTokens, workingDir, addDir, workspace, worktree, }) => {
|
|
7218
7961
|
return handleMistralRequestAsync({ sessionManager, asyncJobManager, logger, runtime }, {
|
|
7219
7962
|
prompt,
|
|
7220
7963
|
promptParts,
|
|
@@ -7231,6 +7974,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7231
7974
|
disallowedTools,
|
|
7232
7975
|
correlationId,
|
|
7233
7976
|
optimizePrompt,
|
|
7977
|
+
compressResponse,
|
|
7234
7978
|
idleTimeoutMs,
|
|
7235
7979
|
forceRefresh,
|
|
7236
7980
|
trust,
|
|
@@ -7324,6 +8068,23 @@ export function createGatewayServer(deps = {}) {
|
|
|
7324
8068
|
result.stdout) {
|
|
7325
8069
|
result.stdout = codexDisplayText(result.stdout);
|
|
7326
8070
|
}
|
|
8071
|
+
const compressJob = asyncJobManager.getJobCompressResponse(jobId) && outputFormat !== "json";
|
|
8072
|
+
if (compressJob && result.stdout) {
|
|
8073
|
+
if (outputFormat === "stream-json" && parsed) {
|
|
8074
|
+
result.stdout = parsed.text;
|
|
8075
|
+
}
|
|
8076
|
+
const compressed = compressDisplayText(result.stdout, {
|
|
8077
|
+
provider: asyncJobManager.getJobCli(jobId) ?? "unknown",
|
|
8078
|
+
direction: "outbound",
|
|
8079
|
+
outputFormat,
|
|
8080
|
+
outputSchemaDeclared: false,
|
|
8081
|
+
lossless: true,
|
|
8082
|
+
});
|
|
8083
|
+
if (compressed.text !== result.stdout) {
|
|
8084
|
+
result.stdout = compressed.text;
|
|
8085
|
+
safeRecordCompression(result.correlationId, compressed, runtime);
|
|
8086
|
+
}
|
|
8087
|
+
}
|
|
7327
8088
|
if (callerIsRemote()) {
|
|
7328
8089
|
const leakedId = result.providerSessionId;
|
|
7329
8090
|
delete result.providerSessionId;
|
|
@@ -7346,7 +8107,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7346
8107
|
...(parsed
|
|
7347
8108
|
? {
|
|
7348
8109
|
parsed: {
|
|
7349
|
-
text: parsed.text,
|
|
8110
|
+
...(compressJob ? {} : { text: parsed.text }),
|
|
7350
8111
|
costUsd: parsed.costUsd,
|
|
7351
8112
|
usage: parsed.usage,
|
|
7352
8113
|
model: parsed.model,
|
|
@@ -7354,7 +8115,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7354
8115
|
},
|
|
7355
8116
|
}
|
|
7356
8117
|
: {}),
|
|
7357
|
-
}, null, 2),
|
|
8118
|
+
}, null, compressJob ? 0 : 2),
|
|
7358
8119
|
},
|
|
7359
8120
|
],
|
|
7360
8121
|
};
|
|
@@ -7476,21 +8237,28 @@ export function createGatewayServer(deps = {}) {
|
|
|
7476
8237
|
}, async () => {
|
|
7477
8238
|
const health = asyncJobManager.getJobHealth();
|
|
7478
8239
|
const storeAttached = asyncJobManager.hasStore();
|
|
7479
|
-
const
|
|
8240
|
+
const durableAdmission = asyncJobManager.getDurableAdmissionHealth();
|
|
8241
|
+
const asyncJobsConfigured = persistence.backend !== "none" && persistence.asyncJobsEnabled;
|
|
8242
|
+
const asyncJobsEffective = asyncJobsConfigured && storeAttached && durableAdmission.admitting;
|
|
7480
8243
|
const persistenceBlock = {
|
|
7481
8244
|
backend: persistence.backend,
|
|
7482
8245
|
dbPath: persistence.path,
|
|
7483
8246
|
dsn: persistence.dsn ? "[redacted]" : null,
|
|
7484
8247
|
retentionDays: persistence.retentionDays,
|
|
7485
8248
|
dedupWindowMs: persistence.dedupWindowMs,
|
|
8249
|
+
asyncJobsConfigured,
|
|
8250
|
+
storeAttached,
|
|
7486
8251
|
asyncJobsEnabled: asyncJobsEffective,
|
|
8252
|
+
durableAdmission,
|
|
7487
8253
|
acknowledgeEphemeral: persistence.acknowledgeEphemeral,
|
|
7488
8254
|
sources: persistence.sources,
|
|
7489
8255
|
warning: asyncJobsEffective
|
|
7490
8256
|
? null
|
|
7491
8257
|
: persistence.backend === "none"
|
|
7492
8258
|
? "Async job persistence is disabled (backend = 'none'). *_request_async tools are NOT registered on this gateway. Set [persistence].backend = 'sqlite' (or 'memory' + acknowledgeEphemeral = true) to enable them."
|
|
7493
|
-
:
|
|
8259
|
+
: !storeAttached
|
|
8260
|
+
? `Async job persistence is configured (backend = '${persistence.backend}') but the durable job store failed to open, so *_request_async / llm_job_* tools are NOT registered on this gateway. Check gateway startup logs for the store-open error.`
|
|
8261
|
+
: "Async job persistence is attached but durable admission is temporarily disabled while its heartbeat lease recovers. Existing async tools fail closed until admission is restored.",
|
|
7494
8262
|
};
|
|
7495
8263
|
const outboundProviders = {
|
|
7496
8264
|
xai: providers.xai
|
|
@@ -7556,6 +8324,46 @@ export function createGatewayServer(deps = {}) {
|
|
|
7556
8324
|
external: mem.external,
|
|
7557
8325
|
},
|
|
7558
8326
|
};
|
|
8327
|
+
const leastCostBlock = (() => {
|
|
8328
|
+
const lc = runtime.leastCost;
|
|
8329
|
+
if (!lc.enabled)
|
|
8330
|
+
return { enabled: false };
|
|
8331
|
+
const env = buildRouterEnv({
|
|
8332
|
+
performanceMetrics: runtime.performanceMetrics,
|
|
8333
|
+
limiterSnapshot: runtime.asyncJobManager.getLimiterSnapshot(),
|
|
8334
|
+
apiProviders: enabledApiProviders(runtime.providers),
|
|
8335
|
+
preferCatalogPrice: lc.preferCatalogPrice,
|
|
8336
|
+
});
|
|
8337
|
+
const providerHealth = env.providers().map(provider => ({
|
|
8338
|
+
provider,
|
|
8339
|
+
telemetryTier: telemetryTierFor(provider),
|
|
8340
|
+
authed: env.isAuthed(provider),
|
|
8341
|
+
breakerState: env.breakerState(provider),
|
|
8342
|
+
atCapacity: env.atCapacity(provider),
|
|
8343
|
+
candidates: env.models(provider).map(model => {
|
|
8344
|
+
const mc = env.modelCost(provider, model);
|
|
8345
|
+
const family = modelIdToFamily(model);
|
|
8346
|
+
const tier = lc.tiers[`${provider}:${family}`];
|
|
8347
|
+
return {
|
|
8348
|
+
model,
|
|
8349
|
+
family: mc.family,
|
|
8350
|
+
priced: mc.source !== "unknown",
|
|
8351
|
+
priceSource: mc.source,
|
|
8352
|
+
tier: tier ?? null,
|
|
8353
|
+
};
|
|
8354
|
+
}),
|
|
8355
|
+
}));
|
|
8356
|
+
return {
|
|
8357
|
+
enabled: true,
|
|
8358
|
+
minTier: lc.minTier,
|
|
8359
|
+
maxCostUsd: lc.maxCostUsd,
|
|
8360
|
+
priorsScope: lc.priorsScope,
|
|
8361
|
+
preferCatalogPrice: lc.preferCatalogPrice,
|
|
8362
|
+
allowUnpriced: lc.allowUnpriced,
|
|
8363
|
+
maxReroutes: lc.maxReroutes,
|
|
8364
|
+
providers: providerHealth,
|
|
8365
|
+
};
|
|
8366
|
+
})();
|
|
7559
8367
|
return {
|
|
7560
8368
|
content: [
|
|
7561
8369
|
{
|
|
@@ -7566,6 +8374,7 @@ export function createGatewayServer(deps = {}) {
|
|
|
7566
8374
|
backpressure,
|
|
7567
8375
|
persistence: persistenceBlock,
|
|
7568
8376
|
outboundProviders,
|
|
8377
|
+
leastCost: leastCostBlock,
|
|
7569
8378
|
}, null, 2),
|
|
7570
8379
|
},
|
|
7571
8380
|
],
|
|
@@ -8234,7 +9043,7 @@ async function initializeSessionManager() {
|
|
|
8234
9043
|
});
|
|
8235
9044
|
logger.info("File-based session manager initialized");
|
|
8236
9045
|
}
|
|
8237
|
-
resourceProvider = new ResourceProvider(sessionManager, performanceMetrics, getFlightRecorder(logger), getCacheAwarenessConfig(logger), getProvidersConfig(logger), undefined, getAcpConfig(logger));
|
|
9046
|
+
resourceProvider = new ResourceProvider(sessionManager, performanceMetrics, getFlightRecorder(logger), getCacheAwarenessConfig(logger), getProvidersConfig(logger), undefined, getAcpConfig(logger), getLeastCostConfig(logger));
|
|
8238
9047
|
}
|
|
8239
9048
|
function registerHealthResource(server) {
|
|
8240
9049
|
if (db) {
|
|
@@ -8261,20 +9070,26 @@ function registerHealthResource(server) {
|
|
|
8261
9070
|
description: "Async job health (CPU, memory, zombie detection)",
|
|
8262
9071
|
mimeType: "application/json",
|
|
8263
9072
|
}, async (uri) => {
|
|
8264
|
-
const
|
|
9073
|
+
const manager = getAsyncJobManager();
|
|
9074
|
+
const health = manager.getJobHealth();
|
|
8265
9075
|
return {
|
|
8266
9076
|
contents: [
|
|
8267
9077
|
{
|
|
8268
9078
|
uri: uri.href,
|
|
8269
9079
|
mimeType: "application/json",
|
|
8270
|
-
text: JSON.stringify(health, null, 2),
|
|
9080
|
+
text: JSON.stringify({ ...health, durableAdmission: manager.getDurableAdmissionHealth() }, null, 2),
|
|
8271
9081
|
},
|
|
8272
9082
|
],
|
|
8273
9083
|
};
|
|
8274
9084
|
});
|
|
8275
9085
|
logger.info("Process health resource registered");
|
|
8276
9086
|
}
|
|
8277
|
-
|
|
9087
|
+
let shutdownPromise = null;
|
|
9088
|
+
function shutdown(signal, exitCode = 0) {
|
|
9089
|
+
shutdownPromise ??= performShutdown(signal, exitCode);
|
|
9090
|
+
return shutdownPromise;
|
|
9091
|
+
}
|
|
9092
|
+
async function performShutdown(signal, exitCode) {
|
|
8278
9093
|
logger.info(`Received ${signal}, shutting down gracefully...`);
|
|
8279
9094
|
try {
|
|
8280
9095
|
if (asyncJobManager) {
|
|
@@ -8298,6 +9113,12 @@ async function shutdown(signal) {
|
|
|
8298
9113
|
logger.info("MCP server closed");
|
|
8299
9114
|
activeServer = null;
|
|
8300
9115
|
}
|
|
9116
|
+
if (jobStore) {
|
|
9117
|
+
jobStore.close();
|
|
9118
|
+
logger.info("Durable job store closed");
|
|
9119
|
+
jobStore = null;
|
|
9120
|
+
jobStoreInitialized = false;
|
|
9121
|
+
}
|
|
8301
9122
|
if (db) {
|
|
8302
9123
|
await db.disconnect();
|
|
8303
9124
|
logger.info("Database connections closed");
|
|
@@ -8306,7 +9127,7 @@ async function shutdown(signal) {
|
|
|
8306
9127
|
flightRecorder.close();
|
|
8307
9128
|
logger.info("Flight recorder closed");
|
|
8308
9129
|
}
|
|
8309
|
-
process.exit(
|
|
9130
|
+
process.exit(exitCode);
|
|
8310
9131
|
}
|
|
8311
9132
|
catch (error) {
|
|
8312
9133
|
logger.error("Error during shutdown:", error);
|
|
@@ -8700,15 +9521,23 @@ async function main() {
|
|
|
8700
9521
|
logger.info(`Starting llm-cli-gateway MCP server with ${transportMode} transport`);
|
|
8701
9522
|
await initializeSessionManager();
|
|
8702
9523
|
void warmProviderCapabilities({ logger }).catch(() => undefined);
|
|
9524
|
+
const persistence = getPersistenceConfig(logger);
|
|
9525
|
+
const runtimeAsyncJobManager = getAsyncJobManager(logger);
|
|
9526
|
+
if (persistence.backend !== "none" &&
|
|
9527
|
+
persistence.asyncJobsEnabled &&
|
|
9528
|
+
!runtimeAsyncJobManager.hasStore()) {
|
|
9529
|
+
throw new Error(`Configured ${persistence.backend} job store could not be opened; refusing to start without durable async persistence`);
|
|
9530
|
+
}
|
|
8703
9531
|
const serverDeps = {
|
|
8704
9532
|
sessionManager,
|
|
8705
9533
|
resourceProvider,
|
|
8706
9534
|
db,
|
|
8707
9535
|
performanceMetrics,
|
|
8708
|
-
asyncJobManager:
|
|
9536
|
+
asyncJobManager: runtimeAsyncJobManager,
|
|
8709
9537
|
approvalManager: getApprovalManager(logger),
|
|
8710
9538
|
flightRecorder: getFlightRecorder(logger),
|
|
8711
9539
|
logger,
|
|
9540
|
+
persistence,
|
|
8712
9541
|
};
|
|
8713
9542
|
if (transportMode === "http") {
|
|
8714
9543
|
activeHttpGateway = await startHttpGateway({
|
|
@@ -8725,6 +9554,12 @@ async function main() {
|
|
|
8725
9554
|
activeServer = createGatewayServer({
|
|
8726
9555
|
...serverDeps,
|
|
8727
9556
|
});
|
|
9557
|
+
activeServer.server.onclose = () => {
|
|
9558
|
+
void shutdown("stdio MCP connection closed");
|
|
9559
|
+
};
|
|
9560
|
+
process.stdin.once("end", () => {
|
|
9561
|
+
void shutdown("stdio stdin EOF");
|
|
9562
|
+
});
|
|
8728
9563
|
registerHealthResource(activeServer);
|
|
8729
9564
|
const transport = new StdioServerTransport();
|
|
8730
9565
|
await activeServer.connect(transport);
|
|
@@ -8734,6 +9569,6 @@ const __entryUrl = entrypointFileURL(process.argv[1]);
|
|
|
8734
9569
|
if (__entryUrl === import.meta.url) {
|
|
8735
9570
|
main().catch(error => {
|
|
8736
9571
|
logger.error("Fatal server error:", error);
|
|
8737
|
-
|
|
9572
|
+
void shutdown("fatal startup error", 1);
|
|
8738
9573
|
});
|
|
8739
9574
|
}
|