@agentclientprotocol/claude-agent-acp 0.91.1-preview.3 → 0.91.1-preview.5
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/acp-agent.d.ts +9 -0
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +84 -7
- package/dist/client-system-prompt.d.ts +41 -0
- package/dist/client-system-prompt.d.ts.map +1 -0
- package/dist/client-system-prompt.js +40 -0
- package/dist/native-subagents.d.ts +58 -3
- package/dist/native-subagents.d.ts.map +1 -1
- package/dist/native-subagents.js +174 -9
- package/dist/subagent-history.d.ts +5 -0
- package/dist/subagent-history.d.ts.map +1 -1
- package/dist/subagent-history.js +62 -9
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +54 -9
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +3 -0
- package/dist/v2/prompt.d.ts +2 -1
- package/dist/v2/prompt.d.ts.map +1 -1
- package/dist/v2/prompt.js +1 -1
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +24 -5
- package/dist/v2/terminal.js +2 -3
- package/dist/v2/tool-call-meta.d.ts +22 -13
- package/dist/v2/tool-call-meta.d.ts.map +1 -1
- package/dist/v2/tool-call-meta.js +44 -16
- package/package.json +2 -2
package/dist/native-subagents.js
CHANGED
|
@@ -15,6 +15,7 @@ export class NativeSubagentRuntime {
|
|
|
15
15
|
publish;
|
|
16
16
|
logger;
|
|
17
17
|
lineage;
|
|
18
|
+
launchedAgent;
|
|
18
19
|
enabled;
|
|
19
20
|
/** The form of the child sessions; see {@link SubagentForm}. */
|
|
20
21
|
form;
|
|
@@ -31,23 +32,37 @@ export class NativeSubagentRuntime {
|
|
|
31
32
|
*/
|
|
32
33
|
childByToolCall = new Map();
|
|
33
34
|
taskFinishPromises = new Map();
|
|
34
|
-
/** The
|
|
35
|
+
/** The session that made each SendMessage call. */
|
|
35
36
|
senderByToolUse = new Map();
|
|
36
37
|
generationByTaskId = new Map();
|
|
37
38
|
pending = new Map();
|
|
38
39
|
pendingCount = 0;
|
|
40
|
+
/** The AIR form: agents being restored from the history, by agent id. */
|
|
41
|
+
agentRestores = new Map();
|
|
42
|
+
/** The AIR form: launch ids being resolved to an agent, by launch id. */
|
|
43
|
+
launchRestores = new Map();
|
|
44
|
+
/** The AIR form: launch ids that the history has no restorable agent for. */
|
|
45
|
+
unresolvedLaunches = new Set();
|
|
46
|
+
/** The AIR form: agents that the history could not place, so it is read once. */
|
|
47
|
+
unrestorableAgents = new Set();
|
|
48
|
+
/** The latest generation number given to a restored agent. */
|
|
49
|
+
lastRestoredGeneration = 0;
|
|
50
|
+
/** Bumped when the restore state is dropped; a restore that began before it is void. */
|
|
51
|
+
restoreEpoch = 0;
|
|
39
52
|
/** The RFD form: how many times the parent was cancelled (see {@link awaitingUser}). */
|
|
40
53
|
parentCancels = 0;
|
|
41
54
|
/**
|
|
42
55
|
* `form` is undefined for a client without native subagent sessions. The RFD
|
|
43
|
-
* form restores an agent from before a load or resume with `lineage`.
|
|
56
|
+
* form restores an agent from before a load or resume with `lineage`. The AIR
|
|
57
|
+
* form does too, and `launchedAgent` finds the agent that a launch call made.
|
|
44
58
|
*/
|
|
45
|
-
constructor(form, rootSessionId, session, publish, logger, lineage) {
|
|
59
|
+
constructor(form, rootSessionId, session, publish, logger, lineage, launchedAgent) {
|
|
46
60
|
this.rootSessionId = rootSessionId;
|
|
47
61
|
this.session = session;
|
|
48
62
|
this.publish = publish;
|
|
49
63
|
this.logger = logger;
|
|
50
64
|
this.lineage = lineage;
|
|
65
|
+
this.launchedAgent = launchedAgent;
|
|
51
66
|
this.form = form;
|
|
52
67
|
this.enabled = form !== undefined;
|
|
53
68
|
this.children = session.nativeSubagentsByTaskId ??= new Map();
|
|
@@ -61,7 +76,7 @@ export class NativeSubagentRuntime {
|
|
|
61
76
|
}
|
|
62
77
|
async route(notification, deliver, forcedSessionId) {
|
|
63
78
|
const routed = await this.routeUpdate(notification, deliver, forcedSessionId);
|
|
64
|
-
if (routed
|
|
79
|
+
if (routed)
|
|
65
80
|
this.rememberSendMessage(routed);
|
|
66
81
|
return routed;
|
|
67
82
|
}
|
|
@@ -137,7 +152,13 @@ export class NativeSubagentRuntime {
|
|
|
137
152
|
if (this.enabled && claudeMeta?.parentToolUseId) {
|
|
138
153
|
const child = this.childByParentToolUse.get(claudeMeta.parentToolUseId);
|
|
139
154
|
if (!child || !child.announced) {
|
|
140
|
-
|
|
155
|
+
const launchId = claudeMeta.parentToolUseId;
|
|
156
|
+
this.buffer(launchId, notification);
|
|
157
|
+
// An id that this process never saw is the launch call of an agent
|
|
158
|
+
// from before a load or resume; a new Agent call is known by now.
|
|
159
|
+
if (!child && this.form === "air" && this.isUnseenLaunch(launchId)) {
|
|
160
|
+
await this.restoreLaunch(launchId, deliver);
|
|
161
|
+
}
|
|
141
162
|
return null;
|
|
142
163
|
}
|
|
143
164
|
return this.toChild(child, notification, toolCallId);
|
|
@@ -157,6 +178,22 @@ export class NativeSubagentRuntime {
|
|
|
157
178
|
await this.resumeChild(known, task.taskId, promptText(task.prompt), task.toolUseId ?? undefined, deliver);
|
|
158
179
|
return;
|
|
159
180
|
}
|
|
181
|
+
// The SDK starts an agent from before a restart under the id of the
|
|
182
|
+
// SendMessage call that resumed it, as in the RFD form (see `restore`).
|
|
183
|
+
// Its output comes under the id of the call that launched it.
|
|
184
|
+
if (this.form === "air" &&
|
|
185
|
+
task.subagentType &&
|
|
186
|
+
task.toolUseId &&
|
|
187
|
+
this.senderByToolUse.has(task.toolUseId) &&
|
|
188
|
+
!this.children.has(task.taskId)) {
|
|
189
|
+
await this.restoreAgent(task.taskId, deliver, promptText(task.prompt));
|
|
190
|
+
const restored = this.children.get(task.taskId);
|
|
191
|
+
if (restored) {
|
|
192
|
+
this.taskByToolUse.set(task.toolUseId, task.taskId);
|
|
193
|
+
this.childByParentToolUse.set(task.toolUseId, restored);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
160
197
|
if (!task.subagentType) {
|
|
161
198
|
if (task.toolUseId) {
|
|
162
199
|
this.takePending(task.toolUseId);
|
|
@@ -193,13 +230,19 @@ export class NativeSubagentRuntime {
|
|
|
193
230
|
* In the RFD form, the running patch only reports that the child runs: its
|
|
194
231
|
* prompt is a guess (the latest SendMessage to the agent), and the
|
|
195
232
|
* `task_started` or the SendMessage result carries the delegation.
|
|
233
|
+
*
|
|
234
|
+
* In the AIR form, an agent that the runtime does not know is restored from
|
|
235
|
+
* the session's history (see {@link restoreAgent}).
|
|
196
236
|
*/
|
|
197
237
|
async taskResumed(taskId, deliver, prompt) {
|
|
198
238
|
if (!this.enabled)
|
|
199
239
|
return;
|
|
200
240
|
const previous = this.children.get(taskId);
|
|
201
|
-
if (!previous)
|
|
241
|
+
if (!previous) {
|
|
242
|
+
if (this.form === "air")
|
|
243
|
+
await this.restoreAgent(taskId, deliver, prompt);
|
|
202
244
|
return;
|
|
245
|
+
}
|
|
203
246
|
const finishing = this.taskFinishPromises.get(taskId) ?? previous.terminalPromise;
|
|
204
247
|
if (finishing)
|
|
205
248
|
await finishing.catch(() => { });
|
|
@@ -350,6 +393,7 @@ export class NativeSubagentRuntime {
|
|
|
350
393
|
this.identityByToolUse.clear();
|
|
351
394
|
this.controlByToolUse.clear();
|
|
352
395
|
this.parentByToolUse.clear();
|
|
396
|
+
this.forgetRestores();
|
|
353
397
|
}
|
|
354
398
|
if (errors.length === 1)
|
|
355
399
|
throw errors[0];
|
|
@@ -496,6 +540,105 @@ export class NativeSubagentRuntime {
|
|
|
496
540
|
}
|
|
497
541
|
return this.children.get(agentId);
|
|
498
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* The AIR form: whether no call of this process made `launchId`. A new Agent
|
|
545
|
+
* call has a control update before its `task_started`, and a launch that was
|
|
546
|
+
* restored or looked up is registered or remembered.
|
|
547
|
+
*/
|
|
548
|
+
isUnseenLaunch(launchId) {
|
|
549
|
+
return (!!this.launchedAgent && !this.unresolvedLaunches.has(launchId) && this.isUnseenCall(launchId));
|
|
550
|
+
}
|
|
551
|
+
/** Whether this runtime has no record of the tool call `toolUseId`. */
|
|
552
|
+
isUnseenCall(toolUseId) {
|
|
553
|
+
return (!this.taskByToolUse.has(toolUseId) &&
|
|
554
|
+
!this.childByParentToolUse.has(toolUseId) &&
|
|
555
|
+
!this.controlByToolUse.has(toolUseId) &&
|
|
556
|
+
!this.parentByToolUse.has(toolUseId) &&
|
|
557
|
+
!this.identityByToolUse.has(toolUseId));
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* The AIR form: restores the agent that the call `launchId` launched, once.
|
|
561
|
+
* Concurrent callers wait for the same lookup. Without an agent, the updates
|
|
562
|
+
* buffered under `launchId` stay buffered and `launchId` is not looked up again.
|
|
563
|
+
*/
|
|
564
|
+
restoreLaunch(launchId, deliver) {
|
|
565
|
+
const existing = this.launchRestores.get(launchId);
|
|
566
|
+
if (existing)
|
|
567
|
+
return existing;
|
|
568
|
+
const epoch = this.restoreEpoch;
|
|
569
|
+
const restoring = (async () => {
|
|
570
|
+
const agentId = await this.launchedAgent?.(launchId).catch((error) => {
|
|
571
|
+
this.logger.log(`Session ${this.rootSessionId}: failed to read the history of launch ${launchId}: ${error}`);
|
|
572
|
+
return undefined;
|
|
573
|
+
});
|
|
574
|
+
if (epoch !== this.restoreEpoch)
|
|
575
|
+
return;
|
|
576
|
+
if (agentId)
|
|
577
|
+
await this.restoreAgent(agentId, deliver);
|
|
578
|
+
if (epoch !== this.restoreEpoch)
|
|
579
|
+
return;
|
|
580
|
+
const child = this.childByParentToolUse.get(launchId);
|
|
581
|
+
if (!child) {
|
|
582
|
+
this.unresolvedLaunches.add(launchId);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
// Updates that arrived while the generation was announced.
|
|
586
|
+
if (child.announced)
|
|
587
|
+
for (const pending of this.takePending(launchId))
|
|
588
|
+
await deliver(pending);
|
|
589
|
+
})().finally(() => {
|
|
590
|
+
if (this.launchRestores.get(launchId) === restoring)
|
|
591
|
+
this.launchRestores.delete(launchId);
|
|
592
|
+
});
|
|
593
|
+
this.launchRestores.set(launchId, restoring);
|
|
594
|
+
return restoring;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* The AIR form: opens a generation for the agent `agentId` that the runtime
|
|
598
|
+
* does not know, because it ran before the session was loaded or resumed.
|
|
599
|
+
* The session's subagent history (see {@link SubagentLineage}) gives the
|
|
600
|
+
* call that launched it: Claude Code sends the resumed run's messages under
|
|
601
|
+
* that call's id, so it is the generation's parent tool use, and the updates
|
|
602
|
+
* buffered under it are delivered. The parent is the agent that made the call
|
|
603
|
+
* if it is live, else the root. An agent that the history cannot place is
|
|
604
|
+
* not restored. Concurrent signals for one agent open one generation.
|
|
605
|
+
*/
|
|
606
|
+
restoreAgent(agentId, deliver, prompt) {
|
|
607
|
+
if (!this.lineage || this.children.has(agentId) || this.unrestorableAgents.has(agentId)) {
|
|
608
|
+
return Promise.resolve();
|
|
609
|
+
}
|
|
610
|
+
const existing = this.agentRestores.get(agentId);
|
|
611
|
+
if (existing)
|
|
612
|
+
return existing;
|
|
613
|
+
const lineage = this.lineage;
|
|
614
|
+
const epoch = this.restoreEpoch;
|
|
615
|
+
const restoring = (async () => {
|
|
616
|
+
const entries = await lineage(agentId).catch((error) => {
|
|
617
|
+
this.logger.log(`Session ${this.rootSessionId}: failed to read the history of subagent ${agentId}: ${error}`);
|
|
618
|
+
return [];
|
|
619
|
+
});
|
|
620
|
+
if (epoch !== this.restoreEpoch || this.children.has(agentId))
|
|
621
|
+
return;
|
|
622
|
+
const entry = entries.at(-1);
|
|
623
|
+
if (!entry || entry.agentId !== agentId) {
|
|
624
|
+
this.unrestorableAgents.add(agentId);
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
const parent = entry.parentAgentId ? this.children.get(entry.parentAgentId) : undefined;
|
|
628
|
+
await this.openGeneration(agentId, undefined, {
|
|
629
|
+
parentSessionId: parent && this.isLiveSession(parent.sessionId) ? parent.sessionId : this.rootSessionId,
|
|
630
|
+
parentToolUseId: entry.launchId,
|
|
631
|
+
name: subagentDisplayName(entry.name, entry.description, entry.agentType, agentId),
|
|
632
|
+
task: subagentDescription(undefined, entry.description),
|
|
633
|
+
...promptField(promptText(prompt)),
|
|
634
|
+
}, true, deliver, true);
|
|
635
|
+
})().finally(() => {
|
|
636
|
+
if (this.agentRestores.get(agentId) === restoring)
|
|
637
|
+
this.agentRestores.delete(agentId);
|
|
638
|
+
});
|
|
639
|
+
this.agentRestores.set(agentId, restoring);
|
|
640
|
+
return restoring;
|
|
641
|
+
}
|
|
499
642
|
/**
|
|
500
643
|
* The RFD form: the child runs again, and keeps its session. `toolUseId` is
|
|
501
644
|
* the call that delegated, usually a SendMessage: the SDK runs the resumed
|
|
@@ -668,6 +811,15 @@ export class NativeSubagentRuntime {
|
|
|
668
811
|
this.generationByTaskId.clear();
|
|
669
812
|
this.pending.clear();
|
|
670
813
|
this.pendingCount = 0;
|
|
814
|
+
this.forgetRestores();
|
|
815
|
+
}
|
|
816
|
+
/** Lets a later resume signal restore an agent again, and look up a launch again. */
|
|
817
|
+
forgetRestores() {
|
|
818
|
+
this.agentRestores.clear();
|
|
819
|
+
this.launchRestores.clear();
|
|
820
|
+
this.unresolvedLaunches.clear();
|
|
821
|
+
this.unrestorableAgents.clear();
|
|
822
|
+
this.restoreEpoch++;
|
|
671
823
|
}
|
|
672
824
|
takePending(parentToolUseId) {
|
|
673
825
|
const updates = this.pending.get(parentToolUseId) ?? [];
|
|
@@ -716,9 +868,9 @@ export class NativeSubagentRuntime {
|
|
|
716
868
|
* parent tool call. With `announce`, it publishes `subagent_spawned` and
|
|
717
869
|
* delivers the updates that waited for the child.
|
|
718
870
|
*/
|
|
719
|
-
async openGeneration(taskId, previous, fields, announce, deliver) {
|
|
871
|
+
async openGeneration(taskId, previous, fields, announce, deliver, restored = false) {
|
|
720
872
|
const child = {
|
|
721
|
-
sessionId: this.nextChildSessionId(taskId, previous),
|
|
873
|
+
sessionId: this.nextChildSessionId(taskId, previous, restored),
|
|
722
874
|
...fields,
|
|
723
875
|
};
|
|
724
876
|
const toolUseId = child.parentToolUseId;
|
|
@@ -743,8 +895,21 @@ export class NativeSubagentRuntime {
|
|
|
743
895
|
* `subagents/` history, so a fork cannot resume an agent of the original.
|
|
744
896
|
* If a fork ever can, the RFD requires remapping the copied ids, for
|
|
745
897
|
* example by deriving the child id from the root session id.
|
|
898
|
+
*
|
|
899
|
+
* An agent restored from the history (`restored`) is not on its first
|
|
900
|
+
* generation: before the restart the client already stored ids such as
|
|
901
|
+
* `taskId` and `taskId:generation:2`. Its generation is the current time in
|
|
902
|
+
* milliseconds, so it is `taskId:generation:1760000000000`, and each later
|
|
903
|
+
* resume in this process adds one.
|
|
746
904
|
*/
|
|
747
|
-
nextChildSessionId(taskId, previous) {
|
|
905
|
+
nextChildSessionId(taskId, previous, restored = false) {
|
|
906
|
+
if (restored) {
|
|
907
|
+
// A clear() and a new restore within one millisecond must not repeat an id.
|
|
908
|
+
const generation = Math.max(Date.now(), this.lastRestoredGeneration + 1);
|
|
909
|
+
this.lastRestoredGeneration = generation;
|
|
910
|
+
this.generationByTaskId.set(taskId, generation);
|
|
911
|
+
return `${taskId}:generation:${generation}`;
|
|
912
|
+
}
|
|
748
913
|
if (!previous) {
|
|
749
914
|
this.generationByTaskId.set(taskId, 1);
|
|
750
915
|
return taskId;
|
|
@@ -34,6 +34,11 @@ export interface SubagentHistory {
|
|
|
34
34
|
* The ids are empty for a session without a local transcript.
|
|
35
35
|
*/
|
|
36
36
|
export declare function subagentHistory(sessionId: string): Promise<SubagentHistory>;
|
|
37
|
+
/**
|
|
38
|
+
* The agent that the Agent or Task call `launchId` launched, from the subagent
|
|
39
|
+
* history of the session `sessionId`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function subagentLaunchedBy(sessionId: string, launchId: string): Promise<string | undefined>;
|
|
37
42
|
/**
|
|
38
43
|
* The agent `agentId` and its ancestors, root first, from the subagent history
|
|
39
44
|
* of the session `sessionId`. The runtime restores an agent from it when Claude
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"subagent-history.d.ts","sourceRoot":"","sources":["../src/subagent-history.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EACpB,MAAM,gCAAgC,CAAC;AAKxC,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG7F,0DAA0D;AAC1D,MAAM,WAAW,cAAc;IAC7B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uDAAuD;IACvD,UAAU,EAAE,OAAO,CAAC;IACpB,mCAAmC;IACnC,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,qEAAqE;IACrE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACpC;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC,CAwCjF;AAOD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAE7B;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAmD/B;AAED,gFAAgF;AAChF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,CAW5D;AAkDD;;;;;GAKG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE;IAAE,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAAE,GAC1C,OAAO,CAAC,cAAc,EAAE,CAAC,CAW3B"}
|
package/dist/subagent-history.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { getSessionInfo,
|
|
1
|
+
import { getSessionInfo, getSubagentMessages, } from "@anthropic-ai/claude-agent-sdk";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
2
3
|
import { readdir, readFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
4
6
|
import { isNativeSubagentControlTool } from "./native-subagents.js";
|
|
5
7
|
import { findTranscript } from "./resumed-session.js";
|
|
6
8
|
/**
|
|
@@ -16,7 +18,7 @@ export async function subagentHistory(sessionId) {
|
|
|
16
18
|
const transcript = await findTranscript(sessionId);
|
|
17
19
|
if (!transcript)
|
|
18
20
|
return { agents };
|
|
19
|
-
const directory =
|
|
21
|
+
const directory = subagentsDirectory(transcript, sessionId);
|
|
20
22
|
let names;
|
|
21
23
|
try {
|
|
22
24
|
names = await readdir(directory);
|
|
@@ -53,6 +55,17 @@ export async function subagentHistory(sessionId) {
|
|
|
53
55
|
const dir = (await getSessionInfo(sessionId).catch(() => undefined))?.cwd;
|
|
54
56
|
return dir ? { agents, dir } : { agents };
|
|
55
57
|
}
|
|
58
|
+
/** The folder of the subagent transcripts, next to the session transcript `transcript`. */
|
|
59
|
+
function subagentsDirectory(transcript, sessionId) {
|
|
60
|
+
return path.join(path.dirname(transcript), sessionId, "subagents");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The agent that the Agent or Task call `launchId` launched, from the subagent
|
|
64
|
+
* history of the session `sessionId`.
|
|
65
|
+
*/
|
|
66
|
+
export async function subagentLaunchedBy(sessionId, launchId) {
|
|
67
|
+
return (await subagentHistory(sessionId)).agents.get(launchId)?.agentId;
|
|
68
|
+
}
|
|
56
69
|
/**
|
|
57
70
|
* The agent `agentId` and its ancestors, root first, from the subagent history
|
|
58
71
|
* of the session `sessionId`. The runtime restores an agent from it when Claude
|
|
@@ -72,11 +85,12 @@ export async function subagentLineage(sessionId, agentId, logger) {
|
|
|
72
85
|
}
|
|
73
86
|
if (!launchOf.has(agentId))
|
|
74
87
|
return [];
|
|
75
|
-
//
|
|
76
|
-
|
|
77
|
-
|
|
88
|
+
// The SDK's readers return only the active chain, which starts after the
|
|
89
|
+
// last compaction, so the launch calls come from the transcript files.
|
|
90
|
+
const transcript = await findTranscript(sessionId);
|
|
91
|
+
if (!transcript)
|
|
78
92
|
return [];
|
|
79
|
-
|
|
93
|
+
const rootLaunches = await readLaunchIdsFromTranscript(transcript, null, logger);
|
|
80
94
|
const agentLaunches = new Map();
|
|
81
95
|
/** The agent whose transcript has the launch `launchId`, `null` for the root's. */
|
|
82
96
|
const launcherOf = async (launchId) => {
|
|
@@ -85,7 +99,7 @@ export async function subagentLineage(sessionId, agentId, logger) {
|
|
|
85
99
|
for (const candidate of launchOf.keys()) {
|
|
86
100
|
let launches = agentLaunches.get(candidate);
|
|
87
101
|
if (!launches) {
|
|
88
|
-
launches =
|
|
102
|
+
launches = await readLaunchIdsFromTranscript(path.join(subagentsDirectory(transcript, sessionId), `agent-${candidate}.jsonl`), candidate, logger);
|
|
89
103
|
agentLaunches.set(candidate, launches);
|
|
90
104
|
}
|
|
91
105
|
if (launches.has(launchId))
|
|
@@ -127,8 +141,47 @@ export function subagentLaunchIds(content) {
|
|
|
127
141
|
? [block.id]
|
|
128
142
|
: []);
|
|
129
143
|
}
|
|
130
|
-
|
|
131
|
-
|
|
144
|
+
/**
|
|
145
|
+
* The ids of the launch calls in the transcript `file`, every record of it,
|
|
146
|
+
* including those before a compaction. `owner` is the agent the transcript
|
|
147
|
+
* belongs to, null for the session's. The session's transcript can hold
|
|
148
|
+
* sidechain records, which are an agent's, not the root's; an agent's
|
|
149
|
+
* transcript keeps only the records of that agent. A file that cannot be read
|
|
150
|
+
* has no launches, so the agent is not placed under the root.
|
|
151
|
+
*/
|
|
152
|
+
async function readLaunchIdsFromTranscript(file, owner, logger) {
|
|
153
|
+
const launches = new Set();
|
|
154
|
+
const stream = createReadStream(file, { encoding: "utf8" });
|
|
155
|
+
try {
|
|
156
|
+
for await (const line of readline.createInterface({ input: stream, crlfDelay: Infinity })) {
|
|
157
|
+
if (!line.includes('"tool_use"'))
|
|
158
|
+
continue;
|
|
159
|
+
let record;
|
|
160
|
+
try {
|
|
161
|
+
record = JSON.parse(line);
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (record?.type !== "assistant")
|
|
167
|
+
continue;
|
|
168
|
+
if (owner === null
|
|
169
|
+
? record.isSidechain === true
|
|
170
|
+
: record.agentId !== undefined && record.agentId !== owner) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
for (const id of subagentLaunchIds(record.message?.content))
|
|
174
|
+
launches.add(id);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
logger.error(`Failed to read the transcript ${file}:`, error);
|
|
179
|
+
return new Set();
|
|
180
|
+
}
|
|
181
|
+
finally {
|
|
182
|
+
stream.destroy();
|
|
183
|
+
}
|
|
184
|
+
return launches;
|
|
132
185
|
}
|
|
133
186
|
/**
|
|
134
187
|
* The transcript of the subagent `agentId` of a session, or an empty list
|
package/dist/v2/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAkB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAoB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../../src/v2/agent.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,EAAE,cAAc,EAAkB,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAoB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,CAkFb"}
|
package/dist/v2/agent.js
CHANGED
|
@@ -50,6 +50,13 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
50
50
|
const response = restore.method === "resume"
|
|
51
51
|
? await agent.resumeSession(restore.request)
|
|
52
52
|
: await client.replaying(params.sessionId, () => agent.loadSession(restore.request));
|
|
53
|
+
// v2 requires a snapshot of the foreground state after the replay and
|
|
54
|
+
// before the response, also without a replay: a resumed live session can
|
|
55
|
+
// be running a turn, or waiting on a permission request.
|
|
56
|
+
void client.send({
|
|
57
|
+
sessionId: params.sessionId,
|
|
58
|
+
update: stateSnapshot(agent.foregroundState(params.sessionId)),
|
|
59
|
+
});
|
|
53
60
|
return {
|
|
54
61
|
...v2ResumeSessionResponse(response),
|
|
55
62
|
...(await setupCommands(agent, params.sessionId, logger)),
|
|
@@ -80,6 +87,14 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
80
87
|
}))
|
|
81
88
|
.onNotification(v2.methods.agent.session.cancel, ({ params }) => agent.cancel(params));
|
|
82
89
|
}
|
|
90
|
+
/** The `state_update` that reports `state` as the current foreground state. */
|
|
91
|
+
function stateSnapshot(state) {
|
|
92
|
+
if (state === "idle")
|
|
93
|
+
return { sessionUpdate: "state_update", state: "idle" };
|
|
94
|
+
if (state === "running")
|
|
95
|
+
return { sessionUpdate: "state_update", state: "running" };
|
|
96
|
+
return { sessionUpdate: "state_update", state: "requires_action" };
|
|
97
|
+
}
|
|
83
98
|
/**
|
|
84
99
|
* The `availableCommands` of a setup response, so a client can show the
|
|
85
100
|
* session's commands without waiting for an update. The agent sends a v1
|
|
@@ -113,6 +128,13 @@ class V2ClientConnection {
|
|
|
113
128
|
replays = new Map();
|
|
114
129
|
/** The session whose replay announced each replaying subagent. */
|
|
115
130
|
replayRoots = new Map();
|
|
131
|
+
/**
|
|
132
|
+
* The last work state that a replay reported for each subagent, as the
|
|
133
|
+
* `state_update` of the child's own stream. It goes out once the replay
|
|
134
|
+
* ends: v2 forbids replaying a child's past `running` and `idle` as current
|
|
135
|
+
* activity.
|
|
136
|
+
*/
|
|
137
|
+
replayedChildStates = new Map();
|
|
116
138
|
/** The display terminals of the commands that the agent runs. */
|
|
117
139
|
terminals = new V2Terminals();
|
|
118
140
|
/** The `_meta` that the client stores for each tool call. */
|
|
@@ -128,19 +150,28 @@ class V2ClientConnection {
|
|
|
128
150
|
* replayed message, and chunks append, so before the first replayed chunk of
|
|
129
151
|
* each message the client gets the message with no content, which clears it,
|
|
130
152
|
* as v2 requires of a message replayed as chunks.
|
|
153
|
+
*
|
|
154
|
+
* Each replayed subagent gets one `state_update` on its own stream, with its
|
|
155
|
+
* last state, after the replay.
|
|
131
156
|
*/
|
|
132
157
|
async replaying(sessionId, restore) {
|
|
133
158
|
this.replays.set(sessionId, new Set());
|
|
159
|
+
const children = () => [...this.replayRoots].filter(([, root]) => root === sessionId).map(([child]) => child);
|
|
134
160
|
try {
|
|
135
|
-
|
|
161
|
+
const result = await restore();
|
|
162
|
+
for (const child of children()) {
|
|
163
|
+
const state = this.replayedChildStates.get(child);
|
|
164
|
+
if (state)
|
|
165
|
+
void this.send(state);
|
|
166
|
+
}
|
|
167
|
+
return result;
|
|
136
168
|
}
|
|
137
169
|
finally {
|
|
138
170
|
this.replays.delete(sessionId);
|
|
139
|
-
for (const
|
|
140
|
-
if (root !== sessionId)
|
|
141
|
-
continue;
|
|
171
|
+
for (const child of children()) {
|
|
142
172
|
this.replays.delete(child);
|
|
143
173
|
this.replayRoots.delete(child);
|
|
174
|
+
this.replayedChildStates.delete(child);
|
|
144
175
|
}
|
|
145
176
|
}
|
|
146
177
|
}
|
|
@@ -178,8 +209,13 @@ class V2ClientConnection {
|
|
|
178
209
|
return clear ? [clear, v2Update] : [v2Update];
|
|
179
210
|
});
|
|
180
211
|
// A subagent's work state also goes to the child's own stream, after the
|
|
181
|
-
// parent's copy.
|
|
182
|
-
|
|
212
|
+
// parent's copy. During a replay, only its last state goes, after the
|
|
213
|
+
// replay (see `replayedChildStates`).
|
|
214
|
+
let childState = v2ChildStateUpdate(update);
|
|
215
|
+
if (childState && this.replays.has(notification.sessionId)) {
|
|
216
|
+
this.replayedChildStates.set(childState.sessionId, childState);
|
|
217
|
+
childState = undefined;
|
|
218
|
+
}
|
|
183
219
|
// Every update goes to the connection before any is awaited, so an update
|
|
184
220
|
// that another call sends meanwhile cannot come between them.
|
|
185
221
|
await Promise.all([
|
|
@@ -190,7 +226,8 @@ class V2ClientConnection {
|
|
|
190
226
|
/**
|
|
191
227
|
* The v2 updates of a v1 update: for a tool call report, the updates of its
|
|
192
228
|
* terminal first (see {@link V2Terminals}), then the report itself, with the
|
|
193
|
-
* `_meta` that
|
|
229
|
+
* `_meta` keys that it changes (see {@link V2ToolCallMeta}), and during a
|
|
230
|
+
* replay the `_meta` of a tool call that the report cleared.
|
|
194
231
|
*/
|
|
195
232
|
v2Updates(sessionId, update) {
|
|
196
233
|
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
|
@@ -201,8 +238,16 @@ class V2ClientConnection {
|
|
|
201
238
|
if (!report)
|
|
202
239
|
return terminal;
|
|
203
240
|
const starts = report.sessionUpdate === "tool_call";
|
|
204
|
-
const
|
|
205
|
-
|
|
241
|
+
const [reported, metaPatch] = this.toolCallMeta.apply(sessionId, report, starts, this.replays.has(sessionId));
|
|
242
|
+
const reportUpdate = v2SessionUpdate(reported);
|
|
243
|
+
const metaUpdate = metaPatch
|
|
244
|
+
? v2SessionUpdate({ sessionUpdate: "tool_call_update", ...metaPatch })
|
|
245
|
+
: undefined;
|
|
246
|
+
return [
|
|
247
|
+
...terminal,
|
|
248
|
+
...(reportUpdate ? [reportUpdate] : []),
|
|
249
|
+
...(metaUpdate ? [metaUpdate] : []),
|
|
250
|
+
];
|
|
206
251
|
}
|
|
207
252
|
/**
|
|
208
253
|
* The update that clears the message of a replayed chunk, when it is the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;
|
|
1
|
+
{"version":3,"file":"initialize.d.ts","sourceRoot":"","sources":["../../src/v2/initialize.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAGL,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACxB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAG/D,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAmCpF;AASD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAuBxF"}
|
package/dist/v2/initialize.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { PROTOCOL_VERSION as V1_PROTOCOL_VERSION, } from "@agentclientprotocol/sdk";
|
|
11
11
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
12
|
+
import { SYSTEM_PROMPT_CAPABILITY } from "../client-system-prompt.js";
|
|
12
13
|
/** The v1 `initialize` request that a v2 client means. */
|
|
13
14
|
export function v1InitializeRequest(request) {
|
|
14
15
|
const capabilities = request.capabilities;
|
|
@@ -91,6 +92,7 @@ export function v2InitializeResponse(response) {
|
|
|
91
92
|
* - The prompt content and MCP transports that the agent's v1 `initialize`
|
|
92
93
|
* lists: images, embedded context, and HTTP servers. Stdio servers are in the
|
|
93
94
|
* v1 baseline; v2 has no SSE transport.
|
|
95
|
+
* - `systemPrompt`, in `append` and `override` mode.
|
|
94
96
|
*/
|
|
95
97
|
const V2_SESSION_CAPABILITIES = {
|
|
96
98
|
prompt: { image: {}, embeddedContext: {} },
|
|
@@ -98,6 +100,7 @@ const V2_SESSION_CAPABILITIES = {
|
|
|
98
100
|
delete: {},
|
|
99
101
|
fork: {},
|
|
100
102
|
additionalDirectories: {},
|
|
103
|
+
systemPrompt: SYSTEM_PROMPT_CAPABILITY,
|
|
101
104
|
};
|
|
102
105
|
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
|
|
103
106
|
function v2AuthMethod(method) {
|
package/dist/v2/prompt.d.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* v2 answers the prompt once Claude Code takes it in, with the id of the user
|
|
5
5
|
* message it became, and reports the rest of the turn as `state_update`s.
|
|
6
6
|
*/
|
|
7
|
-
import { type PromptRequest } from "@agentclientprotocol/sdk";
|
|
7
|
+
import { type ContentBlock, type PromptRequest } from "@agentclientprotocol/sdk";
|
|
8
8
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
9
9
|
import type { ClaudeAcpAgent } from "../acp-agent.js";
|
|
10
10
|
import type { ForegroundEvents } from "../turn-events.js";
|
|
@@ -50,4 +50,5 @@ export declare function v2Foreground(send: (update: v2.SessionUpdate) => void):
|
|
|
50
50
|
* advertised, so such a block is rejected rather than silently dropped.
|
|
51
51
|
*/
|
|
52
52
|
export declare function v1PromptRequest({ prompt, ...request }: v2.PromptRequest): PromptRequest;
|
|
53
|
+
export declare function v1ContentBlock(block: v2.ContentBlock): ContentBlock;
|
|
53
54
|
//# sourceMappingURL=prompt.d.ts.map
|
package/dist/v2/prompt.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/v2/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/v2/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAGL,KAAK,YAAY,EACjB,KAAK,aAAa,EAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAuC,MAAM,mBAAmB,CAAC;AAE/F;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,cAAc,EACrB,MAAM,EAAE,EAAE,CAAC,aAAa,EACxB,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,KAAK,IAAI,GACvC,OAAO,CAAC,EAAE,CAAC,cAAc,CAAC,CAiC5B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,aAAa,KAAK,IAAI,GAAG,gBAAgB,CAoBvF;AAgCD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,aAAa,GAAG,aAAa,CAEvF;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,GAAG,YAAY,CAenE"}
|
package/dist/v2/prompt.js
CHANGED
|
@@ -129,7 +129,7 @@ function idle({ stopReason, usage, _meta }) {
|
|
|
129
129
|
export function v1PromptRequest({ prompt, ...request }) {
|
|
130
130
|
return { ...request, prompt: prompt.map(v1ContentBlock) };
|
|
131
131
|
}
|
|
132
|
-
function v1ContentBlock(block) {
|
|
132
|
+
export function v1ContentBlock(block) {
|
|
133
133
|
if (v2.ContentBlock.isText(block) ||
|
|
134
134
|
v2.ContentBlock.isImage(block) ||
|
|
135
135
|
v2.ContentBlock.isAudio(block) ||
|
package/dist/v2/session.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/v2/session.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAEL,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EAEvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EAEnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAG/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAQnF;AAED;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,EAAE,CAAC,oBAAoB,GAE7B;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,OAAO,EAAE,oBAAoB,CAAA;CAAE,GACnD;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,CAmBlD;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,GAAG,kBAAkB,CAOtF;AAaD,oGAAoG;AACpG,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAExF;AAED,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,qBAAqB,GAAG,EAAE,CAAC,qBAAqB,CAKjG;AAED,uEAAuE;AACvE,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,EAAE,CAAC,6BAA6B,GACvC,6BAA6B,CAgB/B;AAED,sFAAsF;AACtF,wBAAgB,eAAe,CAAC,OAAO,EAAE,mBAAmB,EAAE,GAAG,EAAE,CAAC,mBAAmB,EAAE,CAQxF;AAWD,oEAAoE;AACpE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC,gBAAgB,EAAE,CAKvF"}
|
package/dist/v2/session.js
CHANGED
|
@@ -5,10 +5,15 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { RequestError, } from "@agentclientprotocol/sdk";
|
|
7
7
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
8
|
+
import { v1ContentBlock } from "./prompt.js";
|
|
8
9
|
export function v1NewSessionRequest(params) {
|
|
9
|
-
const { mcpServers, ...request } = params;
|
|
10
|
+
const { mcpServers, systemPrompt, ...request } = params;
|
|
10
11
|
// v1 requires the list; v2 treats an omitted list as an empty one.
|
|
11
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
...request,
|
|
14
|
+
mcpServers: (mcpServers ?? []).map(v1McpServer),
|
|
15
|
+
...v1SystemPromptField(systemPrompt),
|
|
16
|
+
};
|
|
12
17
|
}
|
|
13
18
|
/**
|
|
14
19
|
* The v1 request that a v2 `session/resume` means.
|
|
@@ -20,7 +25,8 @@ export function v1NewSessionRequest(params) {
|
|
|
20
25
|
* starts elsewhere is rejected.
|
|
21
26
|
*/
|
|
22
27
|
export function v1RestoreSessionRequest(params) {
|
|
23
|
-
const { replayFrom, mcpServers, ...
|
|
28
|
+
const { replayFrom, mcpServers, systemPrompt, ...rest } = params;
|
|
29
|
+
const request = { ...rest, ...v1SystemPromptField(systemPrompt) };
|
|
24
30
|
if (replayFrom == null) {
|
|
25
31
|
return {
|
|
26
32
|
method: "resume",
|
|
@@ -40,8 +46,21 @@ export function v1RestoreSessionRequest(params) {
|
|
|
40
46
|
* returns a session that the client can prompt, with its config options.
|
|
41
47
|
*/
|
|
42
48
|
export function v1ForkSessionRequest(params) {
|
|
43
|
-
const { mcpServers, ...request } = params;
|
|
44
|
-
return {
|
|
49
|
+
const { mcpServers, systemPrompt, ...request } = params;
|
|
50
|
+
return {
|
|
51
|
+
...request,
|
|
52
|
+
...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}),
|
|
53
|
+
...v1SystemPromptField(systemPrompt),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The v1 `systemPrompt`. Its content converts like prompt content: a v2
|
|
58
|
+
* annotation audience can be any string, and v1 takes only `user` or `assistant`.
|
|
59
|
+
*/
|
|
60
|
+
function v1SystemPromptField(systemPrompt) {
|
|
61
|
+
if (systemPrompt == null)
|
|
62
|
+
return {};
|
|
63
|
+
return { systemPrompt: { ...systemPrompt, content: systemPrompt.content.map(v1ContentBlock) } };
|
|
45
64
|
}
|
|
46
65
|
/** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
|
|
47
66
|
export function v2NewSessionResponse(response) {
|