@automatalabs/pi-acp 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +57 -0
  3. package/dist/agent.d.ts +87 -0
  4. package/dist/agent.d.ts.map +1 -0
  5. package/dist/agent.js +398 -0
  6. package/dist/auth.d.ts +4 -0
  7. package/dist/auth.d.ts.map +1 -0
  8. package/dist/auth.js +40 -0
  9. package/dist/config.d.ts +7 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +46 -0
  12. package/dist/deps.d.ts +22 -0
  13. package/dist/deps.d.ts.map +1 -0
  14. package/dist/deps.js +39 -0
  15. package/dist/errors.d.ts +34 -0
  16. package/dist/errors.d.ts.map +1 -0
  17. package/dist/errors.js +104 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +54 -0
  21. package/dist/lib.d.ts +4 -0
  22. package/dist/lib.d.ts.map +1 -0
  23. package/dist/lib.js +2 -0
  24. package/dist/mcp-bridge.d.ts +58 -0
  25. package/dist/mcp-bridge.d.ts.map +1 -0
  26. package/dist/mcp-bridge.js +240 -0
  27. package/dist/permissions.d.ts +11 -0
  28. package/dist/permissions.d.ts.map +1 -0
  29. package/dist/permissions.js +77 -0
  30. package/dist/prompt-content.d.ts +12 -0
  31. package/dist/prompt-content.d.ts.map +1 -0
  32. package/dist/prompt-content.js +35 -0
  33. package/dist/replay.d.ts +4 -0
  34. package/dist/replay.d.ts.map +1 -0
  35. package/dist/replay.js +101 -0
  36. package/dist/server.d.ts +13 -0
  37. package/dist/server.d.ts.map +1 -0
  38. package/dist/server.js +23 -0
  39. package/dist/session.d.ts +58 -0
  40. package/dist/session.d.ts.map +1 -0
  41. package/dist/session.js +414 -0
  42. package/dist/stop-reason.d.ts +4 -0
  43. package/dist/stop-reason.d.ts.map +1 -0
  44. package/dist/stop-reason.js +18 -0
  45. package/dist/structured-output.d.ts +14 -0
  46. package/dist/structured-output.d.ts.map +1 -0
  47. package/dist/structured-output.js +71 -0
  48. package/dist/translate.d.ts +20 -0
  49. package/dist/translate.d.ts.map +1 -0
  50. package/dist/translate.js +110 -0
  51. package/dist/usage.d.ts +35 -0
  52. package/dist/usage.d.ts.map +1 -0
  53. package/dist/usage.js +36 -0
  54. package/package.json +47 -0
package/dist/replay.js ADDED
@@ -0,0 +1,101 @@
1
+ import { mapKind, toContent } from "./translate.js";
2
+ function blocks(content) {
3
+ if (typeof content === "string")
4
+ return [{ type: "text", text: content }];
5
+ return (content ?? []).map((item) => item.type === "text"
6
+ ? { type: "text", text: item.text }
7
+ : { type: "image", data: item.data, mimeType: item.mimeType });
8
+ }
9
+ function bashExecutionToText(message) {
10
+ let text = `Ran \`${message.command ?? ""}\`\n`;
11
+ text += message.output ? `\`\`\`\n${message.output}\n\`\`\`` : "(no output)";
12
+ if (message.cancelled)
13
+ text += "\n\n(command cancelled)";
14
+ else if (message.exitCode !== null && message.exitCode !== undefined && message.exitCode !== 0) {
15
+ text += `\n\nCommand exited with code ${message.exitCode}`;
16
+ }
17
+ if (message.truncated && message.fullOutputPath) {
18
+ text += `\n\n[Output truncated. Full output: ${message.fullOutputPath}]`;
19
+ }
20
+ return text;
21
+ }
22
+ function replayMessage(message) {
23
+ switch (message.role) {
24
+ case "user":
25
+ return blocks(message.content).map((content) => ({
26
+ sessionUpdate: "user_message_chunk",
27
+ content,
28
+ }));
29
+ case "assistant": {
30
+ const updates = [];
31
+ for (const item of (message.content ?? [])) {
32
+ if (item.type === "text") {
33
+ updates.push({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: item.text } });
34
+ }
35
+ else if (item.type === "thinking") {
36
+ updates.push({ sessionUpdate: "agent_thought_chunk", content: { type: "text", text: item.thinking } });
37
+ }
38
+ else if (item.type === "toolCall") {
39
+ updates.push({
40
+ sessionUpdate: "tool_call",
41
+ toolCallId: item.id,
42
+ title: item.name,
43
+ kind: mapKind(item.name),
44
+ status: "pending",
45
+ rawInput: item.arguments,
46
+ _meta: { toolName: item.name },
47
+ });
48
+ }
49
+ }
50
+ return updates;
51
+ }
52
+ case "toolResult": {
53
+ const update = {
54
+ sessionUpdate: "tool_call_update",
55
+ toolCallId: message.toolCallId ?? "",
56
+ status: message.isError ? "failed" : "completed",
57
+ content: toContent({ content: message.content }),
58
+ };
59
+ if (message.details !== undefined)
60
+ update.rawOutput = message.details;
61
+ return [update];
62
+ }
63
+ case "bashExecution":
64
+ return [{ sessionUpdate: "agent_message_chunk", content: { type: "text", text: bashExecutionToText(message) } }];
65
+ case "custom":
66
+ return message.display
67
+ ? blocks(message.content).map((content) => ({
68
+ sessionUpdate: "agent_message_chunk",
69
+ content,
70
+ }))
71
+ : [];
72
+ case "branchSummary":
73
+ case "compactionSummary":
74
+ return [];
75
+ }
76
+ }
77
+ export function replayEntry(entry) {
78
+ switch (entry.type) {
79
+ case "message":
80
+ return replayMessage(entry.message);
81
+ case "custom_message":
82
+ return entry.display
83
+ ? blocks(entry.content).map((content) => ({
84
+ sessionUpdate: "user_message_chunk",
85
+ content,
86
+ }))
87
+ : [];
88
+ case "thinking_level_change":
89
+ case "model_change":
90
+ case "compaction":
91
+ case "branch_summary":
92
+ case "custom":
93
+ case "label":
94
+ case "session_info":
95
+ return [];
96
+ default: {
97
+ const exhaustive = entry;
98
+ return exhaustive;
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,13 @@
1
+ import { type Stream } from "@agentclientprotocol/sdk";
2
+ import { PiAcpAgent } from "./agent.js";
3
+ import { type PiAcpDeps } from "./deps.js";
4
+ export { PiAcpAgent } from "./agent.js";
5
+ export interface RunAcpOptions {
6
+ deps?: Partial<PiAcpDeps>;
7
+ stream?: Stream;
8
+ }
9
+ export declare function runAcp(options?: RunAcpOptions): Promise<{
10
+ connection: import("@agentclientprotocol/sdk").AgentConnection;
11
+ agent: PiAcpAgent;
12
+ }>;
13
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,MAAM,EACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAExD,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,MAAM,CAAC,OAAO,GAAE,aAAkB;;;GAoBvD"}
package/dist/server.js ADDED
@@ -0,0 +1,23 @@
1
+ import { Readable, Writable } from "node:stream";
2
+ import { agent as acpAgent, methods, ndJsonStream, } from "@agentclientprotocol/sdk";
3
+ import { PiAcpAgent } from "./agent.js";
4
+ import { resolveDeps } from "./deps.js";
5
+ export { PiAcpAgent } from "./agent.js";
6
+ export async function runAcp(options = {}) {
7
+ const impl = new PiAcpAgent(await resolveDeps(options.deps));
8
+ const app = acpAgent({ name: "@automatalabs/pi-acp" })
9
+ .onRequest(methods.agent.initialize, (context) => impl.initialize(context))
10
+ .onRequest(methods.agent.authenticate, (context) => impl.authenticate(context))
11
+ .onRequest(methods.agent.session.new, (context) => impl.newSession(context))
12
+ .onRequest(methods.agent.session.load, (context) => impl.loadSession(context))
13
+ .onRequest(methods.agent.session.resume, (context) => impl.resumeSession(context))
14
+ .onRequest(methods.agent.session.fork, (context) => impl.forkSession(context))
15
+ .onRequest(methods.agent.session.list, (context) => impl.listSessions(context))
16
+ .onRequest(methods.agent.session.close, (context) => impl.closeSession(context))
17
+ .onRequest(methods.agent.session.setConfigOption, (context) => impl.setConfigOption(context))
18
+ .onRequest(methods.agent.session.prompt, (context) => impl.prompt(context))
19
+ .onNotification(methods.agent.session.cancel, (context) => impl.cancel(context));
20
+ const stream = options.stream ?? ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
21
+ const connection = app.connect(stream);
22
+ return { connection, agent: impl };
23
+ }
@@ -0,0 +1,58 @@
1
+ import { type AgentContext, type PromptRequest, type PromptResponse, type SessionUpdate } from "@agentclientprotocol/sdk";
2
+ import type { AgentSession, SessionEntry, SessionManager } from "@earendil-works/pi-coding-agent";
3
+ import type { PiAcpDeps } from "./deps.js";
4
+ import { StructuredOutputState } from "./structured-output.js";
5
+ import { type McpClientHandle, type McpResultProjection } from "./mcp-bridge.js";
6
+ export interface PiSessionOptions {
7
+ sessionId: string;
8
+ session: AgentSession;
9
+ manager: SessionManager;
10
+ client: AgentContext;
11
+ deps: PiAcpDeps;
12
+ mcpClients: McpClientHandle[];
13
+ failedMcpResults: Map<string, McpResultProjection>;
14
+ structured: StructuredOutputState;
15
+ onWedged(sessionId: string, session: PiSession): Promise<void>;
16
+ }
17
+ export declare class PiSession {
18
+ readonly sessionId: string;
19
+ readonly pi: AgentSession;
20
+ readonly manager: SessionManager;
21
+ private readonly client;
22
+ private readonly deps;
23
+ private readonly mcpClients;
24
+ private readonly failedMcpResults;
25
+ private readonly structured;
26
+ private readonly onWedged;
27
+ private readonly pending;
28
+ private pump;
29
+ private pumpFailure;
30
+ private stopped;
31
+ private closing;
32
+ private disposed;
33
+ private unsubscribe;
34
+ private activeTurn;
35
+ constructor(options: PiSessionOptions);
36
+ get busy(): boolean;
37
+ configOptions(): import("@agentclientprotocol/sdk").SessionConfigOption[];
38
+ enqueue(update: SessionUpdate): void;
39
+ private startPump;
40
+ drain(): Promise<void>;
41
+ replay(entries: readonly SessionEntry[]): Promise<void>;
42
+ setConfig(configId: string, value: string | boolean): Promise<import("@agentclientprotocol/sdk").SessionConfigOption[]>;
43
+ private disarm;
44
+ private finish;
45
+ private notificationFailure;
46
+ private abortTurn;
47
+ private turnError;
48
+ private runTurnTask;
49
+ private cleanupWedged;
50
+ private startBackstop;
51
+ private handlePiResolved;
52
+ private handlePiRejected;
53
+ prompt(params: PromptRequest, requestSignal: AbortSignal): Promise<PromptResponse>;
54
+ cancel(): void;
55
+ dispose(): Promise<void>;
56
+ disposeResources(): Promise<void>;
57
+ }
58
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAElG,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAK3C,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAS/D,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,mBAAmB,EACzB,MAAM,iBAAiB,CAAC;AAiBzB,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACnD,UAAU,EAAE,qBAAqB,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChE;AAED,qBAAa,SAAS;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,YAAY,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAC;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAY;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAoB;IAC/C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAmC;IACpE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAwB;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+B;IACxD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,WAAW,CAAU;IAC7B,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,UAAU,CAAyB;gBAE/B,OAAO,EAAE,gBAAgB;IAqCrC,IAAI,IAAI,IAAI,OAAO,CAElB;IAED,aAAa;IAIb,OAAO,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI;IAMpC,OAAO,CAAC,SAAS;IAwBX,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAMtB,MAAM,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAWvD,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO;IAKzD,OAAO,CAAC,MAAM;IASd,OAAO,CAAC,MAAM;IAad,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,WAAW;YAOL,aAAa;IAQ3B,OAAO,CAAC,aAAa;YAqCP,gBAAgB;YAsBhB,gBAAgB;IAiBxB,MAAM,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,EAAE,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC;IAsFxF,MAAM,IAAI,IAAI;IAIR,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAWxB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;CAwBxC"}
@@ -0,0 +1,414 @@
1
+ import { methods, } from "@agentclientprotocol/sdk";
2
+ import { adapterError, classifyPreflight, isRequestError, unexpectedError } from "./errors.js";
3
+ import { applyConfig, thinkingLevelOption } from "./config.js";
4
+ import { convertPromptContent } from "./prompt-content.js";
5
+ import { replayEntry } from "./replay.js";
6
+ import { stopReasonFor } from "./stop-reason.js";
7
+ import { translateEvent } from "./translate.js";
8
+ import { agentMessages, promptUsage, terminalAssistant, usageUpdate, } from "./usage.js";
9
+ import { installPermissionWrapper } from "./permissions.js";
10
+ import { disposeMcpBridge, } from "./mcp-bridge.js";
11
+ export class PiSession {
12
+ sessionId;
13
+ pi;
14
+ manager;
15
+ client;
16
+ deps;
17
+ mcpClients;
18
+ failedMcpResults;
19
+ structured;
20
+ onWedged;
21
+ pending = [];
22
+ pump;
23
+ pumpFailure;
24
+ stopped = false;
25
+ closing = false;
26
+ disposed = false;
27
+ unsubscribe;
28
+ activeTurn;
29
+ constructor(options) {
30
+ this.sessionId = options.sessionId;
31
+ this.pi = options.session;
32
+ this.manager = options.manager;
33
+ this.client = options.client;
34
+ this.deps = options.deps;
35
+ this.mcpClients = options.mcpClients;
36
+ this.failedMcpResults = options.failedMcpResults;
37
+ this.structured = options.structured;
38
+ this.onWedged = options.onWedged;
39
+ installPermissionWrapper(this.pi, {
40
+ sessionId: this.sessionId,
41
+ client: this.client,
42
+ drain: () => this.drain(),
43
+ turnSignal: () => this.activeTurn?.controller.signal,
44
+ });
45
+ const innerAfterToolCall = this.pi.agent.afterToolCall;
46
+ this.pi.agent.afterToolCall = async (context, signal) => {
47
+ const innerResult = innerAfterToolCall ? await innerAfterToolCall(context, signal) : undefined;
48
+ const failedResult = this.failedMcpResults.get(context.toolCall.id);
49
+ if (!failedResult)
50
+ return innerResult;
51
+ return {
52
+ ...innerResult,
53
+ content: failedResult.content,
54
+ ...(failedResult.details === undefined ? {} : { details: failedResult.details }),
55
+ isError: true,
56
+ };
57
+ };
58
+ this.unsubscribe = this.pi.subscribe((event) => {
59
+ const failedResult = event.type === "tool_execution_end" && event.isError
60
+ ? this.failedMcpResults.get(event.toolCallId)
61
+ : undefined;
62
+ if (event.type === "tool_execution_end")
63
+ this.failedMcpResults.delete(event.toolCallId);
64
+ for (const update of translateEvent(event, failedResult))
65
+ this.enqueue(update);
66
+ });
67
+ }
68
+ get busy() {
69
+ return this.activeTurn !== undefined || this.closing;
70
+ }
71
+ configOptions() {
72
+ return [thinkingLevelOption(this.pi)];
73
+ }
74
+ enqueue(update) {
75
+ if (this.stopped)
76
+ return;
77
+ this.pending.push(update);
78
+ if (!this.pump)
79
+ this.startPump();
80
+ }
81
+ startPump() {
82
+ this.pump = (async () => {
83
+ while (!this.stopped && this.pending.length > 0) {
84
+ const update = this.pending.shift();
85
+ if (!update)
86
+ continue;
87
+ await this.client.notify(methods.client.session.update, {
88
+ sessionId: this.sessionId,
89
+ update,
90
+ });
91
+ }
92
+ })()
93
+ .catch((error) => {
94
+ this.pumpFailure = error;
95
+ this.stopped = true;
96
+ this.pending.length = 0;
97
+ this.notificationFailure();
98
+ throw error;
99
+ })
100
+ .finally(() => {
101
+ this.pump = undefined;
102
+ });
103
+ this.pump.catch(() => undefined);
104
+ }
105
+ async drain() {
106
+ if (this.pumpFailure !== undefined)
107
+ throw this.pumpFailure;
108
+ while (this.pump)
109
+ await this.pump;
110
+ if (this.pumpFailure !== undefined)
111
+ throw this.pumpFailure;
112
+ }
113
+ async replay(entries) {
114
+ for (const entry of entries) {
115
+ for (const update of replayEntry(entry))
116
+ this.enqueue(update);
117
+ }
118
+ try {
119
+ await this.drain();
120
+ }
121
+ catch {
122
+ throw adapterError("notification_error");
123
+ }
124
+ }
125
+ async setConfig(configId, value) {
126
+ if (this.busy)
127
+ throw adapterError("session_busy");
128
+ return applyConfig(this.pi, this.deps.modelRuntime, configId, value);
129
+ }
130
+ disarm(turn) {
131
+ if (!turn.structured)
132
+ return;
133
+ try {
134
+ this.structured.disarm(this.pi);
135
+ }
136
+ catch (error) {
137
+ console.error("pi-acp structured-output disarm error:", error);
138
+ }
139
+ }
140
+ finish(turn, outcome, keepBackstop = false) {
141
+ if (turn.completed)
142
+ return;
143
+ turn.completed = true;
144
+ turn.removeRequestAbort?.();
145
+ turn.removeRequestAbort = undefined;
146
+ this.disarm(turn);
147
+ if (!keepBackstop)
148
+ turn.settledController.abort();
149
+ if (this.activeTurn === turn)
150
+ this.activeTurn = undefined;
151
+ if ("response" in outcome)
152
+ turn.resolve(outcome.response);
153
+ else
154
+ turn.reject(outcome.error);
155
+ turn.resolveSettlement();
156
+ }
157
+ notificationFailure() {
158
+ const turn = this.activeTurn;
159
+ if (!turn || turn.completed)
160
+ return;
161
+ turn.notificationFailed = true;
162
+ this.abortTurn(turn);
163
+ this.finish(turn, { error: adapterError("notification_error") }, true);
164
+ }
165
+ abortTurn(turn) {
166
+ if (turn.completed)
167
+ return;
168
+ if (!turn.controller.signal.aborted)
169
+ turn.controller.abort();
170
+ }
171
+ turnError(turn, error) {
172
+ if (turn.completed)
173
+ return;
174
+ let terminal;
175
+ try {
176
+ terminal = terminalAssistant(agentMessages(this.pi).slice(turn.startMessageIndex));
177
+ }
178
+ catch {
179
+ terminal = undefined;
180
+ }
181
+ this.finish(turn, { error: isRequestError(error) ? error : unexpectedError(error, terminal) });
182
+ }
183
+ runTurnTask(turn, task) {
184
+ void task.catch((error) => {
185
+ if (turn.completed)
186
+ console.error("pi-acp detached turn error:", error);
187
+ else
188
+ this.turnError(turn, error);
189
+ });
190
+ }
191
+ async cleanupWedged() {
192
+ try {
193
+ await this.onWedged(this.sessionId, this);
194
+ }
195
+ catch (error) {
196
+ console.error("pi-acp wedged-session cleanup error:", error);
197
+ }
198
+ }
199
+ startBackstop(turn) {
200
+ if (turn.backstopStarted)
201
+ return;
202
+ turn.backstopStarted = true;
203
+ const backstop = this.deps.sleep(this.deps.graceMs, turn.settledController.signal).then(async () => {
204
+ try {
205
+ if (!turn.completed) {
206
+ const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
207
+ if (!this.pumpFailure) {
208
+ this.enqueue(usageUpdate(this.pi));
209
+ await this.drain();
210
+ }
211
+ if (!turn.completed) {
212
+ this.finish(turn, {
213
+ response: { stopReason: "cancelled", usage: promptUsage(messages) },
214
+ });
215
+ }
216
+ }
217
+ }
218
+ catch (error) {
219
+ if (this.pumpFailure !== undefined) {
220
+ turn.notificationFailed = true;
221
+ this.finish(turn, { error: adapterError("notification_error") }, true);
222
+ }
223
+ else {
224
+ this.turnError(turn, error);
225
+ }
226
+ }
227
+ if (turn.completed)
228
+ await this.cleanupWedged();
229
+ }, async (error) => {
230
+ if (turn.settledController.signal.aborted || turn.completed)
231
+ return;
232
+ this.turnError(turn, error);
233
+ await this.cleanupWedged();
234
+ });
235
+ this.runTurnTask(turn, backstop);
236
+ }
237
+ async handlePiResolved(turn) {
238
+ if (turn.completed)
239
+ return;
240
+ try {
241
+ const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
242
+ if (turn.structured) {
243
+ const json = this.structured.takeJson();
244
+ if (json !== undefined) {
245
+ this.enqueue({ sessionUpdate: "agent_message_chunk", content: { type: "text", text: json } });
246
+ }
247
+ }
248
+ this.enqueue(usageUpdate(this.pi));
249
+ await this.drain();
250
+ if (turn.completed)
251
+ return;
252
+ const terminal = terminalAssistant(messages);
253
+ const stopReason = stopReasonFor(terminal, turn.controller.signal.aborted);
254
+ this.finish(turn, { response: { stopReason, usage: promptUsage(messages) } });
255
+ }
256
+ catch (error) {
257
+ if (this.pumpFailure !== undefined)
258
+ this.notificationFailure();
259
+ else
260
+ this.turnError(turn, error);
261
+ }
262
+ }
263
+ async handlePiRejected(turn, error) {
264
+ if (turn.completed)
265
+ return;
266
+ if (!turn.controller.signal.aborted) {
267
+ this.finish(turn, { error: classifyPreflight(error) });
268
+ return;
269
+ }
270
+ try {
271
+ const messages = agentMessages(this.pi).slice(turn.startMessageIndex);
272
+ this.enqueue(usageUpdate(this.pi));
273
+ await this.drain();
274
+ this.finish(turn, { response: { stopReason: "cancelled", usage: promptUsage(messages) } });
275
+ }
276
+ catch (settlementError) {
277
+ if (this.pumpFailure !== undefined)
278
+ this.notificationFailure();
279
+ else
280
+ this.turnError(turn, settlementError);
281
+ }
282
+ }
283
+ async prompt(params, requestSignal) {
284
+ if (this.busy)
285
+ throw adapterError("session_busy");
286
+ const converted = convertPromptContent(params.prompt);
287
+ const schema = params._meta?.outputSchema;
288
+ let text = converted.text;
289
+ let structured = false;
290
+ if (schema !== undefined) {
291
+ let instruction;
292
+ try {
293
+ instruction = this.structured.arm(this.pi, schema);
294
+ }
295
+ catch (error) {
296
+ if (isRequestError(error))
297
+ throw error;
298
+ throw unexpectedError(error);
299
+ }
300
+ text = text ? `${instruction}\n\n${text}` : instruction;
301
+ structured = true;
302
+ }
303
+ let startMessageIndex;
304
+ try {
305
+ startMessageIndex = agentMessages(this.pi).length;
306
+ }
307
+ catch (error) {
308
+ if (structured) {
309
+ try {
310
+ this.structured.disarm(this.pi);
311
+ }
312
+ catch (disarmError) {
313
+ console.error("pi-acp structured-output disarm error:", disarmError);
314
+ }
315
+ }
316
+ throw unexpectedError(error);
317
+ }
318
+ let resolve;
319
+ let reject;
320
+ const result = new Promise((res, rej) => {
321
+ resolve = res;
322
+ reject = rej;
323
+ });
324
+ let resolveSettlement;
325
+ const settlement = new Promise((res) => {
326
+ resolveSettlement = res;
327
+ });
328
+ const turn = {
329
+ controller: new AbortController(),
330
+ settledController: new AbortController(),
331
+ settlement,
332
+ resolveSettlement,
333
+ completed: false,
334
+ notificationFailed: false,
335
+ backstopStarted: false,
336
+ resolve,
337
+ reject,
338
+ startMessageIndex,
339
+ structured,
340
+ };
341
+ this.activeTurn = turn;
342
+ turn.controller.signal.addEventListener("abort", () => {
343
+ try {
344
+ this.pi.agent.abort();
345
+ }
346
+ catch (error) {
347
+ console.error("pi-acp abort error:", error);
348
+ }
349
+ finally {
350
+ this.startBackstop(turn);
351
+ }
352
+ }, { once: true });
353
+ const abortFromRequest = () => this.abortTurn(turn);
354
+ if (requestSignal.aborted)
355
+ abortFromRequest();
356
+ else {
357
+ requestSignal.addEventListener("abort", abortFromRequest, { once: true });
358
+ turn.removeRequestAbort = () => requestSignal.removeEventListener("abort", abortFromRequest);
359
+ }
360
+ let piPromise;
361
+ try {
362
+ piPromise = this.pi.prompt(text, { images: converted.images });
363
+ }
364
+ catch (error) {
365
+ this.runTurnTask(turn, this.handlePiRejected(turn, error));
366
+ return result;
367
+ }
368
+ void piPromise.then(() => this.runTurnTask(turn, this.handlePiResolved(turn)), (error) => this.runTurnTask(turn, this.handlePiRejected(turn, error)));
369
+ return result;
370
+ }
371
+ cancel() {
372
+ if (this.activeTurn)
373
+ this.abortTurn(this.activeTurn);
374
+ }
375
+ async dispose() {
376
+ if (this.disposed)
377
+ return;
378
+ this.closing = true;
379
+ const turn = this.activeTurn;
380
+ if (turn) {
381
+ this.abortTurn(turn);
382
+ await turn.settlement;
383
+ }
384
+ await this.disposeResources();
385
+ }
386
+ async disposeResources() {
387
+ if (this.disposed)
388
+ return;
389
+ this.closing = true;
390
+ this.disposed = true;
391
+ this.stopped = true;
392
+ this.pending.length = 0;
393
+ this.failedMcpResults.clear();
394
+ try {
395
+ this.unsubscribe?.();
396
+ }
397
+ catch (error) {
398
+ console.error("pi-acp unsubscribe error:", error);
399
+ }
400
+ this.unsubscribe = undefined;
401
+ try {
402
+ await this.pi.dispose();
403
+ }
404
+ catch (error) {
405
+ console.error("pi-acp session dispose error:", error);
406
+ }
407
+ try {
408
+ await disposeMcpBridge(this.mcpClients, this.deps);
409
+ }
410
+ catch (error) {
411
+ console.error("pi-acp MCP disposal error:", error);
412
+ }
413
+ }
414
+ }
@@ -0,0 +1,4 @@
1
+ import type { PromptResponse } from "@agentclientprotocol/sdk";
2
+ import type { AssistantLike } from "./usage.js";
3
+ export declare function stopReasonFor(terminal: AssistantLike | undefined, aborted: boolean): PromptResponse["stopReason"];
4
+ //# sourceMappingURL=stop-reason.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stop-reason.d.ts","sourceRoot":"","sources":["../src/stop-reason.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,GAAG,SAAS,EACnC,OAAO,EAAE,OAAO,GACf,cAAc,CAAC,YAAY,CAAC,CAe9B"}
@@ -0,0 +1,18 @@
1
+ import { classifyTerminal, unexpectedError } from "./errors.js";
2
+ export function stopReasonFor(terminal, aborted) {
3
+ if (aborted || terminal?.stopReason === "aborted")
4
+ return "cancelled";
5
+ switch (terminal?.stopReason) {
6
+ case "stop":
7
+ case "toolUse":
8
+ return "end_turn";
9
+ case "length":
10
+ return "max_tokens";
11
+ case "error":
12
+ throw classifyTerminal(terminal);
13
+ case undefined:
14
+ return "end_turn";
15
+ default:
16
+ throw unexpectedError(new Error("Unknown pi stop reason"), terminal);
17
+ }
18
+ }
@@ -0,0 +1,14 @@
1
+ import type { AgentSession, ToolDefinition } from "@earendil-works/pi-coding-agent";
2
+ export declare const STRUCTURED_TOOL_NAME = "__acp_structured_output";
3
+ export declare class StructuredOutputState {
4
+ readonly tool: ToolDefinition;
5
+ private readonly schemaHolder;
6
+ private captured;
7
+ private baseActive;
8
+ constructor();
9
+ install(session: AgentSession): void;
10
+ arm(session: AgentSession, schema: unknown): string;
11
+ disarm(session: AgentSession): void;
12
+ takeJson(): string | undefined;
13
+ }
14
+ //# sourceMappingURL=structured-output.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"structured-output.d.ts","sourceRoot":"","sources":["../src/structured-output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAIpF,eAAO,MAAM,oBAAoB,4BAA4B,CAAC;AAE9D,qBAAa,qBAAqB;IAChC,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAC3C,OAAO,CAAC,QAAQ,CAAU;IAC1B,OAAO,CAAC,UAAU,CAAgB;;IAoBlC,OAAO,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAQpC,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM;IAsBnD,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,IAAI;IAKnC,QAAQ,IAAI,MAAM,GAAG,SAAS;CAQ/B"}