llm-cli-gateway 1.4.0 → 1.5.13
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 +135 -1
- package/README.md +358 -15
- package/dist/approval-manager.d.ts +1 -1
- package/dist/async-job-manager.d.ts +32 -2
- package/dist/async-job-manager.js +101 -16
- package/dist/auth.d.ts +15 -0
- package/dist/auth.js +46 -0
- package/dist/cli-updater.d.ts +19 -2
- package/dist/cli-updater.js +110 -7
- package/dist/codex-json-parser.d.ts +34 -0
- package/dist/codex-json-parser.js +105 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.js +167 -0
- package/dist/doctor.d.ts +110 -0
- package/dist/doctor.js +280 -0
- package/dist/endpoint-exposure.d.ts +22 -0
- package/dist/endpoint-exposure.js +231 -0
- package/dist/entrypoint-url.d.ts +1 -0
- package/dist/entrypoint-url.js +5 -0
- package/dist/executor.d.ts +9 -1
- package/dist/executor.js +52 -17
- package/dist/flight-recorder.d.ts +3 -1
- package/dist/flight-recorder.js +31 -2
- package/dist/gateway-server.d.ts +2 -0
- package/dist/gateway-server.js +1 -0
- package/dist/gemini-json-parser.d.ts +21 -0
- package/dist/gemini-json-parser.js +47 -0
- package/dist/health.d.ts +7 -0
- package/dist/health.js +22 -0
- package/dist/http-transport.d.ts +22 -0
- package/dist/http-transport.js +164 -0
- package/dist/index.d.ts +186 -2
- package/dist/index.js +2761 -1454
- package/dist/job-store.d.ts +118 -2
- package/dist/job-store.js +176 -5
- package/dist/logger.d.ts +9 -0
- package/dist/logger.js +14 -0
- package/dist/model-registry.js +40 -6
- package/dist/provider-login-guidance.d.ts +21 -0
- package/dist/provider-login-guidance.js +98 -0
- package/dist/provider-status.d.ts +41 -0
- package/dist/provider-status.js +203 -0
- package/dist/request-helpers.d.ts +484 -4
- package/dist/request-helpers.js +613 -0
- package/dist/resources.js +44 -0
- package/dist/session-manager-pg.js +1 -0
- package/dist/session-manager.d.ts +1 -1
- package/dist/session-manager.js +2 -1
- package/dist/upstream-contracts.d.ts +62 -0
- package/dist/upstream-contracts.js +620 -0
- package/dist/validation-normalizer.d.ts +23 -0
- package/dist/validation-normalizer.js +79 -0
- package/dist/validation-orchestrator.d.ts +47 -0
- package/dist/validation-orchestrator.js +145 -0
- package/dist/validation-prompts.d.ts +15 -0
- package/dist/validation-prompts.js +52 -0
- package/dist/validation-report.d.ts +57 -0
- package/dist/validation-report.js +129 -0
- package/dist/validation-tools.d.ts +7 -0
- package/dist/validation-tools.js +198 -0
- package/package.json +25 -10
- package/setup/status.schema.json +271 -0
package/dist/job-store.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Logger } from "./logger.js";
|
|
2
|
+
import type { PersistenceConfig } from "./config.js";
|
|
2
3
|
export type JobStoreStatus = "running" | "completed" | "failed" | "canceled" | "orphaned";
|
|
3
4
|
export interface JobRecord {
|
|
4
5
|
id: string;
|
|
@@ -22,7 +23,43 @@ export declare function resolveJobStoreDbPath(): string | null;
|
|
|
22
23
|
export declare function resolveJobRetentionMs(): number;
|
|
23
24
|
export declare function resolveDedupWindowMs(): number;
|
|
24
25
|
export declare function computeRequestKey(cli: string, args: string[], extra?: string): string;
|
|
25
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Public surface every backend (sqlite/postgres/memory) must implement. The
|
|
28
|
+
* AsyncJobManager talks to this interface only.
|
|
29
|
+
*/
|
|
30
|
+
export interface JobStore {
|
|
31
|
+
recordStart(input: {
|
|
32
|
+
id: string;
|
|
33
|
+
correlationId: string;
|
|
34
|
+
requestKey: string;
|
|
35
|
+
cli: string;
|
|
36
|
+
args: string[];
|
|
37
|
+
outputFormat?: string;
|
|
38
|
+
startedAt: string;
|
|
39
|
+
pid: number | null;
|
|
40
|
+
}): void;
|
|
41
|
+
recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
|
|
42
|
+
recordComplete(input: {
|
|
43
|
+
id: string;
|
|
44
|
+
status: Exclude<JobStoreStatus, "running">;
|
|
45
|
+
exitCode: number | null;
|
|
46
|
+
stdout: string;
|
|
47
|
+
stderr: string;
|
|
48
|
+
outputTruncated: boolean;
|
|
49
|
+
error: string | null;
|
|
50
|
+
finishedAt: string;
|
|
51
|
+
}): void;
|
|
52
|
+
getById(id: string): JobRecord | null;
|
|
53
|
+
findByRequestKey(requestKey: string): JobRecord | null;
|
|
54
|
+
markOrphanedOnStartup(): number;
|
|
55
|
+
evictExpired(): number;
|
|
56
|
+
close(): void;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* SQLite-backed job store. Default backend for production. Durable across
|
|
60
|
+
* gateway restarts; safe for single-instance deployments.
|
|
61
|
+
*/
|
|
62
|
+
export declare class SqliteJobStore implements JobStore {
|
|
26
63
|
private logger;
|
|
27
64
|
private db;
|
|
28
65
|
private retentionMs;
|
|
@@ -34,7 +71,10 @@ export declare class JobStore {
|
|
|
34
71
|
private findByRequestKeyStmt;
|
|
35
72
|
private markOrphanedStmt;
|
|
36
73
|
private deleteExpiredStmt;
|
|
37
|
-
constructor(dbPath: string, logger?: Logger
|
|
74
|
+
constructor(dbPath: string, logger?: Logger, options?: {
|
|
75
|
+
retentionMs?: number;
|
|
76
|
+
dedupWindowMs?: number;
|
|
77
|
+
});
|
|
38
78
|
/**
|
|
39
79
|
* Insert a new running job row. Caller has already computed requestKey.
|
|
40
80
|
*/
|
|
@@ -82,3 +122,79 @@ export declare class JobStore {
|
|
|
82
122
|
evictExpired(): number;
|
|
83
123
|
close(): void;
|
|
84
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Backwards-compatibility alias. Older code and tests construct `new JobStore(path)`
|
|
127
|
+
* directly; that surface now resolves to the SQLite implementation. Prefer
|
|
128
|
+
* `createJobStore(config)` in new code.
|
|
129
|
+
*
|
|
130
|
+
* @deprecated Use `SqliteJobStore` directly, or `createJobStore(persistenceConfig)`.
|
|
131
|
+
*/
|
|
132
|
+
export declare const JobStoreClass: typeof SqliteJobStore;
|
|
133
|
+
/**
|
|
134
|
+
* In-process job store. Same semantics as SqliteJobStore but state lives in a
|
|
135
|
+
* Map and is lost on process exit. Use for tests and ephemeral/CI gateways
|
|
136
|
+
* that have explicitly acknowledged the trade-off via
|
|
137
|
+
* `[persistence].acknowledgeEphemeral = true`.
|
|
138
|
+
*/
|
|
139
|
+
export declare class MemoryJobStore implements JobStore {
|
|
140
|
+
private rows;
|
|
141
|
+
private retentionMs;
|
|
142
|
+
private dedupWindowMs;
|
|
143
|
+
constructor(options?: {
|
|
144
|
+
retentionMs?: number;
|
|
145
|
+
dedupWindowMs?: number;
|
|
146
|
+
});
|
|
147
|
+
recordStart(input: {
|
|
148
|
+
id: string;
|
|
149
|
+
correlationId: string;
|
|
150
|
+
requestKey: string;
|
|
151
|
+
cli: string;
|
|
152
|
+
args: string[];
|
|
153
|
+
outputFormat?: string;
|
|
154
|
+
startedAt: string;
|
|
155
|
+
pid: number | null;
|
|
156
|
+
}): void;
|
|
157
|
+
recordOutput(id: string, stdout: string, stderr: string, outputTruncated: boolean): void;
|
|
158
|
+
recordComplete(input: {
|
|
159
|
+
id: string;
|
|
160
|
+
status: Exclude<JobStoreStatus, "running">;
|
|
161
|
+
exitCode: number | null;
|
|
162
|
+
stdout: string;
|
|
163
|
+
stderr: string;
|
|
164
|
+
outputTruncated: boolean;
|
|
165
|
+
error: string | null;
|
|
166
|
+
finishedAt: string;
|
|
167
|
+
}): void;
|
|
168
|
+
getById(id: string): JobRecord | null;
|
|
169
|
+
findByRequestKey(requestKey: string): JobRecord | null;
|
|
170
|
+
/**
|
|
171
|
+
* In-memory stores have no cross-process state, so any "running" rows here
|
|
172
|
+
* came from this very process and aren't actually orphaned. No-op.
|
|
173
|
+
*/
|
|
174
|
+
markOrphanedOnStartup(): number;
|
|
175
|
+
evictExpired(): number;
|
|
176
|
+
close(): void;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Stub for the planned Postgres backend. The interface and config surface ship
|
|
180
|
+
* now so multi-instance deployments can plan around them, but the
|
|
181
|
+
* implementation is intentionally not yet provided — calling code must select
|
|
182
|
+
* `sqlite` or `memory` until a real impl lands.
|
|
183
|
+
*/
|
|
184
|
+
export declare class PostgresJobStore implements JobStore {
|
|
185
|
+
constructor(_dsn: string, _logger?: Logger);
|
|
186
|
+
recordStart(): void;
|
|
187
|
+
recordOutput(): void;
|
|
188
|
+
recordComplete(): void;
|
|
189
|
+
getById(): JobRecord | null;
|
|
190
|
+
findByRequestKey(): JobRecord | null;
|
|
191
|
+
markOrphanedOnStartup(): number;
|
|
192
|
+
evictExpired(): number;
|
|
193
|
+
close(): void;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Construct the JobStore appropriate to the resolved PersistenceConfig.
|
|
197
|
+
* Returns `null` when `backend = "none"` — callers must not register
|
|
198
|
+
* `*_request_async` tools in that case (use `config.asyncJobsEnabled`).
|
|
199
|
+
*/
|
|
200
|
+
export declare function createJobStore(config: PersistenceConfig, logger?: Logger): JobStore | null;
|
package/dist/job-store.js
CHANGED
|
@@ -59,7 +59,11 @@ function rowToRecord(row) {
|
|
|
59
59
|
expiresAt: row.expires_at,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
-
|
|
62
|
+
/**
|
|
63
|
+
* SQLite-backed job store. Default backend for production. Durable across
|
|
64
|
+
* gateway restarts; safe for single-instance deployments.
|
|
65
|
+
*/
|
|
66
|
+
export class SqliteJobStore {
|
|
63
67
|
logger;
|
|
64
68
|
db;
|
|
65
69
|
retentionMs;
|
|
@@ -71,7 +75,7 @@ export class JobStore {
|
|
|
71
75
|
findByRequestKeyStmt;
|
|
72
76
|
markOrphanedStmt;
|
|
73
77
|
deleteExpiredStmt;
|
|
74
|
-
constructor(dbPath, logger = noopLogger) {
|
|
78
|
+
constructor(dbPath, logger = noopLogger, options = {}) {
|
|
75
79
|
this.logger = logger;
|
|
76
80
|
const require = createRequire(import.meta.url);
|
|
77
81
|
const BetterSqlite3 = require("better-sqlite3");
|
|
@@ -114,8 +118,8 @@ export class JobStore {
|
|
|
114
118
|
// Best effort permissions hardening.
|
|
115
119
|
}
|
|
116
120
|
}
|
|
117
|
-
this.retentionMs = resolveJobRetentionMs();
|
|
118
|
-
this.dedupWindowMs = resolveDedupWindowMs();
|
|
121
|
+
this.retentionMs = options.retentionMs ?? resolveJobRetentionMs();
|
|
122
|
+
this.dedupWindowMs = options.dedupWindowMs ?? resolveDedupWindowMs();
|
|
119
123
|
this.insertStmt = this.db.prepare(`
|
|
120
124
|
INSERT INTO jobs (id, correlation_id, request_key, cli, args_json, output_format,
|
|
121
125
|
status, exit_code, stdout, stderr, output_truncated, error,
|
|
@@ -245,7 +249,174 @@ export class JobStore {
|
|
|
245
249
|
this.db.close();
|
|
246
250
|
}
|
|
247
251
|
catch (err) {
|
|
248
|
-
this.logger.error("
|
|
252
|
+
this.logger.error("SqliteJobStore close failed", err);
|
|
249
253
|
}
|
|
250
254
|
}
|
|
251
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Backwards-compatibility alias. Older code and tests construct `new JobStore(path)`
|
|
258
|
+
* directly; that surface now resolves to the SQLite implementation. Prefer
|
|
259
|
+
* `createJobStore(config)` in new code.
|
|
260
|
+
*
|
|
261
|
+
* @deprecated Use `SqliteJobStore` directly, or `createJobStore(persistenceConfig)`.
|
|
262
|
+
*/
|
|
263
|
+
export const JobStoreClass = SqliteJobStore;
|
|
264
|
+
/**
|
|
265
|
+
* In-process job store. Same semantics as SqliteJobStore but state lives in a
|
|
266
|
+
* Map and is lost on process exit. Use for tests and ephemeral/CI gateways
|
|
267
|
+
* that have explicitly acknowledged the trade-off via
|
|
268
|
+
* `[persistence].acknowledgeEphemeral = true`.
|
|
269
|
+
*/
|
|
270
|
+
export class MemoryJobStore {
|
|
271
|
+
rows = new Map();
|
|
272
|
+
retentionMs;
|
|
273
|
+
dedupWindowMs;
|
|
274
|
+
constructor(options = {}) {
|
|
275
|
+
this.retentionMs = options.retentionMs ?? resolveJobRetentionMs();
|
|
276
|
+
this.dedupWindowMs = options.dedupWindowMs ?? resolveDedupWindowMs();
|
|
277
|
+
}
|
|
278
|
+
recordStart(input) {
|
|
279
|
+
this.rows.set(input.id, {
|
|
280
|
+
id: input.id,
|
|
281
|
+
correlationId: input.correlationId,
|
|
282
|
+
requestKey: input.requestKey,
|
|
283
|
+
cli: input.cli,
|
|
284
|
+
argsJson: JSON.stringify(input.args),
|
|
285
|
+
outputFormat: input.outputFormat ?? null,
|
|
286
|
+
status: "running",
|
|
287
|
+
exitCode: null,
|
|
288
|
+
stdout: "",
|
|
289
|
+
stderr: "",
|
|
290
|
+
outputTruncated: false,
|
|
291
|
+
error: null,
|
|
292
|
+
startedAt: input.startedAt,
|
|
293
|
+
finishedAt: null,
|
|
294
|
+
pid: input.pid,
|
|
295
|
+
expiresAt: FAR_FUTURE_ISO,
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
recordOutput(id, stdout, stderr, outputTruncated) {
|
|
299
|
+
const row = this.rows.get(id);
|
|
300
|
+
if (!row)
|
|
301
|
+
return;
|
|
302
|
+
row.stdout = stdout;
|
|
303
|
+
row.stderr = stderr;
|
|
304
|
+
row.outputTruncated = outputTruncated;
|
|
305
|
+
}
|
|
306
|
+
recordComplete(input) {
|
|
307
|
+
const row = this.rows.get(input.id);
|
|
308
|
+
if (!row)
|
|
309
|
+
return;
|
|
310
|
+
row.status = input.status;
|
|
311
|
+
row.exitCode = input.exitCode;
|
|
312
|
+
row.stdout = input.stdout;
|
|
313
|
+
row.stderr = input.stderr;
|
|
314
|
+
row.outputTruncated = input.outputTruncated;
|
|
315
|
+
row.error = input.error;
|
|
316
|
+
row.finishedAt = input.finishedAt;
|
|
317
|
+
row.expiresAt = new Date(Date.parse(input.finishedAt) + this.retentionMs).toISOString();
|
|
318
|
+
}
|
|
319
|
+
getById(id) {
|
|
320
|
+
const row = this.rows.get(id);
|
|
321
|
+
return row ? { ...row } : null;
|
|
322
|
+
}
|
|
323
|
+
findByRequestKey(requestKey) {
|
|
324
|
+
const cutoffMs = Date.now() - this.dedupWindowMs;
|
|
325
|
+
let best = null;
|
|
326
|
+
for (const row of this.rows.values()) {
|
|
327
|
+
if (row.requestKey !== requestKey)
|
|
328
|
+
continue;
|
|
329
|
+
if (row.status !== "running" && row.status !== "completed")
|
|
330
|
+
continue;
|
|
331
|
+
if (Date.parse(row.startedAt) < cutoffMs)
|
|
332
|
+
continue;
|
|
333
|
+
if (!best || Date.parse(row.startedAt) > Date.parse(best.startedAt)) {
|
|
334
|
+
best = row;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return best ? { ...best } : null;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* In-memory stores have no cross-process state, so any "running" rows here
|
|
341
|
+
* came from this very process and aren't actually orphaned. No-op.
|
|
342
|
+
*/
|
|
343
|
+
markOrphanedOnStartup() {
|
|
344
|
+
return 0;
|
|
345
|
+
}
|
|
346
|
+
evictExpired() {
|
|
347
|
+
const nowIso = new Date().toISOString();
|
|
348
|
+
let removed = 0;
|
|
349
|
+
for (const [id, row] of this.rows) {
|
|
350
|
+
if (row.expiresAt < nowIso) {
|
|
351
|
+
this.rows.delete(id);
|
|
352
|
+
removed++;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return removed;
|
|
356
|
+
}
|
|
357
|
+
close() {
|
|
358
|
+
this.rows.clear();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Stub for the planned Postgres backend. The interface and config surface ship
|
|
363
|
+
* now so multi-instance deployments can plan around them, but the
|
|
364
|
+
* implementation is intentionally not yet provided — calling code must select
|
|
365
|
+
* `sqlite` or `memory` until a real impl lands.
|
|
366
|
+
*/
|
|
367
|
+
export class PostgresJobStore {
|
|
368
|
+
constructor(_dsn, _logger = noopLogger) {
|
|
369
|
+
throw new Error("PostgresJobStore is not yet implemented. Use backend = 'sqlite' (single-instance) or " +
|
|
370
|
+
"backend = 'memory' (ephemeral) until the Postgres backend ships.");
|
|
371
|
+
}
|
|
372
|
+
recordStart() {
|
|
373
|
+
throw new Error("not implemented");
|
|
374
|
+
}
|
|
375
|
+
recordOutput() {
|
|
376
|
+
throw new Error("not implemented");
|
|
377
|
+
}
|
|
378
|
+
recordComplete() {
|
|
379
|
+
throw new Error("not implemented");
|
|
380
|
+
}
|
|
381
|
+
getById() {
|
|
382
|
+
throw new Error("not implemented");
|
|
383
|
+
}
|
|
384
|
+
findByRequestKey() {
|
|
385
|
+
throw new Error("not implemented");
|
|
386
|
+
}
|
|
387
|
+
markOrphanedOnStartup() {
|
|
388
|
+
throw new Error("not implemented");
|
|
389
|
+
}
|
|
390
|
+
evictExpired() {
|
|
391
|
+
throw new Error("not implemented");
|
|
392
|
+
}
|
|
393
|
+
close() {
|
|
394
|
+
/* no-op */
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Construct the JobStore appropriate to the resolved PersistenceConfig.
|
|
399
|
+
* Returns `null` when `backend = "none"` — callers must not register
|
|
400
|
+
* `*_request_async` tools in that case (use `config.asyncJobsEnabled`).
|
|
401
|
+
*/
|
|
402
|
+
export function createJobStore(config, logger = noopLogger) {
|
|
403
|
+
const opts = {
|
|
404
|
+
retentionMs: config.retentionDays * 24 * 60 * 60 * 1000,
|
|
405
|
+
dedupWindowMs: config.dedupWindowMs,
|
|
406
|
+
};
|
|
407
|
+
switch (config.backend) {
|
|
408
|
+
case "none":
|
|
409
|
+
return null;
|
|
410
|
+
case "memory":
|
|
411
|
+
return new MemoryJobStore(opts);
|
|
412
|
+
case "postgres":
|
|
413
|
+
// Throws today; design surface is honest so callers can react.
|
|
414
|
+
return new PostgresJobStore(config.dsn ?? "", logger);
|
|
415
|
+
case "sqlite":
|
|
416
|
+
default:
|
|
417
|
+
if (!config.path) {
|
|
418
|
+
throw new Error("SqliteJobStore requires a non-empty path");
|
|
419
|
+
}
|
|
420
|
+
return new SqliteJobStore(config.path, logger, opts);
|
|
421
|
+
}
|
|
422
|
+
}
|
package/dist/logger.d.ts
CHANGED
|
@@ -2,5 +2,14 @@ export interface Logger {
|
|
|
2
2
|
info(message: string, meta?: unknown): void;
|
|
3
3
|
error(message: string, meta?: unknown): void;
|
|
4
4
|
debug(message: string, meta?: unknown): void;
|
|
5
|
+
/** Optional: callers that want explicit WARN routing can implement this. */
|
|
6
|
+
warn?(message: string, meta?: unknown): void;
|
|
5
7
|
}
|
|
6
8
|
export declare const noopLogger: Logger;
|
|
9
|
+
/**
|
|
10
|
+
* Emit a warning through whichever logger surface is available. Some Logger
|
|
11
|
+
* implementations (legacy) only provide `info`/`error`/`debug`; in that case
|
|
12
|
+
* the message is prefixed with `[WARN]` and routed through `info` so it still
|
|
13
|
+
* reaches stderr.
|
|
14
|
+
*/
|
|
15
|
+
export declare function logWarn(logger: Logger, message: string, meta?: unknown): void;
|
package/dist/logger.js
CHANGED
|
@@ -2,4 +2,18 @@ export const noopLogger = {
|
|
|
2
2
|
info: () => { },
|
|
3
3
|
error: () => { },
|
|
4
4
|
debug: () => { },
|
|
5
|
+
warn: () => { },
|
|
5
6
|
};
|
|
7
|
+
/**
|
|
8
|
+
* Emit a warning through whichever logger surface is available. Some Logger
|
|
9
|
+
* implementations (legacy) only provide `info`/`error`/`debug`; in that case
|
|
10
|
+
* the message is prefixed with `[WARN]` and routed through `info` so it still
|
|
11
|
+
* reaches stderr.
|
|
12
|
+
*/
|
|
13
|
+
export function logWarn(logger, message, meta) {
|
|
14
|
+
if (typeof logger.warn === "function") {
|
|
15
|
+
logger.warn(message, meta);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
logger.info(`[WARN] ${message}`, meta);
|
|
19
|
+
}
|
package/dist/model-registry.js
CHANGED
|
@@ -14,17 +14,19 @@ const FALLBACK_INFO = {
|
|
|
14
14
|
modelOrder: ["opus", "sonnet", "haiku"],
|
|
15
15
|
},
|
|
16
16
|
codex: {
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
17
|
+
// U26: gpt-5.5 is the bundled fallback default. Config/env overrides still
|
|
18
|
+
// win (applyCodexOverrides runs after). Older aliases are retained in the
|
|
19
|
+
// models map so callers that still pass `gpt-5.3-codex` resolve cleanly.
|
|
20
20
|
description: "OpenAI's Codex CLI - best for code execution in sandboxed environments",
|
|
21
21
|
models: {
|
|
22
|
-
"gpt-5.
|
|
23
|
-
"gpt-5.
|
|
22
|
+
"gpt-5.5": "Latest Codex frontier model. Best for: most Codex tasks (default since U26)",
|
|
23
|
+
"gpt-5.4": "Frontier coding and professional-work model. Best for: long-running agentic work",
|
|
24
|
+
"gpt-5.3-codex": "Legacy specialized Codex model (kept for backwards-compat). Best for: agentic coding workflows with Codex-tuned behavior",
|
|
24
25
|
"gpt-5.2": "Strong general-purpose GPT-5 model. Best for: broad coding and reasoning tasks",
|
|
25
26
|
"gpt-5-pro": "Highest-capability GPT-5 model. Best for: deep reasoning and difficult professional workflows",
|
|
26
27
|
},
|
|
27
|
-
|
|
28
|
+
defaultModel: "gpt-5.5",
|
|
29
|
+
modelOrder: ["gpt-5.5", "gpt-5.4", "gpt-5.3-codex", "gpt-5.2", "gpt-5-pro"],
|
|
28
30
|
},
|
|
29
31
|
gemini: {
|
|
30
32
|
description: "Google's Gemini CLI - best for multimodal tasks and Google ecosystem integration",
|
|
@@ -42,6 +44,20 @@ const FALLBACK_INFO = {
|
|
|
42
44
|
},
|
|
43
45
|
modelOrder: ["grok-build"],
|
|
44
46
|
},
|
|
47
|
+
mistral: {
|
|
48
|
+
// Mistral Vibe selects the active model via the VIBE_ACTIVE_MODEL environment
|
|
49
|
+
// variable; there is NO `--model` flag. Aliases here are still resolvable so
|
|
50
|
+
// callers can pass e.g. `latest` → `devstral-medium`; the resolved value is
|
|
51
|
+
// injected via env in prepareMistralRequest.
|
|
52
|
+
description: "Mistral AI's Vibe CLI - agentic coding via Mistral models (model selection via VIBE_ACTIVE_MODEL env var)",
|
|
53
|
+
models: {
|
|
54
|
+
"devstral-medium": "Default Vibe coding model. Best for: most Vibe sessions (default when VIBE_ACTIVE_MODEL is unset)",
|
|
55
|
+
"devstral-large": "Higher-capability Devstral model. Best for: harder reasoning/coding tasks",
|
|
56
|
+
"mistral-large-latest": "General-purpose flagship Mistral model. Best for: non-Devstral reasoning workloads",
|
|
57
|
+
},
|
|
58
|
+
defaultModel: "devstral-medium",
|
|
59
|
+
modelOrder: ["devstral-medium", "devstral-large", "mistral-large-latest"],
|
|
60
|
+
},
|
|
45
61
|
};
|
|
46
62
|
const MODEL_CACHE_TTL_MS = 2 * 60 * 1000;
|
|
47
63
|
const MAX_GEMINI_HISTORY_FILES = 200;
|
|
@@ -98,11 +114,13 @@ function buildCliInfo() {
|
|
|
98
114
|
codex: cloneInfo(FALLBACK_INFO.codex),
|
|
99
115
|
gemini: cloneInfo(FALLBACK_INFO.gemini),
|
|
100
116
|
grok: cloneInfo(FALLBACK_INFO.grok),
|
|
117
|
+
mistral: cloneInfo(FALLBACK_INFO.mistral),
|
|
101
118
|
};
|
|
102
119
|
applyClaudeOverrides(info.claude);
|
|
103
120
|
applyCodexOverrides(info.codex);
|
|
104
121
|
applyGeminiOverrides(info.gemini);
|
|
105
122
|
applyGrokOverrides(info.grok);
|
|
123
|
+
applyMistralOverrides(info.mistral);
|
|
106
124
|
return info;
|
|
107
125
|
}
|
|
108
126
|
function cloneInfo(source) {
|
|
@@ -322,6 +340,22 @@ function applyGrokOverrides(info) {
|
|
|
322
340
|
}
|
|
323
341
|
info.modelOrder = buildOrder(info, info.defaultModel);
|
|
324
342
|
}
|
|
343
|
+
function applyMistralOverrides(info) {
|
|
344
|
+
// Vibe selects its active model via VIBE_ACTIVE_MODEL (no --model flag). When
|
|
345
|
+
// present, treat it as the configured default so resolveModelAlias("latest")
|
|
346
|
+
// returns the user-selected value.
|
|
347
|
+
const envDefault = process.env.MISTRAL_DEFAULT_MODEL || process.env.VIBE_ACTIVE_MODEL;
|
|
348
|
+
addEnvModels(info, "MISTRAL_MODELS");
|
|
349
|
+
addEnvAliases(info, "mistral", "MISTRAL_MODEL_ALIASES");
|
|
350
|
+
addGlobalEnvAliases(info, "mistral");
|
|
351
|
+
if (envDefault) {
|
|
352
|
+
const source = process.env.MISTRAL_DEFAULT_MODEL
|
|
353
|
+
? "MISTRAL_DEFAULT_MODEL"
|
|
354
|
+
: "VIBE_ACTIVE_MODEL";
|
|
355
|
+
setDefaultModel(info, envDefault, source, "env");
|
|
356
|
+
}
|
|
357
|
+
info.modelOrder = buildOrder(info, info.defaultModel);
|
|
358
|
+
}
|
|
325
359
|
function readJsonStringValue(filePath, paths, info) {
|
|
326
360
|
if (!existsSync(filePath)) {
|
|
327
361
|
return undefined;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { CliType } from "./session-manager.js";
|
|
2
|
+
export interface ProviderLoginGuidance {
|
|
3
|
+
provider: CliType;
|
|
4
|
+
displayName: string;
|
|
5
|
+
install: {
|
|
6
|
+
summary: string;
|
|
7
|
+
commands: string[];
|
|
8
|
+
documentationUrl?: string;
|
|
9
|
+
};
|
|
10
|
+
login: {
|
|
11
|
+
summary: string;
|
|
12
|
+
commands: string[];
|
|
13
|
+
credentialHandling: string;
|
|
14
|
+
};
|
|
15
|
+
verification: {
|
|
16
|
+
command: string;
|
|
17
|
+
expected: string;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export declare function getProviderLoginGuidance(provider: CliType): ProviderLoginGuidance;
|
|
21
|
+
export declare function getAllProviderLoginGuidance(): Record<CliType, ProviderLoginGuidance>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const GUIDANCE = {
|
|
2
|
+
claude: {
|
|
3
|
+
provider: "claude",
|
|
4
|
+
displayName: "Claude Code",
|
|
5
|
+
install: {
|
|
6
|
+
summary: "Install Claude Code using Anthropic's current official installer.",
|
|
7
|
+
commands: ["npm install -g @anthropic-ai/claude-code"],
|
|
8
|
+
documentationUrl: "https://docs.anthropic.com/claude-code",
|
|
9
|
+
},
|
|
10
|
+
login: {
|
|
11
|
+
summary: "Sign in through Claude Code's official browser/device flow.",
|
|
12
|
+
commands: ["claude auth login"],
|
|
13
|
+
credentialHandling: "Do not paste Claude passwords, OAuth tokens, or credential files into the gateway or a remote chat.",
|
|
14
|
+
},
|
|
15
|
+
verification: {
|
|
16
|
+
command: "claude auth status --json",
|
|
17
|
+
expected: "loggedIn is true",
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
codex: {
|
|
21
|
+
provider: "codex",
|
|
22
|
+
displayName: "Codex CLI",
|
|
23
|
+
install: {
|
|
24
|
+
summary: "Install Codex CLI using OpenAI's npm package or the current official installer.",
|
|
25
|
+
commands: ["npm install -g @openai/codex"],
|
|
26
|
+
documentationUrl: "https://developers.openai.com/codex",
|
|
27
|
+
},
|
|
28
|
+
login: {
|
|
29
|
+
summary: "Sign in through Codex's official login flow.",
|
|
30
|
+
commands: ["codex login", "codex login --device-auth"],
|
|
31
|
+
credentialHandling: "Prefer browser or device-code login. Do not paste API keys or access tokens into assistant prompts.",
|
|
32
|
+
},
|
|
33
|
+
verification: {
|
|
34
|
+
command: "codex login status",
|
|
35
|
+
expected: "command reports that Codex is logged in",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
gemini: {
|
|
39
|
+
provider: "gemini",
|
|
40
|
+
displayName: "Gemini CLI",
|
|
41
|
+
install: {
|
|
42
|
+
summary: "Install Gemini CLI using Google's npm package or current official installer.",
|
|
43
|
+
commands: ["npm install -g @google/gemini-cli"],
|
|
44
|
+
documentationUrl: "https://github.com/google-gemini/gemini-cli",
|
|
45
|
+
},
|
|
46
|
+
login: {
|
|
47
|
+
summary: "Run Gemini CLI and complete Google's official sign-in flow when prompted.",
|
|
48
|
+
commands: ["gemini"],
|
|
49
|
+
credentialHandling: "Let Gemini CLI store credentials in its own local store. Do not paste OAuth files or API keys into chat.",
|
|
50
|
+
},
|
|
51
|
+
verification: {
|
|
52
|
+
command: "gemini --version",
|
|
53
|
+
expected: "CLI is installed; doctor checks the local Gemini credential store for login evidence",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
grok: {
|
|
57
|
+
provider: "grok",
|
|
58
|
+
displayName: "Grok CLI",
|
|
59
|
+
install: {
|
|
60
|
+
summary: "Install Grok CLI using xAI's current official installer or managed update flow.",
|
|
61
|
+
commands: ["npm install -g grok-build"],
|
|
62
|
+
documentationUrl: "https://docs.x.ai/build/cli",
|
|
63
|
+
},
|
|
64
|
+
login: {
|
|
65
|
+
summary: "Sign in through Grok's official OAuth or device-code flow.",
|
|
66
|
+
commands: ["grok login --oauth", "grok login --device-auth"],
|
|
67
|
+
credentialHandling: "Do not paste xAI API keys, OAuth tokens, or Grok auth files into the gateway or a remote chat.",
|
|
68
|
+
},
|
|
69
|
+
verification: {
|
|
70
|
+
command: "grok inspect --json",
|
|
71
|
+
expected: "CLI can inspect local configuration and a local auth store is present",
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
mistral: {
|
|
75
|
+
provider: "mistral",
|
|
76
|
+
displayName: "Mistral Vibe CLI",
|
|
77
|
+
install: {
|
|
78
|
+
summary: "Install Mistral Vibe CLI via pip, uv, or Homebrew (Vibe does not self-update; cli_upgrade dispatches to the installer it detects).",
|
|
79
|
+
commands: ["pip install vibe-cli", "uv tool install vibe-cli", "brew install mistral-vibe"],
|
|
80
|
+
documentationUrl: "https://docs.mistral.ai/agents/vibe-cli",
|
|
81
|
+
},
|
|
82
|
+
login: {
|
|
83
|
+
summary: "Sign in through Mistral's official auth flow and enable session_logging in ~/.vibe/config.toml.",
|
|
84
|
+
commands: ["vibe auth login", "vibe config set session_logging.enabled true"],
|
|
85
|
+
credentialHandling: "Do not paste Mistral API keys, OAuth tokens, or ~/.vibe/credentials into the gateway or a remote chat.",
|
|
86
|
+
},
|
|
87
|
+
verification: {
|
|
88
|
+
command: "vibe --version",
|
|
89
|
+
expected: "Vibe CLI is installed; doctor additionally checks ~/.vibe/config.toml for session_logging.enabled=true",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
export function getProviderLoginGuidance(provider) {
|
|
94
|
+
return GUIDANCE[provider];
|
|
95
|
+
}
|
|
96
|
+
export function getAllProviderLoginGuidance() {
|
|
97
|
+
return { ...GUIDANCE };
|
|
98
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CliType } from "./session-manager.js";
|
|
2
|
+
import { type ProviderLoginGuidance } from "./provider-login-guidance.js";
|
|
3
|
+
export type ProviderLoginStatus = "authenticated" | "not_authenticated" | "unknown" | "not_checked";
|
|
4
|
+
export interface ProviderRuntimeStatus {
|
|
5
|
+
provider: CliType;
|
|
6
|
+
displayName: string;
|
|
7
|
+
command: string;
|
|
8
|
+
installed: boolean;
|
|
9
|
+
version: string | null;
|
|
10
|
+
versionCommand: string[];
|
|
11
|
+
loginStatus: ProviderLoginStatus;
|
|
12
|
+
loginCheck: {
|
|
13
|
+
method: "cli" | "credential_store" | "not_checked";
|
|
14
|
+
command: string[] | null;
|
|
15
|
+
credentialStore: "present" | "not_found" | "not_checked";
|
|
16
|
+
detail: string;
|
|
17
|
+
};
|
|
18
|
+
guidance: ProviderLoginGuidance;
|
|
19
|
+
}
|
|
20
|
+
export declare const PROVIDER_COMMANDS: Record<CliType, string>;
|
|
21
|
+
export declare function listProviderRuntimeStatuses(): Record<CliType, ProviderRuntimeStatus>;
|
|
22
|
+
export declare function getProviderRuntimeStatus(provider: CliType): ProviderRuntimeStatus;
|
|
23
|
+
export interface GeminiAuthMethods {
|
|
24
|
+
oauth: boolean;
|
|
25
|
+
geminiApiKey: boolean;
|
|
26
|
+
googleApiKey: boolean;
|
|
27
|
+
vertexAi: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface GeminiAuthStatus {
|
|
30
|
+
status: "present" | "not_found";
|
|
31
|
+
methods: GeminiAuthMethods;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* U27: Detect Gemini auth across all supported methods.
|
|
35
|
+
* Returns "present" if ANY of:
|
|
36
|
+
* - OAuth credential file present (~/.gemini/oauth_creds.json, etc.)
|
|
37
|
+
* - GEMINI_API_KEY env var set and non-empty
|
|
38
|
+
* - GOOGLE_API_KEY env var set and non-empty
|
|
39
|
+
* - GOOGLE_CLOUD_PROJECT set AND GOOGLE_GENAI_USE_VERTEXAI=true
|
|
40
|
+
*/
|
|
41
|
+
export declare function geminiAuthStatus(env?: NodeJS.ProcessEnv, home?: string): GeminiAuthStatus;
|