@nextclaw/ncp-agent-runtime 0.3.14 → 0.3.16-beta.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 +54 -4
- package/dist/index.js +445 -11
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpAssistantReasoningNormalizationMode, NcpContextBuilder, NcpContextPrepareOptions, NcpEncodeContext, NcpEndpointEvent, NcpInvalidToolArgumentsResult, NcpLLMApi, NcpLLMApiInput, NcpLLMApiOptions, NcpMessagePart, NcpRoundBuffer, NcpStreamEncoder, NcpTool, NcpToolCallResult, NcpToolDefinition, NcpToolRegistry, OpenAIChatChunk, OpenAIContentPart, OpenAITool } from "@nextclaw/ncp";
|
|
1
|
+
import { NcpAgentConversationStateManager, NcpAgentRunInput, NcpAgentRunOptions, NcpAgentRuntime, NcpAssistantReasoningNormalizationMode, NcpContextBuilder, NcpContextPrepareOptions, NcpEncodeContext, NcpEndpointEvent, NcpInvalidToolArgumentsResult, NcpLLMApi, NcpLLMApiInput, NcpLLMApiOptions, NcpMessagePart, NcpRoundBuffer, NcpStreamEncoder, NcpTool, NcpToolCallResult, NcpToolDefinition, NcpToolRegistry, OpenAIChatChunk, OpenAIChatMessage, OpenAIContentPart, OpenAITool } from "@nextclaw/ncp";
|
|
2
2
|
|
|
3
3
|
//#region src/asset-store.d.ts
|
|
4
4
|
type StoredAssetRecord = {
|
|
@@ -142,7 +142,55 @@ declare class EchoNcpLLMApi implements NcpLLMApi {
|
|
|
142
142
|
generate: (input: NcpLLMApiInput, options?: NcpLLMApiOptions) => AsyncGenerator<OpenAIChatChunk>;
|
|
143
143
|
}
|
|
144
144
|
//#endregion
|
|
145
|
-
//#region src/
|
|
145
|
+
//#region src/tool-result-content.manager.d.ts
|
|
146
|
+
type ToolResultContentContext = {
|
|
147
|
+
toolCallId?: string;
|
|
148
|
+
toolName?: string;
|
|
149
|
+
};
|
|
150
|
+
type ToolResultContentManagerOptions = {
|
|
151
|
+
maxModelVisibleChars?: number;
|
|
152
|
+
maxToolMessagesChars?: number;
|
|
153
|
+
maxStringValueChars?: number;
|
|
154
|
+
maxModelVisibleImages?: number;
|
|
155
|
+
maxArrayItems?: number;
|
|
156
|
+
maxObjectKeys?: number;
|
|
157
|
+
maxDepth?: number;
|
|
158
|
+
};
|
|
159
|
+
declare class ToolResultContentManager {
|
|
160
|
+
private readonly maxModelVisibleChars;
|
|
161
|
+
private readonly maxToolMessagesChars;
|
|
162
|
+
private readonly maxStringValueChars;
|
|
163
|
+
private readonly maxModelVisibleImages;
|
|
164
|
+
private readonly maxArrayItems;
|
|
165
|
+
private readonly maxObjectKeys;
|
|
166
|
+
private readonly maxDepth;
|
|
167
|
+
private readonly imageService;
|
|
168
|
+
constructor(options?: ToolResultContentManagerOptions);
|
|
169
|
+
normalizeToolCallResult: (result: NcpToolCallResult) => NcpToolCallResult;
|
|
170
|
+
compactInput: (input: NcpLLMApiInput) => NcpLLMApiInput;
|
|
171
|
+
toModelContent: (result: unknown, context?: ToolResultContentContext) => string;
|
|
172
|
+
toVisualObservationMessages: (toolResults: ReadonlyArray<NcpToolCallResult>) => OpenAIChatMessage[];
|
|
173
|
+
private readonly normalizeResult;
|
|
174
|
+
private readonly buildContentItems;
|
|
175
|
+
private readonly readVisibleImages;
|
|
176
|
+
private readonly buildVisualObservationMessage;
|
|
177
|
+
private readonly compactToolMessages;
|
|
178
|
+
private readonly compactToolMessage;
|
|
179
|
+
private readonly normalizeStringResult;
|
|
180
|
+
private readonly redactValue;
|
|
181
|
+
private readonly redactArray;
|
|
182
|
+
private readonly redactObject;
|
|
183
|
+
private readonly redactString;
|
|
184
|
+
private readonly attachNotice;
|
|
185
|
+
private readonly buildEnvelope;
|
|
186
|
+
private readonly buildStringNotice;
|
|
187
|
+
private readonly buildNotice;
|
|
188
|
+
private readonly buildOmittedHistoricalToolResult;
|
|
189
|
+
private readonly boundModelContentText;
|
|
190
|
+
}
|
|
191
|
+
declare const defaultToolResultContentManager: ToolResultContentManager;
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/agent-runtime.service.d.ts
|
|
146
194
|
type DefaultNcpAgentRuntimeConfig = {
|
|
147
195
|
contextBuilder: NcpContextBuilder;
|
|
148
196
|
llmApi: NcpLLMApi;
|
|
@@ -150,6 +198,7 @@ type DefaultNcpAgentRuntimeConfig = {
|
|
|
150
198
|
stateManager: NcpAgentConversationStateManager;
|
|
151
199
|
streamEncoder?: NcpStreamEncoder;
|
|
152
200
|
reasoningNormalizationMode?: NcpAssistantReasoningNormalizationMode;
|
|
201
|
+
toolResultContentManager?: ToolResultContentManager;
|
|
153
202
|
};
|
|
154
203
|
declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
|
|
155
204
|
private readonly contextBuilder;
|
|
@@ -158,6 +207,7 @@ declare class DefaultNcpAgentRuntime implements NcpAgentRuntime {
|
|
|
158
207
|
private readonly stateManager;
|
|
159
208
|
private readonly streamEncoder;
|
|
160
209
|
private readonly reasoningNormalizationMode;
|
|
210
|
+
private readonly toolResultContentManager;
|
|
161
211
|
constructor(config: DefaultNcpAgentRuntimeConfig);
|
|
162
212
|
run: (this: DefaultNcpAgentRuntime, input: NcpAgentRunInput, options?: NcpAgentRunOptions) => AsyncGenerator<NcpEndpointEvent>;
|
|
163
213
|
/**
|
|
@@ -210,6 +260,6 @@ declare function createToolExecutionFailedResult(params: {
|
|
|
210
260
|
toolName: string;
|
|
211
261
|
};
|
|
212
262
|
};
|
|
213
|
-
declare function appendToolRoundToInput(input: NcpLLMApiInput, reasoning: string, text: string, toolResults: ReadonlyArray<NcpToolCallResult
|
|
263
|
+
declare function appendToolRoundToInput(input: NcpLLMApiInput, reasoning: string, text: string, toolResults: ReadonlyArray<NcpToolCallResult>, toolResultContentManager?: ToolResultContentManager): NcpLLMApiInput;
|
|
214
264
|
//#endregion
|
|
215
|
-
export { type AssetMeta, type AssetPutInput, type AssetRef, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, type StoredAssetRecord, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
|
|
265
|
+
export { type AssetMeta, type AssetPutInput, type AssetRef, DefaultNcpAgentRuntime, type DefaultNcpAgentRuntimeConfig, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, type StoredAssetRecord, ToolResultContentManager, type ToolResultContentManagerOptions, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, defaultToolResultContentManager, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
|
package/dist/index.js
CHANGED
|
@@ -147,6 +147,423 @@ function buildNcpUserContent(parts, options = {}) {
|
|
|
147
147
|
return content;
|
|
148
148
|
}
|
|
149
149
|
//#endregion
|
|
150
|
+
//#region src/tool-result-content.utils.ts
|
|
151
|
+
const LARGE_INLINE_PAYLOAD_CHARS$1 = 2048;
|
|
152
|
+
function sanitizePositiveInt(value, fallback) {
|
|
153
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
154
|
+
const normalized = Math.floor(value);
|
|
155
|
+
return normalized > 0 ? normalized : fallback;
|
|
156
|
+
}
|
|
157
|
+
function serializeValue(value) {
|
|
158
|
+
if (typeof value === "string") return {
|
|
159
|
+
text: value,
|
|
160
|
+
ok: true
|
|
161
|
+
};
|
|
162
|
+
try {
|
|
163
|
+
return {
|
|
164
|
+
text: JSON.stringify(value ?? null),
|
|
165
|
+
ok: true
|
|
166
|
+
};
|
|
167
|
+
} catch (error) {
|
|
168
|
+
return {
|
|
169
|
+
text: `[unserializable tool result: ${error instanceof Error ? error.message : String(error)}]`,
|
|
170
|
+
ok: false
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function truncateMiddle(value, maxChars) {
|
|
175
|
+
if (value.length <= maxChars) return value;
|
|
176
|
+
if (maxChars <= 0) return "";
|
|
177
|
+
const marker = "\n... [truncated] ...\n";
|
|
178
|
+
if (maxChars <= 21) return value.slice(0, maxChars);
|
|
179
|
+
const keepChars = maxChars - 21;
|
|
180
|
+
const headChars = Math.ceil(keepChars / 2);
|
|
181
|
+
const tailChars = Math.floor(keepChars / 2);
|
|
182
|
+
return `${value.slice(0, headChars)}${marker}${value.slice(value.length - tailChars)}`;
|
|
183
|
+
}
|
|
184
|
+
function isLargeDataUrl(value) {
|
|
185
|
+
return value.length > LARGE_INLINE_PAYLOAD_CHARS$1 && /^data:[^,]+;base64,/i.test(value);
|
|
186
|
+
}
|
|
187
|
+
function readDataUrlMime(value) {
|
|
188
|
+
return /^data:([^;,]+)/i.exec(value)?.[1] ?? "application/octet-stream";
|
|
189
|
+
}
|
|
190
|
+
function isLargeBase64LikeString(value) {
|
|
191
|
+
if (value.length <= LARGE_INLINE_PAYLOAD_CHARS$1) return false;
|
|
192
|
+
const compact = value.replace(/\s+/g, "");
|
|
193
|
+
return compact.length > LARGE_INLINE_PAYLOAD_CHARS$1 && /^[A-Za-z0-9+/=_-]+$/.test(compact);
|
|
194
|
+
}
|
|
195
|
+
function isBinaryLike(value) {
|
|
196
|
+
return value instanceof ArrayBuffer || ArrayBuffer.isView(value);
|
|
197
|
+
}
|
|
198
|
+
function getBinaryByteLength(value) {
|
|
199
|
+
if (value instanceof ArrayBuffer) return value.byteLength;
|
|
200
|
+
if (ArrayBuffer.isView(value)) return value.byteLength;
|
|
201
|
+
return 0;
|
|
202
|
+
}
|
|
203
|
+
function readStringField(value, key) {
|
|
204
|
+
const field = value[key];
|
|
205
|
+
return typeof field === "string" && field.length > 0 ? field : null;
|
|
206
|
+
}
|
|
207
|
+
//#endregion
|
|
208
|
+
//#region src/tool-result-image.service.ts
|
|
209
|
+
const LARGE_INLINE_PAYLOAD_CHARS = 2048;
|
|
210
|
+
const DEFAULT_MAX_MODEL_VISIBLE_IMAGES$1 = 4;
|
|
211
|
+
const DEFAULT_MAX_ARRAY_ITEMS$1 = 50;
|
|
212
|
+
const DEFAULT_MAX_OBJECT_KEYS$1 = 80;
|
|
213
|
+
const DEFAULT_MAX_DEPTH$1 = 8;
|
|
214
|
+
var ToolResultImageService = class {
|
|
215
|
+
maxModelVisibleImages;
|
|
216
|
+
maxArrayItems;
|
|
217
|
+
maxObjectKeys;
|
|
218
|
+
maxDepth;
|
|
219
|
+
constructor(options = {}) {
|
|
220
|
+
this.maxModelVisibleImages = sanitizePositiveInt(options.maxModelVisibleImages, DEFAULT_MAX_MODEL_VISIBLE_IMAGES$1);
|
|
221
|
+
this.maxArrayItems = sanitizePositiveInt(options.maxArrayItems, DEFAULT_MAX_ARRAY_ITEMS$1);
|
|
222
|
+
this.maxObjectKeys = sanitizePositiveInt(options.maxObjectKeys, DEFAULT_MAX_OBJECT_KEYS$1);
|
|
223
|
+
this.maxDepth = sanitizePositiveInt(options.maxDepth, DEFAULT_MAX_DEPTH$1);
|
|
224
|
+
}
|
|
225
|
+
extractImageItems = (value) => {
|
|
226
|
+
const images = [];
|
|
227
|
+
this.collectImageItems(value, images, { depth: 0 });
|
|
228
|
+
return images;
|
|
229
|
+
};
|
|
230
|
+
readVisibleImages = (contentItems) => {
|
|
231
|
+
if (!contentItems) return [];
|
|
232
|
+
return contentItems.filter((item) => item.type === "input_image").slice(0, this.maxModelVisibleImages);
|
|
233
|
+
};
|
|
234
|
+
toOpenAiImagePart = (item) => ({
|
|
235
|
+
type: "image_url",
|
|
236
|
+
image_url: {
|
|
237
|
+
url: item.imageUrl,
|
|
238
|
+
...item.detail ? { detail: toOpenAiImageDetail(item.detail) } : {}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
summarizeImageContentItem = (value) => {
|
|
242
|
+
const type = readStringField(value, "type");
|
|
243
|
+
if (type === "image") return {
|
|
244
|
+
type: "image",
|
|
245
|
+
mimeType: readStringField(value, "mimeType") ?? readStringField(value, "mime_type"),
|
|
246
|
+
detail: readImageDetail(value),
|
|
247
|
+
dataOmitted: true,
|
|
248
|
+
originalDataChars: readStringField(value, "data")?.length ?? 0
|
|
249
|
+
};
|
|
250
|
+
if (type === "input_image" || type === "image_url") {
|
|
251
|
+
const imageUrl = readImageUrl(value);
|
|
252
|
+
return {
|
|
253
|
+
type,
|
|
254
|
+
imageUrl: imageUrl ? redactImageUrl(imageUrl) : void 0,
|
|
255
|
+
detail: readImageDetail(value)
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
return null;
|
|
259
|
+
};
|
|
260
|
+
collectImageItems = (value, output, context) => {
|
|
261
|
+
if (output.length >= this.maxModelVisibleImages || context.depth > this.maxDepth) return;
|
|
262
|
+
if (typeof value === "string") {
|
|
263
|
+
const image = imageDataUrlToContentItem(value);
|
|
264
|
+
if (image) output.push(image);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (!value || typeof value !== "object" || isBinaryLike(value)) return;
|
|
268
|
+
if (Array.isArray(value)) {
|
|
269
|
+
value.slice(0, this.maxArrayItems).forEach((item) => this.collectImageItems(item, output, { depth: context.depth + 1 }));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const image = readImageContentItem(value);
|
|
273
|
+
if (image) {
|
|
274
|
+
output.push(image);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
Object.values(value).slice(0, this.maxObjectKeys).forEach((item) => this.collectImageItems(item, output, { depth: context.depth + 1 }));
|
|
278
|
+
};
|
|
279
|
+
};
|
|
280
|
+
function imageDataUrlToContentItem(value) {
|
|
281
|
+
if (!/^data:image\/[^,]+;base64,/i.test(value)) return null;
|
|
282
|
+
return {
|
|
283
|
+
type: "input_image",
|
|
284
|
+
imageUrl: value,
|
|
285
|
+
mimeType: readDataUrlMime(value),
|
|
286
|
+
originalDataChars: value.length
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function readImageContentItem(value) {
|
|
290
|
+
const type = readStringField(value, "type");
|
|
291
|
+
if (type === "image") {
|
|
292
|
+
const data = readStringField(value, "data");
|
|
293
|
+
const mimeType = readStringField(value, "mimeType") ?? readStringField(value, "mime_type") ?? "image/png";
|
|
294
|
+
if (!data) return null;
|
|
295
|
+
return {
|
|
296
|
+
type: "input_image",
|
|
297
|
+
imageUrl: `data:${mimeType};base64,${data}`,
|
|
298
|
+
mimeType,
|
|
299
|
+
detail: readImageDetail(value),
|
|
300
|
+
originalDataChars: data.length
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
if (type === "input_image" || type === "image_url") {
|
|
304
|
+
const imageUrl = readImageUrl(value);
|
|
305
|
+
if (!imageUrl) return null;
|
|
306
|
+
return {
|
|
307
|
+
type: "input_image",
|
|
308
|
+
imageUrl,
|
|
309
|
+
detail: readImageDetail(value)
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function toOpenAiImageDetail(detail) {
|
|
315
|
+
if (detail === "low" || detail === "high" || detail === "auto") return detail;
|
|
316
|
+
if (detail === "original") return "high";
|
|
317
|
+
}
|
|
318
|
+
function readImageDetail(value) {
|
|
319
|
+
const direct = readStringField(value, "detail");
|
|
320
|
+
if (isImageDetail(direct)) return direct;
|
|
321
|
+
const meta = value._meta;
|
|
322
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return;
|
|
323
|
+
const codexDetail = readStringField(meta, "codex/imageDetail");
|
|
324
|
+
return isImageDetail(codexDetail) ? codexDetail : void 0;
|
|
325
|
+
}
|
|
326
|
+
function isImageDetail(value) {
|
|
327
|
+
return value === "low" || value === "high" || value === "auto" || value === "original";
|
|
328
|
+
}
|
|
329
|
+
function readImageUrl(value) {
|
|
330
|
+
const direct = readStringField(value, "image_url");
|
|
331
|
+
if (direct) return direct;
|
|
332
|
+
const nested = value.image_url;
|
|
333
|
+
if (!nested || typeof nested !== "object" || Array.isArray(nested)) return null;
|
|
334
|
+
return readStringField(nested, "url");
|
|
335
|
+
}
|
|
336
|
+
function redactImageUrl(value) {
|
|
337
|
+
if (isLargeDataUrl(value)) return `[omitted data URL payload: mime=${readDataUrlMime(value)}, originalChars=${value.length}]`;
|
|
338
|
+
return value.length > LARGE_INLINE_PAYLOAD_CHARS ? truncateMiddle(value, LARGE_INLINE_PAYLOAD_CHARS) : value;
|
|
339
|
+
}
|
|
340
|
+
//#endregion
|
|
341
|
+
//#region src/tool-result-content.manager.ts
|
|
342
|
+
const DEFAULT_MAX_MODEL_VISIBLE_CHARS = 1e4;
|
|
343
|
+
const DEFAULT_MAX_TOOL_MESSAGES_CHARS = 6e4;
|
|
344
|
+
const DEFAULT_MAX_STRING_VALUE_CHARS = 4e3;
|
|
345
|
+
const DEFAULT_MAX_MODEL_VISIBLE_IMAGES = 4;
|
|
346
|
+
const DEFAULT_MAX_ARRAY_ITEMS = 50;
|
|
347
|
+
const DEFAULT_MAX_OBJECT_KEYS = 80;
|
|
348
|
+
const DEFAULT_MAX_DEPTH = 8;
|
|
349
|
+
const TRUNCATION_MARKER = "[NextClaw tool result truncated]";
|
|
350
|
+
var ToolResultContentManager = class {
|
|
351
|
+
maxModelVisibleChars;
|
|
352
|
+
maxToolMessagesChars;
|
|
353
|
+
maxStringValueChars;
|
|
354
|
+
maxModelVisibleImages;
|
|
355
|
+
maxArrayItems;
|
|
356
|
+
maxObjectKeys;
|
|
357
|
+
maxDepth;
|
|
358
|
+
imageService;
|
|
359
|
+
constructor(options = {}) {
|
|
360
|
+
this.maxModelVisibleChars = sanitizePositiveInt(options.maxModelVisibleChars, DEFAULT_MAX_MODEL_VISIBLE_CHARS);
|
|
361
|
+
this.maxToolMessagesChars = sanitizePositiveInt(options.maxToolMessagesChars, DEFAULT_MAX_TOOL_MESSAGES_CHARS);
|
|
362
|
+
this.maxStringValueChars = sanitizePositiveInt(options.maxStringValueChars, DEFAULT_MAX_STRING_VALUE_CHARS);
|
|
363
|
+
this.maxModelVisibleImages = sanitizePositiveInt(options.maxModelVisibleImages, DEFAULT_MAX_MODEL_VISIBLE_IMAGES);
|
|
364
|
+
this.maxArrayItems = sanitizePositiveInt(options.maxArrayItems, DEFAULT_MAX_ARRAY_ITEMS);
|
|
365
|
+
this.maxObjectKeys = sanitizePositiveInt(options.maxObjectKeys, DEFAULT_MAX_OBJECT_KEYS);
|
|
366
|
+
this.maxDepth = sanitizePositiveInt(options.maxDepth, DEFAULT_MAX_DEPTH);
|
|
367
|
+
this.imageService = new ToolResultImageService({
|
|
368
|
+
maxModelVisibleImages: this.maxModelVisibleImages,
|
|
369
|
+
maxArrayItems: this.maxArrayItems,
|
|
370
|
+
maxObjectKeys: this.maxObjectKeys,
|
|
371
|
+
maxDepth: this.maxDepth
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
normalizeToolCallResult = (result) => {
|
|
375
|
+
const context = {
|
|
376
|
+
toolCallId: result.toolCallId,
|
|
377
|
+
toolName: result.toolName
|
|
378
|
+
};
|
|
379
|
+
const normalized = this.normalizeResult(result.result, {
|
|
380
|
+
toolCallId: result.toolCallId,
|
|
381
|
+
toolName: result.toolName
|
|
382
|
+
});
|
|
383
|
+
const contentItems = this.buildContentItems(result.result, normalized.value, context);
|
|
384
|
+
return {
|
|
385
|
+
...result,
|
|
386
|
+
...normalized.changed ? { result: normalized.value } : {},
|
|
387
|
+
contentItems
|
|
388
|
+
};
|
|
389
|
+
};
|
|
390
|
+
compactInput = (input) => ({
|
|
391
|
+
...input,
|
|
392
|
+
messages: this.compactToolMessages(input.messages)
|
|
393
|
+
});
|
|
394
|
+
toModelContent = (result, context = {}) => {
|
|
395
|
+
const serialized = serializeValue(this.normalizeResult(result, context).value);
|
|
396
|
+
if (serialized.text.length <= this.maxModelVisibleChars) return serialized.text;
|
|
397
|
+
return this.boundModelContentText(JSON.stringify(this.buildEnvelope({
|
|
398
|
+
context,
|
|
399
|
+
originalSerializedChars: serialized.text.length,
|
|
400
|
+
previewSource: serialized.text
|
|
401
|
+
})));
|
|
402
|
+
};
|
|
403
|
+
toVisualObservationMessages = (toolResults) => {
|
|
404
|
+
return toolResults.flatMap((result) => {
|
|
405
|
+
const images = this.readVisibleImages(result.contentItems);
|
|
406
|
+
if (images.length === 0) return [];
|
|
407
|
+
return [this.buildVisualObservationMessage(result, images)];
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
normalizeResult = (result, context) => {
|
|
411
|
+
if (typeof result === "string") return this.normalizeStringResult(result, context);
|
|
412
|
+
const serialized = serializeValue(result);
|
|
413
|
+
if (serialized.ok && serialized.text.length <= this.maxModelVisibleChars) return {
|
|
414
|
+
value: result,
|
|
415
|
+
changed: false
|
|
416
|
+
};
|
|
417
|
+
const redacted = this.redactValue(result, { depth: 0 });
|
|
418
|
+
const redactedSerialized = serializeValue(redacted);
|
|
419
|
+
if (redactedSerialized.text.length <= this.maxModelVisibleChars) return {
|
|
420
|
+
value: this.attachNotice(redacted, {
|
|
421
|
+
context,
|
|
422
|
+
originalSerializedChars: serialized.text.length
|
|
423
|
+
}),
|
|
424
|
+
changed: true
|
|
425
|
+
};
|
|
426
|
+
return {
|
|
427
|
+
value: this.buildEnvelope({
|
|
428
|
+
context,
|
|
429
|
+
originalSerializedChars: serialized.text.length,
|
|
430
|
+
previewSource: redactedSerialized.text
|
|
431
|
+
}),
|
|
432
|
+
changed: true
|
|
433
|
+
};
|
|
434
|
+
};
|
|
435
|
+
buildContentItems = (rawResult, normalizedResult, context) => {
|
|
436
|
+
return [{
|
|
437
|
+
type: "input_text",
|
|
438
|
+
text: this.toModelContent(normalizedResult, context)
|
|
439
|
+
}, ...this.imageService.extractImageItems(rawResult).slice(0, this.maxModelVisibleImages)];
|
|
440
|
+
};
|
|
441
|
+
readVisibleImages = (contentItems) => this.imageService.readVisibleImages(contentItems);
|
|
442
|
+
buildVisualObservationMessage = (toolResult, images) => {
|
|
443
|
+
const imageParts = images.map(this.imageService.toOpenAiImagePart);
|
|
444
|
+
const omittedCount = Math.max(0, (toolResult.contentItems ?? []).filter((item) => item.type === "input_image").length - imageParts.length);
|
|
445
|
+
return {
|
|
446
|
+
role: "user",
|
|
447
|
+
content: [{
|
|
448
|
+
type: "text",
|
|
449
|
+
text: [
|
|
450
|
+
`Tool "${toolResult.toolName}" returned ${imageParts.length} image(s).`,
|
|
451
|
+
"Use the attached image content as the visual observation for the preceding tool result.",
|
|
452
|
+
omittedCount > 0 ? `${omittedCount} additional image(s) were omitted by budget.` : ""
|
|
453
|
+
].filter(Boolean).join(" ")
|
|
454
|
+
}, ...imageParts]
|
|
455
|
+
};
|
|
456
|
+
};
|
|
457
|
+
compactToolMessages = (messages) => {
|
|
458
|
+
let remainingChars = this.maxToolMessagesChars;
|
|
459
|
+
const output = messages.slice();
|
|
460
|
+
for (let index = output.length - 1; index >= 0; index -= 1) {
|
|
461
|
+
const message = output[index];
|
|
462
|
+
if (message.role !== "tool") continue;
|
|
463
|
+
const compacted = this.compactToolMessage(message, remainingChars);
|
|
464
|
+
output[index] = compacted.message;
|
|
465
|
+
remainingChars = compacted.remainingChars;
|
|
466
|
+
}
|
|
467
|
+
return output;
|
|
468
|
+
};
|
|
469
|
+
compactToolMessage = (message, remainingChars) => {
|
|
470
|
+
if (message.content.length <= remainingChars) return {
|
|
471
|
+
message,
|
|
472
|
+
remainingChars: remainingChars - message.content.length
|
|
473
|
+
};
|
|
474
|
+
const content = remainingChars >= 1e3 ? truncateMiddle(message.content, remainingChars) : this.buildOmittedHistoricalToolResult(message.content.length);
|
|
475
|
+
return {
|
|
476
|
+
message: {
|
|
477
|
+
...message,
|
|
478
|
+
content
|
|
479
|
+
},
|
|
480
|
+
remainingChars: Math.max(0, remainingChars - content.length)
|
|
481
|
+
};
|
|
482
|
+
};
|
|
483
|
+
normalizeStringResult = (result, context) => {
|
|
484
|
+
const redacted = this.redactString(result);
|
|
485
|
+
if (redacted.length <= this.maxModelVisibleChars && redacted === result) return {
|
|
486
|
+
value: result,
|
|
487
|
+
changed: false
|
|
488
|
+
};
|
|
489
|
+
if (redacted.length <= this.maxModelVisibleChars) return {
|
|
490
|
+
value: this.buildStringNotice(context, result.length, redacted),
|
|
491
|
+
changed: true
|
|
492
|
+
};
|
|
493
|
+
return {
|
|
494
|
+
value: this.buildStringNotice(context, result.length, truncateMiddle(redacted, this.maxModelVisibleChars)),
|
|
495
|
+
changed: true
|
|
496
|
+
};
|
|
497
|
+
};
|
|
498
|
+
redactValue = (value, context) => {
|
|
499
|
+
if (typeof value === "string") return this.redactString(value);
|
|
500
|
+
if (!value || typeof value !== "object") return value;
|
|
501
|
+
if (value instanceof Date) return value.toISOString();
|
|
502
|
+
const imageSummary = this.imageService.summarizeImageContentItem(value);
|
|
503
|
+
if (imageSummary) return imageSummary;
|
|
504
|
+
if (isBinaryLike(value)) return `[omitted binary payload: bytes=${getBinaryByteLength(value)}]`;
|
|
505
|
+
if (context.depth >= this.maxDepth) return `[omitted nested ${Array.isArray(value) ? "array" : "object"} beyond depth ${this.maxDepth}]`;
|
|
506
|
+
if (Array.isArray(value)) return this.redactArray(value, context);
|
|
507
|
+
return this.redactObject(value, context);
|
|
508
|
+
};
|
|
509
|
+
redactArray = (value, context) => {
|
|
510
|
+
const visibleItems = value.slice(0, this.maxArrayItems).map((item) => this.redactValue(item, { depth: context.depth + 1 }));
|
|
511
|
+
if (value.length <= this.maxArrayItems) return visibleItems;
|
|
512
|
+
return [...visibleItems, `[omitted ${value.length - this.maxArrayItems} array items from tool result]`];
|
|
513
|
+
};
|
|
514
|
+
redactObject = (value, context) => {
|
|
515
|
+
const entries = Object.entries(value);
|
|
516
|
+
const visibleEntries = entries.slice(0, this.maxObjectKeys);
|
|
517
|
+
const output = Object.fromEntries(visibleEntries.map(([key, item]) => [key, this.redactValue(item, { depth: context.depth + 1 })]));
|
|
518
|
+
if (entries.length > this.maxObjectKeys) output._nextclawOmittedKeys = entries.length - this.maxObjectKeys;
|
|
519
|
+
return output;
|
|
520
|
+
};
|
|
521
|
+
redactString = (value) => {
|
|
522
|
+
if (isLargeDataUrl(value)) return `[omitted data URL payload: mime=${readDataUrlMime(value)}, originalChars=${value.length}]`;
|
|
523
|
+
if (isLargeBase64LikeString(value)) return `[omitted base64-like payload: originalChars=${value.length}]`;
|
|
524
|
+
if (value.length <= this.maxStringValueChars) return value;
|
|
525
|
+
return truncateMiddle(value, this.maxStringValueChars);
|
|
526
|
+
};
|
|
527
|
+
attachNotice = (value, params) => {
|
|
528
|
+
const notice = this.buildNotice(params.context, params.originalSerializedChars);
|
|
529
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return {
|
|
530
|
+
...value,
|
|
531
|
+
_nextclawToolResultNotice: notice
|
|
532
|
+
};
|
|
533
|
+
return {
|
|
534
|
+
type: "nextclaw.tool_result_sanitized",
|
|
535
|
+
notice,
|
|
536
|
+
result: value
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
buildEnvelope = (params) => {
|
|
540
|
+
const { context, originalSerializedChars, previewSource } = params;
|
|
541
|
+
return {
|
|
542
|
+
type: "nextclaw.tool_result_truncated",
|
|
543
|
+
notice: this.buildNotice(context, originalSerializedChars),
|
|
544
|
+
originalSerializedChars,
|
|
545
|
+
visibleLimitChars: this.maxModelVisibleChars,
|
|
546
|
+
preview: truncateMiddle(previewSource, Math.max(1e3, this.maxModelVisibleChars - 1e3))
|
|
547
|
+
};
|
|
548
|
+
};
|
|
549
|
+
buildStringNotice = (context, originalChars, preview) => [
|
|
550
|
+
TRUNCATION_MARKER,
|
|
551
|
+
this.buildNotice(context, originalChars),
|
|
552
|
+
"",
|
|
553
|
+
preview
|
|
554
|
+
].join("\n");
|
|
555
|
+
buildNotice = (context, originalSerializedChars) => {
|
|
556
|
+
const { toolCallId: rawToolCallId, toolName: rawToolName } = context;
|
|
557
|
+
return `${TRUNCATION_MARKER}${rawToolName ? ` tool=${rawToolName}` : ""}${rawToolCallId ? ` toolCallId=${rawToolCallId}` : ""} originalSerializedChars=${originalSerializedChars} visibleLimitChars=${this.maxModelVisibleChars}`;
|
|
558
|
+
};
|
|
559
|
+
buildOmittedHistoricalToolResult = (originalChars) => `${TRUNCATION_MARKER} older tool result omitted from active model context originalChars=${originalChars}`;
|
|
560
|
+
boundModelContentText = (value) => {
|
|
561
|
+
if (value.length <= this.maxModelVisibleChars) return value;
|
|
562
|
+
return truncateMiddle(value, this.maxModelVisibleChars);
|
|
563
|
+
};
|
|
564
|
+
};
|
|
565
|
+
const defaultToolResultContentManager = new ToolResultContentManager();
|
|
566
|
+
//#endregion
|
|
150
567
|
//#region src/utils.ts
|
|
151
568
|
const toolSchemaValidator = new AjvPkg({
|
|
152
569
|
allErrors: true,
|
|
@@ -289,7 +706,7 @@ function createToolExecutionFailedResult(params) {
|
|
|
289
706
|
}
|
|
290
707
|
};
|
|
291
708
|
}
|
|
292
|
-
function appendToolRoundToInput(input, reasoning, text, toolResults) {
|
|
709
|
+
function appendToolRoundToInput(input, reasoning, text, toolResults, toolResultContentManager = defaultToolResultContentManager) {
|
|
293
710
|
const assistantMsg = {
|
|
294
711
|
role: "assistant",
|
|
295
712
|
content: text || null,
|
|
@@ -305,17 +722,22 @@ function appendToolRoundToInput(input, reasoning, text, toolResults) {
|
|
|
305
722
|
};
|
|
306
723
|
const toolMsgs = toolResults.map((tr) => ({
|
|
307
724
|
role: "tool",
|
|
308
|
-
content:
|
|
725
|
+
content: toolResultContentManager.toModelContent(tr.result, {
|
|
726
|
+
toolCallId: tr.toolCallId,
|
|
727
|
+
toolName: tr.toolName
|
|
728
|
+
}),
|
|
309
729
|
tool_call_id: tr.toolCallId
|
|
310
730
|
}));
|
|
311
|
-
|
|
731
|
+
const visualMessages = toolResultContentManager.toVisualObservationMessages(toolResults);
|
|
732
|
+
return toolResultContentManager.compactInput({
|
|
312
733
|
...input,
|
|
313
734
|
messages: [
|
|
314
735
|
...input.messages,
|
|
315
736
|
assistantMsg,
|
|
316
|
-
...toolMsgs
|
|
737
|
+
...toolMsgs,
|
|
738
|
+
...visualMessages
|
|
317
739
|
]
|
|
318
|
-
};
|
|
740
|
+
});
|
|
319
741
|
}
|
|
320
742
|
//#endregion
|
|
321
743
|
//#region src/context-builder.ts
|
|
@@ -374,7 +796,8 @@ function messageToOpenAI(msg, options) {
|
|
|
374
796
|
toolCallId: p.toolCallId ?? "",
|
|
375
797
|
toolName: p.toolName,
|
|
376
798
|
args: p.args ?? {},
|
|
377
|
-
result: p.result
|
|
799
|
+
result: p.result,
|
|
800
|
+
resultContentItems: p.resultContentItems
|
|
378
801
|
});
|
|
379
802
|
}
|
|
380
803
|
const text = texts.join("");
|
|
@@ -399,6 +822,14 @@ function messageToOpenAI(msg, options) {
|
|
|
399
822
|
content: typeof t.result === "string" ? t.result : JSON.stringify(t.result),
|
|
400
823
|
tool_call_id: t.toolCallId
|
|
401
824
|
});
|
|
825
|
+
out.push(...defaultToolResultContentManager.toVisualObservationMessages(toolInvocations.map((toolInvocation) => ({
|
|
826
|
+
toolCallId: toolInvocation.toolCallId,
|
|
827
|
+
toolName: toolInvocation.toolName,
|
|
828
|
+
args: isRecord(toolInvocation.args) ? toolInvocation.args : null,
|
|
829
|
+
rawArgsText: typeof toolInvocation.args === "string" ? toolInvocation.args : JSON.stringify(toolInvocation.args ?? {}),
|
|
830
|
+
result: toolInvocation.result,
|
|
831
|
+
contentItems: toolInvocation.resultContentItems
|
|
832
|
+
}))));
|
|
402
833
|
} else out.push({
|
|
403
834
|
role: "assistant",
|
|
404
835
|
content: text,
|
|
@@ -1127,7 +1558,7 @@ var DefaultNcpRoundCollector = class {
|
|
|
1127
1558
|
}
|
|
1128
1559
|
};
|
|
1129
1560
|
//#endregion
|
|
1130
|
-
//#region src/runtime.ts
|
|
1561
|
+
//#region src/agent-runtime.service.ts
|
|
1131
1562
|
var DefaultNcpAgentRuntime = class {
|
|
1132
1563
|
contextBuilder;
|
|
1133
1564
|
llmApi;
|
|
@@ -1135,12 +1566,14 @@ var DefaultNcpAgentRuntime = class {
|
|
|
1135
1566
|
stateManager;
|
|
1136
1567
|
streamEncoder;
|
|
1137
1568
|
reasoningNormalizationMode;
|
|
1569
|
+
toolResultContentManager;
|
|
1138
1570
|
constructor(config) {
|
|
1139
1571
|
this.contextBuilder = config.contextBuilder;
|
|
1140
1572
|
this.llmApi = config.llmApi;
|
|
1141
1573
|
this.toolRegistry = config.toolRegistry;
|
|
1142
1574
|
this.stateManager = config.stateManager;
|
|
1143
1575
|
this.reasoningNormalizationMode = config.reasoningNormalizationMode ?? "off";
|
|
1576
|
+
this.toolResultContentManager = config.toolResultContentManager ?? defaultToolResultContentManager;
|
|
1144
1577
|
this.streamEncoder = config.streamEncoder ?? new DefaultNcpStreamEncoder({ reasoningNormalizationMode: this.reasoningNormalizationMode });
|
|
1145
1578
|
}
|
|
1146
1579
|
run = async function* (input, options) {
|
|
@@ -1194,14 +1627,15 @@ var DefaultNcpAgentRuntime = class {
|
|
|
1194
1627
|
for await (const event of this.streamEncoder.encode(tappedStream, ctx)) yield event;
|
|
1195
1628
|
const toolResults = [];
|
|
1196
1629
|
for (const toolCall of roundCollector.getToolCalls()) {
|
|
1197
|
-
const toolResult = await this.executeToolCall(toolCall);
|
|
1630
|
+
const toolResult = this.toolResultContentManager.normalizeToolCallResult(await this.executeToolCall(toolCall));
|
|
1198
1631
|
toolResults.push(toolResult);
|
|
1199
1632
|
yield {
|
|
1200
1633
|
type: NcpEventType.MessageToolCallResult,
|
|
1201
1634
|
payload: {
|
|
1202
1635
|
sessionId: ctx.sessionId,
|
|
1203
1636
|
toolCallId: toolCall.toolCallId,
|
|
1204
|
-
content: toolResult.result
|
|
1637
|
+
content: toolResult.result,
|
|
1638
|
+
contentItems: toolResult.contentItems
|
|
1205
1639
|
}
|
|
1206
1640
|
};
|
|
1207
1641
|
}
|
|
@@ -1217,7 +1651,7 @@ var DefaultNcpAgentRuntime = class {
|
|
|
1217
1651
|
done = true;
|
|
1218
1652
|
break;
|
|
1219
1653
|
}
|
|
1220
|
-
currentInput = appendToolRoundToInput(currentInput, roundCollector.getReasoning(), roundCollector.getText(), toolResults);
|
|
1654
|
+
currentInput = appendToolRoundToInput(currentInput, roundCollector.getReasoning(), roundCollector.getText(), toolResults, this.toolResultContentManager);
|
|
1221
1655
|
}
|
|
1222
1656
|
};
|
|
1223
1657
|
executeToolCall = async function(toolCall) {
|
|
@@ -1280,4 +1714,4 @@ var DefaultNcpAgentRuntime = class {
|
|
|
1280
1714
|
};
|
|
1281
1715
|
};
|
|
1282
1716
|
//#endregion
|
|
1283
|
-
export { DefaultNcpAgentRuntime, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
|
|
1717
|
+
export { DefaultNcpAgentRuntime, DefaultNcpContextBuilder, DefaultNcpRoundBuffer, DefaultNcpStreamEncoder, DefaultNcpToolRegistry, EchoNcpLLMApi, LocalAssetStore, ToolResultContentManager, appendToolRoundToInput, assertOpenAiFunctionParametersSchema, buildAssetContentPath, buildNcpUserContent, buildOpenAiFunctionTool, createInvalidToolArgumentsResult, createToolExecutionFailedResult, defaultToolResultContentManager, genId, getOpenAiFunctionParametersSchemaIssues, isTextLikeAsset, parseToolArgs, validateToolArgs };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextclaw/ncp-agent-runtime",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16-beta.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Default agent runtime implementation built on NCP interfaces.",
|
|
6
6
|
"type": "module",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
],
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"ajv": "^8.17.1",
|
|
19
|
-
"@nextclaw/ncp": "0.5.
|
|
19
|
+
"@nextclaw/ncp": "0.5.6-beta.0"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"@types/node": "^20.17.6",
|