@agentclientprotocol/claude-agent-acp 0.95.1-preview.1 → 0.95.1-preview.3

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.
@@ -1,5 +1,6 @@
1
1
  import { RequestError, } from "@agentclientprotocol/sdk/unstable";
2
2
  import { AIR_SUBAGENT_KEY, airExtensionMeta } from "./air-extension.js";
3
+ import { MODEL_CONFIG_ID } from "./session-model.js";
3
4
  import { isFinalToolCallStatus } from "./tool-calls/renderer.js";
4
5
  const MAX_PENDING_PARENTS = 64;
5
6
  const MAX_PENDING_UPDATES = 256;
@@ -17,6 +18,7 @@ export class NativeSubagentRuntime {
17
18
  logger;
18
19
  lineage;
19
20
  launchedAgent;
21
+ models;
20
22
  enabled;
21
23
  /** The form of the child sessions; see {@link SubagentForm}. */
22
24
  form;
@@ -42,6 +44,12 @@ export class NativeSubagentRuntime {
42
44
  * closes an unfinished call has no `toolName` to recognize it by.
43
45
  */
44
46
  hiddenLaunches = new Map();
47
+ /**
48
+ * The RFD form: the SendMessage calls that the client is not shown, until
49
+ * their final update, with their first report and the session that made
50
+ * them. A message that reaches an announced child replaces its call.
51
+ */
52
+ hiddenSendMessages = new Map();
45
53
  /**
46
54
  * The RFD form: the SubagentHandback calls of announced children that the
47
55
  * client is not shown yet, with their first report, the report from their
@@ -69,14 +77,17 @@ export class NativeSubagentRuntime {
69
77
  * `form` is undefined for a client without native subagent sessions. The RFD
70
78
  * form restores an agent from before a load or resume with `lineage`. The AIR
71
79
  * form does too, and `launchedAgent` finds the agent that a launch call made.
80
+ * `models` maps the model of a child response to the root session's entry
81
+ * (see {@link reportModel}).
72
82
  */
73
- constructor(form, rootSessionId, session, publish, logger, lineage, launchedAgent) {
83
+ constructor(form, rootSessionId, session, publish, logger, lineage, launchedAgent, models) {
74
84
  this.rootSessionId = rootSessionId;
75
85
  this.session = session;
76
86
  this.publish = publish;
77
87
  this.logger = logger;
78
88
  this.lineage = lineage;
79
89
  this.launchedAgent = launchedAgent;
90
+ this.models = models;
80
91
  this.form = form;
81
92
  this.enabled = form !== undefined;
82
93
  this.children = session.nativeSubagentsByTaskId ??= new Map();
@@ -90,9 +101,10 @@ export class NativeSubagentRuntime {
90
101
  }
91
102
  async route(notification, deliver, forcedSessionId) {
92
103
  const routed = await this.routeUpdate(notification, deliver, forcedSessionId);
93
- if (routed)
94
- this.rememberSendMessage(routed);
95
- return routed;
104
+ if (!routed)
105
+ return routed;
106
+ this.rememberSendMessage(routed);
107
+ return this.form === "rfd" ? this.routeRfdSendMessage(routed, forcedSessionId) : routed;
96
108
  }
97
109
  /**
98
110
  * The RFD form: the work state that the client holds for the announced child
@@ -104,57 +116,71 @@ export class NativeSubagentRuntime {
104
116
  const child = this.children.get(sessionId);
105
117
  return child?.announced ? child.workState : undefined;
106
118
  }
107
- /** Report the model of a child response, not the parent's model or a launch-time alias. */
119
+ /**
120
+ * Reports the model of a child response, an exact model id, as the entry of
121
+ * the root session's `model` option that matches it, else as the id (see
122
+ * {@link responseModelEntry}). The child gets no model before its first
123
+ * response: the adapter cannot tell the model that Claude Code picks for it.
124
+ * The client gets a new configuration only when the entry changes.
125
+ */
108
126
  async reportModel(parentToolUseId, model, deliver) {
109
- if (this.form !== "rfd" || !parentToolUseId || !model || model === "<synthetic>") {
127
+ if (!this.form || !parentToolUseId || !model || model === "<synthetic>") {
110
128
  return;
111
129
  }
112
130
  await deliver({
113
131
  sessionId: this.rootSessionId,
114
132
  update: {
115
133
  sessionUpdate: "config_option_update",
116
- configOptions: [
117
- {
118
- id: "model",
119
- name: "Model",
120
- category: "model",
121
- type: "select",
122
- currentValue: model,
123
- options: [{ value: model, name: model }],
124
- },
125
- ],
134
+ configOptions: [childModelOption(responseModelEntry(model, this.models))],
126
135
  _meta: { claudeCode: { parentToolUseId } },
127
136
  },
128
137
  });
129
138
  }
130
- /** Hook observations can precede the task event that establishes a child's parent. */
139
+ /** The configuration that the client last got for the announced child `sessionId`, if any. */
140
+ configurationOf(sessionId) {
141
+ const published = this.configBySession.get(sessionId)?.published;
142
+ if (!this.childOfSession(sessionId)?.announced || !published)
143
+ return undefined;
144
+ return JSON.parse(published);
145
+ }
146
+ /**
147
+ * Takes configuration options of the child `agentId`: an SDK agent id, as a
148
+ * hook names it, or a child session id. Hook observations can precede the
149
+ * task event that establishes a child's parent.
150
+ */
131
151
  async reportConfiguration(agentId, options, meta) {
132
- if (this.form !== "rfd" || agentId === this.rootSessionId || options.length === 0)
152
+ if (!this.form || agentId === this.rootSessionId || options.length === 0)
133
153
  return;
134
- let config = this.configBySession.get(agentId);
154
+ // In AIR's draft, the session id of a resumed or restored agent is
155
+ // `<agent id>:generation:<n>`, so the agent id names its current generation.
156
+ const known = this.children.get(agentId) ?? this.childOfSession(agentId);
157
+ const sessionId = known?.sessionId ?? agentId;
158
+ let config = this.configBySession.get(sessionId);
135
159
  if (!config) {
136
- const pending = [...this.configBySession.keys()].filter((id) => !this.children.has(id)).length;
137
- if (!this.children.has(agentId) && pending >= MAX_PENDING_PARENTS) {
160
+ const pending = [...this.configBySession.keys()].filter((id) => !this.childOfSession(id)).length;
161
+ if (!known && pending >= MAX_PENDING_PARENTS) {
138
162
  this.logger.log(`Session ${this.rootSessionId}: dropping configuration for unannounced subagent ${agentId}; pending buffer limit reached`);
139
163
  return;
140
164
  }
141
165
  config = { options: new Map(), publishing: Promise.resolve() };
142
- this.configBySession.set(agentId, config);
166
+ this.configBySession.set(sessionId, config);
143
167
  }
144
168
  for (const option of options)
145
169
  config.options.set(option.id, option);
146
- const child = this.children.get(agentId);
147
- if (child?.announced)
148
- await this.publishConfiguration(child, meta);
170
+ if (known?.announced)
171
+ await this.publishConfiguration(known, meta);
149
172
  }
150
173
  async publishConfiguration(child, meta) {
151
174
  const config = this.configBySession.get(child.sessionId);
152
- if (!config)
175
+ // AIR's draft ends a child for good, so it gets no update after its end,
176
+ // also when the end comes while this publication waits for an earlier one.
177
+ const ended = () => this.form === "air" && (child.terminalState !== undefined || !!child.terminalPromise);
178
+ if (!config || ended())
153
179
  return;
154
180
  // SDK hooks can run concurrently with streamed responses. Serialize snapshots
155
181
  // so a delayed publication cannot replace newer settings with older ones.
156
182
  const publishing = config.publishing.then(async () => {
157
- if (this.configBySession.get(child.sessionId) !== config)
183
+ if (this.configBySession.get(child.sessionId) !== config || ended())
158
184
  return;
159
185
  const priority = ["mode", "model", "thought_level", "model_config"];
160
186
  const rank = (option) => {
@@ -256,11 +282,11 @@ export class NativeSubagentRuntime {
256
282
  (claudeMeta?.parentToolUseId
257
283
  ? this.childByParentToolUse.get(claudeMeta.parentToolUseId)
258
284
  : undefined);
285
+ if (announcedChild?.announced && update.sessionUpdate === "config_option_update") {
286
+ await this.reportConfiguration(announcedChild.sessionId, update.configOptions, update._meta);
287
+ return null;
288
+ }
259
289
  if (this.form === "rfd" && announcedChild?.announced) {
260
- if (update.sessionUpdate === "config_option_update") {
261
- await this.reportConfiguration(announcedChild.sessionId, update.configOptions, update._meta);
262
- return null;
263
- }
264
290
  const handback = await this.routeRfdHandback(notification, announcedChild);
265
291
  if (handback !== undefined)
266
292
  return handback;
@@ -781,7 +807,10 @@ export class NativeSubagentRuntime {
781
807
  });
782
808
  }
783
809
  if (prompt && toolUseId) {
784
- await publishPrompt(child, prompt, toolUseId, this.senderByToolUse.get(toolUseId), this.publish);
810
+ const sendMessage = this.hiddenSendMessages.get(toolUseId);
811
+ await publishPrompt(child, prompt, toolUseId, sendMessage?.sender ?? this.senderByToolUse.get(toolUseId), this.publish, { replacesCall: sendMessage !== undefined });
812
+ if (sendMessage)
813
+ sendMessage.delivered = true;
785
814
  }
786
815
  for (const pending of toolUseId ? this.takePending(toolUseId) : [])
787
816
  await deliver(pending);
@@ -812,10 +841,14 @@ export class NativeSubagentRuntime {
812
841
  return;
813
842
  await this.reportState(child, rfdEndState("failed", { error }));
814
843
  }
815
- /** Announces `child` in the client's form. A runtime without one never does. */
844
+ /**
845
+ * Announces `child` in the client's form, then its configuration. A runtime
846
+ * without a form never does.
847
+ */
816
848
  async announce(child) {
817
- if (this.form)
818
- await announceNativeSubagent(child, this.publish, this.form);
849
+ if (!this.form)
850
+ return;
851
+ await announceNativeSubagent(child, this.publish, this.form);
819
852
  await this.publishConfiguration(child);
820
853
  }
821
854
  /** The RFD form: publishes the work state of `child` on its parent. */
@@ -963,6 +996,42 @@ export class NativeSubagentRuntime {
963
996
  }
964
997
  return unexposedControlFallback(handback.initial, notification, child.sessionId);
965
998
  }
999
+ /**
1000
+ * The RFD form: an update of a SendMessage call, which its message replaces
1001
+ * once it reaches an announced child: the sender's outgoing view and the
1002
+ * child's incoming one, see {@link linkedSessionMessages}. The SDK reports
1003
+ * that delivery before the call's final update. A call whose message reached
1004
+ * no announced child, such as one to the parent, is shown after all, with its
1005
+ * final status. Any other update is returned as is.
1006
+ */
1007
+ routeRfdSendMessage(notification, forcedSessionId) {
1008
+ const { update } = notification;
1009
+ if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
1010
+ return notification;
1011
+ }
1012
+ const toolCallId = update.toolCallId;
1013
+ // A permission request already showed the call to the client, so it keeps
1014
+ // getting its updates, and its message goes only to the child.
1015
+ if (forcedSessionId) {
1016
+ this.hiddenSendMessages.delete(toolCallId);
1017
+ return notification;
1018
+ }
1019
+ let sendMessage = this.hiddenSendMessages.get(toolCallId);
1020
+ if (!sendMessage) {
1021
+ const claudeMeta = update._meta?.claudeCode;
1022
+ if (update.sessionUpdate !== "tool_call" || claudeMeta?.toolName !== "SendMessage") {
1023
+ return notification;
1024
+ }
1025
+ sendMessage = { initial: notification, sender: notification.sessionId, delivered: false };
1026
+ this.hiddenSendMessages.set(toolCallId, sendMessage);
1027
+ }
1028
+ if (!isFinalToolCallStatus(update.status))
1029
+ return null;
1030
+ this.hiddenSendMessages.delete(toolCallId);
1031
+ if (sendMessage.delivered)
1032
+ return null;
1033
+ return unexposedControlFallback(sendMessage.initial, notification, sendMessage.sender);
1034
+ }
966
1035
  /** Whether `update` is of an Agent/Task call, by its tool name or, in the
967
1036
  * RFD form, by the id of a call the client is not shown. */
968
1037
  isControlUpdate(update) {
@@ -1019,6 +1088,7 @@ export class NativeSubagentRuntime {
1019
1088
  this.taskFinishPromises.clear();
1020
1089
  this.senderByToolUse.clear();
1021
1090
  this.hiddenLaunches.clear();
1091
+ this.hiddenSendMessages.clear();
1022
1092
  this.handbacks.clear();
1023
1093
  this.generationByTaskId.clear();
1024
1094
  this.pending.clear();
@@ -1131,6 +1201,31 @@ export class NativeSubagentRuntime {
1131
1201
  return `${taskId}:generation:${generation}`;
1132
1202
  }
1133
1203
  }
1204
+ /**
1205
+ * The `model` option of a child session. The client cannot change a child's
1206
+ * configuration, so the select offers only the current model.
1207
+ */
1208
+ export function childModelOption(entry) {
1209
+ return {
1210
+ id: MODEL_CONFIG_ID,
1211
+ name: "Model",
1212
+ category: "model",
1213
+ type: "select",
1214
+ currentValue: entry.value,
1215
+ options: [{ value: entry.value, name: entry.name }],
1216
+ };
1217
+ }
1218
+ /**
1219
+ * The model entry for `model`, the model id of a child's response: the entry
1220
+ * of the root session's `model` option that matches it, else the id, with its
1221
+ * display name when the session's model list has one. Undefined for no model,
1222
+ * and for `<synthetic>`, which marks a message that no model wrote.
1223
+ */
1224
+ export function responseModelEntry(model, models) {
1225
+ if (typeof model !== "string" || !model || model === "<synthetic>")
1226
+ return undefined;
1227
+ return models?.entry(model) ?? { value: model, name: models?.name(model) ?? model };
1228
+ }
1134
1229
  /**
1135
1230
  * Announces the child on its parent, once, before any update of the child.
1136
1231
  * The RFD form announces it as running and then sends the prompt as the
@@ -1161,7 +1256,7 @@ export async function announceNativeSubagent(child, publish, form) {
1161
1256
  child.announced = true;
1162
1257
  // The immediate parent made the Agent/Task call that launched the child.
1163
1258
  if (child.prompt) {
1164
- await publishPrompt(child, child.prompt, child.parentToolUseId, child.parentSessionId, publish, { replacesLaunch: true });
1259
+ await publishPrompt(child, child.prompt, child.parentToolUseId, child.parentSessionId, publish, { replacesCall: true });
1165
1260
  }
1166
1261
  return;
1167
1262
  }
@@ -1221,17 +1316,17 @@ export async function finishNativeSubagent(session, taskId, state, publish) {
1221
1316
  * transcript is the same delegation and is not sent again. `sender` is the
1222
1317
  * session that made that call, when known.
1223
1318
  *
1224
- * A launch's prompt (`replacesLaunch`) also goes to the parent, see
1225
- * {@link linkedSessionMessages}. A SendMessage call stays a tool call of the
1226
- * sender, so its message has only the incoming view.
1319
+ * When the message replaces its call (`replacesCall`), a launch's or a
1320
+ * SendMessage call the client is not shown, it also goes to the sender, see
1321
+ * {@link linkedSessionMessages}. Otherwise it has only the incoming view.
1227
1322
  */
1228
- async function publishPrompt(child, prompt, toolUseId, sender, publish, { replacesLaunch = false } = {}) {
1323
+ async function publishPrompt(child, prompt, toolUseId, sender, publish, { replacesCall = false } = {}) {
1229
1324
  child.promptIds ??= new Set();
1230
1325
  const messageId = toolUseId ?? `${child.sessionId}:prompt:${child.promptIds.size + 1}`;
1231
1326
  if (child.promptIds.has(messageId))
1232
1327
  return;
1233
1328
  child.promptIds.add(messageId);
1234
- if (replacesLaunch && sender !== undefined && toolUseId !== undefined) {
1329
+ if (replacesCall && sender !== undefined && toolUseId !== undefined) {
1235
1330
  for (const view of linkedSessionMessages(toolUseId, sender, child.sessionId, prompt)) {
1236
1331
  await publish(view);
1237
1332
  }
@@ -1252,8 +1347,9 @@ async function publishPrompt(child, prompt, toolUseId, sender, publish, { replac
1252
1347
  * The RFD form: a message that a tool call sent, which the client is not
1253
1348
  * shown, as two session messages: the sender's outgoing view, then the
1254
1349
  * recipient's incoming one. That call is a launch's Agent/Task call, whose
1255
- * prompt goes from the parent to the child, or a child's SubagentHandback
1256
- * call, whose report goes from the child to the parent. Both views use the
1350
+ * prompt goes from the parent to the child, a SendMessage call, whose message
1351
+ * goes from its sender to a child, or a child's SubagentHandback call, whose
1352
+ * report goes from the child to the parent. Both views use the
1257
1353
  * call's tool use id, and each names the other with the draft RFD's
1258
1354
  * `recipientMessageId` and `senderMessageId`.
1259
1355
  */
@@ -1472,7 +1568,8 @@ function isFailedToolCallUpdate(update) {
1472
1568
  * A tool call that the client was not shown, as one `tool_call` with its
1473
1569
  * final status, shown after all: an Agent/Task call whose child was never
1474
1570
  * exposed (AIR's draft shows only a failed one, the RFD form also a completed
1475
- * one), or a failed SubagentHandback call.
1571
+ * one), a SendMessage call whose message reached no announced child, or a
1572
+ * failed SubagentHandback call.
1476
1573
  */
1477
1574
  function unexposedControlFallback(initial, terminal, sessionId) {
1478
1575
  if (terminal.update.sessionUpdate !== "tool_call" &&
@@ -69,5 +69,28 @@ export declare function applyAvailableModelsAllowlist(sdkModels: ModelInfo[], al
69
69
  */
70
70
  export declare function narrowToGatewayModels(models: ModelInfo[], listedModelIds: readonly string[], regionPrefix?: string): ModelInfo[];
71
71
  export declare function getAvailableModels(query: Query, models: ModelInfo[], sdkModels: ModelInfo[], settingsManager: ModelSettingsSource, logger: ModelLogger, isResumedSession: boolean, sessionId: string, resumedModelHint?: string, narrowedByGateway?: boolean): Promise<SessionModelState>;
72
+ /** The display name of the model id `model` in `modelInfos`, if it is there. */
73
+ export declare function modelDisplayName(modelInfos: ModelInfo[], model: string): string | undefined;
74
+ /**
75
+ * The entry of the `model` option among `configOptions` for `model`, a model
76
+ * id that a response reported, so that a client can compare it with the
77
+ * session's own model. Best first:
78
+ *
79
+ * 1. the current entry, when it runs that model up to a context hint: a
80
+ * session on `opus[1m]` or `default` that answers as `claude-opus-4-6`;
81
+ * 2. the entry whose value or resolved model is that id;
82
+ * 3. an entry that runs that model up to a context hint.
83
+ *
84
+ * Among several entries for one model, a concrete entry comes before
85
+ * `default`, as in {@link resolveModelPreference}. Undefined when no entry
86
+ * matches. A model's resolved id comes from `modelInfos`
87
+ * (`ModelInfo.resolvedModel`), the wire id that a response reports, as for
88
+ * {@link matchResumedModel}. It is compared as it is: a dated id such as
89
+ * `claude-haiku-4-5-20251001` matches only the same dated id.
90
+ */
91
+ export declare function modelEntryFor(configOptions: readonly SessionConfigOption[], modelInfos: ModelInfo[], model: string): {
92
+ value: string;
93
+ name: string;
94
+ } | undefined;
72
95
  export {};
73
96
  //# sourceMappingURL=session-model.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"session-model.d.ts","sourceRoot":"","sources":["../src/session-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAIjF,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AACF,KAAK,mBAAmB,GAAG;IAAE,WAAW,IAAI,QAAQ,CAAA;CAAE,CAAC;AAmGvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA2ChG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAyBnF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,SAAS,EAAE,EACvB,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAsDrB;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CASvF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,MAAM,EAAE,EACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,SAAS,EAAE,CA6Db;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EAAE,EACnB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,YAAY,CAAC,EAAE,MAAM,GACpB,SAAS,EAAE,CAiBb;AAWD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,eAAe,EAAE,mBAAmB,EACpC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,OAAO,EACzB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,UAAQ,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAoJ5B"}
1
+ {"version":3,"file":"session-model.d.ts","sourceRoot":"","sources":["../src/session-model.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,gCAAgC,CAAC;AAIjF,eAAO,MAAM,eAAe,UAAU,CAAC;AAEvC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,eAAe,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;CACrC,CAAC;AACF,KAAK,mBAAmB,GAAG;IAAE,WAAW,IAAI,QAAQ,CAAA;CAAE,CAAC;AAmGvD,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CA2ChG;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,CAyBnF;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,iBAAiB,EACzB,UAAU,EAAE,SAAS,EAAE,EACvB,mBAAmB,EAAE,OAAO,GAC3B,mBAAmB,CAsDrB;AA+CD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CASvF;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,6BAA6B,CAC3C,SAAS,EAAE,SAAS,EAAE,EACtB,SAAS,EAAE,MAAM,EAAE,EACnB,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC/C,YAAY,GAAE,SAAS,MAAM,EAAO,GACnC,SAAS,EAAE,CA6Db;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EAAE,EACnB,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,YAAY,CAAC,EAAE,MAAM,GACpB,SAAS,EAAE,CAiBb;AAWD,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,SAAS,EAAE,EACnB,SAAS,EAAE,SAAS,EAAE,EACtB,eAAe,EAAE,mBAAmB,EACpC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,OAAO,EACzB,SAAS,EAAE,MAAM,EACjB,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,UAAQ,GACxB,OAAO,CAAC,iBAAiB,CAAC,CAoJ5B;AAED,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,SAAS,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAG3F;AAUD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,SAAS,mBAAmB,EAAE,EAC7C,UAAU,EAAE,SAAS,EAAE,EACvB,KAAK,EAAE,MAAM,GACZ;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAkB7C"}
@@ -523,3 +523,46 @@ export async function getAvailableModels(query, models, sdkModels, settingsManag
523
523
  currentModelId: currentModel.value,
524
524
  };
525
525
  }
526
+ /** The display name of the model id `model` in `modelInfos`, if it is there. */
527
+ export function modelDisplayName(modelInfos, model) {
528
+ return modelInfos.find((info) => info.value === model || info.resolvedModel === model)
529
+ ?.displayName;
530
+ }
531
+ /** The flat entries of a session's `model` select option. */
532
+ function modelOptionEntries(option) {
533
+ if (option.type !== "select")
534
+ return [];
535
+ return option.options.flatMap((entry) => "value" in entry ? [{ value: entry.value, name: entry.name }] : entry.options);
536
+ }
537
+ /**
538
+ * The entry of the `model` option among `configOptions` for `model`, a model
539
+ * id that a response reported, so that a client can compare it with the
540
+ * session's own model. Best first:
541
+ *
542
+ * 1. the current entry, when it runs that model up to a context hint: a
543
+ * session on `opus[1m]` or `default` that answers as `claude-opus-4-6`;
544
+ * 2. the entry whose value or resolved model is that id;
545
+ * 3. an entry that runs that model up to a context hint.
546
+ *
547
+ * Among several entries for one model, a concrete entry comes before
548
+ * `default`, as in {@link resolveModelPreference}. Undefined when no entry
549
+ * matches. A model's resolved id comes from `modelInfos`
550
+ * (`ModelInfo.resolvedModel`), the wire id that a response reports, as for
551
+ * {@link matchResumedModel}. It is compared as it is: a dated id such as
552
+ * `claude-haiku-4-5-20251001` matches only the same dated id.
553
+ */
554
+ export function modelEntryFor(configOptions, modelInfos, model) {
555
+ const option = configOptions.find((candidate) => candidate.id === MODEL_CONFIG_ID);
556
+ if (option?.type !== "select")
557
+ return undefined;
558
+ // Concrete entries first; `sort` keeps the list order otherwise.
559
+ const entries = modelOptionEntries(option).sort((left, right) => Number(left.value === "default") - Number(right.value === "default"));
560
+ const observed = canonicalizeModelId(model);
561
+ const resolved = (value) => canonicalizeModelId(modelInfos.find((info) => info.value === value)?.resolvedModel ?? value);
562
+ const sameModel = (value) => stripContextHints(resolved(value)) === stripContextHints(observed);
563
+ const current = entries.find((entry) => entry.value === option.currentValue);
564
+ if (current && sameModel(current.value))
565
+ return current;
566
+ return (entries.find((entry) => entry.value === model || resolved(entry.value) === observed) ??
567
+ entries.find((entry) => sameModel(entry.value)));
568
+ }
@@ -16,13 +16,26 @@
16
16
  * Unlike live, the replay does not tell runs apart, so a child that handed
17
17
  * back any report gets no reply from its launch's result.
18
18
  *
19
+ * A SendMessage call whose message the replay delivered to a child before it
20
+ * reached the call is that message too: the sender's outgoing view, at the
21
+ * call, and the child's incoming one, in the child's history. A child's
22
+ * history follows its launch, so a call to a child from its parent is always
23
+ * delivered first. Any other SendMessage call stays a tool call.
24
+ *
25
+ * Right after its announcement, each child gets its `model` option: the model
26
+ * of its first response, in its transcript. A later response with another
27
+ * model sends it again. A child without responses gets none. After the load
28
+ * response, a model goes again only when the session's model list, which the
29
+ * replay can lack, maps it to another entry.
30
+ *
19
31
  * A launch without metadata has no known id, so it is not announced: the RFD
20
32
  * forbids inventing one. Its Agent call stays an ordinary tool call of the
21
33
  * parent, and a notice says that subagent history is missing.
22
34
  */
35
+ import type { SessionConfigOption } from "@agentclientprotocol/sdk/unstable";
23
36
  import type { SessionMessage } from "@anthropic-ai/claude-agent-sdk";
24
37
  import type { AcpSessionNotification, SubagentState } from "./acp-subagents.js";
25
- import { type WorkState } from "./native-subagents.js";
38
+ import { type ModelEntry, type SubagentModels, type WorkState } from "./native-subagents.js";
26
39
  import type { SubagentHistory, SubagentRecord } from "./subagent-history.js";
27
40
  type Publish = (notification: AcpSessionNotification) => Promise<void>;
28
41
  /** A child that the replay shows: its launch has metadata. */
@@ -44,10 +57,15 @@ export type ReplayChild = {
44
57
  promptSent: boolean;
45
58
  /** Whether it handed a report to its parent with a SubagentHandback call. */
46
59
  handedBack?: boolean;
60
+ /** The `model` option entry that the replay sent. */
61
+ model?: ModelEntry;
62
+ /** The model id of the latest response in its transcript. */
63
+ responseModel?: string;
47
64
  };
48
65
  export declare class RfdSubagentReplay {
49
66
  private readonly history;
50
67
  private readonly publish;
68
+ private readonly models?;
51
69
  /** The children by the id of the tool use that launched each. */
52
70
  private readonly children;
53
71
  private readonly byAgentId;
@@ -58,7 +76,7 @@ export declare class RfdSubagentReplay {
58
76
  private withoutHistory;
59
77
  /** The SubagentHandback calls, with their report and whether their result failed. */
60
78
  private readonly handbacks;
61
- constructor(history: SubagentHistory, publish: Publish);
79
+ constructor(history: SubagentHistory, publish: Publish, models?: SubagentModels | undefined);
62
80
  /**
63
81
  * Registers the launches, the launch results, and the SendMessage calls in
64
82
  * `messages`, the transcript of the session `ownerSessionId`.
@@ -66,8 +84,18 @@ export declare class RfdSubagentReplay {
66
84
  scan(messages: readonly SessionMessage[], ownerSessionId: string): void;
67
85
  /** The child that the tool use `toolUseId` launched, when the replay shows it. */
68
86
  childOfLaunch(toolUseId: string): ReplayChild | undefined;
69
- /** Announces `child` on its parent, as live: running, with the parent's labels. */
70
- announce(child: ReplayChild): Promise<void>;
87
+ /**
88
+ * Announces `child` on its parent, as live: running, with the parent's
89
+ * labels. Then it sends `firstModel`, the model of the child's first
90
+ * response, when the transcript has one.
91
+ */
92
+ announce(child: ReplayChild, firstModel?: string): Promise<void>;
93
+ /**
94
+ * Takes the model of a replayed response of the child `owner`. A child
95
+ * whose model changes gets its `model` option again.
96
+ */
97
+ responseModel(owner: ReplayChild, model: string | undefined): Promise<void>;
98
+ private sendModel;
71
99
  /**
72
100
  * The rest of a child's user message to replay, or `null` when nothing is
73
101
  * left, after its text went out as the child's incoming `session_message`.
@@ -89,10 +117,11 @@ export declare class RfdSubagentReplay {
89
117
  /**
90
118
  * Takes a replayed update of a tool call, from the transcript of `owner`
91
119
  * when it is a child's. Returns whether the client is not shown it: an
92
- * update of the launch of a child that the replay shows, or of a successful
120
+ * update of the launch of a child that the replay shows, of a SendMessage
121
+ * call whose message the replay already delivered, or of a successful
93
122
  * SubagentHandback call of `owner`. A foreground launch's completed result
94
- * goes out as the child's reply, and a hand-back as the child's message to
95
- * its parent.
123
+ * goes out as the child's reply, a delivered SendMessage as the sender's
124
+ * outgoing message, and a hand-back as the child's message to its parent.
96
125
  */
97
126
  toolCallUpdate(update: AcpSessionNotification["update"], owner: ReplayChild | undefined): Promise<boolean>;
98
127
  /**
@@ -116,7 +145,7 @@ export declare class RfdSubagentReplay {
116
145
  * can be cancelled, as live. Any other child cannot be running, because the
117
146
  * process that ran it is gone, so its replayed end is its current state.
118
147
  */
119
- currentStates(live: (sessionId: string) => WorkState["state"] | undefined): AcpSessionNotification[];
148
+ currentStates(live: (sessionId: string) => WorkState["state"] | undefined, liveConfiguration?: (sessionId: string) => SessionConfigOption[] | undefined): AcpSessionNotification[];
120
149
  private registerLaunch;
121
150
  /** The prompt of the launch, with the launch's id, to the parent and the child, as live. */
122
151
  private sendPrompt;
@@ -127,10 +156,15 @@ export declare class RfdSubagentReplay {
127
156
  * first undelivered SendMessage to the child whose message the delivered
128
157
  * text contains. With that call, the message has the call's id and text and
129
158
  * its sender, as live; without it, its transcript id and the delivered text.
159
+ * Unless the replay already showed the call as a tool call, the message
160
+ * replaces it, so this view names the sender's view, which goes out at the
161
+ * call (see {@link toolCallUpdate}).
130
162
  */
131
163
  private sendDelivery;
132
164
  private sendMessage;
133
165
  }
166
+ /** The model of the first response in `messages`, a subagent's transcript. */
167
+ export declare function firstResponseModel(messages: readonly SessionMessage[]): string | undefined;
134
168
  /**
135
169
  * The end of a foreground launch from its tool result: an error result that
136
170
  * names a stop is a cancellation, any other error a failure.
@@ -1 +1 @@
1
- {"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"subagent-replay.d.ts","sourceRoot":"","sources":["../src/subagent-replay.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAC7E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AACrE,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAOL,KAAK,UAAU,EAKf,KAAK,cAAc,EACnB,KAAK,SAAS,EACf,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE7E,KAAK,OAAO,GAAG,CAAC,YAAY,EAAE,sBAAsB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEvE,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG;IACxB,mDAAmD;IACnD,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,cAAc,CAAC;IACvB,wCAAwC;IACxC,eAAe,EAAE,MAAM,CAAC;IACxB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,mEAAmE;IACnE,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,iEAAiE;IACjE,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB,6EAA6E;IAC7E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,qDAAqD;IACrD,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAeF,qBAAa,iBAAiB;IAgB1B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAjB1B,iEAAiE;IACjE,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAkC;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAyB;IACtD,8DAA8D;IAC9D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,uFAAuF;IACvF,OAAO,CAAC,cAAc,CAAK;IAC3B,qFAAqF;IACrF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAGtB;gBAGe,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,cAAc,YAAA;IAG1C;;;OAGG;IACH,IAAI,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,IAAI;IAmCvE,kFAAkF;IAClF,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAIzD;;;;OAIG;IACG,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAqBtE;;;OAGG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;YAOnE,SAAS;IAYvB;;;;;;;;;;OAUG;IACG,YAAY,CAChB,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,cAAc,EACvB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,OAAO,CAAC;IAanB;;;;OAIG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAO3F;;;;;;;;OAQG;IACG,cAAc,CAClB,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,EACxC,KAAK,EAAE,WAAW,GAAG,SAAS,GAC7B,OAAO,CAAC,OAAO,CAAC;IAoDnB;;;OAGG;IACH,gBAAgB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO;IAOzD;;;OAGG;IACG,MAAM,CAAC,eAAe,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B5E;;;;;;;;;;OAUG;IACH,aAAa,CACX,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,SAAS,CAAC,OAAO,CAAC,GAAG,SAAS,EAC3D,iBAAiB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,mBAAmB,EAAE,GAAG,SAAS,GAC3E,sBAAsB,EAAE;IAoC3B,OAAO,CAAC,cAAc;IAwBtB,4FAA4F;YAC9E,UAAU;IAYxB;;;;;;;;;;OAUG;YACW,YAAY;YAsBZ,WAAW;CAmB1B;AAED,8EAA8E;AAC9E,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,cAAc,EAAE,GAAG,MAAM,GAAG,SAAS,CAO1F;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,WAAW,GAAG,QAAQ,GAAG,WAAW,CAMtC"}