llm-cli-gateway 2.10.0 → 2.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +7 -5
  3. package/dist/acp/event-normalizer.d.ts +42 -0
  4. package/dist/acp/event-normalizer.js +71 -0
  5. package/dist/acp/flight-redaction.d.ts +25 -0
  6. package/dist/acp/flight-redaction.js +40 -0
  7. package/dist/acp/host-services.d.ts +16 -0
  8. package/dist/acp/host-services.js +29 -0
  9. package/dist/acp/permission-bridge.d.ts +15 -0
  10. package/dist/acp/permission-bridge.js +90 -0
  11. package/dist/acp/process-manager.js +7 -1
  12. package/dist/acp/provider-registry.d.ts +1 -1
  13. package/dist/acp/provider-registry.js +13 -0
  14. package/dist/acp/runtime.d.ts +35 -0
  15. package/dist/acp/runtime.js +125 -0
  16. package/dist/acp/session-map.d.ts +42 -0
  17. package/dist/acp/session-map.js +67 -0
  18. package/dist/acp/smoke-harness.d.ts +28 -0
  19. package/dist/acp/smoke-harness.js +90 -0
  20. package/dist/api-http.d.ts +18 -0
  21. package/dist/api-http.js +122 -0
  22. package/dist/api-provider.d.ts +83 -0
  23. package/dist/api-provider.js +258 -0
  24. package/dist/api-request.d.ts +30 -0
  25. package/dist/api-request.js +51 -0
  26. package/dist/approval-manager.d.ts +1 -1
  27. package/dist/approval-manager.js +6 -7
  28. package/dist/async-job-manager.d.ts +19 -4
  29. package/dist/async-job-manager.js +211 -35
  30. package/dist/claude-mcp-config.d.ts +2 -2
  31. package/dist/claude-mcp-config.js +42 -52
  32. package/dist/cli-updater.js +16 -1
  33. package/dist/config.d.ts +20 -0
  34. package/dist/config.js +93 -35
  35. package/dist/doctor.d.ts +1 -1
  36. package/dist/flight-recorder.d.ts +1 -0
  37. package/dist/flight-recorder.js +11 -0
  38. package/dist/index.d.ts +56 -5
  39. package/dist/index.js +639 -38
  40. package/dist/job-store.d.ts +15 -0
  41. package/dist/job-store.js +39 -5
  42. package/dist/mcp-registry.d.ts +17 -0
  43. package/dist/mcp-registry.js +5 -0
  44. package/dist/metrics.js +7 -2
  45. package/dist/model-registry.js +11 -0
  46. package/dist/prompt-parts.d.ts +6 -6
  47. package/dist/provider-login-guidance.js +21 -0
  48. package/dist/provider-status.js +4 -1
  49. package/dist/provider-tool-capabilities.d.ts +4 -3
  50. package/dist/provider-tool-capabilities.js +93 -6
  51. package/dist/request-helpers.d.ts +6 -6
  52. package/dist/request-helpers.js +1 -4
  53. package/dist/session-manager-pg.js +2 -9
  54. package/dist/session-manager.d.ts +9 -4
  55. package/dist/session-manager.js +13 -4
  56. package/dist/upstream-contracts.js +112 -2
  57. package/dist/validation-normalizer.d.ts +2 -2
  58. package/dist/validation-orchestrator.d.ts +2 -0
  59. package/dist/validation-orchestrator.js +28 -7
  60. package/dist/validation-tools.d.ts +61 -0
  61. package/dist/validation-tools.js +36 -21
  62. package/migrations/005_provider_type_open_api_names.sql +28 -0
  63. package/npm-shrinkwrap.json +4 -3
  64. package/package.json +12 -9
package/dist/config.js CHANGED
@@ -5,6 +5,7 @@ import { createRequire } from "module";
5
5
  import { z } from "zod/v3";
6
6
  import { logWarn, noopLogger } from "./logger.js";
7
7
  import { hashSecret, isSecretHash } from "./oauth.js";
8
+ import { isHttpsOrLoopbackUrl, isLoopbackUrl } from "./api-http.js";
8
9
  const DatabaseUrlSchema = z
9
10
  .string()
10
11
  .url()
@@ -267,22 +268,17 @@ export function minStableTokensForModel(config, modelName) {
267
268
  return table.haiku;
268
269
  return table.default;
269
270
  }
271
+ const RESERVED_CLI_PROVIDER_NAMES = [
272
+ "claude",
273
+ "codex",
274
+ "gemini",
275
+ "grok",
276
+ "mistral",
277
+ "devin",
278
+ ];
270
279
  export const DEFAULT_XAI_API_KEY_ENV = "XAI_API_KEY";
271
280
  export const DEFAULT_XAI_BASE_URL = "https://api.x.ai/v1";
272
281
  export const DEFAULT_XAI_MODEL = "grok-build-0.1";
273
- function isHttpsOrLoopbackUrl(value) {
274
- try {
275
- const url = new URL(value);
276
- if (url.protocol === "https:")
277
- return true;
278
- if (url.protocol !== "http:")
279
- return false;
280
- return ["localhost", "127.0.0.1", "::1", "[::1]"].includes(url.hostname);
281
- }
282
- catch {
283
- return false;
284
- }
285
- }
286
282
  const XaiProviderSchema = z
287
283
  .object({
288
284
  api_key_env: z.string().min(1).default(DEFAULT_XAI_API_KEY_ENV),
@@ -296,6 +292,17 @@ const XaiProviderSchema = z
296
292
  default_model: z.string().min(1).default(DEFAULT_XAI_MODEL),
297
293
  })
298
294
  .strict();
295
+ const ApiProviderSchema = z
296
+ .object({
297
+ kind: z.enum(["openai-compatible", "anthropic", "xai-responses"]),
298
+ base_url: z.string().url().refine(isHttpsOrLoopbackUrl, {
299
+ message: "base_url must use https unless it targets localhost/loopback",
300
+ }),
301
+ api_key_env: z.string().min(1).optional(),
302
+ default_model: z.string().min(1),
303
+ models: z.array(z.string().min(1)).nonempty().optional(),
304
+ })
305
+ .strict();
299
306
  function readProvidersFile(configPath, logger) {
300
307
  if (!existsSync(configPath)) {
301
308
  return { raw: undefined, sourcePath: null };
@@ -315,32 +322,83 @@ function readProvidersFile(configPath, logger) {
315
322
  export function loadProvidersConfig(logger = noopLogger) {
316
323
  const configPath = defaultGatewayConfigPath();
317
324
  const { raw, sourcePath } = readProvidersFile(configPath, logger);
318
- const providers = raw ?? {};
319
- const rawXai = providers.xai;
320
- if (rawXai === undefined) {
321
- return {
322
- xai: null,
323
- sources: { configFile: sourcePath },
325
+ const rawProviders = raw ?? {};
326
+ const providers = {};
327
+ let xai = null;
328
+ const rawXai = rawProviders.xai;
329
+ if (rawXai !== undefined) {
330
+ const parsed = XaiProviderSchema.safeParse(rawXai);
331
+ if (parsed.success) {
332
+ xai = {
333
+ apiKeyEnv: parsed.data.api_key_env,
334
+ baseUrl: parsed.data.base_url,
335
+ defaultModel: parsed.data.default_model,
336
+ };
337
+ providers.xai = {
338
+ name: "xai",
339
+ kind: "xai-responses",
340
+ apiKeyEnv: parsed.data.api_key_env,
341
+ baseUrl: parsed.data.base_url,
342
+ defaultModel: parsed.data.default_model,
343
+ };
344
+ }
345
+ else {
346
+ logWarn(logger, "Invalid [providers.xai] config; xAI API provider disabled", {
347
+ error: parsed.error.message,
348
+ });
349
+ }
350
+ }
351
+ for (const [name, rawProvider] of Object.entries(rawProviders)) {
352
+ if (name === "xai")
353
+ continue;
354
+ if (RESERVED_CLI_PROVIDER_NAMES.includes(name)) {
355
+ logWarn(logger, `[providers.${name}] is rejected: "${name}" is a reserved CLI provider name and cannot be used for an API provider`);
356
+ continue;
357
+ }
358
+ const parsed = ApiProviderSchema.safeParse(rawProvider);
359
+ if (!parsed.success) {
360
+ logWarn(logger, `Invalid [providers.${name}] config; API provider disabled`, {
361
+ error: parsed.error.message,
362
+ });
363
+ continue;
364
+ }
365
+ providers[name] = {
366
+ name,
367
+ kind: parsed.data.kind,
368
+ apiKeyEnv: parsed.data.api_key_env ?? null,
369
+ baseUrl: parsed.data.base_url,
370
+ defaultModel: parsed.data.default_model,
371
+ models: parsed.data.models ? [...parsed.data.models] : undefined,
324
372
  };
325
373
  }
326
- const parsed = XaiProviderSchema.safeParse(rawXai);
327
- if (!parsed.success) {
328
- logWarn(logger, "Invalid [providers.xai] config; xAI API provider disabled", {
329
- error: parsed.error.message,
374
+ return { xai, providers, sources: { configFile: sourcePath } };
375
+ }
376
+ function resolveProviderKey(apiKeyEnv, env) {
377
+ if (!apiKeyEnv)
378
+ return null;
379
+ const value = env[apiKeyEnv];
380
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
381
+ }
382
+ export function isApiProviderEnabled(provider, env = process.env) {
383
+ if (resolveProviderKey(provider.apiKeyEnv, env) !== null)
384
+ return true;
385
+ return provider.kind === "openai-compatible" && isLoopbackUrl(provider.baseUrl);
386
+ }
387
+ export function enabledApiProviders(config, env = process.env) {
388
+ const runtimes = [];
389
+ for (const provider of Object.values(config.providers ?? {})) {
390
+ if (!isApiProviderEnabled(provider, env))
391
+ continue;
392
+ runtimes.push({
393
+ name: provider.name,
394
+ kind: provider.kind,
395
+ baseUrl: provider.baseUrl,
396
+ defaultModel: provider.defaultModel,
397
+ models: provider.models,
398
+ apiKey: resolveProviderKey(provider.apiKeyEnv, env) ?? "",
330
399
  });
331
- return {
332
- xai: null,
333
- sources: { configFile: sourcePath },
334
- };
335
400
  }
336
- return {
337
- xai: {
338
- apiKeyEnv: parsed.data.api_key_env,
339
- baseUrl: parsed.data.base_url,
340
- defaultModel: parsed.data.default_model,
341
- },
342
- sources: { configFile: sourcePath },
343
- };
401
+ return runtimes;
344
402
  }
345
403
  export function isXaiProviderEnabled(config, env = process.env) {
346
404
  const keyEnv = config.xai?.apiKeyEnv;
package/dist/doctor.d.ts CHANGED
@@ -3,7 +3,7 @@ import { type ProviderLoginStatus } from "./provider-status.js";
3
3
  import type { FlightRecorderQuery } from "./flight-recorder.js";
4
4
  import { type CacheAwarenessConfig } from "./config.js";
5
5
  import { type ProviderCapabilityId, type ProviderKind } from "./provider-tool-capabilities.js";
6
- export type CliType = "claude" | "codex" | "gemini" | "grok" | "mistral";
6
+ export type CliType = "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin";
7
7
  export interface CacheAwarenessReport {
8
8
  enabled_features: Array<"anthropic_cache_control" | "ttl_warnings">;
9
9
  last_24h: {
@@ -27,6 +27,7 @@ export interface FlightLogResult {
27
27
  optimizationApplied: boolean;
28
28
  thinkingBlocks?: string[];
29
29
  exitCode: number;
30
+ httpStatus?: number;
30
31
  errorMessage?: string;
31
32
  status: "completed" | "failed";
32
33
  }
@@ -40,6 +40,13 @@ function ensureCacheControlBlocksColumn(db) {
40
40
  db.exec("ALTER TABLE requests ADD COLUMN cache_control_blocks INTEGER");
41
41
  }
42
42
  }
43
+ function ensureMetadataHttpStatusColumn(db) {
44
+ const rows = db.prepare("PRAGMA table_info(gateway_metadata)").all();
45
+ const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
46
+ if (!names.has("http_status")) {
47
+ db.exec("ALTER TABLE gateway_metadata ADD COLUMN http_status INTEGER");
48
+ }
49
+ }
43
50
  function ensureCacheControlTtlSecondsColumn(db) {
44
51
  const rows = db.prepare("PRAGMA table_info(requests)").all();
45
52
  const names = new Set(rows.map((row) => (row && typeof row.name === "string" ? row.name : "")));
@@ -137,6 +144,7 @@ export class FlightRecorder {
137
144
  optimization_applied INTEGER DEFAULT 0,
138
145
  thinking_blocks TEXT,
139
146
  exit_code INTEGER,
147
+ http_status INTEGER,
140
148
  error_message TEXT,
141
149
  async_job_id TEXT,
142
150
  status TEXT NOT NULL DEFAULT 'started'
@@ -168,6 +176,7 @@ export class FlightRecorder {
168
176
  .prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(5, ?)")
169
177
  .run(new Date().toISOString());
170
178
  ensureRequestsOwnerColumn(this.db);
179
+ ensureMetadataHttpStatusColumn(this.db);
171
180
  this.db
172
181
  .prepare("INSERT OR IGNORE INTO _migrations(version, applied_at) VALUES(6, ?)")
173
182
  .run(new Date().toISOString());
@@ -229,6 +238,7 @@ export class FlightRecorder {
229
238
  optimization_applied = @optimization_applied,
230
239
  thinking_blocks = @thinking_blocks,
231
240
  exit_code = @exit_code,
241
+ http_status = @http_status,
232
242
  error_message = @error_message,
233
243
  status = @status
234
244
  WHERE request_id = @id AND status = 'started'
@@ -255,6 +265,7 @@ export class FlightRecorder {
255
265
  optimization_applied: result.optimizationApplied ? 1 : 0,
256
266
  thinking_blocks: thinkingBlocks,
257
267
  exit_code: result.exitCode,
268
+ http_status: result.httpStatus ?? null,
258
269
  error_message: result.errorMessage ?? null,
259
270
  status: result.status,
260
271
  });
package/dist/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { z } from "zod/v3";
4
- import { ISessionManager, type ProviderType } from "./session-manager.js";
4
+ import { ISessionManager, type CliType, type ProviderType } from "./session-manager.js";
5
5
  import { ResourceProvider } from "./resources.js";
6
6
  import { PerformanceMetrics } from "./metrics.js";
7
- import { type PersistenceConfig, type CacheAwarenessConfig, type ProvidersConfig } from "./config.js";
7
+ import { type PersistenceConfig, type CacheAwarenessConfig, type ProvidersConfig, type ApiProviderRuntime, type AcpConfig } from "./config.js";
8
8
  import { type XaiReasoningEffort } from "./xai-api-provider.js";
9
9
  import { DatabaseConnection } from "./db.js";
10
10
  import { AsyncJobManager } from "./async-job-manager.js";
@@ -61,8 +61,8 @@ export declare const WORKTREE_SCHEMA: z.ZodUnion<[z.ZodBoolean, z.ZodObject<{
61
61
  ref?: string | undefined;
62
62
  }>]>;
63
63
  export declare const WORKSPACE_ALIAS_SCHEMA: z.ZodString;
64
- export declare const SESSION_PROVIDER_VALUES: readonly ["claude", "codex", "gemini", "grok", "mistral", "grok-api"];
65
- export declare const SESSION_PROVIDER_ENUM: z.ZodEnum<["claude", "codex", "gemini", "grok", "mistral", "grok-api"]>;
64
+ export declare const SESSION_PROVIDER_VALUES: readonly ["claude", "codex", "gemini", "grok", "mistral", "devin", "grok-api"];
65
+ export declare const SESSION_PROVIDER_ENUM: z.ZodEnum<["claude", "codex", "gemini", "grok", "mistral", "devin", "grok-api"]>;
66
66
  export type SessionProvider = ProviderType;
67
67
  export interface GatewayServerDeps {
68
68
  sessionManager?: ISessionManager;
@@ -76,6 +76,7 @@ export interface GatewayServerDeps {
76
76
  persistence?: PersistenceConfig;
77
77
  cacheAwareness?: CacheAwarenessConfig;
78
78
  providers?: ProvidersConfig;
79
+ acpConfig?: AcpConfig;
79
80
  workspaces?: WorkspaceRegistry;
80
81
  }
81
82
  export interface GatewayServerRuntime {
@@ -90,12 +91,20 @@ export interface GatewayServerRuntime {
90
91
  persistence: PersistenceConfig;
91
92
  cacheAwareness: CacheAwarenessConfig;
92
93
  providers: ProvidersConfig;
94
+ acpConfig: AcpConfig;
93
95
  workspaces: WorkspaceRegistry;
94
96
  }
95
97
  export declare function resolveGatewayServerRuntime(deps?: GatewayServerDeps, options?: {
96
98
  isolateState?: boolean;
97
99
  }): GatewayServerRuntime;
98
100
  export declare function shouldRegisterGrokApiTools(providers: ProvidersConfig): boolean;
101
+ export declare function runAcpTransport(deps: HandlerDeps, params: {
102
+ provider: CliType;
103
+ prompt?: string;
104
+ model?: string;
105
+ sessionId?: string;
106
+ correlationId?: string;
107
+ }): Promise<ExtendedToolResponse>;
99
108
  export interface ResolvedWorktree {
100
109
  cwd?: string;
101
110
  worktreePath?: string;
@@ -111,7 +120,7 @@ export declare function resolveWorktreeForRequest(worktreeOpt: boolean | {
111
120
  workspaceRoot?: string;
112
121
  }): Promise<ResolvedWorktree>;
113
122
  export declare function formatWorktreePrefix(worktreePath?: string): string;
114
- export declare function extractUsageAndCost(cli: "claude" | "codex" | "gemini" | "grok" | "mistral", output: string, outputFormat?: string, ctx?: {
123
+ export declare function extractUsageAndCost(cli: "claude" | "codex" | "gemini" | "grok" | "mistral" | "devin", output: string, outputFormat?: string, ctx?: {
115
124
  sessionId?: string;
116
125
  home?: string;
117
126
  }): {
@@ -316,6 +325,20 @@ export interface GrokApiRequestParams {
316
325
  timeoutMs?: number;
317
326
  }
318
327
  export declare function handleGrokApiRequest(deps: HandlerDeps, params: GrokApiRequestParams): Promise<ExtendedToolResponse>;
328
+ interface ApiProviderToolParams {
329
+ prompt?: string;
330
+ system?: string;
331
+ model?: string;
332
+ correlationId?: string;
333
+ maxOutputTokens?: number;
334
+ temperature?: number;
335
+ topP?: number;
336
+ reasoningEffort?: "none" | "low" | "medium" | "high";
337
+ timeoutMs?: number;
338
+ }
339
+ export declare function handleApiProviderRequest(runtimeArg: GatewayServerRuntime, providerRuntime: ApiProviderRuntime, params: ApiProviderToolParams): Promise<ExtendedToolResponse>;
340
+ export declare function handleApiProviderRequestAsync(runtimeArg: GatewayServerRuntime, providerRuntime: ApiProviderRuntime, params: ApiProviderToolParams): ExtendedToolResponse;
341
+ export declare function registerApiProviderTools(server: McpServer, runtime: GatewayServerRuntime, providers: ProvidersConfig, asyncJobsEnabled: boolean): string[];
319
342
  export interface GeminiRequestParams {
320
343
  prompt?: string;
321
344
  promptParts?: PromptParts;
@@ -368,6 +391,7 @@ export interface GrokRequestParams {
368
391
  promptParts?: PromptParts;
369
392
  model?: string;
370
393
  outputFormat?: string;
394
+ transport?: "cli" | "acp";
371
395
  sessionId?: string;
372
396
  resumeLatest: boolean;
373
397
  createNewSession: boolean;
@@ -421,11 +445,38 @@ export interface GrokRequestParams {
421
445
  }
422
446
  export declare function handleGrokRequest(deps: HandlerDeps, params: GrokRequestParams): Promise<ExtendedToolResponse>;
423
447
  export declare function handleGrokRequestAsync(deps: AsyncHandlerDeps, params: Omit<GrokRequestParams, "optimizeResponse">): Promise<ExtendedToolResponse>;
448
+ export interface DevinRequestParams {
449
+ prompt?: string;
450
+ model?: string;
451
+ permissionMode?: "normal" | "auto" | "dangerous" | "yolo" | "bypass";
452
+ promptFile?: string;
453
+ transport?: "cli" | "acp";
454
+ sessionId?: string;
455
+ resumeLatest?: boolean;
456
+ createNewSession?: boolean;
457
+ correlationId?: string;
458
+ optimizePrompt: boolean;
459
+ optimizeResponse?: boolean;
460
+ idleTimeoutMs?: number;
461
+ forceRefresh?: boolean;
462
+ }
463
+ export declare function prepareDevinRequest(params: {
464
+ prompt?: string;
465
+ model?: string;
466
+ permissionMode?: string;
467
+ promptFile?: string;
468
+ correlationId?: string;
469
+ optimizePrompt: boolean;
470
+ operation: string;
471
+ }, _runtime: GatewayServerRuntime): CliRequestPrep | ExtendedToolResponse;
472
+ export declare function handleDevinRequest(deps: HandlerDeps, params: DevinRequestParams): Promise<ExtendedToolResponse>;
473
+ export declare function handleDevinRequestAsync(deps: AsyncHandlerDeps, params: Omit<DevinRequestParams, "optimizeResponse">): Promise<ExtendedToolResponse>;
424
474
  export interface MistralRequestParams {
425
475
  prompt?: string;
426
476
  promptParts?: PromptParts;
427
477
  model?: string;
428
478
  outputFormat?: string;
479
+ transport?: "cli" | "acp";
429
480
  sessionId?: string;
430
481
  resumeLatest: boolean;
431
482
  createNewSession: boolean;