@botbotgo/agent-harness 0.0.29 → 0.0.30

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.28";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.29";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.28";
1
+ export const AGENT_HARNESS_VERSION = "0.0.29";
@@ -6,9 +6,16 @@ type RunnableLike = {
6
6
  streamEvents?: (input: unknown, config?: Record<string, unknown>) => Promise<AsyncIterable<unknown>>;
7
7
  };
8
8
  declare const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
9
+ declare class RuntimeOperationTimeoutError extends Error {
10
+ readonly timeoutMs: number;
11
+ constructor(operation: string, timeoutMs: number);
12
+ }
9
13
  export declare class AgentRuntimeAdapter {
10
14
  private readonly options;
11
15
  constructor(options?: RuntimeAdapterOptions);
16
+ private resolveBindingTimeout;
17
+ private withTimeout;
18
+ private iterateWithTimeout;
12
19
  private materializeModelStream;
13
20
  private createModelFallbackRunnable;
14
21
  private applyStrictToolJsonInstruction;
@@ -32,4 +39,4 @@ export declare class AgentRuntimeAdapter {
32
39
  invoke(binding: CompiledAgentBinding, input: MessageContent, threadId: string, runId: string, resumePayload?: unknown, history?: TranscriptMessage[]): Promise<RunResult>;
33
40
  stream(binding: CompiledAgentBinding, input: MessageContent, threadId: string, history?: TranscriptMessage[]): AsyncGenerator<RuntimeStreamChunk | string>;
34
41
  }
35
- export { AgentRuntimeAdapter as RuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX, AGENT_INTERRUPT_SENTINEL_PREFIX as INTERRUPT_SENTINEL_PREFIX, };
42
+ export { AgentRuntimeAdapter as RuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX, AGENT_INTERRUPT_SENTINEL_PREFIX as INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError, };
@@ -12,9 +12,20 @@ import { resolveDeclaredMiddleware } from "./declared-middleware.js";
12
12
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
13
13
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
14
14
  const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
15
+ class RuntimeOperationTimeoutError extends Error {
16
+ timeoutMs;
17
+ constructor(operation, timeoutMs) {
18
+ super(`${operation} timed out after ${timeoutMs}ms`);
19
+ this.timeoutMs = timeoutMs;
20
+ this.name = "RuntimeOperationTimeoutError";
21
+ }
22
+ }
15
23
  function asObject(value) {
16
24
  return typeof value === "object" && value ? value : undefined;
17
25
  }
26
+ function resolveTimeoutMs(value) {
27
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : undefined;
28
+ }
18
29
  function isPlaceholderApiKey(value) {
19
30
  return typeof value === "string" && value.trim().toLowerCase() === "dummy";
20
31
  }
@@ -113,6 +124,44 @@ export class AgentRuntimeAdapter {
113
124
  constructor(options = {}) {
114
125
  this.options = options;
115
126
  }
127
+ resolveBindingTimeout(binding) {
128
+ return resolveTimeoutMs(binding.langchainAgentParams?.model.init.timeout ?? binding.deepAgentParams?.model.init.timeout);
129
+ }
130
+ async withTimeout(producer, timeoutMs, operation) {
131
+ if (!timeoutMs) {
132
+ return Promise.resolve(producer());
133
+ }
134
+ return new Promise((resolve, reject) => {
135
+ const timer = setTimeout(() => reject(new RuntimeOperationTimeoutError(operation, timeoutMs)), timeoutMs);
136
+ Promise.resolve(producer()).then((value) => {
137
+ clearTimeout(timer);
138
+ resolve(value);
139
+ }, (error) => {
140
+ clearTimeout(timer);
141
+ reject(error);
142
+ });
143
+ });
144
+ }
145
+ async *iterateWithTimeout(iterable, timeoutMs, operation) {
146
+ const iterator = iterable[Symbol.asyncIterator]();
147
+ try {
148
+ for (;;) {
149
+ const next = await this.withTimeout(() => iterator.next(), timeoutMs, operation);
150
+ if (next.done) {
151
+ return;
152
+ }
153
+ yield next.value;
154
+ }
155
+ }
156
+ finally {
157
+ if (typeof iterator.return === "function") {
158
+ const returnResult = iterator.return();
159
+ if (returnResult && typeof returnResult.then === "function") {
160
+ void returnResult.catch(() => undefined);
161
+ }
162
+ }
163
+ }
164
+ }
116
165
  async materializeModelStream(streamFactory, input, config) {
117
166
  const stream = await streamFactory(input, config);
118
167
  let content = "";
@@ -193,7 +242,7 @@ export class AgentRuntimeAdapter {
193
242
  if (!model?.invoke) {
194
243
  return "";
195
244
  }
196
- const synthesized = await model.invoke([
245
+ const synthesized = await this.withTimeout(() => model.invoke([
197
246
  {
198
247
  role: "system",
199
248
  content: "The previous agent run completed tool work but did not produce a final user-facing answer. Write the final answer now using the tool results provided. Do not expose internal state, tools, or reasoning.",
@@ -202,7 +251,7 @@ export class AgentRuntimeAdapter {
202
251
  role: "user",
203
252
  content: `Original user request:\n${extractMessageText(input)}\n\nTool results:\n${toolContext}`,
204
253
  },
205
- ]);
254
+ ]), this.resolveBindingTimeout(binding), "deepagent synthesis invoke");
206
255
  return sanitizeVisibleText(extractVisibleOutput(synthesized));
207
256
  }
208
257
  async resolveModel(model) {
@@ -421,7 +470,7 @@ export class AgentRuntimeAdapter {
421
470
  let result;
422
471
  try {
423
472
  const runnable = await this.create(binding);
424
- result = (await runnable.invoke(request, { configurable: { thread_id: threadId } }));
473
+ result = (await this.withTimeout(() => runnable.invoke(request, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(binding), "agent invoke"));
425
474
  }
426
475
  catch (error) {
427
476
  if (resumePayload !== undefined || !isToolCallParseFailure(error)) {
@@ -429,7 +478,7 @@ export class AgentRuntimeAdapter {
429
478
  }
430
479
  const retriedBinding = this.applyStrictToolJsonInstruction(binding);
431
480
  const runnable = await this.create(retriedBinding);
432
- result = (await runnable.invoke({ messages: this.buildAgentMessages(history, input) }, { configurable: { thread_id: threadId } }));
481
+ result = (await this.withTimeout(() => runnable.invoke({ messages: this.buildAgentMessages(history, input) }, { configurable: { thread_id: threadId } }), this.resolveBindingTimeout(retriedBinding), "agent invoke"));
433
482
  }
434
483
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
435
484
  const extractedOutput = extractVisibleOutput(result);
@@ -452,9 +501,11 @@ export class AgentRuntimeAdapter {
452
501
  }
453
502
  async *stream(binding, input, threadId, history = []) {
454
503
  try {
504
+ const timeoutMs = this.resolveBindingTimeout(binding);
455
505
  if (binding.langchainAgentParams) {
506
+ const langchainParams = binding.langchainAgentParams;
456
507
  const resolvedModel = (await this.resolveModel(binding.langchainAgentParams.model));
457
- const tools = this.resolveTools(binding.langchainAgentParams.tools, binding);
508
+ const tools = this.resolveTools(langchainParams.tools, binding);
458
509
  const canUseDirectModelStream = tools.length === 0 || typeof resolvedModel.bindTools !== "function";
459
510
  const model = canUseDirectModelStream
460
511
  ? resolvedModel
@@ -465,8 +516,8 @@ export class AgentRuntimeAdapter {
465
516
  // agent loop and only adds an extra model round-trip before the runnable path.
466
517
  if (canUseDirectModelStream && typeof model.stream === "function") {
467
518
  let emitted = false;
468
- const stream = await model.stream(this.buildRawModelMessages(binding.langchainAgentParams.systemPrompt, history, input));
469
- for await (const chunk of stream) {
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")) {
470
521
  const delta = readStreamDelta(chunk);
471
522
  if (delta) {
472
523
  emitted = true;
@@ -486,11 +537,11 @@ export class AgentRuntimeAdapter {
486
537
  const runnable = await this.create(binding);
487
538
  const request = { messages: this.buildAgentMessages(history, input) };
488
539
  if (typeof runnable.streamEvents === "function") {
489
- const events = await runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" });
540
+ const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2" }), timeoutMs, "agent streamEvents start");
490
541
  let terminalOutput = "";
491
542
  const seenTerminalOutputs = new Set();
492
543
  let lastStep = "";
493
- for await (const event of events) {
544
+ for await (const event of this.iterateWithTimeout(events, timeoutMs, "agent streamEvents")) {
494
545
  const interruptPayload = extractInterruptPayload(event);
495
546
  if (interruptPayload) {
496
547
  yield { kind: "interrupt", content: interruptPayload };
@@ -527,9 +578,9 @@ export class AgentRuntimeAdapter {
527
578
  }
528
579
  }
529
580
  if (binding.langchainAgentParams && typeof runnable.stream === "function") {
530
- const stream = await runnable.stream(request, { configurable: { thread_id: threadId } });
581
+ const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), timeoutMs, "agent stream start");
531
582
  let emitted = false;
532
- for await (const chunk of stream) {
583
+ for await (const chunk of this.iterateWithTimeout(stream, timeoutMs, "agent stream")) {
533
584
  const delta = readStreamDelta(chunk);
534
585
  if (delta) {
535
586
  emitted = true;
@@ -566,4 +617,4 @@ export class AgentRuntimeAdapter {
566
617
  }
567
618
  }
568
619
  }
569
- export { AgentRuntimeAdapter as RuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX, AGENT_INTERRUPT_SENTINEL_PREFIX as INTERRUPT_SENTINEL_PREFIX, };
620
+ export { AgentRuntimeAdapter as RuntimeAdapter, AGENT_INTERRUPT_SENTINEL_PREFIX, AGENT_INTERRUPT_SENTINEL_PREFIX as INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError, };
@@ -1,7 +1,7 @@
1
1
  import { AUTO_AGENT_ID } from "../contracts/types.js";
2
2
  import { FilePersistence } from "../persistence/file-store.js";
3
3
  import { createPersistentId } from "../utils/id.js";
4
- import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter } from "./agent-runtime-adapter.js";
4
+ import { AGENT_INTERRUPT_SENTINEL_PREFIX, AgentRuntimeAdapter, RuntimeOperationTimeoutError } from "./agent-runtime-adapter.js";
5
5
  import { createResourceBackendResolver, createResourceToolResolver } from "../resource/resource.js";
6
6
  import { EventBus } from "./event-bus.js";
7
7
  import { PolicyEngine } from "./policy-engine.js";
@@ -628,6 +628,20 @@ export class AgentHarness {
628
628
  }) };
629
629
  return;
630
630
  }
631
+ if (error instanceof RuntimeOperationTimeoutError) {
632
+ yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "failed", {
633
+ previousState: null,
634
+ error: error.message,
635
+ }) };
636
+ yield {
637
+ type: "content",
638
+ threadId,
639
+ runId,
640
+ agentId: selectedAgentId,
641
+ content: renderRuntimeFailure(error),
642
+ };
643
+ return;
644
+ }
631
645
  try {
632
646
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId);
633
647
  await this.appendAssistantMessage(threadId, runId, actual.output);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.29",
3
+ "version": "0.0.30",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",