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