@agentclientprotocol/claude-agent-acp 0.85.2-preview.8 → 0.86.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp-agent.d.ts +56 -21
- package/dist/acp-agent.d.ts.map +1 -1
- package/dist/acp-agent.js +161 -34
- 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/permissions/presentation.d.ts +17 -2
- package/dist/permissions/presentation.d.ts.map +1 -1
- package/dist/permissions/presentation.js +4 -0
- 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/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/v2/agent.d.ts +2 -0
- package/dist/v2/agent.d.ts.map +1 -1
- package/dist/v2/agent.js +109 -14
- package/dist/v2/initialize.d.ts +2 -5
- package/dist/v2/initialize.d.ts.map +1 -1
- package/dist/v2/initialize.js +42 -11
- 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/session-update.d.ts +6 -0
- package/dist/v2/session-update.d.ts.map +1 -1
- package/dist/v2/session-update.js +19 -0
- package/dist/v2/session.d.ts +29 -4
- package/dist/v2/session.d.ts.map +1 -1
- package/dist/v2/session.js +41 -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 +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { patchUpdateFromDiffToolResponse, toolUpdateFromDiffToolResponse } from "../../diff.js";
|
|
1
|
+
import { patchUpdateFromDiffToolResponse, toolUpdateFromDiffToolResponse, v2UpdateFromDiffToolResponse, } from "../../diff.js";
|
|
2
2
|
import { markdownEscape, resultText, textContent, toDisplayPath } from "../content.js";
|
|
3
3
|
/**
|
|
4
4
|
* Write: the diff holds the file text. The PostToolUse hook sends the final
|
|
@@ -14,12 +14,15 @@ export class WriteReporter {
|
|
|
14
14
|
locations: write?.file_path ? [{ path: write.file_path }] : [],
|
|
15
15
|
};
|
|
16
16
|
if (write?.file_path) {
|
|
17
|
-
// A negotiated client
|
|
18
|
-
// from the PostToolUse hook. The input does not tell
|
|
19
|
-
// exists, so the live tool call shows no diff: a diff
|
|
20
|
-
// text would claim a creation. The adapter reads the
|
|
21
|
-
// preview. A replay has no preview and no hook, so it
|
|
22
|
-
|
|
17
|
+
// A negotiated client and a v2 client get the exact patch from the
|
|
18
|
+
// approval preview or from the PostToolUse hook. The input does not tell
|
|
19
|
+
// whether the file exists, so the live tool call shows no diff: a diff
|
|
20
|
+
// without the old text would claim a creation. The adapter reads the
|
|
21
|
+
// file only for the preview. A replay has no preview and no hook, so it
|
|
22
|
+
// keeps the diff, except for a v2 client: a v2 diff names the operation,
|
|
23
|
+
// which the input does not tell.
|
|
24
|
+
const inputDiff = capabilities.v2 ? false : !capabilities.diffPatch || replay;
|
|
25
|
+
if (inputDiff) {
|
|
23
26
|
facts.change = [
|
|
24
27
|
{
|
|
25
28
|
type: "diff",
|
|
@@ -68,7 +71,7 @@ export function normalizeWriteInput(input) {
|
|
|
68
71
|
}
|
|
69
72
|
/** Edit: the diff holds the old and the new text. */
|
|
70
73
|
export class EditReporter {
|
|
71
|
-
toolUse(input, { cwd }) {
|
|
74
|
+
toolUse(input, { cwd, capabilities }) {
|
|
72
75
|
const edit = input;
|
|
73
76
|
const displayPath = edit?.file_path ? toDisplayPath(edit.file_path, cwd) : undefined;
|
|
74
77
|
const facts = {
|
|
@@ -76,10 +79,11 @@ export class EditReporter {
|
|
|
76
79
|
kind: "edit",
|
|
77
80
|
locations: edit?.file_path ? [{ path: edit.file_path }] : [],
|
|
78
81
|
};
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
// The standard diff, also for a client that negotiated patches: the input
|
|
83
|
+
// holds a snippet, not the file, so a patch would need line numbers that
|
|
84
|
+
// the adapter does not know here. A v2 diff cannot hold a snippet, so a v2
|
|
85
|
+
// client gets the patch from the PostToolUse hook alone.
|
|
86
|
+
if (edit?.file_path && (edit.old_string || edit.new_string) && !capabilities.v2) {
|
|
83
87
|
facts.change = [
|
|
84
88
|
{
|
|
85
89
|
type: "diff",
|
|
@@ -104,12 +108,14 @@ export class EditReporter {
|
|
|
104
108
|
* PostToolUse `tool_response`. For Write it replaces the optimistic creation
|
|
105
109
|
* diff with the real diff of an updated file. A negotiated client gets an
|
|
106
110
|
* exact git patch built from the written file, or the standard diff when none
|
|
107
|
-
* can be built.
|
|
111
|
+
* can be built. A v2 client gets a v2 diff.
|
|
108
112
|
*/
|
|
109
113
|
async function finalChange(toolResponse, { capabilities }) {
|
|
114
|
+
if (capabilities.v2)
|
|
115
|
+
return v2UpdateFromDiffToolResponse(toolResponse);
|
|
110
116
|
if (!capabilities.diffPatch)
|
|
111
117
|
return toolUpdateFromDiffToolResponse(toolResponse);
|
|
112
|
-
const patch = await patchUpdateFromDiffToolResponse(toolResponse);
|
|
118
|
+
const patch = await patchUpdateFromDiffToolResponse(toolResponse, "air");
|
|
113
119
|
if (patch)
|
|
114
120
|
return patch;
|
|
115
121
|
const created = createdFileDiff(toolResponse);
|
package/dist/v2/agent.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ import { ClaudeAcpAgent, type Logger } from "../acp-agent.js";
|
|
|
6
6
|
* As in `v1AgentApp`, the agent of the connection is created when the
|
|
7
7
|
* connection opens, before the connection processes any inbound message.
|
|
8
8
|
* `onAgent` receives it for the owner of the process (shutdown).
|
|
9
|
+
*
|
|
10
|
+
* The provider methods pass through: their types are the same in v1 and v2.
|
|
9
11
|
*/
|
|
10
12
|
export declare function v2AgentApp(logger: Logger | undefined, onAgent: (agent: ClaudeAcpAgent) => void): v2.AgentApp;
|
|
11
13
|
//# sourceMappingURL=agent.d.ts.map
|
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":"
|
|
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;AAkB9E;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,GACvC,EAAE,CAAC,QAAQ,CA4Db"}
|
package/dist/v2/agent.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
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 { v1PermissionResponse, v2PermissionRequest } from "./permission.js";
|
|
4
5
|
import { v2Prompt } from "./prompt.js";
|
|
5
|
-
import { v1NewSessionRequest,
|
|
6
|
+
import { v1ForkSessionRequests, v1NewSessionRequest, v1RestoreSessionRequest, v1SetSessionConfigOptionRequest, v2ConfigOptions, v2NewSessionResponse, v2ResumeSessionResponse, } from "./session.js";
|
|
6
7
|
import { v2SessionUpdate } from "./session-update.js";
|
|
8
|
+
import { V2Terminals } from "./terminal.js";
|
|
7
9
|
/**
|
|
8
10
|
* The ACP v2 surface, for one connection.
|
|
9
11
|
*
|
|
10
12
|
* As in `v1AgentApp`, the agent of the connection is created when the
|
|
11
13
|
* connection opens, before the connection processes any inbound message.
|
|
12
14
|
* `onAgent` receives it for the owner of the process (shutdown).
|
|
15
|
+
*
|
|
16
|
+
* The provider methods pass through: their types are the same in v1 and v2.
|
|
13
17
|
*/
|
|
14
18
|
export function v2AgentApp(logger, onAgent) {
|
|
15
19
|
let agent;
|
|
@@ -18,16 +22,36 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
18
22
|
.agent({ name: "claude-code-acp" })
|
|
19
23
|
.onConnect((connection) => {
|
|
20
24
|
client = new V2ClientConnection(connection.client, logger ?? console);
|
|
21
|
-
agent = new ClaudeAcpAgent(client, logger);
|
|
25
|
+
agent = new ClaudeAcpAgent(client, logger, { v2: true });
|
|
22
26
|
onAgent(agent);
|
|
23
27
|
})
|
|
24
28
|
.onRequest(v2.methods.agent.initialize, async ({ params }) => v2InitializeResponse(await agent.initialize(v1InitializeRequest(params))))
|
|
25
29
|
.onRequest(v2.methods.agent.auth.login, ({ params }) => agent.authenticate(params))
|
|
26
30
|
.onRequest(v2.methods.agent.auth.logout, ({ params }) => agent.logout(params))
|
|
31
|
+
.onRequest(v2.methods.agent.providers.list, ({ params }) => agent.unstable_listProviders(params))
|
|
32
|
+
.onRequest(v2.methods.agent.providers.set, ({ params }) => agent.unstable_setProvider(params))
|
|
33
|
+
.onRequest(v2.methods.agent.providers.disable, ({ params }) => agent.unstable_disableProvider(params))
|
|
27
34
|
.onRequest(v2.methods.agent.session.new, async ({ params }) => v2NewSessionResponse(await agent.newSession(v1NewSessionRequest(params))))
|
|
28
35
|
.onRequest(v2.methods.agent.session.list, ({ params }) => agent.listSessions(params))
|
|
29
|
-
.onRequest(v2.methods.agent.session.resume, async ({ params }) =>
|
|
30
|
-
|
|
36
|
+
.onRequest(v2.methods.agent.session.resume, async ({ params }) => {
|
|
37
|
+
const restore = v1RestoreSessionRequest(params);
|
|
38
|
+
return v2ResumeSessionResponse(restore.method === "resume"
|
|
39
|
+
? await agent.resumeSession(restore.request)
|
|
40
|
+
: await client.replaying(params.sessionId, () => agent.loadSession(restore.request)));
|
|
41
|
+
})
|
|
42
|
+
.onRequest(v2.methods.agent.session.fork, async ({ params }) => {
|
|
43
|
+
const requests = v1ForkSessionRequests(params);
|
|
44
|
+
const { sessionId } = await agent.unstable_forkSession(requests.fork);
|
|
45
|
+
return {
|
|
46
|
+
sessionId,
|
|
47
|
+
...v2ResumeSessionResponse(await agent.resumeSession(requests.resume(sessionId))),
|
|
48
|
+
};
|
|
49
|
+
})
|
|
50
|
+
.onRequest(v2.methods.agent.session.close, async ({ params }) => {
|
|
51
|
+
const response = await agent.closeSession(params);
|
|
52
|
+
client.terminals.forget(params.sessionId);
|
|
53
|
+
return response;
|
|
54
|
+
})
|
|
31
55
|
.onRequest(v2.methods.agent.session.delete, ({ params }) => agent.deleteSession(params))
|
|
32
56
|
.onRequest(v2.methods.agent.session.setConfigOption, async ({ params }) => {
|
|
33
57
|
const { configOptions, ...response } = await agent.setSessionConfigOption(v1SetSessionConfigOptionRequest(params));
|
|
@@ -41,16 +65,38 @@ export function v2AgentApp(logger, onAgent) {
|
|
|
41
65
|
/**
|
|
42
66
|
* The {@link AcpClient} of an ACP v2 connection: it sends the v1 messages of
|
|
43
67
|
* the agent as v2 messages.
|
|
44
|
-
*
|
|
45
|
-
* Permission requests still reject until the v2 surface translates them.
|
|
46
68
|
*/
|
|
47
69
|
class V2ClientConnection {
|
|
48
70
|
ctx;
|
|
49
71
|
logger;
|
|
72
|
+
/**
|
|
73
|
+
* The sessions whose history replays, each with the messages that the
|
|
74
|
+
* replay has cleared so far.
|
|
75
|
+
*/
|
|
76
|
+
replays = new Map();
|
|
77
|
+
/** The display terminals of the commands that the agent runs. */
|
|
78
|
+
terminals = new V2Terminals();
|
|
50
79
|
constructor(ctx, logger) {
|
|
51
80
|
this.ctx = ctx;
|
|
52
81
|
this.logger = logger;
|
|
53
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Runs `restore`, which replays the history of `sessionId`.
|
|
85
|
+
*
|
|
86
|
+
* The agent replays messages as chunks. A v2 client may still hold a
|
|
87
|
+
* replayed message, and chunks append, so before the first replayed chunk of
|
|
88
|
+
* each message the client gets the message with no content, which clears it,
|
|
89
|
+
* as v2 requires of a message replayed as chunks.
|
|
90
|
+
*/
|
|
91
|
+
async replaying(sessionId, restore) {
|
|
92
|
+
this.replays.set(sessionId, new Set());
|
|
93
|
+
try {
|
|
94
|
+
return await restore();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this.replays.delete(sessionId);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
54
100
|
/**
|
|
55
101
|
* Sends a v2 session update, and logs rather than rejects when that fails.
|
|
56
102
|
* It and {@link sessionUpdate} hand updates to the connection synchronously,
|
|
@@ -72,16 +118,48 @@ class V2ClientConnection {
|
|
|
72
118
|
return this.ctx.notify(method, params);
|
|
73
119
|
}
|
|
74
120
|
async sessionUpdate({ update, ...notification }) {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
121
|
+
const updates = this.v2Updates(notification.sessionId, update).flatMap((v2Update) => {
|
|
122
|
+
const clear = this.replayClear(notification.sessionId, v2Update);
|
|
123
|
+
return clear ? [clear, v2Update] : [v2Update];
|
|
124
|
+
});
|
|
125
|
+
// Every update goes to the connection before any is awaited, so an update
|
|
126
|
+
// that another call sends meanwhile cannot come between them.
|
|
127
|
+
await Promise.all(updates.map((v2Update) => this.ctx.notify(v2.methods.client.session.update, { ...notification, update: v2Update })));
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* The v2 updates of a v1 update: for a tool call report, the updates of its
|
|
131
|
+
* terminal first, then the report itself (see {@link V2Terminals}).
|
|
132
|
+
*/
|
|
133
|
+
v2Updates(sessionId, update) {
|
|
134
|
+
if (update.sessionUpdate !== "tool_call" && update.sessionUpdate !== "tool_call_update") {
|
|
135
|
+
const v2Update = v2SessionUpdate(update);
|
|
136
|
+
return v2Update ? [v2Update] : [];
|
|
81
137
|
}
|
|
138
|
+
const { terminal, report } = this.terminals.split(sessionId, update);
|
|
139
|
+
const reportUpdate = report && v2SessionUpdate(report);
|
|
140
|
+
return [...terminal, ...(reportUpdate ? [reportUpdate] : [])];
|
|
82
141
|
}
|
|
83
|
-
|
|
84
|
-
|
|
142
|
+
/**
|
|
143
|
+
* The update that clears the message of a replayed chunk, when it is the
|
|
144
|
+
* first replayed chunk of that message (see {@link replaying}).
|
|
145
|
+
*/
|
|
146
|
+
replayClear(sessionId, update) {
|
|
147
|
+
const cleared = this.replays.get(sessionId);
|
|
148
|
+
if (!cleared)
|
|
149
|
+
return undefined;
|
|
150
|
+
const kind = MESSAGE_OF_CHUNK.get(update.sessionUpdate);
|
|
151
|
+
if (!kind)
|
|
152
|
+
return undefined;
|
|
153
|
+
const { messageId } = update;
|
|
154
|
+
const key = `${kind} ${messageId}`;
|
|
155
|
+
if (cleared.has(key))
|
|
156
|
+
return undefined;
|
|
157
|
+
cleared.add(key);
|
|
158
|
+
return emptyMessage(kind, messageId);
|
|
159
|
+
}
|
|
160
|
+
async requestPermission(params, signal) {
|
|
161
|
+
const response = await this.ctx.request(v2.methods.client.session.requestPermission, v2PermissionRequest(params), { cancellationSignal: signal });
|
|
162
|
+
return v1PermissionResponse(response);
|
|
85
163
|
}
|
|
86
164
|
// Elicitation is the same in v1 and v2.
|
|
87
165
|
createElicitation(params, signal) {
|
|
@@ -103,6 +181,23 @@ class V2ClientConnection {
|
|
|
103
181
|
return Promise.reject(new Error("ACP v2 has no client file system"));
|
|
104
182
|
}
|
|
105
183
|
}
|
|
184
|
+
/** The message update of each message chunk. */
|
|
185
|
+
const MESSAGE_OF_CHUNK = new Map([
|
|
186
|
+
["user_message_chunk", "user_message"],
|
|
187
|
+
["agent_message_chunk", "agent_message"],
|
|
188
|
+
["agent_thought_chunk", "agent_thought"],
|
|
189
|
+
]);
|
|
190
|
+
function emptyMessage(kind, messageId) {
|
|
191
|
+
const message = { messageId, content: [] };
|
|
192
|
+
switch (kind) {
|
|
193
|
+
case "user_message":
|
|
194
|
+
return { ...message, sessionUpdate: kind };
|
|
195
|
+
case "agent_message":
|
|
196
|
+
return { ...message, sessionUpdate: kind };
|
|
197
|
+
default:
|
|
198
|
+
return { ...message, sessionUpdate: kind };
|
|
199
|
+
}
|
|
200
|
+
}
|
|
106
201
|
function isExtensionMethod(method) {
|
|
107
202
|
return method.startsWith("_");
|
|
108
203
|
}
|
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 what the v2 surface does not serve yet: the
|
|
18
|
+
* extensions of {@link UNSERVED_EXTENSION_KEYS}.
|
|
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,
|
|
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,CA0BpF;AASD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,EAAE,CAAC,kBAAkB,CAuBxF"}
|
package/dist/v2/initialize.js
CHANGED
|
@@ -20,10 +20,12 @@ export function v1InitializeRequest(request) {
|
|
|
20
20
|
// reads, writes, and runs commands itself.
|
|
21
21
|
fs: { readTextFile: false, writeTextFile: false },
|
|
22
22
|
terminal: false,
|
|
23
|
-
// Every v2 client handles boolean config options
|
|
24
|
-
// for a marker. With
|
|
25
|
-
// rather than as transcript messages
|
|
26
|
-
|
|
23
|
+
// Every v2 client handles boolean config options, notices, and
|
|
24
|
+
// compaction updates; v1 asks for a marker. With them, the agent reports
|
|
25
|
+
// advisories as notices rather than as transcript messages, and a
|
|
26
|
+
// compaction as a compaction entity rather than as a "Compact
|
|
27
|
+
// conversation" tool call, also on replay.
|
|
28
|
+
session: { configOptions: { boolean: {} }, notices: {}, compaction: {} },
|
|
27
29
|
// v1 marks terminal auth support with a boolean, v2 with an object.
|
|
28
30
|
auth: {
|
|
29
31
|
terminal: capabilities?.auth?.terminal != null,
|
|
@@ -35,30 +37,59 @@ export function v1InitializeRequest(request) {
|
|
|
35
37
|
...(request._meta != null ? { _meta: request._meta } : {}),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* The top-level `_meta` keys of the agent's v1 `initialize` response that
|
|
42
|
+
* advertise an extension the v2 surface does not serve yet: `steering`, for
|
|
43
|
+
* `_session/steering`.
|
|
44
|
+
*/
|
|
45
|
+
const UNSERVED_EXTENSION_KEYS = ["steering"];
|
|
38
46
|
/**
|
|
39
47
|
* The v2 `initialize` response for the v1 response of the agent.
|
|
40
48
|
*
|
|
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.
|
|
49
|
+
* It does not advertise what the v2 surface does not serve yet: the
|
|
50
|
+
* extensions of {@link UNSERVED_EXTENSION_KEYS}.
|
|
46
51
|
*/
|
|
47
52
|
export function v2InitializeResponse(response) {
|
|
48
53
|
if (!response.agentInfo) {
|
|
49
54
|
throw new Error("ACP v2 requires agentInfo, and the agent reported none");
|
|
50
55
|
}
|
|
51
56
|
const meta = response.agentCapabilities?._meta;
|
|
57
|
+
const topMeta = Object.fromEntries(Object.entries(response._meta ?? {}).filter(([key]) => !UNSERVED_EXTENSION_KEYS.includes(key)));
|
|
52
58
|
return {
|
|
53
59
|
protocolVersion: v2.PROTOCOL_VERSION,
|
|
54
60
|
info: response.agentInfo,
|
|
55
61
|
// v1 `auth.logout` has no v2 counterpart: a v2 agent that advertises auth
|
|
56
62
|
// methods must serve `auth/logout`.
|
|
57
|
-
capabilities:
|
|
63
|
+
capabilities: {
|
|
64
|
+
session: V2_SESSION_CAPABILITIES,
|
|
65
|
+
// `providers/list`, `set`, and `disable`, which the agent serves for
|
|
66
|
+
// every client.
|
|
67
|
+
providers: {},
|
|
68
|
+
...(meta != null ? { _meta: meta } : {}),
|
|
69
|
+
},
|
|
58
70
|
authMethods: (response.authMethods ?? []).map(v2AuthMethod),
|
|
59
|
-
...(
|
|
71
|
+
...(Object.keys(topMeta).length > 0 ? { _meta: topMeta } : {}),
|
|
60
72
|
};
|
|
61
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The session capabilities of the v2 surface, which serves each through a v1
|
|
76
|
+
* method of the agent.
|
|
77
|
+
*
|
|
78
|
+
* - The session baseline: `session/new`, `list`, `resume` (with replay),
|
|
79
|
+
* `close`, `prompt`, `cancel`, and `update`.
|
|
80
|
+
* - `session/delete`, `session/fork` (which returns a session the client can
|
|
81
|
+
* prompt, see `v1ForkSessionRequests`), and `additionalDirectories`.
|
|
82
|
+
* - The prompt content and MCP transports that the agent's v1 `initialize`
|
|
83
|
+
* lists: images, embedded context, and HTTP servers. Stdio servers are in the
|
|
84
|
+
* v1 baseline; v2 has no SSE transport.
|
|
85
|
+
*/
|
|
86
|
+
const V2_SESSION_CAPABILITIES = {
|
|
87
|
+
prompt: { image: {}, embeddedContext: {} },
|
|
88
|
+
mcp: { stdio: {}, http: {} },
|
|
89
|
+
delete: {},
|
|
90
|
+
fork: {},
|
|
91
|
+
additionalDirectories: {},
|
|
92
|
+
};
|
|
62
93
|
/** v2 names the id of a method `methodId`, requires `type`, and lists `env` as name and value pairs. */
|
|
63
94
|
function v2AuthMethod(method) {
|
|
64
95
|
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
|
+
}
|
|
@@ -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,CA0C9B"}
|
|
@@ -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.
|
|
@@ -15,6 +22,8 @@ export function v2SessionUpdate(update) {
|
|
|
15
22
|
case "session_info_update":
|
|
16
23
|
case "usage_update":
|
|
17
24
|
case "notice":
|
|
25
|
+
case "compaction_update":
|
|
26
|
+
case "compaction_summary_chunk":
|
|
18
27
|
return update;
|
|
19
28
|
case "agent_message_chunk":
|
|
20
29
|
case "agent_thought_chunk":
|
|
@@ -26,6 +35,16 @@ export function v2SessionUpdate(update) {
|
|
|
26
35
|
}
|
|
27
36
|
return { ...chunk, messageId };
|
|
28
37
|
}
|
|
38
|
+
case "tool_call":
|
|
39
|
+
case "tool_call_update":
|
|
40
|
+
return v2ToolCallUpdate(update);
|
|
41
|
+
case "plan":
|
|
42
|
+
// A v2 plan of items is replaced whole as well.
|
|
43
|
+
return {
|
|
44
|
+
sessionUpdate: "plan_update",
|
|
45
|
+
plan: { type: "items", planId: SESSION_PLAN_ID, entries: update.entries },
|
|
46
|
+
...(update._meta != null ? { _meta: update._meta } : {}),
|
|
47
|
+
};
|
|
29
48
|
case "current_mode_update":
|
|
30
49
|
// v2 has no modes: the mode is the `mode` config option. Every mode
|
|
31
50
|
// change also reaches the client as a `config_option_update` or in the
|
package/dist/v2/session.d.ts
CHANGED
|
@@ -3,14 +3,39 @@
|
|
|
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 ForkSessionRequest, 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
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* The v1 requests that a v2 `session/fork` means.
|
|
27
|
+
*
|
|
28
|
+
* The agent's v1 fork copies the transcript into a new session and returns
|
|
29
|
+
* only its id; a v1 client then resumes or loads it. A v2 fork returns the new
|
|
30
|
+
* session with its config options, like `session/new`, so the client can
|
|
31
|
+
* prompt it. The v2 surface therefore forks, then resumes the fork without
|
|
32
|
+
* replay, with the request's `cwd`, `additionalDirectories`, and
|
|
33
|
+
* `mcpServers`. The request's `_meta` goes to the fork alone.
|
|
34
|
+
*/
|
|
35
|
+
export declare function v1ForkSessionRequests(params: v2.ForkSessionRequest): {
|
|
36
|
+
fork: ForkSessionRequest;
|
|
37
|
+
resume: (sessionId: string) => ResumeSessionRequest;
|
|
38
|
+
};
|
|
14
39
|
/** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
|
|
15
40
|
export declare function v2NewSessionResponse(response: NewSessionResponse): v2.NewSessionResponse;
|
|
16
41
|
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,EACvB,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;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,EAAE,CAAC,kBAAkB,GAAG;IACpE,IAAI,EAAE,kBAAkB,CAAC;IACzB,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,oBAAoB,CAAC;CACrD,CAUA;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"}
|
package/dist/v2/session.js
CHANGED
|
@@ -11,15 +11,50 @@ export function v1NewSessionRequest(params) {
|
|
|
11
11
|
return { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) };
|
|
12
12
|
}
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
14
|
+
* The v1 request that a v2 `session/resume` means.
|
|
15
|
+
*
|
|
16
|
+
* v2 restores a session with `session/resume` and replays its history only
|
|
17
|
+
* when `replayFrom` asks for it. v1 has two methods: `session/resume`, which
|
|
18
|
+
* never replays, and `session/load`, which replays the whole history before it
|
|
19
|
+
* responds. `replayFrom: { type: "start" }` is the second; a cursor that
|
|
20
|
+
* starts elsewhere is rejected.
|
|
16
21
|
*/
|
|
17
|
-
export function
|
|
22
|
+
export function v1RestoreSessionRequest(params) {
|
|
18
23
|
const { replayFrom, mcpServers, ...request } = params;
|
|
19
|
-
if (replayFrom
|
|
20
|
-
|
|
24
|
+
if (replayFrom == null) {
|
|
25
|
+
return {
|
|
26
|
+
method: "resume",
|
|
27
|
+
request: { ...request, ...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}) },
|
|
28
|
+
};
|
|
21
29
|
}
|
|
22
|
-
|
|
30
|
+
if (replayFrom.type !== "start") {
|
|
31
|
+
throw RequestError.invalidParams({ replayFrom }, `Replaying from a ${replayFrom.type} cursor is not supported`);
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
method: "load",
|
|
35
|
+
request: { ...request, mcpServers: (mcpServers ?? []).map(v1McpServer) },
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The v1 requests that a v2 `session/fork` means.
|
|
40
|
+
*
|
|
41
|
+
* The agent's v1 fork copies the transcript into a new session and returns
|
|
42
|
+
* only its id; a v1 client then resumes or loads it. A v2 fork returns the new
|
|
43
|
+
* session with its config options, like `session/new`, so the client can
|
|
44
|
+
* prompt it. The v2 surface therefore forks, then resumes the fork without
|
|
45
|
+
* replay, with the request's `cwd`, `additionalDirectories`, and
|
|
46
|
+
* `mcpServers`. The request's `_meta` goes to the fork alone.
|
|
47
|
+
*/
|
|
48
|
+
export function v1ForkSessionRequests(params) {
|
|
49
|
+
const { mcpServers, _meta, ...request } = params;
|
|
50
|
+
const session = {
|
|
51
|
+
...request,
|
|
52
|
+
...(mcpServers ? { mcpServers: mcpServers.map(v1McpServer) } : {}),
|
|
53
|
+
};
|
|
54
|
+
return {
|
|
55
|
+
fork: { ...session, ...(_meta != null ? { _meta } : {}) },
|
|
56
|
+
resume: (sessionId) => ({ ...session, sessionId }),
|
|
57
|
+
};
|
|
23
58
|
}
|
|
24
59
|
/** v2 drops `modes`: the session mode is the `mode` config option, which the agent always lists. */
|
|
25
60
|
export function v2NewSessionResponse(response) {
|