@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +135 -124
  2. package/dist/index.js +1105 -1109
  3. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,1205 +1,1201 @@
1
- // src/user-content.ts
2
- function readOptionalString(value) {
3
- if (typeof value !== "string") {
4
- return null;
5
- }
6
- const trimmed = value.trim();
7
- return trimmed.length > 0 ? trimmed : null;
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
- const fileName = readOptionalString(params.fileName) ?? "asset";
11
- const mimeType = readOptionalString(params.mimeType) ?? "application/octet-stream";
12
- const assetUri = readOptionalString(params.assetUri);
13
- const url = readOptionalString(params.url);
14
- const sizeText = typeof params.sizeBytes === "number" && Number.isFinite(params.sizeBytes) ? String(params.sizeBytes) : null;
15
- const lines = [
16
- `[Asset: ${fileName}]`,
17
- `[MIME: ${mimeType}]`,
18
- ...assetUri ? [`[Asset URI: ${assetUri}]`] : [],
19
- ...sizeText ? [`[Size Bytes: ${sizeText}]`] : [],
20
- ...url ? [`[Preview URL: ${url}]`] : [],
21
- "[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.]"
22
- ];
23
- return lines.join("\n");
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
- const fileName = readOptionalString(part.name);
27
- const mimeType = readOptionalString(part.mimeType);
28
- const assetUri = readOptionalString(part.assetUri);
29
- const url = readOptionalString(part.url);
30
- const sizeBytes = typeof part.sizeBytes === "number" ? part.sizeBytes : void 0;
31
- if (assetUri) {
32
- const stored = assetStore?.getByUri(assetUri);
33
- return formatAssetReferenceBlock({
34
- fileName: stored?.fileName ?? fileName,
35
- mimeType: stored?.mimeType ?? mimeType,
36
- assetUri,
37
- url,
38
- sizeBytes: stored?.sizeBytes ?? sizeBytes
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
- const content = [];
53
- for (const part of parts) {
54
- if ((part.type === "text" || part.type === "rich-text") && part.text.trim().length > 0) {
55
- content.push({ type: "text", text: part.text });
56
- continue;
57
- }
58
- if (part.type !== "file") {
59
- continue;
60
- }
61
- const assetReferenceBlock = resolveAssetReferenceBlock(part, options.assetStore);
62
- if (assetReferenceBlock) {
63
- content.push({ type: "text", text: assetReferenceBlock });
64
- }
65
- }
66
- if (content.length === 0) {
67
- return "";
68
- }
69
- if (content.length === 1 && content[0]?.type === "text") {
70
- return content[0].text;
71
- }
72
- return content;
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
- // src/context-builder.ts
149
+ //#endregion
150
+ //#region src/context-builder.ts
76
151
  function isRecord(value) {
77
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
152
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
153
  }
79
- function readOptionalString2(value) {
80
- if (typeof value !== "string") {
81
- return null;
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
- return part.type === "text" || part.type === "rich-text";
160
+ return part.type === "text" || part.type === "rich-text";
88
161
  }
89
162
  function mergeMessageAndRequestMetadata(input) {
90
- const messageMetadata = input.messages.slice().reverse().find((message) => isRecord(message.metadata))?.metadata;
91
- return {
92
- ...isRecord(messageMetadata) ? messageMetadata : {},
93
- ...isRecord(input.metadata) ? input.metadata : {}
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
- const raw = metadata.requested_tools ?? metadata.requestedTools ?? metadata.requested_skills ?? metadata.requestedSkills;
98
- if (!Array.isArray(raw)) {
99
- return [];
100
- }
101
- const deduped = /* @__PURE__ */ new Set();
102
- for (const item of raw) {
103
- const value = readOptionalString2(item);
104
- if (value) {
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
- return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
180
+ return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
112
181
  }
113
182
  function messageToOpenAI(msg, options) {
114
- const role = msg.role;
115
- const parts = msg.parts ?? [];
116
- if (role === "user" || role === "system") {
117
- if (role === "user") {
118
- return [
119
- {
120
- role,
121
- content: buildNcpUserContent(parts, {
122
- assetStore: options.assetStore
123
- })
124
- }
125
- ];
126
- }
127
- const text = parts.filter(isTextLikePart).map((part) => part.text).join("");
128
- return [{ role, content: text }];
129
- }
130
- if (role === "assistant") {
131
- const texts = [];
132
- const reasonings = [];
133
- const toolInvocations = [];
134
- for (const p of parts) {
135
- if (p.type === "reasoning") {
136
- reasonings.push(p.text);
137
- }
138
- if (p.type === "text") {
139
- texts.push(p.text);
140
- }
141
- if (p.type === "tool-invocation" && p.state === "result" && p.result !== void 0) {
142
- toolInvocations.push({
143
- toolCallId: p.toolCallId ?? "",
144
- toolName: p.toolName,
145
- args: p.args ?? {},
146
- result: p.result
147
- });
148
- }
149
- }
150
- const text = texts.join("");
151
- const reasoning = reasonings.join("");
152
- const out = [];
153
- if (toolInvocations.length > 0) {
154
- out.push({
155
- role: "assistant",
156
- content: text || null,
157
- ...reasoning ? { reasoning_content: reasoning } : {},
158
- tool_calls: toolInvocations.map((t) => ({
159
- id: t.toolCallId,
160
- type: "function",
161
- function: {
162
- name: t.toolName,
163
- arguments: typeof t.args === "string" ? t.args : JSON.stringify(t.args ?? {})
164
- }
165
- }))
166
- });
167
- for (const t of toolInvocations) {
168
- out.push({
169
- role: "tool",
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
- toolRegistry;
187
- assetStore;
188
- constructor(toolRegistryOrOptions) {
189
- if (isDefaultNcpContextBuilderOptions(toolRegistryOrOptions)) {
190
- this.toolRegistry = toolRegistryOrOptions.toolRegistry;
191
- this.assetStore = toolRegistryOrOptions.assetStore;
192
- return;
193
- }
194
- this.toolRegistry = toolRegistryOrOptions;
195
- }
196
- prepare = (input, options) => {
197
- const maxMessages = options?.maxMessages ?? 50;
198
- const sessionMessages = options?.sessionMessages ?? [];
199
- const systemPrompt = options?.systemPrompt;
200
- const requestMetadata = mergeMessageAndRequestMetadata(input);
201
- const requestedToolNames = readRequestedToolNames(requestMetadata);
202
- const messages = [];
203
- if (systemPrompt) {
204
- messages.push({ role: "system", content: systemPrompt });
205
- }
206
- for (const msg of sessionMessages.slice(-maxMessages)) {
207
- messages.push(
208
- ...messageToOpenAI(msg, {
209
- assetStore: this.assetStore
210
- })
211
- );
212
- }
213
- for (const msg of input.messages) {
214
- messages.push(
215
- ...messageToOpenAI(msg, {
216
- assetStore: this.assetStore
217
- })
218
- );
219
- }
220
- const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
221
- const filteredToolDefinitions = requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions;
222
- const tools = filteredToolDefinitions.map((definition) => ({
223
- type: "function",
224
- function: {
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
- // src/asset-store.ts
240
- import { createHash, randomUUID } from "crypto";
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
- return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
285
+ return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
247
286
  }
248
287
  function normalizeFileName(value) {
249
- const trimmed = value.trim();
250
- return trimmed.length > 0 ? trimmed : "asset.bin";
288
+ const trimmed = value.trim();
289
+ return trimmed.length > 0 ? trimmed : "asset.bin";
251
290
  }
252
291
  function normalizeMimeType(value) {
253
- const normalized = value?.trim().toLowerCase() ?? "";
254
- return normalized.length > 0 ? normalized : "application/octet-stream";
292
+ const normalized = value?.trim().toLowerCase() ?? "";
293
+ return normalized.length > 0 ? normalized : "application/octet-stream";
255
294
  }
256
295
  function buildAssetId() {
257
- return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
296
+ return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
258
297
  }
259
298
  function ensureAssetRoot(rootDir) {
260
- const normalized = rootDir.trim();
261
- if (!normalized) {
262
- throw new Error("LocalAssetStore requires a non-empty rootDir.");
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
- const year = String(timestamp.getFullYear());
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
- return `${ASSET_URI_SCHEME}${storageKey}`;
307
+ return `${ASSET_URI_SCHEME}${storageKey}`;
274
308
  }
275
309
  function parseAssetUri(uri) {
276
- const normalized = uri.trim();
277
- if (!normalized.startsWith(ASSET_URI_SCHEME)) {
278
- return null;
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
- const normalized = storageKey.trim().replace(/^\/+|\/+$/g, "");
285
- if (!normalized) {
286
- throw new Error("Asset storage key must not be empty.");
287
- }
288
- const segments = normalized.split("/");
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
- const fileName = normalizeFileName(value.fileName);
296
- return {
297
- ...value,
298
- fileName,
299
- storedName: normalizeSegment(value.storedName || fileName),
300
- mimeType: normalizeMimeType(value.mimeType)
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
- return {
305
- uri: record.uri,
306
- fileName: record.fileName,
307
- mimeType: record.mimeType,
308
- sizeBytes: record.sizeBytes,
309
- createdAt: record.createdAt
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
- const mimeType = normalizeMimeType(params.mimeType);
314
- 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") {
315
- return true;
316
- }
317
- const normalizedName = (params.fileName ?? "").trim().toLowerCase();
318
- return [
319
- ".json",
320
- ".md",
321
- ".txt",
322
- ".csv",
323
- ".xml",
324
- ".yaml",
325
- ".yml",
326
- ".js",
327
- ".mjs",
328
- ".cjs",
329
- ".ts",
330
- ".tsx",
331
- ".jsx",
332
- ".py",
333
- ".rb",
334
- ".go",
335
- ".rs",
336
- ".java",
337
- ".kt",
338
- ".swift",
339
- ".php",
340
- ".css",
341
- ".scss",
342
- ".html",
343
- ".sql",
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
- rootDir;
349
- constructor(options) {
350
- this.rootDir = ensureAssetRoot(options.rootDir);
351
- }
352
- async put(input) {
353
- const record = input.kind === "path" ? await this.putFromPath(input) : await this.putFromBytes({
354
- fileName: input.fileName,
355
- mimeType: input.mimeType,
356
- bytes: input.bytes,
357
- createdAt: input.createdAt
358
- });
359
- return { uri: record.uri };
360
- }
361
- async putBytes(params) {
362
- return this.putFromBytes(params);
363
- }
364
- async putPath(params) {
365
- return this.putFromPath({
366
- kind: "path",
367
- ...params
368
- });
369
- }
370
- async export(ref, targetPath) {
371
- const record = await this.statRecord(ref.uri);
372
- if (!record) {
373
- throw new Error(`Asset not found: ${ref.uri}`);
374
- }
375
- const outputPath = resolve(targetPath);
376
- await ensureParentDirectory(outputPath);
377
- await copyFile(this.resolveContentPathOrThrow(record), outputPath);
378
- return outputPath;
379
- }
380
- async stat(ref) {
381
- const record = await this.statRecord(ref.uri);
382
- return record ? toAssetMeta(record) : null;
383
- }
384
- getByUri(uri) {
385
- const storageKey = parseAssetUri(uri);
386
- if (!storageKey) {
387
- return null;
388
- }
389
- const metaPath = join(this.resolveStorageKeyDirectory(storageKey), "meta.json");
390
- if (!existsSync(metaPath)) {
391
- return null;
392
- }
393
- const text = readFileSync(metaPath, "utf8");
394
- return hydrateStoredAssetRecord(JSON.parse(text));
395
- }
396
- async statRecord(uri) {
397
- const record = this.getByUri(uri);
398
- if (!record) {
399
- return null;
400
- }
401
- try {
402
- await stat(this.resolveContentPathOrThrow(record));
403
- return record;
404
- } catch {
405
- return null;
406
- }
407
- }
408
- async readAssetBytes(uri) {
409
- const record = await this.statRecord(uri);
410
- if (!record) {
411
- return null;
412
- }
413
- try {
414
- return await readFile(this.resolveContentPathOrThrow(record));
415
- } catch {
416
- return null;
417
- }
418
- }
419
- resolveContentPath(uri) {
420
- const record = this.getByUri(uri);
421
- return record ? this.resolveContentPathOrThrow(record) : null;
422
- }
423
- async putFromPath(input) {
424
- const sourcePath = resolve(input.path);
425
- const sourceStats = await stat(sourcePath);
426
- if (!sourceStats.isFile()) {
427
- throw new Error(`Asset source path is not a file: ${sourcePath}`);
428
- }
429
- const bytes = readFileSync(sourcePath);
430
- const record = this.buildRecord({
431
- fileName: input.fileName ?? basename(sourcePath),
432
- mimeType: input.mimeType,
433
- bytes,
434
- createdAt: input.createdAt
435
- });
436
- const assetDir = this.resolveStorageKeyDirectory(record.storageKey);
437
- await mkdir(assetDir, { recursive: true });
438
- copyFileSync(sourcePath, join(assetDir, record.storedName));
439
- await this.writeMeta(assetDir, record);
440
- return record;
441
- }
442
- async putFromBytes(params) {
443
- const bytes = Buffer.from(params.bytes);
444
- const record = this.buildRecord({
445
- fileName: params.fileName,
446
- mimeType: params.mimeType,
447
- bytes,
448
- createdAt: params.createdAt
449
- });
450
- const assetDir = this.resolveStorageKeyDirectory(record.storageKey);
451
- await mkdir(assetDir, { recursive: true });
452
- await writeFile(join(assetDir, record.storedName), bytes);
453
- await this.writeMeta(assetDir, record);
454
- return record;
455
- }
456
- buildRecord(params) {
457
- const createdAt = params.createdAt ?? /* @__PURE__ */ new Date();
458
- const id = buildAssetId();
459
- const storageKey = buildStorageKey(createdAt, id);
460
- const fileName = normalizeFileName(params.fileName);
461
- return {
462
- id,
463
- uri: buildAssetUri(storageKey),
464
- storageKey,
465
- fileName,
466
- storedName: normalizeSegment(fileName),
467
- mimeType: normalizeMimeType(params.mimeType),
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
- const query = new URLSearchParams({ uri: params.assetUri });
486
- return `${params.basePath}?${query.toString()}`;
496
+ const query = new URLSearchParams({ uri: params.assetUri });
497
+ return `${params.basePath}?${query.toString()}`;
487
498
  }
488
499
  async function ensureParentDirectory(filePath) {
489
- await mkdir(dirname(filePath), { recursive: true });
500
+ await mkdir(dirname(filePath), { recursive: true });
490
501
  }
491
-
492
- // src/round-buffer.ts
502
+ //#endregion
503
+ //#region src/round-buffer.ts
493
504
  var DefaultNcpRoundBuffer = class {
494
- text = "";
495
- toolCalls = [];
496
- pending = null;
497
- appendText = (delta) => {
498
- this.text += delta;
499
- };
500
- getText = () => {
501
- return this.text;
502
- };
503
- appendToolCall = (result) => {
504
- this.toolCalls.push(result);
505
- };
506
- getToolCalls = () => {
507
- return [...this.toolCalls];
508
- };
509
- startToolCall = (toolCallId, toolName) => {
510
- this.pending = { toolCallId, toolName, args: void 0 };
511
- };
512
- appendToolCallArgs = (args) => {
513
- if (this.pending) this.pending.args = args;
514
- };
515
- consumePendingToolCall = () => {
516
- const p = this.pending;
517
- this.pending = null;
518
- return p;
519
- };
520
- clear = () => {
521
- this.text = "";
522
- this.toolCalls.length = 0;
523
- this.pending = null;
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
- // src/stream-encoder.ts
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
- const idx = toolDelta.index;
539
- return typeof idx === "number" && Number.isFinite(idx) ? idx : fallback;
544
+ const idx = toolDelta.index;
545
+ return typeof idx === "number" && Number.isFinite(idx) ? idx : fallback;
540
546
  }
541
547
  function applyToolDelta(current, toolDelta) {
542
- const next = { ...current, argumentsText: current.argumentsText };
543
- if (typeof toolDelta.id === "string" && toolDelta.id.trim()) {
544
- next.id = toolDelta.id;
545
- }
546
- const fn = toolDelta.function;
547
- if (fn && typeof fn === "object" && !Array.isArray(fn)) {
548
- if (typeof fn.name === "string" && fn.name.trim()) {
549
- next.name = fn.name.trim();
550
- }
551
- if (typeof fn.arguments === "string" && fn.arguments.length > 0) {
552
- next.argumentsText += fn.arguments;
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
- const content = delta.content;
559
- if (typeof content !== "string" || content.length === 0) return state;
560
- if (state.normalizer) {
561
- let textStarted = state.textStarted;
562
- for (const segment of state.normalizer.push(content)) {
563
- if (segment.type === "reasoning") {
564
- yield {
565
- type: NcpEventType.MessageReasoningDelta,
566
- payload: { ...ctx, delta: segment.text }
567
- };
568
- continue;
569
- }
570
- if (!textStarted) {
571
- yield { type: NcpEventType.MessageTextStart, payload: ctx };
572
- textStarted = true;
573
- }
574
- yield {
575
- type: NcpEventType.MessageTextDelta,
576
- payload: { ...ctx, delta: segment.text }
577
- };
578
- }
579
- return {
580
- ...state,
581
- textStarted
582
- };
583
- }
584
- if (!state.textStarted) {
585
- yield { type: NcpEventType.MessageTextStart, payload: ctx };
586
- yield { type: NcpEventType.MessageTextDelta, payload: { ...ctx, delta: content } };
587
- return {
588
- ...state,
589
- textStarted: true
590
- };
591
- }
592
- yield { type: NcpEventType.MessageTextDelta, payload: { ...ctx, delta: content } };
593
- return state;
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
- if (!state.normalizer) {
597
- return state;
598
- }
599
- let textStarted = state.textStarted;
600
- for (const segment of state.normalizer.finish()) {
601
- if (segment.type === "reasoning") {
602
- yield {
603
- type: NcpEventType.MessageReasoningDelta,
604
- payload: { ...ctx, delta: segment.text }
605
- };
606
- continue;
607
- }
608
- if (!textStarted) {
609
- yield { type: NcpEventType.MessageTextStart, payload: ctx };
610
- textStarted = true;
611
- }
612
- yield {
613
- type: NcpEventType.MessageTextDelta,
614
- payload: { ...ctx, delta: segment.text }
615
- };
616
- }
617
- return {
618
- ...state,
619
- textStarted
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
- const reasoning = delta.reasoning_content ?? delta.reasoning;
624
- if (typeof reasoning !== "string" || !reasoning) return;
625
- yield { type: NcpEventType.MessageReasoningDelta, payload: { ...ctx, delta: reasoning } };
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
- const toolDeltas = delta.tool_calls;
629
- if (!Array.isArray(toolDeltas)) return;
630
- for (const toolDelta of toolDeltas) {
631
- const index = getToolCallIndex(toolDelta, buffers.size);
632
- const prev = buffers.get(index) ?? {
633
- argumentsText: "",
634
- pendingArgumentDeltas: []
635
- };
636
- const current = applyToolDelta(prev, toolDelta);
637
- const argsDelta = typeof toolDelta.function?.arguments === "string" && toolDelta.function.arguments.length > 0 ? toolDelta.function.arguments : null;
638
- const toolCallId = current.id;
639
- const toolName = current.name;
640
- if (toolCallId && toolName && !current.emittedStart) {
641
- yield {
642
- type: NcpEventType.MessageToolCallStart,
643
- payload: { ...ctx, toolCallId, toolName }
644
- };
645
- current.emittedStart = true;
646
- }
647
- if (argsDelta) {
648
- if (current.emittedStart && current.id) {
649
- yield {
650
- type: NcpEventType.MessageToolCallArgsDelta,
651
- payload: {
652
- ...ctx,
653
- toolCallId: current.id,
654
- delta: argsDelta
655
- }
656
- };
657
- current.emittedArgsDelta = true;
658
- } else {
659
- current.pendingArgumentDeltas = [
660
- ...current.pendingArgumentDeltas ?? [],
661
- argsDelta
662
- ];
663
- }
664
- }
665
- if (current.emittedStart && current.id && (current.pendingArgumentDeltas?.length ?? 0) > 0) {
666
- for (const pendingDelta of current.pendingArgumentDeltas ?? []) {
667
- yield {
668
- type: NcpEventType.MessageToolCallArgsDelta,
669
- payload: {
670
- ...ctx,
671
- toolCallId: current.id,
672
- delta: pendingDelta
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
- const ordered = Array.from(buffers.entries()).sort(([a], [b]) => a - b);
684
- for (const [, buf] of ordered) {
685
- if (!buf.id || !buf.name) continue;
686
- if (!buf.emittedArgsDelta) {
687
- yield {
688
- type: NcpEventType.MessageToolCallArgs,
689
- payload: { ...ctx, toolCallId: buf.id, args: buf.argumentsText }
690
- };
691
- }
692
- yield {
693
- type: NcpEventType.MessageToolCallEnd,
694
- payload: { ...ctx, toolCallId: buf.id }
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
- return {
700
- textStarted: false,
701
- normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
702
- };
738
+ return {
739
+ textStarted: false,
740
+ normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
741
+ };
703
742
  }
704
-
705
- // src/stream-encoder.ts
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
- reasoningNormalizationMode;
708
- constructor(config = {}) {
709
- this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
710
- }
711
- async *encode(stream, context) {
712
- const { sessionId, messageId } = context;
713
- let state = createStreamContentState(this.reasoningNormalizationMode);
714
- const toolCallBuffers = /* @__PURE__ */ new Map();
715
- for await (const chunk of stream) {
716
- const choice = chunk.choices?.[0];
717
- if (!choice) continue;
718
- const delta = choice.delta;
719
- if (delta) {
720
- yield* emitReasoningDelta(delta, { sessionId, messageId });
721
- const nextState = yield* emitTextDeltas(delta, { sessionId, messageId }, state);
722
- state = nextState;
723
- yield* emitToolCallDeltas(delta, toolCallBuffers, { sessionId, messageId });
724
- }
725
- const finishReason = choice.finish_reason;
726
- if (typeof finishReason === "string" && finishReason.trim().length > 0) {
727
- state = yield* flushTextDeltas({ sessionId, messageId }, state);
728
- yield* flushToolCalls(toolCallBuffers, { sessionId, messageId });
729
- if (state.textStarted) {
730
- yield { type: NcpEventType2.MessageTextEnd, payload: { sessionId, messageId } };
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
- // src/tool-registry.ts
797
+ //#endregion
798
+ //#region src/tool-registry.ts
738
799
  var DefaultNcpToolRegistry = class {
739
- tools = /* @__PURE__ */ new Map();
740
- constructor(tools = []) {
741
- for (const t of tools) {
742
- this.tools.set(t.name, t);
743
- }
744
- }
745
- register = (tool) => {
746
- this.tools.set(tool.name, tool);
747
- };
748
- listTools = () => {
749
- return [...this.tools.values()];
750
- };
751
- getTool = (name) => {
752
- return this.tools.get(name);
753
- };
754
- getToolDefinitions = () => {
755
- return this.listTools().map((t) => ({
756
- name: t.name,
757
- description: t.description,
758
- parameters: t.parameters
759
- }));
760
- };
761
- execute = async (_toolCallId, toolName, args) => {
762
- const tool = this.tools.get(toolName);
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
- // src/llm-api-echo.ts
825
+ //#endregion
826
+ //#region src/llm-api-echo.ts
768
827
  function getLastUserContent(messages) {
769
- for (let i = messages.length - 1; i >= 0; i--) {
770
- const m = messages[i];
771
- if (m.role !== "user") continue;
772
- if (typeof m.content === "string") return m.content;
773
- if (Array.isArray(m.content)) {
774
- return m.content.map((p) => p.type === "text" ? p.text : "").join("");
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
- generate = async function* (input, options) {
781
- const text = getLastUserContent(input.messages);
782
- const signal = options?.signal;
783
- for (const char of text) {
784
- if (signal?.aborted) break;
785
- yield {
786
- choices: [{ index: 0, delta: { content: char } }]
787
- };
788
- }
789
- yield {
790
- choices: [
791
- {
792
- index: 0,
793
- delta: {},
794
- finish_reason: "stop"
795
- }
796
- ],
797
- usage: { prompt_tokens: 0, completion_tokens: text.length, total_tokens: text.length }
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
- // src/runtime.ts
803
- import {
804
- isHiddenNcpMessage,
805
- NcpEventType as NcpEventType3
806
- } from "@nextclaw/ncp";
807
-
808
- // src/utils.ts
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
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
870
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
811
871
  }
812
872
  function stringifyRawArgs(args) {
813
- if (typeof args === "string") {
814
- return args;
815
- }
816
- if (args && typeof args === "object" && !Array.isArray(args)) {
817
- try {
818
- return JSON.stringify(args);
819
- } catch {
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
- if (args && typeof args === "object" && !Array.isArray(args)) {
827
- return {
828
- ok: true,
829
- rawText: stringifyRawArgs(args),
830
- value: args
831
- };
832
- }
833
- const rawText = stringifyRawArgs(args);
834
- if (typeof args !== "string") {
835
- return {
836
- ok: false,
837
- rawText,
838
- issues: ["Tool arguments must be a JSON object string."]
839
- };
840
- }
841
- const trimmed = args.trim();
842
- if (!trimmed) {
843
- return {
844
- ok: false,
845
- rawText,
846
- issues: ["Tool arguments are empty."]
847
- };
848
- }
849
- try {
850
- const parsed = JSON.parse(trimmed);
851
- if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
852
- return {
853
- ok: false,
854
- rawText,
855
- issues: ["Tool arguments JSON must decode to an object."]
856
- };
857
- }
858
- return {
859
- ok: true,
860
- rawText,
861
- value: parsed
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
- if (!schema) {
873
- return [];
874
- }
875
- return validateToolValue(args, schema, "");
920
+ if (!schema) return [];
921
+ const validate = getOrCreateValidator(schema);
922
+ if (validate(args)) return [];
923
+ return formatSchemaIssues(validate.errors);
876
924
  }
877
- function validateToolValue(value, schema, path) {
878
- const label = path || "parameter";
879
- const type = typeof schema.type === "string" ? schema.type : void 0;
880
- if (type && !matchesSchemaType(value, type)) {
881
- return [`${label} should be ${type}`];
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 matchesSchemaType(value, type) {
927
- switch (type) {
928
- case "string":
929
- return typeof value === "string";
930
- case "integer":
931
- return typeof value === "number" && Number.isInteger(value);
932
- case "number":
933
- return typeof value === "number";
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
- return {
946
- ok: false,
947
- error: {
948
- code: "invalid_tool_arguments",
949
- message: "Tool arguments are invalid.",
950
- toolCallId: params.toolCallId,
951
- toolName: params.toolName,
952
- rawArgumentsText: params.rawArgumentsText,
953
- issues: params.issues
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
- const assistantMsg = {
959
- role: "assistant",
960
- content: text || null,
961
- ...reasoning ? { reasoning_content: reasoning } : {},
962
- tool_calls: toolResults.map((tr) => ({
963
- id: tr.toolCallId,
964
- type: "function",
965
- function: {
966
- name: tr.toolName,
967
- arguments: tr.rawArgsText
968
- }
969
- }))
970
- };
971
- const toolMsgs = toolResults.map((tr) => ({
972
- role: "tool",
973
- content: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result ?? {}),
974
- tool_call_id: tr.toolCallId
975
- }));
976
- return {
977
- ...input,
978
- messages: [...input.messages, assistantMsg, ...toolMsgs]
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
- // src/round-collector.ts
983
- import {
984
- normalizeAssistantText
985
- } from "@nextclaw/ncp";
994
+ //#endregion
995
+ //#region src/round-collector.ts
986
996
  var DefaultNcpRoundCollector = class {
987
- constructor(reasoningNormalizationMode = "off") {
988
- this.reasoningNormalizationMode = reasoningNormalizationMode;
989
- }
990
- rawText = "";
991
- explicitReasoning = "";
992
- toolCallBuffers = /* @__PURE__ */ new Map();
993
- clear() {
994
- this.rawText = "";
995
- this.explicitReasoning = "";
996
- this.toolCallBuffers.clear();
997
- }
998
- consumeChunk(chunk) {
999
- const choice = chunk.choices?.[0];
1000
- if (!choice) {
1001
- return;
1002
- }
1003
- const delta = choice.delta;
1004
- if (!delta) {
1005
- return;
1006
- }
1007
- if (typeof delta.content === "string" && delta.content.length > 0) {
1008
- this.rawText += delta.content;
1009
- }
1010
- const reasoning = delta.reasoning_content ?? delta.reasoning;
1011
- if (typeof reasoning === "string" && reasoning.length > 0) {
1012
- this.explicitReasoning += reasoning;
1013
- }
1014
- const toolDeltas = delta.tool_calls;
1015
- if (!Array.isArray(toolDeltas)) {
1016
- return;
1017
- }
1018
- for (const toolDelta of toolDeltas) {
1019
- const index = getToolCallIndex(toolDelta, this.toolCallBuffers.size);
1020
- const previous = this.toolCallBuffers.get(index) ?? { argumentsText: "" };
1021
- const current = applyToolDelta(previous, toolDelta);
1022
- this.toolCallBuffers.set(index, current);
1023
- }
1024
- }
1025
- getText() {
1026
- if (this.reasoningNormalizationMode !== "think-tags") {
1027
- return this.rawText;
1028
- }
1029
- return normalizeAssistantText(this.rawText, this.reasoningNormalizationMode).text;
1030
- }
1031
- getReasoning() {
1032
- if (this.explicitReasoning.length > 0) {
1033
- return this.explicitReasoning;
1034
- }
1035
- if (this.reasoningNormalizationMode !== "think-tags") {
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
- // src/runtime.ts
1047
+ //#endregion
1048
+ //#region src/runtime.ts
1058
1049
  var DefaultNcpAgentRuntime = class {
1059
- contextBuilder;
1060
- llmApi;
1061
- toolRegistry;
1062
- stateManager;
1063
- streamEncoder;
1064
- reasoningNormalizationMode;
1065
- constructor(config) {
1066
- this.contextBuilder = config.contextBuilder;
1067
- this.llmApi = config.llmApi;
1068
- this.toolRegistry = config.toolRegistry;
1069
- this.stateManager = config.stateManager;
1070
- this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
1071
- this.streamEncoder = config.streamEncoder ?? new DefaultNcpStreamEncoder({
1072
- reasoningNormalizationMode: this.reasoningNormalizationMode
1073
- });
1074
- }
1075
- run = async function* (input, options) {
1076
- const ctx = {
1077
- messageId: genId(),
1078
- runId: genId(),
1079
- sessionId: input.sessionId,
1080
- correlationId: input.correlationId
1081
- };
1082
- const sessionMessages = this.stateManager.getSnapshot().messages;
1083
- const modelInput = this.contextBuilder.prepare(input, {
1084
- sessionMessages
1085
- });
1086
- for (const msg of input.messages) {
1087
- if (isHiddenNcpMessage(msg)) {
1088
- continue;
1089
- }
1090
- const messageSent = {
1091
- type: NcpEventType3.MessageSent,
1092
- payload: { sessionId: input.sessionId, message: msg }
1093
- };
1094
- await this.stateManager.dispatch(messageSent);
1095
- }
1096
- const runStarted = {
1097
- type: NcpEventType3.RunStarted,
1098
- payload: { sessionId: ctx.sessionId, messageId: ctx.messageId, runId: ctx.runId }
1099
- };
1100
- await this.stateManager.dispatch(runStarted);
1101
- yield runStarted;
1102
- for await (const event of this.runLoop(modelInput, ctx, options)) {
1103
- await this.stateManager.dispatch(event);
1104
- yield event;
1105
- }
1106
- };
1107
- /**
1108
- * Agent loop: LLM stream → encoder events → tool execution (if any) → next round or finish.
1109
- * RunFinished is emitted only when the entire loop completes (no more tool calls).
1110
- * The stream encoder does not emit RunFinished; it only converts chunks to NCP events.
1111
- */
1112
- runLoop = async function* (llmInput, ctx, options) {
1113
- const roundCollector = new DefaultNcpRoundCollector(this.reasoningNormalizationMode);
1114
- let currentInput = llmInput;
1115
- let done = false;
1116
- while (!done && !options?.signal?.aborted) {
1117
- roundCollector.clear();
1118
- const stream = this.llmApi.generate(currentInput, { signal: options?.signal });
1119
- const tappedStream = this.tapStream(stream, (chunk) => roundCollector.consumeChunk(chunk));
1120
- for await (const event of this.streamEncoder.encode(tappedStream, ctx)) {
1121
- yield event;
1122
- }
1123
- const toolResults = [];
1124
- for (const toolCall of roundCollector.getToolCalls()) {
1125
- const tool = this.toolRegistry.getTool(toolCall.toolName);
1126
- const parsedArgs = parseToolArgs(toolCall.args);
1127
- let result;
1128
- let args = null;
1129
- if (!parsedArgs.ok) {
1130
- result = createInvalidToolArgumentsResult({
1131
- toolCallId: toolCall.toolCallId,
1132
- toolName: toolCall.toolName,
1133
- rawArgumentsText: parsedArgs.rawText,
1134
- issues: parsedArgs.issues
1135
- });
1136
- } else {
1137
- const schemaIssues = validateToolArgs(parsedArgs.value, tool?.parameters);
1138
- const validationIssues = schemaIssues.length > 0 ? schemaIssues : typeof tool?.validateArgs === "function" ? tool.validateArgs(parsedArgs.value) : [];
1139
- if (validationIssues.length > 0) {
1140
- result = createInvalidToolArgumentsResult({
1141
- toolCallId: toolCall.toolCallId,
1142
- toolName: toolCall.toolName,
1143
- rawArgumentsText: parsedArgs.rawText,
1144
- issues: validationIssues
1145
- });
1146
- } else {
1147
- args = parsedArgs.value;
1148
- result = await this.toolRegistry.execute(
1149
- toolCall.toolCallId,
1150
- toolCall.toolName,
1151
- parsedArgs.value
1152
- );
1153
- }
1154
- }
1155
- toolResults.push({
1156
- toolCallId: toolCall.toolCallId,
1157
- toolName: toolCall.toolName,
1158
- args,
1159
- rawArgsText: parsedArgs.rawText,
1160
- result
1161
- });
1162
- yield {
1163
- type: NcpEventType3.MessageToolCallResult,
1164
- payload: {
1165
- sessionId: ctx.sessionId,
1166
- toolCallId: toolCall.toolCallId,
1167
- content: result
1168
- }
1169
- };
1170
- }
1171
- if (toolResults.length === 0) {
1172
- yield {
1173
- type: NcpEventType3.RunFinished,
1174
- payload: { sessionId: ctx.sessionId, messageId: ctx.messageId, runId: ctx.runId }
1175
- };
1176
- done = true;
1177
- break;
1178
- }
1179
- currentInput = appendToolRoundToInput(
1180
- currentInput,
1181
- roundCollector.getReasoning(),
1182
- roundCollector.getText(),
1183
- toolResults
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
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 };