@botbotgo/agent-harness 0.0.30 → 0.0.32

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.
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.29";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.31";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.29";
1
+ export const AGENT_HARNESS_VERSION = "0.0.31";
@@ -7,13 +7,16 @@ type RunnableLike = {
7
7
  };
8
8
  declare const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
9
9
  declare class RuntimeOperationTimeoutError extends Error {
10
+ readonly operation: string;
10
11
  readonly timeoutMs: number;
11
- constructor(operation: string, timeoutMs: number);
12
+ readonly stage: "stream" | "invoke";
13
+ constructor(operation: string, timeoutMs: number, stage?: "stream" | "invoke");
12
14
  }
13
15
  export declare class AgentRuntimeAdapter {
14
16
  private readonly options;
15
17
  constructor(options?: RuntimeAdapterOptions);
16
18
  private resolveBindingTimeout;
19
+ private resolveStreamIdleTimeout;
17
20
  private withTimeout;
18
21
  private iterateWithTimeout;
19
22
  private materializeModelStream;
@@ -1,10 +1,12 @@
1
1
  import { Command, MemorySaver } from "@langchain/langgraph";
2
+ import { tool as createLangChainTool } from "@langchain/core/tools";
2
3
  import { createDeepAgent } from "deepagents";
3
4
  import { ChatAnthropic } from "@langchain/anthropic";
4
5
  import { ChatGoogle } from "@langchain/google";
5
6
  import { ChatOllama } from "@langchain/ollama";
6
7
  import { ChatOpenAI } from "@langchain/openai";
7
8
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
9
+ import { z } from "zod";
8
10
  import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
9
11
  import { extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
10
12
  import { wrapToolForExecution } from "./tool-hitl.js";
@@ -13,10 +15,14 @@ import { extractMessageText, normalizeMessageContent } from "../utils/message-co
13
15
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
14
16
  const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
15
17
  class RuntimeOperationTimeoutError extends Error {
18
+ operation;
16
19
  timeoutMs;
17
- constructor(operation, timeoutMs) {
20
+ stage;
21
+ constructor(operation, timeoutMs, stage = operation.includes("stream") ? "stream" : "invoke") {
18
22
  super(`${operation} timed out after ${timeoutMs}ms`);
23
+ this.operation = operation;
19
24
  this.timeoutMs = timeoutMs;
25
+ this.stage = stage;
20
26
  this.name = "RuntimeOperationTimeoutError";
21
27
  }
22
28
  }
@@ -116,6 +122,37 @@ function wrapResolvedToolWithModelFacingName(resolvedTool, modelFacingName) {
116
122
  },
117
123
  });
118
124
  }
125
+ function hasCallableToolHandler(value) {
126
+ if (typeof value !== "object" || value === null) {
127
+ return false;
128
+ }
129
+ const typed = value;
130
+ return typeof typed.invoke === "function" || typeof typed.call === "function" || typeof typed.func === "function";
131
+ }
132
+ function normalizeResolvedToolSchema(resolvedTool) {
133
+ const schema = resolvedTool.schema;
134
+ if (schema && typeof schema.parse === "function" && "_def" in schema) {
135
+ return resolvedTool.schema;
136
+ }
137
+ if (schema && (schema.type === "object" || typeof schema.properties === "object")) {
138
+ return schema;
139
+ }
140
+ return z.object({}).passthrough();
141
+ }
142
+ function asStructuredExecutableTool(resolvedTool, modelFacingName, description) {
143
+ if (!hasCallableToolHandler(resolvedTool)) {
144
+ return resolvedTool;
145
+ }
146
+ const handler = resolvedTool.invoke ?? resolvedTool.call ?? resolvedTool.func;
147
+ if (typeof handler !== "function") {
148
+ return resolvedTool;
149
+ }
150
+ return createLangChainTool(async (input, config) => handler(input, config), {
151
+ name: modelFacingName,
152
+ description,
153
+ schema: normalizeResolvedToolSchema(resolvedTool),
154
+ });
155
+ }
119
156
  function countConfiguredTools(binding) {
120
157
  return binding.langchainAgentParams?.tools.length ?? binding.deepAgentParams?.tools.length ?? 0;
121
158
  }
@@ -127,12 +164,23 @@ export class AgentRuntimeAdapter {
127
164
  resolveBindingTimeout(binding) {
128
165
  return resolveTimeoutMs(binding.langchainAgentParams?.model.init.timeout ?? binding.deepAgentParams?.model.init.timeout);
129
166
  }
130
- async withTimeout(producer, timeoutMs, operation) {
167
+ resolveStreamIdleTimeout(binding) {
168
+ const configuredIdleTimeout = resolveTimeoutMs(binding.langchainAgentParams?.model.init.streamIdleTimeout ?? binding.deepAgentParams?.model.init.streamIdleTimeout);
169
+ if (configuredIdleTimeout) {
170
+ return configuredIdleTimeout;
171
+ }
172
+ const invokeTimeout = this.resolveBindingTimeout(binding);
173
+ if (invokeTimeout) {
174
+ return Math.min(invokeTimeout, 15_000);
175
+ }
176
+ return 15_000;
177
+ }
178
+ async withTimeout(producer, timeoutMs, operation, stage = operation.includes("stream") ? "stream" : "invoke") {
131
179
  if (!timeoutMs) {
132
180
  return Promise.resolve(producer());
133
181
  }
134
182
  return new Promise((resolve, reject) => {
135
- const timer = setTimeout(() => reject(new RuntimeOperationTimeoutError(operation, timeoutMs)), timeoutMs);
183
+ const timer = setTimeout(() => reject(new RuntimeOperationTimeoutError(operation, timeoutMs, stage)), timeoutMs);
136
184
  Promise.resolve(producer()).then((value) => {
137
185
  clearTimeout(timer);
138
186
  resolve(value);
@@ -146,7 +194,7 @@ export class AgentRuntimeAdapter {
146
194
  const iterator = iterable[Symbol.asyncIterator]();
147
195
  try {
148
196
  for (;;) {
149
- const next = await this.withTimeout(() => iterator.next(), timeoutMs, operation);
197
+ const next = await this.withTimeout(() => iterator.next(), timeoutMs, operation, "stream");
150
198
  if (next.done) {
151
199
  return;
152
200
  }
@@ -251,7 +299,7 @@ export class AgentRuntimeAdapter {
251
299
  role: "user",
252
300
  content: `Original user request:\n${extractMessageText(input)}\n\nTool results:\n${toolContext}`,
253
301
  },
254
- ]), this.resolveBindingTimeout(binding), "deepagent synthesis invoke");
302
+ ]), this.resolveBindingTimeout(binding), "deepagent synthesis invoke", "invoke");
255
303
  return sanitizeVisibleText(extractVisibleOutput(synthesized));
256
304
  }
257
305
  async resolveModel(model) {
@@ -302,6 +350,10 @@ export class AgentRuntimeAdapter {
302
350
  }
303
351
  const wrappedTool = wrapToolForExecution(resolvedTool, compiledTool);
304
352
  const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
353
+ const structuredTool = asStructuredExecutableTool(wrappedTool, modelFacingName, compiledTool.description);
354
+ if (structuredTool !== wrappedTool) {
355
+ return structuredTool;
356
+ }
305
357
  return modelFacingName === compiledTool.name ? wrappedTool : wrapResolvedToolWithModelFacingName(wrappedTool, modelFacingName);
306
358
  });
307
359
  }
@@ -470,7 +522,7 @@ export class AgentRuntimeAdapter {
470
522
  let result;
471
523
  try {
472
524
  const runnable = await this.create(binding);
473
- result = (await this.withTimeout(() => runnable.invoke(request, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(binding), "agent invoke"));
525
+ result = (await this.withTimeout(() => runnable.invoke(request, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(binding), "agent invoke", "invoke"));
474
526
  }
475
527
  catch (error) {
476
528
  if (resumePayload !== undefined || !isToolCallParseFailure(error)) {
@@ -478,7 +530,7 @@ export class AgentRuntimeAdapter {
478
530
  }
479
531
  const retriedBinding = this.applyStrictToolJsonInstruction(binding);
480
532
  const runnable = await this.create(retriedBinding);
481
- result = (await this.withTimeout(() => runnable.invoke({ messages: this.buildAgentMessages(history, input) }, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(retriedBinding), "agent invoke"));
533
+ result = (await this.withTimeout(() => runnable.invoke({ messages: this.buildAgentMessages(history, input) }, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(retriedBinding), "agent invoke", "invoke"));
482
534
  }
483
535
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
484
536
  const extractedOutput = extractVisibleOutput(result);
@@ -501,7 +553,8 @@ export class AgentRuntimeAdapter {
501
553
  }
502
554
  async *stream(binding, input, threadId, history = []) {
503
555
  try {
504
- const timeoutMs = this.resolveBindingTimeout(binding);
556
+ const invokeTimeoutMs = this.resolveBindingTimeout(binding);
557
+ const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
505
558
  if (binding.langchainAgentParams) {
506
559
  const langchainParams = binding.langchainAgentParams;
507
560
  const resolvedModel = (await this.resolveModel(binding.langchainAgentParams.model));
@@ -516,8 +569,8 @@ export class AgentRuntimeAdapter {
516
569
  // agent loop and only adds an extra model round-trip before the runnable path.
517
570
  if (canUseDirectModelStream && typeof model.stream === "function") {
518
571
  let emitted = false;
519
- const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), timeoutMs, "model stream start");
520
- for await (const chunk of this.iterateWithTimeout(stream, timeoutMs, "model stream")) {
572
+ const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), invokeTimeoutMs, "model stream start", "stream");
573
+ for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream")) {
521
574
  const delta = readStreamDelta(chunk);
522
575
  if (delta) {
523
576
  emitted = true;
@@ -537,11 +590,11 @@ export class AgentRuntimeAdapter {
537
590
  const runnable = await this.create(binding);
538
591
  const request = { messages: this.buildAgentMessages(history, input) };
539
592
  if (typeof runnable.streamEvents === "function") {
540
- const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), timeoutMs, "agent streamEvents start");
593
+ const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), invokeTimeoutMs, "agent streamEvents start", "stream");
541
594
  let terminalOutput = "";
542
595
  const seenTerminalOutputs = new Set();
543
596
  let lastStep = "";
544
- for await (const event of this.iterateWithTimeout(events, timeoutMs, "agent streamEvents")) {
597
+ for await (const event of this.iterateWithTimeout(events, streamIdleTimeoutMs, "agent streamEvents")) {
545
598
  const interruptPayload = extractInterruptPayload(event);
546
599
  if (interruptPayload) {
547
600
  yield { kind: "interrupt", content: interruptPayload };
@@ -578,9 +631,9 @@ export class AgentRuntimeAdapter {
578
631
  }
579
632
  }
580
633
  if (binding.langchainAgentParams && typeof runnable.stream === "function") {
581
- const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), timeoutMs, "agent stream start");
634
+ const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), invokeTimeoutMs, "agent stream start", "stream");
582
635
  let emitted = false;
583
- for await (const chunk of this.iterateWithTimeout(stream, timeoutMs, "agent stream")) {
636
+ for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream")) {
584
637
  const delta = readStreamDelta(chunk);
585
638
  if (delta) {
586
639
  emitted = true;
@@ -628,7 +628,7 @@ export class AgentHarness {
628
628
  }) };
629
629
  return;
630
630
  }
631
- if (error instanceof RuntimeOperationTimeoutError) {
631
+ if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
632
632
  yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
633
633
  previousState: null,
634
634
  error: error.message,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",