@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 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/runtime.d.ts
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>): NcpLLMApiInput;
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: typeof tr.result === "string" ? tr.result : JSON.stringify(tr.result ?? {}),
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
- return {
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.14",
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.4"
19
+ "@nextclaw/ncp": "0.5.6-beta.0"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/node": "^20.17.6",