llm-cli-gateway 2.13.2 → 2.14.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/CHANGELOG.md +139 -0
- package/README.md +68 -29
- package/dist/acp/client.d.ts +29 -1
- package/dist/acp/client.js +78 -4
- package/dist/acp/errors.d.ts +9 -1
- package/dist/acp/errors.js +19 -0
- package/dist/acp/event-normalizer.d.ts +12 -0
- package/dist/acp/event-normalizer.js +16 -0
- package/dist/acp/flight-redaction.d.ts +3 -0
- package/dist/acp/flight-redaction.js +3 -0
- package/dist/acp/permission-bridge.js +11 -5
- package/dist/acp/process-manager.d.ts +2 -1
- package/dist/acp/process-manager.js +43 -4
- package/dist/acp/provider-registry.js +19 -11
- package/dist/acp/runtime.d.ts +8 -0
- package/dist/acp/runtime.js +47 -4
- package/dist/acp/types.d.ts +3083 -55
- package/dist/acp/types.js +242 -5
- package/dist/async-job-manager.d.ts +38 -1
- package/dist/async-job-manager.js +287 -20
- package/dist/codex-json-parser.d.ts +1 -0
- package/dist/codex-json-parser.js +6 -0
- package/dist/config.d.ts +19 -0
- package/dist/config.js +84 -1
- package/dist/flight-recorder.d.ts +2 -0
- package/dist/flight-recorder.js +20 -0
- package/dist/gemini-json-parser.d.ts +2 -0
- package/dist/gemini-json-parser.js +45 -8
- package/dist/grok-json-parser.d.ts +14 -0
- package/dist/grok-json-parser.js +156 -0
- package/dist/index.d.ts +47 -2
- package/dist/index.js +504 -122
- package/dist/job-store.d.ts +119 -11
- package/dist/job-store.js +372 -42
- package/dist/model-registry.d.ts +1 -0
- package/dist/model-registry.js +46 -0
- package/dist/oauth.js +2 -2
- package/dist/postgres-job-store-worker.d.ts +1 -0
- package/dist/postgres-job-store-worker.js +444 -0
- package/dist/pricing.d.ts +3 -2
- package/dist/provider-acp-capabilities.d.ts +52 -0
- package/dist/provider-acp-capabilities.js +101 -0
- package/dist/provider-admin-tools.d.ts +100 -0
- package/dist/provider-admin-tools.js +572 -0
- package/dist/provider-capability-cache.d.ts +46 -0
- package/dist/provider-capability-cache.js +248 -0
- package/dist/provider-capability-discovery.d.ts +85 -0
- package/dist/provider-capability-discovery.js +461 -0
- package/dist/provider-capability-resolver.d.ts +29 -0
- package/dist/provider-capability-resolver.js +92 -0
- package/dist/provider-definition-assertions.d.ts +9 -0
- package/dist/provider-definition-assertions.js +147 -0
- package/dist/provider-definitions.d.ts +127 -0
- package/dist/provider-definitions.js +758 -0
- package/dist/provider-help-parser.d.ts +34 -0
- package/dist/provider-help-parser.js +203 -0
- package/dist/provider-model-discovery.d.ts +30 -0
- package/dist/provider-model-discovery.js +229 -0
- package/dist/provider-output-metadata.d.ts +7 -0
- package/dist/provider-output-metadata.js +68 -0
- package/dist/provider-schema-builder.d.ts +22 -0
- package/dist/provider-schema-builder.js +55 -0
- package/dist/provider-surface-generator.d.ts +98 -0
- package/dist/provider-surface-generator.js +140 -0
- package/dist/provider-tool-capabilities.d.ts +3 -2
- package/dist/provider-tool-capabilities.js +77 -62
- package/dist/request-helpers.d.ts +37 -4
- package/dist/request-helpers.js +134 -0
- package/dist/resources.d.ts +6 -1
- package/dist/resources.js +64 -192
- package/dist/session-manager.js +18 -11
- package/dist/sqlite-driver.d.ts +1 -1
- package/dist/sqlite-driver.js +2 -1
- package/dist/stream-json-parser.d.ts +1 -0
- package/dist/stream-json-parser.js +3 -0
- package/dist/upstream-contracts.d.ts +17 -1
- package/dist/upstream-contracts.js +209 -36
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
package/dist/model-registry.js
CHANGED
|
@@ -139,6 +139,7 @@ function cloneInfo(source) {
|
|
|
139
139
|
aliases: source.aliases ? { ...source.aliases } : undefined,
|
|
140
140
|
modelMetadata: source.modelMetadata ? { ...source.modelMetadata } : {},
|
|
141
141
|
warnings: source.warnings ? [...source.warnings] : [],
|
|
142
|
+
defaultAgent: source.defaultAgent,
|
|
142
143
|
};
|
|
143
144
|
Object.keys(cloned.models).forEach(model => {
|
|
144
145
|
cloned.modelMetadata[model] = cloned.modelMetadata[model] ?? {
|
|
@@ -373,6 +374,7 @@ function applyGeminiOverrides(info) {
|
|
|
373
374
|
}
|
|
374
375
|
function applyGrokOverrides(info) {
|
|
375
376
|
const envDefault = process.env.GROK_DEFAULT_MODEL;
|
|
377
|
+
applyGrokConfig(info);
|
|
376
378
|
addEnvModels(info, "GROK_MODELS");
|
|
377
379
|
addEnvAliases(info, "grok", "GROK_MODEL_ALIASES");
|
|
378
380
|
addGlobalEnvAliases(info, "grok");
|
|
@@ -381,8 +383,48 @@ function applyGrokOverrides(info) {
|
|
|
381
383
|
}
|
|
382
384
|
info.modelOrder = buildOrder(info, info.defaultModel);
|
|
383
385
|
}
|
|
386
|
+
function applyGrokConfig(info) {
|
|
387
|
+
const configPath = process.env.GROK_CONFIG_PATH || path.join(homedir(), ".grok", "config.toml");
|
|
388
|
+
if (!existsSync(configPath)) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
try {
|
|
392
|
+
const parsed = parseToml(readFileSync(configPath, "utf-8"));
|
|
393
|
+
const models = readRecordProperty(parsed, "models");
|
|
394
|
+
Object.entries(models).forEach(([key, value]) => {
|
|
395
|
+
if (typeof value !== "string") {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (key === "default") {
|
|
399
|
+
setDefaultModel(info, value, `${configPath} [models].default`, "config");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
addModel(info, value, `Custom Grok model from config.toml [models].${key}`, {
|
|
403
|
+
source: "config",
|
|
404
|
+
sourceDetail: `${configPath} [models].${key}`,
|
|
405
|
+
confidence: "medium",
|
|
406
|
+
});
|
|
407
|
+
});
|
|
408
|
+
const ui = readRecordProperty(parsed, "ui");
|
|
409
|
+
const forkModel = readStringProperty(ui, "fork_secondary_model");
|
|
410
|
+
if (forkModel) {
|
|
411
|
+
addModel(info, forkModel, "Configured Grok fork/secondary model", {
|
|
412
|
+
source: "config",
|
|
413
|
+
sourceDetail: `${configPath} [ui].fork_secondary_model`,
|
|
414
|
+
confidence: "medium",
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (error) {
|
|
419
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
420
|
+
addWarning(info, `Could not parse Grok config ${configPath}: ${message}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
384
423
|
function applyMistralOverrides(info) {
|
|
385
424
|
const vibeConfig = readVibeConfig(info);
|
|
425
|
+
if (vibeConfig.defaultAgent) {
|
|
426
|
+
info.defaultAgent = vibeConfig.defaultAgent;
|
|
427
|
+
}
|
|
386
428
|
addVibeModelEntries(info, parseVibeModels(process.env.VIBE_MODELS, "VIBE_MODELS"), "env");
|
|
387
429
|
addVibeModelEntries(info, vibeConfig.models, "config");
|
|
388
430
|
addEnvModels(info, "MISTRAL_MODELS");
|
|
@@ -426,6 +468,10 @@ function readVibeConfig(info) {
|
|
|
426
468
|
if (activeModel) {
|
|
427
469
|
result.activeModel = activeModel.trim();
|
|
428
470
|
}
|
|
471
|
+
const defaultAgent = readStringProperty(parsed, "default_agent");
|
|
472
|
+
if (defaultAgent) {
|
|
473
|
+
result.defaultAgent = defaultAgent.trim();
|
|
474
|
+
}
|
|
429
475
|
result.models = parseVibeModelArray(readRecordOrArrayProperty(parsed, "models"), configPath);
|
|
430
476
|
}
|
|
431
477
|
catch (error) {
|
package/dist/oauth.js
CHANGED
|
@@ -273,7 +273,7 @@ export class OAuthServer {
|
|
|
273
273
|
return {
|
|
274
274
|
resource: joinBaseAndPath(baseUrl, this.opts.protectedPath),
|
|
275
275
|
authorization_servers: [baseUrl],
|
|
276
|
-
scopes_supported: ["mcp", "workspace:admin"],
|
|
276
|
+
scopes_supported: ["mcp", "workspace:admin", "cli:admin"],
|
|
277
277
|
bearer_methods_supported: ["header"],
|
|
278
278
|
};
|
|
279
279
|
}
|
|
@@ -291,7 +291,7 @@ export class OAuthServer {
|
|
|
291
291
|
code_challenge_methods_supported: this.opts.config.allowPlainPkce
|
|
292
292
|
? ["S256", "plain"]
|
|
293
293
|
: ["S256"],
|
|
294
|
-
scopes_supported: ["mcp", "workspace:admin"],
|
|
294
|
+
scopes_supported: ["mcp", "workspace:admin", "cli:admin"],
|
|
295
295
|
};
|
|
296
296
|
}
|
|
297
297
|
registrationAllowedByPolicy(req, params) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
let pool = null;
|
|
4
|
+
const PG_NOW_MS = "(EXTRACT(EPOCH FROM now()) * 1000)::bigint";
|
|
5
|
+
function signal(shared, status) {
|
|
6
|
+
const view = new Int32Array(shared);
|
|
7
|
+
Atomics.store(view, 0, status);
|
|
8
|
+
Atomics.notify(view, 0, 1);
|
|
9
|
+
}
|
|
10
|
+
function ok(value) {
|
|
11
|
+
return { ok: true, value };
|
|
12
|
+
}
|
|
13
|
+
function fail(error) {
|
|
14
|
+
return {
|
|
15
|
+
ok: false,
|
|
16
|
+
error: {
|
|
17
|
+
message: error instanceof Error ? error.message : String(error),
|
|
18
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
function getPool() {
|
|
23
|
+
if (!pool)
|
|
24
|
+
throw new Error("PostgresJobStore worker pool is not initialized");
|
|
25
|
+
return pool;
|
|
26
|
+
}
|
|
27
|
+
async function withClient(fn) {
|
|
28
|
+
const client = await getPool().connect();
|
|
29
|
+
try {
|
|
30
|
+
return await fn(client);
|
|
31
|
+
}
|
|
32
|
+
finally {
|
|
33
|
+
client.release();
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function init() {
|
|
37
|
+
let pg;
|
|
38
|
+
try {
|
|
39
|
+
pg = await import("pg");
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
const code = error?.code;
|
|
43
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
44
|
+
throw new Error("Postgres persistence requires optional peer dependency 'pg'. Install it alongside llm-cli-gateway to use backend = 'postgres'.", { cause: error });
|
|
45
|
+
}
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
pool = new pg.Pool({
|
|
49
|
+
connectionString: workerData.dsn,
|
|
50
|
+
connectionTimeoutMillis: workerData.connectionTimeoutMillis,
|
|
51
|
+
});
|
|
52
|
+
await getPool().query("SELECT 1");
|
|
53
|
+
await getPool().query(`
|
|
54
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
55
|
+
id TEXT PRIMARY KEY,
|
|
56
|
+
correlation_id TEXT NOT NULL,
|
|
57
|
+
request_key TEXT NOT NULL,
|
|
58
|
+
cli TEXT NOT NULL,
|
|
59
|
+
args_json TEXT NOT NULL,
|
|
60
|
+
output_format TEXT,
|
|
61
|
+
status TEXT NOT NULL,
|
|
62
|
+
exit_code INTEGER,
|
|
63
|
+
stdout TEXT,
|
|
64
|
+
stderr TEXT,
|
|
65
|
+
output_truncated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
66
|
+
error TEXT,
|
|
67
|
+
started_at TEXT NOT NULL,
|
|
68
|
+
finished_at TEXT,
|
|
69
|
+
pid INTEGER,
|
|
70
|
+
expires_at TEXT NOT NULL,
|
|
71
|
+
owner_principal TEXT,
|
|
72
|
+
transport TEXT NOT NULL DEFAULT 'process',
|
|
73
|
+
http_status INTEGER,
|
|
74
|
+
payload_json TEXT,
|
|
75
|
+
owner_instance TEXT,
|
|
76
|
+
lease_deadline BIGINT
|
|
77
|
+
);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_request_key ON jobs(request_key);
|
|
79
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
80
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_expires_at ON jobs(expires_at);
|
|
81
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_request_key_finished ON jobs(request_key, finished_at);
|
|
82
|
+
|
|
83
|
+
CREATE TABLE IF NOT EXISTS gateway_instances (
|
|
84
|
+
instance_id TEXT PRIMARY KEY,
|
|
85
|
+
role TEXT,
|
|
86
|
+
hostname TEXT,
|
|
87
|
+
pid INTEGER,
|
|
88
|
+
started_at BIGINT NOT NULL,
|
|
89
|
+
last_heartbeat BIGINT NOT NULL
|
|
90
|
+
);
|
|
91
|
+
CREATE INDEX IF NOT EXISTS idx_gateway_instances_heartbeat
|
|
92
|
+
ON gateway_instances(last_heartbeat);
|
|
93
|
+
|
|
94
|
+
CREATE TABLE IF NOT EXISTS validation_runs (
|
|
95
|
+
validation_id TEXT PRIMARY KEY,
|
|
96
|
+
owner_principal TEXT NOT NULL,
|
|
97
|
+
intent TEXT NOT NULL,
|
|
98
|
+
created_at TEXT NOT NULL,
|
|
99
|
+
request_json TEXT NOT NULL,
|
|
100
|
+
provider_links TEXT NOT NULL,
|
|
101
|
+
judge_link TEXT,
|
|
102
|
+
status TEXT NOT NULL
|
|
103
|
+
);
|
|
104
|
+
CREATE INDEX IF NOT EXISTS idx_validation_runs_owner ON validation_runs(owner_principal);
|
|
105
|
+
|
|
106
|
+
CREATE TABLE IF NOT EXISTS validation_run_jobs (
|
|
107
|
+
job_id TEXT PRIMARY KEY,
|
|
108
|
+
validation_id TEXT NOT NULL,
|
|
109
|
+
role TEXT NOT NULL
|
|
110
|
+
);
|
|
111
|
+
CREATE INDEX IF NOT EXISTS idx_validation_run_jobs_run ON validation_run_jobs(validation_id);
|
|
112
|
+
|
|
113
|
+
CREATE TABLE IF NOT EXISTS validation_receipts (
|
|
114
|
+
validation_id TEXT PRIMARY KEY,
|
|
115
|
+
owner_principal TEXT NOT NULL,
|
|
116
|
+
minted_at TEXT NOT NULL,
|
|
117
|
+
schema_version TEXT NOT NULL,
|
|
118
|
+
report_json TEXT NOT NULL,
|
|
119
|
+
canonical_sha256 TEXT NOT NULL,
|
|
120
|
+
prev_sha256 TEXT,
|
|
121
|
+
seq INTEGER,
|
|
122
|
+
signature TEXT,
|
|
123
|
+
models TEXT NOT NULL,
|
|
124
|
+
has_material_disagreement BOOLEAN NOT NULL,
|
|
125
|
+
confidence TEXT NOT NULL
|
|
126
|
+
);
|
|
127
|
+
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)");
|
|
136
|
+
}
|
|
137
|
+
async function op(method, args) {
|
|
138
|
+
switch (method) {
|
|
139
|
+
case "init":
|
|
140
|
+
await init();
|
|
141
|
+
return null;
|
|
142
|
+
case "recordStart": {
|
|
143
|
+
const input = args[0];
|
|
144
|
+
await getPool().query(`INSERT INTO jobs (id, correlation_id, request_key, cli, args_json, output_format,
|
|
145
|
+
status, exit_code, stdout, stderr, output_truncated, error,
|
|
146
|
+
started_at, finished_at, pid, expires_at, owner_principal,
|
|
147
|
+
transport, http_status, payload_json, owner_instance, lease_deadline)
|
|
148
|
+
VALUES ($1, $2, $3, $4, $5, $6, 'queued', NULL, '', '', FALSE, NULL,
|
|
149
|
+
$7, NULL, $8, $9, $10, $11, NULL, $12, $13, ${PG_NOW_MS} + $14)`, [
|
|
150
|
+
input.id,
|
|
151
|
+
input.correlationId,
|
|
152
|
+
input.requestKey,
|
|
153
|
+
input.cli,
|
|
154
|
+
JSON.stringify(input.args),
|
|
155
|
+
input.outputFormat ?? null,
|
|
156
|
+
input.startedAt,
|
|
157
|
+
input.pid,
|
|
158
|
+
workerData.farFutureIso,
|
|
159
|
+
input.ownerPrincipal ?? null,
|
|
160
|
+
input.transport ?? "process",
|
|
161
|
+
input.payloadJson ?? null,
|
|
162
|
+
input.ownerInstance ?? null,
|
|
163
|
+
workerData.leaseTtlMs,
|
|
164
|
+
]);
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
case "markRunning": {
|
|
168
|
+
const [id, opts] = args;
|
|
169
|
+
const result = await getPool().query(`UPDATE jobs
|
|
170
|
+
SET status = 'running', pid = $2, lease_deadline = ${PG_NOW_MS} + $3::bigint
|
|
171
|
+
WHERE id = $1 AND status = 'queued'`, [id, opts?.pid ?? null, workerData.leaseTtlMs]);
|
|
172
|
+
return (result.rowCount ?? 0) > 0;
|
|
173
|
+
}
|
|
174
|
+
case "registerInstance": {
|
|
175
|
+
const meta = args[0];
|
|
176
|
+
await getPool().query(`INSERT INTO gateway_instances (instance_id, role, hostname, pid, started_at, last_heartbeat)
|
|
177
|
+
VALUES ($1, $2, $3, $4, ${PG_NOW_MS}, ${PG_NOW_MS})
|
|
178
|
+
ON CONFLICT (instance_id) DO UPDATE SET
|
|
179
|
+
role = EXCLUDED.role, hostname = EXCLUDED.hostname, pid = EXCLUDED.pid,
|
|
180
|
+
last_heartbeat = EXCLUDED.last_heartbeat`, [meta.instanceId, meta.role ?? null, meta.hostname ?? null, meta.pid ?? null]);
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
case "heartbeat": {
|
|
184
|
+
const instanceId = args[0];
|
|
185
|
+
await withClient(async (client) => {
|
|
186
|
+
await client.query("BEGIN");
|
|
187
|
+
try {
|
|
188
|
+
await client.query(`UPDATE gateway_instances SET last_heartbeat = ${PG_NOW_MS} WHERE instance_id = $1`, [instanceId]);
|
|
189
|
+
await client.query(`UPDATE jobs SET lease_deadline = ${PG_NOW_MS} + $2
|
|
190
|
+
WHERE owner_instance = $1 AND status IN ('queued', 'running')`, [instanceId, workerData.leaseTtlMs]);
|
|
191
|
+
await client.query("COMMIT");
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
await client.query("ROLLBACK");
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
case "deregisterInstance": {
|
|
201
|
+
await getPool().query("DELETE FROM gateway_instances WHERE instance_id = $1", [args[0]]);
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
case "selectStaleProcessCandidates": {
|
|
205
|
+
const result = await getPool().query(`SELECT j.id AS id, j.pid AS pid, j.transport AS transport,
|
|
206
|
+
j.owner_instance AS owner_instance, gi.hostname AS hostname
|
|
207
|
+
FROM jobs j
|
|
208
|
+
LEFT JOIN gateway_instances gi ON gi.instance_id = j.owner_instance
|
|
209
|
+
WHERE j.status IN ('queued', 'running')
|
|
210
|
+
AND j.transport = 'process'
|
|
211
|
+
AND j.pid IS NOT NULL
|
|
212
|
+
AND (j.lease_deadline IS NULL OR j.lease_deadline < ${PG_NOW_MS})`);
|
|
213
|
+
return result.rows.map(r => ({
|
|
214
|
+
id: r.id,
|
|
215
|
+
pid: r.pid,
|
|
216
|
+
transport: r.transport ?? "process",
|
|
217
|
+
ownerInstance: r.owner_instance ?? null,
|
|
218
|
+
hostname: r.hostname ?? null,
|
|
219
|
+
}));
|
|
220
|
+
}
|
|
221
|
+
case "recoverStaleJobs": {
|
|
222
|
+
const [leaseTtlMs, httpJobGraceMs, liveConfirmedIds] = args;
|
|
223
|
+
const excludeIds = liveConfirmedIds ?? [];
|
|
224
|
+
const httpGraceCutoffMs = "(" + PG_NOW_MS + " - $1::bigint)";
|
|
225
|
+
return await withClient(async (client) => {
|
|
226
|
+
await client.query("BEGIN");
|
|
227
|
+
try {
|
|
228
|
+
if (excludeIds.length > 0) {
|
|
229
|
+
await client.query(`UPDATE jobs SET lease_deadline = ${PG_NOW_MS} + $1::bigint, pid = NULL
|
|
230
|
+
WHERE status IN ('queued', 'running') AND id = ANY($2::text[])`, [leaseTtlMs, excludeIds]);
|
|
231
|
+
}
|
|
232
|
+
const orphan = await client.query(`UPDATE jobs
|
|
233
|
+
SET status = 'orphaned',
|
|
234
|
+
error = COALESCE(error, 'owning gateway instance is no longer alive'),
|
|
235
|
+
finished_at = COALESCE(finished_at, $2),
|
|
236
|
+
expires_at = $3,
|
|
237
|
+
lease_deadline = NULL
|
|
238
|
+
WHERE status IN ('queued', 'running')
|
|
239
|
+
AND (lease_deadline IS NULL OR lease_deadline < ${PG_NOW_MS})
|
|
240
|
+
AND (transport <> 'http'
|
|
241
|
+
OR (EXTRACT(EPOCH FROM started_at::timestamptz) * 1000)::bigint < ${httpGraceCutoffMs})
|
|
242
|
+
AND NOT (id = ANY($4::text[]))
|
|
243
|
+
RETURNING id, correlation_id, started_at, stdout, stderr, exit_code, transport, http_status`, [
|
|
244
|
+
httpJobGraceMs,
|
|
245
|
+
new Date().toISOString(),
|
|
246
|
+
new Date(Date.now() + workerData.retentionMs).toISOString(),
|
|
247
|
+
excludeIds,
|
|
248
|
+
]);
|
|
249
|
+
await client.query("COMMIT");
|
|
250
|
+
return { orphaned: orphan.rows };
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
await client.query("ROLLBACK");
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
case "gcInstances": {
|
|
259
|
+
const result = await getPool().query(`DELETE FROM gateway_instances WHERE last_heartbeat < ${PG_NOW_MS} - $1::bigint`, [args[0]]);
|
|
260
|
+
return result.rowCount ?? 0;
|
|
261
|
+
}
|
|
262
|
+
case "recordOutput":
|
|
263
|
+
await getPool().query("UPDATE jobs SET stdout = $2, stderr = $3, output_truncated = $4 WHERE id = $1", args);
|
|
264
|
+
return null;
|
|
265
|
+
case "recordComplete": {
|
|
266
|
+
const input = args[0];
|
|
267
|
+
const expiresAt = new Date(Date.parse(input.finishedAt) + workerData.retentionMs).toISOString();
|
|
268
|
+
await getPool().query(`UPDATE jobs
|
|
269
|
+
SET status = $2, exit_code = $3, stdout = $4, stderr = $5,
|
|
270
|
+
output_truncated = $6, error = $7, finished_at = $8,
|
|
271
|
+
expires_at = $9, http_status = $10, lease_deadline = NULL
|
|
272
|
+
WHERE id = $1 AND status IN ('queued', 'running', 'orphaned')`, [
|
|
273
|
+
input.id,
|
|
274
|
+
input.status,
|
|
275
|
+
input.exitCode,
|
|
276
|
+
input.stdout,
|
|
277
|
+
input.stderr,
|
|
278
|
+
input.outputTruncated,
|
|
279
|
+
input.error,
|
|
280
|
+
input.finishedAt,
|
|
281
|
+
expiresAt,
|
|
282
|
+
input.httpStatus ?? null,
|
|
283
|
+
]);
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
case "getById": {
|
|
287
|
+
const result = await getPool().query("SELECT * FROM jobs WHERE id = $1", [args[0]]);
|
|
288
|
+
return result.rows[0] ?? null;
|
|
289
|
+
}
|
|
290
|
+
case "findByRequestKey": {
|
|
291
|
+
const cutoff = new Date(Date.now() - workerData.dedupWindowMs).toISOString();
|
|
292
|
+
const result = await getPool().query(`SELECT * FROM jobs
|
|
293
|
+
WHERE request_key = $1
|
|
294
|
+
AND started_at >= $2
|
|
295
|
+
AND (
|
|
296
|
+
status IN ('running', 'completed')
|
|
297
|
+
OR (status = 'queued' AND lease_deadline IS NOT NULL AND lease_deadline >= ${PG_NOW_MS})
|
|
298
|
+
)
|
|
299
|
+
ORDER BY started_at DESC
|
|
300
|
+
LIMIT 1`, [args[0], cutoff]);
|
|
301
|
+
return result.rows[0] ?? null;
|
|
302
|
+
}
|
|
303
|
+
case "markOrphanedOnStartup":
|
|
304
|
+
return await withClient(async (client) => {
|
|
305
|
+
const now = new Date().toISOString();
|
|
306
|
+
const expiresAt = new Date(Date.now() + workerData.retentionMs).toISOString();
|
|
307
|
+
await client.query("BEGIN");
|
|
308
|
+
try {
|
|
309
|
+
const rows = await client.query("SELECT id, correlation_id, started_at, stdout, stderr, exit_code, transport, http_status FROM jobs WHERE status = 'running'");
|
|
310
|
+
const update = await client.query(`UPDATE jobs
|
|
311
|
+
SET status = 'orphaned',
|
|
312
|
+
error = COALESCE(error, 'Gateway restarted while job was running'),
|
|
313
|
+
finished_at = COALESCE(finished_at, $1),
|
|
314
|
+
expires_at = $2
|
|
315
|
+
WHERE status = 'running'`, [now, expiresAt]);
|
|
316
|
+
await client.query("COMMIT");
|
|
317
|
+
return { count: update.rowCount ?? 0, orphaned: rows.rows };
|
|
318
|
+
}
|
|
319
|
+
catch (error) {
|
|
320
|
+
await client.query("ROLLBACK");
|
|
321
|
+
throw error;
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
case "evictExpired": {
|
|
325
|
+
const result = await getPool().query("DELETE FROM jobs WHERE expires_at < $1", [
|
|
326
|
+
new Date().toISOString(),
|
|
327
|
+
]);
|
|
328
|
+
return result.rowCount ?? 0;
|
|
329
|
+
}
|
|
330
|
+
case "recordValidationRun": {
|
|
331
|
+
const run = args[0];
|
|
332
|
+
await withClient(async (client) => {
|
|
333
|
+
await client.query("BEGIN");
|
|
334
|
+
try {
|
|
335
|
+
await client.query(`INSERT INTO validation_runs
|
|
336
|
+
(validation_id, owner_principal, intent, created_at, request_json,
|
|
337
|
+
provider_links, judge_link, status)
|
|
338
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
339
|
+
ON CONFLICT (validation_id) DO NOTHING`, [
|
|
340
|
+
run.validationId,
|
|
341
|
+
run.ownerPrincipal,
|
|
342
|
+
run.intent,
|
|
343
|
+
run.createdAt,
|
|
344
|
+
run.requestJson,
|
|
345
|
+
JSON.stringify(run.providerLinks),
|
|
346
|
+
run.judgeLink ? JSON.stringify(run.judgeLink) : null,
|
|
347
|
+
run.status,
|
|
348
|
+
]);
|
|
349
|
+
for (const link of run.providerLinks) {
|
|
350
|
+
await client.query(`INSERT INTO validation_run_jobs (job_id, validation_id, role)
|
|
351
|
+
VALUES ($1, $2, 'provider')
|
|
352
|
+
ON CONFLICT (job_id) DO NOTHING`, [link.jobId, run.validationId]);
|
|
353
|
+
}
|
|
354
|
+
await client.query("COMMIT");
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
await client.query("ROLLBACK");
|
|
358
|
+
throw error;
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
case "getValidationRun": {
|
|
364
|
+
const result = await getPool().query("SELECT * FROM validation_runs WHERE validation_id = $1", [args[0]]);
|
|
365
|
+
return result.rows[0] ?? null;
|
|
366
|
+
}
|
|
367
|
+
case "setValidationJudgeLink": {
|
|
368
|
+
const [validationId, judgeLink] = args;
|
|
369
|
+
await withClient(async (client) => {
|
|
370
|
+
await client.query("BEGIN");
|
|
371
|
+
try {
|
|
372
|
+
await client.query("UPDATE validation_runs SET judge_link = $2 WHERE validation_id = $1", [validationId, JSON.stringify(judgeLink)]);
|
|
373
|
+
await client.query(`INSERT INTO validation_run_jobs (job_id, validation_id, role)
|
|
374
|
+
VALUES ($1, $2, 'judge')
|
|
375
|
+
ON CONFLICT (job_id) DO NOTHING`, [judgeLink.jobId, validationId]);
|
|
376
|
+
await client.query("COMMIT");
|
|
377
|
+
}
|
|
378
|
+
catch (error) {
|
|
379
|
+
await client.query("ROLLBACK");
|
|
380
|
+
throw error;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
case "setValidationRunStatus":
|
|
386
|
+
await getPool().query("UPDATE validation_runs SET status = $2 WHERE validation_id = $1", args);
|
|
387
|
+
return null;
|
|
388
|
+
case "getValidationRunIdByJobId": {
|
|
389
|
+
const result = await getPool().query("SELECT validation_id FROM validation_run_jobs WHERE job_id = $1", [args[0]]);
|
|
390
|
+
return result.rows[0] ? result.rows[0].validation_id : null;
|
|
391
|
+
}
|
|
392
|
+
case "recordValidationReceipt": {
|
|
393
|
+
const receipt = args[0];
|
|
394
|
+
await getPool().query(`INSERT INTO validation_receipts
|
|
395
|
+
(validation_id, owner_principal, minted_at, schema_version, report_json,
|
|
396
|
+
canonical_sha256, prev_sha256, seq, signature, models,
|
|
397
|
+
has_material_disagreement, confidence)
|
|
398
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
399
|
+
ON CONFLICT (validation_id) DO NOTHING`, [
|
|
400
|
+
receipt.validationId,
|
|
401
|
+
receipt.ownerPrincipal,
|
|
402
|
+
receipt.mintedAt,
|
|
403
|
+
receipt.schemaVersion,
|
|
404
|
+
receipt.reportJson,
|
|
405
|
+
receipt.canonicalSha256,
|
|
406
|
+
receipt.prevSha256,
|
|
407
|
+
receipt.seq,
|
|
408
|
+
receipt.signature,
|
|
409
|
+
JSON.stringify(receipt.models),
|
|
410
|
+
receipt.hasMaterialDisagreement,
|
|
411
|
+
receipt.confidence,
|
|
412
|
+
]);
|
|
413
|
+
return null;
|
|
414
|
+
}
|
|
415
|
+
case "getValidationReceipt": {
|
|
416
|
+
const result = await getPool().query("SELECT * FROM validation_receipts WHERE validation_id = $1", [args[0]]);
|
|
417
|
+
return result.rows[0] ?? null;
|
|
418
|
+
}
|
|
419
|
+
case "close":
|
|
420
|
+
if (pool)
|
|
421
|
+
await pool.end();
|
|
422
|
+
pool = null;
|
|
423
|
+
return null;
|
|
424
|
+
default:
|
|
425
|
+
throw new Error(`Unknown PostgresJobStore worker method: ${method}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
parentPort?.on("message", async (message) => {
|
|
429
|
+
const { method, args, resultPath, shared } = message;
|
|
430
|
+
let payload;
|
|
431
|
+
try {
|
|
432
|
+
payload = ok(await op(method, args));
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
payload = fail(error);
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
writeFileSync(resultPath, JSON.stringify(payload));
|
|
439
|
+
signal(shared, 1);
|
|
440
|
+
}
|
|
441
|
+
catch {
|
|
442
|
+
signal(shared, 2);
|
|
443
|
+
}
|
|
444
|
+
});
|
package/dist/pricing.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import type { CliType } from "./provider-types.js";
|
|
1
2
|
export interface PricePerMillion {
|
|
2
3
|
inputUsd: number;
|
|
3
4
|
outputUsd: number;
|
|
4
5
|
cacheReadMultiplier: number;
|
|
5
6
|
}
|
|
6
7
|
export declare const PRICING_AS_OF = "2026-06-13";
|
|
7
|
-
export declare function getPricing(cli:
|
|
8
|
-
export declare function estimateCacheSavingsUsd(cli:
|
|
8
|
+
export declare function getPricing(cli: CliType, model: string): PricePerMillion;
|
|
9
|
+
export declare function estimateCacheSavingsUsd(cli: CliType, model: string, cacheReadTokens: number): number;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AcpConfig } from "./config.js";
|
|
2
|
+
import type { ParsedAcpInitialize } from "./provider-capability-discovery.js";
|
|
3
|
+
import type { CliType } from "./session-manager.js";
|
|
4
|
+
export interface AcpHostServicePolicy {
|
|
5
|
+
readonly filesystemRead: "deny-by-default";
|
|
6
|
+
readonly filesystemWriteAllowed: boolean;
|
|
7
|
+
readonly terminalAllowed: boolean;
|
|
8
|
+
readonly mutatingSessionOpsAllowed: boolean;
|
|
9
|
+
readonly permissionRouting: "approval-manager";
|
|
10
|
+
readonly unknownToolKind: "deny-by-default";
|
|
11
|
+
}
|
|
12
|
+
export interface ProviderAcpInitializeDetail {
|
|
13
|
+
readonly source: "discovered" | "static-fallback";
|
|
14
|
+
readonly degraded: boolean;
|
|
15
|
+
readonly protocolVersion: number | string | null;
|
|
16
|
+
readonly agentInfo: {
|
|
17
|
+
readonly name?: string;
|
|
18
|
+
readonly version?: string;
|
|
19
|
+
} | null;
|
|
20
|
+
readonly authMethods: readonly string[];
|
|
21
|
+
readonly promptCapabilities: readonly string[];
|
|
22
|
+
readonly mcpCapabilities: readonly string[];
|
|
23
|
+
readonly sessionCapabilities: readonly string[];
|
|
24
|
+
readonly extensionMethods: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface ProviderAcpCapabilityRecord {
|
|
27
|
+
readonly schemaVersion: "provider-acp-capability.v1";
|
|
28
|
+
readonly provider: CliType;
|
|
29
|
+
readonly displayName: string;
|
|
30
|
+
readonly native: boolean;
|
|
31
|
+
readonly nativeEntrypoint: string | null;
|
|
32
|
+
readonly entrypoint: {
|
|
33
|
+
readonly command: string;
|
|
34
|
+
readonly args: readonly string[];
|
|
35
|
+
} | null;
|
|
36
|
+
readonly probeArgv: readonly (readonly string[])[];
|
|
37
|
+
readonly agentTypes: readonly {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly description: string;
|
|
40
|
+
}[];
|
|
41
|
+
readonly evidence: string;
|
|
42
|
+
readonly initialize: ProviderAcpInitializeDetail | null;
|
|
43
|
+
readonly supportedSessionMethods: readonly string[];
|
|
44
|
+
readonly hostServicePolicy: AcpHostServicePolicy;
|
|
45
|
+
}
|
|
46
|
+
export declare function deriveSupportedMethodsFromDiscovered(parsed: ParsedAcpInitialize): readonly string[];
|
|
47
|
+
export interface BuildProviderAcpRecordOptions {
|
|
48
|
+
readonly discovered?: ParsedAcpInitialize | null;
|
|
49
|
+
readonly discoveredDegraded?: boolean;
|
|
50
|
+
readonly acpConfig?: AcpConfig | null;
|
|
51
|
+
}
|
|
52
|
+
export declare function buildProviderAcpCapabilityRecord(provider: CliType, options?: BuildProviderAcpRecordOptions): ProviderAcpCapabilityRecord;
|