@agentclientprotocol/claude-agent-acp 0.85.2-preview.2 → 0.85.2-preview.21
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/README.md +1 -0
- package/dist/acp-agent.d.ts +100 -23
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +528 -183
- package/dist/air-extension.d.ts +2 -0
- package/dist/air-extension.d.ts.map +1 -1
- package/dist/air-extension.js +2 -0
- package/dist/clear-context-coordinator.d.ts +1 -0
- package/dist/clear-context-coordinator.d.ts.map +1 -1
- package/dist/clear-context-coordinator.js +2 -1
- package/dist/diff.d.ts +45 -8
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +111 -30
- package/dist/mcp-command.d.ts +51 -0
- package/dist/mcp-command.d.ts.map +1 -0
- package/dist/mcp-command.js +321 -0
- package/dist/permissions/presentation.d.ts +17 -2
- package/dist/permissions/presentation.d.ts.map +1 -1
- package/dist/permissions/presentation.js +4 -0
- package/dist/resumed-session.d.ts +16 -6
- package/dist/resumed-session.d.ts.map +1 -1
- package/dist/resumed-session.js +91 -25
- package/dist/session-config-ids.d.ts +1 -0
- package/dist/session-config-ids.d.ts.map +1 -1
- package/dist/session-config-ids.js +1 -0
- package/dist/session-mode.d.ts +14 -0
- package/dist/session-mode.d.ts.map +1 -1
- package/dist/session-mode.js +22 -0
- package/dist/tool-calls/client-capabilities.d.ts +20 -2
- package/dist/tool-calls/client-capabilities.d.ts.map +1 -1
- package/dist/tool-calls/client-capabilities.js +14 -4
- package/dist/tool-calls/facts.d.ts +8 -1
- package/dist/tool-calls/facts.d.ts.map +1 -1
- package/dist/tool-calls/renderer.d.ts +5 -1
- package/dist/tool-calls/renderer.d.ts.map +1 -1
- package/dist/tool-calls/renderer.js +14 -3
- package/dist/tool-calls/reporters/bash.d.ts.map +1 -1
- package/dist/tool-calls/reporters/bash.js +32 -5
- package/dist/tool-calls/reporters/file-edit.d.ts +1 -1
- package/dist/tool-calls/reporters/file-edit.d.ts.map +1 -1
- package/dist/tool-calls/reporters/file-edit.js +20 -14
- package/dist/turn-events.d.ts +11 -5
- package/dist/turn-events.d.ts.map +1 -1
- package/dist/usage-markdown.d.ts +2 -0
- package/dist/usage-markdown.d.ts.map +1 -1
- package/dist/usage-markdown.js +2 -1
- package/dist/utils.d.ts +12 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +28 -0
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +121 -17
- package/dist/v2/initialize.d.ts +2 -5
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +23 -6
- package/dist/v2/permission.d.ts +25 -0
- package/dist/v2/permission.d.ts.map +1 -0
- package/dist/v2/permission.js +38 -0
- package/dist/v2/prompt.d.ts +47 -0
- package/dist/v2/prompt.d.ts.map +1 -0
- package/dist/v2/prompt.js +153 -0
- package/dist/v2/session-update.d.ts +6 -0
- package/dist/v2/session-update.d.ts.map +1 -1
- package/dist/v2/session-update.js +27 -0
- package/dist/v2/session.d.ts +15 -4
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +20 -6
- package/dist/v2/terminal.d.ts +42 -0
- package/dist/v2/terminal.d.ts.map +1 -0
- package/dist/v2/terminal.js +95 -0
- package/dist/v2/tool-call.d.ts +38 -0
- package/dist/v2/tool-call.d.ts.map +1 -0
- package/dist/v2/tool-call.js +49 -0
- package/package.json +2 -2
package/dist/v2/agent.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
2
2
|
import { ClaudeAcpAgent } from "../acp-agent.js";
|
|
3
3
|
import { v1InitializeRequest, v2InitializeResponse } from "./initialize.js";
|
|
4
|
-
import {
|
|
4
|
+
import { v1PermissionResponse, v2PermissionRequest } from "./permission.js";
|
|
5
|
+
import { v2Prompt } from "./prompt.js";
|
|
6
|
+
import { v1NewSessionRequest, v1RestoreSessionRequest, v1SetSessionConfigOptionRequest, v2ConfigOptions, v2NewSessionResponse, v2ResumeSessionResponse, } from "./session.js";
|
|
5
7
|
import { v2SessionUpdate } from "./session-update.js";
|
|
8
|
+
import { V2Terminals } from "./terminal.js";
|
|
6
9
|
/**
|
|
7
10
|
* The ACP v2 surface, for one connection.
|
|
8
11
|
*
|
|
@@ -12,10 +15,12 @@ import { v2SessionUpdate } from "./session-update.js";
|
|
|
12
15
|
*/
|
|
13
16
|
export function v2AgentApp(logger, onAgent) {
|
|
14
17
|
let agent;
|
|
18
|
+
let client;
|
|
15
19
|
return v2
|
|
16
20
|
.agent({ name: "claude-code-acp" })
|
|
17
21
|
.onConnect((connection) => {
|
|
18
|
-
|
|
22
|
+
client = new V2ClientConnection(connection.client, logger ?? console);
|
|
23
|
+
agent = new ClaudeAcpAgent(client, logger, { v2: true });
|
|
19
24
|
onAgent(agent);
|
|
20
25
|
})
|
|
21
26
|
.onRequest(v2.methods.agent.initialize, async ({ params }) => v2InitializeResponse(await agent.initialize(v1InitializeRequest(params))))
|
|
@@ -23,25 +28,75 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
23
28
|
.onRequest(v2.methods.agent.auth.logout, ({ params }) => agent.logout(params))
|
|
24
29
|
.onRequest(v2.methods.agent.session.new, async ({ params }) => v2NewSessionResponse(await agent.newSession(v1NewSessionRequest(params))))
|
|
25
30
|
.onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
|
|
26
|
-
.onRequest(v2.methods.agent.session.resume, async ({ params }) =>
|
|
27
|
-
|
|
31
|
+
.onRequest(v2.methods.agent.session.resume, async ({ params }) => {
|
|
32
|
+
const restore = v1RestoreSessionRequest(params);
|
|
33
|
+
return v2ResumeSessionResponse(restore.method === "resume"
|
|
34
|
+
? await agent.resumeSession(restore.request)
|
|
35
|
+
: await client.replaying(params.sessionId, () => agent.loadSession(restore.request)));
|
|
36
|
+
})
|
|
37
|
+
.onRequest(v2.methods.agent.session.close, async ({ params }) => {
|
|
38
|
+
const response = await agent.closeSession(params);
|
|
39
|
+
client.terminals.forget(params.sessionId);
|
|
40
|
+
return response;
|
|
41
|
+
})
|
|
28
42
|
.onRequest(v2.methods.agent.session.delete, ({ params }) => agent.deleteSession(params))
|
|
29
43
|
.onRequest(v2.methods.agent.session.setConfigOption, async ({ params }) => {
|
|
30
44
|
const { configOptions, ...response } = await agent.setSessionConfigOption(v1SetSessionConfigOptionRequest(params));
|
|
31
45
|
return { ...response, configOptions: v2ConfigOptions(configOptions) };
|
|
32
|
-
})
|
|
46
|
+
})
|
|
47
|
+
.onRequest(v2.methods.agent.session.prompt, ({ params }) => v2Prompt(agent, params, (update) => {
|
|
48
|
+
void client.send({ sessionId: params.sessionId, update });
|
|
49
|
+
}))
|
|
50
|
+
.onNotification(v2.methods.agent.session.cancel, ({ params }) => agent.cancel(params));
|
|
33
51
|
}
|
|
34
52
|
/**
|
|
35
53
|
* The {@link AcpClient} of an ACP v2 connection: it sends the v1 messages of
|
|
36
54
|
* the agent as v2 messages.
|
|
37
|
-
*
|
|
38
|
-
* The v2 surface does not serve prompts yet, so permission requests, which
|
|
39
|
-
* only a prompt turn makes, still reject.
|
|
40
55
|
*/
|
|
41
56
|
class V2ClientConnection {
|
|
42
57
|
ctx;
|
|
43
|
-
|
|
58
|
+
logger;
|
|
59
|
+
/**
|
|
60
|
+
* The sessions whose history replays, each with the messages that the
|
|
61
|
+
* replay has cleared so far.
|
|
62
|
+
*/
|
|
63
|
+
replays = new Map();
|
|
64
|
+
/** The display terminals of the commands that the agent runs. */
|
|
65
|
+
terminals = new V2Terminals();
|
|
66
|
+
constructor(ctx, logger) {
|
|
44
67
|
this.ctx = ctx;
|
|
68
|
+
this.logger = logger;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Runs `restore`, which replays the history of `sessionId`.
|
|
72
|
+
*
|
|
73
|
+
* The agent replays messages as chunks. A v2 client may still hold a
|
|
74
|
+
* replayed message, and chunks append, so before the first replayed chunk of
|
|
75
|
+
* each message the client gets the message with no content, which clears it,
|
|
76
|
+
* as v2 requires of a message replayed as chunks.
|
|
77
|
+
*/
|
|
78
|
+
async replaying(sessionId, restore) {
|
|
79
|
+
this.replays.set(sessionId, new Set());
|
|
80
|
+
try {
|
|
81
|
+
return await restore();
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
this.replays.delete(sessionId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Sends a v2 session update, and logs rather than rejects when that fails.
|
|
89
|
+
* It and {@link sessionUpdate} hand updates to the connection synchronously,
|
|
90
|
+
* so they go out in the order of the calls. The agent's consumer awaits each
|
|
91
|
+
* of its updates before it reports the next turn event, so a turn's state
|
|
92
|
+
* follows the output that came before it. (An update the consumer is still
|
|
93
|
+
* routing can be overtaken by an event from elsewhere, such as a permission
|
|
94
|
+
* request opening.)
|
|
95
|
+
*/
|
|
96
|
+
send(notification) {
|
|
97
|
+
return this.ctx.notify(v2.methods.client.session.update, notification).catch((error) => {
|
|
98
|
+
this.logger.error(`Failed to send a ${notification.update.sessionUpdate} update:`, error);
|
|
99
|
+
});
|
|
45
100
|
}
|
|
46
101
|
extNotification(method, params) {
|
|
47
102
|
if (!isExtensionMethod(method)) {
|
|
@@ -50,16 +105,48 @@ class V2ClientConnection {
|
|
|
50
105
|
return this.ctx.notify(method, params);
|
|
51
106
|
}
|
|
52
107
|
async sessionUpdate({ update, ...notification }) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
108
|
+
const updates = this.v2Updates(notification.sessionId, update).flatMap((v2Update) => {
|
|
109
|
+
const clear = this.replayClear(notification.sessionId, v2Update);
|
|
110
|
+
return clear ? [clear, v2Update] : [v2Update];
|
|
111
|
+
});
|
|
112
|
+
// Every update goes to the connection before any is awaited, so an update
|
|
113
|
+
// that another call sends meanwhile cannot come between them.
|
|
114
|
+
await Promise.all(updates.map((v2Update) => this.ctx.notify(v2.methods.client.session.update, { ...notification, update: v2Update })));
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* The v2 updates of a v1 update: for a tool call report, the updates of its
|
|
118
|
+
* terminal first, then the report itself (see {@link V2Terminals}).
|
|
119
|
+
*/
|
|
120
|
+
v2Updates(sessionId, update) {
|
|
121
|
+
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
|
122
|
+
const v2Update = v2SessionUpdate(update);
|
|
123
|
+
return v2Update ? [v2Update] : [];
|
|
59
124
|
}
|
|
125
|
+
const { terminal, report } = this.terminals.split(sessionId, update);
|
|
126
|
+
const reportUpdate = report && v2SessionUpdate(report);
|
|
127
|
+
return [...terminal, ...(reportUpdate ? [reportUpdate] : [])];
|
|
60
128
|
}
|
|
61
|
-
|
|
62
|
-
|
|
129
|
+
/**
|
|
130
|
+
* The update that clears the message of a replayed chunk, when it is the
|
|
131
|
+
* first replayed chunk of that message (see {@link replaying}).
|
|
132
|
+
*/
|
|
133
|
+
replayClear(sessionId, update) {
|
|
134
|
+
const cleared = this.replays.get(sessionId);
|
|
135
|
+
if (!cleared)
|
|
136
|
+
return undefined;
|
|
137
|
+
const kind = MESSAGE_OF_CHUNK.get(update.sessionUpdate);
|
|
138
|
+
if (!kind)
|
|
139
|
+
return undefined;
|
|
140
|
+
const { messageId } = update;
|
|
141
|
+
const key = `${kind} ${messageId}`;
|
|
142
|
+
if (cleared.has(key))
|
|
143
|
+
return undefined;
|
|
144
|
+
cleared.add(key);
|
|
145
|
+
return emptyMessage(kind, messageId);
|
|
146
|
+
}
|
|
147
|
+
async requestPermission(params, signal) {
|
|
148
|
+
const response = await this.ctx.request(v2.methods.client.session.requestPermission, v2PermissionRequest(params), { cancellationSignal: signal });
|
|
149
|
+
return v1PermissionResponse(response);
|
|
63
150
|
}
|
|
64
151
|
// Elicitation is the same in v1 and v2.
|
|
65
152
|
createElicitation(params, signal) {
|
|
@@ -81,6 +168,23 @@ class V2ClientConnection {
|
|
|
81
168
|
return Promise.reject(new Error("ACP v2 has no client file system"));
|
|
82
169
|
}
|
|
83
170
|
}
|
|
171
|
+
/** The message update of each message chunk. */
|
|
172
|
+
const MESSAGE_OF_CHUNK = new Map([
|
|
173
|
+
["user_message_chunk", "user_message"],
|
|
174
|
+
["agent_message_chunk", "agent_message"],
|
|
175
|
+
["agent_thought_chunk", "agent_thought"],
|
|
176
|
+
]);
|
|
177
|
+
function emptyMessage(kind, messageId) {
|
|
178
|
+
const message = { messageId, content: [] };
|
|
179
|
+
switch (kind) {
|
|
180
|
+
case "user_message":
|
|
181
|
+
return { ...message, sessionUpdate: kind };
|
|
182
|
+
case "agent_message":
|
|
183
|
+
return { ...message, sessionUpdate: kind };
|
|
184
|
+
default:
|
|
185
|
+
return { ...message, sessionUpdate: kind };
|
|
186
|
+
}
|
|
187
|
+
}
|
|
84
188
|
function isExtensionMethod(method) {
|
|
85
189
|
return method.startsWith("_");
|
|
86
190
|
}
|
package/dist/v2/initialize.d.ts
CHANGED
|
@@ -14,11 +14,8 @@ export declare function v1InitializeRequest(request: v2.InitializeRequest): Init
|
|
|
14
14
|
/**
|
|
15
15
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
16
16
|
*
|
|
17
|
-
* It
|
|
18
|
-
*
|
|
19
|
-
* `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
|
|
20
|
-
* the adapter does not serve it yet. Nor does it advertise the unstable
|
|
21
|
-
* `providers` methods, which the v2 surface does not serve yet either.
|
|
17
|
+
* It does not advertise the unstable `providers` methods, which the v2
|
|
18
|
+
* surface does not serve yet.
|
|
22
19
|
*/
|
|
23
20
|
export declare function v2InitializeResponse(response: InitializeResponse): v2.InitializeResponse;
|
|
24
21
|
//# sourceMappingURL=initialize.d.ts.map
|
|
@@ -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;AAE/D,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAwBpF;AAED
|
|
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;AAE/D,0DAA0D;AAC1D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAwBpF;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAiBxF"}
|
package/dist/v2/initialize.js
CHANGED
|
@@ -38,11 +38,8 @@ export function v1InitializeRequest(request) {
|
|
|
38
38
|
/**
|
|
39
39
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
40
40
|
*
|
|
41
|
-
* It
|
|
42
|
-
*
|
|
43
|
-
* `resume`, `close`, `prompt`, `cancel`, and `update`), and the v2 surface of
|
|
44
|
-
* the adapter does not serve it yet. Nor does it advertise the unstable
|
|
45
|
-
* `providers` methods, which the v2 surface does not serve yet either.
|
|
41
|
+
* It does not advertise the unstable `providers` methods, which the v2
|
|
42
|
+
* surface does not serve yet.
|
|
46
43
|
*/
|
|
47
44
|
export function v2InitializeResponse(response) {
|
|
48
45
|
if (!response.agentInfo) {
|
|
@@ -54,11 +51,31 @@ export function v2InitializeResponse(response) {
|
|
|
54
51
|
info: response.agentInfo,
|
|
55
52
|
// v1 `auth.logout` has no v2 counterpart: a v2 agent that advertises auth
|
|
56
53
|
// methods must serve `auth/logout`.
|
|
57
|
-
capabilities:
|
|
54
|
+
capabilities: {
|
|
55
|
+
session: V2_SESSION_CAPABILITIES,
|
|
56
|
+
...(meta != null ? { _meta: meta } : {}),
|
|
57
|
+
},
|
|
58
58
|
authMethods: (response.authMethods ?? []).map(v2AuthMethod),
|
|
59
59
|
...(response._meta != null ? { _meta: response._meta } : {}),
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* The session capabilities of the v2 surface, which serves each through a v1
|
|
64
|
+
* method of the agent.
|
|
65
|
+
*
|
|
66
|
+
* - The session baseline: `session/new`, `list`, `resume` (with replay),
|
|
67
|
+
* `close`, `prompt`, `cancel`, and `update`.
|
|
68
|
+
* - `session/delete` and `additionalDirectories`. Not `session/fork` yet.
|
|
69
|
+
* - The prompt content and MCP transports that the agent's v1 `initialize`
|
|
70
|
+
* lists: images, embedded context, and HTTP servers. Stdio servers are in the
|
|
71
|
+
* v1 baseline; v2 has no SSE transport.
|
|
72
|
+
*/
|
|
73
|
+
const V2_SESSION_CAPABILITIES = {
|
|
74
|
+
prompt: { image: {}, embeddedContext: {} },
|
|
75
|
+
mcp: { stdio: {}, http: {} },
|
|
76
|
+
delete: {},
|
|
77
|
+
additionalDirectories: {},
|
|
78
|
+
};
|
|
62
79
|
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
|
|
63
80
|
function v2AuthMethod(method) {
|
|
64
81
|
if ("type" in method && method.type === "terminal") {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 form of the permission requests that `ClaudeAcpAgent` sends, and the
|
|
3
|
+
* v1 form of their answers.
|
|
4
|
+
*/
|
|
5
|
+
import type { RequestPermissionResponse } from "@agentclientprotocol/sdk";
|
|
6
|
+
import type * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
7
|
+
import type { AcpPermissionRequest } from "../permissions/presentation.js";
|
|
8
|
+
/**
|
|
9
|
+
* The v2 form of a permission request of the agent.
|
|
10
|
+
*
|
|
11
|
+
* v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
|
|
12
|
+
* its own `title` and `description`, which the agent sets for a v2 client
|
|
13
|
+
* (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
|
|
14
|
+
* call, as the same patch that a `tool_call_update` carries.
|
|
15
|
+
*/
|
|
16
|
+
export declare function v2PermissionRequest(request: AcpPermissionRequest): v2.RequestPermissionRequest;
|
|
17
|
+
/**
|
|
18
|
+
* The v1 form of the answer to a permission request.
|
|
19
|
+
*
|
|
20
|
+
* v2 adds outcomes that v1 does not know, which the draft says are no
|
|
21
|
+
* approval. They become `cancelled`, which the agent reads as an aborted tool
|
|
22
|
+
* use, as it does a cancelled prompt.
|
|
23
|
+
*/
|
|
24
|
+
export declare function v1PermissionResponse(response: v2.RequestPermissionResponse): RequestPermissionResponse;
|
|
25
|
+
//# sourceMappingURL=permission.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permission.d.ts","sourceRoot":"","sources":["../../src/v2/permission.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,KAAK,EAAE,MAAM,0CAA0C,CAAC;AACpE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AAG3E;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,EAAE,CAAC,wBAAwB,CAY9F;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,EAAE,CAAC,yBAAyB,GACrC,yBAAyB,CAS3B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { v2ToolCallPatch } from "./tool-call.js";
|
|
2
|
+
/**
|
|
3
|
+
* The v2 form of a permission request of the agent.
|
|
4
|
+
*
|
|
5
|
+
* v1 asks about a tool call, whose title the prompt shows. v2 gives the prompt
|
|
6
|
+
* its own `title` and `description`, which the agent sets for a v2 client
|
|
7
|
+
* (`AcpPermissionRequest`), and asks about a `subject`: here always the tool
|
|
8
|
+
* call, as the same patch that a `tool_call_update` carries.
|
|
9
|
+
*/
|
|
10
|
+
export function v2PermissionRequest(request) {
|
|
11
|
+
if (request.title === undefined) {
|
|
12
|
+
throw new Error("An ACP v2 permission request needs a title");
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
sessionId: request.sessionId,
|
|
16
|
+
title: request.title,
|
|
17
|
+
...(request.description ? { description: request.description } : {}),
|
|
18
|
+
subject: { type: "tool_call", toolCall: v2ToolCallPatch(request.toolCall) },
|
|
19
|
+
options: request.options,
|
|
20
|
+
...(request._meta != null ? { _meta: request._meta } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The v1 form of the answer to a permission request.
|
|
25
|
+
*
|
|
26
|
+
* v2 adds outcomes that v1 does not know, which the draft says are no
|
|
27
|
+
* approval. They become `cancelled`, which the agent reads as an aborted tool
|
|
28
|
+
* use, as it does a cancelled prompt.
|
|
29
|
+
*/
|
|
30
|
+
export function v1PermissionResponse(response) {
|
|
31
|
+
const outcome = response.outcome;
|
|
32
|
+
return {
|
|
33
|
+
outcome: outcome.outcome === "selected"
|
|
34
|
+
? outcome
|
|
35
|
+
: { outcome: "cancelled" },
|
|
36
|
+
...(response._meta != null ? { _meta: response._meta } : {}),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP v2 `session/prompt`, served through the turn events of `ClaudeAcpAgent`.
|
|
3
|
+
*
|
|
4
|
+
* v2 answers the prompt once Claude Code takes it in, with the id of the user
|
|
5
|
+
* message it became, and reports the rest of the turn as `state_update`s.
|
|
6
|
+
*/
|
|
7
|
+
import { type PromptRequest } from "@agentclientprotocol/sdk";
|
|
8
|
+
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
9
|
+
import type { ClaudeAcpAgent } from "../acp-agent.js";
|
|
10
|
+
/**
|
|
11
|
+
* Starts a turn for `params`, and answers when Claude Code takes the prompt
|
|
12
|
+
* in. `send` delivers a session update of the prompt's session; it must not
|
|
13
|
+
* throw, and must deliver updates in the order it is called, also relative to
|
|
14
|
+
* the agent's own updates.
|
|
15
|
+
*
|
|
16
|
+
* - Taken in: the prompt's `user_message`, then `running`, then the answer.
|
|
17
|
+
* - Waiting on a permission request or a question: `requires_action`, and
|
|
18
|
+
* `running` once none is open.
|
|
19
|
+
* - Ended: `idle` with the stop reason, usage, and `_meta` of the turn.
|
|
20
|
+
* - Failed after it was taken in: `idle` with {@link FAILED_STOP_REASON} and
|
|
21
|
+
* the JSON-RPC error that v1 answers with in `_meta.claudeCode.error`, and
|
|
22
|
+
* an error `notice` before it that shows the failure. v2 has no error on
|
|
23
|
+
* `idle`, and a notice must not be the only report of a failure.
|
|
24
|
+
* - Ended or failed before it was taken in: the answer is a JSON-RPC error,
|
|
25
|
+
* `-32800` for a prompt that a cancel ended in the queue.
|
|
26
|
+
*
|
|
27
|
+
* Deferred decision: a turn whose background subagents still run ends only
|
|
28
|
+
* when they finish (`Turn.deferredSettle`), so `idle` waits for them too. The
|
|
29
|
+
* hold exists for v1, whose clients stop listening at the prompt response.
|
|
30
|
+
* v2 allows background updates while idle, so it could report `idle` at the
|
|
31
|
+
* turn's result instead, with a turn event for the hold. To be decided with
|
|
32
|
+
* async tasks (step 6 in `docs/acp-v2.md`).
|
|
33
|
+
*/
|
|
34
|
+
export declare function v2Prompt(agent: ClaudeAcpAgent, params: v2.PromptRequest, send: (update: v2.SessionUpdate) => void): Promise<v2.PromptResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* The stop reason of a turn that failed after it was taken in. v2's stop
|
|
37
|
+
* reasons all describe work that ended without an error, and a custom one
|
|
38
|
+
* starts with `_`; clients show it as a generic stop.
|
|
39
|
+
*/
|
|
40
|
+
export declare const FAILED_STOP_REASON = "_error";
|
|
41
|
+
/**
|
|
42
|
+
* A v2 prompt may hold content blocks that v1 cannot express: a custom one,
|
|
43
|
+
* or one from a newer ACP version. Clients send only the kinds the agent
|
|
44
|
+
* advertised, so such a block is rejected rather than silently dropped.
|
|
45
|
+
*/
|
|
46
|
+
export declare function v1PromptRequest({ prompt, ...request }: v2.PromptRequest): PromptRequest;
|
|
47
|
+
//# sourceMappingURL=prompt.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.d.ts","sourceRoot":"","sources":["../../src/v2/prompt.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAIL,KAAK,aAAa,EAEnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAItD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;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,CA0D5B;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,WAAW,CAAC;AA2B3C;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,aAAa,GAAG,aAAa,CAEvF"}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACP v2 `session/prompt`, served through the turn events of `ClaudeAcpAgent`.
|
|
3
|
+
*
|
|
4
|
+
* v2 answers the prompt once Claude Code takes it in, with the id of the user
|
|
5
|
+
* message it became, and reports the rest of the turn as `state_update`s.
|
|
6
|
+
*/
|
|
7
|
+
import { RequestError, } from "@agentclientprotocol/sdk";
|
|
8
|
+
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
9
|
+
import { splitNoticeText } from "../session-notices.js";
|
|
10
|
+
/**
|
|
11
|
+
* Starts a turn for `params`, and answers when Claude Code takes the prompt
|
|
12
|
+
* in. `send` delivers a session update of the prompt's session; it must not
|
|
13
|
+
* throw, and must deliver updates in the order it is called, also relative to
|
|
14
|
+
* the agent's own updates.
|
|
15
|
+
*
|
|
16
|
+
* - Taken in: the prompt's `user_message`, then `running`, then the answer.
|
|
17
|
+
* - Waiting on a permission request or a question: `requires_action`, and
|
|
18
|
+
* `running` once none is open.
|
|
19
|
+
* - Ended: `idle` with the stop reason, usage, and `_meta` of the turn.
|
|
20
|
+
* - Failed after it was taken in: `idle` with {@link FAILED_STOP_REASON} and
|
|
21
|
+
* the JSON-RPC error that v1 answers with in `_meta.claudeCode.error`, and
|
|
22
|
+
* an error `notice` before it that shows the failure. v2 has no error on
|
|
23
|
+
* `idle`, and a notice must not be the only report of a failure.
|
|
24
|
+
* - Ended or failed before it was taken in: the answer is a JSON-RPC error,
|
|
25
|
+
* `-32800` for a prompt that a cancel ended in the queue.
|
|
26
|
+
*
|
|
27
|
+
* Deferred decision: a turn whose background subagents still run ends only
|
|
28
|
+
* when they finish (`Turn.deferredSettle`), so `idle` waits for them too. The
|
|
29
|
+
* hold exists for v1, whose clients stop listening at the prompt response.
|
|
30
|
+
* v2 allows background updates while idle, so it could report `idle` at the
|
|
31
|
+
* turn's result instead, with a turn event for the hold. To be decided with
|
|
32
|
+
* async tasks (step 6 in `docs/acp-v2.md`).
|
|
33
|
+
*/
|
|
34
|
+
export function v2Prompt(agent, params, send) {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let inserted = false;
|
|
37
|
+
const events = {
|
|
38
|
+
inserted(messageId) {
|
|
39
|
+
inserted = true;
|
|
40
|
+
send({ sessionUpdate: "user_message", messageId, content: params.prompt });
|
|
41
|
+
send({ sessionUpdate: "state_update", state: "running" });
|
|
42
|
+
resolve({ messageId });
|
|
43
|
+
},
|
|
44
|
+
awaitingUser() {
|
|
45
|
+
send({ sessionUpdate: "state_update", state: "requires_action" });
|
|
46
|
+
},
|
|
47
|
+
resumed() {
|
|
48
|
+
send({ sessionUpdate: "state_update", state: "running" });
|
|
49
|
+
},
|
|
50
|
+
ended(outcome) {
|
|
51
|
+
if (inserted) {
|
|
52
|
+
send(idle(outcome));
|
|
53
|
+
}
|
|
54
|
+
else if (outcome.stopReason === "cancelled") {
|
|
55
|
+
reject(RequestError.requestCancelled());
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
reject(RequestError.internalError({ stopReason: outcome.stopReason }, "The turn ended before Claude Code took the prompt in"));
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
failed(error, title) {
|
|
62
|
+
if (!inserted) {
|
|
63
|
+
reject(error);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const failure = jsonRpcError(error);
|
|
67
|
+
send({
|
|
68
|
+
sessionUpdate: "notice",
|
|
69
|
+
severity: "error",
|
|
70
|
+
...splitNoticeText(title ?? failure.message, "The turn failed"),
|
|
71
|
+
});
|
|
72
|
+
send({
|
|
73
|
+
sessionUpdate: "state_update",
|
|
74
|
+
state: "idle",
|
|
75
|
+
stopReason: FAILED_STOP_REASON,
|
|
76
|
+
_meta: { claudeCode: { error: failure } },
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
let request;
|
|
81
|
+
try {
|
|
82
|
+
request = v1PromptRequest(params);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
reject(error);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
agent.startTurn(request, events).catch(reject);
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* The stop reason of a turn that failed after it was taken in. v2's stop
|
|
93
|
+
* reasons all describe work that ended without an error, and a custom one
|
|
94
|
+
* starts with `_`; clients show it as a generic stop.
|
|
95
|
+
*/
|
|
96
|
+
export const FAILED_STOP_REASON = "_error";
|
|
97
|
+
/** The JSON-RPC error that v1 answers a failed prompt with. */
|
|
98
|
+
function jsonRpcError(error) {
|
|
99
|
+
if (error instanceof RequestError) {
|
|
100
|
+
return {
|
|
101
|
+
code: error.code,
|
|
102
|
+
message: error.message,
|
|
103
|
+
...(error.data !== undefined ? { data: error.data } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
code: RequestError.internalError().code,
|
|
108
|
+
message: error instanceof Error ? error.message : String(error),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function idle({ stopReason, usage, _meta }) {
|
|
112
|
+
return {
|
|
113
|
+
sessionUpdate: "state_update",
|
|
114
|
+
state: "idle",
|
|
115
|
+
stopReason,
|
|
116
|
+
...(usage ? { usage } : {}),
|
|
117
|
+
...(_meta ? { _meta } : {}),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* A v2 prompt may hold content blocks that v1 cannot express: a custom one,
|
|
122
|
+
* or one from a newer ACP version. Clients send only the kinds the agent
|
|
123
|
+
* advertised, so such a block is rejected rather than silently dropped.
|
|
124
|
+
*/
|
|
125
|
+
export function v1PromptRequest({ prompt, ...request }) {
|
|
126
|
+
return { ...request, prompt: prompt.map(v1ContentBlock) };
|
|
127
|
+
}
|
|
128
|
+
function v1ContentBlock(block) {
|
|
129
|
+
if (v2.ContentBlock.isText(block) ||
|
|
130
|
+
v2.ContentBlock.isImage(block) ||
|
|
131
|
+
v2.ContentBlock.isAudio(block) ||
|
|
132
|
+
v2.ContentBlock.isResourceLink(block) ||
|
|
133
|
+
v2.ContentBlock.isResource(block)) {
|
|
134
|
+
const { annotations, ...rest } = block;
|
|
135
|
+
return annotations != null ? { ...rest, annotations: v1Annotations(annotations) } : rest;
|
|
136
|
+
}
|
|
137
|
+
throw RequestError.invalidParams({ type: block.type }, `Content blocks of type ${block.type} are not supported`);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* v2 also accepts audiences of a newer ACP version; v1 knows two. An audience
|
|
141
|
+
* of only unknown roles is dropped rather than narrowed to nobody.
|
|
142
|
+
*/
|
|
143
|
+
function v1Annotations({ audience, ...annotations }) {
|
|
144
|
+
if (audience == null)
|
|
145
|
+
return annotations;
|
|
146
|
+
const known = audience.filter(isV1Role);
|
|
147
|
+
return known.length > 0 || audience.length === 0
|
|
148
|
+
? { ...annotations, audience: known }
|
|
149
|
+
: annotations;
|
|
150
|
+
}
|
|
151
|
+
function isV1Role(role) {
|
|
152
|
+
return role === "assistant" || role === "user";
|
|
153
|
+
}
|
|
@@ -4,6 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
6
6
|
import type { AcpSessionNotification } from "../acp-subagents.js";
|
|
7
|
+
/**
|
|
8
|
+
* The id of the plan of a session. v1 has one plan per session, which every
|
|
9
|
+
* plan update replaces whole, from TodoWrite and from the Task tools alike.
|
|
10
|
+
* v2 tracks plans by id, so that plan gets one.
|
|
11
|
+
*/
|
|
12
|
+
export declare const SESSION_PLAN_ID = "plan";
|
|
7
13
|
/**
|
|
8
14
|
* The v2 form of a v1 session update, or `undefined` when v2 has no
|
|
9
15
|
* counterpart and the update tells a v2 client nothing new.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-update.d.ts","sourceRoot":"","sources":["../../src/v2/session-update.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAC/D,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAIlE;;;;GAIG;AACH,eAAO,MAAM,eAAe,SAAS,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,sBAAsB,CAAC,QAAQ,CAAC,GACvC,EAAE,CAAC,aAAa,GAAG,SAAS,CAwC9B"}
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
import { v2AvailableCommands, v2ConfigOptions } from "./session.js";
|
|
2
|
+
import { v2ToolCallUpdate } from "./tool-call.js";
|
|
3
|
+
/**
|
|
4
|
+
* The id of the plan of a session. v1 has one plan per session, which every
|
|
5
|
+
* plan update replaces whole, from TodoWrite and from the Task tools alike.
|
|
6
|
+
* v2 tracks plans by id, so that plan gets one.
|
|
7
|
+
*/
|
|
8
|
+
export const SESSION_PLAN_ID = "plan";
|
|
2
9
|
/**
|
|
3
10
|
* The v2 form of a v1 session update, or `undefined` when v2 has no
|
|
4
11
|
* counterpart and the update tells a v2 client nothing new.
|
|
@@ -16,6 +23,26 @@ export function v2SessionUpdate(update) {
|
|
|
16
23
|
case "usage_update":
|
|
17
24
|
case "notice":
|
|
18
25
|
return update;
|
|
26
|
+
case "agent_message_chunk":
|
|
27
|
+
case "agent_thought_chunk":
|
|
28
|
+
case "user_message_chunk": {
|
|
29
|
+
// v2 requires the id of the message a chunk belongs to; v1 allows none.
|
|
30
|
+
const { messageId, ...chunk } = update;
|
|
31
|
+
if (!messageId) {
|
|
32
|
+
throw new Error(`An ACP v2 ${update.sessionUpdate} needs a messageId`);
|
|
33
|
+
}
|
|
34
|
+
return { ...chunk, messageId };
|
|
35
|
+
}
|
|
36
|
+
case "tool_call":
|
|
37
|
+
case "tool_call_update":
|
|
38
|
+
return v2ToolCallUpdate(update);
|
|
39
|
+
case "plan":
|
|
40
|
+
// A v2 plan of items is replaced whole as well.
|
|
41
|
+
return {
|
|
42
|
+
sessionUpdate: "plan_update",
|
|
43
|
+
plan: { type: "items", planId: SESSION_PLAN_ID, entries: update.entries },
|
|
44
|
+
...(update._meta != null ? { _meta: update._meta } : {}),
|
|
45
|
+
};
|
|
19
46
|
case "current_mode_update":
|
|
20
47
|
// v2 has no modes: the mode is the `mode` config option. Every mode
|
|
21
48
|
// change also reaches the client as a `config_option_update` or in the
|
package/dist/v2/session.d.ts
CHANGED
|
@@ -3,14 +3,25 @@
|
|
|
3
3
|
* `ClaudeAcpAgent`: v2 requests become the v1 requests that the agent serves,
|
|
4
4
|
* and its v1 responses become v2 responses.
|
|
5
5
|
*/
|
|
6
|
-
import { type AvailableCommand, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
|
|
6
|
+
import { type AvailableCommand, type LoadSessionRequest, type NewSessionRequest, type NewSessionResponse, type ResumeSessionRequest, type ResumeSessionResponse, type SessionConfigOption, type SetSessionConfigOptionRequest } from "@agentclientprotocol/sdk";
|
|
7
7
|
import * as v2 from "@agentclientprotocol/sdk/experimental/v2";
|
|
8
8
|
export declare function v1NewSessionRequest(params: v2.NewSessionRequest): NewSessionRequest;
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* The v1 request that a v2 `session/resume` means.
|
|
11
|
+
*
|
|
12
|
+
* v2 restores a session with `session/resume` and replays its history only
|
|
13
|
+
* when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
|
|
14
|
+
* never replays, and `session/load`, which replays the whole history before it
|
|
15
|
+
* responds. `replayFrom: { type: "start" }` is the second; a cursor that
|
|
16
|
+
* starts elsewhere is rejected.
|
|
12
17
|
*/
|
|
13
|
-
export declare function
|
|
18
|
+
export declare function v1RestoreSessionRequest(params: v2.ResumeSessionRequest): {
|
|
19
|
+
method: "resume";
|
|
20
|
+
request: ResumeSessionRequest;
|
|
21
|
+
} | {
|
|
22
|
+
method: "load";
|
|
23
|
+
request: LoadSessionRequest;
|
|
24
|
+
};
|
|
14
25
|
/** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
|
|
15
26
|
export declare function v2NewSessionResponse(response: NewSessionResponse): v2.NewSessionResponse;
|
|
16
27
|
export declare function v2ResumeSessionResponse(response: ResumeSessionResponse): v2.ResumeSessionResponse;
|
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,
|
|
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,EAEvB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EAGxB,KAAK,6BAA6B,EACnC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,0CAA0C,CAAC;AAE/D,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,EAAE,CAAC,iBAAiB,GAAG,iBAAiB,CAInF;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,CAkBlD;AAED,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"}
|