llm-cli-gateway 2.16.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 +28 -0
- 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 +41 -1
- package/dist/index.js +658 -30
- 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-definitions.d.ts +4 -0
- package/dist/provider-definitions.js +28 -0
- package/dist/request-helpers.d.ts +2 -2
- 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.js +1 -1
- 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 +9 -4
- package/setup/status.schema.json +68 -0
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;
|
|
@@ -696,5 +700,41 @@ export declare function handleCodexRequestAsync(deps: AsyncHandlerDeps, params:
|
|
|
696
700
|
ref?: string;
|
|
697
701
|
};
|
|
698
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
|
+
};
|
|
699
739
|
export declare function createGatewayServer(deps?: GatewayServerDeps): McpServer;
|
|
700
740
|
export {};
|