@botbotgo/agent-harness 0.0.36 → 0.0.38
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/dist/contracts/types.d.ts +2 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/runtime/agent-runtime-adapter.js +39 -10
- package/dist/runtime/harness.js +12 -1
- package/dist/runtime/parsing/stream-event-parsing.d.ts +2 -0
- package/dist/runtime/parsing/stream-event-parsing.js +35 -3
- package/dist/runtime/support/harness-support.d.ts +1 -0
- package/dist/runtime/support/harness-support.js +27 -0
- package/package.json +1 -1
|
@@ -236,6 +236,7 @@ export type RunListeners = {
|
|
|
236
236
|
onToolResult?: (item: {
|
|
237
237
|
toolName: string;
|
|
238
238
|
output: unknown;
|
|
239
|
+
isError?: boolean;
|
|
239
240
|
}) => void | Promise<void>;
|
|
240
241
|
};
|
|
241
242
|
export type MessageContentPart = {
|
|
@@ -289,6 +290,7 @@ export type HarnessStreamItem = {
|
|
|
289
290
|
agentId: string;
|
|
290
291
|
toolName: string;
|
|
291
292
|
output: unknown;
|
|
293
|
+
isError?: boolean;
|
|
292
294
|
};
|
|
293
295
|
export type TranscriptMessage = {
|
|
294
296
|
role: "user" | "assistant";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.37";
|
package/dist/package-version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
1
|
+
export const AGENT_HARNESS_VERSION = "0.0.37";
|
|
@@ -32,6 +32,16 @@ function asObject(value) {
|
|
|
32
32
|
function resolveTimeoutMs(value) {
|
|
33
33
|
return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
|
|
34
34
|
}
|
|
35
|
+
function computeRemainingTimeoutMs(deadlineAt, fallbackTimeoutMs) {
|
|
36
|
+
if (!deadlineAt) {
|
|
37
|
+
return fallbackTimeoutMs;
|
|
38
|
+
}
|
|
39
|
+
const remaining = deadlineAt - Date.now();
|
|
40
|
+
if (remaining <= 0) {
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
return fallbackTimeoutMs ? Math.min(fallbackTimeoutMs, remaining) : remaining;
|
|
44
|
+
}
|
|
35
45
|
function isPlaceholderApiKey(value) {
|
|
36
46
|
return typeof value === "string" && value.trim().toLowerCase() === "dummy";
|
|
37
47
|
}
|
|
@@ -209,11 +219,27 @@ export class AgentRuntimeAdapter {
|
|
|
209
219
|
});
|
|
210
220
|
});
|
|
211
221
|
}
|
|
212
|
-
async *iterateWithTimeout(iterable, timeoutMs, operation) {
|
|
222
|
+
async *iterateWithTimeout(iterable, timeoutMs, operation, deadlineAt, deadlineTimeoutMs) {
|
|
213
223
|
const iterator = iterable[Symbol.asyncIterator]();
|
|
214
224
|
try {
|
|
215
225
|
for (;;) {
|
|
216
|
-
const
|
|
226
|
+
const effectiveTimeoutMs = computeRemainingTimeoutMs(deadlineAt, timeoutMs);
|
|
227
|
+
if (effectiveTimeoutMs !== undefined && effectiveTimeoutMs <= 0) {
|
|
228
|
+
throw new RuntimeOperationTimeoutError(operation, deadlineTimeoutMs ?? timeoutMs ?? 0, "invoke");
|
|
229
|
+
}
|
|
230
|
+
let next;
|
|
231
|
+
try {
|
|
232
|
+
next = await this.withTimeout(() => iterator.next(), effectiveTimeoutMs, operation, "stream");
|
|
233
|
+
}
|
|
234
|
+
catch (error) {
|
|
235
|
+
if (error instanceof RuntimeOperationTimeoutError &&
|
|
236
|
+
deadlineAt &&
|
|
237
|
+
deadlineTimeoutMs &&
|
|
238
|
+
effectiveTimeoutMs !== timeoutMs) {
|
|
239
|
+
throw new RuntimeOperationTimeoutError(operation, deadlineTimeoutMs, "invoke");
|
|
240
|
+
}
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
217
243
|
if (next.done) {
|
|
218
244
|
return;
|
|
219
245
|
}
|
|
@@ -591,6 +617,7 @@ export class AgentRuntimeAdapter {
|
|
|
591
617
|
try {
|
|
592
618
|
const invokeTimeoutMs = this.resolveBindingTimeout(binding);
|
|
593
619
|
const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
|
|
620
|
+
const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
|
|
594
621
|
if (binding.langchainAgentParams) {
|
|
595
622
|
const langchainParams = binding.langchainAgentParams;
|
|
596
623
|
const resolvedModel = (await this.resolveModel(binding.langchainAgentParams.model));
|
|
@@ -605,8 +632,8 @@ export class AgentRuntimeAdapter {
|
|
|
605
632
|
// agent loop and only adds an extra model round-trip before the runnable path.
|
|
606
633
|
if (canUseDirectModelStream && typeof model.stream === "function") {
|
|
607
634
|
let emitted = false;
|
|
608
|
-
const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), invokeTimeoutMs, "model stream start", "stream");
|
|
609
|
-
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream")) {
|
|
635
|
+
const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
|
|
636
|
+
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
610
637
|
const delta = readStreamDelta(chunk);
|
|
611
638
|
if (delta) {
|
|
612
639
|
emitted = true;
|
|
@@ -626,12 +653,13 @@ export class AgentRuntimeAdapter {
|
|
|
626
653
|
const runnable = await this.create(binding);
|
|
627
654
|
const request = { messages: this.buildAgentMessages(history, input) };
|
|
628
655
|
if (typeof runnable.streamEvents === "function") {
|
|
629
|
-
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), invokeTimeoutMs, "agent streamEvents start", "stream");
|
|
656
|
+
const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
|
|
630
657
|
const allowVisibleStreamDeltas = Boolean(binding.langchainAgentParams);
|
|
631
658
|
let emittedOutput = "";
|
|
659
|
+
let emittedToolError = false;
|
|
632
660
|
const seenTerminalOutputs = new Set();
|
|
633
661
|
let lastStep = "";
|
|
634
|
-
for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents")) {
|
|
662
|
+
for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents", streamDeadlineAt, invokeTimeoutMs)) {
|
|
635
663
|
const interruptPayload = extractInterruptPayload(event);
|
|
636
664
|
if (interruptPayload) {
|
|
637
665
|
yield { kind: "interrupt", content: interruptPayload };
|
|
@@ -668,7 +696,8 @@ export class AgentRuntimeAdapter {
|
|
|
668
696
|
}
|
|
669
697
|
const toolResult = extractToolResult(event);
|
|
670
698
|
if (toolResult) {
|
|
671
|
-
|
|
699
|
+
emittedToolError = emittedToolError || toolResult.isError === true;
|
|
700
|
+
yield { kind: "tool-result", toolName: toolResult.toolName, output: toolResult.output, isError: toolResult.isError };
|
|
672
701
|
}
|
|
673
702
|
const output = extractTerminalStreamOutput(event);
|
|
674
703
|
if (output) {
|
|
@@ -686,14 +715,14 @@ export class AgentRuntimeAdapter {
|
|
|
686
715
|
}
|
|
687
716
|
}
|
|
688
717
|
}
|
|
689
|
-
if (emittedOutput) {
|
|
718
|
+
if (emittedOutput || emittedToolError) {
|
|
690
719
|
return;
|
|
691
720
|
}
|
|
692
721
|
}
|
|
693
722
|
if (binding.langchainAgentParams && typeof runnable.stream === "function") {
|
|
694
|
-
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), invokeTimeoutMs, "agent stream start", "stream");
|
|
723
|
+
const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
|
|
695
724
|
let emitted = false;
|
|
696
|
-
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream")) {
|
|
725
|
+
for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
|
|
697
726
|
const delta = readStreamDelta(chunk);
|
|
698
727
|
if (delta) {
|
|
699
728
|
emitted = true;
|
package/dist/runtime/harness.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createResourceBackendResolver, createResourceToolResolver } from "../re
|
|
|
6
6
|
import { EventBus } from "./event-bus.js";
|
|
7
7
|
import { PolicyEngine } from "./policy-engine.js";
|
|
8
8
|
import { getRoutingSystemPrompt } from "../workspace/support/workspace-ref-utils.js";
|
|
9
|
-
import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, requiresResearchRoute, } from "./support/harness-support.js";
|
|
9
|
+
import { createHarnessEvent, createPendingApproval, heuristicRoute, inferRoutingBindings, resolveDeterministicRouteIntent, renderRuntimeFailure, renderToolFailure, requiresResearchRoute, } from "./support/harness-support.js";
|
|
10
10
|
import { createCheckpointerForConfig, createStoreForConfig } from "./support/runtime-factories.js";
|
|
11
11
|
import { resolveCompiledEmbeddingModel, resolveCompiledEmbeddingModelRef } from "./support/embedding-models.js";
|
|
12
12
|
import { resolveCompiledVectorStore, resolveCompiledVectorStoreRef } from "./support/vector-stores.js";
|
|
@@ -429,6 +429,7 @@ export class AgentHarness {
|
|
|
429
429
|
await this.notifyListener(listeners.onToolResult, {
|
|
430
430
|
toolName: item.toolName,
|
|
431
431
|
output: item.output,
|
|
432
|
+
isError: item.isError,
|
|
432
433
|
});
|
|
433
434
|
}
|
|
434
435
|
}
|
|
@@ -543,6 +544,7 @@ export class AgentHarness {
|
|
|
543
544
|
try {
|
|
544
545
|
const priorHistory = await this.loadPriorHistory(threadId, runId);
|
|
545
546
|
let assistantOutput = "";
|
|
547
|
+
const toolErrors = [];
|
|
546
548
|
for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory)) {
|
|
547
549
|
if (chunk) {
|
|
548
550
|
const normalizedChunk = typeof chunk === "string"
|
|
@@ -591,6 +593,9 @@ export class AgentHarness {
|
|
|
591
593
|
continue;
|
|
592
594
|
}
|
|
593
595
|
if (normalizedChunk.kind === "tool-result") {
|
|
596
|
+
if (normalizedChunk.isError) {
|
|
597
|
+
toolErrors.push(renderToolFailure(normalizedChunk.toolName, normalizedChunk.output));
|
|
598
|
+
}
|
|
594
599
|
yield {
|
|
595
600
|
type: "tool-result",
|
|
596
601
|
threadId,
|
|
@@ -598,6 +603,7 @@ export class AgentHarness {
|
|
|
598
603
|
agentId: selectedAgentId,
|
|
599
604
|
toolName: normalizedChunk.toolName,
|
|
600
605
|
output: normalizedChunk.output,
|
|
606
|
+
isError: normalizedChunk.isError,
|
|
601
607
|
};
|
|
602
608
|
continue;
|
|
603
609
|
}
|
|
@@ -606,6 +612,11 @@ export class AgentHarness {
|
|
|
606
612
|
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, normalizedChunk.content);
|
|
607
613
|
}
|
|
608
614
|
}
|
|
615
|
+
if (!assistantOutput && toolErrors.length > 0) {
|
|
616
|
+
assistantOutput = toolErrors.join("\n\n");
|
|
617
|
+
emitted = true;
|
|
618
|
+
yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, assistantOutput);
|
|
619
|
+
}
|
|
609
620
|
if (!assistantOutput) {
|
|
610
621
|
const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
|
|
611
622
|
if (actual.output) {
|
|
@@ -14,6 +14,7 @@ export type RuntimeStreamChunk = {
|
|
|
14
14
|
kind: "tool-result";
|
|
15
15
|
toolName: string;
|
|
16
16
|
output: unknown;
|
|
17
|
+
isError?: boolean;
|
|
17
18
|
};
|
|
18
19
|
export declare function extractTerminalStreamOutput(event: unknown): string;
|
|
19
20
|
export declare function extractReasoningStreamOutput(event: unknown): string;
|
|
@@ -23,6 +24,7 @@ export declare function extractAgentStep(event: unknown): string | null;
|
|
|
23
24
|
export declare function extractToolResult(event: unknown): {
|
|
24
25
|
toolName: string;
|
|
25
26
|
output: unknown;
|
|
27
|
+
isError?: boolean;
|
|
26
28
|
} | null;
|
|
27
29
|
export declare function extractInterruptPayload(event: unknown): string | null;
|
|
28
30
|
export declare function normalizeTerminalOutputKey(value: string): string;
|
|
@@ -50,6 +50,31 @@ function summarizeStepValue(value) {
|
|
|
50
50
|
function humanizeEventName(name) {
|
|
51
51
|
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._-]+/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
52
52
|
}
|
|
53
|
+
function readToolErrorText(value) {
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
return value;
|
|
56
|
+
}
|
|
57
|
+
if (value instanceof Error) {
|
|
58
|
+
return value.message;
|
|
59
|
+
}
|
|
60
|
+
if (typeof value !== "object" || !value) {
|
|
61
|
+
return "";
|
|
62
|
+
}
|
|
63
|
+
const record = value;
|
|
64
|
+
for (const candidate of [record.error, record.message, record.detail, record.details]) {
|
|
65
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
function isErrorLikeToolOutput(value) {
|
|
72
|
+
const message = readToolErrorText(value).trim();
|
|
73
|
+
if (!message) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
return /(^|\b)(error|exception|failed|failure|denied|timed out|timeout|not permitted|eperm|eacces)(\b|:)/i.test(message);
|
|
77
|
+
}
|
|
53
78
|
export function extractTerminalStreamOutput(event) {
|
|
54
79
|
if (typeof event !== "object" || !event)
|
|
55
80
|
return "";
|
|
@@ -109,6 +134,10 @@ export function extractAgentStep(event) {
|
|
|
109
134
|
const outputSummary = formatStepValue(typed.data?.output, 80);
|
|
110
135
|
return outputSummary ? `tool ${name || "tool"} done ${outputSummary}` : `tool ${name || "tool"} done`;
|
|
111
136
|
}
|
|
137
|
+
if (typed.event === "on_tool_error" || (typed.event === "on_chain_error" && typed.run_type === "tool")) {
|
|
138
|
+
const errorSummary = formatStepValue(typed.data?.error ?? typed.data?.output, 80);
|
|
139
|
+
return errorSummary ? `tool ${name || "tool"} error ${errorSummary}` : `tool ${name || "tool"} error`;
|
|
140
|
+
}
|
|
112
141
|
if (typed.event === "on_chat_model_start") {
|
|
113
142
|
const inputTools = typed.data?.input?.tools;
|
|
114
143
|
const tools = typed.invocation_params?.tools ?? inputTools ?? [];
|
|
@@ -148,14 +177,17 @@ export function extractToolResult(event) {
|
|
|
148
177
|
return null;
|
|
149
178
|
const typed = event;
|
|
150
179
|
const isToolEnd = typed.event === "on_tool_end" || (typed.event === "on_chain_end" && typed.run_type === "tool");
|
|
180
|
+
const isToolError = typed.event === "on_tool_error" || (typed.event === "on_chain_error" && typed.run_type === "tool");
|
|
151
181
|
const toolName = typeof typed.name === "string" ? typed.name : "";
|
|
152
|
-
if (!isToolEnd || !toolName) {
|
|
182
|
+
if ((!isToolEnd && !isToolError) || !toolName) {
|
|
153
183
|
return null;
|
|
154
184
|
}
|
|
155
|
-
const rawOutput = typed.data?.output;
|
|
185
|
+
const rawOutput = isToolError ? typed.data?.error ?? typed.data?.output : typed.data?.output;
|
|
186
|
+
const normalizedOutput = typeof rawOutput === "string" ? parseMaybeJson(rawOutput) : rawOutput;
|
|
156
187
|
return {
|
|
157
188
|
toolName,
|
|
158
|
-
output:
|
|
189
|
+
output: normalizedOutput,
|
|
190
|
+
isError: isToolError || isErrorLikeToolOutput(normalizedOutput),
|
|
159
191
|
};
|
|
160
192
|
}
|
|
161
193
|
export function extractInterruptPayload(event) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ApprovalRecord, HarnessEvent, WorkspaceBundle } from "../../contracts/types.js";
|
|
2
2
|
export declare function renderRuntimeFailure(error: unknown): string;
|
|
3
|
+
export declare function renderToolFailure(toolName: string, output: unknown): string;
|
|
3
4
|
export declare function parseInterruptContent(content: string): {
|
|
4
5
|
toolName?: string;
|
|
5
6
|
toolId?: string;
|
|
@@ -3,6 +3,33 @@ export function renderRuntimeFailure(error) {
|
|
|
3
3
|
const message = error instanceof Error ? error.message : String(error);
|
|
4
4
|
return `runtime_error=${message}`.trim();
|
|
5
5
|
}
|
|
6
|
+
function readToolErrorMessage(output) {
|
|
7
|
+
if (typeof output === "string" && output.trim()) {
|
|
8
|
+
return output.trim();
|
|
9
|
+
}
|
|
10
|
+
if (output instanceof Error) {
|
|
11
|
+
return output.message;
|
|
12
|
+
}
|
|
13
|
+
if (typeof output !== "object" || !output) {
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
const record = output;
|
|
17
|
+
for (const candidate of [record.error, record.message, record.detail, record.details]) {
|
|
18
|
+
if (typeof candidate === "string" && candidate.trim()) {
|
|
19
|
+
return candidate.trim();
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
return JSON.stringify(output);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return String(output);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export function renderToolFailure(toolName, output) {
|
|
30
|
+
const detail = readToolErrorMessage(output);
|
|
31
|
+
return detail ? `Tool ${toolName} failed: ${detail}` : `Tool ${toolName} failed.`;
|
|
32
|
+
}
|
|
6
33
|
export function parseInterruptContent(content) {
|
|
7
34
|
try {
|
|
8
35
|
const raw = JSON.parse(content);
|