@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 +87 -3
- package/dist/index.js +352 -29
- package/package.json +2 -2
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
|
-
|
|
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/
|
|
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
|
|
13
|
-
|
|
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
|
|
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
|
|
23
|
-
content.push({ type: "text", text: part.text });
|
|
58
|
+
if (part.type !== "file") {
|
|
24
59
|
continue;
|
|
25
60
|
}
|
|
26
|
-
|
|
27
|
-
|
|
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 =
|
|
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
|
|
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 [
|
|
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
|
-
|
|
136
|
-
|
|
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(
|
|
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(
|
|
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:
|
|
168
|
-
thinkingLevel:
|
|
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.
|
|
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.
|
|
18
|
+
"@nextclaw/ncp": "0.4.0"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@types/node": "^20.17.6",
|