@botbotgo/agent-harness 0.0.48 → 0.0.50

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.
@@ -198,6 +198,44 @@ registerToolKind({
198
198
  ];
199
199
  },
200
200
  });
201
+ registerToolKind({
202
+ type: "provider",
203
+ validate(tool) {
204
+ if (!tool.name || !tool.description) {
205
+ throw new Error(`Tool ${tool.id} provider tool requires name and description`);
206
+ }
207
+ const providerTool = typeof tool.config?.providerTool === "object" && tool.config.providerTool
208
+ ? tool.config.providerTool
209
+ : undefined;
210
+ if (typeof providerTool?.provider !== "string" || !providerTool.provider.trim()) {
211
+ throw new Error(`Tool ${tool.id} provider tool must define providerTool.provider`);
212
+ }
213
+ if (typeof providerTool?.tool !== "string" || !providerTool.tool.trim()) {
214
+ throw new Error(`Tool ${tool.id} provider tool must define providerTool.tool`);
215
+ }
216
+ },
217
+ compile(tool) {
218
+ return [
219
+ {
220
+ id: tool.id,
221
+ type: "provider",
222
+ name: tool.name,
223
+ description: tool.description,
224
+ config: tool.config,
225
+ inputSchemaRef: tool.inputSchemaRef,
226
+ bundleRefs: [],
227
+ hitl: tool.hitl
228
+ ? {
229
+ enabled: tool.hitl.enabled,
230
+ allow: tool.hitl.allow ?? ["approve", "edit", "reject"],
231
+ }
232
+ : undefined,
233
+ retryable: tool.retryable,
234
+ runtimeValue: { name: tool.name, description: tool.description, type: "provider" },
235
+ },
236
+ ];
237
+ },
238
+ });
201
239
  registerToolKind({
202
240
  type: "bundle",
203
241
  validate(tool, tools) {
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.47";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.49";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.47";
1
+ export const AGENT_HARNESS_VERSION = "0.0.49";
@@ -1,18 +1,4 @@
1
1
  export declare function escapeHtml(value: string): string;
2
- /** CSS class for anchors that should open in the host app embedded browser (Wallee). */
3
- export declare const WALLEE_OUTPUT_LINK_CLASS = "wallee-output-link";
4
- /** `data-wallee-url` — target URL for the embedded browser (http/https only). */
5
- export declare const WALLEE_BROWSER_URL_ATTR = "data-wallee-url";
6
- export declare function isAllowedWalleeBrowserUrl(url: string): boolean;
7
- /**
8
- * Escape plain text and wrap http(s) URLs in Wallee output anchors (open in host embedded browser).
9
- */
10
- export declare function linkifyPlainTextForWalleeBrowser(text: string): string;
11
- /**
12
- * Like {@link markdownToHtml} but inline http(s) URLs and markdown links `[label](https://…)` render as
13
- * Wallee embedded-browser anchors (`wallee-output-link` + `data-wallee-url`).
14
- */
15
- export declare function markdownToWalleeOutputHtml(markdown: string): string;
16
2
  export declare function markdownToHtml(markdown: string): string;
17
3
  export declare function markdownToConsole(markdown: string): string;
18
4
  export declare function renderTemplate(data: Record<string, unknown>, template: string): string;
@@ -8,152 +8,6 @@ export function escapeHtml(value) {
8
8
  .replaceAll('"', "&quot;")
9
9
  .replaceAll("'", "&#39;");
10
10
  }
11
- /** CSS class for anchors that should open in the host app embedded browser (Wallee). */
12
- export const WALLEE_OUTPUT_LINK_CLASS = "wallee-output-link";
13
- /** `data-wallee-url` — target URL for the embedded browser (http/https only). */
14
- export const WALLEE_BROWSER_URL_ATTR = "data-wallee-url";
15
- export function isAllowedWalleeBrowserUrl(url) {
16
- try {
17
- const u = new URL(url);
18
- return u.protocol === "http:" || u.protocol === "https:";
19
- }
20
- catch {
21
- return false;
22
- }
23
- }
24
- function walleeOutputAnchor(url, labelEscaped) {
25
- return `<a class="${WALLEE_OUTPUT_LINK_CLASS}" ${WALLEE_BROWSER_URL_ATTR}="${escapeHtml(url)}" href="#">${labelEscaped}</a>`;
26
- }
27
- /**
28
- * Escape plain text and wrap http(s) URLs in Wallee output anchors (open in host embedded browser).
29
- */
30
- export function linkifyPlainTextForWalleeBrowser(text) {
31
- const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
32
- const parts = [];
33
- let last = 0;
34
- let m;
35
- while ((m = urlRe.exec(text)) !== null) {
36
- parts.push(escapeHtml(text.slice(last, m.index)));
37
- const raw = m[0];
38
- const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
39
- const rest = raw.slice(trimmed.length);
40
- if (isAllowedWalleeBrowserUrl(trimmed)) {
41
- parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
42
- if (rest) {
43
- parts.push(escapeHtml(rest));
44
- }
45
- }
46
- else {
47
- parts.push(escapeHtml(raw));
48
- }
49
- last = m.index + raw.length;
50
- }
51
- parts.push(escapeHtml(text.slice(last)));
52
- return parts.join("");
53
- }
54
- function applyBasicInlineMarkdown(escaped) {
55
- return escaped
56
- .replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
57
- .replace(/\*(.+?)\*/g, "<em>$1</em>")
58
- .replace(/`([^`]+)`/g, "<code>$1</code>");
59
- }
60
- function linkifyPlainTextSegmentWithWalleeMarkdown(text) {
61
- const mdLinkPattern = /\[([^\]]*)\]\((https?:\/\/[^)\s]+)\)/g;
62
- const segments = [];
63
- let lastIndex = 0;
64
- let m;
65
- while ((m = mdLinkPattern.exec(text)) !== null) {
66
- if (m.index > lastIndex) {
67
- segments.push({ type: "text", text: text.slice(lastIndex, m.index) });
68
- }
69
- segments.push({ type: "mdlink", label: m[1], url: m[2] });
70
- lastIndex = m.index + m[0].length;
71
- }
72
- if (lastIndex < text.length) {
73
- segments.push({ type: "text", text: text.slice(lastIndex) });
74
- }
75
- if (segments.length === 0) {
76
- segments.push({ type: "text", text });
77
- }
78
- return segments
79
- .map((seg) => {
80
- if (seg.type === "mdlink") {
81
- if (!isAllowedWalleeBrowserUrl(seg.url)) {
82
- return `${escapeHtml(seg.label)} (${escapeHtml(seg.url)})`;
83
- }
84
- return walleeOutputAnchor(seg.url, escapeHtml(seg.label));
85
- }
86
- return linkifyBareUrlsWithInlineMarkdown(seg.text);
87
- })
88
- .join("");
89
- }
90
- function linkifyBareUrlsWithInlineMarkdown(text) {
91
- const urlRe = /\bhttps?:\/\/[^\s<>"']+/g;
92
- const parts = [];
93
- let last = 0;
94
- let m;
95
- while ((m = urlRe.exec(text)) !== null) {
96
- parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last, m.index))));
97
- const raw = m[0];
98
- const trimmed = raw.replace(/[.,;:!?)\]]+$/u, "");
99
- const rest = raw.slice(trimmed.length);
100
- if (isAllowedWalleeBrowserUrl(trimmed)) {
101
- parts.push(walleeOutputAnchor(trimmed, escapeHtml(trimmed)));
102
- if (rest) {
103
- parts.push(applyBasicInlineMarkdown(escapeHtml(rest)));
104
- }
105
- }
106
- else {
107
- parts.push(applyBasicInlineMarkdown(escapeHtml(raw)));
108
- }
109
- last = m.index + raw.length;
110
- }
111
- parts.push(applyBasicInlineMarkdown(escapeHtml(text.slice(last))));
112
- return parts.join("");
113
- }
114
- /**
115
- * Like {@link markdownToHtml} but inline http(s) URLs and markdown links `[label](https://…)` render as
116
- * Wallee embedded-browser anchors (`wallee-output-link` + `data-wallee-url`).
117
- */
118
- export function markdownToWalleeOutputHtml(markdown) {
119
- const normalized = markdown.replace(/\r\n/g, "\n");
120
- const blocks = normalized.split(/\n\n+/);
121
- const html = [];
122
- for (const block of blocks) {
123
- const trimmed = block.trim();
124
- if (!trimmed) {
125
- continue;
126
- }
127
- if (trimmed.startsWith("```") && trimmed.endsWith("```")) {
128
- const lines = trimmed.split("\n");
129
- const language = lines[0]?.slice(3).trim();
130
- const code = lines.slice(1, -1).join("\n");
131
- html.push(`<pre class="ah-code"><code${language ? ` data-language="${escapeHtml(language)}"` : ""}>${escapeHtml(code)}</code></pre>`);
132
- continue;
133
- }
134
- if (/^#{1,6}\s/.test(trimmed)) {
135
- const match = trimmed.match(/^(#{1,6})\s+(.*)$/);
136
- const level = match?.[1].length ?? 1;
137
- const content = linkifyPlainTextSegmentWithWalleeMarkdown(match?.[2] ?? trimmed);
138
- html.push(`<h${level}>${content}</h${level}>`);
139
- continue;
140
- }
141
- if (trimmed.split("\n").every((line) => /^[-*]\s+/.test(line))) {
142
- const items = trimmed
143
- .split("\n")
144
- .map((line) => line.replace(/^[-*]\s+/, ""))
145
- .map((line) => `<li>${linkifyPlainTextSegmentWithWalleeMarkdown(line)}</li>`)
146
- .join("");
147
- html.push(`<ul>${items}</ul>`);
148
- continue;
149
- }
150
- html.push(`<p>${trimmed
151
- .split("\n")
152
- .map((line) => linkifyPlainTextSegmentWithWalleeMarkdown(line))
153
- .join("<br />")}</p>`);
154
- }
155
- return html.join("");
156
- }
157
11
  function renderInlineMarkdown(text) {
158
12
  const escaped = escapeHtml(text);
159
13
  return escaped
@@ -20,12 +20,6 @@ function installedResourceProviderEntry() {
20
20
  try {
21
21
  return require.resolve("@botbotgo/agent-harness-resource");
22
22
  }
23
- catch {
24
- // Fall through to the legacy package name for backward compatibility only.
25
- }
26
- try {
27
- return require.resolve("@botbotgo/agent-harness-builtin");
28
- }
29
23
  catch {
30
24
  return null;
31
25
  }
@@ -33,6 +33,8 @@ export declare class AgentRuntimeAdapter {
33
33
  private normalizeInterruptPolicy;
34
34
  private compileInterruptOn;
35
35
  private resolveInterruptOn;
36
+ private resolveFilesystemBackend;
37
+ private resolveLangChainAutomaticMiddleware;
36
38
  private resolveMiddleware;
37
39
  private resolveCheckpointer;
38
40
  private buildRouteSystemPrompt;
@@ -1,13 +1,15 @@
1
1
  import { Command, MemorySaver } from "@langchain/langgraph";
2
2
  import { tool as createLangChainTool } from "@langchain/core/tools";
3
- import { createDeepAgent } from "deepagents";
3
+ import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
4
4
  import { ChatAnthropic } from "@langchain/anthropic";
5
+ import { tools as anthropicProviderTools } from "@langchain/anthropic";
5
6
  import { ChatGoogle } from "@langchain/google";
6
7
  import { ChatOllama } from "@langchain/ollama";
7
8
  import { ChatOpenAI } from "@langchain/openai";
9
+ import { tools as openAIProviderTools } from "@langchain/openai";
8
10
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
9
11
  import { z } from "zod";
10
- import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
12
+ import { extractEmptyAssistantMessageFailure, extractContentBlocks, extractOutputContent, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
11
13
  import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
12
14
  import { wrapToolForExecution } from "./tool-hitl.js";
13
15
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
@@ -112,6 +114,35 @@ function buildToolNameMapping(tools) {
112
114
  }
113
115
  return { originalToModelFacing, modelFacingToOriginal };
114
116
  }
117
+ function hasConfiguredSubagentSupport(binding) {
118
+ const params = getBindingLangChainParams(binding);
119
+ if (!params) {
120
+ return false;
121
+ }
122
+ return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
123
+ }
124
+ function instantiateProviderTool(compiledTool) {
125
+ const providerTool = asRecord(compiledTool.config?.providerTool);
126
+ const provider = typeof providerTool?.provider === "string" ? providerTool.provider.trim().toLowerCase() : "";
127
+ const toolName = typeof providerTool?.tool === "string" ? providerTool.tool.trim() : "";
128
+ const args = asRecord(providerTool?.args) ?? {};
129
+ if (!provider || !toolName) {
130
+ throw new Error(`Provider tool ${compiledTool.id} must define providerTool.provider and providerTool.tool`);
131
+ }
132
+ const registry = provider === "openai"
133
+ ? openAIProviderTools
134
+ : provider === "anthropic"
135
+ ? anthropicProviderTools
136
+ : undefined;
137
+ if (!registry) {
138
+ throw new Error(`Provider tool ${compiledTool.id} uses unsupported provider ${provider}`);
139
+ }
140
+ const factory = registry[toolName];
141
+ if (typeof factory !== "function") {
142
+ throw new Error(`Provider tool ${compiledTool.id} references unknown ${provider} tool ${toolName}`);
143
+ }
144
+ return factory(args);
145
+ }
115
146
  function wrapResolvedToolWithModelFacingName(resolvedTool, modelFacingName) {
116
147
  if (typeof resolvedTool !== "object" || resolvedTool === null) {
117
148
  return resolvedTool;
@@ -393,10 +424,10 @@ export class AgentRuntimeAdapter {
393
424
  resolveTools(tools, binding) {
394
425
  const resolved = this.options.toolResolver ? this.options.toolResolver(tools.map((tool) => tool.id), binding) : [];
395
426
  const toolNameMapping = this.buildToolNameMapping(tools);
396
- return resolved.map((resolvedTool, index) => {
397
- const compiledTool = tools[index];
398
- if (!compiledTool) {
399
- return resolvedTool;
427
+ return tools.flatMap((compiledTool, index) => {
428
+ const resolvedTool = resolved[index] ?? (compiledTool.type === "provider" ? instantiateProviderTool(compiledTool) : undefined);
429
+ if (resolvedTool === undefined) {
430
+ return [];
400
431
  }
401
432
  const wrappedTool = wrapToolForExecution(resolvedTool, compiledTool, binding);
402
433
  const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
@@ -443,14 +474,55 @@ export class AgentRuntimeAdapter {
443
474
  resolveInterruptOn(binding) {
444
475
  return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
445
476
  }
477
+ resolveFilesystemBackend(binding) {
478
+ return new FilesystemBackend({
479
+ rootDir: "/",
480
+ virtualMode: false,
481
+ maxFileSizeMb: 10,
482
+ });
483
+ }
484
+ async resolveLangChainAutomaticMiddleware(binding) {
485
+ const params = getBindingLangChainParams(binding);
486
+ if (!params) {
487
+ return [];
488
+ }
489
+ const automaticMiddleware = [];
490
+ if ((params.skills?.length ?? 0) > 0) {
491
+ automaticMiddleware.push(createSkillsMiddleware({
492
+ backend: this.resolveFilesystemBackend(binding),
493
+ sources: params.skills,
494
+ }));
495
+ }
496
+ if ((params.memory?.length ?? 0) > 0) {
497
+ automaticMiddleware.push(createMemoryMiddleware({
498
+ backend: this.resolveFilesystemBackend(binding),
499
+ sources: params.memory,
500
+ }));
501
+ }
502
+ if (hasConfiguredSubagentSupport(binding)) {
503
+ automaticMiddleware.push(createSubAgentMiddleware({
504
+ defaultModel: (await this.resolveModel(params.model)),
505
+ defaultTools: this.resolveTools(params.tools, binding),
506
+ defaultInterruptOn: getBindingInterruptCompatibilityRules(binding),
507
+ subagents: (await this.resolveSubagents(params.subagents ?? [], binding)),
508
+ generalPurposeAgent: params.generalPurposeAgent,
509
+ taskDescription: params.taskDescription ?? null,
510
+ }));
511
+ }
512
+ return automaticMiddleware;
513
+ }
446
514
  async resolveMiddleware(binding, interruptOn) {
447
515
  const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
448
516
  resolveModel: (model) => this.resolveModel(model),
449
517
  resolveCustom: this.options.declaredMiddlewareResolver,
450
518
  binding,
451
519
  });
520
+ const automaticMiddleware = isLangChainBinding(binding)
521
+ ? await this.resolveLangChainAutomaticMiddleware(binding)
522
+ : [];
452
523
  const middleware = [
453
524
  ...declarativeMiddleware,
525
+ ...automaticMiddleware,
454
526
  ...(this.options.middlewareResolver ? this.options.middlewareResolver(binding) : []),
455
527
  ];
456
528
  if (interruptOn && Object.keys(interruptOn).length > 0) {
@@ -484,6 +556,7 @@ export class AgentRuntimeAdapter {
484
556
  async resolveSubagents(subagents, binding) {
485
557
  return Promise.all(subagents.map(async (subagent) => ({
486
558
  ...subagent,
559
+ ...(subagent.passthrough ?? {}),
487
560
  model: subagent.model ? (await this.resolveModel(subagent.model)) : undefined,
488
561
  tools: subagent.tools ? this.resolveTools(subagent.tools) : undefined,
489
562
  interruptOn: this.compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
@@ -506,6 +579,7 @@ export class AgentRuntimeAdapter {
506
579
  throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
507
580
  }
508
581
  return createAgent({
582
+ ...(params.passthrough ?? {}),
509
583
  model: model,
510
584
  tools: tools,
511
585
  systemPrompt: params.systemPrompt,
@@ -526,6 +600,7 @@ export class AgentRuntimeAdapter {
526
600
  throw new Error(`Agent ${binding.agent.id} has no runnable params`);
527
601
  }
528
602
  const deepAgentConfig = {
603
+ ...(params.passthrough ?? {}),
529
604
  model: (await this.resolveModel(params.model)),
530
605
  tools: this.resolveTools(params.tools, binding),
531
606
  systemPrompt: params.systemPrompt,
@@ -600,6 +675,9 @@ export class AgentRuntimeAdapter {
600
675
  }
601
676
  const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
602
677
  const finalMessageText = sanitizeVisibleText(output);
678
+ const outputContent = extractOutputContent(result);
679
+ const contentBlocks = extractContentBlocks(result);
680
+ const structuredResponse = result.structuredResponse;
603
681
  return {
604
682
  threadId,
605
683
  runId,
@@ -608,8 +686,13 @@ export class AgentRuntimeAdapter {
608
686
  interruptContent,
609
687
  output: finalMessageText,
610
688
  finalMessageText,
689
+ ...(outputContent !== undefined ? { outputContent } : {}),
690
+ ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
691
+ ...(structuredResponse !== undefined ? { structuredResponse } : {}),
611
692
  metadata: {
612
- ...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
693
+ ...(structuredResponse !== undefined ? { structuredResponse } : {}),
694
+ ...(outputContent !== undefined ? { outputContent } : {}),
695
+ ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
613
696
  ...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
614
697
  ...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
615
698
  upstreamResult: result,
@@ -1,4 +1,4 @@
1
- import { anthropicPromptCachingMiddleware, contextEditingMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
1
+ import { anthropicPromptCachingMiddleware, contextEditingMiddleware, humanInTheLoopMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
2
2
  function asMiddlewareConfig(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : null;
4
4
  }
@@ -77,9 +77,21 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
77
77
  case "toolEmulator":
78
78
  resolved.push(toolEmulatorMiddleware(runtimeConfig));
79
79
  break;
80
+ case "humanInTheLoop":
81
+ resolved.push(humanInTheLoopMiddleware(runtimeConfig));
82
+ break;
80
83
  case "openAIModeration":
81
84
  resolved.push(openAIModerationMiddleware(runtimeConfig));
82
85
  break;
86
+ case "pii": {
87
+ const piiType = typeof runtimeConfig.piiType === "string" ? runtimeConfig.piiType : undefined;
88
+ if (!piiType) {
89
+ throw new Error("pii middleware requires piiType");
90
+ }
91
+ const { piiType: _piiType, ...piiOptions } = runtimeConfig;
92
+ resolved.push(piiMiddleware(piiType, piiOptions));
93
+ break;
94
+ }
83
95
  case "anthropicPromptCaching":
84
96
  resolved.push(anthropicPromptCachingMiddleware(runtimeConfig));
85
97
  break;
@@ -71,6 +71,7 @@ export declare class AgentHarnessRuntime {
71
71
  private checkpointRefForState;
72
72
  private finalizeContinuedRun;
73
73
  private emitOutputDeltaAndCreateItem;
74
+ private createContentBlocksItem;
74
75
  private emitRunCreated;
75
76
  private setRunStateAndEmit;
76
77
  private requestApprovalAndEmit;
@@ -47,9 +47,9 @@ export class AgentHarnessRuntime {
47
47
  normalizeInvocationEnvelope(options) {
48
48
  const invocation = options.invocation;
49
49
  return {
50
- context: invocation?.context ?? options.context,
51
- state: invocation?.inputs ?? options.state,
52
- files: invocation?.attachments ?? options.files,
50
+ context: invocation?.context,
51
+ state: invocation?.inputs,
52
+ files: invocation?.attachments,
53
53
  invocation,
54
54
  };
55
55
  }
@@ -525,6 +525,15 @@ export class AgentHarnessRuntime {
525
525
  content,
526
526
  };
527
527
  }
528
+ createContentBlocksItem(threadId, runId, agentId, contentBlocks) {
529
+ return {
530
+ type: "content-blocks",
531
+ threadId,
532
+ runId,
533
+ agentId,
534
+ contentBlocks,
535
+ };
536
+ }
528
537
  async emitRunCreated(threadId, runId, payload) {
529
538
  return this.emit(threadId, runId, 1, "run.created", payload);
530
539
  }
@@ -678,6 +687,10 @@ export class AgentHarnessRuntime {
678
687
  await this.notifyListener(listeners.onChunk, item.content);
679
688
  continue;
680
689
  }
690
+ if (item.type === "content-blocks") {
691
+ await this.notifyListener(listeners.onContentBlocks, item.contentBlocks);
692
+ continue;
693
+ }
681
694
  if (item.type === "reasoning") {
682
695
  await this.notifyListener(listeners.onReasoning, item.content);
683
696
  continue;
@@ -890,6 +903,9 @@ export class AgentHarnessRuntime {
890
903
  }
891
904
  if (!assistantOutput) {
892
905
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
906
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
907
+ yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
908
+ }
893
909
  if (actual.output) {
894
910
  assistantOutput = actual.output;
895
911
  emitted = true;
@@ -949,6 +965,9 @@ export class AgentHarnessRuntime {
949
965
  try {
950
966
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
951
967
  await this.appendAssistantMessage(threadId, runId, actual.output);
968
+ if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
969
+ yield this.createContentBlocksItem(threadId, runId, selectedAgentId, actual.contentBlocks);
970
+ }
952
971
  if (actual.output) {
953
972
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
954
973
  }
@@ -6,6 +6,8 @@ export declare function readTextContent(value: unknown): string;
6
6
  export declare function hasToolCalls(value: unknown): boolean;
7
7
  export declare function extractToolFallbackContext(value: unknown): string;
8
8
  export declare function extractVisibleOutput(value: unknown): string;
9
+ export declare function extractOutputContent(value: unknown): unknown;
10
+ export declare function extractContentBlocks(value: unknown): unknown[];
9
11
  export declare function extractEmptyAssistantMessageFailure(value: unknown): string;
10
12
  export declare function isToolCallParseFailure(error: unknown): boolean;
11
13
  export declare const STRICT_TOOL_JSON_INSTRUCTION = "When calling tools, return only the tool call itself. The arguments must be a pure JSON object with no explanatory text before or after it.";
@@ -166,8 +166,8 @@ function extractMessageContent(message) {
166
166
  if (typeof message !== "object" || !message)
167
167
  return "";
168
168
  const typed = message;
169
- if (typeof typed.content === "string")
170
- return typed.content;
169
+ if (typed.content !== undefined)
170
+ return readTextContent(typed.content);
171
171
  if (typeof typed.kwargs === "object" && typed.kwargs) {
172
172
  return readTextContent(typed.kwargs.content);
173
173
  }
@@ -341,6 +341,79 @@ export function extractVisibleOutput(value) {
341
341
  }
342
342
  return "";
343
343
  }
344
+ function isContentBlock(value) {
345
+ return typeof value === "object" && value !== null && typeof value.type === "string";
346
+ }
347
+ function normalizeContentBlocks(value) {
348
+ if (!Array.isArray(value)) {
349
+ return [];
350
+ }
351
+ return value.filter(isContentBlock).map((block) => ({ ...block }));
352
+ }
353
+ function extractStructuredOutputContent(value) {
354
+ if (typeof value !== "object" || !value)
355
+ return undefined;
356
+ const typed = value;
357
+ if (typed.output && typeof typed.output === "object") {
358
+ const nested = extractStructuredOutputContent(typed.output);
359
+ if (nested !== undefined)
360
+ return nested;
361
+ }
362
+ if (typed.content !== undefined) {
363
+ return typed.content;
364
+ }
365
+ if (!Array.isArray(typed.messages)) {
366
+ return undefined;
367
+ }
368
+ for (let index = typed.messages.length - 1; index >= 0; index -= 1) {
369
+ const message = typed.messages[index];
370
+ if (typeof message !== "object" || !message)
371
+ continue;
372
+ const ids = Array.isArray(message.id)
373
+ ? (message.id.filter((item) => typeof item === "string"))
374
+ : [];
375
+ const typeName = ids.at(-1);
376
+ const runtimeType = typeof message._getType === "function"
377
+ ? message._getType()
378
+ : typeof message.getType === "function"
379
+ ? message.getType()
380
+ : undefined;
381
+ if (!(typeName === "AIMessage" || runtimeType === "ai")) {
382
+ continue;
383
+ }
384
+ if (hasToolCalls(message)) {
385
+ continue;
386
+ }
387
+ const directContent = message.content;
388
+ if (directContent !== undefined) {
389
+ return directContent;
390
+ }
391
+ const kwargs = typeof message.kwargs === "object" && message.kwargs
392
+ ? (message.kwargs)
393
+ : undefined;
394
+ if (kwargs?.content !== undefined) {
395
+ return kwargs.content;
396
+ }
397
+ }
398
+ return undefined;
399
+ }
400
+ export function extractOutputContent(value) {
401
+ const content = extractStructuredOutputContent(value);
402
+ if (content !== undefined) {
403
+ return content;
404
+ }
405
+ return undefined;
406
+ }
407
+ export function extractContentBlocks(value) {
408
+ const outputContent = extractOutputContent(value);
409
+ if (outputContent === undefined) {
410
+ return [];
411
+ }
412
+ if (typeof outputContent === "string") {
413
+ return outputContent.trim() ? [{ type: "text", text: outputContent }] : [];
414
+ }
415
+ return normalizeContentBlocks(outputContent);
416
+ }
344
417
  export function extractEmptyAssistantMessageFailure(value) {
345
418
  if (typeof value !== "object" || !value)
346
419
  return "";
@@ -105,6 +105,9 @@ function buildSubagent(agent, workspaceRoot, models, tools, parentSkills, parent
105
105
  responseFormat: agent.deepAgentConfig?.responseFormat,
106
106
  contextSchema: agent.deepAgentConfig?.contextSchema,
107
107
  middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
108
+ passthrough: typeof agent.deepAgentConfig?.passthrough === "object" && agent.deepAgentConfig.passthrough
109
+ ? { ...agent.deepAgentConfig.passthrough }
110
+ : undefined,
108
111
  };
109
112
  }
110
113
  function resolveDirectPrompt(agent) {
@@ -243,6 +246,22 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
243
246
  responseFormat: agent.langchainAgentConfig?.responseFormat,
244
247
  contextSchema: agent.langchainAgentConfig?.contextSchema,
245
248
  middleware: compileMiddlewareConfigs(agent.langchainAgentConfig?.middleware, models, agent.id),
249
+ passthrough: typeof agent.langchainAgentConfig?.passthrough === "object" && agent.langchainAgentConfig.passthrough
250
+ ? { ...agent.langchainAgentConfig.passthrough }
251
+ : undefined,
252
+ subagents: agent.subagentRefs.map((ref) => {
253
+ const subagent = agents.get(resolveRefId(ref));
254
+ if (!subagent) {
255
+ throw new Error(`Missing subagent ${ref} for agent ${agent.id}`);
256
+ }
257
+ return buildSubagent(subagent, workspaceRoot, models, tools, compiledAgentSkills, compiledAgentModel, compiledAgentMemory);
258
+ }),
259
+ memory: compiledAgentMemory,
260
+ skills: compiledAgentSkills,
261
+ generalPurposeAgent: typeof agent.langchainAgentConfig?.generalPurposeAgent === "boolean" ? agent.langchainAgentConfig.generalPurposeAgent : undefined,
262
+ taskDescription: typeof agent.langchainAgentConfig?.taskDescription === "string" && agent.langchainAgentConfig.taskDescription.trim()
263
+ ? agent.langchainAgentConfig.taskDescription
264
+ : undefined,
246
265
  includeAgentName: agent.langchainAgentConfig?.includeAgentName === "inline" ? "inline" : undefined,
247
266
  version: agent.langchainAgentConfig?.version === "v1" || agent.langchainAgentConfig?.version === "v2"
248
267
  ? agent.langchainAgentConfig.version
@@ -269,6 +288,9 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
269
288
  responseFormat: agent.deepAgentConfig?.responseFormat,
270
289
  contextSchema: agent.deepAgentConfig?.contextSchema,
271
290
  middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
291
+ passthrough: typeof agent.deepAgentConfig?.passthrough === "object" && agent.deepAgentConfig.passthrough
292
+ ? { ...agent.deepAgentConfig.passthrough }
293
+ : undefined,
272
294
  description: agent.description,
273
295
  subagents: agent.subagentRefs.map((ref) => {
274
296
  const subagent = agents.get(resolveRefId(ref));