@agentproto/driver-agent-cli 1.1.0 → 2.0.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.
@@ -184,8 +184,13 @@ var modelsSchema = z.object({
184
184
  env: z.record(z.string(), z.string()).optional(),
185
185
  // How a model is selected at session start: "config" (ACP
186
186
  // set_config_option, default) | "command" (a `/model <id>` control turn,
187
- // for agents like hermes that ignore the ACP session model config).
188
- apply: z.enum(["config", "command"]).optional()
187
+ // for agents like hermes that ignore the ACP session model config) |
188
+ // "arg" (a CLI argument composed into bin_args at spawn, for agents
189
+ // like codex-acp that take their model as `-c model="<id>"`).
190
+ apply: z.enum(["config", "command", "arg"]).optional(),
191
+ // Argv template for apply:"arg" — {model} interpolates the requested
192
+ // model id. See AgentCliModels.bin_args_template.
193
+ bin_args_template: z.array(z.string()).optional()
189
194
  }).strict();
190
195
  var capabilitiesSchema = z.object({
191
196
  streaming: z.boolean().optional(),
@@ -207,6 +212,14 @@ var capabilitiesSchema = z.object({
207
212
  var modeSchema = z.object({
208
213
  id: z.string().regex(MODE_ID_PATTERN),
209
214
  description: z.string().optional(),
215
+ // Which orthogonal session-config axis this mode expresses (SPEC-tracked in
216
+ // @agentproto/runtime's session-config.ts). Narrowed to only "context" (SPEC
217
+ // §3.4a): route is derived from the model catalog (@route), not a manifest
218
+ // mode, and posture comes from the harness's own ACP mode registry
219
+ // (SessionModeState.availableModes) — neither is a `modes[]` entry anymore.
220
+ // The legacy "posture"/"route" classification still exists, but only inside
221
+ // the daemon's back-compat `decomposeMode` shim, not this manifest surface.
222
+ kind: z.enum(["context"]).optional(),
210
223
  bin_args_prepend: z.array(z.string()).optional(),
211
224
  bin_args_append: z.array(z.string()).optional(),
212
225
  env: z.record(z.string(), z.string()).optional(),
@@ -433,6 +446,15 @@ function createAcpProtocolArm(options) {
433
446
  get sessionId() {
434
447
  return session?.sessionId;
435
448
  },
449
+ get availableConfigOptions() {
450
+ return session?.availableConfigOptions ?? [];
451
+ },
452
+ get availableModes() {
453
+ return session?.availableModes ?? [];
454
+ },
455
+ get currentModeId() {
456
+ return session?.currentModeId;
457
+ },
436
458
  async connect(opts) {
437
459
  const permissionHandler = options.onPermissionRequest ?? defaultPermissionHandlerForMode(options.requestedMode);
438
460
  client = await createAcpClient({
@@ -490,6 +512,14 @@ function createAcpProtocolArm(options) {
490
512
  if (!session) return;
491
513
  await session.cancel();
492
514
  },
515
+ async setConfigOption(configId, value) {
516
+ if (!session) return { applied: false, reason: "not-connected" };
517
+ return session.setConfigOption(configId, value);
518
+ },
519
+ async setSessionMode(modeId) {
520
+ if (!session) return { applied: false, reason: "not-connected" };
521
+ return session.setSessionMode(modeId);
522
+ },
493
523
  respondPermission(requestId, resolution) {
494
524
  return client?.respondPermission(requestId, resolution) ?? false;
495
525
  },
@@ -603,6 +633,33 @@ function createPrintSession(opts) {
603
633
  async cancel() {
604
634
  activeChild?.kill("SIGTERM");
605
635
  },
636
+ // The print arm spawns a fresh subprocess PER TURN from `baseArgs`,
637
+ // which already has the model baked in (composed once, before this
638
+ // session object was even constructed — see `bin_args_template`
639
+ // handling in define-agent-cli.ts/compose.ts). There is no live
640
+ // connection to apply a mid-session config change to, and rebuilding
641
+ // `baseArgs` would mean a fresh session, not a live switch — so this
642
+ // is honestly `requires-restart`, same as the `arg` model-apply
643
+ // strategy for ACP arms.
644
+ async setModel() {
645
+ return { applied: false, reason: "requires-restart" };
646
+ },
647
+ // No ACP session — no wrapper-advertised capability read-surface and
648
+ // no `session/set_mode` RPC to switch live. Mirrors the "arm has no
649
+ // mid-session config surface" case `define-agent-cli.ts` reports for
650
+ // `setModel` on other arms.
651
+ availableConfigOptions: [],
652
+ availableModes: [],
653
+ currentModeId: void 0,
654
+ async setSessionMode() {
655
+ return { applied: false, reason: "not-supported" };
656
+ },
657
+ // Same as `setSessionMode`: a print arm has no live ACP session to apply
658
+ // `set_config_option(configId:"effort")` against — effort is baked into
659
+ // the spawn argv/env, so a live change is honestly `not-supported`.
660
+ async setEffort() {
661
+ return { applied: false, reason: "not-supported" };
662
+ },
606
663
  async close() {
607
664
  activeChild?.kill("SIGTERM");
608
665
  mcpRestore?.();
@@ -762,8 +819,12 @@ function createMastraMapperState() {
762
819
  return { lastTextLength: 0 };
763
820
  }
764
821
  function extractTextFromBlocks(content) {
765
- if (!Array.isArray(content)) return "";
766
- return content.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("");
822
+ let blocks = content;
823
+ if (blocks !== null && typeof blocks === "object" && !Array.isArray(blocks) && Array.isArray(blocks.parts)) {
824
+ blocks = blocks.parts;
825
+ }
826
+ if (!Array.isArray(blocks)) return "";
827
+ return blocks.filter((c) => c.type === "text" && typeof c.text === "string").map((c) => c.text).join("");
767
828
  }
768
829
  function mapMastraEvent(evt, sessionId, stderrLines, state) {
769
830
  switch (evt.type) {
@@ -977,6 +1038,31 @@ async function createProprietaryProtocolArm(options) {
977
1038
  return await candidate(options.definition);
978
1039
  }
979
1040
 
1041
+ // src/legacy-modes.ts
1042
+ var LEGACY_GATEWAY_MODE_IDS = /* @__PURE__ */ new Set([
1043
+ "moonshot",
1044
+ "openrouter",
1045
+ "requesty",
1046
+ "deepseek"
1047
+ ]);
1048
+ var LEGACY_POSTURE_MODE_IDS = /* @__PURE__ */ new Set([
1049
+ "default",
1050
+ "plan",
1051
+ "accept-edits",
1052
+ "bypass-permissions",
1053
+ "read-only",
1054
+ "full-access",
1055
+ "build"
1056
+ ]);
1057
+ function inferLegacyModeKind(modeId) {
1058
+ if (LEGACY_GATEWAY_MODE_IDS.has(modeId)) return "route";
1059
+ if (LEGACY_POSTURE_MODE_IDS.has(modeId)) return "posture";
1060
+ return "context";
1061
+ }
1062
+ function isLegacyExtractedModeId(modeId) {
1063
+ return LEGACY_GATEWAY_MODE_IDS.has(modeId) || LEGACY_POSTURE_MODE_IDS.has(modeId);
1064
+ }
1065
+
980
1066
  // src/manifest/compose.ts
981
1067
  var RuntimeConfigError = class extends Error {
982
1068
  code;
@@ -1002,14 +1088,15 @@ function composeSpawn(handle, config) {
1002
1088
  if (config.mode !== void 0) {
1003
1089
  const mode = (handle.modes ?? []).find((m) => m.id === config.mode);
1004
1090
  if (!mode) {
1005
- const known = (handle.modes ?? []).map((m) => m.id).join(", ") || "(none)";
1006
- throw new RuntimeConfigError(
1007
- "unknown_mode",
1008
- "config.mode",
1009
- `Mode '${config.mode}' is not declared by manifest '${handle.id}'. Known modes: ${known}`
1010
- );
1011
- }
1012
- if ((mode.apply ?? "bin_args") === "bin_args") {
1091
+ if (!isLegacyExtractedModeId(config.mode)) {
1092
+ const known = (handle.modes ?? []).map((m) => m.id).join(", ") || "(none)";
1093
+ throw new RuntimeConfigError(
1094
+ "unknown_mode",
1095
+ "config.mode",
1096
+ `Mode '${config.mode}' is not declared by manifest '${handle.id}'. Known modes: ${known}`
1097
+ );
1098
+ }
1099
+ } else if ((mode.apply ?? "bin_args") === "bin_args") {
1013
1100
  if (mode.bin_args_prepend) prepend.push(...mode.bin_args_prepend);
1014
1101
  if (mode.bin_args_append) append.push(...mode.bin_args_append);
1015
1102
  if (mode.env) Object.assign(env, mode.env);
@@ -1053,6 +1140,13 @@ function composeSpawn(handle, config) {
1053
1140
  );
1054
1141
  }
1055
1142
  }
1143
+ if (typeof requestedModel === "string" && handle.models?.apply === "arg" && handle.models.bin_args_template) {
1144
+ append.push(
1145
+ ...handle.models.bin_args_template.map(
1146
+ (token) => token.replace("{model}", requestedModel)
1147
+ )
1148
+ );
1149
+ }
1056
1150
  if (config.continuation !== void 0 && handle.continuation) {
1057
1151
  if (!handle.continuation.supported.includes(config.continuation)) {
1058
1152
  throw new RuntimeConfigError(
@@ -1201,6 +1295,11 @@ var defineAgentCli = createDoctype(
1201
1295
  "defineAgentCli (AIP-45): session.mode=resumable requires capabilities.resumable: true"
1202
1296
  );
1203
1297
  }
1298
+ if (data.models?.apply === "arg" && !data.models.bin_args_template?.length) {
1299
+ throw new Error(
1300
+ 'defineAgentCli (AIP-45): models.apply="arg" requires models.bin_args_template'
1301
+ );
1302
+ }
1204
1303
  },
1205
1304
  build(def) {
1206
1305
  return { ...def };
@@ -1220,17 +1319,20 @@ function createAgentCliRuntime(definition) {
1220
1319
  for (const k of composed.envUnset) delete env[k];
1221
1320
  const authSpec = opts?.auth;
1222
1321
  const hasGatewayAuthToken = "ANTHROPIC_AUTH_TOKEN" in composed.env;
1223
- const engageAuth = !!authSpec && !hasGatewayAuthToken && (authSpec.enforce === "always" || authSpec.explicit === true);
1322
+ const hasGatewayBaseUrl = "ANTHROPIC_BASE_URL" in composed.env;
1323
+ const engageAuth = !!authSpec && !hasGatewayAuthToken && !hasGatewayBaseUrl && (authSpec.enforce === "always" || authSpec.explicit === true);
1224
1324
  if (authSpec && engageAuth) {
1225
1325
  for (const key of authSpec.unsetEnv) {
1226
1326
  if (!(key in composed.env)) delete env[key];
1227
1327
  }
1228
1328
  if (!authSpec.credential) {
1229
- throw new RuntimeConfigError(
1230
- "missing_auth_credential",
1231
- "opts.auth.credential",
1232
- `agent-cli '${definition.id}': auth mode "${authSpec.mode}" requires an explicit credential (resolved from a named config/store ref), but none was provided.` + (authSpec.mode === "subscription" ? ` Mint one via \`claude setup-token\` (bills the Max/Pro subscription, not API credits) and configure it \u2014 never inherited from the shell.` : ` Configure an API key (\`agentproto auth provider set \u2026\` or per-spawn) \u2014 never inherited from the shell.`)
1233
- );
1329
+ const slug = definition.id;
1330
+ const providerLabel = definition.provider ?? "<provider>";
1331
+ const configBlock = `{"defaults":{"adapters":{"${slug}":{"auth":{"mode":"api-key"}}}}} in ~/.agentproto/config.json`;
1332
+ const storeHint = authSpec.ignoredApiKeyInStore ? ` (providers.json already has a stored key for ${providerLabel} \u2014 ignored until that block exists)` : "";
1333
+ const supportsSub = !!definition.authSubscription;
1334
+ const message = authSpec.neitherConfigured && supportsSub ? `agent-cli '${slug}': no billing auth. Subscription \u2192 \`claude setup-token\`. Api-key \u2192 \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}. Add ${configBlock}. Never inherited from the shell.` : authSpec.mode === "subscription" ? `agent-cli '${slug}': run \`claude setup-token\` and configure the result \u2014 bills the Max/Pro subscription, not API credits. Never inherited from the shell.` : `agent-cli '${slug}': run \`agentproto auth provider set ${providerLabel} sk-\u2026\`${storeHint}, then add ${configBlock}. Never inherited from the shell.`;
1335
+ throw new RuntimeConfigError("missing_auth_credential", "opts.auth.credential", message);
1234
1336
  }
1235
1337
  env[authSpec.setEnv] = authSpec.credential;
1236
1338
  }
@@ -1318,21 +1420,109 @@ function createAgentCliRuntime(definition) {
1318
1420
  await applyModelCommand(arm, String(optModel));
1319
1421
  }
1320
1422
  const sessionId = arm.sessionId ?? randomUUID();
1423
+ let currentTurnId;
1321
1424
  return {
1322
1425
  sessionId,
1323
1426
  pid: child?.pid,
1427
+ // Capability read-surface (SPEC §3.9/§3.4a) — snapshotted once
1428
+ // `arm.connect()` above has resolved, so an ACP arm's captured
1429
+ // `newSession`/`loadSession` response is already populated. Arms
1430
+ // that don't model this (print, proprietary) leave the getters
1431
+ // undefined, defaulted here to the same empty/absent shape their
1432
+ // `setModel`-style counterparts use for "not supported".
1433
+ availableConfigOptions: arm.availableConfigOptions ?? [],
1434
+ availableModes: arm.availableModes ?? [],
1435
+ currentModeId: arm.currentModeId,
1324
1436
  send(message) {
1325
1437
  const turnId = randomUUID();
1438
+ currentTurnId = turnId;
1326
1439
  return promptTurn(arm, turnId, message);
1327
1440
  },
1441
+ /**
1442
+ * Cancel the in-flight turn — the wire equivalent of Ctrl-C at the
1443
+ * agent's own TUI, and the primitive the host's interrupt path
1444
+ * (`SessionsRegistry.interruptSession`, `{interrupt: true}`) is built
1445
+ * on.
1446
+ *
1447
+ * This used to be `abortController.abort()` alone, which cancelled
1448
+ * NOTHING: that controller is the CONNECTION-level signal, handed to
1449
+ * `arm.connect({abortSignal})` at spawn, and no arm reads it. So the
1450
+ * abort fired into the void while the agent kept working — a turn
1451
+ * "cancelled" this way ran happily to completion, and the host's 60s
1452
+ * settle-wait then timed out on a turn that was never asked to stop.
1453
+ * It only ever LOOKED like it worked for turns that happened to end
1454
+ * naturally inside that wait.
1455
+ *
1456
+ * Both arms have always implemented the real thing — the ACP arm
1457
+ * sends `session/cancel`, the print arm SIGTERMs the child — it was
1458
+ * simply never called. Call it.
1459
+ *
1460
+ * Connection-level abort deliberately stays out of this: aborting the
1461
+ * connection would end the SESSION, and cancelling a turn must leave
1462
+ * it alive and idle for the next prompt. That distinction is the whole
1463
+ * point of interrupt-vs-kill.
1464
+ */
1328
1465
  async cancel() {
1329
- abortController.abort();
1466
+ if (currentTurnId === void 0) return;
1467
+ await arm.cancel(currentTurnId);
1330
1468
  },
1331
1469
  ...arm.respondPermission ? {
1332
1470
  respondPermission(requestId, resolution) {
1333
1471
  return arm.respondPermission(requestId, resolution);
1334
1472
  }
1335
1473
  } : {},
1474
+ /**
1475
+ * Mid-session model switch — the runtime counterpart to the
1476
+ * spawn-time `modelApply` handling above. Dispatches on the same
1477
+ * `definition.models.apply` strategy so a live switch behaves
1478
+ * exactly like the spawn-time apply would have, just against an
1479
+ * already-running session. See {@link SetModelResult} for the
1480
+ * reason vocabulary; this never throws and never tears down the
1481
+ * session on a rejected switch.
1482
+ */
1483
+ async setModel(modelId) {
1484
+ if (modelApply === "arg") {
1485
+ return { applied: false, reason: "requires-restart" };
1486
+ }
1487
+ if (modelApply === "command") {
1488
+ return applyModelCommand(arm, modelId);
1489
+ }
1490
+ if (!arm.setConfigOption) {
1491
+ return { applied: false, reason: "not-supported" };
1492
+ }
1493
+ const result = await arm.setConfigOption("model", modelId);
1494
+ return result.applied ? { applied: true, model: modelId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1495
+ },
1496
+ /**
1497
+ * Mid-session posture switch — the native-mode counterpart to
1498
+ * `setModel`, wired directly to the arm's `setSessionMode` (only
1499
+ * the ACP arm implements it). See {@link SetSessionModeResult} for
1500
+ * the reason vocabulary; this never throws and never tears down
1501
+ * the session on a rejected switch.
1502
+ */
1503
+ async setSessionMode(modeId) {
1504
+ if (!arm.setSessionMode) {
1505
+ return { applied: false, reason: "not-supported" };
1506
+ }
1507
+ const result = await arm.setSessionMode(modeId);
1508
+ return result.applied ? { applied: true, modeId } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1509
+ },
1510
+ /**
1511
+ * Mid-session effort switch — the effort-axis counterpart to
1512
+ * `setModel`'s `"config"` strategy, wired to the same
1513
+ * `arm.setConfigOption` surface with `configId:"effort"` (only the
1514
+ * ACP arm implements it). Best-effort and non-fatal: an effort label
1515
+ * the current model rejects resolves `{applied:false, reason}` (SPEC
1516
+ * risk R7), never thrown and never tearing down the session. See
1517
+ * {@link SetEffortResult} for the reason vocabulary.
1518
+ */
1519
+ async setEffort(effort) {
1520
+ if (!arm.setConfigOption) {
1521
+ return { applied: false, reason: "not-supported" };
1522
+ }
1523
+ const result = await arm.setConfigOption("effort", effort);
1524
+ return result.applied ? { applied: true, effort } : { applied: false, ...result.reason ? { reason: result.reason } : {} };
1525
+ },
1336
1526
  async close() {
1337
1527
  await arm.close();
1338
1528
  if (child && !child.killed) child.kill("SIGTERM");
@@ -1352,17 +1542,19 @@ async function applyModelCommand(arm, modelId) {
1352
1542
  if (/switch|model\s+set|now using/i.test(JSON.stringify(evt))) acked = true;
1353
1543
  }
1354
1544
  } catch (err) {
1545
+ const reason = err instanceof Error ? err.message : String(err);
1355
1546
  console.warn(
1356
1547
  `[agent-cli] /model ${modelId} control turn failed (continuing on default):`,
1357
1548
  err instanceof Error ? err.message : err
1358
1549
  );
1359
- return;
1550
+ return { applied: false, reason };
1360
1551
  }
1361
1552
  if (!acked) {
1362
- console.warn(
1363
- `[agent-cli] /model ${modelId}: no switch acknowledgement \u2014 agent may be on its default model`
1364
- );
1553
+ const reason = "no switch acknowledgement \u2014 agent may be on its default model";
1554
+ console.warn(`[agent-cli] /model ${modelId}: ${reason}`);
1555
+ return { applied: false, reason };
1365
1556
  }
1557
+ return { applied: true, model: modelId };
1366
1558
  }
1367
1559
  async function* promptTurn(arm, turnId, message) {
1368
1560
  await arm.send(turnId, message);
@@ -1420,6 +1612,6 @@ function filterStringEnv(env) {
1420
1612
  return out;
1421
1613
  }
1422
1614
 
1423
- export { RuntimeConfigError, agentCliFrontmatterSchema, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, mapMastraEvent, planModePermissionHandler, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
1424
- //# sourceMappingURL=chunk-2E3ATB4P.mjs.map
1425
- //# sourceMappingURL=chunk-2E3ATB4P.mjs.map
1615
+ export { LEGACY_GATEWAY_MODE_IDS, LEGACY_POSTURE_MODE_IDS, RuntimeConfigError, agentCliFrontmatterSchema, autoAllowPermissionHandler, composeSpawn, createAcpProtocolArm, createAgentCliRuntime, createMastraMapperState, createPrintSession, createProprietaryProtocolArm, defineAgentCli, inferLegacyModeKind, isLegacyExtractedModeId, mapMastraEvent, planModePermissionHandler, resolveContinuationStrategy, runtimeConfigSchema, toFileBasedMcpServers };
1616
+ //# sourceMappingURL=chunk-LP542BMX.mjs.map
1617
+ //# sourceMappingURL=chunk-LP542BMX.mjs.map