@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.
Files changed (3) hide show
  1. package/dist/index.d.ts +132 -124
  2. package/dist/index.js +1083 -1107
  3. package/package.json +3 -4
package/dist/index.js CHANGED
@@ -1,1205 +1,1181 @@
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
+ //#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
- 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");
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
- 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;
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
- 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;
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
- // src/context-builder.ts
148
+ //#endregion
149
+ //#region src/context-builder.ts
76
150
  function isRecord(value) {
77
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
151
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
152
  }
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;
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
- return part.type === "text" || part.type === "rich-text";
159
+ return part.type === "text" || part.type === "rich-text";
88
160
  }
89
161
  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
- };
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
- 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];
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
- return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
179
+ return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
112
180
  }
113
181
  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 [];
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
- 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
- };
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
- // 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/";
280
+ //#endregion
281
+ //#region src/asset-store.ts
282
+ const ASSET_URI_SCHEME = "asset://store/";
245
283
  function normalizeSegment(value) {
246
- return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
284
+ return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
247
285
  }
248
286
  function normalizeFileName(value) {
249
- const trimmed = value.trim();
250
- return trimmed.length > 0 ? trimmed : "asset.bin";
287
+ const trimmed = value.trim();
288
+ return trimmed.length > 0 ? trimmed : "asset.bin";
251
289
  }
252
290
  function normalizeMimeType(value) {
253
- const normalized = value?.trim().toLowerCase() ?? "";
254
- return normalized.length > 0 ? normalized : "application/octet-stream";
291
+ const normalized = value?.trim().toLowerCase() ?? "";
292
+ return normalized.length > 0 ? normalized : "application/octet-stream";
255
293
  }
256
294
  function buildAssetId() {
257
- return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
295
+ return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
258
296
  }
259
297
  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);
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
- 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}`;
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
- return `${ASSET_URI_SCHEME}${storageKey}`;
306
+ return `${ASSET_URI_SCHEME}${storageKey}`;
274
307
  }
275
308
  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;
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
- 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("/");
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
- const fileName = normalizeFileName(value.fileName);
296
- return {
297
- ...value,
298
- fileName,
299
- storedName: normalizeSegment(value.storedName || fileName),
300
- mimeType: normalizeMimeType(value.mimeType)
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
- return {
305
- uri: record.uri,
306
- fileName: record.fileName,
307
- mimeType: record.mimeType,
308
- sizeBytes: record.sizeBytes,
309
- createdAt: record.createdAt
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
- 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));
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
- 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
- }
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
- const query = new URLSearchParams({ uri: params.assetUri });
486
- return `${params.basePath}?${query.toString()}`;
495
+ const query = new URLSearchParams({ uri: params.assetUri });
496
+ return `${params.basePath}?${query.toString()}`;
487
497
  }
488
498
  async function ensureParentDirectory(filePath) {
489
- await mkdir(dirname(filePath), { recursive: true });
499
+ await mkdir(dirname(filePath), { recursive: true });
490
500
  }
491
-
492
- // src/round-buffer.ts
501
+ //#endregion
502
+ //#region src/round-buffer.ts
493
503
  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
- };
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
- // 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";
540
+ //#endregion
541
+ //#region src/stream-encoder.utils.ts
537
542
  function getToolCallIndex(toolDelta, fallback) {
538
- const idx = toolDelta.index;
539
- return typeof idx === "number" && Number.isFinite(idx) ? idx : fallback;
543
+ const idx = toolDelta.index;
544
+ return typeof idx === "number" && Number.isFinite(idx) ? idx : fallback;
540
545
  }
541
546
  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;
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
- 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;
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
- 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
- };
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
- const reasoning = delta.reasoning_content ?? delta.reasoning;
624
- if (typeof reasoning !== "string" || !reasoning) return;
625
- yield { type: NcpEventType.MessageReasoningDelta, payload: { ...ctx, delta: reasoning } };
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
- 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
- }
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
- 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
- }
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
- return {
700
- textStarted: false,
701
- normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
702
- };
737
+ return {
738
+ textStarted: false,
739
+ normalizer: reasoningNormalizationMode === "think-tags" ? new NcpAssistantTextStreamNormalizer(reasoningNormalizationMode) : null
740
+ };
703
741
  }
704
-
705
- // src/stream-encoder.ts
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
- 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
- }
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
- // src/tool-registry.ts
796
+ //#endregion
797
+ //#region src/tool-registry.ts
738
798
  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
- };
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
- // src/llm-api-echo.ts
824
+ //#endregion
825
+ //#region src/llm-api-echo.ts
768
826
  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 "";
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
- 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
- };
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
- // src/runtime.ts
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
- return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
863
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 11)}`;
811
864
  }
812
865
  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 ?? "");
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
- 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
- }
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
- if (!schema) {
873
- return [];
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
- 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;
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
- 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
- }
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
- 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
- };
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
- 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
- };
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
- // src/round-collector.ts
983
- import {
984
- normalizeAssistantText
985
- } from "@nextclaw/ncp";
997
+ //#endregion
998
+ //#region src/round-collector.ts
986
999
  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
- }
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
- // src/runtime.ts
1050
+ //#endregion
1051
+ //#region src/runtime.ts
1058
1052
  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
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 };