llm-cli-gateway 2.16.0 → 2.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/least-cost-routing/SKILL.md +123 -0
- package/CHANGELOG.md +37 -0
- package/README.md +12 -12
- package/dist/acp/client.js +5 -0
- package/dist/acp/flight-redaction.d.ts +2 -0
- package/dist/acp/flight-redaction.js +2 -0
- package/dist/acp/runtime.d.ts +1 -0
- package/dist/acp/runtime.js +20 -0
- package/dist/acp/types.d.ts +52 -0
- package/dist/acp/types.js +8 -0
- package/dist/api-provider.d.ts +1 -0
- package/dist/api-provider.js +1 -0
- package/dist/async-job-manager.d.ts +16 -0
- package/dist/async-job-manager.js +70 -22
- package/dist/claude-mcp-config.js +1 -1
- package/dist/compressor/transforms/ansi.js +12 -2
- package/dist/config.d.ts +31 -0
- package/dist/config.js +142 -7
- package/dist/db.js +4 -4
- package/dist/doctor.d.ts +34 -1
- package/dist/doctor.js +85 -3
- package/dist/executor.d.ts +5 -0
- package/dist/executor.js +11 -1
- package/dist/flight-recorder.d.ts +10 -0
- package/dist/flight-recorder.js +68 -2
- package/dist/http-transport.js +19 -21
- package/dist/index.d.ts +42 -3
- package/dist/index.js +666 -41
- package/dist/job-store.d.ts +10 -2
- package/dist/job-store.js +154 -43
- package/dist/lcr-priors.d.ts +60 -0
- package/dist/lcr-priors.js +190 -0
- package/dist/lcr-router-env.d.ts +20 -0
- package/dist/lcr-router-env.js +133 -0
- package/dist/lcr-telemetry.d.ts +2 -0
- package/dist/lcr-telemetry.js +17 -0
- package/dist/least-cost-router.d.ts +86 -0
- package/dist/least-cost-router.js +296 -0
- package/dist/least-cost-types.d.ts +34 -0
- package/dist/least-cost-types.js +1 -0
- package/dist/migrate-sessions.js +1 -1
- package/dist/migrate.js +1 -1
- package/dist/model-registry.js +1 -1
- package/dist/postgres-job-store-worker.js +56 -13
- package/dist/pricing.d.ts +17 -0
- package/dist/pricing.js +167 -0
- package/dist/provider-admin-tools.js +12 -4
- package/dist/provider-definitions.d.ts +4 -0
- package/dist/provider-definitions.js +33 -5
- package/dist/provider-tool-capabilities.js +6 -9
- package/dist/request-helpers.d.ts +3 -4
- package/dist/request-helpers.js +9 -10
- package/dist/resources.d.ts +37 -2
- package/dist/resources.js +96 -1
- package/dist/retry.d.ts +1 -0
- package/dist/retry.js +1 -1
- package/dist/token-estimator.d.ts +6 -0
- package/dist/token-estimator.js +59 -0
- package/dist/upstream-contracts.d.ts +4 -1
- package/dist/upstream-contracts.js +255 -71
- package/dist/validation-receipt.js +1 -1
- package/dist/validation-tools.d.ts +6 -0
- package/dist/validation-tools.js +174 -54
- package/npm-shrinkwrap.json +2 -2
- package/package.json +14 -5
- package/setup/status.schema.json +68 -0
package/dist/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;
|
package/dist/executor.js
CHANGED
|
@@ -2,7 +2,7 @@ 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
6
|
import { applySpawnEnvIsolation } from "./spawn-env-isolation.js";
|
|
7
7
|
export function providerCommandName(command) {
|
|
8
8
|
if (command === "gemini")
|
|
@@ -25,6 +25,16 @@ function getCircuitBreaker(command) {
|
|
|
25
25
|
circuitBreakers.set(command, circuitBreaker);
|
|
26
26
|
return circuitBreaker;
|
|
27
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
|
+
}
|
|
28
38
|
function getNvmPath() {
|
|
29
39
|
if (cachedNvmPath !== undefined) {
|
|
30
40
|
return cachedNvmPath;
|
|
@@ -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[];
|
|
@@ -44,6 +45,13 @@ export interface CompressionTelemetry {
|
|
|
44
45
|
compressedChars: number;
|
|
45
46
|
estimatedTokensSaved: number;
|
|
46
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
|
+
}
|
|
47
55
|
export declare function resolveFlightRecorderDbPath(): string | null;
|
|
48
56
|
export declare class FlightRecorder {
|
|
49
57
|
private db;
|
|
@@ -59,6 +67,7 @@ export declare class FlightRecorder {
|
|
|
59
67
|
logStart(entry: FlightLogStart): void;
|
|
60
68
|
logComplete(correlationId: string, result: FlightLogResult): void;
|
|
61
69
|
recordCompressionTelemetry(correlationId: string, telemetry: CompressionTelemetry): void;
|
|
70
|
+
recordRouting(correlationId: string, routing: RoutingRecord): void;
|
|
62
71
|
private redactStart;
|
|
63
72
|
private redactResult;
|
|
64
73
|
queryRequests<T = Record<string, unknown>>(sql: string, ...params: unknown[]): T[];
|
|
@@ -69,6 +78,7 @@ export declare class NoopFlightRecorder {
|
|
|
69
78
|
logStart(_entry: FlightLogStart): void;
|
|
70
79
|
logComplete(_correlationId: string, _result: FlightLogResult): void;
|
|
71
80
|
recordCompressionTelemetry(_correlationId: string, _telemetry: CompressionTelemetry): void;
|
|
81
|
+
recordRouting(_correlationId: string, _routing: RoutingRecord): void;
|
|
72
82
|
queryRequests<T = Record<string, unknown>>(_sql: string, ..._params: unknown[]): T[];
|
|
73
83
|
flush(): void;
|
|
74
84
|
close(): void;
|
package/dist/flight-recorder.js
CHANGED
|
@@ -83,6 +83,35 @@ function ensureCompressionColumns(db) {
|
|
|
83
83
|
db.exec("ALTER TABLE gateway_metadata ADD COLUMN compression_tokens_saved_est INTEGER");
|
|
84
84
|
}
|
|
85
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
|
+
}
|
|
86
115
|
export function resolveFlightRecorderDbPath() {
|
|
87
116
|
const configured = process.env.LLM_GATEWAY_LOGS_DB;
|
|
88
117
|
if (configured !== undefined) {
|
|
@@ -161,7 +190,8 @@ export class FlightRecorder {
|
|
|
161
190
|
output_tokens INTEGER,
|
|
162
191
|
cache_read_tokens INTEGER,
|
|
163
192
|
cache_creation_tokens INTEGER,
|
|
164
|
-
owner_principal TEXT
|
|
193
|
+
owner_principal TEXT,
|
|
194
|
+
cost_basis TEXT
|
|
165
195
|
);
|
|
166
196
|
|
|
167
197
|
CREATE TABLE IF NOT EXISTS gateway_metadata (
|
|
@@ -178,6 +208,12 @@ export class FlightRecorder {
|
|
|
178
208
|
async_job_id TEXT,
|
|
179
209
|
provider_session_id TEXT,
|
|
180
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,
|
|
181
217
|
status TEXT NOT NULL DEFAULT 'started'
|
|
182
218
|
);
|
|
183
219
|
|
|
@@ -219,6 +255,14 @@ export class FlightRecorder {
|
|
|
219
255
|
this.db
|
|
220
256
|
.prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(8, ?)")
|
|
221
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());
|
|
222
266
|
if (process.platform !== "win32") {
|
|
223
267
|
try {
|
|
224
268
|
chmodSync(dbPath, 0o600);
|
|
@@ -265,7 +309,8 @@ export class FlightRecorder {
|
|
|
265
309
|
input_tokens = @input_tokens,
|
|
266
310
|
output_tokens = @output_tokens,
|
|
267
311
|
cache_read_tokens = @cache_read_tokens,
|
|
268
|
-
cache_creation_tokens = @cache_creation_tokens
|
|
312
|
+
cache_creation_tokens = @cache_creation_tokens,
|
|
313
|
+
cost_basis = @cost_basis
|
|
269
314
|
WHERE id = @id
|
|
270
315
|
`);
|
|
271
316
|
const updateMetadata = this.db.prepare(`
|
|
@@ -296,6 +341,7 @@ export class FlightRecorder {
|
|
|
296
341
|
output_tokens: result.outputTokens ?? null,
|
|
297
342
|
cache_read_tokens: result.cacheReadTokens ?? null,
|
|
298
343
|
cache_creation_tokens: result.cacheCreationTokens ?? null,
|
|
344
|
+
cost_basis: result.costBasis ?? null,
|
|
299
345
|
});
|
|
300
346
|
updateMetadata.run({
|
|
301
347
|
id: correlationId,
|
|
@@ -338,6 +384,25 @@ export class FlightRecorder {
|
|
|
338
384
|
tokens_saved_est: telemetry.estimatedTokensSaved,
|
|
339
385
|
});
|
|
340
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
|
+
}
|
|
341
406
|
redactStart(entry) {
|
|
342
407
|
return {
|
|
343
408
|
...entry,
|
|
@@ -372,6 +437,7 @@ export class NoopFlightRecorder {
|
|
|
372
437
|
logStart(_entry) { }
|
|
373
438
|
logComplete(_correlationId, _result) { }
|
|
374
439
|
recordCompressionTelemetry(_correlationId, _telemetry) { }
|
|
440
|
+
recordRouting(_correlationId, _routing) { }
|
|
375
441
|
queryRequests(_sql, ..._params) {
|
|
376
442
|
return [];
|
|
377
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
|
@@ -5,7 +5,8 @@ import { ISessionManager, type CliType, type ProviderType } from "./session-mana
|
|
|
5
5
|
import { ResourceProvider } from "./resources.js";
|
|
6
6
|
import { PerformanceMetrics } from "./metrics.js";
|
|
7
7
|
import { type CompressResult } from "./compressor/index.js";
|
|
8
|
-
import { type PersistenceConfig, type CacheAwarenessConfig, type CompressionConfig, type ProvidersConfig, type ApiProviderRuntime, type AcpConfig, type AdminConfig } from "./config.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";
|
|
9
10
|
import { DatabaseConnection } from "./db.js";
|
|
10
11
|
import { type DevinAcpAgentType } from "./provider-definitions.js";
|
|
11
12
|
import { AsyncJobManager } from "./async-job-manager.js";
|
|
@@ -39,6 +40,7 @@ type ExtendedToolResponse = {
|
|
|
39
40
|
};
|
|
40
41
|
reviewIntegrity?: ReviewIntegrityResult;
|
|
41
42
|
warnings?: WarningEntry[];
|
|
43
|
+
routing?: RouteResponseBlock;
|
|
42
44
|
compression?: CompressResult;
|
|
43
45
|
};
|
|
44
46
|
export declare const logger: {
|
|
@@ -83,6 +85,7 @@ export interface GatewayServerDeps {
|
|
|
83
85
|
acpConfig?: AcpConfig;
|
|
84
86
|
adminConfig?: AdminConfig;
|
|
85
87
|
workspaces?: WorkspaceRegistry;
|
|
88
|
+
leastCost?: LeastCostConfig;
|
|
86
89
|
}
|
|
87
90
|
export interface GatewayServerRuntime {
|
|
88
91
|
sessionManager: ISessionManager;
|
|
@@ -100,6 +103,7 @@ export interface GatewayServerRuntime {
|
|
|
100
103
|
acpConfig: AcpConfig;
|
|
101
104
|
adminConfig: AdminConfig;
|
|
102
105
|
workspaces: WorkspaceRegistry;
|
|
106
|
+
leastCost: LeastCostConfig;
|
|
103
107
|
}
|
|
104
108
|
export declare function resolveGatewayServerRuntime(deps?: GatewayServerDeps, options?: {
|
|
105
109
|
isolateState?: boolean;
|
|
@@ -382,7 +386,6 @@ export declare function buildMistralRetryPrep(params: Pick<MistralRequestParams,
|
|
|
382
386
|
}, recoveryModel: string): {
|
|
383
387
|
args: string[];
|
|
384
388
|
env: Record<string, string>;
|
|
385
|
-
ignoredDisallowedTools: boolean;
|
|
386
389
|
};
|
|
387
390
|
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;
|
|
388
391
|
export interface GrokApiRequestParams {
|
|
@@ -538,7 +541,7 @@ export declare function handleGrokRequestAsync(deps: AsyncHandlerDeps, params: O
|
|
|
538
541
|
export interface DevinRequestParams {
|
|
539
542
|
prompt?: string;
|
|
540
543
|
model?: string;
|
|
541
|
-
permissionMode?: "auto" | "smart" | "dangerous";
|
|
544
|
+
permissionMode?: "auto" | "accept-edits" | "smart" | "dangerous";
|
|
542
545
|
promptFile?: string;
|
|
543
546
|
config?: string;
|
|
544
547
|
sandbox?: boolean;
|
|
@@ -696,5 +699,41 @@ export declare function handleCodexRequestAsync(deps: AsyncHandlerDeps, params:
|
|
|
696
699
|
ref?: string;
|
|
697
700
|
};
|
|
698
701
|
}): Promise<ExtendedToolResponse>;
|
|
702
|
+
export interface RouteResponseBlock {
|
|
703
|
+
chosen: {
|
|
704
|
+
provider: string;
|
|
705
|
+
model: string;
|
|
706
|
+
} | null;
|
|
707
|
+
tier?: string;
|
|
708
|
+
estCostUsd?: number;
|
|
709
|
+
costBasis?: string;
|
|
710
|
+
confidence?: string;
|
|
711
|
+
nearTie?: boolean;
|
|
712
|
+
estInputTokens?: number;
|
|
713
|
+
estOutputTokens?: number;
|
|
714
|
+
priceAsOf?: string;
|
|
715
|
+
priceSource?: string;
|
|
716
|
+
consideredCount: number;
|
|
717
|
+
rejected: {
|
|
718
|
+
candidate: {
|
|
719
|
+
provider: string;
|
|
720
|
+
model: string;
|
|
721
|
+
};
|
|
722
|
+
reason: string;
|
|
723
|
+
}[];
|
|
724
|
+
reroutes: number;
|
|
725
|
+
error?: string;
|
|
726
|
+
}
|
|
727
|
+
export declare function isTransientRouteFailure(provider: string, response: ExtendedToolResponse): boolean;
|
|
728
|
+
export declare function deriveCostBasis(provider: string, resolvedModel: string, usage: {
|
|
729
|
+
inputTokens?: number;
|
|
730
|
+
outputTokens?: number;
|
|
731
|
+
cacheReadTokens?: number;
|
|
732
|
+
cacheCreationTokens?: number;
|
|
733
|
+
costUsd?: number;
|
|
734
|
+
}): {
|
|
735
|
+
costUsd?: number;
|
|
736
|
+
costBasis?: CostBasis;
|
|
737
|
+
};
|
|
699
738
|
export declare function createGatewayServer(deps?: GatewayServerDeps): McpServer;
|
|
700
739
|
export {};
|