@pwrdrvr/agent-acp 0.15.0 → 0.16.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.
package/dist/index.d.ts CHANGED
@@ -14,7 +14,16 @@ interface AcpJsonRpcTransport {
14
14
  * drives. Injectable (via `createConnection`) so the adapter can be unit-tested
15
15
  * without spawning a real agent. The library's `ClientSideConnection` satisfies
16
16
  * this structurally (its methods take typed schema params; we pass records that
17
- * match at runtime). */
17
+ * match at runtime).
18
+ *
19
+ * Every entry here MUST exist on the real `ClientSideConnection`. It is
20
+ * attached with an `as unknown as` cast (see `createConnection`), so a name
21
+ * that is merely plausible type-checks and then throws
22
+ * "conn.<name> is not a function" at runtime, on the wire, against a user's
23
+ * agent. `setSessionModel` was such an entry: ACP 1.3 has no `session/set_model`
24
+ * at all, so every model selection died before it was sent. Anything the SDK
25
+ * does not expose as a typed method belongs in `dispatch`'s default arm, which
26
+ * sends it verbatim through `request()`. */
18
27
  interface AcpAgentConnection {
19
28
  initialize(params: Record<string, unknown>): Promise<unknown>;
20
29
  newSession(params: Record<string, unknown>): Promise<unknown>;
@@ -22,7 +31,6 @@ interface AcpAgentConnection {
22
31
  prompt(params: Record<string, unknown>): Promise<unknown>;
23
32
  cancel(params: Record<string, unknown>): Promise<void>;
24
33
  setSessionMode(params: Record<string, unknown>): Promise<unknown>;
25
- setSessionModel(params: Record<string, unknown>): Promise<unknown>;
26
34
  authenticate(params: Record<string, unknown>): Promise<unknown>;
27
35
  request(method: string, params: Record<string, unknown>): Promise<unknown>;
28
36
  notify(method: string, params: Record<string, unknown>): Promise<void>;
@@ -301,7 +309,11 @@ type AcpPromptContentBlock = {
301
309
  mimeType: string;
302
310
  data: string;
303
311
  };
304
- type AcpRuntimeOptionSource = "mode" | "model" | "configOption";
312
+ type AcpRuntimeOptionSource = "mode" | "model" | "configOption"
313
+ /** A model write that travels as `session/set_config_option` because the
314
+ * agent exposes model choice as a config option rather than via the legacy
315
+ * top-level `models` capability. See `setModel`. */
316
+ | "modelConfigOption";
305
317
  type AcpAgentClientOptions = {
306
318
  /** The transport (real stdio or a fake). */
307
319
  transport: AcpJsonRpcTransport;
@@ -389,8 +401,9 @@ declare class AcpAgentClient implements AgentBackend {
389
401
  * Public `AgentBackend.startThread`: accepts NEUTRAL `AgentStartThreadOptions`
390
402
  * and maps them onto an ACP `session/new`. ACP supports:
391
403
  * • `cwd` → `session/new.cwd`.
392
- * • `model` → applied via `session/set_model` after the session opens, when
393
- * the agent advertises model selection (best-effort; debug-logged if not).
404
+ * • `model` → applied after the session opens, by whichever route the agent
405
+ * advertises: the `model` config option, or legacy `session/set_model`
406
+ * (best-effort; debug-logged if not). See `setModel`.
394
407
  * • `instructions` is NOT injected here — ACP `session/new` has no base-
395
408
  * instructions slot, matching the adapter's existing behavior. A host that
396
409
  * wants system framing sends it as leading turn text.
@@ -480,6 +493,25 @@ declare class AcpAgentClient implements AgentBackend {
480
493
  * releasing here never blocks resume. */
481
494
  archiveThread(threadId: string): Promise<void>;
482
495
  setMode(threadId: string, modeId: string): Promise<void>;
496
+ /** Apply a model selection by whichever route the agent actually advertises.
497
+ *
498
+ * ACP 1.3 has NO `session/set_model` — it is absent from `AGENT_METHODS`.
499
+ * An agent that offers model choice does it through a config option with
500
+ * `category: "model"` (Kimi's `session/new` returns exactly that, and nothing
501
+ * under `models`). Some agents still accept `session/set_model` as a vendor
502
+ * extension, and the `models.availableModels` shape is still honoured for
503
+ * them, but it is not what the protocol defines any more.
504
+ *
505
+ * Two reasons to prefer the config option when the agent offers it. The READ
506
+ * path already draws this distinction — `modelIdFromCapabilities` and
507
+ * `modelsFromCapabilities` both fall back to the config option — and the two
508
+ * disagreeing lets a host list models it cannot then select. And the config
509
+ * option's REPLY carries the agent's full refreshed config set, where
510
+ * `session/set_model` answers `{}` and refreshes only via an async
511
+ * notification. On Kimi the model DETERMINES which thought levels exist —
512
+ * `K2.7 Coding` offers only `on`, `K3` offers `low`/`high`/`max` — and
513
+ * `applyReasoning` picks from `runtimeCapabilities` on the following
514
+ * `startTurn`, so the refresh this write leaves behind is what it reads. */
483
515
  setModel(threadId: string, modelId: string): Promise<void>;
484
516
  setConfigOption(threadId: string, optionId: string, value: string): Promise<void>;
485
517
  close(): Promise<void>;
package/dist/index.js CHANGED
@@ -144,8 +144,9 @@ var AcpConnection = class {
144
144
  return await conn.prompt(params);
145
145
  case "session/set_mode":
146
146
  return await conn.setSessionMode(params);
147
- case "session/set_model":
148
- return await conn.setSessionModel(params);
147
+ // `session/set_model` is deliberately NOT cased here: SDK 1.3 has no
148
+ // typed `setSessionModel`, so it goes out through the default arm's
149
+ // generic `request()` for agents still speaking the older method.
149
150
  case "session/cancel":
150
151
  await conn.cancel(params);
151
152
  return {};
@@ -1061,8 +1062,9 @@ var AcpAgentClient = class {
1061
1062
  * Public `AgentBackend.startThread`: accepts NEUTRAL `AgentStartThreadOptions`
1062
1063
  * and maps them onto an ACP `session/new`. ACP supports:
1063
1064
  * • `cwd` → `session/new.cwd`.
1064
- * • `model` → applied via `session/set_model` after the session opens, when
1065
- * the agent advertises model selection (best-effort; debug-logged if not).
1065
+ * • `model` → applied after the session opens, by whichever route the agent
1066
+ * advertises: the `model` config option, or legacy `session/set_model`
1067
+ * (best-effort; debug-logged if not). See `setModel`.
1066
1068
  * • `instructions` is NOT injected here — ACP `session/new` has no base-
1067
1069
  * instructions slot, matching the adapter's existing behavior. A host that
1068
1070
  * wants system framing sends it as leading turn text.
@@ -1440,7 +1442,32 @@ var AcpAgentClient = class {
1440
1442
  async setMode(threadId, modeId) {
1441
1443
  await this.setRuntimeOption(threadId, "mode", modeId, modeId);
1442
1444
  }
1445
+ /** Apply a model selection by whichever route the agent actually advertises.
1446
+ *
1447
+ * ACP 1.3 has NO `session/set_model` — it is absent from `AGENT_METHODS`.
1448
+ * An agent that offers model choice does it through a config option with
1449
+ * `category: "model"` (Kimi's `session/new` returns exactly that, and nothing
1450
+ * under `models`). Some agents still accept `session/set_model` as a vendor
1451
+ * extension, and the `models.availableModels` shape is still honoured for
1452
+ * them, but it is not what the protocol defines any more.
1453
+ *
1454
+ * Two reasons to prefer the config option when the agent offers it. The READ
1455
+ * path already draws this distinction — `modelIdFromCapabilities` and
1456
+ * `modelsFromCapabilities` both fall back to the config option — and the two
1457
+ * disagreeing lets a host list models it cannot then select. And the config
1458
+ * option's REPLY carries the agent's full refreshed config set, where
1459
+ * `session/set_model` answers `{}` and refreshes only via an async
1460
+ * notification. On Kimi the model DETERMINES which thought levels exist —
1461
+ * `K2.7 Coding` offers only `on`, `K3` offers `low`/`high`/`max` — and
1462
+ * `applyReasoning` picks from `runtimeCapabilities` on the following
1463
+ * `startTurn`, so the refresh this write leaves behind is what it reads. */
1443
1464
  async setModel(threadId, modelId) {
1465
+ const advertisesModelsCapability = (this.runtimeCapabilities?.models?.availableModels?.length ?? 0) > 0;
1466
+ const option = modelConfigOption(this.runtimeCapabilities);
1467
+ if (!advertisesModelsCapability && option !== void 0) {
1468
+ await this.setRuntimeOption(threadId, "modelConfigOption", option.id, modelId);
1469
+ return;
1470
+ }
1444
1471
  await this.setRuntimeOption(threadId, "model", modelId, modelId);
1445
1472
  }
1446
1473
  async setConfigOption(threadId, optionId, value) {
@@ -1597,7 +1624,16 @@ var AcpAgentClient = class {
1597
1624
  value
1598
1625
  );
1599
1626
  const runtimeCapabilities = this.captureRuntimeCapabilities("session-load", result);
1600
- const requested = source === "configOption" ? { configValues: { [optionId]: value }, updatedAt: this.now() } : source === "mode" ? { currentModeId: value, updatedAt: this.now() } : { currentModelId: value, updatedAt: this.now() };
1627
+ const requested = source === "configOption" ? { configValues: { [optionId]: value }, updatedAt: this.now() } : source === "mode" ? { currentModeId: value, updatedAt: this.now() } : source === "modelConfigOption" ? (
1628
+ // Record BOTH: it IS the config option's value, and it IS the
1629
+ // session's model. A consumer reading `currentModelId` must not
1630
+ // have to know which route the agent happened to expose.
1631
+ {
1632
+ currentModelId: value,
1633
+ configValues: { [optionId]: value },
1634
+ updatedAt: this.now()
1635
+ }
1636
+ ) : { currentModelId: value, updatedAt: this.now() };
1601
1637
  session.runtimeState = mergeAcpRuntimeState(session.runtimeState, requested);
1602
1638
  const effectiveCapabilities = runtimeCapabilities ?? this.runtimeCapabilities;
1603
1639
  if (effectiveCapabilities) {
@@ -1610,7 +1646,7 @@ var AcpAgentClient = class {
1610
1646
  this.emitThreadSettings(session, effectiveCapabilities, session.runtimeState);
1611
1647
  }
1612
1648
  async setRuntimeOptionOnTransport(protocolSessionId, source, optionId, value) {
1613
- if (source === "configOption") {
1649
+ if (source === "configOption" || source === "modelConfigOption") {
1614
1650
  return await this.transport.request("session/set_config_option", {
1615
1651
  sessionId: protocolSessionId,
1616
1652
  configId: optionId,