@botbotgo/agent-harness 0.0.42 → 0.0.44
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 +35 -10
- package/dist/config/agents/direct.yaml +14 -13
- package/dist/config/agents/orchestra.yaml +20 -19
- package/dist/config/mcp.yaml +20 -0
- package/dist/config/stores.yaml +19 -0
- package/dist/config/tools.yaml +13 -0
- package/dist/config/workspace.yaml +5 -4
- package/dist/contracts/types.d.ts +27 -1
- package/dist/extensions.js +2 -3
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/persistence/file-store.d.ts +8 -6
- package/dist/persistence/file-store.js +2 -0
- package/dist/presentation.d.ts +14 -0
- package/dist/presentation.js +146 -0
- package/dist/runtime/agent-runtime-adapter.d.ts +1 -0
- package/dist/runtime/agent-runtime-adapter.js +62 -42
- package/dist/runtime/harness.d.ts +2 -0
- package/dist/runtime/harness.js +98 -12
- package/dist/runtime/inventory.js +2 -1
- package/dist/runtime/support/compiled-binding.d.ts +15 -0
- package/dist/runtime/support/compiled-binding.js +56 -0
- package/dist/runtime/support/harness-support.d.ts +2 -2
- package/dist/runtime/support/harness-support.js +14 -12
- package/dist/workspace/agent-binding-compiler.js +103 -32
- package/dist/workspace/object-loader.js +56 -5
- package/dist/workspace/support/agent-capabilities.d.ts +9 -0
- package/dist/workspace/support/agent-capabilities.js +41 -0
- package/dist/workspace/validate.js +11 -13
- package/package.json +8 -2
package/dist/presentation.js
CHANGED
|
@@ -8,6 +8,152 @@ 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
|
+
}
|
|
11
157
|
function renderInlineMarkdown(text) {
|
|
12
158
|
const escaped = escapeHtml(text);
|
|
13
159
|
return escaped
|
|
@@ -27,6 +27,7 @@ export declare class AgentRuntimeAdapter {
|
|
|
27
27
|
private buildToolNameMapping;
|
|
28
28
|
private buildAgentMessages;
|
|
29
29
|
private buildInvocationRequest;
|
|
30
|
+
private buildStateSnapshot;
|
|
30
31
|
private buildRawModelMessages;
|
|
31
32
|
private resolveTools;
|
|
32
33
|
private normalizeInterruptPolicy;
|
|
@@ -12,8 +12,9 @@ import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, ex
|
|
|
12
12
|
import { wrapToolForExecution } from "./tool-hitl.js";
|
|
13
13
|
import { resolveDeclaredMiddleware } from "./declared-middleware.js";
|
|
14
14
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
15
|
+
import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
|
|
15
16
|
function countConfiguredTools(binding) {
|
|
16
|
-
return binding.
|
|
17
|
+
return getBindingPrimaryTools(binding).length;
|
|
17
18
|
}
|
|
18
19
|
const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
|
|
19
20
|
const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
|
|
@@ -142,6 +143,11 @@ function hasCallableToolHandler(value) {
|
|
|
142
143
|
const typed = value;
|
|
143
144
|
return typeof typed.invoke === "function" || typeof typed.call === "function" || typeof typed.func === "function";
|
|
144
145
|
}
|
|
146
|
+
function asRecord(value) {
|
|
147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
148
|
+
? { ...value }
|
|
149
|
+
: undefined;
|
|
150
|
+
}
|
|
145
151
|
function normalizeResolvedToolSchema(resolvedTool) {
|
|
146
152
|
const schema = resolvedTool.schema;
|
|
147
153
|
if (schema && typeof schema.parse === "function" && "_def" in schema) {
|
|
@@ -172,10 +178,10 @@ export class AgentRuntimeAdapter {
|
|
|
172
178
|
this.options = options;
|
|
173
179
|
}
|
|
174
180
|
resolveBindingTimeout(binding) {
|
|
175
|
-
return resolveTimeoutMs(binding
|
|
181
|
+
return resolveTimeoutMs(getBindingModelInit(binding)?.timeout);
|
|
176
182
|
}
|
|
177
183
|
resolveStreamIdleTimeout(binding) {
|
|
178
|
-
const configuredIdleTimeout = resolveTimeoutMs(binding
|
|
184
|
+
const configuredIdleTimeout = resolveTimeoutMs(getBindingModelInit(binding)?.streamIdleTimeout);
|
|
179
185
|
if (configuredIdleTimeout) {
|
|
180
186
|
return configuredIdleTimeout;
|
|
181
187
|
}
|
|
@@ -284,35 +290,38 @@ export class AgentRuntimeAdapter {
|
|
|
284
290
|
};
|
|
285
291
|
}
|
|
286
292
|
applyStrictToolJsonInstruction(binding) {
|
|
287
|
-
if (binding
|
|
293
|
+
if (isLangChainBinding(binding)) {
|
|
294
|
+
const params = getBindingLangChainParams(binding);
|
|
288
295
|
return {
|
|
289
296
|
...binding,
|
|
290
297
|
langchainAgentParams: {
|
|
291
|
-
...
|
|
292
|
-
systemPrompt: [
|
|
298
|
+
...params,
|
|
299
|
+
systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
|
|
293
300
|
},
|
|
294
301
|
};
|
|
295
302
|
}
|
|
296
|
-
if (binding
|
|
303
|
+
if (isDeepAgentBinding(binding)) {
|
|
304
|
+
const params = getBindingDeepAgentParams(binding);
|
|
297
305
|
return {
|
|
298
306
|
...binding,
|
|
299
307
|
deepAgentParams: {
|
|
300
|
-
...
|
|
301
|
-
systemPrompt: [
|
|
308
|
+
...params,
|
|
309
|
+
systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
|
|
302
310
|
},
|
|
303
311
|
};
|
|
304
312
|
}
|
|
305
313
|
return binding;
|
|
306
314
|
}
|
|
307
315
|
async synthesizeDeepAgentAnswer(binding, input, result) {
|
|
308
|
-
|
|
316
|
+
const params = getBindingDeepAgentParams(binding);
|
|
317
|
+
if (!params) {
|
|
309
318
|
return "";
|
|
310
319
|
}
|
|
311
320
|
const toolContext = extractToolFallbackContext(result);
|
|
312
321
|
if (!toolContext) {
|
|
313
322
|
return "";
|
|
314
323
|
}
|
|
315
|
-
const model = (await this.resolveModel(
|
|
324
|
+
const model = (await this.resolveModel(params.model));
|
|
316
325
|
if (!model?.invoke) {
|
|
317
326
|
return "";
|
|
318
327
|
}
|
|
@@ -365,6 +374,14 @@ export class AgentRuntimeAdapter {
|
|
|
365
374
|
messages: this.buildAgentMessages(history, input),
|
|
366
375
|
};
|
|
367
376
|
}
|
|
377
|
+
buildStateSnapshot(result) {
|
|
378
|
+
const snapshot = { ...result };
|
|
379
|
+
delete snapshot.messages;
|
|
380
|
+
delete snapshot.__interrupt__;
|
|
381
|
+
delete snapshot.structuredResponse;
|
|
382
|
+
delete snapshot.files;
|
|
383
|
+
return Object.keys(snapshot).length > 0 ? snapshot : undefined;
|
|
384
|
+
}
|
|
368
385
|
buildRawModelMessages(systemPrompt, history, input) {
|
|
369
386
|
const messages = [];
|
|
370
387
|
if (systemPrompt) {
|
|
@@ -424,14 +441,10 @@ export class AgentRuntimeAdapter {
|
|
|
424
441
|
return compiled.size > 0 ? Object.fromEntries(compiled.entries()) : undefined;
|
|
425
442
|
}
|
|
426
443
|
resolveInterruptOn(binding) {
|
|
427
|
-
|
|
428
|
-
return this.compileInterruptOn(binding.deepAgentParams.tools, binding.deepAgentParams.interruptOn);
|
|
429
|
-
}
|
|
430
|
-
return this.compileInterruptOn(binding.langchainAgentParams?.tools ?? [], binding.agent.langchainAgentConfig?.interruptOn);
|
|
444
|
+
return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
|
|
431
445
|
}
|
|
432
446
|
async resolveMiddleware(binding, interruptOn) {
|
|
433
|
-
const declarativeMiddleware = await resolveDeclaredMiddleware(binding
|
|
434
|
-
binding.deepAgentParams?.middleware, {
|
|
447
|
+
const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
|
|
435
448
|
resolveModel: (model) => this.resolveModel(model),
|
|
436
449
|
resolveCustom: this.options.declaredMiddlewareResolver,
|
|
437
450
|
binding,
|
|
@@ -484,30 +497,31 @@ export class AgentRuntimeAdapter {
|
|
|
484
497
|
})));
|
|
485
498
|
}
|
|
486
499
|
async create(binding) {
|
|
487
|
-
if (binding
|
|
500
|
+
if (isLangChainBinding(binding)) {
|
|
501
|
+
const params = getBindingLangChainParams(binding);
|
|
488
502
|
const interruptOn = this.resolveInterruptOn(binding);
|
|
489
|
-
const model = (await this.resolveModel(
|
|
490
|
-
const tools = this.resolveTools(
|
|
503
|
+
const model = (await this.resolveModel(params.model));
|
|
504
|
+
const tools = this.resolveTools(params.tools, binding);
|
|
491
505
|
if (tools.length > 0 && typeof model.bindTools !== "function") {
|
|
492
|
-
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${
|
|
506
|
+
throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
|
|
493
507
|
}
|
|
494
508
|
return createAgent({
|
|
495
509
|
model: model,
|
|
496
510
|
tools: tools,
|
|
497
|
-
systemPrompt:
|
|
498
|
-
stateSchema:
|
|
499
|
-
responseFormat:
|
|
500
|
-
contextSchema:
|
|
511
|
+
systemPrompt: params.systemPrompt,
|
|
512
|
+
stateSchema: params.stateSchema,
|
|
513
|
+
responseFormat: params.responseFormat,
|
|
514
|
+
contextSchema: params.contextSchema,
|
|
501
515
|
middleware: (await this.resolveMiddleware(binding, interruptOn)),
|
|
502
516
|
checkpointer: this.resolveCheckpointer(binding),
|
|
503
517
|
store: this.options.storeResolver?.(binding),
|
|
504
|
-
includeAgentName:
|
|
505
|
-
version:
|
|
506
|
-
name:
|
|
507
|
-
description:
|
|
518
|
+
includeAgentName: params.includeAgentName,
|
|
519
|
+
version: params.version,
|
|
520
|
+
name: params.name,
|
|
521
|
+
description: params.description,
|
|
508
522
|
});
|
|
509
523
|
}
|
|
510
|
-
const params = binding
|
|
524
|
+
const params = getBindingDeepAgentParams(binding);
|
|
511
525
|
if (!params) {
|
|
512
526
|
throw new Error(`Agent ${binding.agent.id} has no runnable params`);
|
|
513
527
|
}
|
|
@@ -532,10 +546,8 @@ export class AgentRuntimeAdapter {
|
|
|
532
546
|
return createDeepAgent(deepAgentConfig);
|
|
533
547
|
}
|
|
534
548
|
async route(input, primaryBinding, secondaryBinding, options = {}) {
|
|
535
|
-
const routeModelConfig = primaryBinding
|
|
536
|
-
|
|
537
|
-
secondaryBinding.langchainAgentParams?.model ??
|
|
538
|
-
secondaryBinding.deepAgentParams?.model;
|
|
549
|
+
const routeModelConfig = getBindingPrimaryModel(primaryBinding) ??
|
|
550
|
+
getBindingPrimaryModel(secondaryBinding);
|
|
539
551
|
if (!routeModelConfig) {
|
|
540
552
|
throw new Error("No router model configuration available");
|
|
541
553
|
}
|
|
@@ -587,13 +599,21 @@ export class AgentRuntimeAdapter {
|
|
|
587
599
|
throw new Error(emptyAssistantMessageFailure);
|
|
588
600
|
}
|
|
589
601
|
const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
|
|
602
|
+
const finalMessageText = sanitizeVisibleText(output);
|
|
590
603
|
return {
|
|
591
604
|
threadId,
|
|
592
605
|
runId,
|
|
593
606
|
agentId: binding.agent.id,
|
|
594
607
|
state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? "waiting_for_approval" : "completed",
|
|
595
608
|
interruptContent,
|
|
596
|
-
output:
|
|
609
|
+
output: finalMessageText,
|
|
610
|
+
finalMessageText,
|
|
611
|
+
metadata: {
|
|
612
|
+
...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
|
|
613
|
+
...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
|
|
614
|
+
...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
|
|
615
|
+
upstreamResult: result,
|
|
616
|
+
},
|
|
597
617
|
};
|
|
598
618
|
}
|
|
599
619
|
async *stream(binding, input, threadId, history = [], options = {}) {
|
|
@@ -601,9 +621,9 @@ export class AgentRuntimeAdapter {
|
|
|
601
621
|
const invokeTimeoutMs = this.resolveBindingTimeout(binding);
|
|
602
622
|
const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
|
|
603
623
|
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
604
|
-
if (binding
|
|
605
|
-
const langchainParams = binding
|
|
606
|
-
const resolvedModel = (await this.resolveModel(
|
|
624
|
+
if (isLangChainBinding(binding)) {
|
|
625
|
+
const langchainParams = getBindingLangChainParams(binding);
|
|
626
|
+
const resolvedModel = (await this.resolveModel(langchainParams.model));
|
|
607
627
|
const tools = this.resolveTools(langchainParams.tools, binding);
|
|
608
628
|
const canUseDirectModelStream = tools.length === 0 || typeof resolvedModel.bindTools !== "function";
|
|
609
629
|
const model = canUseDirectModelStream
|
|
@@ -615,7 +635,7 @@ export class AgentRuntimeAdapter {
|
|
|
615
635
|
// agent loop and only adds an extra model round-trip before the runnable path.
|
|
616
636
|
if (canUseDirectModelStream && typeof model.stream === "function") {
|
|
617
637
|
let emitted = false;
|
|
618
|
-
const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(
|
|
638
|
+
const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
|
|
619
639
|
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
620
640
|
const delta = readStreamDelta(chunk);
|
|
621
641
|
if (delta) {
|
|
@@ -637,7 +657,7 @@ export class AgentRuntimeAdapter {
|
|
|
637
657
|
const request = this.buildInvocationRequest(history, input, options);
|
|
638
658
|
if (typeof runnable.streamEvents === "function") {
|
|
639
659
|
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
640
|
-
const allowVisibleStreamDeltas =
|
|
660
|
+
const allowVisibleStreamDeltas = isLangChainBinding(binding);
|
|
641
661
|
let emittedOutput = "";
|
|
642
662
|
let emittedToolError = false;
|
|
643
663
|
const seenTerminalOutputs = new Set();
|
|
@@ -662,7 +682,7 @@ export class AgentRuntimeAdapter {
|
|
|
662
682
|
}
|
|
663
683
|
}
|
|
664
684
|
}
|
|
665
|
-
if (binding
|
|
685
|
+
if (isDeepAgentBinding(binding)) {
|
|
666
686
|
const stateStreamOutput = extractStateStreamOutput(event);
|
|
667
687
|
if (stateStreamOutput) {
|
|
668
688
|
const nextOutput = computeIncrementalOutput(emittedOutput, sanitizeVisibleText(stateStreamOutput));
|
|
@@ -702,7 +722,7 @@ export class AgentRuntimeAdapter {
|
|
|
702
722
|
return;
|
|
703
723
|
}
|
|
704
724
|
}
|
|
705
|
-
if (binding
|
|
725
|
+
if (isLangChainBinding(binding) && typeof runnable.stream === "function") {
|
|
706
726
|
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
707
727
|
let emitted = false;
|
|
708
728
|
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
@@ -24,6 +24,8 @@ export declare class AgentHarnessRuntime {
|
|
|
24
24
|
private readonly concurrencyConfig;
|
|
25
25
|
private activeRunSlots;
|
|
26
26
|
private readonly pendingRunSlots;
|
|
27
|
+
private toPublicApprovalRecord;
|
|
28
|
+
private normalizeInvocationEnvelope;
|
|
27
29
|
private listHostBindings;
|
|
28
30
|
private defaultRunRoot;
|
|
29
31
|
private heuristicRoute;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -15,6 +15,7 @@ import { FileBackedStore } from "./store.js";
|
|
|
15
15
|
import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
|
|
16
16
|
import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
|
|
17
17
|
import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
|
|
18
|
+
import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
|
|
18
19
|
export class AgentHarnessRuntime {
|
|
19
20
|
workspace;
|
|
20
21
|
runtimeAdapterOptions;
|
|
@@ -39,6 +40,19 @@ export class AgentHarnessRuntime {
|
|
|
39
40
|
concurrencyConfig;
|
|
40
41
|
activeRunSlots = 0;
|
|
41
42
|
pendingRunSlots = [];
|
|
43
|
+
toPublicApprovalRecord(approval) {
|
|
44
|
+
const { toolCallId: _toolCallId, checkpointRef: _checkpointRef, eventRefs: _eventRefs, ...publicApproval } = approval;
|
|
45
|
+
return publicApproval;
|
|
46
|
+
}
|
|
47
|
+
normalizeInvocationEnvelope(options) {
|
|
48
|
+
const invocation = options.invocation;
|
|
49
|
+
return {
|
|
50
|
+
context: invocation?.context ?? options.context,
|
|
51
|
+
state: invocation?.inputs ?? options.state,
|
|
52
|
+
files: invocation?.attachments ?? options.files,
|
|
53
|
+
invocation,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
42
56
|
listHostBindings() {
|
|
43
57
|
return inferRoutingBindings(this.workspace).hostBindings;
|
|
44
58
|
}
|
|
@@ -79,7 +93,7 @@ export class AgentHarnessRuntime {
|
|
|
79
93
|
return requestedAgentId;
|
|
80
94
|
}
|
|
81
95
|
resolveStore(binding) {
|
|
82
|
-
const storeConfig = binding
|
|
96
|
+
const storeConfig = binding ? getBindingStoreConfig(binding) : undefined;
|
|
83
97
|
const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
|
|
84
98
|
if (!storeConfig) {
|
|
85
99
|
return this.defaultStore;
|
|
@@ -184,7 +198,7 @@ export class AgentHarnessRuntime {
|
|
|
184
198
|
if (!binding) {
|
|
185
199
|
throw new Error(`Unknown agent ${agentId}`);
|
|
186
200
|
}
|
|
187
|
-
return binding
|
|
201
|
+
return getBindingPrimaryTools(binding);
|
|
188
202
|
}
|
|
189
203
|
resolveAgentTools(agentId) {
|
|
190
204
|
const binding = this.getBinding(agentId);
|
|
@@ -259,10 +273,11 @@ export class AgentHarnessRuntime {
|
|
|
259
273
|
return false;
|
|
260
274
|
}
|
|
261
275
|
return true;
|
|
262
|
-
});
|
|
276
|
+
}).map((approval) => this.toPublicApprovalRecord(approval));
|
|
263
277
|
}
|
|
264
278
|
async getApproval(approvalId) {
|
|
265
|
-
|
|
279
|
+
const approval = await this.persistence.getApproval(approvalId);
|
|
280
|
+
return approval ? this.toPublicApprovalRecord(approval) : null;
|
|
266
281
|
}
|
|
267
282
|
async createToolMcpServer(options) {
|
|
268
283
|
const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
|
|
@@ -340,7 +355,8 @@ export class AgentHarnessRuntime {
|
|
|
340
355
|
threadId,
|
|
341
356
|
runId,
|
|
342
357
|
agentId: binding.agent.id,
|
|
343
|
-
executionMode: binding
|
|
358
|
+
executionMode: getBindingAdapterKind(binding),
|
|
359
|
+
adapterKind: getBindingAdapterKind(binding),
|
|
344
360
|
createdAt,
|
|
345
361
|
});
|
|
346
362
|
return { threadId, runId, createdAt };
|
|
@@ -500,6 +516,7 @@ export class AgentHarnessRuntime {
|
|
|
500
516
|
}
|
|
501
517
|
async dispatchRunListeners(stream, listeners) {
|
|
502
518
|
let latestEvent;
|
|
519
|
+
let latestResult;
|
|
503
520
|
let output = "";
|
|
504
521
|
for await (const item of stream) {
|
|
505
522
|
if (item.type === "event") {
|
|
@@ -507,6 +524,10 @@ export class AgentHarnessRuntime {
|
|
|
507
524
|
await this.notifyListener(listeners.onEvent, item.event);
|
|
508
525
|
continue;
|
|
509
526
|
}
|
|
527
|
+
if (item.type === "result") {
|
|
528
|
+
latestResult = item.result;
|
|
529
|
+
continue;
|
|
530
|
+
}
|
|
510
531
|
if (item.type === "content") {
|
|
511
532
|
output += item.content;
|
|
512
533
|
await this.notifyListener(listeners.onChunk, item.content);
|
|
@@ -531,6 +552,13 @@ export class AgentHarnessRuntime {
|
|
|
531
552
|
if (!latestEvent) {
|
|
532
553
|
throw new Error("run did not emit any events");
|
|
533
554
|
}
|
|
555
|
+
if (latestResult) {
|
|
556
|
+
return {
|
|
557
|
+
...latestResult,
|
|
558
|
+
output: latestResult.output || output,
|
|
559
|
+
finalMessageText: latestResult.finalMessageText ?? latestResult.output ?? output,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
534
562
|
const thread = await this.getThread(latestEvent.threadId);
|
|
535
563
|
if (!thread) {
|
|
536
564
|
throw new Error(`Unknown thread ${latestEvent.threadId}`);
|
|
@@ -561,6 +589,7 @@ export class AgentHarnessRuntime {
|
|
|
561
589
|
}
|
|
562
590
|
const releaseRunSlot = await this.acquireRunSlot();
|
|
563
591
|
try {
|
|
592
|
+
const invocation = this.normalizeInvocationEnvelope(options);
|
|
564
593
|
const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
|
|
565
594
|
const binding = this.workspace.bindings.get(selectedAgentId);
|
|
566
595
|
if (!binding) {
|
|
@@ -575,13 +604,13 @@ export class AgentHarnessRuntime {
|
|
|
575
604
|
agentId: binding.agent.id,
|
|
576
605
|
requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
|
|
577
606
|
selectedAgentId,
|
|
578
|
-
executionMode: binding
|
|
607
|
+
executionMode: getBindingAdapterKind(binding),
|
|
579
608
|
});
|
|
580
609
|
try {
|
|
581
610
|
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
|
|
582
|
-
context:
|
|
583
|
-
state:
|
|
584
|
-
files:
|
|
611
|
+
context: invocation.context,
|
|
612
|
+
state: invocation.state,
|
|
613
|
+
files: invocation.files,
|
|
585
614
|
});
|
|
586
615
|
const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
|
|
587
616
|
previousState: null,
|
|
@@ -615,6 +644,7 @@ export class AgentHarnessRuntime {
|
|
|
615
644
|
async *streamEvents(options) {
|
|
616
645
|
const releaseRunSlot = await this.acquireRunSlot();
|
|
617
646
|
try {
|
|
647
|
+
const invocation = this.normalizeInvocationEnvelope(options);
|
|
618
648
|
const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
|
|
619
649
|
const binding = this.workspace.bindings.get(selectedAgentId);
|
|
620
650
|
if (!binding) {
|
|
@@ -644,9 +674,9 @@ export class AgentHarnessRuntime {
|
|
|
644
674
|
let assistantOutput = "";
|
|
645
675
|
const toolErrors = [];
|
|
646
676
|
for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
|
|
647
|
-
context:
|
|
648
|
-
state:
|
|
649
|
-
files:
|
|
677
|
+
context: invocation.context,
|
|
678
|
+
state: invocation.state,
|
|
679
|
+
files: invocation.files,
|
|
650
680
|
})) {
|
|
651
681
|
if (chunk) {
|
|
652
682
|
const normalizedChunk = typeof chunk === "string"
|
|
@@ -669,6 +699,20 @@ export class AgentHarnessRuntime {
|
|
|
669
699
|
type: "event",
|
|
670
700
|
event: approvalRequest.event,
|
|
671
701
|
};
|
|
702
|
+
yield {
|
|
703
|
+
type: "result",
|
|
704
|
+
result: {
|
|
705
|
+
threadId,
|
|
706
|
+
runId,
|
|
707
|
+
agentId: selectedAgentId,
|
|
708
|
+
state: "waiting_for_approval",
|
|
709
|
+
output: assistantOutput,
|
|
710
|
+
finalMessageText: assistantOutput,
|
|
711
|
+
interruptContent: normalizedChunk.content,
|
|
712
|
+
approvalId: approvalRequest.approval.approvalId,
|
|
713
|
+
pendingActionId: approvalRequest.approval.pendingActionId,
|
|
714
|
+
},
|
|
715
|
+
};
|
|
672
716
|
return;
|
|
673
717
|
}
|
|
674
718
|
if (normalizedChunk.kind === "reasoning") {
|
|
@@ -728,6 +772,17 @@ export class AgentHarnessRuntime {
|
|
|
728
772
|
}
|
|
729
773
|
}
|
|
730
774
|
await this.appendAssistantMessage(threadId, runId, assistantOutput);
|
|
775
|
+
yield {
|
|
776
|
+
type: "result",
|
|
777
|
+
result: {
|
|
778
|
+
threadId,
|
|
779
|
+
runId,
|
|
780
|
+
agentId: selectedAgentId,
|
|
781
|
+
state: "completed",
|
|
782
|
+
output: assistantOutput,
|
|
783
|
+
finalMessageText: assistantOutput,
|
|
784
|
+
},
|
|
785
|
+
};
|
|
731
786
|
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
|
|
732
787
|
previousState: null,
|
|
733
788
|
}) };
|
|
@@ -753,6 +808,17 @@ export class AgentHarnessRuntime {
|
|
|
753
808
|
agentId: selectedAgentId,
|
|
754
809
|
content: renderRuntimeFailure(error),
|
|
755
810
|
};
|
|
811
|
+
yield {
|
|
812
|
+
type: "result",
|
|
813
|
+
result: {
|
|
814
|
+
threadId,
|
|
815
|
+
runId,
|
|
816
|
+
agentId: selectedAgentId,
|
|
817
|
+
state: "failed",
|
|
818
|
+
output: renderRuntimeFailure(error),
|
|
819
|
+
finalMessageText: renderRuntimeFailure(error),
|
|
820
|
+
},
|
|
821
|
+
};
|
|
756
822
|
return;
|
|
757
823
|
}
|
|
758
824
|
try {
|
|
@@ -761,6 +827,15 @@ export class AgentHarnessRuntime {
|
|
|
761
827
|
if (actual.output) {
|
|
762
828
|
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
|
|
763
829
|
}
|
|
830
|
+
yield {
|
|
831
|
+
type: "result",
|
|
832
|
+
result: {
|
|
833
|
+
...actual,
|
|
834
|
+
threadId,
|
|
835
|
+
runId,
|
|
836
|
+
agentId: selectedAgentId,
|
|
837
|
+
},
|
|
838
|
+
};
|
|
764
839
|
yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
|
|
765
840
|
previousState: null,
|
|
766
841
|
}) };
|
|
@@ -779,6 +854,17 @@ export class AgentHarnessRuntime {
|
|
|
779
854
|
agentId: selectedAgentId,
|
|
780
855
|
content: renderRuntimeFailure(invokeError),
|
|
781
856
|
};
|
|
857
|
+
yield {
|
|
858
|
+
type: "result",
|
|
859
|
+
result: {
|
|
860
|
+
threadId,
|
|
861
|
+
runId,
|
|
862
|
+
agentId: selectedAgentId,
|
|
863
|
+
state: "failed",
|
|
864
|
+
output: renderRuntimeFailure(invokeError),
|
|
865
|
+
finalMessageText: renderRuntimeFailure(invokeError),
|
|
866
|
+
},
|
|
867
|
+
};
|
|
782
868
|
return;
|
|
783
869
|
}
|
|
784
870
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readSkillMetadata } from "./support/skill-metadata.js";
|
|
2
|
+
import { getBindingPrimaryTools } from "./support/compiled-binding.js";
|
|
2
3
|
function listHostBindings(workspace) {
|
|
3
4
|
return Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
|
|
4
5
|
}
|
|
@@ -31,7 +32,7 @@ export function listAgentTools(workspace, agentId) {
|
|
|
31
32
|
if (!binding) {
|
|
32
33
|
return [];
|
|
33
34
|
}
|
|
34
|
-
return dedupeTools(binding
|
|
35
|
+
return dedupeTools(getBindingPrimaryTools(binding));
|
|
35
36
|
}
|
|
36
37
|
export function listAgentSkills(workspace, agentId) {
|
|
37
38
|
const binding = findAgentBinding(workspace, agentId);
|