@agentproto/driver-agent-cli 2.3.0 → 2.4.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.
@@ -4,7 +4,7 @@ import { createAcpClient } from '@agentproto/acp/client';
4
4
  import { spawn } from 'child_process';
5
5
  import { existsSync, readFileSync, mkdirSync, writeFileSync, rmSync, rmdirSync, mkdtempSync } from 'fs';
6
6
  import { createInterface } from 'readline';
7
- import { join } from 'path';
7
+ import { join, dirname, delimiter } from 'path';
8
8
  import { tmpdir, homedir } from 'os';
9
9
  import { loadAdapterSpawnSandboxConfig, resolveCommandSandbox } from '@agentproto/command-sandbox';
10
10
  import { randomUUID } from 'crypto';
@@ -151,11 +151,14 @@ var authSchema = z.object({
151
151
  grace_s: z.number().int().nonnegative().optional()
152
152
  }).strict().optional()
153
153
  }).strict();
154
- var authSubscriptionSchema = z.object({
154
+ var authSubscriptionEntrySchema = z.object({
155
155
  setEnv: z.string().min(1).optional(),
156
156
  external: z.boolean().optional(),
157
157
  conflictEnv: z.array(z.string()).optional(),
158
- unsetEnvAdd: z.array(z.string()).optional()
158
+ unsetEnvAdd: z.array(z.string()).optional(),
159
+ // Provider scope for a multi-provider adapter's bearer surface (pi's
160
+ // ANTHROPIC_OAUTH_TOKEN is anthropic-only) — see AgentCliAuthSubscription.
161
+ provider: z.string().min(1).optional()
159
162
  }).strict().superRefine((v, ctx) => {
160
163
  if (v.external) {
161
164
  if (v.setEnv !== void 0) {
@@ -173,6 +176,20 @@ var authSubscriptionSchema = z.object({
173
176
  });
174
177
  }
175
178
  });
179
+ var authSubscriptionSchema = z.union([authSubscriptionEntrySchema, z.array(authSubscriptionEntrySchema).min(1)]).superRefine((v, ctx) => {
180
+ if (!Array.isArray(v)) return;
181
+ const seen = /* @__PURE__ */ new Set();
182
+ for (const entry of v) {
183
+ const key = entry.provider ?? "\0unscoped";
184
+ if (seen.has(key)) {
185
+ ctx.addIssue({
186
+ code: z.ZodIssueCode.custom,
187
+ message: entry.provider ? `authSubscription: duplicate provider scope "${entry.provider}" \u2014 two entries claim the same provider, which is ambiguous at spawn time.` : "authSubscription: more than one unscoped entry (no `provider`) \u2014 ambiguous which one applies at spawn time."
188
+ });
189
+ }
190
+ seen.add(key);
191
+ }
192
+ });
176
193
  var sessionSchema = z.object({
177
194
  mode: z.enum(["ephemeral", "persistent", "resumable"]).default("ephemeral"),
178
195
  idle_timeout_ms: z.number().int().min(1e3).default(6e5),
@@ -493,6 +510,9 @@ function createAcpProtocolArm(options) {
493
510
  get availableConfigOptions() {
494
511
  return session?.availableConfigOptions ?? [];
495
512
  },
513
+ get modelApplyRejection() {
514
+ return session?.modelApplyRejection;
515
+ },
496
516
  get availableModes() {
497
517
  return session?.availableModes ?? [];
498
518
  },
@@ -646,6 +666,14 @@ async function wrapAgentCliSpawn(bin, args, opts) {
646
666
  }
647
667
 
648
668
  // src/protocol/print-arm.ts
669
+ function printModelMismatch(requested, reported) {
670
+ const basename = (id) => {
671
+ const noSuffix = id.split("@")[0] ?? id;
672
+ const segs = noSuffix.split("/");
673
+ return (segs[segs.length - 1] ?? noSuffix).trim().toLowerCase();
674
+ };
675
+ return basename(requested) !== basename(reported);
676
+ }
649
677
  var DEFAULT_OUTPUT = ["--output-format", "stream-json"];
650
678
  var DEFAULT_PRE_PROMPT = ["--no-interactive"];
651
679
  var DEFAULT_RESUME = { flag: "--resume", kind: "value" };
@@ -725,14 +753,29 @@ function createPrintSession(opts) {
725
753
  const jcodeState = eventSchema === "jcode-ndjson" ? createJcodeMapperState() : void 0;
726
754
  const rl = createInterface({ input: child.stdout, crlfDelay: Infinity });
727
755
  const queue = new EventQueue();
756
+ let modelMismatchAborted = false;
728
757
  rl.on("line", (line) => {
729
758
  if (!line.trim()) return;
759
+ if (modelMismatchAborted) return;
730
760
  let evt;
731
761
  try {
732
762
  evt = JSON.parse(line);
733
763
  } catch {
734
764
  return;
735
765
  }
766
+ if (opts.expectedModel && eventSchema === "jcode-ndjson" && evt.type === "start" && typeof evt.model === "string" && printModelMismatch(opts.expectedModel, evt.model)) {
767
+ modelMismatchAborted = true;
768
+ const provider = typeof evt.provider === "string" ? ` (provider ${evt.provider})` : "";
769
+ queue.push({
770
+ kind: "error",
771
+ error: {
772
+ message: `model mismatch: requested "${opts.expectedModel}" but the agent started on "${evt.model}"${provider} \u2014 its CLI silently fell back to a default it resolved itself. This adapter routes AND bills by the model id, so the turn was aborted instead of silently running (and billing) a model you didn't ask for. Check the id against the adapter's model menu.`
773
+ }
774
+ });
775
+ queue.end();
776
+ child.kill("SIGTERM");
777
+ return;
778
+ }
736
779
  const csid = captureSessionId(evt, eventSchema);
737
780
  if (csid) capturedSessionId = csid;
738
781
  const sid = capturedSessionId || sessionId || "";
@@ -1626,6 +1669,13 @@ function createArmSessionControls(arm, definition) {
1626
1669
  }
1627
1670
  };
1628
1671
  }
1672
+ var MODEL_SWITCH_ACK_RE = /switch|model\s+set|now using/i;
1673
+ function isModelSwitchAcknowledgement(evt) {
1674
+ return MODEL_SWITCH_ACK_RE.test(JSON.stringify(evt));
1675
+ }
1676
+ function parseModelSwitchCommand(text) {
1677
+ return /^\s*\/model\s+(\S+)/i.exec(text)?.[1];
1678
+ }
1629
1679
  async function applyModelCommand(arm, modelId) {
1630
1680
  const turnId = randomUUID();
1631
1681
  let acked = false;
@@ -1634,7 +1684,7 @@ async function applyModelCommand(arm, modelId) {
1634
1684
  type: "text",
1635
1685
  text: `/model ${modelId}`
1636
1686
  })) {
1637
- if (/switch|model\s+set|now using/i.test(JSON.stringify(evt))) acked = true;
1687
+ if (isModelSwitchAcknowledgement(evt)) acked = true;
1638
1688
  }
1639
1689
  } catch (err) {
1640
1690
  const reason = err instanceof Error ? err.message : String(err);
@@ -1709,8 +1759,24 @@ var defineAgentCli = createDoctype(
1709
1759
  }
1710
1760
  }
1711
1761
  );
1712
- function resolveSpawnBin(bin) {
1713
- return bin === "node" ? process.execPath : bin;
1762
+ function resolveSpawnBin(bin, deps) {
1763
+ const execPath = process.execPath;
1764
+ if (bin === "node") return execPath;
1765
+ if (bin === "npx" || bin === "npm") {
1766
+ const exists = existsSync;
1767
+ const sibling = join(
1768
+ dirname(execPath),
1769
+ process.platform === "win32" ? `${bin}.cmd` : bin
1770
+ );
1771
+ if (exists(sibling)) return sibling;
1772
+ }
1773
+ return bin;
1774
+ }
1775
+ function ensureExecDirOnPath(env, execPath = process.execPath) {
1776
+ const execDir = dirname(execPath);
1777
+ const parts = (env.PATH ?? "").split(delimiter).filter(Boolean);
1778
+ if (parts.includes(execDir)) return;
1779
+ env.PATH = [...parts, execDir].join(delimiter);
1714
1780
  }
1715
1781
  function createAgentCliRuntime(definition) {
1716
1782
  return {
@@ -1751,6 +1817,7 @@ function createAgentCliRuntime(definition) {
1751
1817
  }
1752
1818
  }
1753
1819
  Object.assign(env, opts?.env ?? {});
1820
+ ensureExecDirOnPath(env);
1754
1821
  const permissionMode = resolveClaudeCodePermissionMode(
1755
1822
  definition,
1756
1823
  opts?.posture,
@@ -1793,6 +1860,7 @@ function createAgentCliRuntime(definition) {
1793
1860
  env.CLAUDE_CONFIG_DIR = claudeConfigDir;
1794
1861
  }
1795
1862
  if (definition.protocol === "print") {
1863
+ const printModel = config?.options?.model;
1796
1864
  return createPrintSession({
1797
1865
  bin: resolvedBin,
1798
1866
  baseArgs: composed.binArgs,
@@ -1802,7 +1870,8 @@ function createAgentCliRuntime(definition) {
1802
1870
  ...opts?.mcpServers ? { mcpServers: opts.mcpServers } : {},
1803
1871
  printConfig: definition.print,
1804
1872
  commandSandbox: opts?.commandSandbox,
1805
- ...claudeConfigDir ? { extraWritePaths: [claudeConfigDir] } : {}
1873
+ ...claudeConfigDir ? { extraWritePaths: [claudeConfigDir] } : {},
1874
+ ...definition.routeSelection === "derived-from-model" && printModel ? { expectedModel: String(printModel) } : {}
1806
1875
  });
1807
1876
  }
1808
1877
  let child;
@@ -1835,8 +1904,10 @@ function createAgentCliRuntime(definition) {
1835
1904
  });
1836
1905
  if (spawnFailure) {
1837
1906
  const isEnoent = spawnFailure.code === "ENOENT";
1838
- const enoentHint = isEnoent ? `
1839
- '${execBin}' was not found on the daemon's PATH. If it works in your own terminal, the daemon's environment may be stale \u2014 try \`agentproto daemon restart\`.` : "";
1907
+ const cwdMissing = isEnoent && !existsSync(cwd);
1908
+ const enoentHint = !isEnoent ? "" : cwdMissing ? `
1909
+ cwd '${cwd}' does not exist \u2014 Node reports a missing working directory with this same ENOENT. Recreate the directory, or pass a valid cwd/workspace (is the workspace path stale, or its worktree removed?).` : `
1910
+ '${execBin}' was not found on the daemon's PATH. If it works in your own terminal, the daemon's environment may be stale \u2014 restart the daemon from a shell where \`which ${definition.bin}\` resolves (\`agentproto daemon restart\`).`;
1840
1911
  throw new Error(
1841
1912
  `agent-cli '${definition.id}': failed to spawn '${execBin} ${execArgs.join(" ")}': ${spawnFailure.message}${enoentHint}`
1842
1913
  );
@@ -1889,8 +1960,25 @@ function createAgentCliRuntime(definition) {
1889
1960
  ...turnIdleTimeoutMs !== void 0 ? { turnIdleTimeoutMs } : {},
1890
1961
  ...opts?.permissionHold ? { permissionHold: true } : {}
1891
1962
  });
1963
+ if (configModel && definition.routeSelection === "derived-from-model" && arm.modelApplyRejection) {
1964
+ const { requested, reason } = arm.modelApplyRejection;
1965
+ await arm.close().catch(() => {
1966
+ });
1967
+ if (child && !child.killed) child.kill("SIGTERM");
1968
+ throw derivedModelRefusalError(definition, requested, reason);
1969
+ }
1892
1970
  if (optModel && modelApply === "command") {
1893
- await applyModelCommand(arm, String(optModel));
1971
+ const commandResult = await applyModelCommand(arm, String(optModel));
1972
+ if (!commandResult.applied && definition.routeSelection === "derived-from-model") {
1973
+ await arm.close().catch(() => {
1974
+ });
1975
+ if (child && !child.killed) child.kill("SIGTERM");
1976
+ throw derivedModelRefusalError(
1977
+ definition,
1978
+ String(optModel),
1979
+ commandResult.reason ?? "model switch not acknowledged"
1980
+ );
1981
+ }
1894
1982
  }
1895
1983
  const sessionId = arm.sessionId ?? randomUUID();
1896
1984
  let currentTurnId;
@@ -1999,7 +2087,14 @@ function filterStringEnv(env) {
1999
2087
  }
2000
2088
  return out;
2001
2089
  }
2090
+ function derivedModelRefusalError(definition, requested, reason) {
2091
+ const first = definition.models?.allowed?.[0];
2092
+ const example = (typeof first === "string" ? first : first?.id) ?? "openrouter/anthropic/claude-sonnet-4-6";
2093
+ return new Error(
2094
+ `${definition.bin}: refusing to start on the default model \u2014 the requested model "${requested}" was not applied (${reason}). This adapter routes AND bills by the model id's own provider prefix, so continuing would silently run (and bill) a model you didn't ask for. Use the adapter's "<provider>/<model>" form (e.g. "${example}"), or pick an id from its model menu.`
2095
+ );
2096
+ }
2002
2097
 
2003
- export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, applyModelCommand, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createArmSessionControls, createJcodeMapperState, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, inferLegacyModeKind, isLegacyExtractedModeId, mapJcodeEvent, mapMastraEvent, planModePermissionHandler, promptTurn, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
2004
- //# sourceMappingURL=chunk-774L5IO5.mjs.map
2005
- //# sourceMappingURL=chunk-774L5IO5.mjs.map
2098
+ export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, applyModelCommand, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createArmSessionControls, createJcodeMapperState, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, inferLegacyModeKind, isLegacyExtractedModeId, isModelSwitchAcknowledgement, mapJcodeEvent, mapMastraEvent, parseModelSwitchCommand, planModePermissionHandler, promptTurn, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
2099
+ //# sourceMappingURL=chunk-DBU5VORQ.mjs.map
2100
+ //# sourceMappingURL=chunk-DBU5VORQ.mjs.map