@wrongstack/acp 1.0.32 → 1.0.35
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/agent/protocol-contract.d.ts +0 -10
- package/dist/agent/protocol-handler.d.ts +1 -6
- package/dist/agent/server-agent-turn-content.d.ts +117 -0
- package/dist/agent/server-agent-turn.d.ts +4 -67
- package/dist/agent.js +138 -136
- package/dist/client/acp-session-prompt.d.ts +23 -0
- package/dist/client/acp-session.d.ts +1 -0
- package/dist/client.js +193 -169
- package/dist/index.js +193 -169
- package/package.json +3 -3
|
@@ -17,16 +17,6 @@ type WireMessage = {
|
|
|
17
17
|
};
|
|
18
18
|
export declare function toWire(msg: WireMessage): ACPMessage;
|
|
19
19
|
export declare const WRONGSTACK_VERSION: string;
|
|
20
|
-
/** What kinds of content the agent accepts in a prompt. */
|
|
21
|
-
export interface PromptCapabilities {
|
|
22
|
-
image: boolean;
|
|
23
|
-
audio: boolean;
|
|
24
|
-
embeddedContext: boolean;
|
|
25
|
-
}
|
|
26
|
-
export interface AgentCapabilities {
|
|
27
|
-
loadSession: boolean;
|
|
28
|
-
promptCapabilities: PromptCapabilities;
|
|
29
|
-
}
|
|
30
20
|
export interface RunTurnInput {
|
|
31
21
|
sessionId: string;
|
|
32
22
|
/** Current session configuration, isolated from other sessions. */
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { type ProtocolHandlerOptions, WRONGSTACK_VERSION } from './protocol-contract.js';
|
|
2
|
-
|
|
3
|
-
export type { AgentCapabilities, ClientCapabilities, McpServer, PromptCapabilities, ProtocolHandlerOptions, RunTurn, RunTurnApi, RunTurnInput, RunTurnPermissionRequest, RunTurnResult, SessionConfigOption, SessionMode, SessionPersistence, SessionState, } from './protocol-contract.js';
|
|
2
|
+
export type { ClientCapabilities, McpServer, ProtocolHandlerOptions, RunTurn, RunTurnApi, RunTurnInput, RunTurnPermissionRequest, RunTurnResult, SessionPersistence, SessionState, } from './protocol-contract.js';
|
|
4
3
|
export { WRONGSTACK_VERSION };
|
|
5
4
|
export declare class ACPProtocolHandler {
|
|
6
5
|
private readonly transport;
|
|
@@ -60,8 +59,4 @@ export declare class ACPProtocolHandler {
|
|
|
60
59
|
private sendError;
|
|
61
60
|
private allocId;
|
|
62
61
|
}
|
|
63
|
-
/** Internal deterministic seam used by the per-file coverage suite. */
|
|
64
|
-
export declare const protocolHandlerCoverage: {
|
|
65
|
-
errorToJsonRpc: typeof errorToJsonRpc;
|
|
66
|
-
};
|
|
67
62
|
//# sourceMappingURL=protocol-handler.d.ts.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ACPServerAgentTurn — `RunTurn` adapter for the v1 server side.
|
|
3
|
+
*
|
|
4
|
+
* Wires the ACP v1 server (`ACPProtocolHandler`) to a core `Agent`.
|
|
5
|
+
* Each session gets its own `Agent` instance (per spec: sessions are
|
|
6
|
+
* isolated; sharing agents across sessions would defeat isolation).
|
|
7
|
+
* The agent is created lazily on the first `session/prompt` and
|
|
8
|
+
* torn down when the server is closed or the session is removed.
|
|
9
|
+
*
|
|
10
|
+
* The adapter:
|
|
11
|
+
* - converts the ACP `ContentBlock[]` prompt into a single string
|
|
12
|
+
* (concatenating text blocks; non-text blocks are recorded as a
|
|
13
|
+
* note in the prompt — future work can route images / audio to
|
|
14
|
+
* the appropriate provider)
|
|
15
|
+
* - calls `agent.run(prompt, {signal})` to drive the core loop
|
|
16
|
+
* - captures the agent's text result and emits it as one or more
|
|
17
|
+
* `agent_message_chunk` notifications
|
|
18
|
+
* - maps the agent's stop semantics to a v1 `StopReason`
|
|
19
|
+
*
|
|
20
|
+
* Streaming: the core `Agent` API is not currently token-streamed
|
|
21
|
+
* through this surface (its `run()` returns a final `RunResult`).
|
|
22
|
+
* v1 clients expect text deltas, but most implementations batch
|
|
23
|
+
* them — a single chunk per turn is acceptable. A future
|
|
24
|
+
* enhancement can use the Agent's `Renderer` interface to capture
|
|
25
|
+
* deltas as they're written, then forward them as multiple chunks.
|
|
26
|
+
*
|
|
27
|
+
* Scope: the adapter is deliberately minimal. It does NOT:
|
|
28
|
+
* - model the full conversation history across turns (the v1 spec
|
|
29
|
+
* leaves this to the agent; on the next prompt we re-feed the
|
|
30
|
+
* latest user message and the agent handles its own history)
|
|
31
|
+
* - use the agent's tool registry, permission policy, or
|
|
32
|
+
* extensions (this adapter is the lowest-fidelity integration;
|
|
33
|
+
* a future PR can wire a richer session-aware agent)
|
|
34
|
+
* - stream deltas token-by-token (see "Streaming" above)
|
|
35
|
+
*
|
|
36
|
+
* Cancellation: the parent `AbortSignal` propagates through
|
|
37
|
+
* `agent.run({signal})` and the underlying provider call observes
|
|
38
|
+
* it. On abort, the adapter maps the resulting `AbortError` to
|
|
39
|
+
* `{stopReason: 'cancelled'}`.
|
|
40
|
+
*
|
|
41
|
+
* Background delegations: a background `delegate` result is queued for the
|
|
42
|
+
* session's leader and injected by the core agent loop at its next iteration
|
|
43
|
+
* boundary — on ACP that is the next `session/prompt`, because ACP never
|
|
44
|
+
* starts a turn on its own. So the client is not left guessing, the adapter
|
|
45
|
+
* listens for `leader.delivery_pending` on the session agent's event bus and,
|
|
46
|
+
* while no turn is running, sends one coalesced unprompted `session/update`
|
|
47
|
+
* ("Background delegation <id> finished; send any message to continue.")
|
|
48
|
+
* through `RunTurnApi.sendSessionUpdate`. Nothing is sent after the session is
|
|
49
|
+
* disposed, and a notice failure never reaches the event bus.
|
|
50
|
+
*/
|
|
51
|
+
import type { Agent, AgentInput } from '@wrongstack/core/agent';
|
|
52
|
+
import type { ContentBlock, PlanEntry, StopReason, ToolKind, UsageCost } from '../types/acp-v1.js';
|
|
53
|
+
/**
|
|
54
|
+
* Prime a freshly-created agent's conversation state with restored history.
|
|
55
|
+
* Each recorded user/agent chunk becomes a `user`/`assistant` message so the
|
|
56
|
+
* model continues the prior conversation instead of starting blank.
|
|
57
|
+
*/
|
|
58
|
+
export declare function seedAgentContext(agent: Agent, history: ReadonlyArray<{
|
|
59
|
+
sessionUpdate: string;
|
|
60
|
+
content: unknown;
|
|
61
|
+
}>): void;
|
|
62
|
+
/** Map a WrongStack tool name to the closest ACP ToolKind for UI grouping. */
|
|
63
|
+
export declare function toolNameToKind(name: string): ToolKind;
|
|
64
|
+
/** A short, human-readable title for a tool call card. */
|
|
65
|
+
export declare function toolTitle(name: string, input: unknown): string;
|
|
66
|
+
export declare function isRecord(v: unknown): v is Record<string, unknown>;
|
|
67
|
+
/**
|
|
68
|
+
* Convert an ACP `ContentBlock[]` prompt into a core `AgentInput`.
|
|
69
|
+
*
|
|
70
|
+
* When the prompt is all text we return a plain string (the common,
|
|
71
|
+
* cheapest path). When it carries images we build a multimodal
|
|
72
|
+
* `ContentBlock[]` the provider can pass to a vision-capable model.
|
|
73
|
+
* Images go through core's shared ingest (allowlist, base64, size and count
|
|
74
|
+
* caps, the bytes decide the media type): the client's label used to reach
|
|
75
|
+
* the provider unchecked. Every other block renders as in the text path, so
|
|
76
|
+
* an embedded resource keeps its `[embedded resource: <uri>]` header.
|
|
77
|
+
*/
|
|
78
|
+
export declare function promptToAgentInput(blocks: readonly ContentBlock[]): AgentInput;
|
|
79
|
+
/**
|
|
80
|
+
* Convert an ACP `ContentBlock[]` prompt to a single user-message
|
|
81
|
+
* string. Text blocks are concatenated; image / audio / resource
|
|
82
|
+
* blocks are recorded as a bracketed placeholder (full multimodal
|
|
83
|
+
* support is a future PR — the adapter is v1-text only for now).
|
|
84
|
+
*/
|
|
85
|
+
export declare function promptToText(blocks: readonly ContentBlock[]): string;
|
|
86
|
+
/**
|
|
87
|
+
* Extract the agent's final text from a `RunResult`. The shape
|
|
88
|
+
* varies across core versions, so we read the most common fields
|
|
89
|
+
* defensively and concatenate whatever text we find.
|
|
90
|
+
*/
|
|
91
|
+
export declare function extractText(result: unknown): string;
|
|
92
|
+
/**
|
|
93
|
+
* Map a `RunResult` (and the parent signal) to a v1 `StopReason`.
|
|
94
|
+
*
|
|
95
|
+
* If the parent signal was aborted, return `'cancelled'`. Otherwise
|
|
96
|
+
* the agent completed normally — we treat any non-error result
|
|
97
|
+
* as `'end_turn'`. The core `RunResult` doesn't currently surface
|
|
98
|
+
* a per-turn stop reason, so v1's `'max_tokens'`, `'max_turn_requests'`,
|
|
99
|
+
* and `'refusal'` discriminators can't be emitted precisely; we
|
|
100
|
+
* log a warning if the result carries an error and return the
|
|
101
|
+
* generic end_turn.
|
|
102
|
+
*/
|
|
103
|
+
export declare function pickStopReason(result: unknown, signal: AbortSignal): StopReason;
|
|
104
|
+
/**
|
|
105
|
+
* Extract a plan from the agent's RunResult, if available.
|
|
106
|
+
* The plan is an array of PlanEntry objects.
|
|
107
|
+
*/
|
|
108
|
+
export declare function extractPlan(result: unknown): PlanEntry[];
|
|
109
|
+
/**
|
|
110
|
+
* Extract usage/token info from the agent's RunResult, if available.
|
|
111
|
+
*/
|
|
112
|
+
export declare function extractUsage(result: unknown): {
|
|
113
|
+
used: number;
|
|
114
|
+
size: number;
|
|
115
|
+
cost?: UsageCost | undefined;
|
|
116
|
+
} | null;
|
|
117
|
+
//# sourceMappingURL=server-agent-turn-content.d.ts.map
|
|
@@ -48,9 +48,10 @@
|
|
|
48
48
|
* through `RunTurnApi.sendSessionUpdate`. Nothing is sent after the session is
|
|
49
49
|
* disposed, and a notice failure never reaches the event bus.
|
|
50
50
|
*/
|
|
51
|
-
import type { Agent
|
|
52
|
-
import type {
|
|
51
|
+
import type { Agent } from '@wrongstack/core/agent';
|
|
52
|
+
import type { McpServer } from '../types/acp-v1.js';
|
|
53
53
|
import type { RunTurn, RunTurnApi, RunTurnResult } from './protocol-handler.js';
|
|
54
|
+
import { extractPlan, extractText, extractUsage, isRecord, pickStopReason, promptToAgentInput, promptToText, seedAgentContext, toolNameToKind, toolTitle } from './server-agent-turn-content.js';
|
|
54
55
|
export interface ACPServerAgentTurnOptions {
|
|
55
56
|
/**
|
|
56
57
|
* Factory that creates a fresh `Agent` for a given session.
|
|
@@ -105,7 +106,7 @@ interface SessionReplayUpdate {
|
|
|
105
106
|
* A `RunTurn` that also exposes the recorded per-session history so the
|
|
106
107
|
* server can replay it on `session/load`.
|
|
107
108
|
*/
|
|
108
|
-
|
|
109
|
+
interface ACPServerAgentTurn {
|
|
109
110
|
(input: Parameters<RunTurn>[0], emit: Parameters<RunTurn>[1], api?: Parameters<RunTurn>[2]): Promise<RunTurnResult>;
|
|
110
111
|
/** Recorded user/agent text turns for a session, in order. */
|
|
111
112
|
replay(sessionId: string): SessionReplayUpdate[];
|
|
@@ -142,32 +143,6 @@ declare function agentOwnsSession(agent: Agent, acpSessionId: string, eventSessi
|
|
|
142
143
|
declare function deliveryNoticeText(deliveryIds: readonly string[]): string;
|
|
143
144
|
declare function trimHistory(entries: SessionReplayUpdate[], retainedBytes: number, maxEntries: number, maxBytes: number): number;
|
|
144
145
|
declare function replayEntryBytes(entry: SessionReplayUpdate): number;
|
|
145
|
-
/**
|
|
146
|
-
* Prime a freshly-created agent's conversation state with restored history.
|
|
147
|
-
* Each recorded user/agent chunk becomes a `user`/`assistant` message so the
|
|
148
|
-
* model continues the prior conversation instead of starting blank.
|
|
149
|
-
*/
|
|
150
|
-
declare function seedAgentContext(agent: Agent, history: ReadonlyArray<{
|
|
151
|
-
sessionUpdate: string;
|
|
152
|
-
content: unknown;
|
|
153
|
-
}>): void;
|
|
154
|
-
/** Map a WrongStack tool name to the closest ACP ToolKind for UI grouping. */
|
|
155
|
-
declare function toolNameToKind(name: string): ToolKind;
|
|
156
|
-
/** A short, human-readable title for a tool call card. */
|
|
157
|
-
declare function toolTitle(name: string, input: unknown): string;
|
|
158
|
-
declare function isRecord(v: unknown): v is Record<string, unknown>;
|
|
159
|
-
/**
|
|
160
|
-
* Convert an ACP `ContentBlock[]` prompt into a core `AgentInput`.
|
|
161
|
-
*
|
|
162
|
-
* When the prompt is all text we return a plain string (the common,
|
|
163
|
-
* cheapest path). When it carries images we build a multimodal
|
|
164
|
-
* `ContentBlock[]` the provider can pass to a vision-capable model.
|
|
165
|
-
* Images go through core's shared ingest (allowlist, base64, size and count
|
|
166
|
-
* caps, the bytes decide the media type): the client's label used to reach
|
|
167
|
-
* the provider unchecked. Every other block renders as in the text path, so
|
|
168
|
-
* an embedded resource keeps its `[embedded resource: <uri>]` header.
|
|
169
|
-
*/
|
|
170
|
-
declare function promptToAgentInput(blocks: readonly ContentBlock[]): AgentInput;
|
|
171
146
|
/**
|
|
172
147
|
* Tear down the agents and timers held by a turn factory. The
|
|
173
148
|
* server's `close()` should call this so child connections don't
|
|
@@ -176,44 +151,6 @@ declare function promptToAgentInput(blocks: readonly ContentBlock[]): AgentInput
|
|
|
176
151
|
export declare function disposeACPServerAgentTurn(opts: {
|
|
177
152
|
agents: Map<string, Agent>;
|
|
178
153
|
}): Promise<void>;
|
|
179
|
-
/**
|
|
180
|
-
* Convert an ACP `ContentBlock[]` prompt to a single user-message
|
|
181
|
-
* string. Text blocks are concatenated; image / audio / resource
|
|
182
|
-
* blocks are recorded as a bracketed placeholder (full multimodal
|
|
183
|
-
* support is a future PR — the adapter is v1-text only for now).
|
|
184
|
-
*/
|
|
185
|
-
declare function promptToText(blocks: readonly ContentBlock[]): string;
|
|
186
|
-
/**
|
|
187
|
-
* Extract the agent's final text from a `RunResult`. The shape
|
|
188
|
-
* varies across core versions, so we read the most common fields
|
|
189
|
-
* defensively and concatenate whatever text we find.
|
|
190
|
-
*/
|
|
191
|
-
declare function extractText(result: unknown): string;
|
|
192
|
-
/**
|
|
193
|
-
* Map a `RunResult` (and the parent signal) to a v1 `StopReason`.
|
|
194
|
-
*
|
|
195
|
-
* If the parent signal was aborted, return `'cancelled'`. Otherwise
|
|
196
|
-
* the agent completed normally — we treat any non-error result
|
|
197
|
-
* as `'end_turn'`. The core `RunResult` doesn't currently surface
|
|
198
|
-
* a per-turn stop reason, so v1's `'max_tokens'`, `'max_turn_requests'`,
|
|
199
|
-
* and `'refusal'` discriminators can't be emitted precisely; we
|
|
200
|
-
* log a warning if the result carries an error and return the
|
|
201
|
-
* generic end_turn.
|
|
202
|
-
*/
|
|
203
|
-
declare function pickStopReason(result: unknown, signal: AbortSignal): StopReason;
|
|
204
|
-
/**
|
|
205
|
-
* Extract a plan from the agent's RunResult, if available.
|
|
206
|
-
* The plan is an array of PlanEntry objects.
|
|
207
|
-
*/
|
|
208
|
-
declare function extractPlan(result: unknown): PlanEntry[];
|
|
209
|
-
/**
|
|
210
|
-
* Extract usage/token info from the agent's RunResult, if available.
|
|
211
|
-
*/
|
|
212
|
-
declare function extractUsage(result: unknown): {
|
|
213
|
-
used: number;
|
|
214
|
-
size: number;
|
|
215
|
-
cost?: UsageCost | undefined;
|
|
216
|
-
} | null;
|
|
217
154
|
/** Internal deterministic seams used by the per-file coverage suite. */
|
|
218
155
|
export declare const serverAgentTurnCoverage: {
|
|
219
156
|
finitePositiveLimit: typeof finitePositiveLimit;
|
package/dist/agent.js
CHANGED
|
@@ -999,8 +999,145 @@ var ACPProtocolHandler = class {
|
|
|
999
999
|
}
|
|
1000
1000
|
};
|
|
1001
1001
|
|
|
1002
|
-
// src/agent/server-agent-turn.ts
|
|
1002
|
+
// src/agent/server-agent-turn-content.ts
|
|
1003
1003
|
import { parseIncomingImages } from "@wrongstack/core/utils";
|
|
1004
|
+
function seedAgentContext(agent, history) {
|
|
1005
|
+
const state = agent.ctx?.state;
|
|
1006
|
+
if (!state?.appendMessage) return;
|
|
1007
|
+
for (const u of history) {
|
|
1008
|
+
const text = u.content?.text;
|
|
1009
|
+
if (typeof text !== "string" || text.length === 0) continue;
|
|
1010
|
+
const role = u.sessionUpdate === "user_message_chunk" ? "user" : "assistant";
|
|
1011
|
+
state.appendMessage({ role, content: text });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
function toolNameToKind(name) {
|
|
1015
|
+
const n = name.toLowerCase();
|
|
1016
|
+
if (n.includes("read") || n.includes("cat")) return "read";
|
|
1017
|
+
if (n.includes("write") || n.includes("edit") || n.includes("apply") || n.includes("patch"))
|
|
1018
|
+
return "edit";
|
|
1019
|
+
if (n.includes("delete") || n === "rm" || n.startsWith("rm_") || n.endsWith("_rm"))
|
|
1020
|
+
return "delete";
|
|
1021
|
+
if (n.includes("move") || n.includes("rename") || n.includes("mv")) return "move";
|
|
1022
|
+
if (n.includes("grep") || n.includes("glob") || n.includes("search") || n.includes("find"))
|
|
1023
|
+
return "search";
|
|
1024
|
+
if (n.includes("bash") || n.includes("shell") || n.includes("exec") || n.includes("run") || n.includes("terminal"))
|
|
1025
|
+
return "execute";
|
|
1026
|
+
if (n.includes("fetch") || n.includes("http") || n.includes("web") || n.includes("url"))
|
|
1027
|
+
return "fetch";
|
|
1028
|
+
if (n.includes("think") || n.includes("plan")) return "think";
|
|
1029
|
+
return "other";
|
|
1030
|
+
}
|
|
1031
|
+
function toolTitle(name, input) {
|
|
1032
|
+
if (isRecord(input)) {
|
|
1033
|
+
const path3 = input.path ?? input.file ?? input.filePath ?? input.pattern ?? input.command;
|
|
1034
|
+
if (typeof path3 === "string" && path3.length > 0) {
|
|
1035
|
+
return `${name}: ${path3.length > 80 ? `${path3.slice(0, 77)}\u2026` : path3}`;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return name;
|
|
1039
|
+
}
|
|
1040
|
+
function isRecord(v) {
|
|
1041
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1042
|
+
}
|
|
1043
|
+
function promptToAgentInput(blocks) {
|
|
1044
|
+
const images = blocks.filter((b) => b.type === "image");
|
|
1045
|
+
if (images.length === 0) {
|
|
1046
|
+
return promptToText(blocks);
|
|
1047
|
+
}
|
|
1048
|
+
let parsed;
|
|
1049
|
+
try {
|
|
1050
|
+
parsed = parseIncomingImages(images.map((b) => ({ data: b.data, mediaType: b.mimeType })));
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
throw Object.assign(new Error(err.message), { code: -32602 });
|
|
1053
|
+
}
|
|
1054
|
+
const out = [];
|
|
1055
|
+
let next = 0;
|
|
1056
|
+
for (const b of blocks) {
|
|
1057
|
+
if (b.type === "image") out.push(parsed[next++]);
|
|
1058
|
+
else if (b.type === "text") out.push({ type: "text", text: b.text });
|
|
1059
|
+
else {
|
|
1060
|
+
const text = promptToText([b]);
|
|
1061
|
+
if (text) out.push({ type: "text", text });
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
return out;
|
|
1065
|
+
}
|
|
1066
|
+
function promptToText(blocks) {
|
|
1067
|
+
const parts = [];
|
|
1068
|
+
for (const b of blocks) {
|
|
1069
|
+
if (b.type === "text") {
|
|
1070
|
+
parts.push(b.text);
|
|
1071
|
+
} else if (b.type === "image") {
|
|
1072
|
+
parts.push(`[image: ${b.mimeType}]`);
|
|
1073
|
+
} else if (b.type === "audio") {
|
|
1074
|
+
parts.push(`[audio: ${b.mimeType}]`);
|
|
1075
|
+
} else if (b.type === "resource") {
|
|
1076
|
+
parts.push(
|
|
1077
|
+
"text" in b.resource && typeof b.resource.text === "string" ? `[embedded resource: ${b.resource.uri}]
|
|
1078
|
+
${b.resource.text}` : `[embedded resource: ${b.resource.uri}]`
|
|
1079
|
+
);
|
|
1080
|
+
} else if (b.type === "resource_link") {
|
|
1081
|
+
parts.push(`[resource link: ${b.uri}]`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return parts.join("\n").trim();
|
|
1085
|
+
}
|
|
1086
|
+
function extractText(result) {
|
|
1087
|
+
if (typeof result !== "object" || result === null) return "";
|
|
1088
|
+
const r = result;
|
|
1089
|
+
if (typeof r.text === "string") return r.text;
|
|
1090
|
+
if (Array.isArray(r.content)) {
|
|
1091
|
+
const parts = [];
|
|
1092
|
+
for (const c of r.content) {
|
|
1093
|
+
if (typeof c === "object" && c !== null) {
|
|
1094
|
+
const cb = c;
|
|
1095
|
+
if (cb.type === "text" && typeof cb.text === "string") parts.push(cb.text);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return parts.join("");
|
|
1099
|
+
}
|
|
1100
|
+
return "";
|
|
1101
|
+
}
|
|
1102
|
+
function pickStopReason(result, signal) {
|
|
1103
|
+
if (signal.aborted) return "cancelled";
|
|
1104
|
+
if (typeof result !== "object" || result === null) return "end_turn";
|
|
1105
|
+
const r = result;
|
|
1106
|
+
if (r.error) {
|
|
1107
|
+
return "end_turn";
|
|
1108
|
+
}
|
|
1109
|
+
if (typeof r.stopReason === "string" && r.stopReason) {
|
|
1110
|
+
return r.stopReason;
|
|
1111
|
+
}
|
|
1112
|
+
return "end_turn";
|
|
1113
|
+
}
|
|
1114
|
+
function extractPlan(result) {
|
|
1115
|
+
if (typeof result !== "object" || result === null) return [];
|
|
1116
|
+
const r = result;
|
|
1117
|
+
if (Array.isArray(r.plan)) {
|
|
1118
|
+
return r.plan.filter(
|
|
1119
|
+
(e) => typeof e === "object" && e !== null && typeof e.content === "string"
|
|
1120
|
+
);
|
|
1121
|
+
}
|
|
1122
|
+
return [];
|
|
1123
|
+
}
|
|
1124
|
+
function extractUsage(result) {
|
|
1125
|
+
if (typeof result !== "object" || result === null) return null;
|
|
1126
|
+
const r = result;
|
|
1127
|
+
if (typeof r.usage === "object" && r.usage !== null) {
|
|
1128
|
+
const u = r.usage;
|
|
1129
|
+
if (typeof u.used === "number" && Number.isFinite(u.used) && typeof u.size === "number" && Number.isFinite(u.size)) {
|
|
1130
|
+
return {
|
|
1131
|
+
used: u.used,
|
|
1132
|
+
size: u.size,
|
|
1133
|
+
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// src/agent/server-agent-turn.ts
|
|
1004
1141
|
function makeACPServerAgentTurn(opts) {
|
|
1005
1142
|
const agents = /* @__PURE__ */ new Map();
|
|
1006
1143
|
const timeouts = /* @__PURE__ */ new Map();
|
|
@@ -1320,141 +1457,6 @@ function trimHistory(entries, retainedBytes, maxEntries, maxBytes) {
|
|
|
1320
1457
|
function replayEntryBytes(entry) {
|
|
1321
1458
|
return Buffer.byteLength(JSON.stringify(entry), "utf8");
|
|
1322
1459
|
}
|
|
1323
|
-
function seedAgentContext(agent, history) {
|
|
1324
|
-
const state = agent.ctx?.state;
|
|
1325
|
-
if (!state?.appendMessage) return;
|
|
1326
|
-
for (const u of history) {
|
|
1327
|
-
const text = u.content?.text;
|
|
1328
|
-
if (typeof text !== "string" || text.length === 0) continue;
|
|
1329
|
-
const role = u.sessionUpdate === "user_message_chunk" ? "user" : "assistant";
|
|
1330
|
-
state.appendMessage({ role, content: text });
|
|
1331
|
-
}
|
|
1332
|
-
}
|
|
1333
|
-
function toolNameToKind(name) {
|
|
1334
|
-
const n = name.toLowerCase();
|
|
1335
|
-
if (n.includes("read") || n.includes("cat")) return "read";
|
|
1336
|
-
if (n.includes("write") || n.includes("edit") || n.includes("apply") || n.includes("patch"))
|
|
1337
|
-
return "edit";
|
|
1338
|
-
if (n.includes("delete") || n === "rm" || n.startsWith("rm_") || n.endsWith("_rm"))
|
|
1339
|
-
return "delete";
|
|
1340
|
-
if (n.includes("move") || n.includes("rename") || n.includes("mv")) return "move";
|
|
1341
|
-
if (n.includes("grep") || n.includes("glob") || n.includes("search") || n.includes("find"))
|
|
1342
|
-
return "search";
|
|
1343
|
-
if (n.includes("bash") || n.includes("shell") || n.includes("exec") || n.includes("run") || n.includes("terminal"))
|
|
1344
|
-
return "execute";
|
|
1345
|
-
if (n.includes("fetch") || n.includes("http") || n.includes("web") || n.includes("url"))
|
|
1346
|
-
return "fetch";
|
|
1347
|
-
if (n.includes("think") || n.includes("plan")) return "think";
|
|
1348
|
-
return "other";
|
|
1349
|
-
}
|
|
1350
|
-
function toolTitle(name, input) {
|
|
1351
|
-
if (isRecord(input)) {
|
|
1352
|
-
const path3 = input.path ?? input.file ?? input.filePath ?? input.pattern ?? input.command;
|
|
1353
|
-
if (typeof path3 === "string" && path3.length > 0) {
|
|
1354
|
-
return `${name}: ${path3.length > 80 ? `${path3.slice(0, 77)}\u2026` : path3}`;
|
|
1355
|
-
}
|
|
1356
|
-
}
|
|
1357
|
-
return name;
|
|
1358
|
-
}
|
|
1359
|
-
function isRecord(v) {
|
|
1360
|
-
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
1361
|
-
}
|
|
1362
|
-
function promptToAgentInput(blocks) {
|
|
1363
|
-
const images = blocks.filter((b) => b.type === "image");
|
|
1364
|
-
if (images.length === 0) {
|
|
1365
|
-
return promptToText(blocks);
|
|
1366
|
-
}
|
|
1367
|
-
let parsed;
|
|
1368
|
-
try {
|
|
1369
|
-
parsed = parseIncomingImages(images.map((b) => ({ data: b.data, mediaType: b.mimeType })));
|
|
1370
|
-
} catch (err) {
|
|
1371
|
-
throw Object.assign(new Error(err.message), { code: -32602 });
|
|
1372
|
-
}
|
|
1373
|
-
const out = [];
|
|
1374
|
-
let next = 0;
|
|
1375
|
-
for (const b of blocks) {
|
|
1376
|
-
if (b.type === "image") out.push(parsed[next++]);
|
|
1377
|
-
else if (b.type === "text") out.push({ type: "text", text: b.text });
|
|
1378
|
-
else {
|
|
1379
|
-
const text = promptToText([b]);
|
|
1380
|
-
if (text) out.push({ type: "text", text });
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
return out;
|
|
1384
|
-
}
|
|
1385
|
-
function promptToText(blocks) {
|
|
1386
|
-
const parts = [];
|
|
1387
|
-
for (const b of blocks) {
|
|
1388
|
-
if (b.type === "text") {
|
|
1389
|
-
parts.push(b.text);
|
|
1390
|
-
} else if (b.type === "image") {
|
|
1391
|
-
parts.push(`[image: ${b.mimeType}]`);
|
|
1392
|
-
} else if (b.type === "audio") {
|
|
1393
|
-
parts.push(`[audio: ${b.mimeType}]`);
|
|
1394
|
-
} else if (b.type === "resource") {
|
|
1395
|
-
parts.push(
|
|
1396
|
-
"text" in b.resource && typeof b.resource.text === "string" ? `[embedded resource: ${b.resource.uri}]
|
|
1397
|
-
${b.resource.text}` : `[embedded resource: ${b.resource.uri}]`
|
|
1398
|
-
);
|
|
1399
|
-
} else if (b.type === "resource_link") {
|
|
1400
|
-
parts.push(`[resource link: ${b.uri}]`);
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
return parts.join("\n").trim();
|
|
1404
|
-
}
|
|
1405
|
-
function extractText(result) {
|
|
1406
|
-
if (typeof result !== "object" || result === null) return "";
|
|
1407
|
-
const r = result;
|
|
1408
|
-
if (typeof r.text === "string") return r.text;
|
|
1409
|
-
if (Array.isArray(r.content)) {
|
|
1410
|
-
const parts = [];
|
|
1411
|
-
for (const c of r.content) {
|
|
1412
|
-
if (typeof c === "object" && c !== null) {
|
|
1413
|
-
const cb = c;
|
|
1414
|
-
if (cb.type === "text" && typeof cb.text === "string") parts.push(cb.text);
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
return parts.join("");
|
|
1418
|
-
}
|
|
1419
|
-
return "";
|
|
1420
|
-
}
|
|
1421
|
-
function pickStopReason(result, signal) {
|
|
1422
|
-
if (signal.aborted) return "cancelled";
|
|
1423
|
-
if (typeof result !== "object" || result === null) return "end_turn";
|
|
1424
|
-
const r = result;
|
|
1425
|
-
if (r.error) {
|
|
1426
|
-
return "end_turn";
|
|
1427
|
-
}
|
|
1428
|
-
if (typeof r.stopReason === "string" && r.stopReason) {
|
|
1429
|
-
return r.stopReason;
|
|
1430
|
-
}
|
|
1431
|
-
return "end_turn";
|
|
1432
|
-
}
|
|
1433
|
-
function extractPlan(result) {
|
|
1434
|
-
if (typeof result !== "object" || result === null) return [];
|
|
1435
|
-
const r = result;
|
|
1436
|
-
if (Array.isArray(r.plan)) {
|
|
1437
|
-
return r.plan.filter(
|
|
1438
|
-
(e) => typeof e === "object" && e !== null && typeof e.content === "string"
|
|
1439
|
-
);
|
|
1440
|
-
}
|
|
1441
|
-
return [];
|
|
1442
|
-
}
|
|
1443
|
-
function extractUsage(result) {
|
|
1444
|
-
if (typeof result !== "object" || result === null) return null;
|
|
1445
|
-
const r = result;
|
|
1446
|
-
if (typeof r.usage === "object" && r.usage !== null) {
|
|
1447
|
-
const u = r.usage;
|
|
1448
|
-
if (typeof u.used === "number" && Number.isFinite(u.used) && typeof u.size === "number" && Number.isFinite(u.size)) {
|
|
1449
|
-
return {
|
|
1450
|
-
used: u.used,
|
|
1451
|
-
size: u.size,
|
|
1452
|
-
...typeof u.cost === "object" && u.cost !== null ? { cost: u.cost } : {}
|
|
1453
|
-
};
|
|
1454
|
-
}
|
|
1455
|
-
}
|
|
1456
|
-
return null;
|
|
1457
|
-
}
|
|
1458
1460
|
|
|
1459
1461
|
// src/agent/session-store.ts
|
|
1460
1462
|
import * as fsp2 from "node:fs/promises";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ACPClientTransport } from '../agent/stdio-transport.js';
|
|
2
|
+
import type { AgentCapabilities, ContentBlock, SessionId } from '../types/acp-v1.js';
|
|
3
|
+
import type { State } from './acp-request-state.js';
|
|
4
|
+
import type { ACPProgressHandler, ACPSessionRunResult } from './acp-session-types.js';
|
|
5
|
+
import type { ACPSessionScratch } from './acp-session-updates.js';
|
|
6
|
+
export interface AcpSessionPromptHost {
|
|
7
|
+
closed: boolean;
|
|
8
|
+
state: State;
|
|
9
|
+
promptCallbackAbort: AbortController | null;
|
|
10
|
+
agentCapabilities: AgentCapabilities;
|
|
11
|
+
sessionId: SessionId | null;
|
|
12
|
+
transport: ACPClientTransport;
|
|
13
|
+
createSessionWithAuth(): Promise<SessionId>;
|
|
14
|
+
cancelLateSession(createPromise: Promise<SessionId>): void;
|
|
15
|
+
resetScratch(): void;
|
|
16
|
+
progressHandler: ACPProgressHandler | null;
|
|
17
|
+
allocId(): number;
|
|
18
|
+
sendRequest(id: number, method: string, params: unknown, timeoutMs?: number): Promise<unknown>;
|
|
19
|
+
timeoutMs: number;
|
|
20
|
+
scratch: ACPSessionScratch;
|
|
21
|
+
}
|
|
22
|
+
export declare function prompt(host: AcpSessionPromptHost, blocks: ContentBlock[], signal: AbortSignal, onProgress?: ACPProgressHandler): Promise<ACPSessionRunResult>;
|
|
23
|
+
//# sourceMappingURL=acp-session-prompt.d.ts.map
|