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.
Files changed (79) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +68 -29
  3. package/dist/acp/client.d.ts +29 -1
  4. package/dist/acp/client.js +78 -4
  5. package/dist/acp/errors.d.ts +9 -1
  6. package/dist/acp/errors.js +19 -0
  7. package/dist/acp/event-normalizer.d.ts +12 -0
  8. package/dist/acp/event-normalizer.js +16 -0
  9. package/dist/acp/flight-redaction.d.ts +3 -0
  10. package/dist/acp/flight-redaction.js +3 -0
  11. package/dist/acp/permission-bridge.js +11 -5
  12. package/dist/acp/process-manager.d.ts +2 -1
  13. package/dist/acp/process-manager.js +43 -4
  14. package/dist/acp/provider-registry.js +19 -11
  15. package/dist/acp/runtime.d.ts +8 -0
  16. package/dist/acp/runtime.js +47 -4
  17. package/dist/acp/types.d.ts +3083 -55
  18. package/dist/acp/types.js +242 -5
  19. package/dist/async-job-manager.d.ts +38 -1
  20. package/dist/async-job-manager.js +287 -20
  21. package/dist/codex-json-parser.d.ts +1 -0
  22. package/dist/codex-json-parser.js +6 -0
  23. package/dist/config.d.ts +19 -0
  24. package/dist/config.js +84 -1
  25. package/dist/flight-recorder.d.ts +2 -0
  26. package/dist/flight-recorder.js +20 -0
  27. package/dist/gemini-json-parser.d.ts +2 -0
  28. package/dist/gemini-json-parser.js +45 -8
  29. package/dist/grok-json-parser.d.ts +14 -0
  30. package/dist/grok-json-parser.js +156 -0
  31. package/dist/index.d.ts +47 -2
  32. package/dist/index.js +504 -122
  33. package/dist/job-store.d.ts +119 -11
  34. package/dist/job-store.js +372 -42
  35. package/dist/model-registry.d.ts +1 -0
  36. package/dist/model-registry.js +46 -0
  37. package/dist/oauth.js +2 -2
  38. package/dist/postgres-job-store-worker.d.ts +1 -0
  39. package/dist/postgres-job-store-worker.js +444 -0
  40. package/dist/pricing.d.ts +3 -2
  41. package/dist/provider-acp-capabilities.d.ts +52 -0
  42. package/dist/provider-acp-capabilities.js +101 -0
  43. package/dist/provider-admin-tools.d.ts +100 -0
  44. package/dist/provider-admin-tools.js +572 -0
  45. package/dist/provider-capability-cache.d.ts +46 -0
  46. package/dist/provider-capability-cache.js +248 -0
  47. package/dist/provider-capability-discovery.d.ts +85 -0
  48. package/dist/provider-capability-discovery.js +461 -0
  49. package/dist/provider-capability-resolver.d.ts +29 -0
  50. package/dist/provider-capability-resolver.js +92 -0
  51. package/dist/provider-definition-assertions.d.ts +9 -0
  52. package/dist/provider-definition-assertions.js +147 -0
  53. package/dist/provider-definitions.d.ts +127 -0
  54. package/dist/provider-definitions.js +758 -0
  55. package/dist/provider-help-parser.d.ts +34 -0
  56. package/dist/provider-help-parser.js +203 -0
  57. package/dist/provider-model-discovery.d.ts +30 -0
  58. package/dist/provider-model-discovery.js +229 -0
  59. package/dist/provider-output-metadata.d.ts +7 -0
  60. package/dist/provider-output-metadata.js +68 -0
  61. package/dist/provider-schema-builder.d.ts +22 -0
  62. package/dist/provider-schema-builder.js +55 -0
  63. package/dist/provider-surface-generator.d.ts +98 -0
  64. package/dist/provider-surface-generator.js +140 -0
  65. package/dist/provider-tool-capabilities.d.ts +3 -2
  66. package/dist/provider-tool-capabilities.js +77 -62
  67. package/dist/request-helpers.d.ts +37 -4
  68. package/dist/request-helpers.js +134 -0
  69. package/dist/resources.d.ts +6 -1
  70. package/dist/resources.js +64 -192
  71. package/dist/session-manager.js +18 -11
  72. package/dist/sqlite-driver.d.ts +1 -1
  73. package/dist/sqlite-driver.js +2 -1
  74. package/dist/stream-json-parser.d.ts +1 -0
  75. package/dist/stream-json-parser.js +3 -0
  76. package/dist/upstream-contracts.d.ts +17 -1
  77. package/dist/upstream-contracts.js +209 -36
  78. package/npm-shrinkwrap.json +2 -2
  79. package/package.json +8 -3
@@ -2,6 +2,9 @@ export function redactAcpMessage(input) {
2
2
  let out = redactJsonLikeBodies(input);
3
3
  out = out.replace(/\b(bearer|token|api[_-]?key|secret)\b\s*[:=]?\s*\S+/gi, "$1 <redacted>");
4
4
  out = out.replace(/\b(sk|xai|gsk|key)-[A-Za-z0-9_-]{8,}\b/gi, "<redacted-token>");
5
+ out = out.replace(/\bAIza[A-Za-z0-9_-]{10,}/g, "<redacted-token>");
6
+ out = out.replace(/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g, "<redacted-token>");
7
+ out = out.replace(/\bxox[baprs]-[A-Za-z0-9-]{10,}\b/gi, "<redacted-token>");
5
8
  out = out.replace(/(^|[^A-Za-z0-9._~\\/-])[A-Za-z]:\\[^\s"')\]}>]*/g, "$1<redacted-path>");
6
9
  out = out.replace(/(^|[^A-Za-z0-9._~\\/-])\\\\[^\s"')\]}>]+/g, "$1<redacted-path>");
7
10
  out = out.replace(/(^|[^A-Za-z0-9._~/-])~\/[^\s"')\]}>]+/g, "$1<redacted-path>");
@@ -150,6 +153,22 @@ export class ProviderUnavailableError extends AcpError {
150
153
  this.name = "ProviderUnavailableError";
151
154
  }
152
155
  }
156
+ export class AcpMethodUnsupportedError extends AcpError {
157
+ method;
158
+ constructor(provider, method, debug) {
159
+ super("method_unsupported", `ACP method ${method} is not advertised by provider ${provider}'s capability set; the agent did not declare support for it.`, { provider, debug: { method, ...(debug ?? {}) } });
160
+ this.name = "AcpMethodUnsupportedError";
161
+ this.method = method;
162
+ }
163
+ }
164
+ export class AcpMutatingDisabledError extends AcpError {
165
+ method;
166
+ constructor(provider, method, debug) {
167
+ super("mutating_disabled", `ACP mutating session op ${method} is disabled. Set allow_mutating_session_ops=true under [acp] to permit state-mutating ACP admin ops.`, { provider, debug: { method, ...(debug ?? {}) } });
168
+ this.name = "AcpMutatingDisabledError";
169
+ this.method = method;
170
+ }
171
+ }
153
172
  export class AcpProtocolError extends AcpError {
154
173
  code;
155
174
  constructor(userMessage, options) {
@@ -29,6 +29,12 @@ export type AcpProgressEvent = {
29
29
  readonly kind: "usage";
30
30
  readonly size: number;
31
31
  readonly used: number;
32
+ } | {
33
+ readonly kind: "session_complete";
34
+ readonly stopReason: string;
35
+ } | {
36
+ readonly kind: "error";
37
+ readonly message: string;
32
38
  } | {
33
39
  readonly kind: "other";
34
40
  readonly sessionUpdate: string;
@@ -37,6 +43,12 @@ export declare function summarizeContentBlock(block: ContentBlock): string;
37
43
  export declare function normalizeSessionUpdate(notification: SessionUpdateNotification): AcpProgressEvent;
38
44
  export declare class AcpEventNormalizer {
39
45
  private text;
46
+ private stopReasonValue;
47
+ private errorValue;
40
48
  handle(notification: SessionUpdateNotification): AcpProgressEvent;
49
+ completeWith(stopReason: string): AcpProgressEvent;
50
+ error(message: string): AcpProgressEvent;
41
51
  get finalText(): string;
52
+ get stopReason(): string | undefined;
53
+ get errorMessage(): string | undefined;
42
54
  }
@@ -58,6 +58,8 @@ export function normalizeSessionUpdate(notification) {
58
58
  }
59
59
  export class AcpEventNormalizer {
60
60
  text = "";
61
+ stopReasonValue;
62
+ errorValue;
61
63
  handle(notification) {
62
64
  const event = normalizeSessionUpdate(notification);
63
65
  if (event.kind === "agent_message") {
@@ -65,7 +67,21 @@ export class AcpEventNormalizer {
65
67
  }
66
68
  return event;
67
69
  }
70
+ completeWith(stopReason) {
71
+ this.stopReasonValue = stopReason;
72
+ return { kind: "session_complete", stopReason };
73
+ }
74
+ error(message) {
75
+ this.errorValue = message;
76
+ return { kind: "error", message };
77
+ }
68
78
  get finalText() {
69
79
  return this.text;
70
80
  }
81
+ get stopReason() {
82
+ return this.stopReasonValue;
83
+ }
84
+ get errorMessage() {
85
+ return this.errorValue;
86
+ }
71
87
  }
@@ -20,6 +20,9 @@ export interface AcpFlightResultParams {
20
20
  readonly exitCode: number;
21
21
  readonly inputTokens?: number;
22
22
  readonly outputTokens?: number;
23
+ readonly cacheReadTokens?: number;
23
24
  readonly errorMessage?: string;
25
+ readonly providerSessionId?: string;
26
+ readonly stopReason?: string;
24
27
  }
25
28
  export declare function buildAcpFlightResult(params: AcpFlightResultParams): FlightLogResult;
@@ -35,6 +35,9 @@ export function buildAcpFlightResult(params) {
35
35
  optimizationApplied: false,
36
36
  inputTokens: params.inputTokens,
37
37
  outputTokens: params.outputTokens,
38
+ cacheReadTokens: params.cacheReadTokens,
38
39
  errorMessage: params.errorMessage !== undefined ? redactAcpTextForFlight(params.errorMessage) : undefined,
40
+ providerSessionId: params.providerSessionId,
41
+ stopReason: params.stopReason,
39
42
  };
40
43
  }
@@ -12,9 +12,15 @@ export function categorizeToolCall(toolCall) {
12
12
  return "read";
13
13
  return "other";
14
14
  }
15
- function isAllowOption(option) {
16
- const kind = typeof option.kind === "string" ? option.kind.toLowerCase() : "";
17
- return kind.startsWith("allow");
15
+ function selectSingleUseAllow(options) {
16
+ const kindOf = (o) => typeof o.kind === "string" ? o.kind.toLowerCase() : "";
17
+ const allowOnce = options.find(o => kindOf(o) === "allow_once");
18
+ if (allowOnce)
19
+ return allowOnce;
20
+ return options.find(o => {
21
+ const kind = kindOf(o);
22
+ return kind.startsWith("allow") && !kind.includes("always");
23
+ });
18
24
  }
19
25
  const CANCELLED = { outcome: { outcome: "cancelled" } };
20
26
  export function createAcpPermissionDecider(deps) {
@@ -75,12 +81,12 @@ export function createAcpPermissionDecider(deps) {
75
81
  });
76
82
  return CANCELLED;
77
83
  }
78
- const allow = request.options.find(isAllowOption);
84
+ const allow = selectSingleUseAllow(request.options);
79
85
  if (!allow) {
80
86
  logger.info("acp.permission.denied", {
81
87
  provider: deps.provider,
82
88
  category,
83
- reason: "no_allow_option",
89
+ reason: "no_single_use_allow_option",
84
90
  });
85
91
  return CANCELLED;
86
92
  }
@@ -36,6 +36,7 @@ export interface StartProviderOptions {
36
36
  readonly hostServices: HostServices;
37
37
  readonly callbacks?: AcpClientCallbacks;
38
38
  readonly idleTimeoutMs?: number;
39
+ readonly extraArgs?: readonly string[];
39
40
  }
40
41
  export interface ManagedAcpProcess {
41
42
  readonly provider: CliType;
@@ -51,7 +52,7 @@ export interface ManagedAcpProcess {
51
52
  isHealthy(): boolean;
52
53
  }
53
54
  export declare function buildProviderEnv(provider: CliType, providerConfig: AcpProviderConfig, baseEnv: ProcessEnv): ProcessEnv;
54
- export declare function resolveProviderSpawn(provider: CliType, config: AcpConfig, baseEnv: ProcessEnv, cwd?: string): ResolvedAcpSpawn;
55
+ export declare function resolveProviderSpawn(provider: CliType, config: AcpConfig, baseEnv: ProcessEnv, cwd?: string, extraArgs?: readonly string[]): ResolvedAcpSpawn;
55
56
  export declare const defaultSpawn: AcpSpawnFn;
56
57
  export declare class AcpProcessManager {
57
58
  private readonly config;
@@ -7,6 +7,7 @@ import { JsonRpcStdioTransport, } from "./json-rpc-stdio.js";
7
7
  import { getAcpProviderEntry } from "./provider-registry.js";
8
8
  import { envWithExtendedPath, getExtendedPath } from "../executor.js";
9
9
  import { noopLogger } from "../logger.js";
10
+ const SHUTDOWN_KILL_GRACE_MS = 5000;
10
11
  const SHELL_METACHARACTERS = /[\s|&;<>(){}$`"'\\*?[\]~#! ]/;
11
12
  function assertSafeExecutable(command, provider) {
12
13
  if (command.length === 0 || SHELL_METACHARACTERS.test(command)) {
@@ -22,7 +23,7 @@ export function buildProviderEnv(provider, providerConfig, baseEnv) {
22
23
  }
23
24
  return env;
24
25
  }
25
- export function resolveProviderSpawn(provider, config, baseEnv, cwd) {
26
+ export function resolveProviderSpawn(provider, config, baseEnv, cwd, extraArgs = []) {
26
27
  const providerConfig = config.providers[provider];
27
28
  const registryEntry = getAcpProviderEntry(provider);
28
29
  let command;
@@ -38,8 +39,9 @@ export function resolveProviderSpawn(provider, config, baseEnv, cwd) {
38
39
  else {
39
40
  throw new ProviderUnavailableError(provider, "no ACP entrypoint configured and provider has no native ACP entrypoint", { provider });
40
41
  }
42
+ const combinedArgs = [...args, ...extraArgs];
41
43
  assertSafeExecutable(command, provider);
42
- for (const arg of args) {
44
+ for (const arg of combinedArgs) {
43
45
  if (typeof arg !== "string") {
44
46
  throw new ProviderUnavailableError(provider, "ACP args must be strings", { provider });
45
47
  }
@@ -53,7 +55,7 @@ export function resolveProviderSpawn(provider, config, baseEnv, cwd) {
53
55
  };
54
56
  return {
55
57
  command,
56
- args: [...args],
58
+ args: [...combinedArgs],
57
59
  cwd: cwd ?? `${tmpdir()}/llm-gateway-acp-${provider}`,
58
60
  env: buildProviderEnv(provider, effectiveConfig, baseEnv),
59
61
  };
@@ -82,7 +84,7 @@ export class AcpProcessManager {
82
84
  this.baseEnv = options.baseEnv ?? process.env;
83
85
  }
84
86
  async start(options) {
85
- const resolved = resolveProviderSpawn(options.provider, this.config, this.baseEnv, options.cwd);
87
+ const resolved = resolveProviderSpawn(options.provider, this.config, this.baseEnv, options.cwd, options.extraArgs);
86
88
  let child;
87
89
  try {
88
90
  child = this.spawnFn(resolved);
@@ -119,6 +121,7 @@ export class AcpProcessManager {
119
121
  initializeTimeoutMs: this.config.initializeTimeoutMs,
120
122
  sessionNewTimeoutMs: this.config.sessionNewTimeoutMs,
121
123
  promptTimeoutMs: this.config.promptTimeoutMs,
124
+ allowMutatingSessionOps: this.config.allowMutatingSessionOps,
122
125
  onTerminal: m => this.live.delete(m),
123
126
  });
124
127
  this.live.add(managed);
@@ -162,6 +165,7 @@ class ManagedProcessImpl {
162
165
  _signal = null;
163
166
  _terminalError = null;
164
167
  idleTimer = null;
168
+ killTimer = null;
165
169
  constructor(options) {
166
170
  this.provider = options.provider;
167
171
  this.child = options.child;
@@ -199,6 +203,7 @@ class ManagedProcessImpl {
199
203
  sessionNewMs: options.sessionNewTimeoutMs,
200
204
  promptMs: options.promptTimeoutMs,
201
205
  },
206
+ allowMutatingSessionOps: options.allowMutatingSessionOps ?? false,
202
207
  });
203
208
  options.child.on("exit", (code, signal) => this.handleExit(code, signal));
204
209
  options.child.on("error", err => this.handleSpawnError(err));
@@ -259,7 +264,37 @@ class ManagedProcessImpl {
259
264
  this.idleTimer = null;
260
265
  }
261
266
  }
267
+ armKillTimer() {
268
+ if (this.killTimer || SHUTDOWN_KILL_GRACE_MS <= 0) {
269
+ return;
270
+ }
271
+ this.killTimer = setTimeout(() => {
272
+ this.killTimer = null;
273
+ try {
274
+ this.child.kill("SIGKILL");
275
+ this.logger.info("acp.process.force_killed", {
276
+ provider: this.provider,
277
+ pid: this.pid,
278
+ });
279
+ }
280
+ catch (err) {
281
+ this.logger.error("acp.process.kill_failed", {
282
+ provider: this.provider,
283
+ pid: this.pid,
284
+ errorClass: err instanceof Error ? err.name : "unknown",
285
+ });
286
+ }
287
+ }, SHUTDOWN_KILL_GRACE_MS);
288
+ this.killTimer.unref?.();
289
+ }
290
+ clearKillTimer() {
291
+ if (this.killTimer) {
292
+ clearTimeout(this.killTimer);
293
+ this.killTimer = null;
294
+ }
295
+ }
262
296
  handleExit(code, signal) {
297
+ this.clearKillTimer();
263
298
  if (this._state === "exited" || this._state === "quarantined") {
264
299
  this._exitCode = code;
265
300
  this._signal = signal;
@@ -322,6 +357,7 @@ class ManagedProcessImpl {
322
357
  this.client.notifyProcessExit(error);
323
358
  try {
324
359
  this.child.kill("SIGTERM");
360
+ this.armKillTimer();
325
361
  }
326
362
  catch (err) {
327
363
  this.logger.error("acp.process.kill_failed", {
@@ -348,6 +384,9 @@ class ManagedProcessImpl {
348
384
  if (wasRunning) {
349
385
  try {
350
386
  this.child.kill(signal);
387
+ if (signal !== "SIGKILL") {
388
+ this.armKillTimer();
389
+ }
351
390
  }
352
391
  catch (err) {
353
392
  this.logger.error("acp.process.kill_failed", {
@@ -1,11 +1,19 @@
1
+ import { getProviderDefinition } from "../provider-definitions.js";
2
+ function acpEntrypointFromDefinition(provider) {
3
+ const entry = getProviderDefinition(provider).acp.entrypoint;
4
+ if (!entry) {
5
+ return null;
6
+ }
7
+ return Object.freeze({ command: entry.command, args: Object.freeze([...entry.args]) });
8
+ }
1
9
  const ACP_PROVIDER_REGISTRY = Object.freeze({
2
10
  mistral: Object.freeze({
3
11
  provider: "mistral",
4
12
  displayName: "Mistral Vibe",
5
13
  status: "native_smoke_passed",
6
14
  supportKind: "native",
7
- targetVersion: "vibe 2.17.1",
8
- entrypoint: Object.freeze({ command: "vibe-acp", args: Object.freeze([]) }),
15
+ targetVersion: "vibe 2.18.3",
16
+ entrypoint: acpEntrypointFromDefinition("mistral"),
9
17
  runtimeEnabledDefault: false,
10
18
  shipRuntimePilot: true,
11
19
  runtimePriority: 1,
@@ -17,8 +25,8 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
17
25
  displayName: "xAI Grok CLI",
18
26
  status: "native_smoke_passed",
19
27
  supportKind: "native",
20
- targetVersion: "grok 0.2.73 (9ff14c43bb)",
21
- entrypoint: Object.freeze({ command: "grok", args: Object.freeze(["agent", "stdio"]) }),
28
+ targetVersion: "grok 0.2.77 (44e77bec3a)",
29
+ entrypoint: acpEntrypointFromDefinition("grok"),
22
30
  runtimeEnabledDefault: false,
23
31
  shipRuntimePilot: true,
24
32
  runtimePriority: 2,
@@ -31,7 +39,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
31
39
  status: "adapter_mediated_deferred",
32
40
  supportKind: "adapter_mediated",
33
41
  targetVersion: "codex-cli 0.142.4",
34
- entrypoint: null,
42
+ entrypoint: acpEntrypointFromDefinition("codex"),
35
43
  runtimeEnabledDefault: false,
36
44
  shipRuntimePilot: false,
37
45
  runtimePriority: 0,
@@ -43,8 +51,8 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
43
51
  displayName: "Anthropic Claude Code",
44
52
  status: "adapter_mediated_deferred",
45
53
  supportKind: "adapter_mediated",
46
- targetVersion: "claude 2.1.195",
47
- entrypoint: null,
54
+ targetVersion: "claude 2.1.198",
55
+ entrypoint: acpEntrypointFromDefinition("claude"),
48
56
  runtimeEnabledDefault: false,
49
57
  shipRuntimePilot: false,
50
58
  runtimePriority: 0,
@@ -56,8 +64,8 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
56
64
  displayName: "Google Antigravity",
57
65
  status: "absent_watchlist",
58
66
  supportKind: "none",
59
- targetVersion: "agy 1.0.13",
60
- entrypoint: null,
67
+ targetVersion: "agy 1.0.14",
68
+ entrypoint: acpEntrypointFromDefinition("gemini"),
61
69
  runtimeEnabledDefault: false,
62
70
  shipRuntimePilot: false,
63
71
  runtimePriority: 0,
@@ -70,7 +78,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
70
78
  status: "native_smoke_passed",
71
79
  supportKind: "native",
72
80
  targetVersion: "devin 2026.8.18 (16737566)",
73
- entrypoint: Object.freeze({ command: "devin", args: Object.freeze(["acp"]) }),
81
+ entrypoint: acpEntrypointFromDefinition("devin"),
74
82
  runtimeEnabledDefault: false,
75
83
  shipRuntimePilot: true,
76
84
  runtimePriority: 3,
@@ -83,7 +91,7 @@ const ACP_PROVIDER_REGISTRY = Object.freeze({
83
91
  status: "native_smoke_passed",
84
92
  supportKind: "native",
85
93
  targetVersion: "cursor-agent 2026.06.29-2ad2186",
86
- entrypoint: Object.freeze({ command: "cursor-agent", args: Object.freeze(["acp"]) }),
94
+ entrypoint: acpEntrypointFromDefinition("cursor"),
87
95
  runtimeEnabledDefault: false,
88
96
  shipRuntimePilot: true,
89
97
  runtimePriority: 4,
@@ -25,11 +25,19 @@ export interface AcpRunRequest {
25
25
  readonly sessionId?: string;
26
26
  readonly cwd?: string;
27
27
  readonly correlationId: string;
28
+ readonly agentType?: string;
28
29
  }
29
30
  export interface AcpRunResult {
30
31
  readonly text: string;
31
32
  readonly gatewaySessionId: string;
32
33
  readonly protocolVersion: number | null;
33
34
  readonly durationMs: number;
35
+ readonly stopReason: string | null;
34
36
  }
37
+ export interface AcpPromptUsage {
38
+ inputTokens?: number;
39
+ outputTokens?: number;
40
+ cacheReadTokens?: number;
41
+ }
42
+ export declare function extractAcpPromptUsage(meta: unknown): AcpPromptUsage;
35
43
  export declare function runAcpRequest(deps: AcpRuntimeDeps, req: AcpRunRequest): Promise<AcpRunResult>;
@@ -1,11 +1,29 @@
1
1
  import { AcpEventNormalizer } from "./event-normalizer.js";
2
- import { AcpDisabledError, AcpError, AcpProtocolError, ProviderRuntimeDisabledError, isAcpError, } from "./errors.js";
2
+ import { AcpDisabledError, AcpError, AcpProtocolError, ProviderAcpDisabledError, ProviderRuntimeDisabledError, isAcpError, } from "./errors.js";
3
3
  import { buildAcpFlightResult, buildAcpFlightStart } from "./flight-redaction.js";
4
4
  import { GatewayHostServices } from "./host-services.js";
5
5
  import { createAcpPermissionDecider } from "./permission-bridge.js";
6
6
  import { AcpProcessManager } from "./process-manager.js";
7
7
  import { createAcpSession, recordAcpSessionInfo, resolveAcpResume } from "./session-map.js";
8
+ import { DEVIN_ACP_AGENT_TYPES } from "../provider-definitions.js";
8
9
  import { noopLogger } from "../logger.js";
10
+ export function extractAcpPromptUsage(meta) {
11
+ if (!meta || typeof meta !== "object" || Array.isArray(meta))
12
+ return {};
13
+ const record = meta;
14
+ const num = (value) => typeof value === "number" && Number.isFinite(value) ? value : undefined;
15
+ const usage = {};
16
+ const inputTokens = num(record.inputTokens);
17
+ if (inputTokens !== undefined)
18
+ usage.inputTokens = inputTokens;
19
+ const outputTokens = num(record.outputTokens);
20
+ if (outputTokens !== undefined)
21
+ usage.outputTokens = outputTokens;
22
+ const cacheReadTokens = num(record.cachedReadTokens);
23
+ if (cacheReadTokens !== undefined)
24
+ usage.cacheReadTokens = cacheReadTokens;
25
+ return usage;
26
+ }
9
27
  export async function runAcpRequest(deps, req) {
10
28
  const logger = deps.logger ?? noopLogger;
11
29
  const now = deps.now ?? Date.now;
@@ -15,7 +33,10 @@ export async function runAcpRequest(deps, req) {
15
33
  throw new AcpDisabledError({ provider: req.provider });
16
34
  }
17
35
  const providerConfig = deps.config.providers[req.provider];
18
- if (!providerConfig?.runtimeEnabled) {
36
+ if (providerConfig?.enabled !== true) {
37
+ throw new ProviderAcpDisabledError(req.provider, { provider: req.provider });
38
+ }
39
+ if (providerConfig?.runtimeEnabled !== true) {
19
40
  throw new ProviderRuntimeDisabledError(req.provider, { provider: req.provider });
20
41
  }
21
42
  const permissionDecider = createAcpPermissionDecider({
@@ -60,6 +81,11 @@ export async function runAcpRequest(deps, req) {
60
81
  prompt: promptBlocks,
61
82
  gatewaySessionId,
62
83
  }));
84
+ const extraArgs = req.provider === "devin" &&
85
+ req.agentType !== undefined &&
86
+ DEVIN_ACP_AGENT_TYPES.includes(req.agentType)
87
+ ? ["--agent-type", req.agentType]
88
+ : [];
63
89
  let proc = null;
64
90
  try {
65
91
  proc = await manager.start({
@@ -67,6 +93,7 @@ export async function runAcpRequest(deps, req) {
67
93
  cwd: req.cwd,
68
94
  hostServices,
69
95
  callbacks: { onSessionUpdate: update => normalizer.handle(update) },
96
+ extraArgs,
70
97
  });
71
98
  const cwd = proc.resolved.cwd;
72
99
  const init = proc.client.agentInfo;
@@ -85,16 +112,32 @@ export async function runAcpRequest(deps, req) {
85
112
  agentVersion: init?.agentInfo?.version,
86
113
  });
87
114
  }
88
- await proc.client.prompt({ sessionId: providerSessionId, prompt: promptBlocks });
115
+ const promptResult = await proc.client.prompt({
116
+ sessionId: providerSessionId,
117
+ prompt: promptBlocks,
118
+ });
119
+ normalizer.completeWith(promptResult.stopReason);
89
120
  const text = normalizer.finalText;
90
121
  const durationMs = elapsed();
91
- deps.flightRecorder?.logComplete(req.correlationId, buildAcpFlightResult({ responseText: text, durationMs, status: "completed", exitCode: 0 }));
122
+ const usage = extractAcpPromptUsage(promptResult._meta);
123
+ deps.flightRecorder?.logComplete(req.correlationId, buildAcpFlightResult({
124
+ responseText: text,
125
+ durationMs,
126
+ status: "completed",
127
+ exitCode: 0,
128
+ providerSessionId,
129
+ stopReason: normalizer.stopReason,
130
+ inputTokens: usage.inputTokens,
131
+ outputTokens: usage.outputTokens,
132
+ cacheReadTokens: usage.cacheReadTokens,
133
+ }));
92
134
  logger.info("acp.request.success", { provider: req.provider, durationMs });
93
135
  return {
94
136
  text,
95
137
  gatewaySessionId,
96
138
  protocolVersion: init?.protocolVersion ?? null,
97
139
  durationMs,
140
+ stopReason: normalizer.stopReason ?? null,
98
141
  };
99
142
  }
100
143
  catch (err) {