@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.
- package/README.md +129 -321
- package/dist/contracts/types.d.ts +18 -3
- package/dist/extensions.js +38 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/presentation.d.ts +0 -14
- package/dist/presentation.js +0 -146
- package/dist/resource/resource-impl.js +0 -6
- package/dist/runtime/agent-runtime-adapter.d.ts +2 -0
- package/dist/runtime/agent-runtime-adapter.js +90 -7
- package/dist/runtime/declared-middleware.js +13 -1
- package/dist/runtime/harness.d.ts +1 -0
- package/dist/runtime/harness.js +22 -3
- package/dist/runtime/parsing/output-parsing.d.ts +2 -0
- package/dist/runtime/parsing/output-parsing.js +75 -2
- package/dist/workspace/agent-binding-compiler.js +22 -0
- package/dist/workspace/object-loader.js +51 -21
- package/dist/workspace/resource-compilers.js +11 -4
- package/dist/workspace/support/agent-capabilities.js +2 -2
- package/dist/workspace/support/workspace-ref-utils.d.ts +0 -5
- package/dist/workspace/support/workspace-ref-utils.js +1 -11
- package/dist/workspace/validate.js +6 -0
- package/package.json +4 -10
package/dist/extensions.js
CHANGED
|
@@ -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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.49";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.49";
|
package/dist/presentation.d.ts
CHANGED
|
@@ -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;
|
package/dist/presentation.js
CHANGED
|
@@ -8,152 +8,6 @@ export function escapeHtml(value) {
|
|
|
8
8
|
.replaceAll('"', """)
|
|
9
9
|
.replaceAll("'", "'");
|
|
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
|
|
397
|
-
const
|
|
398
|
-
if (
|
|
399
|
-
return
|
|
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
|
-
...(
|
|
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;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -47,9 +47,9 @@ export class AgentHarnessRuntime {
|
|
|
47
47
|
normalizeInvocationEnvelope(options) {
|
|
48
48
|
const invocation = options.invocation;
|
|
49
49
|
return {
|
|
50
|
-
context: invocation?.context
|
|
51
|
-
state: invocation?.inputs
|
|
52
|
-
files: invocation?.attachments
|
|
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 (
|
|
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));
|