@nextclaw/ncp-agent-runtime 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,71 @@
1
- import { NcpContextBuilder, NcpToolRegistry, NcpAgentRunInput, NcpContextPrepareOptions, NcpLLMApiInput, NcpRoundBuffer, NcpToolCallResult, NcpStreamEncoder, NcpAssistantReasoningNormalizationMode, OpenAIChatChunk, NcpEncodeContext, NcpEndpointEvent, NcpTool, NcpToolDefinition, NcpLLMApi, NcpLLMApiOptions, NcpAgentRuntime, NcpAgentConversationStateManager, NcpAgentRunOptions } from '@nextclaw/ncp';
1
+ import { NcpContextBuilder, NcpToolRegistry, NcpAgentRunInput, NcpContextPrepareOptions, NcpLLMApiInput, NcpMessagePart, OpenAIContentPart, NcpRoundBuffer, NcpToolCallResult, NcpStreamEncoder, NcpAssistantReasoningNormalizationMode, OpenAIChatChunk, NcpEncodeContext, NcpEndpointEvent, NcpTool, NcpToolDefinition, NcpLLMApi, NcpLLMApiOptions, NcpAgentRuntime, NcpAgentConversationStateManager, NcpAgentRunOptions } from '@nextclaw/ncp';
2
2
 
3
+ type StoredAttachmentRecord = {
4
+ id: string;
5
+ uri: string;
6
+ storageKey: string;
7
+ originalName: string;
8
+ storedName: string;
9
+ mimeType: string;
10
+ sizeBytes: number;
11
+ createdAt: string;
12
+ sha256: string;
13
+ };
14
+ type SaveAttachmentParams = {
15
+ fileName: string;
16
+ mimeType?: string | null;
17
+ bytes: Uint8Array;
18
+ createdAt?: Date;
19
+ };
20
+ type AttachmentTextSnapshot = {
21
+ text: string;
22
+ truncated: boolean;
23
+ record: StoredAttachmentRecord;
24
+ };
25
+ declare function isTextLikeAttachment(params: {
26
+ mimeType?: string | null;
27
+ fileName?: string | null;
28
+ }): boolean;
29
+ declare class LocalAttachmentStore {
30
+ readonly rootDir: string;
31
+ constructor(options: {
32
+ rootDir: string;
33
+ });
34
+ saveAttachment(params: SaveAttachmentParams): Promise<StoredAttachmentRecord>;
35
+ getAttachmentByUri(uri: string): StoredAttachmentRecord | null;
36
+ readAttachmentBytes(uri: string): Promise<Buffer | null>;
37
+ readAttachmentBytesSync(uri: string): Buffer | null;
38
+ readTextSnapshotSync(uri: string, options: {
39
+ maxBytes: number;
40
+ }): AttachmentTextSnapshot | null;
41
+ statAttachment(uri: string): Promise<StoredAttachmentRecord | null>;
42
+ resolveContentPath(uri: string): string | null;
43
+ private resolveStorageKeyDirectory;
44
+ }
45
+ declare function buildAttachmentContentPath(params: {
46
+ basePath: string;
47
+ attachmentUri: string;
48
+ }): string;
49
+
50
+ type DefaultNcpContextBuilderOptions = {
51
+ toolRegistry?: NcpToolRegistry;
52
+ attachmentStore?: LocalAttachmentStore | null;
53
+ attachmentTextMaxBytes?: number;
54
+ };
3
55
  declare class DefaultNcpContextBuilder implements NcpContextBuilder {
4
56
  private readonly toolRegistry?;
5
- constructor(toolRegistry?: NcpToolRegistry | undefined);
57
+ private readonly attachmentStore?;
58
+ private readonly attachmentTextMaxBytes?;
59
+ constructor(toolRegistry?: NcpToolRegistry);
60
+ constructor(options?: DefaultNcpContextBuilderOptions);
6
61
  prepare: (input: NcpAgentRunInput, options?: NcpContextPrepareOptions) => NcpLLMApiInput;
7
62
  }
8
63
 
64
+ declare function buildNcpUserContent(parts: NcpMessagePart[], options?: {
65
+ attachmentStore?: LocalAttachmentStore | null;
66
+ maxTextBytes?: number;
67
+ }): string | OpenAIContentPart[];
68
+
9
69
  declare class DefaultNcpRoundBuffer implements NcpRoundBuffer {
10
70
  private text;
11
71
  private readonly toolCalls;
@@ -77,4 +137,4 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
77
137
  private tapStream;
78
138
  }
79
139
 
80
- export { DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi };
140
+ export { type AttachmentTextSnapshot, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAttachmentStore, type SaveAttachmentParams, type StoredAttachmentRecord, buildAttachmentContentPath, buildNcpUserContent, isTextLikeAttachment };
package/dist/index.js CHANGED
@@ -1,7 +1,217 @@
1
- // src/context-builder.ts
2
- function isRecord(value) {
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1
+ // src/attachment-store.ts
2
+ import { createHash, randomUUID } from "crypto";
3
+ import { mkdir, readFile, stat, writeFile } from "fs/promises";
4
+ import { existsSync, readFileSync } from "fs";
5
+ import { dirname, join, resolve } from "path";
6
+ var ATTACHMENT_URI_SCHEME = "attachment://local/";
7
+ function normalizeSegment(value) {
8
+ return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "attachment.bin";
9
+ }
10
+ function normalizeOriginalName(value) {
11
+ const trimmed = value.trim();
12
+ return trimmed.length > 0 ? trimmed : "attachment.bin";
13
+ }
14
+ function normalizeMimeType(value) {
15
+ const normalized = value?.trim().toLowerCase() ?? "";
16
+ return normalized.length > 0 ? normalized : "application/octet-stream";
17
+ }
18
+ function buildAttachmentId() {
19
+ return `att_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
20
+ }
21
+ function ensureAttachmentRoot(rootDir) {
22
+ const normalized = rootDir.trim();
23
+ if (!normalized) {
24
+ throw new Error("LocalAttachmentStore requires a non-empty rootDir.");
25
+ }
26
+ return resolve(normalized);
27
+ }
28
+ function buildStorageKey(timestamp, attachmentId) {
29
+ const year = String(timestamp.getFullYear());
30
+ const month = String(timestamp.getMonth() + 1).padStart(2, "0");
31
+ const day = String(timestamp.getDate()).padStart(2, "0");
32
+ return `${year}/${month}/${day}/${attachmentId}`;
33
+ }
34
+ function buildAttachmentUri(storageKey) {
35
+ return `${ATTACHMENT_URI_SCHEME}${storageKey}`;
36
+ }
37
+ function parseAttachmentUri(uri) {
38
+ const normalized = uri.trim();
39
+ if (!normalized.startsWith(ATTACHMENT_URI_SCHEME)) {
40
+ return null;
41
+ }
42
+ const storageKey = normalized.slice(ATTACHMENT_URI_SCHEME.length).replace(/^\/+/, "").trim();
43
+ return storageKey.length > 0 ? storageKey : null;
44
+ }
45
+ function ensureStorageKey(storageKey) {
46
+ const normalized = storageKey.trim().replace(/^\/+|\/+$/g, "");
47
+ if (!normalized) {
48
+ throw new Error("Attachment storage key must not be empty.");
49
+ }
50
+ const segments = normalized.split("/");
51
+ if (segments.some((segment) => segment === "" || segment === "." || segment === "..")) {
52
+ throw new Error(`Invalid attachment storage key: ${storageKey}`);
53
+ }
54
+ return segments.join("/");
55
+ }
56
+ function hydrateStoredAttachmentRecord(value) {
57
+ const originalName = normalizeOriginalName(value.originalName);
58
+ return {
59
+ ...value,
60
+ originalName,
61
+ storedName: normalizeSegment(value.storedName || originalName),
62
+ mimeType: normalizeMimeType(value.mimeType)
63
+ };
64
+ }
65
+ function isTextLikeAttachment(params) {
66
+ const mimeType = normalizeMimeType(params.mimeType);
67
+ 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") {
68
+ return true;
69
+ }
70
+ const normalizedName = (params.fileName ?? "").trim().toLowerCase();
71
+ return [
72
+ ".json",
73
+ ".md",
74
+ ".txt",
75
+ ".csv",
76
+ ".xml",
77
+ ".yaml",
78
+ ".yml",
79
+ ".js",
80
+ ".mjs",
81
+ ".cjs",
82
+ ".ts",
83
+ ".tsx",
84
+ ".jsx",
85
+ ".py",
86
+ ".rb",
87
+ ".go",
88
+ ".rs",
89
+ ".java",
90
+ ".kt",
91
+ ".swift",
92
+ ".php",
93
+ ".css",
94
+ ".scss",
95
+ ".html",
96
+ ".sql",
97
+ ".sh"
98
+ ].some((suffix) => normalizedName.endsWith(suffix));
99
+ }
100
+ var LocalAttachmentStore = class {
101
+ rootDir;
102
+ constructor(options) {
103
+ this.rootDir = ensureAttachmentRoot(options.rootDir);
104
+ }
105
+ async saveAttachment(params) {
106
+ const createdAt = params.createdAt ?? /* @__PURE__ */ new Date();
107
+ const id = buildAttachmentId();
108
+ const storageKey = buildStorageKey(createdAt, id);
109
+ const attachmentDir = this.resolveStorageKeyDirectory(storageKey);
110
+ const originalName = normalizeOriginalName(params.fileName);
111
+ const storedName = normalizeSegment(originalName);
112
+ const mimeType = normalizeMimeType(params.mimeType);
113
+ const bytes = Buffer.from(params.bytes);
114
+ const record = {
115
+ id,
116
+ uri: buildAttachmentUri(storageKey),
117
+ storageKey,
118
+ originalName,
119
+ storedName,
120
+ mimeType,
121
+ sizeBytes: bytes.length,
122
+ createdAt: createdAt.toISOString(),
123
+ sha256: createHash("sha256").update(bytes).digest("hex")
124
+ };
125
+ await mkdir(attachmentDir, { recursive: true });
126
+ await writeFile(join(attachmentDir, storedName), bytes);
127
+ await writeFile(
128
+ join(attachmentDir, "meta.json"),
129
+ `${JSON.stringify(record)}
130
+ `,
131
+ "utf8"
132
+ );
133
+ return record;
134
+ }
135
+ getAttachmentByUri(uri) {
136
+ const storageKey = parseAttachmentUri(uri);
137
+ if (!storageKey) {
138
+ return null;
139
+ }
140
+ const metaPath = join(this.resolveStorageKeyDirectory(storageKey), "meta.json");
141
+ if (!existsSync(metaPath)) {
142
+ return null;
143
+ }
144
+ const text = readFileSync(metaPath, "utf8");
145
+ return hydrateStoredAttachmentRecord(JSON.parse(text));
146
+ }
147
+ async readAttachmentBytes(uri) {
148
+ const record = this.getAttachmentByUri(uri);
149
+ if (!record) {
150
+ return null;
151
+ }
152
+ const filePath = join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
153
+ try {
154
+ return await readFile(filePath);
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+ readAttachmentBytesSync(uri) {
160
+ const record = this.getAttachmentByUri(uri);
161
+ if (!record) {
162
+ return null;
163
+ }
164
+ const filePath = join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
165
+ return existsSync(filePath) ? readFileSync(filePath) : null;
166
+ }
167
+ readTextSnapshotSync(uri, options) {
168
+ const record = this.getAttachmentByUri(uri);
169
+ if (!record || !isTextLikeAttachment({ mimeType: record.mimeType, fileName: record.originalName })) {
170
+ return null;
171
+ }
172
+ const bytes = this.readAttachmentBytesSync(uri);
173
+ if (!bytes) {
174
+ return null;
175
+ }
176
+ const truncated = bytes.length > options.maxBytes;
177
+ const snapshotBytes = truncated ? bytes.subarray(0, options.maxBytes) : bytes;
178
+ return {
179
+ text: snapshotBytes.toString("utf8"),
180
+ truncated,
181
+ record
182
+ };
183
+ }
184
+ async statAttachment(uri) {
185
+ const record = this.getAttachmentByUri(uri);
186
+ if (!record) {
187
+ return null;
188
+ }
189
+ const filePath = join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
190
+ try {
191
+ await stat(filePath);
192
+ return record;
193
+ } catch {
194
+ return null;
195
+ }
196
+ }
197
+ resolveContentPath(uri) {
198
+ const record = this.getAttachmentByUri(uri);
199
+ if (!record) {
200
+ return null;
201
+ }
202
+ return join(this.resolveStorageKeyDirectory(record.storageKey), record.storedName);
203
+ }
204
+ resolveStorageKeyDirectory(storageKey) {
205
+ return join(this.rootDir, ensureStorageKey(storageKey));
206
+ }
207
+ };
208
+ function buildAttachmentContentPath(params) {
209
+ const query = new URLSearchParams({ uri: params.attachmentUri });
210
+ return `${params.basePath}?${query.toString()}`;
4
211
  }
212
+
213
+ // src/user-content.ts
214
+ var DEFAULT_ATTACHMENT_TEXT_MAX_BYTES = 32 * 1024;
5
215
  function readOptionalString(value) {
6
216
  if (typeof value !== "string") {
7
217
  return null;
@@ -9,28 +219,98 @@ function readOptionalString(value) {
9
219
  const trimmed = value.trim();
10
220
  return trimmed.length > 0 ? trimmed : null;
11
221
  }
12
- function isRenderableImagePart(part) {
13
- return part.type === "file" && typeof part.mimeType === "string" && part.mimeType.startsWith("image/") && (typeof part.url === "string" && part.url.trim().length > 0 || typeof part.contentBase64 === "string" && part.contentBase64.trim().length > 0);
222
+ function decodeInlineBase64(base64) {
223
+ try {
224
+ return Buffer.from(base64, "base64");
225
+ } catch {
226
+ return null;
227
+ }
228
+ }
229
+ function formatTextAttachmentBlock(params) {
230
+ const fileName = readOptionalString(params.fileName) ?? "attachment";
231
+ const mimeType = readOptionalString(params.mimeType) ?? "application/octet-stream";
232
+ const suffix = params.truncated ? "\n[Attachment truncated]" : "";
233
+ return `[Attachment: ${fileName}]
234
+ [MIME: ${mimeType}]
235
+ ${params.text}${suffix}`;
236
+ }
237
+ function resolveImageDataUrl(part, attachmentStore) {
238
+ const attachmentUri = readOptionalString(part.attachmentUri);
239
+ const mimeType = readOptionalString(part.mimeType);
240
+ if (attachmentUri && mimeType?.startsWith("image/")) {
241
+ const bytes = attachmentStore?.readAttachmentBytesSync(attachmentUri);
242
+ if (bytes) {
243
+ return `data:${mimeType};base64,${bytes.toString("base64")}`;
244
+ }
245
+ }
246
+ const url = readOptionalString(part.url);
247
+ if (url) {
248
+ return url;
249
+ }
250
+ const contentBase64 = readOptionalString(part.contentBase64);
251
+ if (!mimeType || !contentBase64 || !mimeType.startsWith("image/")) {
252
+ return null;
253
+ }
254
+ return `data:${mimeType};base64,${contentBase64}`;
255
+ }
256
+ function resolveTextAttachmentBlock(part, options) {
257
+ const attachmentUri = readOptionalString(part.attachmentUri);
258
+ const fileName = readOptionalString(part.name);
259
+ const mimeType = readOptionalString(part.mimeType);
260
+ const maxTextBytes = options.maxTextBytes ?? DEFAULT_ATTACHMENT_TEXT_MAX_BYTES;
261
+ if (attachmentUri) {
262
+ const snapshot = options.attachmentStore?.readTextSnapshotSync(attachmentUri, {
263
+ maxBytes: maxTextBytes
264
+ });
265
+ if (snapshot) {
266
+ return formatTextAttachmentBlock({
267
+ fileName: snapshot.record.originalName,
268
+ mimeType: snapshot.record.mimeType,
269
+ text: snapshot.text,
270
+ truncated: snapshot.truncated
271
+ });
272
+ }
273
+ }
274
+ const contentBase64 = readOptionalString(part.contentBase64);
275
+ if (!contentBase64 || !isTextLikeAttachment({ mimeType, fileName })) {
276
+ return null;
277
+ }
278
+ const bytes = decodeInlineBase64(contentBase64);
279
+ if (!bytes) {
280
+ return null;
281
+ }
282
+ const truncated = bytes.length > maxTextBytes;
283
+ const text = (truncated ? bytes.subarray(0, maxTextBytes) : bytes).toString("utf8");
284
+ return formatTextAttachmentBlock({
285
+ fileName,
286
+ mimeType,
287
+ text,
288
+ truncated
289
+ });
14
290
  }
15
- function toOpenAIUserContent(parts) {
291
+ function buildNcpUserContent(parts, options = {}) {
16
292
  const content = [];
17
293
  for (const part of parts) {
18
- if (part.type === "text" && part.text.trim().length > 0) {
294
+ if ((part.type === "text" || part.type === "rich-text") && part.text.trim().length > 0) {
19
295
  content.push({ type: "text", text: part.text });
20
296
  continue;
21
297
  }
22
- if (part.type === "rich-text" && part.text.trim().length > 0) {
23
- content.push({ type: "text", text: part.text });
298
+ if (part.type !== "file") {
299
+ continue;
300
+ }
301
+ const textAttachmentBlock = resolveTextAttachmentBlock(part, options);
302
+ if (textAttachmentBlock) {
303
+ content.push({ type: "text", text: textAttachmentBlock });
24
304
  continue;
25
305
  }
26
- if (!isRenderableImagePart(part)) {
306
+ const imageUrl = resolveImageDataUrl(part, options.attachmentStore);
307
+ if (!imageUrl) {
27
308
  continue;
28
309
  }
29
- const url = typeof part.url === "string" && part.url.trim().length > 0 ? part.url.trim() : `data:${part.mimeType};base64,${part.contentBase64?.trim() ?? ""}`;
30
310
  content.push({
31
311
  type: "image_url",
32
312
  image_url: {
33
- url
313
+ url: imageUrl
34
314
  }
35
315
  });
36
316
  }
@@ -42,6 +322,18 @@ function toOpenAIUserContent(parts) {
42
322
  }
43
323
  return content;
44
324
  }
325
+
326
+ // src/context-builder.ts
327
+ function isRecord(value) {
328
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
329
+ }
330
+ function readOptionalString2(value) {
331
+ if (typeof value !== "string") {
332
+ return null;
333
+ }
334
+ const trimmed = value.trim();
335
+ return trimmed.length > 0 ? trimmed : null;
336
+ }
45
337
  function isTextLikePart(part) {
46
338
  return part.type === "text" || part.type === "rich-text";
47
339
  }
@@ -59,19 +351,30 @@ function readRequestedToolNames(metadata) {
59
351
  }
60
352
  const deduped = /* @__PURE__ */ new Set();
61
353
  for (const item of raw) {
62
- const value = readOptionalString(item);
354
+ const value = readOptionalString2(item);
63
355
  if (value) {
64
356
  deduped.add(value);
65
357
  }
66
358
  }
67
359
  return [...deduped];
68
360
  }
69
- function messageToOpenAI(msg) {
361
+ function isDefaultNcpContextBuilderOptions(value) {
362
+ return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
363
+ }
364
+ function messageToOpenAI(msg, options) {
70
365
  const role = msg.role;
71
366
  const parts = msg.parts ?? [];
72
367
  if (role === "user" || role === "system") {
73
368
  if (role === "user") {
74
- return [{ role, content: toOpenAIUserContent(parts) }];
369
+ return [
370
+ {
371
+ role,
372
+ content: buildNcpUserContent(parts, {
373
+ attachmentStore: options.attachmentStore,
374
+ maxTextBytes: options.attachmentTextMaxBytes
375
+ })
376
+ }
377
+ ];
75
378
  }
76
379
  const text = parts.filter(isTextLikePart).map((part) => part.text).join("");
77
380
  return [{ role, content: text }];
@@ -132,8 +435,17 @@ function messageToOpenAI(msg) {
132
435
  return [];
133
436
  }
134
437
  var DefaultNcpContextBuilder = class {
135
- constructor(toolRegistry) {
136
- this.toolRegistry = toolRegistry;
438
+ toolRegistry;
439
+ attachmentStore;
440
+ attachmentTextMaxBytes;
441
+ constructor(toolRegistryOrOptions) {
442
+ if (isDefaultNcpContextBuilderOptions(toolRegistryOrOptions)) {
443
+ this.toolRegistry = toolRegistryOrOptions.toolRegistry;
444
+ this.attachmentStore = toolRegistryOrOptions.attachmentStore;
445
+ this.attachmentTextMaxBytes = toolRegistryOrOptions.attachmentTextMaxBytes;
446
+ return;
447
+ }
448
+ this.toolRegistry = toolRegistryOrOptions;
137
449
  }
138
450
  prepare = (input, options) => {
139
451
  const maxMessages = options?.maxMessages ?? 50;
@@ -146,10 +458,20 @@ var DefaultNcpContextBuilder = class {
146
458
  messages.push({ role: "system", content: systemPrompt });
147
459
  }
148
460
  for (const msg of sessionMessages.slice(-maxMessages)) {
149
- messages.push(...messageToOpenAI(msg));
461
+ messages.push(
462
+ ...messageToOpenAI(msg, {
463
+ attachmentStore: this.attachmentStore,
464
+ attachmentTextMaxBytes: this.attachmentTextMaxBytes
465
+ })
466
+ );
150
467
  }
151
468
  for (const msg of input.messages) {
152
- messages.push(...messageToOpenAI(msg));
469
+ messages.push(
470
+ ...messageToOpenAI(msg, {
471
+ attachmentStore: this.attachmentStore,
472
+ attachmentTextMaxBytes: this.attachmentTextMaxBytes
473
+ })
474
+ );
153
475
  }
154
476
  const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
155
477
  const filteredToolDefinitions = requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions;
@@ -164,8 +486,8 @@ var DefaultNcpContextBuilder = class {
164
486
  return {
165
487
  messages,
166
488
  tools: tools && tools.length > 0 ? tools : void 0,
167
- model: readOptionalString(requestMetadata.model) ?? readOptionalString(requestMetadata.llm_model) ?? readOptionalString(requestMetadata.agent_model) ?? void 0,
168
- thinkingLevel: readOptionalString(requestMetadata.thinking) ?? readOptionalString(requestMetadata.thinking_level) ?? readOptionalString(requestMetadata.thinkingLevel) ?? readOptionalString(requestMetadata.thinking_effort) ?? readOptionalString(requestMetadata.thinkingEffort) ?? null
489
+ model: readOptionalString2(requestMetadata.model) ?? readOptionalString2(requestMetadata.llm_model) ?? readOptionalString2(requestMetadata.agent_model) ?? void 0,
490
+ thinkingLevel: readOptionalString2(requestMetadata.thinking) ?? readOptionalString2(requestMetadata.thinking_level) ?? readOptionalString2(requestMetadata.thinkingLevel) ?? readOptionalString2(requestMetadata.thinking_effort) ?? readOptionalString2(requestMetadata.thinkingEffort) ?? null
169
491
  };
170
492
  };
171
493
  };
@@ -834,5 +1156,9 @@ export {
834
1156
  DefaultNcpRoundBuffer,
835
1157
  DefaultNcpStreamEncoder,
836
1158
  DefaultNcpToolRegistry,
837
- EchoNcpLLMApi
1159
+ EchoNcpLLMApi,
1160
+ LocalAttachmentStore,
1161
+ buildAttachmentContentPath,
1162
+ buildNcpUserContent,
1163
+ isTextLikeAttachment
838
1164
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-agent-runtime",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "private": false,
5
5
  "description": "Default agent runtime implementation built on NCP interfaces.",
6
6
  "type": "module",
@@ -15,7 +15,7 @@
15
15
  "dist"
16
16
  ],
17
17
  "dependencies": {
18
- "@nextclaw/ncp": "0.3.2"
18
+ "@nextclaw/ncp": "0.3.3"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",