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.
Files changed (66) hide show
  1. package/.agents/skills/least-cost-routing/SKILL.md +123 -0
  2. package/CHANGELOG.md +37 -0
  3. package/README.md +12 -12
  4. package/dist/acp/client.js +5 -0
  5. package/dist/acp/flight-redaction.d.ts +2 -0
  6. package/dist/acp/flight-redaction.js +2 -0
  7. package/dist/acp/runtime.d.ts +1 -0
  8. package/dist/acp/runtime.js +20 -0
  9. package/dist/acp/types.d.ts +52 -0
  10. package/dist/acp/types.js +8 -0
  11. package/dist/api-provider.d.ts +1 -0
  12. package/dist/api-provider.js +1 -0
  13. package/dist/async-job-manager.d.ts +16 -0
  14. package/dist/async-job-manager.js +70 -22
  15. package/dist/claude-mcp-config.js +1 -1
  16. package/dist/compressor/transforms/ansi.js +12 -2
  17. package/dist/config.d.ts +31 -0
  18. package/dist/config.js +142 -7
  19. package/dist/db.js +4 -4
  20. package/dist/doctor.d.ts +34 -1
  21. package/dist/doctor.js +85 -3
  22. package/dist/executor.d.ts +5 -0
  23. package/dist/executor.js +11 -1
  24. package/dist/flight-recorder.d.ts +10 -0
  25. package/dist/flight-recorder.js +68 -2
  26. package/dist/http-transport.js +19 -21
  27. package/dist/index.d.ts +42 -3
  28. package/dist/index.js +666 -41
  29. package/dist/job-store.d.ts +10 -2
  30. package/dist/job-store.js +154 -43
  31. package/dist/lcr-priors.d.ts +60 -0
  32. package/dist/lcr-priors.js +190 -0
  33. package/dist/lcr-router-env.d.ts +20 -0
  34. package/dist/lcr-router-env.js +133 -0
  35. package/dist/lcr-telemetry.d.ts +2 -0
  36. package/dist/lcr-telemetry.js +17 -0
  37. package/dist/least-cost-router.d.ts +86 -0
  38. package/dist/least-cost-router.js +296 -0
  39. package/dist/least-cost-types.d.ts +34 -0
  40. package/dist/least-cost-types.js +1 -0
  41. package/dist/migrate-sessions.js +1 -1
  42. package/dist/migrate.js +1 -1
  43. package/dist/model-registry.js +1 -1
  44. package/dist/postgres-job-store-worker.js +56 -13
  45. package/dist/pricing.d.ts +17 -0
  46. package/dist/pricing.js +167 -0
  47. package/dist/provider-admin-tools.js +12 -4
  48. package/dist/provider-definitions.d.ts +4 -0
  49. package/dist/provider-definitions.js +33 -5
  50. package/dist/provider-tool-capabilities.js +6 -9
  51. package/dist/request-helpers.d.ts +3 -4
  52. package/dist/request-helpers.js +9 -10
  53. package/dist/resources.d.ts +37 -2
  54. package/dist/resources.js +96 -1
  55. package/dist/retry.d.ts +1 -0
  56. package/dist/retry.js +1 -1
  57. package/dist/token-estimator.d.ts +6 -0
  58. package/dist/token-estimator.js +59 -0
  59. package/dist/upstream-contracts.d.ts +4 -1
  60. package/dist/upstream-contracts.js +255 -71
  61. package/dist/validation-receipt.js +1 -1
  62. package/dist/validation-tools.d.ts +6 -0
  63. package/dist/validation-tools.js +174 -54
  64. package/npm-shrinkwrap.json +2 -2
  65. package/package.json +14 -5
  66. 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 (let raw of lines) {
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: new Date().toISOString(),
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";
@@ -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;
@@ -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
  }
@@ -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
- try {
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
- let entry;
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: sessionId => {
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 {};