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.
Files changed (61) hide show
  1. package/.agents/skills/least-cost-routing/SKILL.md +123 -0
  2. package/CHANGELOG.md +28 -0
  3. package/dist/acp/client.js +5 -0
  4. package/dist/acp/flight-redaction.d.ts +2 -0
  5. package/dist/acp/flight-redaction.js +2 -0
  6. package/dist/acp/runtime.d.ts +1 -0
  7. package/dist/acp/runtime.js +20 -0
  8. package/dist/acp/types.d.ts +52 -0
  9. package/dist/acp/types.js +8 -0
  10. package/dist/api-provider.d.ts +1 -0
  11. package/dist/api-provider.js +1 -0
  12. package/dist/async-job-manager.d.ts +16 -0
  13. package/dist/async-job-manager.js +70 -22
  14. package/dist/claude-mcp-config.js +1 -1
  15. package/dist/compressor/transforms/ansi.js +12 -2
  16. package/dist/config.d.ts +31 -0
  17. package/dist/config.js +142 -7
  18. package/dist/db.js +4 -4
  19. package/dist/doctor.d.ts +34 -1
  20. package/dist/doctor.js +85 -3
  21. package/dist/executor.d.ts +5 -0
  22. package/dist/executor.js +11 -1
  23. package/dist/flight-recorder.d.ts +10 -0
  24. package/dist/flight-recorder.js +68 -2
  25. package/dist/http-transport.js +19 -21
  26. package/dist/index.d.ts +41 -1
  27. package/dist/index.js +658 -30
  28. package/dist/job-store.d.ts +10 -2
  29. package/dist/job-store.js +154 -43
  30. package/dist/lcr-priors.d.ts +60 -0
  31. package/dist/lcr-priors.js +190 -0
  32. package/dist/lcr-router-env.d.ts +20 -0
  33. package/dist/lcr-router-env.js +133 -0
  34. package/dist/lcr-telemetry.d.ts +2 -0
  35. package/dist/lcr-telemetry.js +17 -0
  36. package/dist/least-cost-router.d.ts +86 -0
  37. package/dist/least-cost-router.js +296 -0
  38. package/dist/least-cost-types.d.ts +34 -0
  39. package/dist/least-cost-types.js +1 -0
  40. package/dist/migrate-sessions.js +1 -1
  41. package/dist/migrate.js +1 -1
  42. package/dist/model-registry.js +1 -1
  43. package/dist/postgres-job-store-worker.js +56 -13
  44. package/dist/pricing.d.ts +17 -0
  45. package/dist/pricing.js +167 -0
  46. package/dist/provider-definitions.d.ts +4 -0
  47. package/dist/provider-definitions.js +28 -0
  48. package/dist/request-helpers.d.ts +2 -2
  49. package/dist/resources.d.ts +37 -2
  50. package/dist/resources.js +96 -1
  51. package/dist/retry.d.ts +1 -0
  52. package/dist/retry.js +1 -1
  53. package/dist/token-estimator.d.ts +6 -0
  54. package/dist/token-estimator.js +59 -0
  55. package/dist/upstream-contracts.js +1 -1
  56. package/dist/validation-receipt.js +1 -1
  57. package/dist/validation-tools.d.ts +6 -0
  58. package/dist/validation-tools.js +174 -54
  59. package/npm-shrinkwrap.json +2 -2
  60. package/package.json +9 -4
  61. package/setup/status.schema.json +68 -0
@@ -0,0 +1,2 @@
1
+ export type TelemetryTier = "T1" | "T2" | "T3" | "T4";
2
+ export declare function telemetryTierFor(provider: string): TelemetryTier;
@@ -0,0 +1,17 @@
1
+ import { CLI_TYPES } from "./provider-types.js";
2
+ const CLI_TELEMETRY_TIERS = {
3
+ claude: "T1",
4
+ codex: "T2",
5
+ gemini: "T2",
6
+ grok: "T3",
7
+ mistral: "T1",
8
+ devin: "T4",
9
+ cursor: "T4",
10
+ };
11
+ const DEFAULT_TELEMETRY_TIER = "T2";
12
+ export function telemetryTierFor(provider) {
13
+ if (CLI_TYPES.includes(provider)) {
14
+ return CLI_TELEMETRY_TIERS[provider];
15
+ }
16
+ return DEFAULT_TELEMETRY_TIER;
17
+ }
@@ -0,0 +1,86 @@
1
+ import type { ModelCost, CostBasis, Confidence, QualityTier, PriceSource } from "./least-cost-types.js";
2
+ export interface Candidate {
3
+ provider: string;
4
+ model: string;
5
+ }
6
+ export interface CandidateCapabilities {
7
+ acceptsImages: boolean;
8
+ acceptsAttachments: boolean;
9
+ toolCalling: boolean;
10
+ jsonSchema: boolean;
11
+ outputFormats: readonly string[];
12
+ capabilityScope: string;
13
+ effortLevels: readonly string[];
14
+ }
15
+ export interface RouterEnv {
16
+ providers(): readonly string[];
17
+ models(provider: string): readonly string[];
18
+ isAuthed(provider: string): boolean;
19
+ breakerState(provider: string): string;
20
+ atCapacity(provider: string): boolean;
21
+ capabilities(provider: string): CandidateCapabilities;
22
+ modelCost(provider: string, model: string): ModelCost;
23
+ successRate(provider: string, model: string): number;
24
+ meanLatencyMs(provider: string, model: string): number;
25
+ calibrationK(prompt: string, family: string): number;
26
+ outputPrior(provider: string, model: string): {
27
+ median: number;
28
+ p90: number;
29
+ } | null;
30
+ confidenceBand(prompt: string, family: string): Confidence;
31
+ }
32
+ export interface RequiredCapabilities {
33
+ images?: boolean;
34
+ attachments?: boolean;
35
+ toolCalling?: boolean;
36
+ jsonSchema?: boolean;
37
+ outputFormat?: string;
38
+ effort?: string;
39
+ }
40
+ export interface RouteRequestInput {
41
+ prompt: string;
42
+ candidates?: Candidate[];
43
+ minTier?: QualityTier;
44
+ maxCostUsd?: number;
45
+ expectedOutputTokens?: number;
46
+ maxOutputTokens?: number;
47
+ requiredCapabilities?: RequiredCapabilities;
48
+ allowUnpriced?: boolean;
49
+ budgetWaiver?: boolean;
50
+ fallback?: Candidate;
51
+ }
52
+ export interface RouterConfig {
53
+ minTier: QualityTier;
54
+ maxCostUsd: number;
55
+ defaultExpectedOutputTokens: number;
56
+ budgetOutputSafetyFactor: number;
57
+ allowUnpriced: boolean;
58
+ tiers: Record<string, QualityTier>;
59
+ candidates: {
60
+ allow: string[];
61
+ deny: string[];
62
+ };
63
+ preferenceOrder?: string[];
64
+ }
65
+ export interface RejectedCandidate {
66
+ candidate: Candidate;
67
+ reason: string;
68
+ }
69
+ export interface RouteDecision {
70
+ chosen: Candidate | null;
71
+ tier?: QualityTier;
72
+ estCostUsd?: number;
73
+ costBasis?: CostBasis;
74
+ confidence?: Confidence;
75
+ nearTie?: boolean;
76
+ estInputTokens?: number;
77
+ estOutputTokens?: number;
78
+ priceAsOf?: string;
79
+ priceSource?: PriceSource;
80
+ consideredCount: number;
81
+ rejected: RejectedCandidate[];
82
+ reroutes: number;
83
+ error?: "NoEligibleCandidate" | "BudgetExceeded";
84
+ }
85
+ export declare function selectCandidate(req: RouteRequestInput, env: RouterEnv, config: RouterConfig, excluded?: ReadonlySet<string>): RouteDecision;
86
+ export declare function selectCheapestPerTier(req: RouteRequestInput, env: RouterEnv, config: RouterConfig, excluded?: ReadonlySet<string>): RouteDecision[];
@@ -0,0 +1,296 @@
1
+ import { modelIdToFamily, composeCost } from "./pricing.js";
2
+ import { estimateInputTokens } from "./token-estimator.js";
3
+ const TIER_ORDER = {
4
+ economy: 0,
5
+ standard: 1,
6
+ frontier: 2,
7
+ };
8
+ function candidateKey(c) {
9
+ return `${c.provider}:${c.model}`;
10
+ }
11
+ function isExplicitlyListed(c, explicit) {
12
+ if (!explicit)
13
+ return false;
14
+ return explicit.some(e => e.provider === c.provider && e.model === c.model);
15
+ }
16
+ function passesAllowDeny(c, config) {
17
+ const providerKey = c.provider;
18
+ const pairKey = candidateKey(c);
19
+ const matches = (entry) => entry === providerKey || entry === pairKey;
20
+ if (config.candidates.deny.some(matches))
21
+ return false;
22
+ if (config.candidates.allow.length === 0)
23
+ return true;
24
+ return config.candidates.allow.some(matches);
25
+ }
26
+ function buildPool(req, env, config) {
27
+ const source = req.candidates && req.candidates.length > 0
28
+ ? req.candidates
29
+ : env
30
+ .providers()
31
+ .flatMap(provider => env.models(provider).map(model => ({ provider, model })));
32
+ return source.filter(c => passesAllowDeny(c, config));
33
+ }
34
+ function checkCapabilities(caps, required) {
35
+ if (!required)
36
+ return { ok: true };
37
+ if (required.images === true && !caps.acceptsImages) {
38
+ return { ok: false, reason: "capability:images" };
39
+ }
40
+ if (required.attachments === true && !caps.acceptsAttachments) {
41
+ return { ok: false, reason: "capability:attachments" };
42
+ }
43
+ if (required.toolCalling === true && !caps.toolCalling) {
44
+ return { ok: false, reason: "capability:toolCalling" };
45
+ }
46
+ if (required.jsonSchema === true && !caps.jsonSchema) {
47
+ return { ok: false, reason: "capability:jsonSchema" };
48
+ }
49
+ if (required.outputFormat !== undefined && !caps.outputFormats.includes(required.outputFormat)) {
50
+ return { ok: false, reason: "capability:outputFormat" };
51
+ }
52
+ if (required.effort !== undefined && !caps.effortLevels.includes(required.effort)) {
53
+ return { ok: false, reason: "capability:effort" };
54
+ }
55
+ return { ok: true };
56
+ }
57
+ function checkTier(candidate, req, config) {
58
+ const family = modelIdToFamily(candidate.model);
59
+ const tierKey = `${candidate.provider}:${family}`;
60
+ const tier = config.tiers[tierKey];
61
+ if (tier === undefined) {
62
+ if (isExplicitlyListed(candidate, req.candidates)) {
63
+ return { ok: true, tier: undefined };
64
+ }
65
+ return { ok: false, reason: "untiered" };
66
+ }
67
+ const effectiveMinTier = req.minTier ?? config.minTier;
68
+ if (TIER_ORDER[tier] < TIER_ORDER[effectiveMinTier]) {
69
+ return { ok: false, reason: "below-min-tier" };
70
+ }
71
+ return { ok: true, tier };
72
+ }
73
+ function evaluateBaseEligibility(candidate, req, env) {
74
+ if (!env.isAuthed(candidate.provider)) {
75
+ return { ok: false, reason: "auth" };
76
+ }
77
+ if (env.breakerState(candidate.provider) !== "CLOSED") {
78
+ return { ok: false, reason: "breaker" };
79
+ }
80
+ if (env.atCapacity(candidate.provider)) {
81
+ return { ok: false, reason: "capacity" };
82
+ }
83
+ const caps = env.capabilities(candidate.provider);
84
+ const capResult = checkCapabilities(caps, req.requiredCapabilities);
85
+ if (!capResult.ok) {
86
+ return { ok: false, reason: capResult.reason };
87
+ }
88
+ if (caps.capabilityScope === "maintain-only" && !isExplicitlyListed(candidate, req.candidates)) {
89
+ return { ok: false, reason: "maintain-only" };
90
+ }
91
+ return { ok: true };
92
+ }
93
+ const CONFIDENCE_ORDER = { low: 0, medium: 1, high: 2 };
94
+ function minConfidence(a, b) {
95
+ return CONFIDENCE_ORDER[a] <= CONFIDENCE_ORDER[b] ? a : b;
96
+ }
97
+ function rankCandidate(candidate, tier, modelCost, req, config, env) {
98
+ const family = modelCost.family !== "unknown" ? modelCost.family : modelIdToFamily(candidate.model);
99
+ const calibrationK = env.calibrationK(req.prompt, family);
100
+ const estInputTokens = estimateInputTokens(req.prompt, { family, calibrationK });
101
+ const prior = env.outputPrior(candidate.provider, candidate.model);
102
+ const estOutputTokens = req.expectedOutputTokens ??
103
+ (prior ? Math.max(1, Math.round(prior.median)) : config.defaultExpectedOutputTokens);
104
+ const budgetOutputTokens = req.maxOutputTokens ??
105
+ (prior
106
+ ? Math.max(1, Math.round(prior.p90))
107
+ : config.defaultExpectedOutputTokens * config.budgetOutputSafetyFactor);
108
+ const rankResult = composeCost(null, { estInputTokens, estOutputTokens }, modelCost);
109
+ const band = env.confidenceBand(req.prompt, family);
110
+ return {
111
+ candidate,
112
+ tier,
113
+ modelCost,
114
+ estInputTokens,
115
+ estOutputTokens,
116
+ rankCostUsd: rankResult.costUsd,
117
+ rankKey: modelCost.source === "unknown" ? Number.POSITIVE_INFINITY : rankResult.costUsd,
118
+ costBasis: rankResult.cost_basis,
119
+ confidence: minConfidence(rankResult.confidence, band),
120
+ budgetOutputTokens,
121
+ };
122
+ }
123
+ function preferenceIndex(provider, preferenceOrder) {
124
+ if (!preferenceOrder)
125
+ return Number.MAX_SAFE_INTEGER;
126
+ const idx = preferenceOrder.indexOf(provider);
127
+ return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
128
+ }
129
+ function compareCandidates(a, b, env, config) {
130
+ if (a.rankKey !== b.rankKey)
131
+ return a.rankKey - b.rankKey;
132
+ const successA = env.successRate(a.candidate.provider, a.candidate.model);
133
+ const successB = env.successRate(b.candidate.provider, b.candidate.model);
134
+ if (successA !== successB)
135
+ return successB - successA;
136
+ const latencyA = env.meanLatencyMs(a.candidate.provider, a.candidate.model);
137
+ const latencyB = env.meanLatencyMs(b.candidate.provider, b.candidate.model);
138
+ if (latencyA !== latencyB)
139
+ return latencyA - latencyB;
140
+ const prefA = preferenceIndex(a.candidate.provider, config.preferenceOrder);
141
+ const prefB = preferenceIndex(b.candidate.provider, config.preferenceOrder);
142
+ if (prefA !== prefB)
143
+ return prefA - prefB;
144
+ const lexA = `${a.candidate.provider}/${a.candidate.model}`;
145
+ const lexB = `${b.candidate.provider}/${b.candidate.model}`;
146
+ return lexA.localeCompare(lexB);
147
+ }
148
+ function checkBudget(ranked, req, config, isFallback) {
149
+ const { modelCost, estInputTokens } = ranked;
150
+ const allowUnpriced = req.allowUnpriced ?? config.allowUnpriced;
151
+ const budgetWaiver = req.budgetWaiver ?? false;
152
+ if (modelCost.source === "unknown") {
153
+ if (allowUnpriced && budgetWaiver)
154
+ return { ok: true };
155
+ return { ok: false, reason: "budget" };
156
+ }
157
+ const budgetResult = composeCost(null, { estInputTokens, estOutputTokens: ranked.budgetOutputTokens }, modelCost);
158
+ const budgetCost = budgetResult.costUsd;
159
+ const effectiveMax = req.maxCostUsd ?? config.maxCostUsd;
160
+ if (budgetCost > effectiveMax) {
161
+ if (isFallback && budgetWaiver)
162
+ return { ok: true };
163
+ return { ok: false, reason: "budget" };
164
+ }
165
+ return { ok: true };
166
+ }
167
+ function toDecisionFromRanked(ranked, consideredCount, rejected, nearTie) {
168
+ return {
169
+ chosen: ranked.candidate,
170
+ tier: ranked.tier,
171
+ estCostUsd: ranked.rankCostUsd,
172
+ costBasis: ranked.costBasis,
173
+ confidence: ranked.confidence,
174
+ nearTie,
175
+ estInputTokens: ranked.estInputTokens,
176
+ estOutputTokens: ranked.estOutputTokens,
177
+ priceAsOf: ranked.modelCost.asOf,
178
+ priceSource: ranked.modelCost.source,
179
+ consideredCount,
180
+ rejected,
181
+ reroutes: 0,
182
+ };
183
+ }
184
+ function buildRankedPool(req, env, config, excluded) {
185
+ const pool = buildPool(req, env, config).filter(c => !excluded?.has(candidateKey(c)));
186
+ const rejected = [];
187
+ const eligible = [];
188
+ for (const candidate of pool) {
189
+ const baseEligibility = evaluateBaseEligibility(candidate, req, env);
190
+ if (!baseEligibility.ok) {
191
+ rejected.push({ candidate, reason: baseEligibility.reason });
192
+ continue;
193
+ }
194
+ const tierResult = checkTier(candidate, req, config);
195
+ if (!tierResult.ok) {
196
+ rejected.push({ candidate, reason: tierResult.reason });
197
+ continue;
198
+ }
199
+ const modelCost = env.modelCost(candidate.provider, candidate.model);
200
+ const allowUnpriced = req.allowUnpriced ?? config.allowUnpriced;
201
+ if (modelCost.source === "unknown" && !allowUnpriced) {
202
+ rejected.push({ candidate, reason: "unpriced" });
203
+ continue;
204
+ }
205
+ eligible.push({ candidate, tier: tierResult.tier, modelCost });
206
+ }
207
+ const ranked = eligible.map(e => rankCandidate(e.candidate, e.tier, e.modelCost, req, config, env));
208
+ ranked.sort((a, b) => compareCandidates(a, b, env, config));
209
+ return { ranked, rejected };
210
+ }
211
+ export function selectCandidate(req, env, config, excluded) {
212
+ const { ranked, rejected } = buildRankedPool(req, env, config, excluded);
213
+ if (ranked.length === 0) {
214
+ if (req.fallback) {
215
+ const fallbackReq = {
216
+ ...req,
217
+ candidates: [...(req.candidates ?? []), req.fallback],
218
+ };
219
+ const fbBase = evaluateBaseEligibility(req.fallback, fallbackReq, env);
220
+ if (!fbBase.ok) {
221
+ return {
222
+ chosen: null,
223
+ consideredCount: 0,
224
+ rejected: [...rejected, { candidate: req.fallback, reason: fbBase.reason }],
225
+ reroutes: 0,
226
+ error: "NoEligibleCandidate",
227
+ };
228
+ }
229
+ const fbTier = checkTier(req.fallback, fallbackReq, config);
230
+ if (!fbTier.ok) {
231
+ return {
232
+ chosen: null,
233
+ consideredCount: 0,
234
+ rejected: [...rejected, { candidate: req.fallback, reason: fbTier.reason }],
235
+ reroutes: 0,
236
+ error: "NoEligibleCandidate",
237
+ };
238
+ }
239
+ const fallbackModelCost = env.modelCost(req.fallback.provider, req.fallback.model);
240
+ const fallbackRanked = rankCandidate(req.fallback, fbTier.tier, fallbackModelCost, req, config, env);
241
+ const budget = checkBudget(fallbackRanked, req, config, true);
242
+ if (!budget.ok) {
243
+ return {
244
+ chosen: null,
245
+ consideredCount: 0,
246
+ rejected: [...rejected, { candidate: req.fallback, reason: budget.reason }],
247
+ reroutes: 0,
248
+ error: "BudgetExceeded",
249
+ };
250
+ }
251
+ return toDecisionFromRanked(fallbackRanked, 0, rejected, false);
252
+ }
253
+ return {
254
+ chosen: null,
255
+ consideredCount: 0,
256
+ rejected,
257
+ reroutes: 0,
258
+ error: "NoEligibleCandidate",
259
+ };
260
+ }
261
+ const best = ranked[0];
262
+ const secondBest = ranked.length > 1 ? ranked[1] : undefined;
263
+ const nearTie = secondBest !== undefined &&
264
+ Number.isFinite(best.rankKey) &&
265
+ best.rankKey > 0 &&
266
+ Math.abs(secondBest.rankKey - best.rankKey) / best.rankKey <= 0.01;
267
+ const budget = checkBudget(best, req, config, false);
268
+ if (!budget.ok) {
269
+ return {
270
+ chosen: null,
271
+ consideredCount: ranked.length,
272
+ rejected: [...rejected, { candidate: best.candidate, reason: budget.reason }],
273
+ reroutes: 0,
274
+ error: "BudgetExceeded",
275
+ };
276
+ }
277
+ return toDecisionFromRanked(best, ranked.length, rejected, nearTie);
278
+ }
279
+ export function selectCheapestPerTier(req, env, config, excluded) {
280
+ const { ranked, rejected } = buildRankedPool(req, env, config, excluded);
281
+ const decidedTiers = new Set();
282
+ const decisions = [];
283
+ for (const candidate of ranked) {
284
+ const tier = candidate.tier;
285
+ if (tier === undefined)
286
+ continue;
287
+ if (decidedTiers.has(tier))
288
+ continue;
289
+ decidedTiers.add(tier);
290
+ const budget = checkBudget(candidate, req, config, false);
291
+ if (!budget.ok)
292
+ continue;
293
+ decisions.push(toDecisionFromRanked(candidate, ranked.length, rejected, false));
294
+ }
295
+ return decisions.sort((a, b) => TIER_ORDER[a.tier] - TIER_ORDER[b.tier]);
296
+ }
@@ -0,0 +1,34 @@
1
+ export type AccountingMode = "inclusive" | "disjoint";
2
+ export type CostBasis = "provider-reported" | "derived-from-tokens" | "pre-flight-estimate";
3
+ export type Confidence = "high" | "medium" | "low";
4
+ export type QualityTier = "economy" | "standard" | "frontier";
5
+ export type PriceSource = "table" | "api-catalog" | "unknown";
6
+ export interface ModelCost {
7
+ inputUsdPerMTok: number;
8
+ outputUsdPerMTok: number;
9
+ cacheReadMultiplier: number;
10
+ cacheWriteUsdPerMTok: number;
11
+ accountingMode: AccountingMode;
12
+ family: string;
13
+ source: PriceSource;
14
+ asOf: string;
15
+ }
16
+ export interface TokenCounts {
17
+ inputTokens: number;
18
+ outputTokens: number;
19
+ cacheReadTokens?: number;
20
+ cacheCreationTokens?: number;
21
+ reasoningTokens?: number;
22
+ reportedCostUsd?: number;
23
+ }
24
+ export interface TokenEstimate {
25
+ estInputTokens: number;
26
+ estOutputTokens: number;
27
+ estCacheReadTokens?: number;
28
+ estCacheWriteTokens?: number;
29
+ }
30
+ export interface CostResult {
31
+ costUsd: number;
32
+ cost_basis: CostBasis;
33
+ confidence: Confidence;
34
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -22,7 +22,7 @@ export async function migrateFromFile(filePath, pgManager) {
22
22
  fileData = JSON.parse(fileContent);
23
23
  }
24
24
  catch (error) {
25
- throw new Error(`Failed to read sessions file: ${error instanceof Error ? error.message : String(error)}`);
25
+ throw new Error(`Failed to read sessions file: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
26
26
  }
27
27
  console.error(`Found ${Object.keys(fileData.sessions).length} sessions to migrate`);
28
28
  for (const [id, session] of Object.entries(fileData.sessions)) {
package/dist/migrate.js CHANGED
@@ -86,7 +86,7 @@ async function importOptionalPg() {
86
86
  }
87
87
  catch (error) {
88
88
  if (error?.code === "ERR_MODULE_NOT_FOUND" || error?.code === "MODULE_NOT_FOUND") {
89
- throw new Error("PostgreSQL migrations require optional peer dependency 'pg'. Install it alongside llm-cli-gateway before running migrate.");
89
+ throw new Error("PostgreSQL migrations require optional peer dependency 'pg'. Install it alongside llm-cli-gateway before running migrate.", { cause: error });
90
90
  }
91
91
  throw error;
92
92
  }
@@ -192,7 +192,7 @@ function addWarning(info, warning) {
192
192
  }
193
193
  }
194
194
  function isValidModelName(model) {
195
- return model.length > 0 && !/[\u0000-\u001f\u007f]/.test(model) && !/\s/.test(model);
195
+ return model.length > 0 && !/[\s\p{Cc}]/u.test(model);
196
196
  }
197
197
  function addModel(info, model, description, metadata, options = {}) {
198
198
  const normalized = model.trim();
@@ -1,7 +1,21 @@
1
- import { writeFileSync } from "node:fs";
2
1
  import { parentPort, workerData } from "node:worker_threads";
3
2
  let pool = null;
4
3
  const PG_NOW_MS = "(EXTRACT(EPOCH FROM now()) * 1000)::bigint";
4
+ const PG_BOOTSTRAP_LOCK_KEY = 13_920_260_713;
5
+ const PG_POOL_MAX = 1;
6
+ const PG_STATEMENT_TIMEOUT_MS = 25_000;
7
+ const PG_LOCK_TIMEOUT_MS = 5_000;
8
+ const PG_QUERY_TIMEOUT_MS = 27_000;
9
+ function reportDiagnostic(kind, error) {
10
+ const message = error instanceof Error ? error.message : String(error);
11
+ const stack = error instanceof Error ? error.stack : undefined;
12
+ parentPort?.postMessage({
13
+ type: "postgres-job-store-diagnostic",
14
+ kind,
15
+ message,
16
+ stack,
17
+ });
18
+ }
5
19
  function signal(shared, status) {
6
20
  const view = new Int32Array(shared);
7
21
  Atomics.store(view, 0, status);
@@ -48,9 +62,22 @@ async function init() {
48
62
  pool = new pg.Pool({
49
63
  connectionString: workerData.dsn,
50
64
  connectionTimeoutMillis: workerData.connectionTimeoutMillis,
65
+ max: PG_POOL_MAX,
66
+ idleTimeoutMillis: 30_000,
67
+ statement_timeout: PG_STATEMENT_TIMEOUT_MS,
68
+ lock_timeout: PG_LOCK_TIMEOUT_MS,
69
+ query_timeout: PG_QUERY_TIMEOUT_MS,
70
+ application_name: "llm-cli-gateway-job-store",
51
71
  });
72
+ getPool().on("error", error => reportDiagnostic("pool_error", error));
52
73
  await getPool().query("SELECT 1");
53
- await getPool().query(`
74
+ await withClient(async (client) => {
75
+ await client.query("BEGIN");
76
+ try {
77
+ await client.query("SET LOCAL lock_timeout = 0");
78
+ await client.query("SELECT pg_advisory_xact_lock($1::bigint)", [PG_BOOTSTRAP_LOCK_KEY]);
79
+ await client.query(`SET LOCAL lock_timeout = '${PG_LOCK_TIMEOUT_MS}ms'`);
80
+ await client.query(`
54
81
  CREATE TABLE IF NOT EXISTS jobs (
55
82
  id TEXT PRIMARY KEY,
56
83
  correlation_id TEXT NOT NULL,
@@ -125,14 +152,26 @@ async function init() {
125
152
  confidence TEXT NOT NULL
126
153
  );
127
154
  CREATE INDEX IF NOT EXISTS idx_validation_receipts_owner ON validation_receipts(owner_principal);
128
- `);
129
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_principal TEXT");
130
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS transport TEXT NOT NULL DEFAULT 'process'");
131
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS http_status INTEGER");
132
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS payload_json TEXT");
133
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_instance TEXT");
134
- await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS lease_deadline BIGINT");
135
- await getPool().query("CREATE INDEX IF NOT EXISTS idx_jobs_owner_status ON jobs(owner_instance, status)");
155
+ `);
156
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_principal TEXT");
157
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS transport TEXT NOT NULL DEFAULT 'process'");
158
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS http_status INTEGER");
159
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS payload_json TEXT");
160
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_instance TEXT");
161
+ await client.query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS lease_deadline BIGINT");
162
+ await client.query("CREATE INDEX IF NOT EXISTS idx_jobs_owner_status ON jobs(owner_instance, status)");
163
+ await client.query("COMMIT");
164
+ }
165
+ catch (error) {
166
+ try {
167
+ await client.query("ROLLBACK");
168
+ }
169
+ catch (rollbackError) {
170
+ reportDiagnostic("bootstrap_rollback_error", rollbackError);
171
+ }
172
+ throw error;
173
+ }
174
+ });
136
175
  }
137
176
  async function op(method, args) {
138
177
  switch (method) {
@@ -426,7 +465,7 @@ async function op(method, args) {
426
465
  }
427
466
  }
428
467
  parentPort?.on("message", async (message) => {
429
- const { method, args, resultPath, shared } = message;
468
+ const { method, args, shared, responsePort } = message;
430
469
  let payload;
431
470
  try {
432
471
  payload = ok(await op(method, args));
@@ -435,10 +474,14 @@ parentPort?.on("message", async (message) => {
435
474
  payload = fail(error);
436
475
  }
437
476
  try {
438
- writeFileSync(resultPath, JSON.stringify(payload));
477
+ responsePort.postMessage(payload);
439
478
  signal(shared, 1);
440
479
  }
441
- catch {
480
+ catch (error) {
481
+ reportDiagnostic("response_transport_error", error);
442
482
  signal(shared, 2);
443
483
  }
484
+ finally {
485
+ responsePort.close();
486
+ }
444
487
  });
package/dist/pricing.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { CliType } from "./provider-types.js";
2
+ import type { ModelCost, TokenCounts, TokenEstimate, CostResult, AccountingMode } from "./least-cost-types.js";
2
3
  export interface PricePerMillion {
3
4
  inputUsd: number;
4
5
  outputUsd: number;
@@ -7,3 +8,19 @@ export interface PricePerMillion {
7
8
  export declare const PRICING_AS_OF = "2026-06-13";
8
9
  export declare function getPricing(cli: CliType, model: string): PricePerMillion;
9
10
  export declare function estimateCacheSavingsUsd(cli: CliType, model: string, cacheReadTokens: number): number;
11
+ export declare function modelIdToFamily(model: string): string;
12
+ export declare const API_CATALOG_AS_OF = "2026-07-11";
13
+ export interface ApiCatalogEntry {
14
+ inputUsdPerMTok: number;
15
+ outputUsdPerMTok: number;
16
+ cacheReadMultiplier: number;
17
+ accountingMode?: AccountingMode;
18
+ family?: string;
19
+ }
20
+ export type ApiCatalog = ReadonlyMap<string, ApiCatalogEntry>;
21
+ export declare const API_CATALOG: ApiCatalog;
22
+ export declare function getModelCost(provider: string, model: string, opts?: {
23
+ catalog?: ApiCatalog;
24
+ preferCatalog?: boolean;
25
+ }): ModelCost;
26
+ export declare function composeCost(counts: TokenCounts | null, estimate: TokenEstimate, modelCost: ModelCost): CostResult;