@botbotgo/agent-harness 0.0.314 → 0.0.315

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.
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.313";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.314";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.313";
1
+ export const AGENT_HARNESS_VERSION = "0.0.314";
@@ -22,6 +22,7 @@ export declare function executeRequestInvocation(options: {
22
22
  state?: Record<string, unknown>;
23
23
  files?: Record<string, unknown>;
24
24
  memoryContext?: string;
25
+ toolRuntimeContext?: Record<string, unknown>;
25
26
  }) => Promise<Map<string, ExecutableTool>>;
26
27
  callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
27
28
  }): Promise<RequestResult>;
@@ -34,6 +34,7 @@ export async function executeRequestInvocation(options) {
34
34
  executableTools,
35
35
  builtinExecutableTools: builtinExecutableTools,
36
36
  callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
37
+ toolRuntimeContext: invokeOptions.toolRuntimeContext,
37
38
  });
38
39
  const result = localOrUpstreamInvocation.result;
39
40
  const executedToolResults = [...localOrUpstreamInvocation.executedToolResults];
@@ -16,6 +16,7 @@ export declare function invokeRuntimeWithLocalTools(options: {
16
16
  executableTools: Map<string, ExecutableTool>;
17
17
  builtinExecutableTools: Map<string, ExecutableTool>;
18
18
  callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
19
+ toolRuntimeContext?: Record<string, unknown>;
19
20
  }): Promise<{
20
21
  result: Record<string, unknown>;
21
22
  executedToolResults: ExecutedToolResult[];
@@ -14,5 +14,6 @@ export async function invokeRuntimeWithLocalTools(options) {
14
14
  executableTools: options.executableTools,
15
15
  builtinExecutableTools: options.builtinExecutableTools,
16
16
  callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
17
+ toolRuntimeContext: options.toolRuntimeContext,
17
18
  });
18
19
  }
@@ -14,10 +14,11 @@ type LocalToolInvocationParams = {
14
14
  executableTools: Map<string, ExecutableTool>;
15
15
  builtinExecutableTools: Map<string, ExecutableTool>;
16
16
  callRuntimeWithToolParseRecovery: (request: unknown) => Promise<Record<string, unknown>>;
17
+ toolRuntimeContext?: Record<string, unknown>;
17
18
  };
18
19
  type LocalToolInvocationResult = {
19
20
  result: Record<string, unknown>;
20
21
  executedToolResults: ExecutedToolResult[];
21
22
  };
22
- export declare function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, }: LocalToolInvocationParams): Promise<LocalToolInvocationResult>;
23
+ export declare function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, toolRuntimeContext, }: LocalToolInvocationParams): Promise<LocalToolInvocationResult>;
23
24
  export {};
@@ -3,6 +3,7 @@ import { createModelFacingToolNameLookupCandidates, resolveModelFacingToolName }
3
3
  import { canReplayToolCallsLocally } from "./tool/tool-replay.js";
4
4
  import { extractToolCallsFromResult, normalizeToolArgsForSchema, stringifyToolOutput } from "./tool/tool-arguments.js";
5
5
  import { extractMemoryCandidatesFromToolOutput } from "../harness/system/runtime-memory-candidates.js";
6
+ import { maybePersistLargeToolOutput } from "./tool/tool-output-artifacts.js";
6
7
  const TOOL_FOLLOW_UP_INSTRUCTION = "One or more tool results are already available in this conversation. Answer the user's current request directly from the existing context and tool results. Do not ask the user to repeat inputs that are already present above.";
7
8
  function extractLatestUserInput(request) {
8
9
  const typedRequest = request;
@@ -19,7 +20,7 @@ function extractLatestUserInput(request) {
19
20
  }
20
21
  return undefined;
21
22
  }
22
- export async function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, }) {
23
+ export async function runLocalToolInvocationLoop({ binding, request, primaryTools, toolNameMapping, executableTools, builtinExecutableTools, callRuntimeWithToolParseRecovery, toolRuntimeContext, }) {
23
24
  const executedToolResults = [];
24
25
  let activeRequest = request;
25
26
  let currentMessages = Array.isArray(activeRequest.messages) ? [...activeRequest.messages] : [];
@@ -74,17 +75,24 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
74
75
  const normalizedArgs = normalizeToolArgsForSchema(toolCall.args, activeExecutable.schema, toolCall.rawArgsInput, {
75
76
  latestUserInput,
76
77
  });
77
- const toolResult = await activeExecutable.invoke(normalizedArgs);
78
+ const toolResult = toolRuntimeContext
79
+ ? await activeExecutable.invoke(normalizedArgs, { toolRuntimeContext })
80
+ : await activeExecutable.invoke(normalizedArgs);
78
81
  const memoryCandidates = compiledTool ? extractMemoryCandidatesFromToolOutput(compiledTool, toolResult) : [];
79
- executedToolResults.push({
82
+ const safeToolResult = await maybePersistLargeToolOutput({
80
83
  toolName: activeExecutable.name,
81
84
  output: toolResult,
85
+ toolRuntimeContext: toolRuntimeContext,
86
+ });
87
+ executedToolResults.push({
88
+ toolName: activeExecutable.name,
89
+ output: safeToolResult,
82
90
  ...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
83
91
  });
84
92
  nextMessages.push(new ToolMessage({
85
93
  name: activeExecutable.name,
86
94
  tool_call_id: toolCall.id ?? `tool-${iteration + 1}-${toolIndex + 1}`,
87
- content: stringifyToolOutput(toolResult),
95
+ content: stringifyToolOutput(safeToolResult),
88
96
  }));
89
97
  }
90
98
  currentMessages = nextMessages;
@@ -73,16 +73,19 @@ export declare function resolveBuiltinMiddlewareTools(input: {
73
73
  context?: Record<string, unknown>;
74
74
  state?: Record<string, unknown>;
75
75
  files?: Record<string, unknown>;
76
+ toolRuntimeContext?: Record<string, unknown>;
76
77
  };
77
78
  resolveBuiltinMiddlewareBackend: (binding: CompiledAgentBinding, options?: {
78
79
  context?: Record<string, unknown>;
79
80
  state?: Record<string, unknown>;
80
81
  files?: Record<string, unknown>;
82
+ toolRuntimeContext?: Record<string, unknown>;
81
83
  }) => unknown;
82
84
  invokeBuiltinTaskTool: (binding: CompiledAgentBinding, toolInput: unknown, options?: {
83
85
  context?: Record<string, unknown>;
84
86
  state?: Record<string, unknown>;
85
87
  files?: Record<string, unknown>;
88
+ toolRuntimeContext?: Record<string, unknown>;
86
89
  }) => Promise<unknown>;
87
90
  }): Promise<Map<string, ExecutableTool>>;
88
91
  export declare function materializeAutomaticSummarizationMiddleware(input: {
@@ -182,6 +182,7 @@ export async function resolveBuiltinMiddlewareTools(input) {
182
182
  return createBuiltinMiddlewareTools(backend, {
183
183
  includeTaskTool: isDeepAgentBinding(input.binding),
184
184
  workspaceRoot: input.binding.harnessRuntime.workspaceRoot,
185
+ toolRuntimeContext: input.options?.toolRuntimeContext,
185
186
  invokeTaskTool: isDeepAgentBinding(input.binding)
186
187
  ? async (toolInput) => input.invokeBuiltinTaskTool(input.binding, toolInput, input.options)
187
188
  : undefined,
@@ -250,4 +250,5 @@ export declare function createBuiltinMiddlewareTools(backend: BuiltinMiddlewareB
250
250
  includeTaskTool: boolean;
251
251
  invokeTaskTool?: (input: unknown) => Promise<unknown>;
252
252
  workspaceRoot?: string;
253
+ toolRuntimeContext?: Record<string, unknown>;
253
254
  }): Promise<Map<string, BuiltinExecutableTool>>;
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { isSandboxBackend } from "deepagents";
4
4
  import { isRecord } from "../../../utils/object.js";
5
5
  import { formatBuiltinTodoSnapshot, isLowSignalTodoContent, summarizeBuiltinWriteTodosArgs, truncateLines } from "../runtime-adapter-support.js";
6
+ import { maybePersistLargeToolOutput, resolveToolRuntimeContext } from "./tool-output-artifacts.js";
6
7
  export const BUILTIN_MIDDLEWARE_TOOL_DESCRIPTORS = [
7
8
  { name: "write_todos", description: "Create and update the runtime todo board for multi-step work." },
8
9
  { name: "read_todos", description: "Read the current runtime todo board." },
@@ -159,6 +160,11 @@ export async function createBuiltinMiddlewareTools(backend, options) {
159
160
  blocked: 0,
160
161
  },
161
162
  };
163
+ const finalizeOutput = async (toolName, output, toolConfig) => maybePersistLargeToolOutput({
164
+ toolName,
165
+ output,
166
+ toolRuntimeContext: resolveToolRuntimeContext(toolConfig, options.toolRuntimeContext),
167
+ });
162
168
  tools.set("write_todos", {
163
169
  name: "write_todos",
164
170
  description: "Create and update the runtime todo board for multi-step work.",
@@ -200,11 +206,11 @@ export async function createBuiltinMiddlewareTools(backend, options) {
200
206
  name: "ls",
201
207
  description: "List files in a directory.",
202
208
  schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
203
- invoke: async (input) => {
209
+ invoke: async (input, toolConfig) => {
204
210
  const targetPath = normalizeWorkspacePathOrThrow(pathScopedBackend, isRecord(input) && typeof input.path === "string" ? input.path : "/");
205
211
  const shallowListing = await tryExecuteShallowDirectoryListing(pathScopedBackend, targetPath);
206
212
  if (typeof shallowListing === "string") {
207
- return shallowListing.length > 0 ? shallowListing : `No files found in ${targetPath}`;
213
+ return finalizeOutput("ls", shallowListing.length > 0 ? shallowListing : `No files found in ${targetPath}`, toolConfig);
208
214
  }
209
215
  const legacyInfos = (await Promise.resolve(backend.lsInfo?.(targetPath))) ?? [];
210
216
  const infos = legacyInfos.length > 0
@@ -217,14 +223,14 @@ export async function createBuiltinMiddlewareTools(backend, options) {
217
223
  if (infos.length === 0) {
218
224
  return `No files found in ${targetPath}`;
219
225
  }
220
- return truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`));
226
+ return finalizeOutput("ls", truncateLines(infos.map((info) => info.is_dir ? `${info.path} (directory)` : `${info.path}${info.size ? ` (${info.size} bytes)` : ""}`)), toolConfig);
221
227
  },
222
228
  });
223
229
  tools.set("list_files", {
224
230
  name: "list_files",
225
231
  description: "List files in a directory.",
226
232
  schema: z.object({ path: z.string().optional().default("/") }).passthrough(),
227
- invoke: async (input) => tools.get("ls").invoke(input),
233
+ invoke: async (input, toolConfig) => tools.get("ls").invoke(input, toolConfig),
228
234
  });
229
235
  tools.set("read_file", {
230
236
  name: "read_file",
@@ -352,7 +358,7 @@ export async function createBuiltinMiddlewareTools(backend, options) {
352
358
  name: "execute",
353
359
  description: "Run a shell command in the workspace sandbox.",
354
360
  schema: z.object({ command: z.string() }).passthrough(),
355
- invoke: async (input) => {
361
+ invoke: async (input, toolConfig) => {
356
362
  if (!isSandboxBackend(backend) || typeof backend.execute !== "function") {
357
363
  return "Error: Execution not available. This agent's backend does not support command execution (SandboxBackendProtocol).";
358
364
  }
@@ -365,29 +371,29 @@ export async function createBuiltinMiddlewareTools(backend, options) {
365
371
  if (result.truncated) {
366
372
  parts.push("\n[Output was truncated due to size limits]");
367
373
  }
368
- return parts.join("");
374
+ return finalizeOutput("execute", parts.join(""), toolConfig);
369
375
  },
370
376
  });
371
377
  tools.set("run_command", {
372
378
  name: "run_command",
373
379
  description: "Run a shell command in the workspace sandbox.",
374
380
  schema: z.object({ command: z.string() }).passthrough(),
375
- invoke: async (input) => tools.get("execute").invoke(input),
381
+ invoke: async (input, toolConfig) => tools.get("execute").invoke(input, toolConfig),
376
382
  });
377
383
  tools.set("fetch_url", {
378
384
  name: "fetch_url",
379
385
  description: "Fetch a URL and return the response body.",
380
386
  schema: z.object({ url: z.string() }).passthrough(),
381
- invoke: async (input) => {
387
+ invoke: async (input, toolConfig) => {
382
388
  if (typeof backend.fetchUrl !== "function") {
383
389
  return notAvailable("fetch_url", "URL fetching");
384
390
  }
385
391
  const typed = isRecord(input) ? input : {};
386
392
  const result = await Promise.resolve(backend.fetchUrl(typeof typed.url === "string" ? typed.url : ""));
387
393
  if (typeof result === "string") {
388
- return result;
394
+ return finalizeOutput("fetch_url", result, toolConfig);
389
395
  }
390
- return formatHttpResponse(result);
396
+ return finalizeOutput("fetch_url", formatHttpResponse(result), toolConfig);
391
397
  },
392
398
  });
393
399
  tools.set("http_request", {
@@ -399,7 +405,7 @@ export async function createBuiltinMiddlewareTools(backend, options) {
399
405
  headers: z.record(z.string(), z.string()).optional(),
400
406
  body: z.string().optional(),
401
407
  }).passthrough(),
402
- invoke: async (input) => {
408
+ invoke: async (input, toolConfig) => {
403
409
  if (typeof backend.httpRequest !== "function") {
404
410
  return notAvailable("http_request", "structured HTTP requests");
405
411
  }
@@ -413,9 +419,9 @@ export async function createBuiltinMiddlewareTools(backend, options) {
413
419
  body: typeof typed.body === "string" ? typed.body : undefined,
414
420
  }));
415
421
  if (typeof result === "string") {
416
- return result;
422
+ return finalizeOutput("http_request", result, toolConfig);
417
423
  }
418
- return formatHttpResponse(result);
424
+ return finalizeOutput("http_request", formatHttpResponse(result), toolConfig);
419
425
  },
420
426
  });
421
427
  tools.set("send_message", {
@@ -0,0 +1,35 @@
1
+ type ArtifactCreateInput = {
2
+ sessionId: string;
3
+ requestId: string;
4
+ kind: string;
5
+ path: string;
6
+ content: unknown;
7
+ artifactId?: string;
8
+ createdAt?: string;
9
+ };
10
+ type ArtifactCreateResult = {
11
+ artifactId: string;
12
+ kind: string;
13
+ path: string;
14
+ createdAt: string;
15
+ };
16
+ type ToolRuntimeContext = {
17
+ runtime?: {
18
+ current?: {
19
+ sessionId?: string;
20
+ requestId?: string;
21
+ };
22
+ artifacts?: {
23
+ create?: (input: ArtifactCreateInput) => Promise<ArtifactCreateResult> | ArtifactCreateResult;
24
+ };
25
+ };
26
+ };
27
+ export declare function resolveToolRuntimeContext(toolConfig?: Record<string, unknown>, fallback?: Record<string, unknown>): ToolRuntimeContext | undefined;
28
+ export declare function maybePersistLargeToolOutput(input: {
29
+ toolName: string;
30
+ output: unknown;
31
+ toolRuntimeContext?: Record<string, unknown>;
32
+ maxInlineChars?: number;
33
+ previewChars?: number;
34
+ }): Promise<unknown>;
35
+ export {};
@@ -0,0 +1,88 @@
1
+ function asRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value)
3
+ ? value
4
+ : undefined;
5
+ }
6
+ function toSerializedToolOutput(output) {
7
+ if (typeof output === "string") {
8
+ return output;
9
+ }
10
+ try {
11
+ return JSON.stringify(output, null, 2);
12
+ }
13
+ catch {
14
+ return String(output);
15
+ }
16
+ }
17
+ function truncatePreview(text, maxChars) {
18
+ if (text.length <= maxChars) {
19
+ return text;
20
+ }
21
+ return `${text.slice(0, Math.max(0, maxChars - 15))}\n...[truncated]`;
22
+ }
23
+ function sanitizeArtifactToolName(toolName) {
24
+ return toolName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "tool";
25
+ }
26
+ export function resolveToolRuntimeContext(toolConfig, fallback) {
27
+ const dynamic = asRecord(toolConfig?.toolRuntimeContext);
28
+ const configurable = asRecord(toolConfig?.configurable);
29
+ const fallbackRuntime = asRecord(asRecord(fallback)?.runtime);
30
+ const fallbackCurrent = asRecord(fallbackRuntime?.current);
31
+ const resolved = (dynamic ?? fallback);
32
+ const runtime = asRecord(resolved?.runtime);
33
+ if (!runtime) {
34
+ return resolved;
35
+ }
36
+ return {
37
+ ...resolved,
38
+ runtime: {
39
+ ...runtime,
40
+ current: {
41
+ ...fallbackCurrent,
42
+ ...asRecord(runtime.current),
43
+ ...(typeof configurable?.thread_id === "string" ? { sessionId: configurable.thread_id } : {}),
44
+ ...(typeof configurable?.request_id === "string" ? { requestId: configurable.request_id } : {}),
45
+ },
46
+ },
47
+ };
48
+ }
49
+ export async function maybePersistLargeToolOutput(input) {
50
+ const maxInlineChars = input.maxInlineChars ?? 12_000;
51
+ const previewChars = input.previewChars ?? 2_000;
52
+ const serialized = toSerializedToolOutput(input.output);
53
+ if (serialized.length <= maxInlineChars) {
54
+ return input.output;
55
+ }
56
+ const runtimeContext = input.toolRuntimeContext;
57
+ const runtime = runtimeContext?.runtime;
58
+ const sessionId = runtime?.current?.sessionId;
59
+ const requestId = runtime?.current?.requestId;
60
+ const createArtifact = runtime?.artifacts?.create;
61
+ const createdAt = new Date().toISOString();
62
+ const safeToolName = sanitizeArtifactToolName(input.toolName);
63
+ let artifact;
64
+ if (typeof createArtifact === "function" && typeof sessionId === "string" && typeof requestId === "string") {
65
+ artifact = await Promise.resolve(createArtifact({
66
+ sessionId,
67
+ requestId,
68
+ kind: "tool-output",
69
+ path: `artifacts/tool-output-${safeToolName}-${Date.now()}.json`,
70
+ createdAt,
71
+ content: {
72
+ toolName: input.toolName,
73
+ createdAt,
74
+ output: input.output,
75
+ },
76
+ }));
77
+ }
78
+ return {
79
+ truncated: true,
80
+ toolName: input.toolName,
81
+ message: artifact
82
+ ? "Full tool output was stored as a request artifact because it exceeded the inline size limit."
83
+ : "Tool output exceeded the inline size limit and was truncated for runtime stability.",
84
+ originalSizeChars: serialized.length,
85
+ preview: truncatePreview(serialized, previewChars),
86
+ ...(artifact ? { artifact } : {}),
87
+ };
88
+ }
@@ -302,7 +302,12 @@ export class AgentRuntimeAdapter {
302
302
  const resolvedModel = await this.resolveModel(primaryModel);
303
303
  const resolvedTools = this.resolveTools(primaryTools, binding);
304
304
  const builtinMiddlewareTools = materializeModelExposedBuiltinMiddlewareTools({
305
- builtinTools: await this.resolveBuiltinMiddlewareTools(binding, { sessionId: options.sessionId ?? options.legacySessionId }),
305
+ builtinTools: await this.resolveBuiltinMiddlewareTools(binding, {
306
+ sessionId: options.sessionId ?? options.legacySessionId,
307
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
308
+ sessionId: options.sessionId ?? options.legacySessionId,
309
+ }),
310
+ }),
306
311
  explicitToolNames: primaryTools.map((tool) => tool.name),
307
312
  });
308
313
  const resolvedMiddleware = await this.resolveMiddleware(binding, interruptOn, { sessionId: options.sessionId ?? options.legacySessionId });
@@ -330,9 +335,9 @@ export class AgentRuntimeAdapter {
330
335
  if (isLangChainBinding(binding)) {
331
336
  return this.createLangChainRunnable(binding, { sessionId: options.sessionId ?? options.legacySessionId });
332
337
  }
333
- return this.createDeepAgentRunnable(binding);
338
+ return this.createDeepAgentRunnable(binding, { sessionId: options.sessionId ?? options.legacySessionId });
334
339
  }
335
- async createDeepAgentRunnable(binding) {
340
+ async createDeepAgentRunnable(binding, options = {}) {
336
341
  const executionKind = getBindingExecutionKind(binding);
337
342
  const primaryModel = getBindingPrimaryModel(binding);
338
343
  const primaryTools = getBindingPrimaryTools(binding);
@@ -342,7 +347,12 @@ export class AgentRuntimeAdapter {
342
347
  const resolvedModel = await this.resolveModel(primaryModel);
343
348
  const resolvedTools = this.resolveTools(primaryTools, binding);
344
349
  const builtinMiddlewareTools = materializeModelExposedBuiltinMiddlewareTools({
345
- builtinTools: await this.resolveBuiltinMiddlewareTools(binding),
350
+ builtinTools: await this.resolveBuiltinMiddlewareTools(binding, {
351
+ sessionId: options.sessionId ?? options.legacySessionId,
352
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
353
+ sessionId: options.sessionId ?? options.legacySessionId,
354
+ }),
355
+ }),
346
356
  explicitToolNames: primaryTools.map((tool) => tool.name),
347
357
  });
348
358
  const resolvedMiddleware = await this.resolveMiddleware(binding);
@@ -174,6 +174,7 @@ export class AgentHarnessRuntime {
174
174
  artifacts: {
175
175
  list: (sessionId, requestId) => this.listRequestArtifacts(sessionId, requestId),
176
176
  read: (sessionId, requestId, artifactPath) => this.readRequestArtifact(sessionId, requestId, artifactPath),
177
+ create: (artifact) => this.recordArtifact(artifact),
177
178
  },
178
179
  schedules: {
179
180
  manage: (options) => this.manageSchedule(options),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.314",
3
+ "version": "0.0.315",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",