@nextclaw/ncp-agent-runtime 0.2.2 → 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,14 +1,342 @@
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()}`;
211
+ }
212
+
213
+ // src/user-content.ts
214
+ var DEFAULT_ATTACHMENT_TEXT_MAX_BYTES = 32 * 1024;
215
+ function readOptionalString(value) {
216
+ if (typeof value !== "string") {
217
+ return null;
218
+ }
219
+ const trimmed = value.trim();
220
+ return trimmed.length > 0 ? trimmed : null;
221
+ }
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
+ });
290
+ }
291
+ function buildNcpUserContent(parts, options = {}) {
292
+ const content = [];
293
+ for (const part of parts) {
294
+ if ((part.type === "text" || part.type === "rich-text") && part.text.trim().length > 0) {
295
+ content.push({ type: "text", text: part.text });
296
+ continue;
297
+ }
298
+ if (part.type !== "file") {
299
+ continue;
300
+ }
301
+ const textAttachmentBlock = resolveTextAttachmentBlock(part, options);
302
+ if (textAttachmentBlock) {
303
+ content.push({ type: "text", text: textAttachmentBlock });
304
+ continue;
305
+ }
306
+ const imageUrl = resolveImageDataUrl(part, options.attachmentStore);
307
+ if (!imageUrl) {
308
+ continue;
309
+ }
310
+ content.push({
311
+ type: "image_url",
312
+ image_url: {
313
+ url: imageUrl
314
+ }
315
+ });
316
+ }
317
+ if (content.length === 0) {
318
+ return "";
319
+ }
320
+ if (content.length === 1 && content[0]?.type === "text") {
321
+ return content[0].text;
322
+ }
323
+ return content;
324
+ }
325
+
1
326
  // src/context-builder.ts
2
327
  function isRecord(value) {
3
328
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
4
329
  }
5
- function readOptionalString(value) {
330
+ function readOptionalString2(value) {
6
331
  if (typeof value !== "string") {
7
332
  return null;
8
333
  }
9
334
  const trimmed = value.trim();
10
335
  return trimmed.length > 0 ? trimmed : null;
11
336
  }
337
+ function isTextLikePart(part) {
338
+ return part.type === "text" || part.type === "rich-text";
339
+ }
12
340
  function mergeMessageAndRequestMetadata(input) {
13
341
  const messageMetadata = input.messages.slice().reverse().find((message) => isRecord(message.metadata))?.metadata;
14
342
  return {
@@ -23,18 +351,32 @@ function readRequestedToolNames(metadata) {
23
351
  }
24
352
  const deduped = /* @__PURE__ */ new Set();
25
353
  for (const item of raw) {
26
- const value = readOptionalString(item);
354
+ const value = readOptionalString2(item);
27
355
  if (value) {
28
356
  deduped.add(value);
29
357
  }
30
358
  }
31
359
  return [...deduped];
32
360
  }
33
- function messageToOpenAI(msg) {
361
+ function isDefaultNcpContextBuilderOptions(value) {
362
+ return Boolean(value) && typeof value === "object" && !("getToolDefinitions" in value);
363
+ }
364
+ function messageToOpenAI(msg, options) {
34
365
  const role = msg.role;
35
366
  const parts = msg.parts ?? [];
36
367
  if (role === "user" || role === "system") {
37
- const text = parts.filter((p) => p.type === "text").map((p) => p.text).join("");
368
+ if (role === "user") {
369
+ return [
370
+ {
371
+ role,
372
+ content: buildNcpUserContent(parts, {
373
+ attachmentStore: options.attachmentStore,
374
+ maxTextBytes: options.attachmentTextMaxBytes
375
+ })
376
+ }
377
+ ];
378
+ }
379
+ const text = parts.filter(isTextLikePart).map((part) => part.text).join("");
38
380
  return [{ role, content: text }];
39
381
  }
40
382
  if (role === "assistant") {
@@ -93,8 +435,17 @@ function messageToOpenAI(msg) {
93
435
  return [];
94
436
  }
95
437
  var DefaultNcpContextBuilder = class {
96
- constructor(toolRegistry) {
97
- 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;
98
449
  }
99
450
  prepare = (input, options) => {
100
451
  const maxMessages = options?.maxMessages ?? 50;
@@ -107,10 +458,20 @@ var DefaultNcpContextBuilder = class {
107
458
  messages.push({ role: "system", content: systemPrompt });
108
459
  }
109
460
  for (const msg of sessionMessages.slice(-maxMessages)) {
110
- messages.push(...messageToOpenAI(msg));
461
+ messages.push(
462
+ ...messageToOpenAI(msg, {
463
+ attachmentStore: this.attachmentStore,
464
+ attachmentTextMaxBytes: this.attachmentTextMaxBytes
465
+ })
466
+ );
111
467
  }
112
468
  for (const msg of input.messages) {
113
- messages.push(...messageToOpenAI(msg));
469
+ messages.push(
470
+ ...messageToOpenAI(msg, {
471
+ attachmentStore: this.attachmentStore,
472
+ attachmentTextMaxBytes: this.attachmentTextMaxBytes
473
+ })
474
+ );
114
475
  }
115
476
  const toolDefinitions = this.toolRegistry?.getToolDefinitions() ?? [];
116
477
  const filteredToolDefinitions = requestedToolNames.length > 0 ? toolDefinitions.filter((definition) => requestedToolNames.includes(definition.name)) : toolDefinitions;
@@ -125,8 +486,8 @@ var DefaultNcpContextBuilder = class {
125
486
  return {
126
487
  messages,
127
488
  tools: tools && tools.length > 0 ? tools : void 0,
128
- model: readOptionalString(requestMetadata.model) ?? readOptionalString(requestMetadata.llm_model) ?? readOptionalString(requestMetadata.agent_model) ?? void 0,
129
- 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
130
491
  };
131
492
  };
132
493
  };
@@ -795,5 +1156,9 @@ export {
795
1156
  DefaultNcpRoundBuffer,
796
1157
  DefaultNcpStreamEncoder,
797
1158
  DefaultNcpToolRegistry,
798
- EchoNcpLLMApi
1159
+ EchoNcpLLMApi,
1160
+ LocalAttachmentStore,
1161
+ buildAttachmentContentPath,
1162
+ buildNcpUserContent,
1163
+ isTextLikeAttachment
799
1164
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/ncp-agent-runtime",
3
- "version": "0.2.2",
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,17 +15,19 @@
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",
22
22
  "prettier": "^3.3.3",
23
23
  "tsup": "^8.3.5",
24
- "typescript": "^5.6.3"
24
+ "typescript": "^5.6.3",
25
+ "vitest": "^2.1.2"
25
26
  },
26
27
  "scripts": {
27
28
  "build": "tsup src/index.ts --format esm --dts --out-dir dist",
28
29
  "lint": "eslint .",
30
+ "test": "vitest run",
29
31
  "tsc": "tsc -p tsconfig.json"
30
32
  }
31
33
  }