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/doctor.js
CHANGED
|
@@ -7,7 +7,11 @@ import { createEndpointExposureReport, redactDiagnosticUrl, } from "./endpoint-e
|
|
|
7
7
|
import { getApiProviderStatus, listProviderRuntimeStatuses, } from "./provider-status.js";
|
|
8
8
|
import { getApiProviderLoginGuidance, } from "./provider-login-guidance.js";
|
|
9
9
|
import { CLAUDE_MCP_SERVER_NAMES } from "./claude-mcp-config.js";
|
|
10
|
-
import { diagnoseRemoteOAuthConfig, enabledApiProviders, loadCacheAwarenessConfig, loadProvidersConfig, } from "./config.js";
|
|
10
|
+
import { defaultLeastCostConfig, diagnoseRemoteOAuthConfig, enabledApiProviders, loadCacheAwarenessConfig, loadLeastCostConfig, loadProvidersConfig, } from "./config.js";
|
|
11
|
+
import { telemetryTierFor } from "./lcr-telemetry.js";
|
|
12
|
+
import { API_CATALOG_AS_OF, PRICING_AS_OF, getModelCost, modelIdToFamily } from "./pricing.js";
|
|
13
|
+
import { computeLcrPriorsFromDb } from "./lcr-priors.js";
|
|
14
|
+
import { getCliInfo } from "./model-registry.js";
|
|
11
15
|
import { loadWorkspaceRegistry, remoteSafeWorkspaceSummary, } from "./workspace-registry.js";
|
|
12
16
|
import { buildRemoteConnectorUrls, resolveConfiguredRemoteOrigin } from "./remote-url.js";
|
|
13
17
|
import { computeGlobalCacheStats } from "./cache-stats.js";
|
|
@@ -58,7 +62,7 @@ export function checkVibeSessionLogging(home = homedir()) {
|
|
|
58
62
|
function parseVibeSessionLoggingEnabled(text) {
|
|
59
63
|
const lines = text.split(/\r?\n/);
|
|
60
64
|
let inSection = false;
|
|
61
|
-
for (
|
|
65
|
+
for (const raw of lines) {
|
|
62
66
|
const line = raw.replace(/#.*$/, "").trim();
|
|
63
67
|
if (!line)
|
|
64
68
|
continue;
|
|
@@ -426,6 +430,70 @@ function buildCacheAwarenessReport(opts) {
|
|
|
426
430
|
per_cli: perCli,
|
|
427
431
|
};
|
|
428
432
|
}
|
|
433
|
+
const MS_PER_DAY = 86_400_000;
|
|
434
|
+
function computePricingStaleDays(runIso) {
|
|
435
|
+
const run = Date.parse(runIso);
|
|
436
|
+
const table = Date.parse(PRICING_AS_OF);
|
|
437
|
+
const catalog = Date.parse(API_CATALOG_AS_OF);
|
|
438
|
+
const newer = Math.max(table, catalog);
|
|
439
|
+
if (!Number.isFinite(run) || !Number.isFinite(newer))
|
|
440
|
+
return 0;
|
|
441
|
+
const days = Math.floor((run - newer) / MS_PER_DAY);
|
|
442
|
+
return days > 0 ? days : 0;
|
|
443
|
+
}
|
|
444
|
+
function buildLeastCostReport(cfg, generatedAt, flightRecorder) {
|
|
445
|
+
const pricing = {
|
|
446
|
+
tableAsOf: PRICING_AS_OF,
|
|
447
|
+
apiCatalogAsOf: API_CATALOG_AS_OF,
|
|
448
|
+
staleDays: computePricingStaleDays(generatedAt),
|
|
449
|
+
};
|
|
450
|
+
if (!cfg.enabled) {
|
|
451
|
+
return { enabled: false, pricing, providers: [], untieredModels: [], calibrationQuality: [] };
|
|
452
|
+
}
|
|
453
|
+
const tierMap = new Map(Object.entries(cfg.tiers));
|
|
454
|
+
const cliInfo = getCliInfo();
|
|
455
|
+
const providers = [];
|
|
456
|
+
const untieredModels = [];
|
|
457
|
+
for (const provider of CLI_TYPES) {
|
|
458
|
+
const candidates = Object.keys(cliInfo[provider].models).map(model => {
|
|
459
|
+
const family = modelIdToFamily(model);
|
|
460
|
+
const cost = getModelCost(provider, model, { preferCatalog: cfg.preferCatalogPrice });
|
|
461
|
+
const tier = tierMap.get(`${provider}:${family}`) ?? null;
|
|
462
|
+
if (tier === null) {
|
|
463
|
+
untieredModels.push(`${provider}:${model}`);
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
model,
|
|
467
|
+
family,
|
|
468
|
+
priced: cost.source !== "unknown",
|
|
469
|
+
priceSource: cost.source,
|
|
470
|
+
tier,
|
|
471
|
+
};
|
|
472
|
+
});
|
|
473
|
+
providers.push({
|
|
474
|
+
provider,
|
|
475
|
+
telemetryTier: telemetryTierFor(provider),
|
|
476
|
+
candidates,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
const calibrationQuality = [];
|
|
480
|
+
if (flightRecorder && cfg.priorsScope !== "off") {
|
|
481
|
+
try {
|
|
482
|
+
const priors = computeLcrPriorsFromDb(flightRecorder, { priorsScope: cfg.priorsScope });
|
|
483
|
+
for (const [bucket, entry] of priors.calibration) {
|
|
484
|
+
calibrationQuality.push({
|
|
485
|
+
bucket,
|
|
486
|
+
k: entry.k,
|
|
487
|
+
samples: entry.samples,
|
|
488
|
+
confidence: entry.confidence,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
catch {
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
return { enabled: true, pricing, providers, untieredModels, calibrationQuality };
|
|
496
|
+
}
|
|
429
497
|
function buildProviderCapabilitySummary(providerStatuses) {
|
|
430
498
|
const capabilities = getProviderToolCapabilities({
|
|
431
499
|
includeSkills: true,
|
|
@@ -502,10 +570,12 @@ export function createDoctorReport(envOrOptions = process.env) {
|
|
|
502
570
|
if (probeReport) {
|
|
503
571
|
upstream.probe_report = probeReport;
|
|
504
572
|
}
|
|
573
|
+
const generatedAt = new Date().toISOString();
|
|
574
|
+
const leastCostConfig = opts.leastCost ?? defaultLeastCostConfig();
|
|
505
575
|
const report = {
|
|
506
576
|
schema_version: "1.0",
|
|
507
577
|
ok: true,
|
|
508
|
-
generated_at:
|
|
578
|
+
generated_at: generatedAt,
|
|
509
579
|
system: {
|
|
510
580
|
os: platform(),
|
|
511
581
|
arch: arch(),
|
|
@@ -564,6 +634,7 @@ export function createDoctorReport(envOrOptions = process.env) {
|
|
|
564
634
|
client_config: clientConfigStatus(),
|
|
565
635
|
cache_awareness: buildCacheAwarenessReport(opts),
|
|
566
636
|
provider_capabilities: buildProviderCapabilitySummary(providerStatuses),
|
|
637
|
+
least_cost: buildLeastCostReport(leastCostConfig, generatedAt, opts.flightRecorder),
|
|
567
638
|
upstream,
|
|
568
639
|
next_actions: [],
|
|
569
640
|
};
|
|
@@ -613,11 +684,17 @@ export async function printDoctorJson(opts = {}) {
|
|
|
613
684
|
let cacheAwareness;
|
|
614
685
|
let flightRecorder;
|
|
615
686
|
let providersConfig;
|
|
687
|
+
let leastCost;
|
|
616
688
|
try {
|
|
617
689
|
cacheAwareness = loadCacheAwarenessConfig();
|
|
618
690
|
}
|
|
619
691
|
catch {
|
|
620
692
|
}
|
|
693
|
+
try {
|
|
694
|
+
leastCost = loadLeastCostConfig();
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
}
|
|
621
698
|
try {
|
|
622
699
|
const dbPath = resolveFlightRecorderDbPath();
|
|
623
700
|
if (dbPath)
|
|
@@ -644,6 +721,7 @@ export async function printDoctorJson(opts = {}) {
|
|
|
644
721
|
probeUpstream: opts.probeUpstream,
|
|
645
722
|
providersConfig,
|
|
646
723
|
apiReachability,
|
|
724
|
+
leastCost,
|
|
647
725
|
});
|
|
648
726
|
process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
|
|
649
727
|
if (flightRecorder) {
|
|
@@ -685,6 +763,10 @@ function isCreateDoctorReportOptions(value) {
|
|
|
685
763
|
const candidate = value.flightRecorder;
|
|
686
764
|
return candidate === undefined || typeof candidate === "object";
|
|
687
765
|
}
|
|
766
|
+
if (Object.prototype.hasOwnProperty.call(value, "leastCost")) {
|
|
767
|
+
const candidate = value.leastCost;
|
|
768
|
+
return candidate === undefined || typeof candidate === "object";
|
|
769
|
+
}
|
|
688
770
|
if (Object.prototype.hasOwnProperty.call(value, "env")) {
|
|
689
771
|
const candidate = value.env;
|
|
690
772
|
return candidate === undefined || typeof candidate === "object";
|
package/dist/executor.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { ChildProcess, type SpawnOptions } from "child_process";
|
|
2
|
+
import { CircuitBreakerState } from "./retry.js";
|
|
2
3
|
import type { Logger } from "./logger.js";
|
|
4
|
+
import type { CliType } from "./provider-types.js";
|
|
3
5
|
export interface ExecuteOptions {
|
|
4
6
|
timeout?: number;
|
|
5
7
|
idleTimeout?: number;
|
|
@@ -14,6 +16,9 @@ export interface ExecuteResult {
|
|
|
14
16
|
code: number;
|
|
15
17
|
}
|
|
16
18
|
export declare function providerCommandName(command: string): string;
|
|
19
|
+
export declare function cliBreakerState(cli: CliType): CircuitBreakerState;
|
|
20
|
+
export declare function resetCliBreakersForTest(): void;
|
|
21
|
+
export declare function primeCliBreakerStateForTest(command: string, state: CircuitBreakerState): void;
|
|
17
22
|
export declare function buildExtendedPath(env?: NodeJS.ProcessEnv, home?: string, nodePath?: string, platform?: NodeJS.Platform): string;
|
|
18
23
|
export declare function getExtendedPath(): string;
|
|
19
24
|
export declare function envWithExtendedPath(baseEnv?: NodeJS.ProcessEnv, extendedPath?: string, platform?: NodeJS.Platform): NodeJS.ProcessEnv;
|
|
@@ -35,5 +40,6 @@ export declare function spawnCliProcess(command: string, args: string[], options
|
|
|
35
40
|
cwd?: string;
|
|
36
41
|
env: NodeJS.ProcessEnv;
|
|
37
42
|
stdio: SpawnOptions["stdio"];
|
|
43
|
+
logger?: Logger;
|
|
38
44
|
}): ChildProcess;
|
|
39
45
|
export declare function executeCli(command: string, args: string[], options?: ExecuteOptions): Promise<ExecuteResult>;
|
package/dist/executor.js
CHANGED
|
@@ -2,7 +2,8 @@ import { spawn, spawnSync } from "child_process";
|
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { delimiter, join, dirname, extname, win32 } from "path";
|
|
4
4
|
import { readdirSync, existsSync } from "fs";
|
|
5
|
-
import { createCircuitBreaker, withRetry } from "./retry.js";
|
|
5
|
+
import { createCircuitBreaker, withRetry, CircuitBreakerState } from "./retry.js";
|
|
6
|
+
import { applySpawnEnvIsolation } from "./spawn-env-isolation.js";
|
|
6
7
|
export function providerCommandName(command) {
|
|
7
8
|
if (command === "gemini")
|
|
8
9
|
return "agy";
|
|
@@ -24,6 +25,16 @@ function getCircuitBreaker(command) {
|
|
|
24
25
|
circuitBreakers.set(command, circuitBreaker);
|
|
25
26
|
return circuitBreaker;
|
|
26
27
|
}
|
|
28
|
+
export function cliBreakerState(cli) {
|
|
29
|
+
const command = providerCommandName(cli);
|
|
30
|
+
return circuitBreakers.get(command)?.state ?? CircuitBreakerState.CLOSED;
|
|
31
|
+
}
|
|
32
|
+
export function resetCliBreakersForTest() {
|
|
33
|
+
circuitBreakers.clear();
|
|
34
|
+
}
|
|
35
|
+
export function primeCliBreakerStateForTest(command, state) {
|
|
36
|
+
getCircuitBreaker(command).state = state;
|
|
37
|
+
}
|
|
27
38
|
function getNvmPath() {
|
|
28
39
|
if (cachedNvmPath !== undefined) {
|
|
29
40
|
return cachedNvmPath;
|
|
@@ -309,9 +320,10 @@ function killWindowsProcessTree(pid) {
|
|
|
309
320
|
return result.status === 0;
|
|
310
321
|
}
|
|
311
322
|
export function spawnCliProcess(command, args, options) {
|
|
323
|
+
const env = applySpawnEnvIsolation(options.env, options.logger);
|
|
312
324
|
const detached = shouldDetachProviderProcess();
|
|
313
325
|
const resolved = resolveCommandForSpawn(command, args, {
|
|
314
|
-
envPath: pathValueFromEnv(
|
|
326
|
+
envPath: pathValueFromEnv(env, process.platform),
|
|
315
327
|
});
|
|
316
328
|
const proc = spawn(resolved.command, resolved.args, {
|
|
317
329
|
cwd: options.cwd,
|
|
@@ -319,7 +331,7 @@ export function spawnCliProcess(command, args, options) {
|
|
|
319
331
|
windowsHide: true,
|
|
320
332
|
windowsVerbatimArguments: resolved.windowsVerbatimArguments,
|
|
321
333
|
stdio: options.stdio,
|
|
322
|
-
env
|
|
334
|
+
env,
|
|
323
335
|
});
|
|
324
336
|
if (proc.pid)
|
|
325
337
|
registerProcessGroup(proc.pid);
|
|
@@ -337,6 +349,7 @@ export async function executeCli(command, args, options = {}) {
|
|
|
337
349
|
cwd,
|
|
338
350
|
stdio,
|
|
339
351
|
env: { ...baseEnv, ...(extraEnv ?? {}) },
|
|
352
|
+
logger: options.logger,
|
|
340
353
|
});
|
|
341
354
|
if (stdin !== undefined && proc.stdin) {
|
|
342
355
|
proc.stdin.write(stdin);
|
|
@@ -23,6 +23,7 @@ export interface FlightLogResult {
|
|
|
23
23
|
retryCount: number;
|
|
24
24
|
circuitBreakerState: string;
|
|
25
25
|
costUsd?: number;
|
|
26
|
+
costBasis?: string;
|
|
26
27
|
approvalDecision?: string;
|
|
27
28
|
optimizationApplied: boolean;
|
|
28
29
|
thinkingBlocks?: string[];
|
|
@@ -37,6 +38,20 @@ interface LoggerLike {
|
|
|
37
38
|
info: (message: string, ...args: any[]) => void;
|
|
38
39
|
error: (message: string, ...args: any[]) => void;
|
|
39
40
|
}
|
|
41
|
+
export interface CompressionTelemetry {
|
|
42
|
+
route: string;
|
|
43
|
+
transforms: string[];
|
|
44
|
+
originalChars: number;
|
|
45
|
+
compressedChars: number;
|
|
46
|
+
estimatedTokensSaved: number;
|
|
47
|
+
}
|
|
48
|
+
export interface RoutingRecord {
|
|
49
|
+
estCostUsd?: number | null;
|
|
50
|
+
estConfidence?: string | null;
|
|
51
|
+
reason?: string | null;
|
|
52
|
+
considered?: number | null;
|
|
53
|
+
reroutes?: number | null;
|
|
54
|
+
}
|
|
40
55
|
export declare function resolveFlightRecorderDbPath(): string | null;
|
|
41
56
|
export declare class FlightRecorder {
|
|
42
57
|
private db;
|
|
@@ -51,6 +66,8 @@ export declare class FlightRecorder {
|
|
|
51
66
|
});
|
|
52
67
|
logStart(entry: FlightLogStart): void;
|
|
53
68
|
logComplete(correlationId: string, result: FlightLogResult): void;
|
|
69
|
+
recordCompressionTelemetry(correlationId: string, telemetry: CompressionTelemetry): void;
|
|
70
|
+
recordRouting(correlationId: string, routing: RoutingRecord): void;
|
|
54
71
|
private redactStart;
|
|
55
72
|
private redactResult;
|
|
56
73
|
queryRequests<T = Record<string, unknown>>(sql: string, ...params: unknown[]): T[];
|
|
@@ -60,6 +77,8 @@ export declare class FlightRecorder {
|
|
|
60
77
|
export declare class NoopFlightRecorder {
|
|
61
78
|
logStart(_entry: FlightLogStart): void;
|
|
62
79
|
logComplete(_correlationId: string, _result: FlightLogResult): void;
|
|
80
|
+
recordCompressionTelemetry(_correlationId: string, _telemetry: CompressionTelemetry): void;
|
|
81
|
+
recordRouting(_correlationId: string, _routing: RoutingRecord): void;
|
|
63
82
|
queryRequests<T = Record<string, unknown>>(_sql: string, ..._params: unknown[]): T[];
|
|
64
83
|
flush(): void;
|
|
65
84
|
close(): void;
|
package/dist/flight-recorder.js
CHANGED
|
@@ -64,6 +64,54 @@ function ensureMetadataProviderSessionColumns(db) {
|
|
|
64
64
|
db.exec("ALTER TABLE gateway_metadata ADD COLUMN stop_reason TEXT");
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
function ensureCompressionColumns(db) {
|
|
68
|
+
const rows = db.prepare("PRAGMA table_info(gateway_metadata)").all();
|
|
69
|
+
const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
|
|
70
|
+
if (!names.has("compression_route")) {
|
|
71
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_route TEXT");
|
|
72
|
+
}
|
|
73
|
+
if (!names.has("compression_transforms")) {
|
|
74
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_transforms TEXT");
|
|
75
|
+
}
|
|
76
|
+
if (!names.has("compression_original_chars")) {
|
|
77
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_original_chars INTEGER");
|
|
78
|
+
}
|
|
79
|
+
if (!names.has("compression_compressed_chars")) {
|
|
80
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_compressed_chars INTEGER");
|
|
81
|
+
}
|
|
82
|
+
if (!names.has("compression_tokens_saved_est")) {
|
|
83
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_tokens_saved_est INTEGER");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function ensureRequestsCostBasisColumn(db) {
|
|
87
|
+
const rows = db.prepare("PRAGMA table_info(requests)").all();
|
|
88
|
+
const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
|
|
89
|
+
if (!names.has("cost_basis")) {
|
|
90
|
+
db.exec("ALTER TABLE requests ADD COLUMN cost_basis TEXT");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function ensureMetadataRoutingColumns(db) {
|
|
94
|
+
const rows = db.prepare("PRAGMA table_info(gateway_metadata)").all();
|
|
95
|
+
const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
|
|
96
|
+
if (!names.has("routed")) {
|
|
97
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN routed INTEGER");
|
|
98
|
+
}
|
|
99
|
+
if (!names.has("route_est_cost_usd")) {
|
|
100
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN route_est_cost_usd REAL");
|
|
101
|
+
}
|
|
102
|
+
if (!names.has("route_est_confidence")) {
|
|
103
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN route_est_confidence TEXT");
|
|
104
|
+
}
|
|
105
|
+
if (!names.has("route_reason")) {
|
|
106
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN route_reason TEXT");
|
|
107
|
+
}
|
|
108
|
+
if (!names.has("route_considered")) {
|
|
109
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN route_considered INTEGER");
|
|
110
|
+
}
|
|
111
|
+
if (!names.has("route_reroutes")) {
|
|
112
|
+
db.exec("ALTER TABLE gateway_metadata ADD COLUMN route_reroutes INTEGER");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
67
115
|
export function resolveFlightRecorderDbPath() {
|
|
68
116
|
const configured = process.env.LLM_GATEWAY_LOGS_DB;
|
|
69
117
|
if (configured !== undefined) {
|
|
@@ -142,7 +190,8 @@ export class FlightRecorder {
|
|
|
142
190
|
output_tokens INTEGER,
|
|
143
191
|
cache_read_tokens INTEGER,
|
|
144
192
|
cache_creation_tokens INTEGER,
|
|
145
|
-
owner_principal TEXT
|
|
193
|
+
owner_principal TEXT,
|
|
194
|
+
cost_basis TEXT
|
|
146
195
|
);
|
|
147
196
|
|
|
148
197
|
CREATE TABLE IF NOT EXISTS gateway_metadata (
|
|
@@ -159,6 +208,12 @@ export class FlightRecorder {
|
|
|
159
208
|
async_job_id TEXT,
|
|
160
209
|
provider_session_id TEXT,
|
|
161
210
|
stop_reason TEXT,
|
|
211
|
+
routed INTEGER,
|
|
212
|
+
route_est_cost_usd REAL,
|
|
213
|
+
route_est_confidence TEXT,
|
|
214
|
+
route_reason TEXT,
|
|
215
|
+
route_considered INTEGER,
|
|
216
|
+
route_reroutes INTEGER,
|
|
162
217
|
status TEXT NOT NULL DEFAULT 'started'
|
|
163
218
|
);
|
|
164
219
|
|
|
@@ -196,6 +251,18 @@ export class FlightRecorder {
|
|
|
196
251
|
this.db
|
|
197
252
|
.prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(7, ?)")
|
|
198
253
|
.run(new Date().toISOString());
|
|
254
|
+
ensureCompressionColumns(this.db);
|
|
255
|
+
this.db
|
|
256
|
+
.prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(8, ?)")
|
|
257
|
+
.run(new Date().toISOString());
|
|
258
|
+
ensureRequestsCostBasisColumn(this.db);
|
|
259
|
+
this.db
|
|
260
|
+
.prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(9, ?)")
|
|
261
|
+
.run(new Date().toISOString());
|
|
262
|
+
ensureMetadataRoutingColumns(this.db);
|
|
263
|
+
this.db
|
|
264
|
+
.prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(10, ?)")
|
|
265
|
+
.run(new Date().toISOString());
|
|
199
266
|
if (process.platform !== "win32") {
|
|
200
267
|
try {
|
|
201
268
|
chmodSync(dbPath, 0o600);
|
|
@@ -242,7 +309,8 @@ export class FlightRecorder {
|
|
|
242
309
|
input_tokens = @input_tokens,
|
|
243
310
|
output_tokens = @output_tokens,
|
|
244
311
|
cache_read_tokens = @cache_read_tokens,
|
|
245
|
-
cache_creation_tokens = @cache_creation_tokens
|
|
312
|
+
cache_creation_tokens = @cache_creation_tokens,
|
|
313
|
+
cost_basis = @cost_basis
|
|
246
314
|
WHERE id = @id
|
|
247
315
|
`);
|
|
248
316
|
const updateMetadata = this.db.prepare(`
|
|
@@ -273,6 +341,7 @@ export class FlightRecorder {
|
|
|
273
341
|
output_tokens: result.outputTokens ?? null,
|
|
274
342
|
cache_read_tokens: result.cacheReadTokens ?? null,
|
|
275
343
|
cache_creation_tokens: result.cacheCreationTokens ?? null,
|
|
344
|
+
cost_basis: result.costBasis ?? null,
|
|
276
345
|
});
|
|
277
346
|
updateMetadata.run({
|
|
278
347
|
id: correlationId,
|
|
@@ -297,6 +366,43 @@ export class FlightRecorder {
|
|
|
297
366
|
logComplete(correlationId, result) {
|
|
298
367
|
this.updateCompleteTxn(correlationId, this.redactEnabled ? this.redactResult(result) : result);
|
|
299
368
|
}
|
|
369
|
+
recordCompressionTelemetry(correlationId, telemetry) {
|
|
370
|
+
this.db
|
|
371
|
+
.prepare(`UPDATE gateway_metadata
|
|
372
|
+
SET compression_route = @route,
|
|
373
|
+
compression_transforms = @transforms,
|
|
374
|
+
compression_original_chars = @original_chars,
|
|
375
|
+
compression_compressed_chars = @compressed_chars,
|
|
376
|
+
compression_tokens_saved_est = @tokens_saved_est
|
|
377
|
+
WHERE request_id = @id AND compression_route IS NULL`)
|
|
378
|
+
.run({
|
|
379
|
+
id: correlationId,
|
|
380
|
+
route: telemetry.route,
|
|
381
|
+
transforms: telemetry.transforms.join(","),
|
|
382
|
+
original_chars: telemetry.originalChars,
|
|
383
|
+
compressed_chars: telemetry.compressedChars,
|
|
384
|
+
tokens_saved_est: telemetry.estimatedTokensSaved,
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
recordRouting(correlationId, routing) {
|
|
388
|
+
this.db
|
|
389
|
+
.prepare(`UPDATE gateway_metadata
|
|
390
|
+
SET routed = 1,
|
|
391
|
+
route_est_cost_usd = @est_cost_usd,
|
|
392
|
+
route_est_confidence = @est_confidence,
|
|
393
|
+
route_reason = @reason,
|
|
394
|
+
route_considered = @considered,
|
|
395
|
+
route_reroutes = @reroutes
|
|
396
|
+
WHERE request_id = @id`)
|
|
397
|
+
.run({
|
|
398
|
+
id: correlationId,
|
|
399
|
+
est_cost_usd: routing.estCostUsd ?? null,
|
|
400
|
+
est_confidence: routing.estConfidence ?? null,
|
|
401
|
+
reason: routing.reason ?? null,
|
|
402
|
+
considered: routing.considered ?? null,
|
|
403
|
+
reroutes: routing.reroutes ?? null,
|
|
404
|
+
});
|
|
405
|
+
}
|
|
300
406
|
redactStart(entry) {
|
|
301
407
|
return {
|
|
302
408
|
...entry,
|
|
@@ -330,6 +436,8 @@ export class FlightRecorder {
|
|
|
330
436
|
export class NoopFlightRecorder {
|
|
331
437
|
logStart(_entry) { }
|
|
332
438
|
logComplete(_correlationId, _result) { }
|
|
439
|
+
recordCompressionTelemetry(_correlationId, _telemetry) { }
|
|
440
|
+
recordRouting(_correlationId, _routing) { }
|
|
333
441
|
queryRequests(_sql, ..._params) {
|
|
334
442
|
return [];
|
|
335
443
|
}
|
package/dist/http-transport.js
CHANGED
|
@@ -19,12 +19,7 @@ async function readBody(req) {
|
|
|
19
19
|
const raw = await readCappedRawBody(req, maxHttpBodyBytes());
|
|
20
20
|
if (!raw)
|
|
21
21
|
return undefined;
|
|
22
|
-
|
|
23
|
-
return JSON.parse(raw);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
throw error;
|
|
27
|
-
}
|
|
22
|
+
return JSON.parse(raw);
|
|
28
23
|
}
|
|
29
24
|
function methodNotAllowed(res) {
|
|
30
25
|
res.writeHead(405, { allow: "GET, POST, DELETE", "content-type": "application/json" });
|
|
@@ -34,10 +29,6 @@ function jsonError(res, status, message) {
|
|
|
34
29
|
res.writeHead(status, { "content-type": "application/json" });
|
|
35
30
|
res.end(JSON.stringify({ error: message }));
|
|
36
31
|
}
|
|
37
|
-
function jsonResponse(res, status, body) {
|
|
38
|
-
res.writeHead(status, { "content-type": "application/json" });
|
|
39
|
-
res.end(JSON.stringify(body));
|
|
40
|
-
}
|
|
41
32
|
function parseNoAuthPaths(raw, protectedPath) {
|
|
42
33
|
const paths = new Set();
|
|
43
34
|
for (const value of (raw ?? "").split(/[,;\s]+/)) {
|
|
@@ -133,21 +124,21 @@ export async function startHttpGateway(options) {
|
|
|
133
124
|
}
|
|
134
125
|
async function createSession(releaseInitializeReservation) {
|
|
135
126
|
const gatewayServer = options.createGatewayServer(options.deps);
|
|
136
|
-
|
|
127
|
+
const onSessionInitialized = (sessionId) => {
|
|
128
|
+
const initializedAt = Date.now();
|
|
129
|
+
releaseInitializeReservation();
|
|
130
|
+
entry.sessionId = sessionId;
|
|
131
|
+
entry.createdAt = initializedAt;
|
|
132
|
+
entry.lastActivityAt = initializedAt;
|
|
133
|
+
entry.inFlight++;
|
|
134
|
+
sessions.set(sessionId, entry);
|
|
135
|
+
};
|
|
137
136
|
const transport = new StreamableHTTPServerTransport({
|
|
138
137
|
sessionIdGenerator: () => randomUUID(),
|
|
139
|
-
onsessioninitialized:
|
|
140
|
-
const now = Date.now();
|
|
141
|
-
releaseInitializeReservation();
|
|
142
|
-
entry.sessionId = sessionId;
|
|
143
|
-
entry.createdAt = now;
|
|
144
|
-
entry.lastActivityAt = now;
|
|
145
|
-
entry.inFlight++;
|
|
146
|
-
sessions.set(sessionId, entry);
|
|
147
|
-
},
|
|
138
|
+
onsessioninitialized: onSessionInitialized,
|
|
148
139
|
});
|
|
149
140
|
const now = Date.now();
|
|
150
|
-
entry = {
|
|
141
|
+
const entry = {
|
|
151
142
|
server: gatewayServer,
|
|
152
143
|
transport,
|
|
153
144
|
createdAt: now,
|
|
@@ -209,6 +200,9 @@ export async function startHttpGateway(options) {
|
|
|
209
200
|
if (manager) {
|
|
210
201
|
const limiter = manager.getLimiterSnapshot();
|
|
211
202
|
const limits = manager.getConfiguredLimits();
|
|
203
|
+
const durableAdmission = manager.getDurableAdmissionHealth();
|
|
204
|
+
const persistence = options.deps?.persistence;
|
|
205
|
+
const durablePersistenceConfigured = persistence !== undefined && persistence.backend !== "none" && persistence.asyncJobsEnabled;
|
|
212
206
|
payload.jobs = {
|
|
213
207
|
running: limiter.running,
|
|
214
208
|
queued: limiter.queued,
|
|
@@ -222,7 +216,11 @@ export async function startHttpGateway(options) {
|
|
|
222
216
|
saturated: limiter.saturated,
|
|
223
217
|
completedJobMemoryTtlMs: limits.completedJobMemoryTtlMs,
|
|
224
218
|
maxJobOutputBytes: limits.maxJobOutputBytes,
|
|
219
|
+
durableAdmission,
|
|
225
220
|
};
|
|
221
|
+
payload.ready = durableAdmission.storeAttached
|
|
222
|
+
? durableAdmission.admitting
|
|
223
|
+
: !durablePersistenceConfigured;
|
|
226
224
|
}
|
|
227
225
|
return payload;
|
|
228
226
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -4,7 +4,9 @@ import { z } from "zod/v3";
|
|
|
4
4
|
import { ISessionManager, type CliType, type ProviderType } from "./session-manager.js";
|
|
5
5
|
import { ResourceProvider } from "./resources.js";
|
|
6
6
|
import { PerformanceMetrics } from "./metrics.js";
|
|
7
|
-
import { type
|
|
7
|
+
import { type CompressResult } from "./compressor/index.js";
|
|
8
|
+
import { type PersistenceConfig, type CacheAwarenessConfig, type CompressionConfig, type ProvidersConfig, type ApiProviderRuntime, type AcpConfig, type AdminConfig, type LeastCostConfig } from "./config.js";
|
|
9
|
+
import type { CostBasis } from "./least-cost-types.js";
|
|
8
10
|
import { DatabaseConnection } from "./db.js";
|
|
9
11
|
import { type DevinAcpAgentType } from "./provider-definitions.js";
|
|
10
12
|
import { AsyncJobManager } from "./async-job-manager.js";
|
|
@@ -38,6 +40,8 @@ type ExtendedToolResponse = {
|
|
|
38
40
|
};
|
|
39
41
|
reviewIntegrity?: ReviewIntegrityResult;
|
|
40
42
|
warnings?: WarningEntry[];
|
|
43
|
+
routing?: RouteResponseBlock;
|
|
44
|
+
compression?: CompressResult;
|
|
41
45
|
};
|
|
42
46
|
export declare const logger: {
|
|
43
47
|
info: (message: string, ...args: unknown[]) => void;
|
|
@@ -76,10 +80,12 @@ export interface GatewayServerDeps {
|
|
|
76
80
|
logger?: GatewayLogger;
|
|
77
81
|
persistence?: PersistenceConfig;
|
|
78
82
|
cacheAwareness?: CacheAwarenessConfig;
|
|
83
|
+
compression?: CompressionConfig;
|
|
79
84
|
providers?: ProvidersConfig;
|
|
80
85
|
acpConfig?: AcpConfig;
|
|
81
86
|
adminConfig?: AdminConfig;
|
|
82
87
|
workspaces?: WorkspaceRegistry;
|
|
88
|
+
leastCost?: LeastCostConfig;
|
|
83
89
|
}
|
|
84
90
|
export interface GatewayServerRuntime {
|
|
85
91
|
sessionManager: ISessionManager;
|
|
@@ -92,10 +98,12 @@ export interface GatewayServerRuntime {
|
|
|
92
98
|
logger: GatewayLogger;
|
|
93
99
|
persistence: PersistenceConfig;
|
|
94
100
|
cacheAwareness: CacheAwarenessConfig;
|
|
101
|
+
compression: CompressionConfig;
|
|
95
102
|
providers: ProvidersConfig;
|
|
96
103
|
acpConfig: AcpConfig;
|
|
97
104
|
adminConfig: AdminConfig;
|
|
98
105
|
workspaces: WorkspaceRegistry;
|
|
106
|
+
leastCost: LeastCostConfig;
|
|
99
107
|
}
|
|
100
108
|
export declare function resolveGatewayServerRuntime(deps?: GatewayServerDeps, options?: {
|
|
101
109
|
isolateState?: boolean;
|
|
@@ -169,6 +177,11 @@ export declare function extractUsageAndCost(cli: CliType, output: string, output
|
|
|
169
177
|
cacheCreationTokens?: number;
|
|
170
178
|
costUsd?: number;
|
|
171
179
|
};
|
|
180
|
+
export declare function resolveEffectiveCompression(compression: CompressionConfig, input: {
|
|
181
|
+
compressResponse?: boolean;
|
|
182
|
+
outputFormat?: string;
|
|
183
|
+
outputSchemaDeclared?: boolean;
|
|
184
|
+
}): boolean;
|
|
172
185
|
export declare function registerBaseResources(server: McpServer, runtime: GatewayServerRuntime): void;
|
|
173
186
|
interface CliRequestPrep {
|
|
174
187
|
corrId: string;
|
|
@@ -344,6 +357,7 @@ export declare function prepareGrokRequest(params: {
|
|
|
344
357
|
forkSession?: boolean;
|
|
345
358
|
jsonSchema?: string | Record<string, unknown>;
|
|
346
359
|
}, runtime?: GatewayServerRuntime): CliRequestPrep | ExtendedToolResponse;
|
|
360
|
+
export declare function resolveMistralAgentMode(approvalStrategy: "legacy" | "mcp_managed" | undefined, callerMode: MistralAgentMode | undefined): MistralAgentMode;
|
|
347
361
|
export declare function prepareMistralRequest(params: {
|
|
348
362
|
prompt?: string;
|
|
349
363
|
promptParts?: PromptParts;
|
|
@@ -374,7 +388,7 @@ export declare function buildMistralRetryPrep(params: Pick<MistralRequestParams,
|
|
|
374
388
|
env: Record<string, string>;
|
|
375
389
|
ignoredDisallowedTools: boolean;
|
|
376
390
|
};
|
|
377
|
-
export declare function buildCliResponse(cli: CliType, stdout: string, optimizeResponse: boolean, corrId: string, sessionId: string | undefined, prep: CliRequestPrep, durationMs: number, resumable?: boolean, outputFormat?: string, warnings?: WarningEntry[]): ExtendedToolResponse;
|
|
391
|
+
export declare function buildCliResponse(cli: CliType, stdout: string, optimizeResponse: boolean, corrId: string, sessionId: string | undefined, prep: CliRequestPrep, durationMs: number, resumable?: boolean, outputFormat?: string, warnings?: WarningEntry[], compressResponse?: boolean): ExtendedToolResponse;
|
|
378
392
|
export interface GrokApiRequestParams {
|
|
379
393
|
prompt?: string;
|
|
380
394
|
promptParts?: PromptParts;
|
|
@@ -427,6 +441,7 @@ export interface GeminiRequestParams {
|
|
|
427
441
|
correlationId?: string;
|
|
428
442
|
optimizePrompt: boolean;
|
|
429
443
|
optimizeResponse?: boolean;
|
|
444
|
+
compressResponse?: boolean;
|
|
430
445
|
idleTimeoutMs?: number;
|
|
431
446
|
forceRefresh?: boolean;
|
|
432
447
|
outputFormat?: "text" | "json" | "stream-json";
|
|
@@ -482,6 +497,7 @@ export interface GrokRequestParams {
|
|
|
482
497
|
correlationId?: string;
|
|
483
498
|
optimizePrompt: boolean;
|
|
484
499
|
optimizeResponse?: boolean;
|
|
500
|
+
compressResponse?: boolean;
|
|
485
501
|
idleTimeoutMs?: number;
|
|
486
502
|
forceRefresh?: boolean;
|
|
487
503
|
maxTurns?: number;
|
|
@@ -541,6 +557,7 @@ export interface DevinRequestParams {
|
|
|
541
557
|
correlationId?: string;
|
|
542
558
|
optimizePrompt: boolean;
|
|
543
559
|
optimizeResponse?: boolean;
|
|
560
|
+
compressResponse?: boolean;
|
|
544
561
|
idleTimeoutMs?: number;
|
|
545
562
|
forceRefresh?: boolean;
|
|
546
563
|
}
|
|
@@ -580,6 +597,7 @@ export interface CursorRequestParams {
|
|
|
580
597
|
correlationId?: string;
|
|
581
598
|
optimizePrompt: boolean;
|
|
582
599
|
optimizeResponse?: boolean;
|
|
600
|
+
compressResponse?: boolean;
|
|
583
601
|
idleTimeoutMs?: number;
|
|
584
602
|
forceRefresh?: boolean;
|
|
585
603
|
}
|
|
@@ -620,6 +638,7 @@ export interface MistralRequestParams {
|
|
|
620
638
|
correlationId?: string;
|
|
621
639
|
optimizePrompt: boolean;
|
|
622
640
|
optimizeResponse?: boolean;
|
|
641
|
+
compressResponse?: boolean;
|
|
623
642
|
idleTimeoutMs?: number;
|
|
624
643
|
forceRefresh?: boolean;
|
|
625
644
|
trust?: boolean;
|
|
@@ -653,6 +672,7 @@ export declare function handleCodexRequestAsync(deps: AsyncHandlerDeps, params:
|
|
|
653
672
|
createNewSession: boolean;
|
|
654
673
|
correlationId?: string;
|
|
655
674
|
optimizePrompt: boolean;
|
|
675
|
+
compressResponse?: boolean;
|
|
656
676
|
idleTimeoutMs?: number;
|
|
657
677
|
forceRefresh?: boolean;
|
|
658
678
|
outputFormat?: "text" | "json";
|
|
@@ -680,5 +700,41 @@ export declare function handleCodexRequestAsync(deps: AsyncHandlerDeps, params:
|
|
|
680
700
|
ref?: string;
|
|
681
701
|
};
|
|
682
702
|
}): Promise<ExtendedToolResponse>;
|
|
703
|
+
export interface RouteResponseBlock {
|
|
704
|
+
chosen: {
|
|
705
|
+
provider: string;
|
|
706
|
+
model: string;
|
|
707
|
+
} | null;
|
|
708
|
+
tier?: string;
|
|
709
|
+
estCostUsd?: number;
|
|
710
|
+
costBasis?: string;
|
|
711
|
+
confidence?: string;
|
|
712
|
+
nearTie?: boolean;
|
|
713
|
+
estInputTokens?: number;
|
|
714
|
+
estOutputTokens?: number;
|
|
715
|
+
priceAsOf?: string;
|
|
716
|
+
priceSource?: string;
|
|
717
|
+
consideredCount: number;
|
|
718
|
+
rejected: {
|
|
719
|
+
candidate: {
|
|
720
|
+
provider: string;
|
|
721
|
+
model: string;
|
|
722
|
+
};
|
|
723
|
+
reason: string;
|
|
724
|
+
}[];
|
|
725
|
+
reroutes: number;
|
|
726
|
+
error?: string;
|
|
727
|
+
}
|
|
728
|
+
export declare function isTransientRouteFailure(provider: string, response: ExtendedToolResponse): boolean;
|
|
729
|
+
export declare function deriveCostBasis(provider: string, resolvedModel: string, usage: {
|
|
730
|
+
inputTokens?: number;
|
|
731
|
+
outputTokens?: number;
|
|
732
|
+
cacheReadTokens?: number;
|
|
733
|
+
cacheCreationTokens?: number;
|
|
734
|
+
costUsd?: number;
|
|
735
|
+
}): {
|
|
736
|
+
costUsd?: number;
|
|
737
|
+
costBasis?: CostBasis;
|
|
738
|
+
};
|
|
683
739
|
export declare function createGatewayServer(deps?: GatewayServerDeps): McpServer;
|
|
684
740
|
export {};
|