@botbotgo/agent-harness 0.0.5 → 0.0.6

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.
@@ -5,7 +5,7 @@ import { ChatGoogle } from "@langchain/google";
5
5
  import { ChatOllama } from "@langchain/ollama";
6
6
  import { ChatOpenAI } from "@langchain/openai";
7
7
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
8
- import { extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
8
+ import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
9
9
  import { extractAgentStep, extractInterruptPayload, extractReasoningStreamOutput, extractTerminalStreamOutput, extractToolResult, normalizeTerminalOutputKey, readStreamDelta, } from "./parsing/stream-event-parsing.js";
10
10
  import { wrapToolForExecution } from "./tool-hitl.js";
11
11
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
@@ -382,8 +382,13 @@ export class AgentRuntimeAdapter {
382
382
  const interruptContent = Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? JSON.stringify(result.__interrupt__) : undefined;
383
383
  const extractedOutput = extractVisibleOutput(result);
384
384
  const visibleOutput = extractedOutput && !isLikelyToolArgsObject(tryParseJson(extractedOutput)) ? extractedOutput : "";
385
+ const emptyAssistantMessageFailure = extractEmptyAssistantMessageFailure(result);
385
386
  const toolFallback = extractToolFallbackContext(result);
386
- const output = visibleOutput || (await this.synthesizeDeepAgentAnswer(binding, input, result)) || toolFallback || JSON.stringify(result, null, 2);
387
+ const synthesizedOutput = await this.synthesizeDeepAgentAnswer(binding, input, result);
388
+ if (!visibleOutput && !synthesizedOutput && !toolFallback && emptyAssistantMessageFailure) {
389
+ throw new Error(emptyAssistantMessageFailure);
390
+ }
391
+ const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
387
392
  return {
388
393
  threadId,
389
394
  runId,
@@ -6,6 +6,7 @@ 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 extractEmptyAssistantMessageFailure(value: unknown): string;
9
10
  export declare function isToolCallParseFailure(error: unknown): boolean;
10
11
  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.";
11
12
  export declare function wrapResolvedModel<T>(value: T): T;
@@ -225,19 +225,56 @@ function extractAssistantTextFromMessages(messages) {
225
225
  : typeof message.getType === "function"
226
226
  ? message.getType()
227
227
  : undefined;
228
+ if (!(typeName === "AIMessage" || runtimeType === "ai"))
229
+ continue;
228
230
  const kwargs = typeof typed.kwargs === "object" && typed.kwargs ? typed.kwargs : undefined;
229
- if (typeName === "AIMessage" || runtimeType === "ai" || kwargs?.content !== undefined) {
230
- if (hasToolCalls(message))
231
- continue;
232
- if (Array.isArray(kwargs?.tool_calls) && kwargs.tool_calls.length > 0)
233
- continue;
234
- const content = extractMessageContent(message);
235
- if (content)
236
- return content;
237
- }
231
+ if (hasToolCalls(message))
232
+ continue;
233
+ if (Array.isArray(kwargs?.tool_calls) && kwargs.tool_calls.length > 0)
234
+ continue;
235
+ const content = extractMessageContent(message);
236
+ if (content)
237
+ return content;
238
238
  }
239
239
  return "";
240
240
  }
241
+ function extractEmptyAssistantMessageReason(messages) {
242
+ if (!Array.isArray(messages))
243
+ return null;
244
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
245
+ const message = messages[index];
246
+ if (typeof message !== "object" || !message)
247
+ continue;
248
+ const typed = message;
249
+ const ids = Array.isArray(typed.id) ? typed.id.filter((item) => typeof item === "string") : [];
250
+ const typeName = ids.at(-1);
251
+ const runtimeType = typeof message._getType === "function"
252
+ ? message._getType()
253
+ : typeof message.getType === "function"
254
+ ? message.getType()
255
+ : undefined;
256
+ const kwargs = typeof typed.kwargs === "object" && typed.kwargs ? typed.kwargs : undefined;
257
+ if (!(typeName === "AIMessage" || runtimeType === "ai")) {
258
+ continue;
259
+ }
260
+ if (hasToolCalls(message)) {
261
+ return null;
262
+ }
263
+ const content = extractMessageContent(message).trim();
264
+ if (content) {
265
+ return null;
266
+ }
267
+ const responseMetadata = typeof kwargs?.response_metadata === "object" && kwargs.response_metadata
268
+ ? kwargs.response_metadata
269
+ : {};
270
+ return {
271
+ finishReason: typeof responseMetadata.finish_reason === "string" ? responseMetadata.finish_reason : undefined,
272
+ modelProvider: typeof responseMetadata.model_provider === "string" ? responseMetadata.model_provider : undefined,
273
+ modelName: typeof responseMetadata.model_name === "string" ? responseMetadata.model_name : undefined,
274
+ };
275
+ }
276
+ return null;
277
+ }
241
278
  function extractStructuredOutputText(value) {
242
279
  if (typeof value !== "object" || !value)
243
280
  return "";
@@ -304,6 +341,27 @@ export function extractVisibleOutput(value) {
304
341
  }
305
342
  return "";
306
343
  }
344
+ export function extractEmptyAssistantMessageFailure(value) {
345
+ if (typeof value !== "object" || !value)
346
+ return "";
347
+ const typed = value;
348
+ const reason = extractEmptyAssistantMessageReason(typed.messages)
349
+ ?? (typeof typed.output === "object" && typed.output
350
+ ? extractEmptyAssistantMessageReason(typed.output.messages)
351
+ : null);
352
+ if (!reason) {
353
+ return "";
354
+ }
355
+ const detailParts = [
356
+ reason.finishReason ? `finish_reason=${reason.finishReason}` : "",
357
+ reason.modelProvider ? `model_provider=${reason.modelProvider}` : "",
358
+ reason.modelName ? `model_name=${reason.modelName}` : "",
359
+ ].filter(Boolean);
360
+ return [
361
+ "empty_final_ai_message: model returned no visible assistant content and no tool calls",
362
+ detailParts.length > 0 ? `(${detailParts.join(", ")})` : "",
363
+ ].filter(Boolean).join(" ");
364
+ }
307
365
  function isAiMessageLike(value) {
308
366
  if (typeof value !== "object" || !value)
309
367
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -25,7 +25,7 @@
25
25
  }
26
26
  },
27
27
  "dependencies": {
28
- "@botbotgo/agent-harness-builtin": "0.0.5"
28
+ "@botbotgo/agent-harness-builtin": "0.0.6"
29
29
  },
30
30
  "scripts": {
31
31
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",