@nextclaw/ncp-agent-runtime 0.2.3 → 0.3.0

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,95 @@
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 StoredAssetRecord = {
4
+ id: string;
5
+ uri: string;
6
+ storageKey: string;
7
+ fileName: string;
8
+ storedName: string;
9
+ mimeType: string;
10
+ sizeBytes: number;
11
+ createdAt: string;
12
+ sha256: string;
13
+ };
14
+ type AssetRef = {
15
+ uri: string;
16
+ };
17
+ type AssetMeta = {
18
+ uri: string;
19
+ fileName: string;
20
+ mimeType: string;
21
+ sizeBytes: number;
22
+ createdAt: string;
23
+ };
24
+ type AssetPutInput = {
25
+ kind: "path";
26
+ path: string;
27
+ fileName?: string;
28
+ mimeType?: string | null;
29
+ createdAt?: Date;
30
+ } | {
31
+ kind: "bytes";
32
+ bytes: Uint8Array;
33
+ fileName: string;
34
+ mimeType?: string | null;
35
+ createdAt?: Date;
36
+ };
37
+ declare function isTextLikeAsset(params: {
38
+ mimeType?: string | null;
39
+ fileName?: string | null;
40
+ }): boolean;
41
+ declare class LocalAssetStore {
42
+ readonly rootDir: string;
43
+ constructor(options: {
44
+ rootDir: string;
45
+ });
46
+ put(input: AssetPutInput): Promise<AssetRef>;
47
+ putBytes(params: {
48
+ fileName: string;
49
+ mimeType?: string | null;
50
+ bytes: Uint8Array;
51
+ createdAt?: Date;
52
+ }): Promise<StoredAssetRecord>;
53
+ putPath(params: {
54
+ path: string;
55
+ fileName?: string;
56
+ mimeType?: string | null;
57
+ createdAt?: Date;
58
+ }): Promise<StoredAssetRecord>;
59
+ export(ref: AssetRef, targetPath: string): Promise<string>;
60
+ stat(ref: AssetRef): Promise<AssetMeta | null>;
61
+ getByUri(uri: string): StoredAssetRecord | null;
62
+ statRecord(uri: string): Promise<StoredAssetRecord | null>;
63
+ readAssetBytes(uri: string): Promise<Buffer | null>;
64
+ resolveContentPath(uri: string): string | null;
65
+ private putFromPath;
66
+ private putFromBytes;
67
+ private buildRecord;
68
+ private writeMeta;
69
+ private resolveContentPathOrThrow;
70
+ private resolveStorageKeyDirectory;
71
+ }
72
+ declare function buildAssetContentPath(params: {
73
+ basePath: string;
74
+ assetUri: string;
75
+ }): string;
76
+
77
+ type DefaultNcpContextBuilderOptions = {
78
+ toolRegistry?: NcpToolRegistry;
79
+ assetStore?: LocalAssetStore | null;
80
+ };
3
81
  declare class DefaultNcpContextBuilder implements NcpContextBuilder {
4
82
  private readonly toolRegistry?;
5
- constructor(toolRegistry?: NcpToolRegistry | undefined);
83
+ private readonly assetStore?;
84
+ constructor(toolRegistry?: NcpToolRegistry);
85
+ constructor(options?: DefaultNcpContextBuilderOptions);
6
86
  prepare: (input: NcpAgentRunInput, options?: NcpContextPrepareOptions) => NcpLLMApiInput;
7
87
  }
8
88
 
89
+ declare function buildNcpUserContent(parts: NcpMessagePart[], options?: {
90
+ assetStore?: LocalAssetStore | null;
91
+ }): string | OpenAIContentPart[];
92
+
9
93
  declare class DefaultNcpRoundBuffer implements NcpRoundBuffer {
10
94
  private text;
11
95
  private readonly toolCalls;
@@ -77,4 +161,4 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
77
161
  private tapStream;
78
162
  }
79
163
 
80
- export { DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi };
164
+ export { type AssetMeta, type AssetPutInput, type AssetRef, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, type StoredAssetRecord, buildAssetContentPath, buildNcpUserContent, isTextLikeAsset };
package/dist/index.js CHANGED
@@ -1,7 +1,4 @@
1
- // src/context-builder.ts
2
- function isRecord(value) {
3
- return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
- }
1
+ // src/user-content.ts
5
2
  function readOptionalString(value) {
6
3
  if (typeof value !== "string") {
7
4
  return null;
@@ -9,30 +6,62 @@ function readOptionalString(value) {
9
6
  const trimmed = value.trim();
10
7
  return trimmed.length > 0 ? trimmed : null;
11
8
  }
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);
9
+ 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");
24
+ }
25
+ 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;
14
50
  }
15
- function toOpenAIUserContent(parts) {
51
+ function buildNcpUserContent(parts, options = {}) {
16
52
  const content = [];
17
53
  for (const part of parts) {
18
- if (part.type === "text" && part.text.trim().length > 0) {
54
+ if ((part.type === "text" || part.type === "rich-text") && part.text.trim().length > 0) {
19
55
  content.push({ type: "text", text: part.text });
20
56
  continue;
21
57
  }
22
- if (part.type === "rich-text" && part.text.trim().length > 0) {
23
- content.push({ type: "text", text: part.text });
58
+ if (part.type !== "file") {
24
59
  continue;
25
60
  }
26
- if (!isRenderableImagePart(part)) {
27
- continue;
61
+ const assetReferenceBlock = resolveAssetReferenceBlock(part, options.assetStore);
62
+ if (assetReferenceBlock) {
63
+ content.push({ type: "text", text: assetReferenceBlock });
28
64
  }
29
- const url = typeof part.url === "string" && part.url.trim().length > 0 ? part.url.trim() : `data:${part.mimeType};base64,${part.contentBase64?.trim() ?? ""}`;
30
- content.push({
31
- type: "image_url",
32
- image_url: {
33
- url
34
- }
35
- });
36
65
  }
37
66
  if (content.length === 0) {
38
67
  return "";
@@ -42,6 +71,18 @@ function toOpenAIUserContent(parts) {
42
71
  }
43
72
  return content;
44
73
  }
74
+
75
+ // src/context-builder.ts
76
+ function isRecord(value) {
77
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
78
+ }
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;
85
+ }
45
86
  function isTextLikePart(part) {
46
87
  return part.type === "text" || part.type === "rich-text";
47
88
  }
@@ -59,19 +100,29 @@ function readRequestedToolNames(metadata) {
59
100
  }
60
101
  const deduped = /* @__PURE__ */ new Set();
61
102
  for (const item of raw) {
62
- const value = readOptionalString(item);
103
+ const value = readOptionalString2(item);
63
104
  if (value) {
64
105
  deduped.add(value);
65
106
  }
66
107
  }
67
108
  return [...deduped];
68
109
  }
69
- function messageToOpenAI(msg) {
110
+ function isDefaultNcpContextBuilderOptions(value) {
111
+ return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
112
+ }
113
+ function messageToOpenAI(msg, options) {
70
114
  const role = msg.role;
71
115
  const parts = msg.parts ?? [];
72
116
  if (role === "user" || role === "system") {
73
117
  if (role === "user") {
74
- return [{ role, content: toOpenAIUserContent(parts) }];
118
+ return [
119
+ {
120
+ role,
121
+ content: buildNcpUserContent(parts, {
122
+ assetStore: options.assetStore
123
+ })
124
+ }
125
+ ];
75
126
  }
76
127
  const text = parts.filter(isTextLikePart).map((part) => part.text).join("");
77
128
  return [{ role, content: text }];
@@ -132,8 +183,15 @@ function messageToOpenAI(msg) {
132
183
  return [];
133
184
  }
134
185
  var DefaultNcpContextBuilder = class {
135
- constructor(toolRegistry) {
136
- this.toolRegistry = toolRegistry;
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;
137
195
  }
138
196
  prepare = (input, options) => {
139
197
  const maxMessages = options?.maxMessages ?? 50;
@@ -146,10 +204,18 @@ var DefaultNcpContextBuilder = class {
146
204
  messages.push({ role: "system", content: systemPrompt });
147
205
  }
148
206
  for (const msg of sessionMessages.slice(-maxMessages)) {
149
- messages.push(...messageToOpenAI(msg));
207
+ messages.push(
208
+ ...messageToOpenAI(msg, {
209
+ assetStore: this.assetStore
210
+ })
211
+ );
150
212
  }
151
213
  for (const msg of input.messages) {
152
- messages.push(...messageToOpenAI(msg));
214
+ messages.push(
215
+ ...messageToOpenAI(msg, {
216
+ assetStore: this.assetStore
217
+ })
218
+ );
153
219
  }
154
220
  const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
155
221
  const filteredToolDefinitions = requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions;
@@ -164,12 +230,265 @@ var DefaultNcpContextBuilder = class {
164
230
  return {
165
231
  messages,
166
232
  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
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
169
235
  };
170
236
  };
171
237
  };
172
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/";
245
+ function normalizeSegment(value) {
246
+ return value.replace(/[^\w.-]+/g, "_").replace(/^_+|_+$/g, "") || "asset.bin";
247
+ }
248
+ function normalizeFileName(value) {
249
+ const trimmed = value.trim();
250
+ return trimmed.length > 0 ? trimmed : "asset.bin";
251
+ }
252
+ function normalizeMimeType(value) {
253
+ const normalized = value?.trim().toLowerCase() ?? "";
254
+ return normalized.length > 0 ? normalized : "application/octet-stream";
255
+ }
256
+ function buildAssetId() {
257
+ return `asset_${Date.now().toString(36)}_${randomUUID().slice(0, 8)}`;
258
+ }
259
+ 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);
265
+ }
266
+ 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}`;
271
+ }
272
+ function buildAssetUri(storageKey) {
273
+ return `${ASSET_URI_SCHEME}${storageKey}`;
274
+ }
275
+ 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;
282
+ }
283
+ 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("/");
293
+ }
294
+ 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
+ };
302
+ }
303
+ 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
+ };
311
+ }
312
+ 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));
346
+ }
347
+ 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
+ }
483
+ };
484
+ function buildAssetContentPath(params) {
485
+ const query = new URLSearchParams({ uri: params.assetUri });
486
+ return `${params.basePath}?${query.toString()}`;
487
+ }
488
+ async function ensureParentDirectory(filePath) {
489
+ await mkdir(dirname(filePath), { recursive: true });
490
+ }
491
+
173
492
  // src/round-buffer.ts
174
493
  var DefaultNcpRoundBuffer = class {
175
494
  text = "";
@@ -834,5 +1153,9 @@ export {
834
1153
  DefaultNcpRoundBuffer,
835
1154
  DefaultNcpStreamEncoder,
836
1155
  DefaultNcpToolRegistry,
837
- EchoNcpLLMApi
1156
+ EchoNcpLLMApi,
1157
+ LocalAssetStore,
1158
+ buildAssetContentPath,
1159
+ buildNcpUserContent,
1160
+ isTextLikeAsset
838
1161
  };
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.3.0",
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.4.0"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@types/node": "^20.17.6",