@botbotgo/agent-harness 0.0.49 → 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 +11 -5
- package/dist/contracts/types.d.ts +13 -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/runtime/agent-runtime-adapter.js +13 -2
- package/dist/runtime/declared-middleware.js +4 -4
- package/dist/runtime/harness.d.ts +1 -0
- package/dist/runtime/harness.js +19 -0
- 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 +9 -0
- package/dist/workspace/object-loader.js +43 -0
- package/dist/workspace/validate.js +3 -0
- package/package.json +4 -10
package/README.md
CHANGED
|
@@ -81,10 +81,12 @@ try {
|
|
|
81
81
|
- YAML-defined host routing and runtime policy
|
|
82
82
|
- LangChain v1 and DeepAgents backend adaptation
|
|
83
83
|
- Auto-discovered local tools and SKILL packages
|
|
84
|
+
- provider-native tools, MCP tools, and workspace-local tool modules
|
|
85
|
+
- persisted threads, runs, approvals, lifecycle events, and queued runs
|
|
86
|
+
- runtime-managed recovery and checkpoint maintenance
|
|
87
|
+
- structured output and multimodal content preservation in run results
|
|
84
88
|
- MCP bridge support for agent-declared MCP servers
|
|
85
89
|
- MCP server support for exposing harness tools outward
|
|
86
|
-
- Persisted threads, runs, approvals, lifecycle events, and queued runs
|
|
87
|
-
- Runtime-managed recovery and checkpoint maintenance
|
|
88
90
|
|
|
89
91
|
## How To Use
|
|
90
92
|
|
|
@@ -120,7 +122,7 @@ const result = await run(runtime, {
|
|
|
120
122
|
});
|
|
121
123
|
```
|
|
122
124
|
|
|
123
|
-
|
|
125
|
+
`run(runtime, { ... })` creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and a simple text `output`. When upstream returns richer output, the runtime also preserves `outputContent`, `contentBlocks`, and `structuredResponse` without making the basic API larger.
|
|
124
126
|
|
|
125
127
|
Use `invocation` as the runtime-facing request envelope:
|
|
126
128
|
|
|
@@ -149,6 +151,9 @@ const result = await run(runtime, {
|
|
|
149
151
|
onChunk(chunk) {
|
|
150
152
|
process.stdout.write(chunk);
|
|
151
153
|
},
|
|
154
|
+
onContentBlocks(blocks) {
|
|
155
|
+
console.log(blocks);
|
|
156
|
+
},
|
|
152
157
|
onEvent(event) {
|
|
153
158
|
console.log(event.eventType, event.payload);
|
|
154
159
|
},
|
|
@@ -244,8 +249,6 @@ Core workspace files:
|
|
|
244
249
|
- `resources/tools/`
|
|
245
250
|
- `resources/skills/`
|
|
246
251
|
|
|
247
|
-
### Client-Configurable YAML Reference
|
|
248
|
-
|
|
249
252
|
There are three configuration layers:
|
|
250
253
|
|
|
251
254
|
- runtime policy in `config/workspace.yaml`
|
|
@@ -406,6 +409,8 @@ Client-configurable agent fields include:
|
|
|
406
409
|
- `spec.responseFormat`
|
|
407
410
|
- `spec.contextSchema`
|
|
408
411
|
|
|
412
|
+
For backend-specific agent options, prefer passing the upstream concept directly in YAML. The loader keeps a small stable product shape, but it also preserves agent-level passthrough fields so new LangChain v1 or DeepAgents parameters can flow into adapters without expanding the public API surface.
|
|
413
|
+
|
|
409
414
|
### `resources/`
|
|
410
415
|
|
|
411
416
|
Use `resources/` for executable local extensions:
|
|
@@ -426,6 +431,7 @@ SKILL packages are discovered from `resources/skills/` and attached to agents th
|
|
|
426
431
|
- upstream LangChain v1 and DeepAgents concepts should be expressed as directly as possible in YAML
|
|
427
432
|
- recovery, approvals, threads, runs, and events are runtime concepts, not backend-specific escape hatches
|
|
428
433
|
- backend implementation details should stay internal unless product requirements force exposure
|
|
434
|
+
- application-level orchestration and lifecycle management stays in the harness
|
|
429
435
|
|
|
430
436
|
In short: `agent-harness` is a public runtime contract generic enough to survive backend changes, while the deep execution semantics stay upstream.
|
|
431
437
|
|
|
@@ -96,6 +96,7 @@ export type LangChainAgentParams = {
|
|
|
96
96
|
responseFormat?: unknown;
|
|
97
97
|
contextSchema?: unknown;
|
|
98
98
|
middleware?: Array<Record<string, unknown>>;
|
|
99
|
+
passthrough?: Record<string, unknown>;
|
|
99
100
|
subagents?: CompiledSubAgent[];
|
|
100
101
|
memory?: string[];
|
|
101
102
|
skills?: string[];
|
|
@@ -118,6 +119,7 @@ export type CompiledSubAgent = {
|
|
|
118
119
|
responseFormat?: unknown;
|
|
119
120
|
contextSchema?: unknown;
|
|
120
121
|
middleware?: Array<Record<string, unknown>>;
|
|
122
|
+
passthrough?: Record<string, unknown>;
|
|
121
123
|
};
|
|
122
124
|
export type DeepAgentParams = {
|
|
123
125
|
model: CompiledModel;
|
|
@@ -126,6 +128,7 @@ export type DeepAgentParams = {
|
|
|
126
128
|
responseFormat?: unknown;
|
|
127
129
|
contextSchema?: unknown;
|
|
128
130
|
middleware?: Array<Record<string, unknown>>;
|
|
131
|
+
passthrough?: Record<string, unknown>;
|
|
129
132
|
description: string;
|
|
130
133
|
subagents: CompiledSubAgent[];
|
|
131
134
|
interruptOn?: Record<string, boolean | object>;
|
|
@@ -257,6 +260,9 @@ export type RunResult = {
|
|
|
257
260
|
state: RunState;
|
|
258
261
|
output: string;
|
|
259
262
|
finalMessageText?: string;
|
|
263
|
+
outputContent?: unknown;
|
|
264
|
+
contentBlocks?: unknown[];
|
|
265
|
+
structuredResponse?: unknown;
|
|
260
266
|
interruptContent?: string;
|
|
261
267
|
agentId?: string;
|
|
262
268
|
approvalId?: string;
|
|
@@ -267,6 +273,7 @@ export type RunResult = {
|
|
|
267
273
|
};
|
|
268
274
|
export type RunListeners = {
|
|
269
275
|
onChunk?: (chunk: string) => void | Promise<void>;
|
|
276
|
+
onContentBlocks?: (blocks: unknown[]) => void | Promise<void>;
|
|
270
277
|
onEvent?: (event: HarnessEvent) => void | Promise<void>;
|
|
271
278
|
onReasoning?: (chunk: string) => void | Promise<void>;
|
|
272
279
|
onStep?: (step: string) => void | Promise<void>;
|
|
@@ -318,6 +325,12 @@ export type HarnessStreamItem = {
|
|
|
318
325
|
runId: string;
|
|
319
326
|
agentId: string;
|
|
320
327
|
content: string;
|
|
328
|
+
} | {
|
|
329
|
+
type: "content-blocks";
|
|
330
|
+
threadId: string;
|
|
331
|
+
runId: string;
|
|
332
|
+
agentId: string;
|
|
333
|
+
contentBlocks: unknown[];
|
|
321
334
|
} | {
|
|
322
335
|
type: "reasoning";
|
|
323
336
|
threadId: string;
|
|
@@ -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
|
|
@@ -9,7 +9,7 @@ import { ChatOpenAI } from "@langchain/openai";
|
|
|
9
9
|
import { tools as openAIProviderTools } from "@langchain/openai";
|
|
10
10
|
import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
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";
|
|
13
13
|
import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractStateStreamOutput, extractVisibleStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
|
|
14
14
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
15
15
|
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
@@ -556,6 +556,7 @@ export class AgentRuntimeAdapter {
|
|
|
556
556
|
async resolveSubagents(subagents, binding) {
|
|
557
557
|
return Promise.all(subagents.map(async (subagent) => ({
|
|
558
558
|
...subagent,
|
|
559
|
+
...(subagent.passthrough ?? {}),
|
|
559
560
|
model: subagent.model ? (await this.resolveModel(subagent.model)) : undefined,
|
|
560
561
|
tools: subagent.tools ? this.resolveTools(subagent.tools) : undefined,
|
|
561
562
|
interruptOn: this.compileInterruptOn(subagent.tools ?? [], subagent.interruptOn),
|
|
@@ -578,6 +579,7 @@ export class AgentRuntimeAdapter {
|
|
|
578
579
|
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
579
580
|
}
|
|
580
581
|
return createAgent({
|
|
582
|
+
...(params.passthrough ?? {}),
|
|
581
583
|
model: model,
|
|
582
584
|
tools: tools,
|
|
583
585
|
systemPrompt: params.systemPrompt,
|
|
@@ -598,6 +600,7 @@ export class AgentRuntimeAdapter {
|
|
|
598
600
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
599
601
|
}
|
|
600
602
|
const deepAgentConfig = {
|
|
603
|
+
...(params.passthrough ?? {}),
|
|
601
604
|
model: (await this.resolveModel(params.model)),
|
|
602
605
|
tools: this.resolveTools(params.tools, binding),
|
|
603
606
|
systemPrompt: params.systemPrompt,
|
|
@@ -672,6 +675,9 @@ export class AgentRuntimeAdapter {
|
|
|
672
675
|
}
|
|
673
676
|
const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
|
|
674
677
|
const finalMessageText = sanitizeVisibleText(output);
|
|
678
|
+
const outputContent = extractOutputContent(result);
|
|
679
|
+
const contentBlocks = extractContentBlocks(result);
|
|
680
|
+
const structuredResponse = result.structuredResponse;
|
|
675
681
|
return {
|
|
676
682
|
threadId,
|
|
677
683
|
runId,
|
|
@@ -680,8 +686,13 @@ export class AgentRuntimeAdapter {
|
|
|
680
686
|
interruptContent,
|
|
681
687
|
output: finalMessageText,
|
|
682
688
|
finalMessageText,
|
|
689
|
+
...(outputContent !== undefined ? { outputContent } : {}),
|
|
690
|
+
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
691
|
+
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
683
692
|
metadata: {
|
|
684
|
-
...(
|
|
693
|
+
...(structuredResponse !== undefined ? { structuredResponse } : {}),
|
|
694
|
+
...(outputContent !== undefined ? { outputContent } : {}),
|
|
695
|
+
...(contentBlocks.length > 0 ? { contentBlocks } : {}),
|
|
685
696
|
...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
|
|
686
697
|
...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
|
|
687
698
|
upstreamResult: result,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { anthropicPromptCachingMiddleware, contextEditingMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware,
|
|
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,6 +77,9 @@ 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;
|
|
@@ -89,9 +92,6 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
|
|
|
89
92
|
resolved.push(piiMiddleware(piiType, piiOptions));
|
|
90
93
|
break;
|
|
91
94
|
}
|
|
92
|
-
case "piiRedaction":
|
|
93
|
-
resolved.push(piiRedactionMiddleware(runtimeConfig));
|
|
94
|
-
break;
|
|
95
95
|
case "anthropicPromptCaching":
|
|
96
96
|
resolved.push(anthropicPromptCachingMiddleware(runtimeConfig));
|
|
97
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
|
@@ -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,9 @@ 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,
|
|
246
252
|
subagents: agent.subagentRefs.map((ref) => {
|
|
247
253
|
const subagent = agents.get(resolveRefId(ref));
|
|
248
254
|
if (!subagent) {
|
|
@@ -282,6 +288,9 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
|
|
|
282
288
|
responseFormat: agent.deepAgentConfig?.responseFormat,
|
|
283
289
|
contextSchema: agent.deepAgentConfig?.contextSchema,
|
|
284
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,
|
|
285
294
|
description: agent.description,
|
|
286
295
|
subagents: agent.subagentRefs.map((ref) => {
|
|
287
296
|
const subagent = agents.get(resolveRefId(ref));
|
|
@@ -215,6 +215,21 @@ function readExecutionConfig(value) {
|
|
|
215
215
|
? { ...value }
|
|
216
216
|
: undefined;
|
|
217
217
|
}
|
|
218
|
+
function cloneConfigValue(value) {
|
|
219
|
+
if (Array.isArray(value)) {
|
|
220
|
+
return value.map((item) => cloneConfigValue(item));
|
|
221
|
+
}
|
|
222
|
+
if (typeof value === "object" && value !== null) {
|
|
223
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, cloneConfigValue(entry)]));
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
function readPassthroughConfig(item, consumedKeys) {
|
|
228
|
+
const passthrough = Object.fromEntries(Object.entries(item)
|
|
229
|
+
.filter(([key]) => !consumedKeys.includes(key))
|
|
230
|
+
.map(([key, value]) => [key, cloneConfigValue(value)]));
|
|
231
|
+
return Object.keys(passthrough).length > 0 ? passthrough : undefined;
|
|
232
|
+
}
|
|
218
233
|
function resolveExecutionBackend(item, current) {
|
|
219
234
|
const execution = readExecutionConfig(item.execution) ?? readExecutionConfig(current?.execution);
|
|
220
235
|
const backend = typeof execution?.backend === "string"
|
|
@@ -232,6 +247,33 @@ function resolveExecutionBackend(item, current) {
|
|
|
232
247
|
}
|
|
233
248
|
function readSharedAgentConfig(item) {
|
|
234
249
|
const middleware = readMiddlewareArray(item.middleware);
|
|
250
|
+
const passthrough = readPassthroughConfig(item, [
|
|
251
|
+
"id",
|
|
252
|
+
"kind",
|
|
253
|
+
"description",
|
|
254
|
+
"modelRef",
|
|
255
|
+
"runRoot",
|
|
256
|
+
"tools",
|
|
257
|
+
"mcpServers",
|
|
258
|
+
"skills",
|
|
259
|
+
"memory",
|
|
260
|
+
"subagents",
|
|
261
|
+
"execution",
|
|
262
|
+
"capabilities",
|
|
263
|
+
"systemPrompt",
|
|
264
|
+
"checkpointer",
|
|
265
|
+
"interruptOn",
|
|
266
|
+
"stateSchema",
|
|
267
|
+
"responseFormat",
|
|
268
|
+
"contextSchema",
|
|
269
|
+
"includeAgentName",
|
|
270
|
+
"version",
|
|
271
|
+
"middleware",
|
|
272
|
+
"backend",
|
|
273
|
+
"store",
|
|
274
|
+
"taskDescription",
|
|
275
|
+
"generalPurposeAgent",
|
|
276
|
+
]);
|
|
235
277
|
return {
|
|
236
278
|
...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
|
|
237
279
|
...((typeof item.checkpointer === "object" && item.checkpointer) || typeof item.checkpointer === "boolean"
|
|
@@ -244,6 +286,7 @@ function readSharedAgentConfig(item) {
|
|
|
244
286
|
...(item.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
|
|
245
287
|
...(item.version === "v1" || item.version === "v2" ? { version: item.version } : {}),
|
|
246
288
|
...(middleware ? { middleware } : {}),
|
|
289
|
+
...(passthrough ? { passthrough } : {}),
|
|
247
290
|
};
|
|
248
291
|
}
|
|
249
292
|
function readLangchainAgentConfig(item) {
|
|
@@ -38,6 +38,9 @@ function validateMiddlewareConfig(agent) {
|
|
|
38
38
|
if (kind === "modelFallback" && !Array.isArray(typed.fallbackModels) && !Array.isArray(typed.models)) {
|
|
39
39
|
throw new Error(`Agent ${agent.id} modelFallback middleware requires fallbackModels or models`);
|
|
40
40
|
}
|
|
41
|
+
if (kind === "humanInTheLoop" && typeof typed.interruptOn !== "object") {
|
|
42
|
+
throw new Error(`Agent ${agent.id} humanInTheLoop middleware requires interruptOn`);
|
|
43
|
+
}
|
|
41
44
|
if (kind === "pii" && typeof typed.piiType !== "string") {
|
|
42
45
|
throw new Error(`Agent ${agent.id} pii middleware requires piiType`);
|
|
43
46
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@botbotgo/agent-harness",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.50",
|
|
4
4
|
"description": "Workspace runtime for multi-agent applications",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "npm@10.9.2",
|
|
@@ -26,11 +26,6 @@
|
|
|
26
26
|
"types": "./dist/tools.d.ts",
|
|
27
27
|
"import": "./dist/tools.js",
|
|
28
28
|
"default": "./dist/tools.js"
|
|
29
|
-
},
|
|
30
|
-
"./presentation": {
|
|
31
|
-
"types": "./dist/presentation.d.ts",
|
|
32
|
-
"import": "./dist/presentation.js",
|
|
33
|
-
"default": "./dist/presentation.js"
|
|
34
29
|
}
|
|
35
30
|
},
|
|
36
31
|
"dependencies": {
|
|
@@ -38,7 +33,7 @@
|
|
|
38
33
|
"@langchain/community": "^1.1.24",
|
|
39
34
|
"@langchain/core": "^1.1.33",
|
|
40
35
|
"@langchain/google": "^0.1.7",
|
|
41
|
-
"@langchain/langgraph": "^1.2.
|
|
36
|
+
"@langchain/langgraph": "^1.2.5",
|
|
42
37
|
"@langchain/langgraph-checkpoint-sqlite": "^1.0.1",
|
|
43
38
|
"@langchain/ollama": "^1.2.6",
|
|
44
39
|
"@langchain/openai": "^1.1.0",
|
|
@@ -46,7 +41,7 @@
|
|
|
46
41
|
"@llamaindex/ollama": "^0.1.23",
|
|
47
42
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
48
43
|
"deepagents": "1.8.4",
|
|
49
|
-
"langchain": "1.2.
|
|
44
|
+
"langchain": "^1.2.36",
|
|
50
45
|
"llamaindex": "^0.12.1",
|
|
51
46
|
"mustache": "^4.2.0",
|
|
52
47
|
"yaml": "^2.8.1",
|
|
@@ -55,9 +50,8 @@
|
|
|
55
50
|
"scripts": {
|
|
56
51
|
"build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
|
|
57
52
|
"check": "tsc -p tsconfig.json --noEmit",
|
|
58
|
-
"test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/
|
|
53
|
+
"test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-capabilities.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/yaml-format.test.ts",
|
|
59
54
|
"test:real-providers": "vitest run test/real-provider-harness.test.ts",
|
|
60
|
-
"test:integration": "npm run build && node scripts/integration-wallee-browser.mjs",
|
|
61
55
|
"release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
|
|
62
56
|
"release:pack": "npm pack --dry-run",
|
|
63
57
|
"release:publish": "npm publish --access public --registry https://registry.npmjs.org/"
|