llm-cli-gateway 2.13.1 → 2.14.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +140 -0
- package/README.md +53 -26
- 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 +2 -0
- package/dist/async-job-manager.js +11 -0
- package/dist/codex-json-parser.d.ts +1 -0
- package/dist/codex-json-parser.js +6 -0
- package/dist/config.d.ts +16 -0
- package/dist/config.js +105 -19
- package/dist/connector-setup.d.ts +39 -0
- package/dist/connector-setup.js +86 -0
- package/dist/doctor.d.ts +39 -1
- package/dist/doctor.js +182 -3
- 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/http-transport.js +27 -3
- package/dist/index.d.ts +48 -2
- package/dist/index.js +633 -144
- package/dist/job-store.d.ts +45 -7
- package/dist/job-store.js +138 -17
- package/dist/model-registry.d.ts +1 -0
- package/dist/model-registry.js +46 -0
- package/dist/oauth.js +17 -16
- package/dist/postgres-job-store-worker.d.ts +1 -0
- package/dist/postgres-job-store-worker.js +327 -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/remote-url.d.ts +28 -0
- package/dist/remote-url.js +61 -0
- 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 +67 -193
- package/dist/session-manager.d.ts +2 -0
- package/dist/session-manager.js +34 -8
- 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/dist/workspace-registry.d.ts +9 -0
- package/dist/workspace-registry.js +24 -4
- package/npm-shrinkwrap.json +2 -2
- package/package.json +8 -3
- package/setup/status.schema.json +75 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs";
|
|
2
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
3
|
+
let pool = null;
|
|
4
|
+
function signal(shared, status) {
|
|
5
|
+
const view = new Int32Array(shared);
|
|
6
|
+
Atomics.store(view, 0, status);
|
|
7
|
+
Atomics.notify(view, 0, 1);
|
|
8
|
+
}
|
|
9
|
+
function ok(value) {
|
|
10
|
+
return { ok: true, value };
|
|
11
|
+
}
|
|
12
|
+
function fail(error) {
|
|
13
|
+
return {
|
|
14
|
+
ok: false,
|
|
15
|
+
error: {
|
|
16
|
+
message: error instanceof Error ? error.message : String(error),
|
|
17
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function getPool() {
|
|
22
|
+
if (!pool)
|
|
23
|
+
throw new Error("PostgresJobStore worker pool is not initialized");
|
|
24
|
+
return pool;
|
|
25
|
+
}
|
|
26
|
+
async function withClient(fn) {
|
|
27
|
+
const client = await getPool().connect();
|
|
28
|
+
try {
|
|
29
|
+
return await fn(client);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
client.release();
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function init() {
|
|
36
|
+
let pg;
|
|
37
|
+
try {
|
|
38
|
+
pg = await import("pg");
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
const code = error?.code;
|
|
42
|
+
if (code === "ERR_MODULE_NOT_FOUND" || code === "MODULE_NOT_FOUND") {
|
|
43
|
+
throw new Error("Postgres persistence requires optional peer dependency 'pg'. Install it alongside llm-cli-gateway to use backend = 'postgres'.", { cause: error });
|
|
44
|
+
}
|
|
45
|
+
throw error;
|
|
46
|
+
}
|
|
47
|
+
pool = new pg.Pool({
|
|
48
|
+
connectionString: workerData.dsn,
|
|
49
|
+
connectionTimeoutMillis: workerData.connectionTimeoutMillis,
|
|
50
|
+
});
|
|
51
|
+
await getPool().query("SELECT 1");
|
|
52
|
+
await getPool().query(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
correlation_id TEXT NOT NULL,
|
|
56
|
+
request_key TEXT NOT NULL,
|
|
57
|
+
cli TEXT NOT NULL,
|
|
58
|
+
args_json TEXT NOT NULL,
|
|
59
|
+
output_format TEXT,
|
|
60
|
+
status TEXT NOT NULL,
|
|
61
|
+
exit_code INTEGER,
|
|
62
|
+
stdout TEXT,
|
|
63
|
+
stderr TEXT,
|
|
64
|
+
output_truncated BOOLEAN NOT NULL DEFAULT FALSE,
|
|
65
|
+
error TEXT,
|
|
66
|
+
started_at TEXT NOT NULL,
|
|
67
|
+
finished_at TEXT,
|
|
68
|
+
pid INTEGER,
|
|
69
|
+
expires_at TEXT NOT NULL,
|
|
70
|
+
owner_principal TEXT,
|
|
71
|
+
transport TEXT NOT NULL DEFAULT 'process',
|
|
72
|
+
http_status INTEGER,
|
|
73
|
+
payload_json TEXT
|
|
74
|
+
);
|
|
75
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_request_key ON jobs(request_key);
|
|
76
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_expires_at ON jobs(expires_at);
|
|
78
|
+
CREATE INDEX IF NOT EXISTS idx_jobs_request_key_finished ON jobs(request_key, finished_at);
|
|
79
|
+
|
|
80
|
+
CREATE TABLE IF NOT EXISTS validation_runs (
|
|
81
|
+
validation_id TEXT PRIMARY KEY,
|
|
82
|
+
owner_principal TEXT NOT NULL,
|
|
83
|
+
intent TEXT NOT NULL,
|
|
84
|
+
created_at TEXT NOT NULL,
|
|
85
|
+
request_json TEXT NOT NULL,
|
|
86
|
+
provider_links TEXT NOT NULL,
|
|
87
|
+
judge_link TEXT,
|
|
88
|
+
status TEXT NOT NULL
|
|
89
|
+
);
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_validation_runs_owner ON validation_runs(owner_principal);
|
|
91
|
+
|
|
92
|
+
CREATE TABLE IF NOT EXISTS validation_run_jobs (
|
|
93
|
+
job_id TEXT PRIMARY KEY,
|
|
94
|
+
validation_id TEXT NOT NULL,
|
|
95
|
+
role TEXT NOT NULL
|
|
96
|
+
);
|
|
97
|
+
CREATE INDEX IF NOT EXISTS idx_validation_run_jobs_run ON validation_run_jobs(validation_id);
|
|
98
|
+
|
|
99
|
+
CREATE TABLE IF NOT EXISTS validation_receipts (
|
|
100
|
+
validation_id TEXT PRIMARY KEY,
|
|
101
|
+
owner_principal TEXT NOT NULL,
|
|
102
|
+
minted_at TEXT NOT NULL,
|
|
103
|
+
schema_version TEXT NOT NULL,
|
|
104
|
+
report_json TEXT NOT NULL,
|
|
105
|
+
canonical_sha256 TEXT NOT NULL,
|
|
106
|
+
prev_sha256 TEXT,
|
|
107
|
+
seq INTEGER,
|
|
108
|
+
signature TEXT,
|
|
109
|
+
models TEXT NOT NULL,
|
|
110
|
+
has_material_disagreement BOOLEAN NOT NULL,
|
|
111
|
+
confidence TEXT NOT NULL
|
|
112
|
+
);
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_validation_receipts_owner ON validation_receipts(owner_principal);
|
|
114
|
+
`);
|
|
115
|
+
await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS owner_principal TEXT");
|
|
116
|
+
await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS transport TEXT NOT NULL DEFAULT 'process'");
|
|
117
|
+
await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS http_status INTEGER");
|
|
118
|
+
await getPool().query("ALTER TABLE jobs ADD COLUMN IF NOT EXISTS payload_json TEXT");
|
|
119
|
+
}
|
|
120
|
+
async function op(method, args) {
|
|
121
|
+
switch (method) {
|
|
122
|
+
case "init":
|
|
123
|
+
await init();
|
|
124
|
+
return null;
|
|
125
|
+
case "recordStart": {
|
|
126
|
+
const input = args[0];
|
|
127
|
+
await getPool().query(`INSERT INTO jobs (id, correlation_id, request_key, cli, args_json, output_format,
|
|
128
|
+
status, exit_code, stdout, stderr, output_truncated, error,
|
|
129
|
+
started_at, finished_at, pid, expires_at, owner_principal,
|
|
130
|
+
transport, http_status, payload_json)
|
|
131
|
+
VALUES ($1, $2, $3, $4, $5, $6, 'running', NULL, '', '', FALSE, NULL,
|
|
132
|
+
$7, NULL, $8, $9, $10, $11, NULL, $12)`, [
|
|
133
|
+
input.id,
|
|
134
|
+
input.correlationId,
|
|
135
|
+
input.requestKey,
|
|
136
|
+
input.cli,
|
|
137
|
+
JSON.stringify(input.args),
|
|
138
|
+
input.outputFormat ?? null,
|
|
139
|
+
input.startedAt,
|
|
140
|
+
input.pid,
|
|
141
|
+
workerData.farFutureIso,
|
|
142
|
+
input.ownerPrincipal ?? null,
|
|
143
|
+
input.transport ?? "process",
|
|
144
|
+
input.payloadJson ?? null,
|
|
145
|
+
]);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
case "recordOutput":
|
|
149
|
+
await getPool().query("UPDATE jobs SET stdout = $2, stderr = $3, output_truncated = $4 WHERE id = $1", args);
|
|
150
|
+
return null;
|
|
151
|
+
case "recordComplete": {
|
|
152
|
+
const input = args[0];
|
|
153
|
+
const expiresAt = new Date(Date.parse(input.finishedAt) + workerData.retentionMs).toISOString();
|
|
154
|
+
await getPool().query(`UPDATE jobs
|
|
155
|
+
SET status = $2, exit_code = $3, stdout = $4, stderr = $5,
|
|
156
|
+
output_truncated = $6, error = $7, finished_at = $8,
|
|
157
|
+
expires_at = $9, http_status = $10
|
|
158
|
+
WHERE id = $1`, [
|
|
159
|
+
input.id,
|
|
160
|
+
input.status,
|
|
161
|
+
input.exitCode,
|
|
162
|
+
input.stdout,
|
|
163
|
+
input.stderr,
|
|
164
|
+
input.outputTruncated,
|
|
165
|
+
input.error,
|
|
166
|
+
input.finishedAt,
|
|
167
|
+
expiresAt,
|
|
168
|
+
input.httpStatus ?? null,
|
|
169
|
+
]);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
case "getById": {
|
|
173
|
+
const result = await getPool().query("SELECT * FROM jobs WHERE id = $1", [args[0]]);
|
|
174
|
+
return result.rows[0] ?? null;
|
|
175
|
+
}
|
|
176
|
+
case "findByRequestKey": {
|
|
177
|
+
const cutoff = new Date(Date.now() - workerData.dedupWindowMs).toISOString();
|
|
178
|
+
const result = await getPool().query(`SELECT * FROM jobs
|
|
179
|
+
WHERE request_key = $1
|
|
180
|
+
AND started_at >= $2
|
|
181
|
+
AND status IN ('running', 'completed')
|
|
182
|
+
ORDER BY started_at DESC
|
|
183
|
+
LIMIT 1`, [args[0], cutoff]);
|
|
184
|
+
return result.rows[0] ?? null;
|
|
185
|
+
}
|
|
186
|
+
case "markOrphanedOnStartup":
|
|
187
|
+
return await withClient(async (client) => {
|
|
188
|
+
const now = new Date().toISOString();
|
|
189
|
+
const expiresAt = new Date(Date.now() + workerData.retentionMs).toISOString();
|
|
190
|
+
await client.query("BEGIN");
|
|
191
|
+
try {
|
|
192
|
+
const rows = await client.query("SELECT id, correlation_id, started_at, stdout, stderr, exit_code, transport, http_status FROM jobs WHERE status = 'running'");
|
|
193
|
+
const update = await client.query(`UPDATE jobs
|
|
194
|
+
SET status = 'orphaned',
|
|
195
|
+
error = COALESCE(error, 'Gateway restarted while job was running'),
|
|
196
|
+
finished_at = COALESCE(finished_at, $1),
|
|
197
|
+
expires_at = $2
|
|
198
|
+
WHERE status = 'running'`, [now, expiresAt]);
|
|
199
|
+
await client.query("COMMIT");
|
|
200
|
+
return { count: update.rowCount ?? 0, orphaned: rows.rows };
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
await client.query("ROLLBACK");
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
case "evictExpired": {
|
|
208
|
+
const result = await getPool().query("DELETE FROM jobs WHERE expires_at < $1", [
|
|
209
|
+
new Date().toISOString(),
|
|
210
|
+
]);
|
|
211
|
+
return result.rowCount ?? 0;
|
|
212
|
+
}
|
|
213
|
+
case "recordValidationRun": {
|
|
214
|
+
const run = args[0];
|
|
215
|
+
await withClient(async (client) => {
|
|
216
|
+
await client.query("BEGIN");
|
|
217
|
+
try {
|
|
218
|
+
await client.query(`INSERT INTO validation_runs
|
|
219
|
+
(validation_id, owner_principal, intent, created_at, request_json,
|
|
220
|
+
provider_links, judge_link, status)
|
|
221
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
222
|
+
ON CONFLICT (validation_id) DO NOTHING`, [
|
|
223
|
+
run.validationId,
|
|
224
|
+
run.ownerPrincipal,
|
|
225
|
+
run.intent,
|
|
226
|
+
run.createdAt,
|
|
227
|
+
run.requestJson,
|
|
228
|
+
JSON.stringify(run.providerLinks),
|
|
229
|
+
run.judgeLink ? JSON.stringify(run.judgeLink) : null,
|
|
230
|
+
run.status,
|
|
231
|
+
]);
|
|
232
|
+
for (const link of run.providerLinks) {
|
|
233
|
+
await client.query(`INSERT INTO validation_run_jobs (job_id, validation_id, role)
|
|
234
|
+
VALUES ($1, $2, 'provider')
|
|
235
|
+
ON CONFLICT (job_id) DO NOTHING`, [link.jobId, run.validationId]);
|
|
236
|
+
}
|
|
237
|
+
await client.query("COMMIT");
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
await client.query("ROLLBACK");
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
case "getValidationRun": {
|
|
247
|
+
const result = await getPool().query("SELECT * FROM validation_runs WHERE validation_id = $1", [args[0]]);
|
|
248
|
+
return result.rows[0] ?? null;
|
|
249
|
+
}
|
|
250
|
+
case "setValidationJudgeLink": {
|
|
251
|
+
const [validationId, judgeLink] = args;
|
|
252
|
+
await withClient(async (client) => {
|
|
253
|
+
await client.query("BEGIN");
|
|
254
|
+
try {
|
|
255
|
+
await client.query("UPDATE validation_runs SET judge_link = $2 WHERE validation_id = $1", [validationId, JSON.stringify(judgeLink)]);
|
|
256
|
+
await client.query(`INSERT INTO validation_run_jobs (job_id, validation_id, role)
|
|
257
|
+
VALUES ($1, $2, 'judge')
|
|
258
|
+
ON CONFLICT (job_id) DO NOTHING`, [judgeLink.jobId, validationId]);
|
|
259
|
+
await client.query("COMMIT");
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
await client.query("ROLLBACK");
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
case "setValidationRunStatus":
|
|
269
|
+
await getPool().query("UPDATE validation_runs SET status = $2 WHERE validation_id = $1", args);
|
|
270
|
+
return null;
|
|
271
|
+
case "getValidationRunIdByJobId": {
|
|
272
|
+
const result = await getPool().query("SELECT validation_id FROM validation_run_jobs WHERE job_id = $1", [args[0]]);
|
|
273
|
+
return result.rows[0] ? result.rows[0].validation_id : null;
|
|
274
|
+
}
|
|
275
|
+
case "recordValidationReceipt": {
|
|
276
|
+
const receipt = args[0];
|
|
277
|
+
await getPool().query(`INSERT INTO validation_receipts
|
|
278
|
+
(validation_id, owner_principal, minted_at, schema_version, report_json,
|
|
279
|
+
canonical_sha256, prev_sha256, seq, signature, models,
|
|
280
|
+
has_material_disagreement, confidence)
|
|
281
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
|
282
|
+
ON CONFLICT (validation_id) DO NOTHING`, [
|
|
283
|
+
receipt.validationId,
|
|
284
|
+
receipt.ownerPrincipal,
|
|
285
|
+
receipt.mintedAt,
|
|
286
|
+
receipt.schemaVersion,
|
|
287
|
+
receipt.reportJson,
|
|
288
|
+
receipt.canonicalSha256,
|
|
289
|
+
receipt.prevSha256,
|
|
290
|
+
receipt.seq,
|
|
291
|
+
receipt.signature,
|
|
292
|
+
JSON.stringify(receipt.models),
|
|
293
|
+
receipt.hasMaterialDisagreement,
|
|
294
|
+
receipt.confidence,
|
|
295
|
+
]);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
case "getValidationReceipt": {
|
|
299
|
+
const result = await getPool().query("SELECT * FROM validation_receipts WHERE validation_id = $1", [args[0]]);
|
|
300
|
+
return result.rows[0] ?? null;
|
|
301
|
+
}
|
|
302
|
+
case "close":
|
|
303
|
+
if (pool)
|
|
304
|
+
await pool.end();
|
|
305
|
+
pool = null;
|
|
306
|
+
return null;
|
|
307
|
+
default:
|
|
308
|
+
throw new Error(`Unknown PostgresJobStore worker method: ${method}`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
parentPort?.on("message", async (message) => {
|
|
312
|
+
const { method, args, resultPath, shared } = message;
|
|
313
|
+
let payload;
|
|
314
|
+
try {
|
|
315
|
+
payload = ok(await op(method, args));
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
payload = fail(error);
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
writeFileSync(resultPath, JSON.stringify(payload));
|
|
322
|
+
signal(shared, 1);
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
signal(shared, 2);
|
|
326
|
+
}
|
|
327
|
+
});
|
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;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { redactAcpMessage } from "./acp/errors.js";
|
|
2
|
+
import { BASELINE_ACP_METHODS, deriveAcpMethodAvailability, } from "./acp/types.js";
|
|
3
|
+
import { getProviderDefinition } from "./provider-definitions.js";
|
|
4
|
+
export function deriveSupportedMethodsFromDiscovered(parsed) {
|
|
5
|
+
const init = {
|
|
6
|
+
protocolVersion: typeof parsed.protocolVersion === "number" ? parsed.protocolVersion : 0,
|
|
7
|
+
agentCapabilities: parsed.agentCapabilities ?? undefined,
|
|
8
|
+
authMethods: parsed.authMethods.map(id => ({ id })),
|
|
9
|
+
};
|
|
10
|
+
const methods = new Set(deriveAcpMethodAvailability(init));
|
|
11
|
+
for (const method of parsed.knownMethods) {
|
|
12
|
+
methods.add(method);
|
|
13
|
+
}
|
|
14
|
+
return [...methods].sort();
|
|
15
|
+
}
|
|
16
|
+
function hostServicePolicy(config) {
|
|
17
|
+
return {
|
|
18
|
+
filesystemRead: "deny-by-default",
|
|
19
|
+
filesystemWriteAllowed: config?.allowWriteHostServices ?? false,
|
|
20
|
+
terminalAllowed: config?.allowTerminalHostServices ?? false,
|
|
21
|
+
mutatingSessionOpsAllowed: config?.allowMutatingSessionOps ?? false,
|
|
22
|
+
permissionRouting: "approval-manager",
|
|
23
|
+
unknownToolKind: "deny-by-default",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function buildProviderAcpCapabilityRecord(provider, options = {}) {
|
|
27
|
+
const def = getProviderDefinition(provider);
|
|
28
|
+
const acp = def.acp;
|
|
29
|
+
const native = acp.classification === "native";
|
|
30
|
+
const policy = hostServicePolicy(options.acpConfig);
|
|
31
|
+
if (!native) {
|
|
32
|
+
return {
|
|
33
|
+
schemaVersion: "provider-acp-capability.v1",
|
|
34
|
+
provider,
|
|
35
|
+
displayName: def.displayName,
|
|
36
|
+
native: false,
|
|
37
|
+
nativeEntrypoint: null,
|
|
38
|
+
entrypoint: null,
|
|
39
|
+
probeArgv: [],
|
|
40
|
+
agentTypes: [],
|
|
41
|
+
evidence: acp.evidence,
|
|
42
|
+
initialize: null,
|
|
43
|
+
supportedSessionMethods: [],
|
|
44
|
+
hostServicePolicy: policy,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const discovered = options.discovered ?? null;
|
|
48
|
+
const initialize = discovered
|
|
49
|
+
? {
|
|
50
|
+
source: "discovered",
|
|
51
|
+
degraded: options.discoveredDegraded ?? false,
|
|
52
|
+
protocolVersion: discovered.protocolVersion,
|
|
53
|
+
agentInfo: discovered.agentInfo
|
|
54
|
+
? {
|
|
55
|
+
...(discovered.agentInfo.name !== undefined
|
|
56
|
+
? { name: redactAcpMessage(discovered.agentInfo.name) }
|
|
57
|
+
: {}),
|
|
58
|
+
...(discovered.agentInfo.version !== undefined
|
|
59
|
+
? { version: redactAcpMessage(discovered.agentInfo.version) }
|
|
60
|
+
: {}),
|
|
61
|
+
}
|
|
62
|
+
: null,
|
|
63
|
+
authMethods: discovered.authMethods,
|
|
64
|
+
promptCapabilities: discovered.promptCapabilities,
|
|
65
|
+
mcpCapabilities: discovered.mcpCapabilities,
|
|
66
|
+
sessionCapabilities: discovered.sessionCapabilities,
|
|
67
|
+
extensionMethods: discovered.extensionMethods,
|
|
68
|
+
}
|
|
69
|
+
: {
|
|
70
|
+
source: "static-fallback",
|
|
71
|
+
degraded: false,
|
|
72
|
+
protocolVersion: null,
|
|
73
|
+
agentInfo: null,
|
|
74
|
+
authMethods: [],
|
|
75
|
+
promptCapabilities: [],
|
|
76
|
+
mcpCapabilities: [],
|
|
77
|
+
sessionCapabilities: [],
|
|
78
|
+
extensionMethods: [],
|
|
79
|
+
};
|
|
80
|
+
const supportedSessionMethods = discovered
|
|
81
|
+
? deriveSupportedMethodsFromDiscovered(discovered)
|
|
82
|
+
: [...BASELINE_ACP_METHODS].sort();
|
|
83
|
+
return {
|
|
84
|
+
schemaVersion: "provider-acp-capability.v1",
|
|
85
|
+
provider,
|
|
86
|
+
displayName: def.displayName,
|
|
87
|
+
native: true,
|
|
88
|
+
nativeEntrypoint: acp.nativeEntrypoint,
|
|
89
|
+
entrypoint: acp.entrypoint
|
|
90
|
+
? { command: acp.entrypoint.command, args: [...acp.entrypoint.args] }
|
|
91
|
+
: null,
|
|
92
|
+
probeArgv: acp.probeArgv.map(argv => [...argv]),
|
|
93
|
+
agentTypes: acp.agentTypes
|
|
94
|
+
? acp.agentTypes.map(t => ({ id: t.id, description: t.description }))
|
|
95
|
+
: [],
|
|
96
|
+
evidence: acp.evidence,
|
|
97
|
+
initialize,
|
|
98
|
+
supportedSessionMethods,
|
|
99
|
+
hostServicePolicy: policy,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { type ExecuteResult } from "./executor.js";
|
|
3
|
+
import { type Logger } from "./logger.js";
|
|
4
|
+
import { type CliType, type ProviderAdminFamily, type ProviderDefinition } from "./provider-definitions.js";
|
|
5
|
+
import type { DiscoveredCapabilitySet } from "./provider-capability-discovery.js";
|
|
6
|
+
import { type CliSubcommandExposure, type CliSubcommandRisk } from "./upstream-contracts.js";
|
|
7
|
+
import type { ApprovalManager } from "./approval-manager.js";
|
|
8
|
+
import { type GatewayRequestContext } from "./request-context.js";
|
|
9
|
+
export declare function adminRiskToExposure(risk: CliSubcommandRisk): CliSubcommandExposure;
|
|
10
|
+
export declare function classifyOperationRisk(operationName: string, familyRisk: CliSubcommandRisk, opts?: {
|
|
11
|
+
isSubcommand?: boolean;
|
|
12
|
+
}): CliSubcommandRisk;
|
|
13
|
+
export declare function familyBaseRisk(provider: CliType, fam: ProviderAdminFamily): CliSubcommandRisk;
|
|
14
|
+
export type AdminDiscoverySource = "subcommand-help" | "root-help" | "not-advertised" | "no-discovery";
|
|
15
|
+
export interface AdminOperation {
|
|
16
|
+
readonly provider: CliType;
|
|
17
|
+
readonly family: string;
|
|
18
|
+
readonly operationId: string;
|
|
19
|
+
readonly argv: readonly string[];
|
|
20
|
+
readonly risk: CliSubcommandRisk;
|
|
21
|
+
readonly exposure: CliSubcommandExposure;
|
|
22
|
+
readonly mutating: boolean;
|
|
23
|
+
readonly available: boolean;
|
|
24
|
+
readonly discoverySource: AdminDiscoverySource;
|
|
25
|
+
readonly summary: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function projectProviderAdminOperations(def: ProviderDefinition, discovered: DiscoveredCapabilitySet | null): AdminOperation[];
|
|
28
|
+
export interface AdminCatalogRow {
|
|
29
|
+
readonly provider: CliType;
|
|
30
|
+
readonly operationId: string;
|
|
31
|
+
readonly family: string;
|
|
32
|
+
readonly argv: readonly string[];
|
|
33
|
+
readonly risk: CliSubcommandRisk;
|
|
34
|
+
readonly exposure: CliSubcommandExposure;
|
|
35
|
+
readonly mutating: boolean;
|
|
36
|
+
readonly available: boolean;
|
|
37
|
+
readonly discoverySource: AdminDiscoverySource;
|
|
38
|
+
readonly summary: string;
|
|
39
|
+
}
|
|
40
|
+
export declare function buildProviderAdminCatalog(options?: {
|
|
41
|
+
provider?: CliType;
|
|
42
|
+
lookup?: (id: CliType) => DiscoveredCapabilitySet | null;
|
|
43
|
+
includeUnavailable?: boolean;
|
|
44
|
+
}): AdminCatalogRow[];
|
|
45
|
+
export declare function redactAdminOutput(text: string): string;
|
|
46
|
+
export type AdminCommandRunner = (executable: string, argv: readonly string[], options: {
|
|
47
|
+
timeoutMs?: number;
|
|
48
|
+
}) => Promise<ExecuteResult>;
|
|
49
|
+
export declare function isSafeAdminToken(token: string): boolean;
|
|
50
|
+
export interface AdminRunResult {
|
|
51
|
+
readonly ok: boolean;
|
|
52
|
+
readonly provider: CliType;
|
|
53
|
+
readonly operationId: string;
|
|
54
|
+
readonly argv: readonly string[];
|
|
55
|
+
readonly exitCode: number | null;
|
|
56
|
+
readonly stdout: string;
|
|
57
|
+
readonly stderr: string;
|
|
58
|
+
readonly redacted: true;
|
|
59
|
+
readonly error?: string;
|
|
60
|
+
}
|
|
61
|
+
export declare function runReadOnlyAdminOperation(op: AdminOperation, deps?: {
|
|
62
|
+
runner?: AdminCommandRunner;
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
logger?: Logger;
|
|
65
|
+
}): Promise<AdminRunResult>;
|
|
66
|
+
export interface AdminAuditRecord {
|
|
67
|
+
readonly ts: string;
|
|
68
|
+
readonly provider: CliType;
|
|
69
|
+
readonly operationId: string;
|
|
70
|
+
readonly argv: readonly string[];
|
|
71
|
+
readonly outcome: "gate_closed" | "denied" | "approved" | "executed";
|
|
72
|
+
readonly approvalId?: string;
|
|
73
|
+
readonly exitCode?: number | null;
|
|
74
|
+
readonly principal?: string;
|
|
75
|
+
}
|
|
76
|
+
export declare function defaultAdminAuditPath(): string;
|
|
77
|
+
export type AdminAuditSink = (record: AdminAuditRecord) => void;
|
|
78
|
+
export declare function defaultAdminAuditSink(auditPath?: string): AdminAuditSink;
|
|
79
|
+
export declare function appendAdminAudit(record: AdminAuditRecord, auditPath?: string): void;
|
|
80
|
+
export interface AdminMutateContext {
|
|
81
|
+
readonly allowMutating: boolean;
|
|
82
|
+
readonly remoteCaller?: boolean;
|
|
83
|
+
readonly remoteAdminAllowed?: boolean;
|
|
84
|
+
readonly approvalManager: ApprovalManager;
|
|
85
|
+
readonly runner?: AdminCommandRunner;
|
|
86
|
+
readonly timeoutMs?: number;
|
|
87
|
+
readonly logger?: Logger;
|
|
88
|
+
readonly principal?: string;
|
|
89
|
+
readonly auditPath?: string;
|
|
90
|
+
readonly auditSink?: AdminAuditSink;
|
|
91
|
+
}
|
|
92
|
+
export declare function runMutatingAdminOperation(op: AdminOperation, ctx: AdminMutateContext): Promise<AdminRunResult>;
|
|
93
|
+
export declare function isRemoteAdminCaller(ctx: GatewayRequestContext | undefined): boolean;
|
|
94
|
+
export declare function remoteCliAdminEnabled(ctx: GatewayRequestContext | undefined): boolean;
|
|
95
|
+
export interface ProviderAdminToolRuntime {
|
|
96
|
+
readonly approvalManager: ApprovalManager;
|
|
97
|
+
readonly logger: Logger;
|
|
98
|
+
readonly allowMutatingCliAdminOps: boolean;
|
|
99
|
+
}
|
|
100
|
+
export declare function registerProviderAdminTools(server: McpServer, runtime: ProviderAdminToolRuntime): void;
|