@rivus/runtime 0.16.6 → 0.18.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 +242 -705
- package/dist/pi.d.ts +62 -332
- package/dist/pi.js +547 -546
- package/package.json +12 -11
- package/dist/chunks/agent-loop.js +0 -164
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { a as isValidUserInput } from "./agent-user-input.js";
|
|
2
|
+
import { Effect, Result } from "effect";
|
|
3
|
+
//#region src/core/application/agent-execution/loop/agent-loop.ts
|
|
4
|
+
function createAgentLoopTextDelta(delta) {
|
|
5
|
+
return {
|
|
6
|
+
delta,
|
|
7
|
+
type: "assistant_text_delta"
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function createAgentLoopTurnStart() {
|
|
11
|
+
return { type: "turn_start" };
|
|
12
|
+
}
|
|
13
|
+
function createAgentLoopThinkingDelta(delta) {
|
|
14
|
+
return {
|
|
15
|
+
delta,
|
|
16
|
+
type: "assistant_thinking_delta"
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
function createAgentLoopModelExecutionStart(options) {
|
|
20
|
+
return {
|
|
21
|
+
...options,
|
|
22
|
+
type: "model_execution_start"
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function createAgentLoopModelExecutionEnd(options) {
|
|
26
|
+
return {
|
|
27
|
+
...options,
|
|
28
|
+
type: "model_execution_end"
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function createAgentLoopSkillExecutionStart(options) {
|
|
32
|
+
return {
|
|
33
|
+
...options,
|
|
34
|
+
type: "skill_execution_start"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function createAgentLoopSkillExecutionEnd(options) {
|
|
38
|
+
return {
|
|
39
|
+
...options,
|
|
40
|
+
type: "skill_execution_end"
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createAgentLoopToolExecutionStart(options) {
|
|
44
|
+
return {
|
|
45
|
+
input: options.input,
|
|
46
|
+
toolCallId: options.toolCallId,
|
|
47
|
+
toolName: options.toolName,
|
|
48
|
+
type: "tool_execution_start"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function createAgentLoopToolExecutionUpdate(options) {
|
|
52
|
+
return {
|
|
53
|
+
input: options.input,
|
|
54
|
+
partialResult: options.partialResult,
|
|
55
|
+
toolCallId: options.toolCallId,
|
|
56
|
+
toolName: options.toolName,
|
|
57
|
+
type: "tool_execution_update"
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function createAgentLoopToolExecutionEnd(options) {
|
|
61
|
+
return {
|
|
62
|
+
isError: options.isError,
|
|
63
|
+
result: options.result,
|
|
64
|
+
toolCallId: options.toolCallId,
|
|
65
|
+
toolName: options.toolName,
|
|
66
|
+
type: "tool_execution_end"
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//#endregion
|
|
70
|
+
//#region src/adapters/model-input/user-input-xml.ts
|
|
71
|
+
/**
|
|
72
|
+
* Prepare one run's structured input at the provider edge. UserInput never
|
|
73
|
+
* stores paths. Paths and bytes are resolved through the run-scoped access
|
|
74
|
+
* binding immediately before the provider prompt is sent.
|
|
75
|
+
*/
|
|
76
|
+
function prepareAgentModelInput(input, options = {}) {
|
|
77
|
+
return Effect.gen(function* () {
|
|
78
|
+
const images = [];
|
|
79
|
+
const unavailable = [];
|
|
80
|
+
let hasUsableContent = false;
|
|
81
|
+
if (!input.userInput) return {
|
|
82
|
+
hasUsableContent: input.text.trim().length > 0,
|
|
83
|
+
images,
|
|
84
|
+
text: input.text
|
|
85
|
+
};
|
|
86
|
+
if (!isValidUserInput(input.userInput)) return {
|
|
87
|
+
hasUsableContent: false,
|
|
88
|
+
images,
|
|
89
|
+
fallbackText: "收到的结构化消息格式无效,无法安全处理。",
|
|
90
|
+
text: input.text
|
|
91
|
+
};
|
|
92
|
+
const rendered = yield* renderMessage(input.userInput.message, {
|
|
93
|
+
access: input.attachmentAccess,
|
|
94
|
+
images,
|
|
95
|
+
markUsable: () => {
|
|
96
|
+
hasUsableContent = true;
|
|
97
|
+
},
|
|
98
|
+
options,
|
|
99
|
+
unavailable
|
|
100
|
+
});
|
|
101
|
+
const fallbackText = [...collectUnavailableText(input.userInput.message), ...unavailable].join("\n");
|
|
102
|
+
return {
|
|
103
|
+
hasUsableContent,
|
|
104
|
+
images,
|
|
105
|
+
...hasUsableContent || !fallbackText ? {} : { fallbackText },
|
|
106
|
+
text: `<user_message version="1" id="${escapeAttribute(input.userInput.deliveryId)}">${rendered}</user_message>`
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function collectUnavailableText(message) {
|
|
111
|
+
const notices = [];
|
|
112
|
+
const visit = (current) => {
|
|
113
|
+
for (const part of current.parts) if (part.type === "notice") notices.push(part.text);
|
|
114
|
+
else if (part.type === "quote") for (const nested of part.messages) visit(nested);
|
|
115
|
+
};
|
|
116
|
+
visit(message);
|
|
117
|
+
return notices;
|
|
118
|
+
}
|
|
119
|
+
function renderMessage(message, context) {
|
|
120
|
+
return Effect.gen(function* () {
|
|
121
|
+
const parts = [];
|
|
122
|
+
for (const part of message.parts) parts.push(yield* renderPart(part, context));
|
|
123
|
+
return `<message ${[
|
|
124
|
+
`id="${escapeAttribute(message.id)}"`,
|
|
125
|
+
`source="${message.source}"`,
|
|
126
|
+
...message.author?.id ? [`author_id="${escapeAttribute(message.author.id)}"`] : [],
|
|
127
|
+
...message.author?.name ? [`author_name="${escapeAttribute(message.author.name)}"`] : [],
|
|
128
|
+
...message.sentAt ? [`sent_at="${escapeAttribute(message.sentAt)}"`] : []
|
|
129
|
+
].join(" ")}>${parts.join("")}</message>`;
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
function renderPart(part, context) {
|
|
133
|
+
const { access, markUsable, unavailable } = context;
|
|
134
|
+
if (part.type === "text") {
|
|
135
|
+
if (part.text.trim().length > 0) markUsable();
|
|
136
|
+
return Effect.succeed(`<text>${escapeXml(part.text)}</text>`);
|
|
137
|
+
}
|
|
138
|
+
if (part.type === "notice") return Effect.sync(() => {
|
|
139
|
+
const recovery = access?.trustedRecovery?.(part);
|
|
140
|
+
if (recovery) markUsable();
|
|
141
|
+
return renderNotice(part, recovery);
|
|
142
|
+
});
|
|
143
|
+
if (part.type === "quote") return Effect.gen(function* () {
|
|
144
|
+
const messages = [];
|
|
145
|
+
for (const message of part.messages) messages.push(yield* renderMessage(message, context));
|
|
146
|
+
return `<quote kind="${part.kind}">${messages.join("")}</quote>`;
|
|
147
|
+
});
|
|
148
|
+
if (part.type === "image") return renderImage(part, context);
|
|
149
|
+
return renderAttachment(part, access, markUsable, unavailable);
|
|
150
|
+
}
|
|
151
|
+
function renderImage(part, context) {
|
|
152
|
+
const { access, images, markUsable, options, unavailable } = context;
|
|
153
|
+
const imageBytes = images.reduce((total, image) => total + Buffer.byteLength(image.data, "base64"), 0);
|
|
154
|
+
const withinImageBudget = (size) => images.length < 4 && size <= 5242880 && imageBytes + size <= 10485760;
|
|
155
|
+
if (options.allowImages === false) return Effect.succeed(renderUnavailableNoticeAndRecord(part.attachmentId, "当前模型不支持原生图片输入", unavailable));
|
|
156
|
+
if (!access) return Effect.succeed(renderUnavailableNoticeAndRecord(part.attachmentId, "执行环境未提供图片读取能力", unavailable));
|
|
157
|
+
if (!/^(image\/(png|jpeg|webp|gif))$/.test(part.mimeType)) return Effect.succeed(renderUnavailableNoticeAndRecord(part.attachmentId, "当前模型不支持此图片格式", unavailable));
|
|
158
|
+
if (part.size !== void 0 && !withinImageBudget(part.size)) return Effect.succeed(renderUnavailableNoticeAndRecord(part.attachmentId, "超出本次图片输入预算", unavailable));
|
|
159
|
+
if (part.size !== void 0 && access.canProvideImage && !access.canProvideImage({
|
|
160
|
+
mimeType: part.mimeType,
|
|
161
|
+
size: part.size,
|
|
162
|
+
index: images.length + 1
|
|
163
|
+
})) return Effect.succeed(renderUnavailableNoticeAndRecord(part.attachmentId, "超出本次图片输入预算或当前模型不支持视觉", unavailable));
|
|
164
|
+
return access.readBytes(part.attachmentId).pipe(Effect.result, Effect.map((result) => {
|
|
165
|
+
if (Result.isFailure(result)) return renderUnavailableNoticeAndRecord(part.attachmentId, "图片在执行环境中不可读", unavailable);
|
|
166
|
+
if (!withinImageBudget(result.success.byteLength) || result.success.byteLength === 0) return renderUnavailableNoticeAndRecord(part.attachmentId, "超出本次图片输入预算或图片为空", unavailable);
|
|
167
|
+
if (access.canProvideImage && !access.canProvideImage({
|
|
168
|
+
mimeType: part.mimeType,
|
|
169
|
+
size: result.success.byteLength,
|
|
170
|
+
index: images.length + 1
|
|
171
|
+
})) return renderUnavailableNoticeAndRecord(part.attachmentId, "超出本次图片输入预算或当前模型不支持视觉", unavailable);
|
|
172
|
+
const imageIndex = images.length + 1;
|
|
173
|
+
images.push({
|
|
174
|
+
type: "image",
|
|
175
|
+
attachmentId: part.attachmentId,
|
|
176
|
+
data: Buffer.from(result.success).toString("base64"),
|
|
177
|
+
imageIndex,
|
|
178
|
+
mimeType: part.mimeType
|
|
179
|
+
});
|
|
180
|
+
markUsable();
|
|
181
|
+
return `<image attachment_id="${escapeAttribute(part.attachmentId)}" image_index="${imageIndex}" mime_type="${escapeAttribute(part.mimeType)}" state="provided" />`;
|
|
182
|
+
}));
|
|
183
|
+
}
|
|
184
|
+
function renderAttachment(part, access, markUsable, unavailable) {
|
|
185
|
+
if (!access) return Effect.succeed(renderUnavailableNoticeAndRecord(part.fileName ?? part.attachmentId, "执行环境未提供文件读取能力", unavailable));
|
|
186
|
+
return access.materialize(part.attachmentId).pipe(Effect.result, Effect.map((result) => {
|
|
187
|
+
if (Result.isFailure(result)) return renderUnavailableNoticeAndRecord(part.fileName ?? part.attachmentId, "附件无法物化到执行环境", unavailable);
|
|
188
|
+
const value = result.success;
|
|
189
|
+
const canUnderstand = access.canUnderstand?.({
|
|
190
|
+
mediaKind: part.mediaKind,
|
|
191
|
+
mimeType: value.mimeType
|
|
192
|
+
}) ?? false;
|
|
193
|
+
if (canUnderstand) markUsable();
|
|
194
|
+
else unavailable.push(`${part.fileName ?? part.attachmentId}:附件已下载,但当前执行环境没有适用的${part.mediaKind === "audio" ? "音频理解工具,请改发文字" : part.mediaKind === "video" ? "视频解析工具" : "文件读取或解析工具"}`);
|
|
195
|
+
const durationMs = value.durationMs ?? part.durationMs;
|
|
196
|
+
return `<file attachment_id="${escapeAttribute(value.attachmentId)}"${value.fileName || part.fileName ? ` name="${escapeAttribute(value.fileName ?? part.fileName ?? "attachment")}"` : ""} path="${escapeAttribute(value.path)}" mime_type="${escapeAttribute(value.mimeType)}" size_bytes="${value.size}" media_kind="${value.mediaKind}" state="available"${durationMs === void 0 ? "" : ` duration_ms="${durationMs}"`}${canUnderstand ? "" : " understood=\"false\""} />`;
|
|
197
|
+
}));
|
|
198
|
+
}
|
|
199
|
+
function renderNotice(part, recovery) {
|
|
200
|
+
const retryable = recovery ? "true" : "false";
|
|
201
|
+
if (!recovery) return `<notice reason="${part.reason}" retryable="${retryable}">${escapeXml(part.text)}</notice>`;
|
|
202
|
+
const args = Object.entries(recovery.arguments).map(([key, value]) => `<arg name="${escapeAttribute(key)}" value="${escapeAttribute(String(value))}" />`).join("");
|
|
203
|
+
return `<notice reason="${part.reason}" retryable="true">${escapeXml(part.text)}<recovery tool="${escapeAttribute(recovery.toolName)}">${args}</recovery></notice>`;
|
|
204
|
+
}
|
|
205
|
+
function renderUnavailableNotice(name, reason) {
|
|
206
|
+
return `<notice reason="unavailable" retryable="false">${escapeXml(`${name}:${reason}`)}</notice>`;
|
|
207
|
+
}
|
|
208
|
+
function renderUnavailableNoticeAndRecord(name, reason, unavailable) {
|
|
209
|
+
unavailable.push(`${name}:${reason}`);
|
|
210
|
+
return renderUnavailableNotice(name, reason);
|
|
211
|
+
}
|
|
212
|
+
function escapeAttribute(value) {
|
|
213
|
+
return escapeXml(value).replaceAll("'", "'").replaceAll("\"", """);
|
|
214
|
+
}
|
|
215
|
+
function escapeXml(value) {
|
|
216
|
+
return Array.from(value).map((character) => {
|
|
217
|
+
const code = character.codePointAt(0) ?? 0;
|
|
218
|
+
if (code >= 0 && code < 32 && code !== 9 && code !== 10 && code !== 13 || code === 65534 || code === 65535) return "�";
|
|
219
|
+
return character;
|
|
220
|
+
}).join("").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
221
|
+
}
|
|
222
|
+
//#endregion
|
|
223
|
+
export { createAgentLoopSkillExecutionStart as a, createAgentLoopToolExecutionEnd as c, createAgentLoopTurnStart as d, createAgentLoopSkillExecutionEnd as i, createAgentLoopToolExecutionStart as l, createAgentLoopModelExecutionEnd as n, createAgentLoopTextDelta as o, createAgentLoopModelExecutionStart as r, createAgentLoopThinkingDelta as s, prepareAgentModelInput as t, createAgentLoopToolExecutionUpdate as u };
|
|
224
|
+
|