@nextclaw/ncp-agent-runtime 0.3.7 → 0.3.9
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/index.d.ts +135 -124
- package/dist/index.js +1105 -1109
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,1205 +1,1201 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
import { copyFileSync, existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { copyFile, mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { basename, dirname, join, resolve } from "node:path";
|
|
5
|
+
import { NcpAssistantTextStreamNormalizer, NcpEventType, isHiddenNcpMessage, normalizeAssistantText } from "@nextclaw/ncp";
|
|
6
|
+
import AjvPkg from "ajv";
|
|
7
|
+
//#region src/user-content.ts
|
|
8
|
+
function readOptionalString$1(value) {
|
|
9
|
+
if (typeof value !== "string") return null;
|
|
10
|
+
const trimmed = value.trim();
|
|
11
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
8
12
|
}
|
|
9
13
|
function formatAssetReferenceBlock(params) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
const fileName = readOptionalString$1(params.fileName) ?? "asset";
|
|
15
|
+
const mimeType = readOptionalString$1(params.mimeType) ?? "application/octet-stream";
|
|
16
|
+
const assetUri = readOptionalString$1(params.assetUri);
|
|
17
|
+
const url = readOptionalString$1(params.url);
|
|
18
|
+
const sizeText = typeof params.sizeBytes === "number" && Number.isFinite(params.sizeBytes) ? String(params.sizeBytes) : null;
|
|
19
|
+
return [
|
|
20
|
+
`[Asset: ${fileName}]`,
|
|
21
|
+
`[MIME: ${mimeType}]`,
|
|
22
|
+
...assetUri ? [`[Asset URI: ${assetUri}]`] : [],
|
|
23
|
+
...sizeText ? [`[Size Bytes: ${sizeText}]`] : [],
|
|
24
|
+
...url ? [`[Preview URL: ${url}]`] : [],
|
|
25
|
+
"[Instruction: This file is not embedded in the prompt. If you need to inspect or transform it, use asset_export to copy it to a normal file path first.]"
|
|
26
|
+
].join("\n");
|
|
27
|
+
}
|
|
28
|
+
function resolveFilePart(part, assetStore) {
|
|
29
|
+
const assetUri = readOptionalString$1(part.assetUri);
|
|
30
|
+
const stored = assetUri ? assetStore?.getByUri(assetUri) : null;
|
|
31
|
+
return {
|
|
32
|
+
fileName: readOptionalString$1(stored?.fileName) ?? readOptionalString$1(part.name) ?? "asset",
|
|
33
|
+
mimeType: readOptionalString$1(stored?.mimeType) ?? readOptionalString$1(part.mimeType) ?? "application/octet-stream",
|
|
34
|
+
assetUri,
|
|
35
|
+
url: readOptionalString$1(part.url),
|
|
36
|
+
contentBase64: readOptionalString$1(part.contentBase64),
|
|
37
|
+
sizeBytes: stored?.sizeBytes ?? (typeof part.sizeBytes === "number" ? part.sizeBytes : void 0),
|
|
38
|
+
contentPath: assetUri ? assetStore?.resolveContentPath(assetUri) ?? null : null
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function formatImageAttachmentHint(params) {
|
|
42
|
+
const { fileName: rawFileName, mimeType: rawMimeType, assetUri: rawAssetUri, sizeBytes } = params;
|
|
43
|
+
const fileName = readOptionalString$1(rawFileName) ?? "asset";
|
|
44
|
+
const mimeType = readOptionalString$1(rawMimeType) ?? "application/octet-stream";
|
|
45
|
+
const assetUri = readOptionalString$1(rawAssetUri);
|
|
46
|
+
const sizeText = typeof sizeBytes === "number" && Number.isFinite(sizeBytes) ? String(sizeBytes) : null;
|
|
47
|
+
return [
|
|
48
|
+
`[Attached Image: ${fileName}]`,
|
|
49
|
+
`[MIME: ${mimeType}]`,
|
|
50
|
+
...assetUri ? [`[Asset URI: ${assetUri}]`] : [],
|
|
51
|
+
...sizeText ? [`[Size Bytes: ${sizeText}]`] : [],
|
|
52
|
+
assetUri ? "[Instruction: This image is embedded in the prompt. If you need to transform or process the original file with tools, use the asset URI.]" : "[Instruction: This image is embedded in the prompt.]"
|
|
53
|
+
].join("\n");
|
|
54
|
+
}
|
|
55
|
+
function isImageMimeType(value) {
|
|
56
|
+
return value?.startsWith("image/") ?? false;
|
|
57
|
+
}
|
|
58
|
+
function isModelReachableImageUrl(value) {
|
|
59
|
+
return value !== null && (/^https?:\/\//i.test(value) || /^data:/i.test(value));
|
|
60
|
+
}
|
|
61
|
+
function buildImageDataUrl(mimeType, bytes) {
|
|
62
|
+
return `data:${mimeType};base64,${Buffer.from(bytes).toString("base64")}`;
|
|
63
|
+
}
|
|
64
|
+
function resolveImageContentPart(part, assetStore) {
|
|
65
|
+
const resolved = resolveFilePart(part, assetStore);
|
|
66
|
+
if (!isImageMimeType(resolved.mimeType)) return null;
|
|
67
|
+
if (resolved.contentBase64) return {
|
|
68
|
+
type: "image_url",
|
|
69
|
+
image_url: {
|
|
70
|
+
url: `data:${resolved.mimeType};base64,${resolved.contentBase64}`,
|
|
71
|
+
detail: "auto"
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
if (resolved.contentPath) return {
|
|
75
|
+
type: "image_url",
|
|
76
|
+
image_url: {
|
|
77
|
+
url: buildImageDataUrl(resolved.mimeType, readFileSync(resolved.contentPath)),
|
|
78
|
+
detail: "auto"
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
const reachableImageUrl = resolved.url;
|
|
82
|
+
if (isModelReachableImageUrl(reachableImageUrl)) return {
|
|
83
|
+
type: "image_url",
|
|
84
|
+
image_url: {
|
|
85
|
+
url: reachableImageUrl,
|
|
86
|
+
detail: "auto"
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function resolveImageAttachmentHint(part, assetStore) {
|
|
92
|
+
const resolved = resolveFilePart(part, assetStore);
|
|
93
|
+
if (!isImageMimeType(resolved.mimeType)) return null;
|
|
94
|
+
return formatImageAttachmentHint({
|
|
95
|
+
fileName: resolved.fileName,
|
|
96
|
+
mimeType: resolved.mimeType,
|
|
97
|
+
assetUri: resolved.assetUri,
|
|
98
|
+
sizeBytes: resolved.sizeBytes
|
|
99
|
+
});
|
|
24
100
|
}
|
|
25
101
|
function resolveAssetReferenceBlock(part, assetStore) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (url || part.contentBase64) {
|
|
42
|
-
return formatAssetReferenceBlock({
|
|
43
|
-
fileName,
|
|
44
|
-
mimeType,
|
|
45
|
-
url,
|
|
46
|
-
sizeBytes
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
102
|
+
const resolved = resolveFilePart(part, assetStore);
|
|
103
|
+
if (resolved.assetUri) return formatAssetReferenceBlock({
|
|
104
|
+
fileName: resolved.fileName,
|
|
105
|
+
mimeType: resolved.mimeType,
|
|
106
|
+
assetUri: resolved.assetUri,
|
|
107
|
+
url: resolved.url,
|
|
108
|
+
sizeBytes: resolved.sizeBytes
|
|
109
|
+
});
|
|
110
|
+
if (resolved.url || resolved.contentBase64) return formatAssetReferenceBlock({
|
|
111
|
+
fileName: resolved.fileName,
|
|
112
|
+
mimeType: resolved.mimeType,
|
|
113
|
+
url: resolved.url,
|
|
114
|
+
sizeBytes: resolved.sizeBytes
|
|
115
|
+
});
|
|
116
|
+
return null;
|
|
50
117
|
}
|
|
51
118
|
function buildNcpUserContent(parts, options = {}) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
119
|
+
const content = [];
|
|
120
|
+
for (const part of parts) {
|
|
121
|
+
if ((part.type === "text" || part.type === "rich-text") && part.text.trim().length > 0) {
|
|
122
|
+
content.push({
|
|
123
|
+
type: "text",
|
|
124
|
+
text: part.text
|
|
125
|
+
});
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (part.type !== "file") continue;
|
|
129
|
+
const imageContentPart = resolveImageContentPart(part, options.assetStore);
|
|
130
|
+
if (imageContentPart) {
|
|
131
|
+
content.push(imageContentPart);
|
|
132
|
+
const imageAttachmentHint = resolveImageAttachmentHint(part, options.assetStore);
|
|
133
|
+
if (imageAttachmentHint) content.push({
|
|
134
|
+
type: "text",
|
|
135
|
+
text: imageAttachmentHint
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const assetReferenceBlock = resolveAssetReferenceBlock(part, options.assetStore);
|
|
140
|
+
if (assetReferenceBlock) content.push({
|
|
141
|
+
type: "text",
|
|
142
|
+
text: assetReferenceBlock
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (content.length === 0) return "";
|
|
146
|
+
if (content.length === 1 && content[0]?.type === "text") return content[0].text;
|
|
147
|
+
return content;
|
|
73
148
|
}
|
|
74
|
-
|
|
75
|
-
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/context-builder.ts
|
|
76
151
|
function isRecord(value) {
|
|
77
|
-
|
|
152
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
78
153
|
}
|
|
79
|
-
function
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const trimmed = value.trim();
|
|
84
|
-
return trimmed.length > 0 ? trimmed : null;
|
|
154
|
+
function readOptionalString(value) {
|
|
155
|
+
if (typeof value !== "string") return null;
|
|
156
|
+
const trimmed = value.trim();
|
|
157
|
+
return trimmed.length > 0 ? trimmed : null;
|
|
85
158
|
}
|
|
86
159
|
function isTextLikePart(part) {
|
|
87
|
-
|
|
160
|
+
return part.type === "text" || part.type === "rich-text";
|
|
88
161
|
}
|
|
89
162
|
function mergeMessageAndRequestMetadata(input) {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
163
|
+
const messageMetadata = input.messages.slice().reverse().find((message) => isRecord(message.metadata))?.metadata;
|
|
164
|
+
return {
|
|
165
|
+
...isRecord(messageMetadata) ? messageMetadata : {},
|
|
166
|
+
...isRecord(input.metadata) ? input.metadata : {}
|
|
167
|
+
};
|
|
95
168
|
}
|
|
96
169
|
function readRequestedToolNames(metadata) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
deduped.add(value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return [...deduped];
|
|
170
|
+
const raw = metadata.requested_tools ?? metadata.requestedTools ?? metadata.requested_skills ?? metadata.requestedSkills;
|
|
171
|
+
if (!Array.isArray(raw)) return [];
|
|
172
|
+
const deduped = /* @__PURE__ */ new Set();
|
|
173
|
+
for (const item of raw) {
|
|
174
|
+
const value = readOptionalString(item);
|
|
175
|
+
if (value) deduped.add(value);
|
|
176
|
+
}
|
|
177
|
+
return [...deduped];
|
|
109
178
|
}
|
|
110
179
|
function isDefaultNcpContextBuilderOptions(value) {
|
|
111
|
-
|
|
180
|
+
return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
|
|
112
181
|
}
|
|
113
182
|
function messageToOpenAI(msg, options) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
content: typeof t.result === "string" ? t.result : JSON.stringify(t.result),
|
|
171
|
-
tool_call_id: t.toolCallId
|
|
172
|
-
});
|
|
173
|
-
}
|
|
174
|
-
} else {
|
|
175
|
-
out.push({
|
|
176
|
-
role: "assistant",
|
|
177
|
-
content: text,
|
|
178
|
-
...reasoning ? { reasoning_content: reasoning } : {}
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
return out;
|
|
182
|
-
}
|
|
183
|
-
return [];
|
|
183
|
+
const role = msg.role;
|
|
184
|
+
const parts = msg.parts ?? [];
|
|
185
|
+
if (role === "user" || role === "system") {
|
|
186
|
+
if (role === "user") return [{
|
|
187
|
+
role,
|
|
188
|
+
content: buildNcpUserContent(parts, { assetStore: options.assetStore })
|
|
189
|
+
}];
|
|
190
|
+
return [{
|
|
191
|
+
role,
|
|
192
|
+
content: parts.filter(isTextLikePart).map((part) => part.text).join("")
|
|
193
|
+
}];
|
|
194
|
+
}
|
|
195
|
+
if (role === "assistant") {
|
|
196
|
+
const texts = [];
|
|
197
|
+
const reasonings = [];
|
|
198
|
+
const toolInvocations = [];
|
|
199
|
+
for (const p of parts) {
|
|
200
|
+
if (p.type === "reasoning") reasonings.push(p.text);
|
|
201
|
+
if (p.type === "text") texts.push(p.text);
|
|
202
|
+
if (p.type === "tool-invocation" && p.state === "result" && p.result !== void 0) toolInvocations.push({
|
|
203
|
+
toolCallId: p.toolCallId ?? "",
|
|
204
|
+
toolName: p.toolName,
|
|
205
|
+
args: p.args ?? {},
|
|
206
|
+
result: p.result
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const text = texts.join("");
|
|
210
|
+
const reasoning = reasonings.join("");
|
|
211
|
+
const out = [];
|
|
212
|
+
if (toolInvocations.length > 0) {
|
|
213
|
+
out.push({
|
|
214
|
+
role: "assistant",
|
|
215
|
+
content: text || null,
|
|
216
|
+
...reasoning ? { reasoning_content: reasoning } : {},
|
|
217
|
+
tool_calls: toolInvocations.map((t) => ({
|
|
218
|
+
id: t.toolCallId,
|
|
219
|
+
type: "function",
|
|
220
|
+
function: {
|
|
221
|
+
name: t.toolName,
|
|
222
|
+
arguments: typeof t.args === "string" ? t.args : JSON.stringify(t.args ?? {})
|
|
223
|
+
}
|
|
224
|
+
}))
|
|
225
|
+
});
|
|
226
|
+
for (const t of toolInvocations) out.push({
|
|
227
|
+
role: "tool",
|
|
228
|
+
content: typeof t.result === "string" ? t.result : JSON.stringify(t.result),
|
|
229
|
+
tool_call_id: t.toolCallId
|
|
230
|
+
});
|
|
231
|
+
} else out.push({
|
|
232
|
+
role: "assistant",
|
|
233
|
+
content: text,
|
|
234
|
+
...reasoning ? { reasoning_content: reasoning } : {}
|
|
235
|
+
});
|
|
236
|
+
return out;
|
|
237
|
+
}
|
|
238
|
+
return [];
|
|
184
239
|
}
|
|
185
240
|
var DefaultNcpContextBuilder = class {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
name: definition.name,
|
|
226
|
-
description: definition.description,
|
|
227
|
-
parameters: definition.parameters
|
|
228
|
-
}
|
|
229
|
-
}));
|
|
230
|
-
return {
|
|
231
|
-
messages,
|
|
232
|
-
tools: tools && tools.length > 0 ? tools : void 0,
|
|
233
|
-
model: readOptionalString2(requestMetadata.model) ?? readOptionalString2(requestMetadata.llm_model) ?? readOptionalString2(requestMetadata.agent_model) ?? void 0,
|
|
234
|
-
thinkingLevel: readOptionalString2(requestMetadata.thinking) ?? readOptionalString2(requestMetadata.thinking_level) ?? readOptionalString2(requestMetadata.thinkingLevel) ?? readOptionalString2(requestMetadata.thinking_effort) ?? readOptionalString2(requestMetadata.thinkingEffort) ?? null
|
|
235
|
-
};
|
|
236
|
-
};
|
|
241
|
+
toolRegistry;
|
|
242
|
+
assetStore;
|
|
243
|
+
constructor(toolRegistryOrOptions) {
|
|
244
|
+
if (isDefaultNcpContextBuilderOptions(toolRegistryOrOptions)) {
|
|
245
|
+
this.toolRegistry = toolRegistryOrOptions.toolRegistry;
|
|
246
|
+
this.assetStore = toolRegistryOrOptions.assetStore;
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this.toolRegistry = toolRegistryOrOptions;
|
|
250
|
+
}
|
|
251
|
+
prepare = (input, options) => {
|
|
252
|
+
const maxMessages = options?.maxMessages ?? 50;
|
|
253
|
+
const sessionMessages = options?.sessionMessages ?? [];
|
|
254
|
+
const systemPrompt = options?.systemPrompt;
|
|
255
|
+
const requestMetadata = mergeMessageAndRequestMetadata(input);
|
|
256
|
+
const requestedToolNames = readRequestedToolNames(requestMetadata);
|
|
257
|
+
const messages = [];
|
|
258
|
+
if (systemPrompt) messages.push({
|
|
259
|
+
role: "system",
|
|
260
|
+
content: systemPrompt
|
|
261
|
+
});
|
|
262
|
+
for (const msg of sessionMessages.slice(-maxMessages)) messages.push(...messageToOpenAI(msg, { assetStore: this.assetStore }));
|
|
263
|
+
for (const msg of input.messages) messages.push(...messageToOpenAI(msg, { assetStore: this.assetStore }));
|
|
264
|
+
const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
|
|
265
|
+
const tools = (requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions).map((definition) => ({
|
|
266
|
+
type: "function",
|
|
267
|
+
function: {
|
|
268
|
+
name: definition.name,
|
|
269
|
+
description: definition.description,
|
|
270
|
+
parameters: definition.parameters
|
|
271
|
+
}
|
|
272
|
+
}));
|
|
273
|
+
return {
|
|
274
|
+
messages,
|
|
275
|
+
tools: tools && tools.length > 0 ? tools : void 0,
|
|
276
|
+
model: readOptionalString(requestMetadata.model) ?? readOptionalString(requestMetadata.llm_model) ?? readOptionalString(requestMetadata.agent_model) ?? void 0,
|
|
277
|
+
thinkingLevel: readOptionalString(requestMetadata.thinking) ?? readOptionalString(requestMetadata.thinking_level) ?? readOptionalString(requestMetadata.thinkingLevel) ?? readOptionalString(requestMetadata.thinking_effort) ?? readOptionalString(requestMetadata.thinkingEffort) ?? null
|
|
278
|
+
};
|
|
279
|
+
};
|
|
237
280
|
};
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
import { copyFileSync, existsSync, readFileSync } from "fs";
|
|
242
|
-
import { copyFile, mkdir, readFile, stat, writeFile } from "fs/promises";
|
|
243
|
-
import { basename, dirname, join, resolve } from "path";
|
|
244
|
-
var ASSET_URI_SCHEME = "asset://store/";
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/asset-store.ts
|
|
283
|
+
const ASSET_URI_SCHEME = "asset://store/";
|
|
245
284
|
function normalizeSegment(value) {
|
|
246
|
-
|
|
285
|
+
return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
|
|
247
286
|
}
|
|
248
287
|
function normalizeFileName(value) {
|
|
249
|
-
|
|
250
|
-
|
|
288
|
+
const trimmed = value.trim();
|
|
289
|
+
return trimmed.length > 0 ? trimmed : "asset.bin";
|
|
251
290
|
}
|
|
252
291
|
function normalizeMimeType(value) {
|
|
253
|
-
|
|
254
|
-
|
|
292
|
+
const normalized = value?.trim().toLowerCase() ?? "";
|
|
293
|
+
return normalized.length > 0 ? normalized : "application/octet-stream";
|
|
255
294
|
}
|
|
256
295
|
function buildAssetId() {
|
|
257
|
-
|
|
296
|
+
return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
|
|
258
297
|
}
|
|
259
298
|
function ensureAssetRoot(rootDir) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
return resolve(normalized);
|
|
299
|
+
const normalized = rootDir.trim();
|
|
300
|
+
if (!normalized) throw new Error("LocalAssetStore requires a non-empty rootDir.");
|
|
301
|
+
return resolve(normalized);
|
|
265
302
|
}
|
|
266
303
|
function buildStorageKey(timestamp, assetId) {
|
|
267
|
-
|
|
268
|
-
const month = String(timestamp.getMonth() + 1).padStart(2, "0");
|
|
269
|
-
const day = String(timestamp.getDate()).padStart(2, "0");
|
|
270
|
-
return `${year}/${month}/${day}/${assetId}`;
|
|
304
|
+
return `${String(timestamp.getFullYear())}/${String(timestamp.getMonth() + 1).padStart(2, "0")}/${String(timestamp.getDate()).padStart(2, "0")}/${assetId}`;
|
|
271
305
|
}
|
|
272
306
|
function buildAssetUri(storageKey) {
|
|
273
|
-
|
|
307
|
+
return `${ASSET_URI_SCHEME}${storageKey}`;
|
|
274
308
|
}
|
|
275
309
|
function parseAssetUri(uri) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const storageKey = normalized.slice(ASSET_URI_SCHEME.length).replace(/^\/+/, "").trim();
|
|
281
|
-
return storageKey.length > 0 ? storageKey : null;
|
|
310
|
+
const normalized = uri.trim();
|
|
311
|
+
if (!normalized.startsWith(ASSET_URI_SCHEME)) return null;
|
|
312
|
+
const storageKey = normalized.slice(14).replace(/^\/+/, "").trim();
|
|
313
|
+
return storageKey.length > 0 ? storageKey : null;
|
|
282
314
|
}
|
|
283
315
|
function ensureStorageKey(storageKey) {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
290
|
-
throw new Error(`Invalid asset storage key: ${storageKey}`);
|
|
291
|
-
}
|
|
292
|
-
return segments.join("/");
|
|
316
|
+
const normalized = storageKey.trim().replace(/^\/+|\/+$/g, "");
|
|
317
|
+
if (!normalized) throw new Error("Asset storage key must not be empty.");
|
|
318
|
+
const segments = normalized.split("/");
|
|
319
|
+
if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) throw new Error(`Invalid asset storage key: ${storageKey}`);
|
|
320
|
+
return segments.join("/");
|
|
293
321
|
}
|
|
294
322
|
function hydrateStoredAssetRecord(value) {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
323
|
+
const fileName = normalizeFileName(value.fileName);
|
|
324
|
+
return {
|
|
325
|
+
...value,
|
|
326
|
+
fileName,
|
|
327
|
+
storedName: normalizeSegment(value.storedName || fileName),
|
|
328
|
+
mimeType: normalizeMimeType(value.mimeType)
|
|
329
|
+
};
|
|
302
330
|
}
|
|
303
331
|
function toAssetMeta(record) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
332
|
+
return {
|
|
333
|
+
uri: record.uri,
|
|
334
|
+
fileName: record.fileName,
|
|
335
|
+
mimeType: record.mimeType,
|
|
336
|
+
sizeBytes: record.sizeBytes,
|
|
337
|
+
createdAt: record.createdAt
|
|
338
|
+
};
|
|
311
339
|
}
|
|
312
340
|
function isTextLikeAsset(params) {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
".sh"
|
|
345
|
-
].some((suffix) => normalizedName.endsWith(suffix));
|
|
341
|
+
const mimeType = normalizeMimeType(params.mimeType);
|
|
342
|
+
if (mimeType.startsWith("text/") || mimeType === "application/json" || mimeType === "application/xml" || mimeType === "text/xml" || mimeType === "application/yaml" || mimeType === "text/yaml" || mimeType === "application/x-yaml" || mimeType === "text/csv") return true;
|
|
343
|
+
const normalizedName = (params.fileName ?? "").trim().toLowerCase();
|
|
344
|
+
return [
|
|
345
|
+
".json",
|
|
346
|
+
".md",
|
|
347
|
+
".txt",
|
|
348
|
+
".csv",
|
|
349
|
+
".xml",
|
|
350
|
+
".yaml",
|
|
351
|
+
".yml",
|
|
352
|
+
".js",
|
|
353
|
+
".mjs",
|
|
354
|
+
".cjs",
|
|
355
|
+
".ts",
|
|
356
|
+
".tsx",
|
|
357
|
+
".jsx",
|
|
358
|
+
".py",
|
|
359
|
+
".rb",
|
|
360
|
+
".go",
|
|
361
|
+
".rs",
|
|
362
|
+
".java",
|
|
363
|
+
".kt",
|
|
364
|
+
".swift",
|
|
365
|
+
".php",
|
|
366
|
+
".css",
|
|
367
|
+
".scss",
|
|
368
|
+
".html",
|
|
369
|
+
".sql",
|
|
370
|
+
".sh"
|
|
371
|
+
].some((suffix) => normalizedName.endsWith(suffix));
|
|
346
372
|
}
|
|
347
373
|
var LocalAssetStore = class {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
sizeBytes: params.bytes.length,
|
|
469
|
-
createdAt: createdAt.toISOString(),
|
|
470
|
-
sha256: createHash("sha256").update(params.bytes).digest("hex")
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
async writeMeta(assetDir, record) {
|
|
474
|
-
await writeFile(join(assetDir, "meta.json"), `${JSON.stringify(record)}
|
|
475
|
-
`, "utf8");
|
|
476
|
-
}
|
|
477
|
-
resolveContentPathOrThrow(record) {
|
|
478
|
-
return join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
|
|
479
|
-
}
|
|
480
|
-
resolveStorageKeyDirectory(storageKey) {
|
|
481
|
-
return join(this.rootDir, ensureStorageKey(storageKey));
|
|
482
|
-
}
|
|
374
|
+
rootDir;
|
|
375
|
+
constructor(options) {
|
|
376
|
+
this.rootDir = ensureAssetRoot(options.rootDir);
|
|
377
|
+
}
|
|
378
|
+
async put(input) {
|
|
379
|
+
return { uri: (input.kind === "path" ? await this.putFromPath(input) : await this.putFromBytes({
|
|
380
|
+
fileName: input.fileName,
|
|
381
|
+
mimeType: input.mimeType,
|
|
382
|
+
bytes: input.bytes,
|
|
383
|
+
createdAt: input.createdAt
|
|
384
|
+
})).uri };
|
|
385
|
+
}
|
|
386
|
+
async putBytes(params) {
|
|
387
|
+
return this.putFromBytes(params);
|
|
388
|
+
}
|
|
389
|
+
async putPath(params) {
|
|
390
|
+
return this.putFromPath({
|
|
391
|
+
kind: "path",
|
|
392
|
+
...params
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
async export(ref, targetPath) {
|
|
396
|
+
const record = await this.statRecord(ref.uri);
|
|
397
|
+
if (!record) throw new Error(`Asset not found: ${ref.uri}`);
|
|
398
|
+
const outputPath = resolve(targetPath);
|
|
399
|
+
await ensureParentDirectory(outputPath);
|
|
400
|
+
await copyFile(this.resolveContentPathOrThrow(record), outputPath);
|
|
401
|
+
return outputPath;
|
|
402
|
+
}
|
|
403
|
+
async stat(ref) {
|
|
404
|
+
const record = await this.statRecord(ref.uri);
|
|
405
|
+
return record ? toAssetMeta(record) : null;
|
|
406
|
+
}
|
|
407
|
+
getByUri(uri) {
|
|
408
|
+
const storageKey = parseAssetUri(uri);
|
|
409
|
+
if (!storageKey) return null;
|
|
410
|
+
const metaPath = join(this.resolveStorageKeyDirectory(storageKey), "meta.json");
|
|
411
|
+
if (!existsSync(metaPath)) return null;
|
|
412
|
+
const text = readFileSync(metaPath, "utf8");
|
|
413
|
+
return hydrateStoredAssetRecord(JSON.parse(text));
|
|
414
|
+
}
|
|
415
|
+
async statRecord(uri) {
|
|
416
|
+
const record = this.getByUri(uri);
|
|
417
|
+
if (!record) return null;
|
|
418
|
+
try {
|
|
419
|
+
await stat(this.resolveContentPathOrThrow(record));
|
|
420
|
+
return record;
|
|
421
|
+
} catch {
|
|
422
|
+
return null;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
async readAssetBytes(uri) {
|
|
426
|
+
const record = await this.statRecord(uri);
|
|
427
|
+
if (!record) return null;
|
|
428
|
+
try {
|
|
429
|
+
return await readFile(this.resolveContentPathOrThrow(record));
|
|
430
|
+
} catch {
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
resolveContentPath(uri) {
|
|
435
|
+
const record = this.getByUri(uri);
|
|
436
|
+
return record ? this.resolveContentPathOrThrow(record) : null;
|
|
437
|
+
}
|
|
438
|
+
async putFromPath(input) {
|
|
439
|
+
const sourcePath = resolve(input.path);
|
|
440
|
+
if (!(await stat(sourcePath)).isFile()) throw new Error(`Asset source path is not a file: ${sourcePath}`);
|
|
441
|
+
const bytes = readFileSync(sourcePath);
|
|
442
|
+
const record = this.buildRecord({
|
|
443
|
+
fileName: input.fileName ?? basename(sourcePath),
|
|
444
|
+
mimeType: input.mimeType,
|
|
445
|
+
bytes,
|
|
446
|
+
createdAt: input.createdAt
|
|
447
|
+
});
|
|
448
|
+
const assetDir = this.resolveStorageKeyDirectory(record.storageKey);
|
|
449
|
+
await mkdir(assetDir, { recursive: true });
|
|
450
|
+
copyFileSync(sourcePath, join(assetDir, record.storedName));
|
|
451
|
+
await this.writeMeta(assetDir, record);
|
|
452
|
+
return record;
|
|
453
|
+
}
|
|
454
|
+
async putFromBytes(params) {
|
|
455
|
+
const bytes = Buffer.from(params.bytes);
|
|
456
|
+
const record = this.buildRecord({
|
|
457
|
+
fileName: params.fileName,
|
|
458
|
+
mimeType: params.mimeType,
|
|
459
|
+
bytes,
|
|
460
|
+
createdAt: params.createdAt
|
|
461
|
+
});
|
|
462
|
+
const assetDir = this.resolveStorageKeyDirectory(record.storageKey);
|
|
463
|
+
await mkdir(assetDir, { recursive: true });
|
|
464
|
+
await writeFile(join(assetDir, record.storedName), bytes);
|
|
465
|
+
await this.writeMeta(assetDir, record);
|
|
466
|
+
return record;
|
|
467
|
+
}
|
|
468
|
+
buildRecord(params) {
|
|
469
|
+
const createdAt = params.createdAt ?? /* @__PURE__ */ new Date();
|
|
470
|
+
const id = buildAssetId();
|
|
471
|
+
const storageKey = buildStorageKey(createdAt, id);
|
|
472
|
+
const fileName = normalizeFileName(params.fileName);
|
|
473
|
+
return {
|
|
474
|
+
id,
|
|
475
|
+
uri: buildAssetUri(storageKey),
|
|
476
|
+
storageKey,
|
|
477
|
+
fileName,
|
|
478
|
+
storedName: normalizeSegment(fileName),
|
|
479
|
+
mimeType: normalizeMimeType(params.mimeType),
|
|
480
|
+
sizeBytes: params.bytes.length,
|
|
481
|
+
createdAt: createdAt.toISOString(),
|
|
482
|
+
sha256: createHash("sha256").update(params.bytes).digest("hex")
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async writeMeta(assetDir, record) {
|
|
486
|
+
await writeFile(join(assetDir, "meta.json"), `${JSON.stringify(record)}\n`, "utf8");
|
|
487
|
+
}
|
|
488
|
+
resolveContentPathOrThrow(record) {
|
|
489
|
+
return join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
|
|
490
|
+
}
|
|
491
|
+
resolveStorageKeyDirectory(storageKey) {
|
|
492
|
+
return join(this.rootDir, ensureStorageKey(storageKey));
|
|
493
|
+
}
|
|
483
494
|
};
|
|
484
495
|
function buildAssetContentPath(params) {
|
|
485
|
-
|
|
486
|
-
|
|
496
|
+
const query = new URLSearchParams({ uri: params.assetUri });
|
|
497
|
+
return `${params.basePath}?${query.toString()}`;
|
|
487
498
|
}
|
|
488
499
|
async function ensureParentDirectory(filePath) {
|
|
489
|
-
|
|
500
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
490
501
|
}
|
|
491
|
-
|
|
492
|
-
|
|
502
|
+
//#endregion
|
|
503
|
+
//#region src/round-buffer.ts
|
|
493
504
|
var DefaultNcpRoundBuffer = class {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
505
|
+
text = "";
|
|
506
|
+
toolCalls = [];
|
|
507
|
+
pending = null;
|
|
508
|
+
appendText = (delta) => {
|
|
509
|
+
this.text += delta;
|
|
510
|
+
};
|
|
511
|
+
getText = () => {
|
|
512
|
+
return this.text;
|
|
513
|
+
};
|
|
514
|
+
appendToolCall = (result) => {
|
|
515
|
+
this.toolCalls.push(result);
|
|
516
|
+
};
|
|
517
|
+
getToolCalls = () => {
|
|
518
|
+
return [...this.toolCalls];
|
|
519
|
+
};
|
|
520
|
+
startToolCall = (toolCallId, toolName) => {
|
|
521
|
+
this.pending = {
|
|
522
|
+
toolCallId,
|
|
523
|
+
toolName,
|
|
524
|
+
args: void 0
|
|
525
|
+
};
|
|
526
|
+
};
|
|
527
|
+
appendToolCallArgs = (args) => {
|
|
528
|
+
if (this.pending) this.pending.args = args;
|
|
529
|
+
};
|
|
530
|
+
consumePendingToolCall = () => {
|
|
531
|
+
const p = this.pending;
|
|
532
|
+
this.pending = null;
|
|
533
|
+
return p;
|
|
534
|
+
};
|
|
535
|
+
clear = () => {
|
|
536
|
+
this.text = "";
|
|
537
|
+
this.toolCalls.length = 0;
|
|
538
|
+
this.pending = null;
|
|
539
|
+
};
|
|
525
540
|
};
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
import {
|
|
529
|
-
NcpEventType as NcpEventType2
|
|
530
|
-
} from "@nextclaw/ncp";
|
|
531
|
-
|
|
532
|
-
// src/stream-encoder.utils.ts
|
|
533
|
-
import {
|
|
534
|
-
NcpAssistantTextStreamNormalizer
|
|
535
|
-
} from "@nextclaw/ncp";
|
|
536
|
-
import { NcpEventType } from "@nextclaw/ncp";
|
|
541
|
+
//#endregion
|
|
542
|
+
//#region src/stream-encoder.utils.ts
|
|
537
543
|
function getToolCallIndex(toolDelta, fallback) {
|
|
538
|
-
|
|
539
|
-
|
|
544
|
+
const idx = toolDelta.index;
|
|
545
|
+
return typeof idx === "number" && Number.isFinite(idx) ? idx : fallback;
|
|
540
546
|
}
|
|
541
547
|
function applyToolDelta(current, toolDelta) {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
return next;
|
|
548
|
+
const next = {
|
|
549
|
+
...current,
|
|
550
|
+
argumentsText: current.argumentsText
|
|
551
|
+
};
|
|
552
|
+
if (typeof toolDelta.id === "string" && toolDelta.id.trim()) next.id = toolDelta.id;
|
|
553
|
+
const fn = toolDelta.function;
|
|
554
|
+
if (fn && typeof fn === "object" && !Array.isArray(fn)) {
|
|
555
|
+
if (typeof fn.name === "string" && fn.name.trim()) next.name = fn.name.trim();
|
|
556
|
+
if (typeof fn.arguments === "string" && fn.arguments.length > 0) next.argumentsText += fn.arguments;
|
|
557
|
+
}
|
|
558
|
+
return next;
|
|
556
559
|
}
|
|
557
560
|
function* emitTextDeltas(delta, ctx, state) {
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
561
|
+
const content = delta.content;
|
|
562
|
+
if (typeof content !== "string" || content.length === 0) return state;
|
|
563
|
+
if (state.normalizer) {
|
|
564
|
+
let textStarted = state.textStarted;
|
|
565
|
+
for (const segment of state.normalizer.push(content)) {
|
|
566
|
+
if (segment.type === "reasoning") {
|
|
567
|
+
yield {
|
|
568
|
+
type: NcpEventType.MessageReasoningDelta,
|
|
569
|
+
payload: {
|
|
570
|
+
...ctx,
|
|
571
|
+
delta: segment.text
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
if (!textStarted) {
|
|
577
|
+
yield {
|
|
578
|
+
type: NcpEventType.MessageTextStart,
|
|
579
|
+
payload: ctx
|
|
580
|
+
};
|
|
581
|
+
textStarted = true;
|
|
582
|
+
}
|
|
583
|
+
yield {
|
|
584
|
+
type: NcpEventType.MessageTextDelta,
|
|
585
|
+
payload: {
|
|
586
|
+
...ctx,
|
|
587
|
+
delta: segment.text
|
|
588
|
+
}
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return {
|
|
592
|
+
...state,
|
|
593
|
+
textStarted
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
if (!state.textStarted) {
|
|
597
|
+
yield {
|
|
598
|
+
type: NcpEventType.MessageTextStart,
|
|
599
|
+
payload: ctx
|
|
600
|
+
};
|
|
601
|
+
yield {
|
|
602
|
+
type: NcpEventType.MessageTextDelta,
|
|
603
|
+
payload: {
|
|
604
|
+
...ctx,
|
|
605
|
+
delta: content
|
|
606
|
+
}
|
|
607
|
+
};
|
|
608
|
+
return {
|
|
609
|
+
...state,
|
|
610
|
+
textStarted: true
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
yield {
|
|
614
|
+
type: NcpEventType.MessageTextDelta,
|
|
615
|
+
payload: {
|
|
616
|
+
...ctx,
|
|
617
|
+
delta: content
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
return state;
|
|
594
621
|
}
|
|
595
622
|
function* flushTextDeltas(ctx, state) {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
623
|
+
if (!state.normalizer) return state;
|
|
624
|
+
let textStarted = state.textStarted;
|
|
625
|
+
for (const segment of state.normalizer.finish()) {
|
|
626
|
+
if (segment.type === "reasoning") {
|
|
627
|
+
yield {
|
|
628
|
+
type: NcpEventType.MessageReasoningDelta,
|
|
629
|
+
payload: {
|
|
630
|
+
...ctx,
|
|
631
|
+
delta: segment.text
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
if (!textStarted) {
|
|
637
|
+
yield {
|
|
638
|
+
type: NcpEventType.MessageTextStart,
|
|
639
|
+
payload: ctx
|
|
640
|
+
};
|
|
641
|
+
textStarted = true;
|
|
642
|
+
}
|
|
643
|
+
yield {
|
|
644
|
+
type: NcpEventType.MessageTextDelta,
|
|
645
|
+
payload: {
|
|
646
|
+
...ctx,
|
|
647
|
+
delta: segment.text
|
|
648
|
+
}
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
return {
|
|
652
|
+
...state,
|
|
653
|
+
textStarted
|
|
654
|
+
};
|
|
621
655
|
}
|
|
622
656
|
function* emitReasoningDelta(delta, ctx) {
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
657
|
+
const reasoning = delta.reasoning_content ?? delta.reasoning;
|
|
658
|
+
if (typeof reasoning !== "string" || !reasoning) return;
|
|
659
|
+
yield {
|
|
660
|
+
type: NcpEventType.MessageReasoningDelta,
|
|
661
|
+
payload: {
|
|
662
|
+
...ctx,
|
|
663
|
+
delta: reasoning
|
|
664
|
+
}
|
|
665
|
+
};
|
|
626
666
|
}
|
|
627
667
|
function* emitToolCallDeltas(delta, buffers, ctx) {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
}
|
|
676
|
-
current.emittedArgsDelta = true;
|
|
677
|
-
current.pendingArgumentDeltas = [];
|
|
678
|
-
}
|
|
679
|
-
buffers.set(index, current);
|
|
680
|
-
}
|
|
668
|
+
const toolDeltas = delta.tool_calls;
|
|
669
|
+
if (!Array.isArray(toolDeltas)) return;
|
|
670
|
+
for (const toolDelta of toolDeltas) {
|
|
671
|
+
const index = getToolCallIndex(toolDelta, buffers.size);
|
|
672
|
+
const current = applyToolDelta(buffers.get(index) ?? {
|
|
673
|
+
argumentsText: "",
|
|
674
|
+
pendingArgumentDeltas: []
|
|
675
|
+
}, toolDelta);
|
|
676
|
+
const argsDelta = typeof toolDelta.function?.arguments === "string" && toolDelta.function.arguments.length > 0 ? toolDelta.function.arguments : null;
|
|
677
|
+
const toolCallId = current.id;
|
|
678
|
+
const toolName = current.name;
|
|
679
|
+
if (toolCallId && toolName && !current.emittedStart) {
|
|
680
|
+
yield {
|
|
681
|
+
type: NcpEventType.MessageToolCallStart,
|
|
682
|
+
payload: {
|
|
683
|
+
...ctx,
|
|
684
|
+
toolCallId,
|
|
685
|
+
toolName
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
current.emittedStart = true;
|
|
689
|
+
}
|
|
690
|
+
if (argsDelta) if (current.emittedStart && current.id) {
|
|
691
|
+
yield {
|
|
692
|
+
type: NcpEventType.MessageToolCallArgsDelta,
|
|
693
|
+
payload: {
|
|
694
|
+
...ctx,
|
|
695
|
+
toolCallId: current.id,
|
|
696
|
+
delta: argsDelta
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
current.emittedArgsDelta = true;
|
|
700
|
+
} else current.pendingArgumentDeltas = [...current.pendingArgumentDeltas ?? [], argsDelta];
|
|
701
|
+
if (current.emittedStart && current.id && (current.pendingArgumentDeltas?.length ?? 0) > 0) {
|
|
702
|
+
for (const pendingDelta of current.pendingArgumentDeltas ?? []) yield {
|
|
703
|
+
type: NcpEventType.MessageToolCallArgsDelta,
|
|
704
|
+
payload: {
|
|
705
|
+
...ctx,
|
|
706
|
+
toolCallId: current.id,
|
|
707
|
+
delta: pendingDelta
|
|
708
|
+
}
|
|
709
|
+
};
|
|
710
|
+
current.emittedArgsDelta = true;
|
|
711
|
+
current.pendingArgumentDeltas = [];
|
|
712
|
+
}
|
|
713
|
+
buffers.set(index, current);
|
|
714
|
+
}
|
|
681
715
|
}
|
|
682
716
|
function* flushToolCalls(buffers, ctx) {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
717
|
+
const ordered = Array.from(buffers.entries()).sort(([a], [b]) => a - b);
|
|
718
|
+
for (const [, buf] of ordered) {
|
|
719
|
+
if (!buf.id || !buf.name) continue;
|
|
720
|
+
if (!buf.emittedArgsDelta) yield {
|
|
721
|
+
type: NcpEventType.MessageToolCallArgs,
|
|
722
|
+
payload: {
|
|
723
|
+
...ctx,
|
|
724
|
+
toolCallId: buf.id,
|
|
725
|
+
args: buf.argumentsText
|
|
726
|
+
}
|
|
727
|
+
};
|
|
728
|
+
yield {
|
|
729
|
+
type: NcpEventType.MessageToolCallEnd,
|
|
730
|
+
payload: {
|
|
731
|
+
...ctx,
|
|
732
|
+
toolCallId: buf.id
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
697
736
|
}
|
|
698
737
|
function createStreamContentState(reasoningNormalizationMode) {
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
738
|
+
return {
|
|
739
|
+
textStarted: false,
|
|
740
|
+
normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
|
|
741
|
+
};
|
|
703
742
|
}
|
|
704
|
-
|
|
705
|
-
|
|
743
|
+
//#endregion
|
|
744
|
+
//#region src/stream-encoder.ts
|
|
745
|
+
/**
|
|
746
|
+
* Converts LLM stream chunks to NCP events (text, reasoning, tool calls).
|
|
747
|
+
* Does not emit RunFinished; that is the runtime's responsibility after the loop completes.
|
|
748
|
+
*/
|
|
706
749
|
var DefaultNcpStreamEncoder = class {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
750
|
+
reasoningNormalizationMode;
|
|
751
|
+
constructor(config = {}) {
|
|
752
|
+
this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
|
|
753
|
+
}
|
|
754
|
+
async *encode(stream, context) {
|
|
755
|
+
const { sessionId, messageId } = context;
|
|
756
|
+
let state = createStreamContentState(this.reasoningNormalizationMode);
|
|
757
|
+
const toolCallBuffers = /* @__PURE__ */ new Map();
|
|
758
|
+
for await (const chunk of stream) {
|
|
759
|
+
const choice = chunk.choices?.[0];
|
|
760
|
+
if (!choice) continue;
|
|
761
|
+
const delta = choice.delta;
|
|
762
|
+
if (delta) {
|
|
763
|
+
yield* emitReasoningDelta(delta, {
|
|
764
|
+
sessionId,
|
|
765
|
+
messageId
|
|
766
|
+
});
|
|
767
|
+
state = yield* emitTextDeltas(delta, {
|
|
768
|
+
sessionId,
|
|
769
|
+
messageId
|
|
770
|
+
}, state);
|
|
771
|
+
yield* emitToolCallDeltas(delta, toolCallBuffers, {
|
|
772
|
+
sessionId,
|
|
773
|
+
messageId
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
const finishReason = choice.finish_reason;
|
|
777
|
+
if (typeof finishReason === "string" && finishReason.trim().length > 0) {
|
|
778
|
+
state = yield* flushTextDeltas({
|
|
779
|
+
sessionId,
|
|
780
|
+
messageId
|
|
781
|
+
}, state);
|
|
782
|
+
yield* flushToolCalls(toolCallBuffers, {
|
|
783
|
+
sessionId,
|
|
784
|
+
messageId
|
|
785
|
+
});
|
|
786
|
+
if (state.textStarted) yield {
|
|
787
|
+
type: NcpEventType.MessageTextEnd,
|
|
788
|
+
payload: {
|
|
789
|
+
sessionId,
|
|
790
|
+
messageId
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
735
796
|
};
|
|
736
|
-
|
|
737
|
-
|
|
797
|
+
//#endregion
|
|
798
|
+
//#region src/tool-registry.ts
|
|
738
799
|
var DefaultNcpToolRegistry = class {
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
return tool ? await tool.execute(args) : void 0;
|
|
764
|
-
};
|
|
800
|
+
tools = /* @__PURE__ */ new Map();
|
|
801
|
+
constructor(tools = []) {
|
|
802
|
+
for (const t of tools) this.tools.set(t.name, t);
|
|
803
|
+
}
|
|
804
|
+
register = (tool) => {
|
|
805
|
+
this.tools.set(tool.name, tool);
|
|
806
|
+
};
|
|
807
|
+
listTools = () => {
|
|
808
|
+
return [...this.tools.values()];
|
|
809
|
+
};
|
|
810
|
+
getTool = (name) => {
|
|
811
|
+
return this.tools.get(name);
|
|
812
|
+
};
|
|
813
|
+
getToolDefinitions = () => {
|
|
814
|
+
return this.listTools().map((t) => ({
|
|
815
|
+
name: t.name,
|
|
816
|
+
description: t.description,
|
|
817
|
+
parameters: t.parameters
|
|
818
|
+
}));
|
|
819
|
+
};
|
|
820
|
+
execute = async (_toolCallId, toolName, args) => {
|
|
821
|
+
const tool = this.tools.get(toolName);
|
|
822
|
+
return tool ? await tool.execute(args) : void 0;
|
|
823
|
+
};
|
|
765
824
|
};
|
|
766
|
-
|
|
767
|
-
|
|
825
|
+
//#endregion
|
|
826
|
+
//#region src/llm-api-echo.ts
|
|
768
827
|
function getLastUserContent(messages) {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
}
|
|
777
|
-
return "";
|
|
828
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
829
|
+
const m = messages[i];
|
|
830
|
+
if (m.role !== "user") continue;
|
|
831
|
+
if (typeof m.content === "string") return m.content;
|
|
832
|
+
if (Array.isArray(m.content)) return m.content.map((p) => p.type === "text" ? p.text : "").join("");
|
|
833
|
+
}
|
|
834
|
+
return "";
|
|
778
835
|
}
|
|
779
836
|
var EchoNcpLLMApi = class {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
837
|
+
generate = async function* (input, options) {
|
|
838
|
+
const text = getLastUserContent(input.messages);
|
|
839
|
+
const signal = options?.signal;
|
|
840
|
+
for (const char of text) {
|
|
841
|
+
if (signal?.aborted) break;
|
|
842
|
+
yield { choices: [{
|
|
843
|
+
index: 0,
|
|
844
|
+
delta: { content: char }
|
|
845
|
+
}] };
|
|
846
|
+
}
|
|
847
|
+
yield {
|
|
848
|
+
choices: [{
|
|
849
|
+
index: 0,
|
|
850
|
+
delta: {},
|
|
851
|
+
finish_reason: "stop"
|
|
852
|
+
}],
|
|
853
|
+
usage: {
|
|
854
|
+
prompt_tokens: 0,
|
|
855
|
+
completion_tokens: text.length,
|
|
856
|
+
total_tokens: text.length
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
};
|
|
800
860
|
};
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
861
|
+
//#endregion
|
|
862
|
+
//#region src/utils.ts
|
|
863
|
+
const toolSchemaValidator = new AjvPkg({
|
|
864
|
+
allErrors: true,
|
|
865
|
+
strict: false,
|
|
866
|
+
removeAdditional: false
|
|
867
|
+
});
|
|
868
|
+
const validatorCache = /* @__PURE__ */ new WeakMap();
|
|
809
869
|
function genId() {
|
|
810
|
-
|
|
870
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
|
|
811
871
|
}
|
|
812
872
|
function stringifyRawArgs(args) {
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
return "[unserializable-object]";
|
|
821
|
-
}
|
|
822
|
-
}
|
|
823
|
-
return String(args ?? "");
|
|
873
|
+
if (typeof args === "string") return args;
|
|
874
|
+
if (args && typeof args === "object" && !Array.isArray(args)) try {
|
|
875
|
+
return JSON.stringify(args);
|
|
876
|
+
} catch {
|
|
877
|
+
return "[unserializable-object]";
|
|
878
|
+
}
|
|
879
|
+
return String(args ?? "");
|
|
824
880
|
}
|
|
825
881
|
function parseToolArgs(args) {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
};
|
|
863
|
-
} catch (error) {
|
|
864
|
-
return {
|
|
865
|
-
ok: false,
|
|
866
|
-
rawText,
|
|
867
|
-
issues: [error instanceof Error ? error.message : "Failed to parse tool arguments JSON."]
|
|
868
|
-
};
|
|
869
|
-
}
|
|
882
|
+
if (args && typeof args === "object" && !Array.isArray(args)) return {
|
|
883
|
+
ok: true,
|
|
884
|
+
rawText: stringifyRawArgs(args),
|
|
885
|
+
value: args
|
|
886
|
+
};
|
|
887
|
+
const rawText = stringifyRawArgs(args);
|
|
888
|
+
if (typeof args !== "string") return {
|
|
889
|
+
ok: false,
|
|
890
|
+
rawText,
|
|
891
|
+
issues: ["Tool arguments must be a JSON object string."]
|
|
892
|
+
};
|
|
893
|
+
const trimmed = args.trim();
|
|
894
|
+
if (!trimmed) return {
|
|
895
|
+
ok: false,
|
|
896
|
+
rawText,
|
|
897
|
+
issues: ["Tool arguments are empty."]
|
|
898
|
+
};
|
|
899
|
+
try {
|
|
900
|
+
const parsed = JSON.parse(trimmed);
|
|
901
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {
|
|
902
|
+
ok: false,
|
|
903
|
+
rawText,
|
|
904
|
+
issues: ["Tool arguments JSON must decode to an object."]
|
|
905
|
+
};
|
|
906
|
+
return {
|
|
907
|
+
ok: true,
|
|
908
|
+
rawText,
|
|
909
|
+
value: parsed
|
|
910
|
+
};
|
|
911
|
+
} catch (error) {
|
|
912
|
+
return {
|
|
913
|
+
ok: false,
|
|
914
|
+
rawText,
|
|
915
|
+
issues: [error instanceof Error ? error.message : "Failed to parse tool arguments JSON."]
|
|
916
|
+
};
|
|
917
|
+
}
|
|
870
918
|
}
|
|
871
919
|
function validateToolArgs(args, schema) {
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
920
|
+
if (!schema) return [];
|
|
921
|
+
const validate = getOrCreateValidator(schema);
|
|
922
|
+
if (validate(args)) return [];
|
|
923
|
+
return formatSchemaIssues(validate.errors);
|
|
876
924
|
}
|
|
877
|
-
function
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const errors = [];
|
|
884
|
-
if (schema.enum && !schema.enum.includes(value)) {
|
|
885
|
-
errors.push(`${label} must be one of ${JSON.stringify(schema.enum)}`);
|
|
886
|
-
}
|
|
887
|
-
if (typeof value === "number") {
|
|
888
|
-
if (schema.minimum !== void 0 && value < schema.minimum) {
|
|
889
|
-
errors.push(`${label} must be >= ${schema.minimum}`);
|
|
890
|
-
}
|
|
891
|
-
if (schema.maximum !== void 0 && value > schema.maximum) {
|
|
892
|
-
errors.push(`${label} must be <= ${schema.maximum}`);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
if (typeof value === "string") {
|
|
896
|
-
if (schema.minLength !== void 0 && value.length < schema.minLength) {
|
|
897
|
-
errors.push(`${label} must be at least ${schema.minLength} chars`);
|
|
898
|
-
}
|
|
899
|
-
if (schema.maxLength !== void 0 && value.length > schema.maxLength) {
|
|
900
|
-
errors.push(`${label} must be at most ${schema.maxLength} chars`);
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
if (type === "object") {
|
|
904
|
-
const objectValue = value;
|
|
905
|
-
for (const key of schema.required ?? []) {
|
|
906
|
-
if (!(key in objectValue)) {
|
|
907
|
-
errors.push(`missing required ${path ? `${path}.${key}` : key}`);
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
const properties = schema.properties ?? {};
|
|
911
|
-
for (const [key, childValue] of Object.entries(objectValue)) {
|
|
912
|
-
const childSchema = properties[key];
|
|
913
|
-
if (!childSchema) {
|
|
914
|
-
continue;
|
|
915
|
-
}
|
|
916
|
-
errors.push(...validateToolValue(childValue, childSchema, path ? `${path}.${key}` : key));
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
if (type === "array" && schema.items && Array.isArray(value)) {
|
|
920
|
-
value.forEach((item, index) => {
|
|
921
|
-
errors.push(...validateToolValue(item, schema.items, `${label}[${index}]`));
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
return errors;
|
|
925
|
+
function getOrCreateValidator(schema) {
|
|
926
|
+
const cached = validatorCache.get(schema);
|
|
927
|
+
if (cached) return cached;
|
|
928
|
+
const validate = toolSchemaValidator.compile(schema);
|
|
929
|
+
validatorCache.set(schema, validate);
|
|
930
|
+
return validate;
|
|
925
931
|
}
|
|
926
|
-
function
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
case "boolean":
|
|
935
|
-
return typeof value === "boolean";
|
|
936
|
-
case "array":
|
|
937
|
-
return Array.isArray(value);
|
|
938
|
-
case "object":
|
|
939
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
940
|
-
default:
|
|
941
|
-
return true;
|
|
942
|
-
}
|
|
932
|
+
function formatSchemaIssues(errors) {
|
|
933
|
+
if (!errors || errors.length === 0) return ["Tool arguments do not match the declared schema."];
|
|
934
|
+
return errors.map((error) => {
|
|
935
|
+
const instancePath = error.instancePath.replace(/^\//, "").replace(/\//g, ".");
|
|
936
|
+
if (error.keyword === "required" && "missingProperty" in error.params && typeof error.params.missingProperty === "string") return `${instancePath ? `${instancePath}.${error.params.missingProperty}` : error.params.missingProperty} is required`;
|
|
937
|
+
if (error.keyword === "additionalProperties" && "additionalProperty" in error.params && typeof error.params.additionalProperty === "string") return `${instancePath ? `${instancePath}.${error.params.additionalProperty}` : error.params.additionalProperty} is not allowed`;
|
|
938
|
+
return `${instancePath || "parameter"}: ${error.message ?? "invalid"}`;
|
|
939
|
+
});
|
|
943
940
|
}
|
|
944
941
|
function createInvalidToolArgumentsResult(params) {
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
942
|
+
return {
|
|
943
|
+
ok: false,
|
|
944
|
+
error: {
|
|
945
|
+
code: "invalid_tool_arguments",
|
|
946
|
+
message: "Tool arguments are invalid.",
|
|
947
|
+
toolCallId: params.toolCallId,
|
|
948
|
+
toolName: params.toolName,
|
|
949
|
+
rawArgumentsText: params.rawArgumentsText,
|
|
950
|
+
issues: params.issues
|
|
951
|
+
}
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
function createToolExecutionFailedResult(params) {
|
|
955
|
+
const { toolCallId, toolName, error } = params;
|
|
956
|
+
return {
|
|
957
|
+
ok: false,
|
|
958
|
+
error: {
|
|
959
|
+
code: "tool_execution_failed",
|
|
960
|
+
message: error instanceof Error ? error.message : String(error),
|
|
961
|
+
toolCallId,
|
|
962
|
+
toolName
|
|
963
|
+
}
|
|
964
|
+
};
|
|
956
965
|
}
|
|
957
966
|
function appendToolRoundToInput(input, reasoning, text, toolResults) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
967
|
+
const assistantMsg = {
|
|
968
|
+
role: "assistant",
|
|
969
|
+
content: text || null,
|
|
970
|
+
...reasoning ? { reasoning_content: reasoning } : {},
|
|
971
|
+
tool_calls: toolResults.map((tr) => ({
|
|
972
|
+
id: tr.toolCallId,
|
|
973
|
+
type: "function",
|
|
974
|
+
function: {
|
|
975
|
+
name: tr.toolName,
|
|
976
|
+
arguments: tr.rawArgsText
|
|
977
|
+
}
|
|
978
|
+
}))
|
|
979
|
+
};
|
|
980
|
+
const toolMsgs = toolResults.map((tr) => ({
|
|
981
|
+
role: "tool",
|
|
982
|
+
content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result ?? {}),
|
|
983
|
+
tool_call_id: tr.toolCallId
|
|
984
|
+
}));
|
|
985
|
+
return {
|
|
986
|
+
...input,
|
|
987
|
+
messages: [
|
|
988
|
+
...input.messages,
|
|
989
|
+
assistantMsg,
|
|
990
|
+
...toolMsgs
|
|
991
|
+
]
|
|
992
|
+
};
|
|
980
993
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
import {
|
|
984
|
-
normalizeAssistantText
|
|
985
|
-
} from "@nextclaw/ncp";
|
|
994
|
+
//#endregion
|
|
995
|
+
//#region src/round-collector.ts
|
|
986
996
|
var DefaultNcpRoundCollector = class {
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
return "";
|
|
1037
|
-
}
|
|
1038
|
-
return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).reasoning;
|
|
1039
|
-
}
|
|
1040
|
-
getToolCalls() {
|
|
1041
|
-
const orderedEntries = Array.from(this.toolCallBuffers.entries()).sort(([left], [right]) => left - right);
|
|
1042
|
-
const toolCalls = [];
|
|
1043
|
-
for (const [, buffer] of orderedEntries) {
|
|
1044
|
-
if (!buffer.id || !buffer.name) {
|
|
1045
|
-
continue;
|
|
1046
|
-
}
|
|
1047
|
-
toolCalls.push({
|
|
1048
|
-
toolCallId: buffer.id,
|
|
1049
|
-
toolName: buffer.name,
|
|
1050
|
-
args: buffer.argumentsText
|
|
1051
|
-
});
|
|
1052
|
-
}
|
|
1053
|
-
return toolCalls;
|
|
1054
|
-
}
|
|
997
|
+
rawText = "";
|
|
998
|
+
explicitReasoning = "";
|
|
999
|
+
toolCallBuffers = /* @__PURE__ */ new Map();
|
|
1000
|
+
constructor(reasoningNormalizationMode = "off") {
|
|
1001
|
+
this.reasoningNormalizationMode = reasoningNormalizationMode;
|
|
1002
|
+
}
|
|
1003
|
+
clear() {
|
|
1004
|
+
this.rawText = "";
|
|
1005
|
+
this.explicitReasoning = "";
|
|
1006
|
+
this.toolCallBuffers.clear();
|
|
1007
|
+
}
|
|
1008
|
+
consumeChunk(chunk) {
|
|
1009
|
+
const choice = chunk.choices?.[0];
|
|
1010
|
+
if (!choice) return;
|
|
1011
|
+
const delta = choice.delta;
|
|
1012
|
+
if (!delta) return;
|
|
1013
|
+
if (typeof delta.content === "string" && delta.content.length > 0) this.rawText += delta.content;
|
|
1014
|
+
const reasoning = delta.reasoning_content ?? delta.reasoning;
|
|
1015
|
+
if (typeof reasoning === "string" && reasoning.length > 0) this.explicitReasoning += reasoning;
|
|
1016
|
+
const toolDeltas = delta.tool_calls;
|
|
1017
|
+
if (!Array.isArray(toolDeltas)) return;
|
|
1018
|
+
for (const toolDelta of toolDeltas) {
|
|
1019
|
+
const index = getToolCallIndex(toolDelta, this.toolCallBuffers.size);
|
|
1020
|
+
const current = applyToolDelta(this.toolCallBuffers.get(index) ?? { argumentsText: "" }, toolDelta);
|
|
1021
|
+
this.toolCallBuffers.set(index, current);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
getText() {
|
|
1025
|
+
if (this.reasoningNormalizationMode !== "think-tags") return this.rawText;
|
|
1026
|
+
return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).text;
|
|
1027
|
+
}
|
|
1028
|
+
getReasoning() {
|
|
1029
|
+
if (this.explicitReasoning.length > 0) return this.explicitReasoning;
|
|
1030
|
+
if (this.reasoningNormalizationMode !== "think-tags") return "";
|
|
1031
|
+
return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).reasoning;
|
|
1032
|
+
}
|
|
1033
|
+
getToolCalls() {
|
|
1034
|
+
const orderedEntries = Array.from(this.toolCallBuffers.entries()).sort(([left], [right]) => left - right);
|
|
1035
|
+
const toolCalls = [];
|
|
1036
|
+
for (const [, buffer] of orderedEntries) {
|
|
1037
|
+
if (!buffer.id || !buffer.name) continue;
|
|
1038
|
+
toolCalls.push({
|
|
1039
|
+
toolCallId: buffer.id,
|
|
1040
|
+
toolName: buffer.name,
|
|
1041
|
+
args: buffer.argumentsText
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
return toolCalls;
|
|
1045
|
+
}
|
|
1055
1046
|
};
|
|
1056
|
-
|
|
1057
|
-
|
|
1047
|
+
//#endregion
|
|
1048
|
+
//#region src/runtime.ts
|
|
1058
1049
|
var DefaultNcpAgentRuntime = class {
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1050
|
+
contextBuilder;
|
|
1051
|
+
llmApi;
|
|
1052
|
+
toolRegistry;
|
|
1053
|
+
stateManager;
|
|
1054
|
+
streamEncoder;
|
|
1055
|
+
reasoningNormalizationMode;
|
|
1056
|
+
constructor(config) {
|
|
1057
|
+
this.contextBuilder = config.contextBuilder;
|
|
1058
|
+
this.llmApi = config.llmApi;
|
|
1059
|
+
this.toolRegistry = config.toolRegistry;
|
|
1060
|
+
this.stateManager = config.stateManager;
|
|
1061
|
+
this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
|
|
1062
|
+
this.streamEncoder = config.streamEncoder ?? new DefaultNcpStreamEncoder({ reasoningNormalizationMode: this.reasoningNormalizationMode });
|
|
1063
|
+
}
|
|
1064
|
+
run = async function* (input, options) {
|
|
1065
|
+
const ctx = {
|
|
1066
|
+
messageId: genId(),
|
|
1067
|
+
runId: genId(),
|
|
1068
|
+
sessionId: input.sessionId,
|
|
1069
|
+
correlationId: input.correlationId
|
|
1070
|
+
};
|
|
1071
|
+
const sessionMessages = this.stateManager.getSnapshot().messages;
|
|
1072
|
+
const modelInput = this.contextBuilder.prepare(input, { sessionMessages });
|
|
1073
|
+
for (const msg of input.messages) {
|
|
1074
|
+
if (isHiddenNcpMessage(msg)) continue;
|
|
1075
|
+
const messageSent = {
|
|
1076
|
+
type: NcpEventType.MessageSent,
|
|
1077
|
+
payload: {
|
|
1078
|
+
sessionId: input.sessionId,
|
|
1079
|
+
message: msg
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
await this.stateManager.dispatch(messageSent);
|
|
1083
|
+
}
|
|
1084
|
+
const runStarted = {
|
|
1085
|
+
type: NcpEventType.RunStarted,
|
|
1086
|
+
payload: {
|
|
1087
|
+
sessionId: ctx.sessionId,
|
|
1088
|
+
messageId: ctx.messageId,
|
|
1089
|
+
runId: ctx.runId
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
await this.stateManager.dispatch(runStarted);
|
|
1093
|
+
yield runStarted;
|
|
1094
|
+
for await (const event of this.runLoop(modelInput, ctx, options)) {
|
|
1095
|
+
await this.stateManager.dispatch(event);
|
|
1096
|
+
yield event;
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
/**
|
|
1100
|
+
* Agent loop: LLM stream → encoder events → tool execution (if any) → next round or finish.
|
|
1101
|
+
* RunFinished is emitted only when the entire loop completes (no more tool calls).
|
|
1102
|
+
* The stream encoder does not emit RunFinished; it only converts chunks to NCP events.
|
|
1103
|
+
*/
|
|
1104
|
+
runLoop = async function* (llmInput, ctx, options) {
|
|
1105
|
+
const roundCollector = new DefaultNcpRoundCollector(this.reasoningNormalizationMode);
|
|
1106
|
+
let currentInput = llmInput;
|
|
1107
|
+
let done = false;
|
|
1108
|
+
while (!done && !options?.signal?.aborted) {
|
|
1109
|
+
roundCollector.clear();
|
|
1110
|
+
const stream = this.llmApi.generate(currentInput, { signal: options?.signal });
|
|
1111
|
+
const tappedStream = this.tapStream(stream, (chunk) => roundCollector.consumeChunk(chunk));
|
|
1112
|
+
for await (const event of this.streamEncoder.encode(tappedStream, ctx)) yield event;
|
|
1113
|
+
const toolResults = [];
|
|
1114
|
+
for (const toolCall of roundCollector.getToolCalls()) {
|
|
1115
|
+
const toolResult = await this.executeToolCall(toolCall);
|
|
1116
|
+
toolResults.push(toolResult);
|
|
1117
|
+
yield {
|
|
1118
|
+
type: NcpEventType.MessageToolCallResult,
|
|
1119
|
+
payload: {
|
|
1120
|
+
sessionId: ctx.sessionId,
|
|
1121
|
+
toolCallId: toolCall.toolCallId,
|
|
1122
|
+
content: toolResult.result
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
if (toolResults.length === 0) {
|
|
1127
|
+
yield {
|
|
1128
|
+
type: NcpEventType.RunFinished,
|
|
1129
|
+
payload: {
|
|
1130
|
+
sessionId: ctx.sessionId,
|
|
1131
|
+
messageId: ctx.messageId,
|
|
1132
|
+
runId: ctx.runId
|
|
1133
|
+
}
|
|
1134
|
+
};
|
|
1135
|
+
done = true;
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
currentInput = appendToolRoundToInput(currentInput, roundCollector.getReasoning(), roundCollector.getText(), toolResults);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
executeToolCall = async function(toolCall) {
|
|
1142
|
+
const tool = this.toolRegistry.getTool(toolCall.toolName);
|
|
1143
|
+
const parsedArgs = parseToolArgs(toolCall.args);
|
|
1144
|
+
if (!parsedArgs.ok) return {
|
|
1145
|
+
toolCallId: toolCall.toolCallId,
|
|
1146
|
+
toolName: toolCall.toolName,
|
|
1147
|
+
args: null,
|
|
1148
|
+
rawArgsText: parsedArgs.rawText,
|
|
1149
|
+
result: createInvalidToolArgumentsResult({
|
|
1150
|
+
toolCallId: toolCall.toolCallId,
|
|
1151
|
+
toolName: toolCall.toolName,
|
|
1152
|
+
rawArgumentsText: parsedArgs.rawText,
|
|
1153
|
+
issues: parsedArgs.issues
|
|
1154
|
+
})
|
|
1155
|
+
};
|
|
1156
|
+
const validationIssues = this.resolveValidationIssues(parsedArgs.value, tool);
|
|
1157
|
+
if (validationIssues.length > 0) return {
|
|
1158
|
+
toolCallId: toolCall.toolCallId,
|
|
1159
|
+
toolName: toolCall.toolName,
|
|
1160
|
+
args: null,
|
|
1161
|
+
rawArgsText: parsedArgs.rawText,
|
|
1162
|
+
result: createInvalidToolArgumentsResult({
|
|
1163
|
+
toolCallId: toolCall.toolCallId,
|
|
1164
|
+
toolName: toolCall.toolName,
|
|
1165
|
+
rawArgumentsText: parsedArgs.rawText,
|
|
1166
|
+
issues: validationIssues
|
|
1167
|
+
})
|
|
1168
|
+
};
|
|
1169
|
+
return {
|
|
1170
|
+
toolCallId: toolCall.toolCallId,
|
|
1171
|
+
toolName: toolCall.toolName,
|
|
1172
|
+
args: parsedArgs.value,
|
|
1173
|
+
rawArgsText: parsedArgs.rawText,
|
|
1174
|
+
result: await this.executeValidatedToolCall(toolCall, parsedArgs.value)
|
|
1175
|
+
};
|
|
1176
|
+
};
|
|
1177
|
+
resolveValidationIssues = function(args, tool) {
|
|
1178
|
+
const schemaIssues = validateToolArgs(args, tool?.parameters);
|
|
1179
|
+
if (schemaIssues.length > 0) return schemaIssues;
|
|
1180
|
+
return typeof tool?.validateArgs === "function" ? tool.validateArgs(args) : [];
|
|
1181
|
+
};
|
|
1182
|
+
executeValidatedToolCall = async function(toolCall, args) {
|
|
1183
|
+
try {
|
|
1184
|
+
return await this.toolRegistry.execute(toolCall.toolCallId, toolCall.toolName, args);
|
|
1185
|
+
} catch (error) {
|
|
1186
|
+
return createToolExecutionFailedResult({
|
|
1187
|
+
toolCallId: toolCall.toolCallId,
|
|
1188
|
+
toolName: toolCall.toolName,
|
|
1189
|
+
error
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
};
|
|
1193
|
+
tapStream = async function* (stream, onChunk) {
|
|
1194
|
+
for await (const chunk of stream) {
|
|
1195
|
+
onChunk(chunk);
|
|
1196
|
+
yield chunk;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1205
1199
|
};
|
|
1200
|
+
//#endregion
|
|
1201
|
+
export { DefaultNcpAgentRuntime, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, buildAssetContentPath, buildNcpUserContent, isTextLikeAsset };
|