@rivus/runtime 0.16.6 → 0.17.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/LICENSE +674 -21
- package/README.md +21 -0
- package/dist/acp.d.ts +7 -92
- package/dist/acp.js +298 -249
- package/dist/chunks/agent-loop.d.ts +143 -95
- package/dist/chunks/agent-user-input.js +109 -0
- package/dist/chunks/rivus-runtime-tool.d.ts +104 -4
- package/dist/chunks/{tool-input-digest.js → tool-risk-policy.js} +7 -14
- package/dist/chunks/user-input-xml.js +224 -0
- package/dist/index.d.ts +107 -823
- package/dist/index.js +217 -679
- package/dist/pi.d.ts +62 -332
- package/dist/pi.js +544 -543
- package/package.json +12 -11
- package/dist/chunks/agent-loop.js +0 -164
package/dist/acp.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { i as isAgentSteeringRequest, o as projectUserInputText } from "./chunks/agent-user-input.js";
|
|
2
|
+
import { c as createAgentLoopToolExecutionEnd, d as createAgentLoopTurnStart, l as createAgentLoopToolExecutionStart, o as createAgentLoopTextDelta, s as createAgentLoopThinkingDelta, t as prepareAgentModelInput, u as createAgentLoopToolExecutionUpdate } from "./chunks/user-input-xml.js";
|
|
2
3
|
import { Effect, Stream } from "effect";
|
|
3
4
|
import { randomUUID } from "node:crypto";
|
|
4
5
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
@@ -6,11 +7,197 @@ import { dirname } from "node:path";
|
|
|
6
7
|
import { Readable, Writable } from "node:stream";
|
|
7
8
|
import * as acp from "@agentclientprotocol/sdk";
|
|
8
9
|
import { spawn } from "node:child_process";
|
|
10
|
+
//#region src/adapters/acp/runtime/acp-agent-server.ts
|
|
11
|
+
function createAcpAgentServer(options) {
|
|
12
|
+
const sessions = /* @__PURE__ */ new Set();
|
|
13
|
+
const active = /* @__PURE__ */ new Map();
|
|
14
|
+
return acp.agent({ name: options.agentName ?? "rivus" }).onRequest(acp.methods.agent.initialize, ({ params }) => ({
|
|
15
|
+
agentCapabilities: {},
|
|
16
|
+
agentInfo: {
|
|
17
|
+
name: options.agentName ?? "rivus",
|
|
18
|
+
version: "1"
|
|
19
|
+
},
|
|
20
|
+
protocolVersion: params.protocolVersion === acp.PROTOCOL_VERSION ? params.protocolVersion : acp.PROTOCOL_VERSION
|
|
21
|
+
})).onRequest(acp.methods.agent.session.new, () => {
|
|
22
|
+
const sessionId = randomUUID();
|
|
23
|
+
sessions.add(sessionId);
|
|
24
|
+
return { sessionId };
|
|
25
|
+
}).onRequest(acp.methods.agent.session.prompt, async ({ client, params }) => {
|
|
26
|
+
if (!sessions.has(params.sessionId)) throw new Error(`Unknown ACP session: ${params.sessionId}`);
|
|
27
|
+
if (active.has(params.sessionId)) throw new Error(`ACP session is already active: ${params.sessionId}`);
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
active.set(params.sessionId, controller);
|
|
30
|
+
const sessionKey = `acp:${params.sessionId}`;
|
|
31
|
+
const releasePermissionBridge = options.permissionBridge?.bind(sessionKey, async (request) => {
|
|
32
|
+
const response = await client.request(acp.methods.client.session.requestPermission, {
|
|
33
|
+
options: request.options.map((option) => ({ ...option })),
|
|
34
|
+
sessionId: params.sessionId,
|
|
35
|
+
toolCall: {
|
|
36
|
+
...request.toolCall.rawInput === void 0 ? {} : { rawInput: request.toolCall.rawInput },
|
|
37
|
+
...request.toolCall.title === void 0 ? {} : { title: request.toolCall.title },
|
|
38
|
+
toolCallId: request.toolCall.toolCallId
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
return response.outcome.outcome === "selected" ? { optionId: response.outcome.optionId } : void 0;
|
|
42
|
+
});
|
|
43
|
+
try {
|
|
44
|
+
await Effect.runPromise(options.loop.run({
|
|
45
|
+
abortSignal: controller.signal,
|
|
46
|
+
runId: randomUUID(),
|
|
47
|
+
sessionKey,
|
|
48
|
+
text: readPromptText(params.prompt)
|
|
49
|
+
}).pipe(Stream.runForEach((event) => Effect.tryPromise({
|
|
50
|
+
catch: (cause) => cause,
|
|
51
|
+
try: async () => {
|
|
52
|
+
const update = mapAgentLoopEvent(event);
|
|
53
|
+
if (update) await client.notify(acp.methods.client.session.update, {
|
|
54
|
+
sessionId: params.sessionId,
|
|
55
|
+
update
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}))));
|
|
59
|
+
return { stopReason: controller.signal.aborted ? "cancelled" : "end_turn" };
|
|
60
|
+
} finally {
|
|
61
|
+
releasePermissionBridge?.();
|
|
62
|
+
active.delete(params.sessionId);
|
|
63
|
+
}
|
|
64
|
+
}).onNotification(acp.methods.agent.session.cancel, ({ params }) => {
|
|
65
|
+
active.get(params.sessionId)?.abort();
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async function serveAcpAgentOnStdio(app) {
|
|
69
|
+
const stream = acp.ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
|
|
70
|
+
await app.connect(stream).closed;
|
|
71
|
+
}
|
|
72
|
+
function readPromptText(prompt) {
|
|
73
|
+
const parts = prompt.flatMap((content) => content.type === "text" ? [content.text] : []);
|
|
74
|
+
if (parts.length !== prompt.length) throw new Error("Rivus ACP facade currently accepts text prompt content only");
|
|
75
|
+
return parts.join("\n");
|
|
76
|
+
}
|
|
77
|
+
function mapAgentLoopEvent(event) {
|
|
78
|
+
if (event.type === "assistant_text_delta") return {
|
|
79
|
+
content: {
|
|
80
|
+
text: event.delta,
|
|
81
|
+
type: "text"
|
|
82
|
+
},
|
|
83
|
+
sessionUpdate: "agent_message_chunk"
|
|
84
|
+
};
|
|
85
|
+
if (event.type === "assistant_thinking_delta") return {
|
|
86
|
+
content: {
|
|
87
|
+
text: event.delta,
|
|
88
|
+
type: "text"
|
|
89
|
+
},
|
|
90
|
+
sessionUpdate: "agent_thought_chunk"
|
|
91
|
+
};
|
|
92
|
+
if (event.type === "tool_execution_start") return {
|
|
93
|
+
name: event.toolName,
|
|
94
|
+
rawInput: event.input,
|
|
95
|
+
sessionUpdate: "tool_call",
|
|
96
|
+
status: "in_progress",
|
|
97
|
+
title: event.toolName,
|
|
98
|
+
toolCallId: event.toolCallId
|
|
99
|
+
};
|
|
100
|
+
if (event.type === "tool_execution_update") return {
|
|
101
|
+
name: event.toolName,
|
|
102
|
+
rawInput: event.input,
|
|
103
|
+
rawOutput: event.partialResult,
|
|
104
|
+
sessionUpdate: "tool_call_update",
|
|
105
|
+
status: "in_progress",
|
|
106
|
+
title: event.toolName,
|
|
107
|
+
toolCallId: event.toolCallId
|
|
108
|
+
};
|
|
109
|
+
if (event.type === "tool_execution_end") return {
|
|
110
|
+
name: event.toolName,
|
|
111
|
+
rawOutput: event.result,
|
|
112
|
+
sessionUpdate: "tool_call_update",
|
|
113
|
+
status: event.isError ? "failed" : "completed",
|
|
114
|
+
title: event.toolName,
|
|
115
|
+
toolCallId: event.toolCallId
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/adapters/acp/runtime/acp-permission-bridge.ts
|
|
120
|
+
function createAcpPermissionBridge() {
|
|
121
|
+
const policies = /* @__PURE__ */ new Map();
|
|
122
|
+
return {
|
|
123
|
+
bind: (sessionKey, policy) => {
|
|
124
|
+
if (policies.has(sessionKey)) throw new Error(`ACP permission bridge is already bound: ${sessionKey}`);
|
|
125
|
+
policies.set(sessionKey, policy);
|
|
126
|
+
return () => {
|
|
127
|
+
if (policies.get(sessionKey) === policy) policies.delete(sessionKey);
|
|
128
|
+
};
|
|
129
|
+
},
|
|
130
|
+
policy: async (request, context) => policies.get(context.sessionKey)?.(request)
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/adapters/acp/runtime/acp-session-store.ts
|
|
135
|
+
/**
|
|
136
|
+
* A small deployment-owned store for ACP provider session identities.
|
|
137
|
+
* The file contains no prompts or credentials, only session-key bindings.
|
|
138
|
+
*/
|
|
139
|
+
function createJsonAcpSessionStore(options) {
|
|
140
|
+
let recordsPromise;
|
|
141
|
+
let writeChain = Promise.resolve();
|
|
142
|
+
const readRecords = async () => {
|
|
143
|
+
try {
|
|
144
|
+
const raw = await readFile(options.filePath, "utf8");
|
|
145
|
+
const parsed = JSON.parse(raw);
|
|
146
|
+
if (!isRecord$1(parsed)) throw new Error("ACP session store must contain an object");
|
|
147
|
+
const records = /* @__PURE__ */ new Map();
|
|
148
|
+
for (const [sessionKey, value] of Object.entries(parsed)) {
|
|
149
|
+
if (!isRecord$1(value) || typeof value.sessionId !== "string" || value.sessionId.trim() === "") throw new Error(`invalid ACP session record for ${sessionKey}`);
|
|
150
|
+
records.set(sessionKey, { sessionId: value.sessionId });
|
|
151
|
+
}
|
|
152
|
+
return records;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (isMissingFile(error)) return /* @__PURE__ */ new Map();
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
const records = async () => {
|
|
159
|
+
recordsPromise ??= readRecords();
|
|
160
|
+
return recordsPromise;
|
|
161
|
+
};
|
|
162
|
+
const persist = async (value) => {
|
|
163
|
+
const temporaryPath = `${options.filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
164
|
+
await mkdir(dirname(options.filePath), { recursive: true });
|
|
165
|
+
await writeFile(temporaryPath, `${JSON.stringify(Object.fromEntries(value), null, 2)}\n`, "utf8");
|
|
166
|
+
await rename(temporaryPath, options.filePath);
|
|
167
|
+
};
|
|
168
|
+
return {
|
|
169
|
+
delete: async (sessionKey) => {
|
|
170
|
+
writeChain = writeChain.then(async () => {
|
|
171
|
+
const current = await records();
|
|
172
|
+
if (!current.delete(sessionKey)) return;
|
|
173
|
+
await persist(current);
|
|
174
|
+
});
|
|
175
|
+
await writeChain;
|
|
176
|
+
},
|
|
177
|
+
load: async (sessionKey) => (await records()).get(sessionKey),
|
|
178
|
+
save: async (sessionKey, record) => {
|
|
179
|
+
writeChain = writeChain.then(async () => {
|
|
180
|
+
const current = await records();
|
|
181
|
+
current.set(sessionKey, { sessionId: record.sessionId });
|
|
182
|
+
await persist(current);
|
|
183
|
+
});
|
|
184
|
+
await writeChain;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function isMissingFile(error) {
|
|
189
|
+
return isRecord$1(error) && error.code === "ENOENT";
|
|
190
|
+
}
|
|
191
|
+
function isRecord$1(value) {
|
|
192
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
193
|
+
}
|
|
194
|
+
//#endregion
|
|
9
195
|
//#region src/adapters/acp/runtime/acp-agent-loop.ts
|
|
10
196
|
function createAcpAgentLoop(options) {
|
|
11
197
|
return {
|
|
12
198
|
run: (input) => Stream.fromAsyncIterable(runAcpSession(input, options), (error) => error),
|
|
13
|
-
supportsSteering: true
|
|
199
|
+
supportsSteering: true,
|
|
200
|
+
supportsStructuredSteering: true
|
|
14
201
|
};
|
|
15
202
|
}
|
|
16
203
|
async function* runAcpSession(input, options) {
|
|
@@ -22,7 +209,11 @@ async function* runAcpSession(input, options) {
|
|
|
22
209
|
let abortFailure;
|
|
23
210
|
let abortSettled = false;
|
|
24
211
|
let aborted = false;
|
|
25
|
-
let currentTurn = startAcpTurn(
|
|
212
|
+
let currentTurn = startAcpTurn({
|
|
213
|
+
text: input.text,
|
|
214
|
+
...input.attachmentAccess ? { attachmentAccess: input.attachmentAccess } : {},
|
|
215
|
+
...input.userInput ? { userInput: input.userInput } : {}
|
|
216
|
+
}, session, input, updates, tools, () => {
|
|
26
217
|
wake?.();
|
|
27
218
|
wake = void 0;
|
|
28
219
|
});
|
|
@@ -40,7 +231,10 @@ async function* runAcpSession(input, options) {
|
|
|
40
231
|
});
|
|
41
232
|
};
|
|
42
233
|
input.abortSignal.addEventListener("abort", onAbort, { once: true });
|
|
43
|
-
const takeSteering = () =>
|
|
234
|
+
const takeSteering = () => {
|
|
235
|
+
if (!input.steering) return;
|
|
236
|
+
return Effect.runPromise(input.steering.nextInput?.() ?? input.steering.next());
|
|
237
|
+
};
|
|
44
238
|
let steering = takeSteering();
|
|
45
239
|
try {
|
|
46
240
|
if (input.abortSignal.aborted) onAbort();
|
|
@@ -54,8 +248,8 @@ async function* runAcpSession(input, options) {
|
|
|
54
248
|
new Promise((resolve) => {
|
|
55
249
|
wake = () => resolve({ type: "wake" });
|
|
56
250
|
}),
|
|
57
|
-
...steering ? [steering.then((
|
|
58
|
-
|
|
251
|
+
...steering ? [steering.then((steeringInput) => ({
|
|
252
|
+
steeringInput,
|
|
59
253
|
type: "steer"
|
|
60
254
|
}))] : [],
|
|
61
255
|
currentTurn.promise.then(() => ({ type: "turn_completed" }), () => ({ type: "turn_completed" }))
|
|
@@ -64,7 +258,7 @@ async function* runAcpSession(input, options) {
|
|
|
64
258
|
await Promise.resolve(session.cancel({ preserveSession: true }));
|
|
65
259
|
await currentTurn.promise;
|
|
66
260
|
updates.push(createAgentLoopTurnStart());
|
|
67
|
-
currentTurn = startAcpTurn(
|
|
261
|
+
currentTurn = startAcpTurn(createSteeringTurnPromptInput(signal.steeringInput), session, input, updates, tools, () => {
|
|
68
262
|
wake?.();
|
|
69
263
|
wake = void 0;
|
|
70
264
|
});
|
|
@@ -110,14 +304,42 @@ function createSteeringTurnPrompt(text) {
|
|
|
110
304
|
text
|
|
111
305
|
].join("\n");
|
|
112
306
|
}
|
|
113
|
-
function
|
|
307
|
+
function createSteeringTurnPromptInput(input) {
|
|
308
|
+
if (typeof input === "string") return { text: createSteeringTurnPrompt(input) };
|
|
309
|
+
const userInput = isAgentSteeringRequest(input) ? input.userInput : input;
|
|
310
|
+
return {
|
|
311
|
+
...isAgentSteeringRequest(input) && input.attachmentAccess ? { attachmentAccess: input.attachmentAccess } : {},
|
|
312
|
+
text: createSteeringTurnPrompt(projectUserInputText(userInput)),
|
|
313
|
+
userInput: {
|
|
314
|
+
...userInput,
|
|
315
|
+
message: {
|
|
316
|
+
...userInput.message,
|
|
317
|
+
parts: [{
|
|
318
|
+
type: "text",
|
|
319
|
+
text: "请优先处理这条 steering 消息,并在回答后继续原任务。",
|
|
320
|
+
source: "direct",
|
|
321
|
+
controlEligible: false
|
|
322
|
+
}, ...userInput.message.parts]
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function startAcpTurn(prompt, session, input, updates, tools, notify) {
|
|
114
328
|
let completed = false;
|
|
115
329
|
let failure;
|
|
116
330
|
return {
|
|
117
|
-
promise:
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
331
|
+
promise: Effect.runPromise(Effect.gen(function* () {
|
|
332
|
+
const prepared = yield* prepareAgentModelInput(toAcpTurnInput(input, prompt), { allowImages: session.supportsImages !== false });
|
|
333
|
+
if (!prepared.hasUsableContent) {
|
|
334
|
+
if (prepared.fallbackText) updates.push(createAgentLoopTextDelta(prepared.fallbackText));
|
|
335
|
+
notify();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
yield* Effect.promise(() => session.prompt(prepared.text, (update) => {
|
|
339
|
+
updates.push(...mapAcpSessionUpdate(update, tools));
|
|
340
|
+
notify();
|
|
341
|
+
}, prepared.images.length > 0 ? { images: prepared.images } : void 0));
|
|
342
|
+
})).then(() => void 0).catch((error) => {
|
|
121
343
|
if (!input.abortSignal.aborted) failure = error;
|
|
122
344
|
}).finally(() => {
|
|
123
345
|
completed = true;
|
|
@@ -131,6 +353,15 @@ function startAcpTurn(text, session, input, updates, tools, notify) {
|
|
|
131
353
|
}
|
|
132
354
|
};
|
|
133
355
|
}
|
|
356
|
+
function toAcpTurnInput(input, prompt) {
|
|
357
|
+
const { attachmentAccess: _initialAccess, userInput: _initialUserInput, ...runInput } = input;
|
|
358
|
+
return {
|
|
359
|
+
...runInput,
|
|
360
|
+
text: prompt.text,
|
|
361
|
+
...prompt.attachmentAccess ? { attachmentAccess: prompt.attachmentAccess } : {},
|
|
362
|
+
...prompt.userInput ? { userInput: prompt.userInput } : {}
|
|
363
|
+
};
|
|
364
|
+
}
|
|
134
365
|
function mapAcpSessionUpdate(update, tools) {
|
|
135
366
|
if (update.sessionUpdate === "agent_message_chunk") {
|
|
136
367
|
const text = readTextContent(update.content);
|
|
@@ -189,137 +420,13 @@ function mapAcpSessionUpdate(update, tools) {
|
|
|
189
420
|
})];
|
|
190
421
|
}
|
|
191
422
|
function readTextContent(content) {
|
|
192
|
-
if (!isRecord
|
|
423
|
+
if (!isRecord(content) || content.type !== "text" || typeof content.text !== "string") return;
|
|
193
424
|
return content.text;
|
|
194
425
|
}
|
|
195
|
-
function isRecord
|
|
426
|
+
function isRecord(value) {
|
|
196
427
|
return typeof value === "object" && value !== null;
|
|
197
428
|
}
|
|
198
429
|
//#endregion
|
|
199
|
-
//#region src/adapters/acp/runtime/acp-agent-server.ts
|
|
200
|
-
function createAcpAgentServer(options) {
|
|
201
|
-
const sessions = /* @__PURE__ */ new Set();
|
|
202
|
-
const active = /* @__PURE__ */ new Map();
|
|
203
|
-
return acp.agent({ name: options.agentName ?? "rivus" }).onRequest(acp.methods.agent.initialize, ({ params }) => ({
|
|
204
|
-
agentCapabilities: {},
|
|
205
|
-
agentInfo: {
|
|
206
|
-
name: options.agentName ?? "rivus",
|
|
207
|
-
version: "1"
|
|
208
|
-
},
|
|
209
|
-
protocolVersion: params.protocolVersion === acp.PROTOCOL_VERSION ? params.protocolVersion : acp.PROTOCOL_VERSION
|
|
210
|
-
})).onRequest(acp.methods.agent.session.new, () => {
|
|
211
|
-
const sessionId = randomUUID();
|
|
212
|
-
sessions.add(sessionId);
|
|
213
|
-
return { sessionId };
|
|
214
|
-
}).onRequest(acp.methods.agent.session.prompt, async ({ client, params }) => {
|
|
215
|
-
if (!sessions.has(params.sessionId)) throw new Error(`Unknown ACP session: ${params.sessionId}`);
|
|
216
|
-
if (active.has(params.sessionId)) throw new Error(`ACP session is already active: ${params.sessionId}`);
|
|
217
|
-
const controller = new AbortController();
|
|
218
|
-
active.set(params.sessionId, controller);
|
|
219
|
-
const sessionKey = `acp:${params.sessionId}`;
|
|
220
|
-
const releasePermissionBridge = options.permissionBridge?.bind(sessionKey, async (request) => {
|
|
221
|
-
const response = await client.request(acp.methods.client.session.requestPermission, {
|
|
222
|
-
options: request.options.map((option) => ({ ...option })),
|
|
223
|
-
sessionId: params.sessionId,
|
|
224
|
-
toolCall: {
|
|
225
|
-
...request.toolCall.rawInput === void 0 ? {} : { rawInput: request.toolCall.rawInput },
|
|
226
|
-
...request.toolCall.title === void 0 ? {} : { title: request.toolCall.title },
|
|
227
|
-
toolCallId: request.toolCall.toolCallId
|
|
228
|
-
}
|
|
229
|
-
});
|
|
230
|
-
return response.outcome.outcome === "selected" ? { optionId: response.outcome.optionId } : void 0;
|
|
231
|
-
});
|
|
232
|
-
try {
|
|
233
|
-
await Effect.runPromise(options.loop.run({
|
|
234
|
-
abortSignal: controller.signal,
|
|
235
|
-
runId: randomUUID(),
|
|
236
|
-
sessionKey,
|
|
237
|
-
text: readPromptText(params.prompt)
|
|
238
|
-
}).pipe(Stream.runForEach((event) => Effect.tryPromise({
|
|
239
|
-
catch: (cause) => cause,
|
|
240
|
-
try: async () => {
|
|
241
|
-
const update = mapAgentLoopEvent(event);
|
|
242
|
-
if (update) await client.notify(acp.methods.client.session.update, {
|
|
243
|
-
sessionId: params.sessionId,
|
|
244
|
-
update
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}))));
|
|
248
|
-
return { stopReason: controller.signal.aborted ? "cancelled" : "end_turn" };
|
|
249
|
-
} finally {
|
|
250
|
-
releasePermissionBridge?.();
|
|
251
|
-
active.delete(params.sessionId);
|
|
252
|
-
}
|
|
253
|
-
}).onNotification(acp.methods.agent.session.cancel, ({ params }) => {
|
|
254
|
-
active.get(params.sessionId)?.abort();
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
async function serveAcpAgentOnStdio(app) {
|
|
258
|
-
const stream = acp.ndJsonStream(Writable.toWeb(process.stdout), Readable.toWeb(process.stdin));
|
|
259
|
-
await app.connect(stream).closed;
|
|
260
|
-
}
|
|
261
|
-
function readPromptText(prompt) {
|
|
262
|
-
const parts = prompt.flatMap((content) => content.type === "text" ? [content.text] : []);
|
|
263
|
-
if (parts.length !== prompt.length) throw new Error("Rivus ACP facade currently accepts text prompt content only");
|
|
264
|
-
return parts.join("\n");
|
|
265
|
-
}
|
|
266
|
-
function mapAgentLoopEvent(event) {
|
|
267
|
-
if (event.type === "assistant_text_delta") return {
|
|
268
|
-
content: {
|
|
269
|
-
text: event.delta,
|
|
270
|
-
type: "text"
|
|
271
|
-
},
|
|
272
|
-
sessionUpdate: "agent_message_chunk"
|
|
273
|
-
};
|
|
274
|
-
if (event.type === "assistant_thinking_delta") return {
|
|
275
|
-
content: {
|
|
276
|
-
text: event.delta,
|
|
277
|
-
type: "text"
|
|
278
|
-
},
|
|
279
|
-
sessionUpdate: "agent_thought_chunk"
|
|
280
|
-
};
|
|
281
|
-
if (event.type === "tool_execution_start") return {
|
|
282
|
-
name: event.toolName,
|
|
283
|
-
rawInput: event.input,
|
|
284
|
-
sessionUpdate: "tool_call",
|
|
285
|
-
status: "in_progress",
|
|
286
|
-
title: event.toolName,
|
|
287
|
-
toolCallId: event.toolCallId
|
|
288
|
-
};
|
|
289
|
-
if (event.type === "tool_execution_update") return {
|
|
290
|
-
name: event.toolName,
|
|
291
|
-
rawInput: event.input,
|
|
292
|
-
rawOutput: event.partialResult,
|
|
293
|
-
sessionUpdate: "tool_call_update",
|
|
294
|
-
status: "in_progress",
|
|
295
|
-
title: event.toolName,
|
|
296
|
-
toolCallId: event.toolCallId
|
|
297
|
-
};
|
|
298
|
-
if (event.type === "tool_execution_end") return {
|
|
299
|
-
name: event.toolName,
|
|
300
|
-
rawOutput: event.result,
|
|
301
|
-
sessionUpdate: "tool_call_update",
|
|
302
|
-
status: event.isError ? "failed" : "completed",
|
|
303
|
-
title: event.toolName,
|
|
304
|
-
toolCallId: event.toolCallId
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
//#endregion
|
|
308
|
-
//#region src/adapters/acp/runtime/acp-permission-bridge.ts
|
|
309
|
-
function createAcpPermissionBridge() {
|
|
310
|
-
const policies = /* @__PURE__ */ new Map();
|
|
311
|
-
return {
|
|
312
|
-
bind: (sessionKey, policy) => {
|
|
313
|
-
if (policies.has(sessionKey)) throw new Error(`ACP permission bridge is already bound: ${sessionKey}`);
|
|
314
|
-
policies.set(sessionKey, policy);
|
|
315
|
-
return () => {
|
|
316
|
-
if (policies.get(sessionKey) === policy) policies.delete(sessionKey);
|
|
317
|
-
};
|
|
318
|
-
},
|
|
319
|
-
policy: async (request, context) => policies.get(context.sessionKey)?.(request)
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
//#endregion
|
|
323
430
|
//#region src/adapters/acp/runtime/acp-permission-policy.ts
|
|
324
431
|
async function decideAcpPermission(request, policy) {
|
|
325
432
|
const selection = await policy?.(request);
|
|
@@ -330,67 +437,6 @@ async function decideAcpPermission(request, policy) {
|
|
|
330
437
|
};
|
|
331
438
|
}
|
|
332
439
|
//#endregion
|
|
333
|
-
//#region src/adapters/acp/runtime/acp-session-store.ts
|
|
334
|
-
/**
|
|
335
|
-
* A small deployment-owned store for ACP provider session identities.
|
|
336
|
-
* The file contains no prompts or credentials, only session-key bindings.
|
|
337
|
-
*/
|
|
338
|
-
function createJsonAcpSessionStore(options) {
|
|
339
|
-
let recordsPromise;
|
|
340
|
-
let writeChain = Promise.resolve();
|
|
341
|
-
const readRecords = async () => {
|
|
342
|
-
try {
|
|
343
|
-
const raw = await readFile(options.filePath, "utf8");
|
|
344
|
-
const parsed = JSON.parse(raw);
|
|
345
|
-
if (!isRecord(parsed)) throw new Error("ACP session store must contain an object");
|
|
346
|
-
const records = /* @__PURE__ */ new Map();
|
|
347
|
-
for (const [sessionKey, value] of Object.entries(parsed)) {
|
|
348
|
-
if (!isRecord(value) || typeof value.sessionId !== "string" || value.sessionId.trim() === "") throw new Error(`invalid ACP session record for ${sessionKey}`);
|
|
349
|
-
records.set(sessionKey, { sessionId: value.sessionId });
|
|
350
|
-
}
|
|
351
|
-
return records;
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (isMissingFile(error)) return /* @__PURE__ */ new Map();
|
|
354
|
-
throw error;
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
const records = async () => {
|
|
358
|
-
recordsPromise ??= readRecords();
|
|
359
|
-
return recordsPromise;
|
|
360
|
-
};
|
|
361
|
-
const persist = async (value) => {
|
|
362
|
-
const temporaryPath = `${options.filePath}.tmp-${process.pid}-${Date.now()}`;
|
|
363
|
-
await mkdir(dirname(options.filePath), { recursive: true });
|
|
364
|
-
await writeFile(temporaryPath, `${JSON.stringify(Object.fromEntries(value), null, 2)}\n`, "utf8");
|
|
365
|
-
await rename(temporaryPath, options.filePath);
|
|
366
|
-
};
|
|
367
|
-
return {
|
|
368
|
-
delete: async (sessionKey) => {
|
|
369
|
-
writeChain = writeChain.then(async () => {
|
|
370
|
-
const current = await records();
|
|
371
|
-
if (!current.delete(sessionKey)) return;
|
|
372
|
-
await persist(current);
|
|
373
|
-
});
|
|
374
|
-
await writeChain;
|
|
375
|
-
},
|
|
376
|
-
load: async (sessionKey) => (await records()).get(sessionKey),
|
|
377
|
-
save: async (sessionKey, record) => {
|
|
378
|
-
writeChain = writeChain.then(async () => {
|
|
379
|
-
const current = await records();
|
|
380
|
-
current.set(sessionKey, { sessionId: record.sessionId });
|
|
381
|
-
await persist(current);
|
|
382
|
-
});
|
|
383
|
-
await writeChain;
|
|
384
|
-
}
|
|
385
|
-
};
|
|
386
|
-
}
|
|
387
|
-
function isMissingFile(error) {
|
|
388
|
-
return isRecord(error) && error.code === "ENOENT";
|
|
389
|
-
}
|
|
390
|
-
function isRecord(value) {
|
|
391
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
392
|
-
}
|
|
393
|
-
//#endregion
|
|
394
440
|
//#region src/adapters/acp/runtime/acp-stdio-agent-loop.ts
|
|
395
441
|
async function buildAcpSession(processConnection, options, input) {
|
|
396
442
|
const mcpServers = createMcpServers(options, input);
|
|
@@ -401,7 +447,7 @@ async function buildAcpSession(processConnection, options, input) {
|
|
|
401
447
|
mcpServers
|
|
402
448
|
}).start();
|
|
403
449
|
await options.sessionStore?.save(input.sessionKey, { sessionId: session.sessionId });
|
|
404
|
-
return session;
|
|
450
|
+
return new ActiveAcpSessionTransport(session);
|
|
405
451
|
}
|
|
406
452
|
function createMcpServers(options, input) {
|
|
407
453
|
return (options.mcpServers?.(input) ?? []).map((server) => ({
|
|
@@ -504,7 +550,7 @@ function createAcpStdioAgentLoop(options) {
|
|
|
504
550
|
current.dispose();
|
|
505
551
|
processConnection.sessions.delete(input.sessionKey);
|
|
506
552
|
}
|
|
507
|
-
const session = new SdkAcpAgentSession(await buildAcpSession(processConnection, options, input), processConnection.connection.agent, input.sessionKey, options.sessionStore);
|
|
553
|
+
const session = new SdkAcpAgentSession(await buildAcpSession(processConnection, options, input), processConnection.connection.agent, input.sessionKey, options.sessionStore, processConnection.agentCapabilities?.promptCapabilities?.image === true);
|
|
508
554
|
sessionKeys.set(session.sessionId, input.sessionKey);
|
|
509
555
|
processConnection.sessions.set(input.sessionKey, session);
|
|
510
556
|
return session;
|
|
@@ -517,12 +563,14 @@ var SdkAcpAgentSession = class {
|
|
|
517
563
|
agent;
|
|
518
564
|
sessionKey;
|
|
519
565
|
sessionStore;
|
|
566
|
+
supportsImages;
|
|
520
567
|
reusable = true;
|
|
521
|
-
constructor(session, agent, sessionKey, sessionStore) {
|
|
568
|
+
constructor(session, agent, sessionKey, sessionStore, supportsImages = false) {
|
|
522
569
|
this.session = session;
|
|
523
570
|
this.agent = agent;
|
|
524
571
|
this.sessionKey = sessionKey;
|
|
525
572
|
this.sessionStore = sessionStore;
|
|
573
|
+
this.supportsImages = supportsImages;
|
|
526
574
|
}
|
|
527
575
|
get sessionId() {
|
|
528
576
|
return this.session.sessionId;
|
|
@@ -543,8 +591,8 @@ var SdkAcpAgentSession = class {
|
|
|
543
591
|
this.session.dispose();
|
|
544
592
|
await this.sessionStore?.delete(this.sessionKey);
|
|
545
593
|
}
|
|
546
|
-
async prompt(text, onUpdate) {
|
|
547
|
-
const failure = this.session.prompt(text).then(() => new Promise(() => void 0), (error) => Promise.reject(error));
|
|
594
|
+
async prompt(text, onUpdate, options) {
|
|
595
|
+
const failure = this.session.prompt(text, options?.images).then(() => new Promise(() => void 0), (error) => Promise.reject(error));
|
|
548
596
|
for (;;) {
|
|
549
597
|
const message = await Promise.race([this.session.nextUpdate(), failure]);
|
|
550
598
|
if (message.kind === "stop") return { stopReason: message.stopReason };
|
|
@@ -552,6 +600,40 @@ var SdkAcpAgentSession = class {
|
|
|
552
600
|
}
|
|
553
601
|
}
|
|
554
602
|
};
|
|
603
|
+
var ActiveAcpSessionTransport = class {
|
|
604
|
+
session;
|
|
605
|
+
sessionId;
|
|
606
|
+
constructor(session) {
|
|
607
|
+
this.session = session;
|
|
608
|
+
this.sessionId = session.sessionId;
|
|
609
|
+
}
|
|
610
|
+
dispose() {
|
|
611
|
+
this.session.dispose();
|
|
612
|
+
}
|
|
613
|
+
nextUpdate() {
|
|
614
|
+
return this.session.nextUpdate().then((message) => {
|
|
615
|
+
if (message.kind === "session_update") return {
|
|
616
|
+
kind: "session_update",
|
|
617
|
+
update: message.update
|
|
618
|
+
};
|
|
619
|
+
return {
|
|
620
|
+
kind: "stop",
|
|
621
|
+
stopReason: message.stopReason
|
|
622
|
+
};
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
prompt(text, images) {
|
|
626
|
+
const prompt = [{
|
|
627
|
+
text,
|
|
628
|
+
type: "text"
|
|
629
|
+
}, ...(images ?? []).map(({ data, mimeType }) => ({
|
|
630
|
+
data,
|
|
631
|
+
mimeType,
|
|
632
|
+
type: "image"
|
|
633
|
+
}))];
|
|
634
|
+
return this.session.prompt(prompt).then((response) => ({ stopReason: response.stopReason }));
|
|
635
|
+
}
|
|
636
|
+
};
|
|
555
637
|
var ResumedAcpSession = class {
|
|
556
638
|
processConnection;
|
|
557
639
|
sessionId;
|
|
@@ -589,13 +671,18 @@ var ResumedAcpSession = class {
|
|
|
589
671
|
resolve
|
|
590
672
|
}));
|
|
591
673
|
}
|
|
592
|
-
prompt(text) {
|
|
674
|
+
prompt(text, images) {
|
|
593
675
|
if (this.disposed) return Promise.reject(/* @__PURE__ */ new Error(`ACP session ${this.sessionId} observer disposed`));
|
|
676
|
+
const prompt = [{
|
|
677
|
+
text,
|
|
678
|
+
type: "text"
|
|
679
|
+
}, ...(images ?? []).map(({ data, mimeType }) => ({
|
|
680
|
+
data,
|
|
681
|
+
mimeType,
|
|
682
|
+
type: "image"
|
|
683
|
+
}))];
|
|
594
684
|
const response = this.processConnection.connection.agent.request(acp.methods.agent.session.prompt, {
|
|
595
|
-
prompt
|
|
596
|
-
text,
|
|
597
|
-
type: "text"
|
|
598
|
-
}],
|
|
685
|
+
prompt,
|
|
599
686
|
sessionId: this.sessionId
|
|
600
687
|
});
|
|
601
688
|
response.then((result) => this.enqueue({
|
|
@@ -726,43 +813,5 @@ function withTimeout(promise, timeoutMs, message) {
|
|
|
726
813
|
});
|
|
727
814
|
}
|
|
728
815
|
//#endregion
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
return fromEffectAgentLoop(createAcpAgentLoop({
|
|
732
|
-
...options.cancellationSettleTimeoutMs === void 0 ? {} : { cancellationSettleTimeoutMs: options.cancellationSettleTimeoutMs },
|
|
733
|
-
...options.disposeSessionAfterRun === void 0 ? {} : { disposeSessionAfterRun: options.disposeSessionAfterRun },
|
|
734
|
-
resolveSession: (input) => options.resolveSession(toCompatibilityAgentLoopInput(input))
|
|
735
|
-
}));
|
|
736
|
-
}
|
|
737
|
-
function createAcpAgentServer$1(options) {
|
|
738
|
-
return createAcpAgentServer({
|
|
739
|
-
...options.agentName === void 0 ? {} : { agentName: options.agentName },
|
|
740
|
-
loop: toEffectAgentLoop(options.loop),
|
|
741
|
-
...options.permissionBridge ? { permissionBridge: options.permissionBridge } : {},
|
|
742
|
-
workingDirectory: options.workingDirectory
|
|
743
|
-
});
|
|
744
|
-
}
|
|
745
|
-
function serveAcpAgentOnStdio$1(app) {
|
|
746
|
-
return serveAcpAgentOnStdio(app);
|
|
747
|
-
}
|
|
748
|
-
function createAcpStdioAgentLoop$1(options) {
|
|
749
|
-
const handle = createAcpStdioAgentLoop({
|
|
750
|
-
...options.arguments ? { arguments: options.arguments } : {},
|
|
751
|
-
...options.clientName === void 0 ? {} : { clientName: options.clientName },
|
|
752
|
-
command: options.command,
|
|
753
|
-
...options.environment ? { environment: options.environment } : {},
|
|
754
|
-
...options.initializationTimeoutMs === void 0 ? {} : { initializationTimeoutMs: options.initializationTimeoutMs },
|
|
755
|
-
...options.mcpServers ? { mcpServers: (input) => options.mcpServers(toCompatibilityAgentLoopInput(input)) } : {},
|
|
756
|
-
...options.onStderr ? { onStderr: options.onStderr } : {},
|
|
757
|
-
...options.permissionPolicy ? { permissionPolicy: options.permissionPolicy } : {},
|
|
758
|
-
...options.sessionStore ? { sessionStore: options.sessionStore } : {},
|
|
759
|
-
...options.terminationTimeoutMs === void 0 ? {} : { terminationTimeoutMs: options.terminationTimeoutMs },
|
|
760
|
-
workingDirectory: options.workingDirectory
|
|
761
|
-
});
|
|
762
|
-
return {
|
|
763
|
-
dispose: () => handle.dispose(),
|
|
764
|
-
loop: fromEffectAgentLoop(handle.loop)
|
|
765
|
-
};
|
|
766
|
-
}
|
|
767
|
-
//#endregion
|
|
768
|
-
export { AcpSessionResumeFailed, AcpSessionResumeUnavailable, createAcpAgentLoop, createAcpAgentServer, createAcpPermissionBridge, createAcpStdioAgentLoop, createJsonAcpSessionStore, createAcpAgentLoop$1 as createLegacyAcpAgentLoop, createAcpAgentServer$1 as createLegacyAcpAgentServer, createAcpStdioAgentLoop$1 as createLegacyAcpStdioAgentLoop, decideAcpPermission, serveAcpAgentOnStdio, serveAcpAgentOnStdio$1 as serveLegacyAcpAgentOnStdio };
|
|
816
|
+
export { createAcpAgentServer, createAcpPermissionBridge, createAcpStdioAgentLoop, createJsonAcpSessionStore, serveAcpAgentOnStdio };
|
|
817
|
+
|