@botbotgo/agent-harness 0.0.326 → 0.0.328

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.
Files changed (56) hide show
  1. package/dist/cli/chat-stream.js +33 -27
  2. package/dist/cli/main.js +30 -3
  3. package/dist/contracts/runtime-requests.d.ts +1 -2
  4. package/dist/contracts/runtime-scheduling.d.ts +1 -1
  5. package/dist/flow/flow-graph-upstream.js +3 -7
  6. package/dist/package-version.d.ts +1 -1
  7. package/dist/package-version.js +1 -1
  8. package/dist/projections/request-events.js +0 -1
  9. package/dist/resource/isolation.js +51 -10
  10. package/dist/resources/toolkit.mjs +183 -0
  11. package/dist/resources/tools/cancel_request.mjs +1 -1
  12. package/dist/resources/tools/fetch_url.mjs +1 -1
  13. package/dist/resources/tools/http_request.mjs +1 -1
  14. package/dist/resources/tools/inspect_approvals.mjs +1 -1
  15. package/dist/resources/tools/inspect_artifacts.mjs +1 -1
  16. package/dist/resources/tools/inspect_events.mjs +1 -1
  17. package/dist/resources/tools/inspect_requests.mjs +1 -1
  18. package/dist/resources/tools/inspect_sessions.mjs +1 -1
  19. package/dist/resources/tools/list_files.mjs +1 -1
  20. package/dist/resources/tools/read_artifact.mjs +1 -1
  21. package/dist/resources/tools/request_approval.mjs +1 -1
  22. package/dist/resources/tools/run_command.mjs +1 -1
  23. package/dist/resources/tools/schedule_task.mjs +1 -1
  24. package/dist/resources/tools/search_files.mjs +1 -1
  25. package/dist/resources/tools/send_message.mjs +1 -1
  26. package/dist/runtime/adapter/compat/deepagent-compat.d.ts +0 -9
  27. package/dist/runtime/adapter/compat/deepagent-compat.js +0 -22
  28. package/dist/runtime/adapter/flow/stream-runtime.d.ts +4 -0
  29. package/dist/runtime/adapter/flow/stream-runtime.js +239 -8
  30. package/dist/runtime/adapter/local-tool-invocation.js +53 -0
  31. package/dist/runtime/adapter/middleware-assembly.js +174 -29
  32. package/dist/runtime/adapter/runtime-adapter-support.js +1 -2
  33. package/dist/runtime/adapter/stream-event-projection.d.ts +17 -0
  34. package/dist/runtime/adapter/stream-event-projection.js +217 -4
  35. package/dist/runtime/adapter/tool/builtin-middleware-tools.d.ts +0 -3
  36. package/dist/runtime/adapter/tool/builtin-middleware-tools.js +37 -17
  37. package/dist/runtime/adapter/tool/resolved-tool.js +29 -3
  38. package/dist/runtime/agent-runtime-adapter.d.ts +3 -3
  39. package/dist/runtime/agent-runtime-adapter.js +12 -33
  40. package/dist/runtime/agent-runtime-assembly.d.ts +3 -21
  41. package/dist/runtime/agent-runtime-assembly.js +4 -56
  42. package/dist/runtime/harness/run/inspection.js +21 -5
  43. package/dist/runtime/harness/run/run-operations.js +2 -1
  44. package/dist/runtime/harness/run/stream-run.d.ts +3 -1
  45. package/dist/runtime/harness/run/stream-run.js +206 -30
  46. package/dist/runtime/harness.js +3 -0
  47. package/dist/runtime/parsing/output-content.js +11 -4
  48. package/dist/runtime/parsing/output-recovery.d.ts +3 -0
  49. package/dist/runtime/parsing/output-recovery.js +57 -11
  50. package/dist/runtime/parsing/output-tool-args.d.ts +4 -0
  51. package/dist/runtime/parsing/output-tool-args.js +122 -0
  52. package/dist/runtime/parsing/stream-event-parsing.js +37 -3
  53. package/dist/runtime/support/harness-support.d.ts +1 -0
  54. package/dist/runtime/support/harness-support.js +44 -2
  55. package/dist/tools.js +34 -4
  56. package/package.json +8 -8
@@ -1,6 +1,7 @@
1
1
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from "../../agent-runtime-adapter.js";
2
+ import { ExecutionReconciliationError } from "../../adapter/flow/stream-runtime.js";
2
3
  import { buildRequestPlanState, summarizeBuiltinWriteTodosArgs } from "../../adapter/runtime-adapter-support.js";
3
- import { renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
4
+ import { describeRuntimeError, renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
4
5
  import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
5
6
  import { createContentBlocksItem, createToolResultKey, } from "../events/streaming.js";
6
7
  import { projectRuntimeSurfaceFromSingleUpstreamEvent } from "./inspection.js";
@@ -19,10 +20,64 @@ function createInitialPlanState(sessionId, requestId, updatedAt) {
19
20
  completed: 0,
20
21
  failed: 0,
21
22
  cancelled: 0,
22
- blocked: 0,
23
23
  },
24
24
  };
25
25
  }
26
+ function planStateHasUnfinishedItems(planState) {
27
+ if (!planState) {
28
+ return false;
29
+ }
30
+ return planState.summary.pending > 0 || planState.summary.inProgress > 0;
31
+ }
32
+ function planStateHasActiveItems(planState) {
33
+ if (!planState) {
34
+ return false;
35
+ }
36
+ return planState.summary.pending > 0 || planState.summary.inProgress > 0;
37
+ }
38
+ function readTerminalStructuredStatus(value) {
39
+ if (typeof value === "string") {
40
+ try {
41
+ return readTerminalStructuredStatus(JSON.parse(value));
42
+ }
43
+ catch {
44
+ return null;
45
+ }
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return null;
49
+ }
50
+ const typed = value;
51
+ if (typed.status === "completed") {
52
+ return typed.status;
53
+ }
54
+ return (readTerminalStructuredStatus(typed.structuredResponse)
55
+ ?? readTerminalStructuredStatus(typed.output)
56
+ ?? readTerminalStructuredStatus(typed.data));
57
+ }
58
+ function reconcilePlanStateToTerminalStatus(planState, status, updatedAt) {
59
+ const items = planState.items.map((item) => ({
60
+ ...item,
61
+ status: item.status === "pending" || item.status === "in_progress"
62
+ ? status
63
+ : item.status,
64
+ }));
65
+ const summary = {
66
+ total: items.length,
67
+ pending: 0,
68
+ inProgress: 0,
69
+ completed: items.filter((item) => item.status === "completed").length,
70
+ failed: items.filter((item) => item.status === "failed").length,
71
+ cancelled: items.filter((item) => item.status === "cancelled").length,
72
+ };
73
+ return {
74
+ ...planState,
75
+ version: planState.version + 1,
76
+ updatedAt,
77
+ items,
78
+ summary,
79
+ };
80
+ }
26
81
  function getPlanStateFromToolResult(input) {
27
82
  if (typeof input.output !== "object" || input.output === null) {
28
83
  return null;
@@ -80,6 +135,26 @@ function buildPlanStateSignature(planState) {
80
135
  function countStructuredTodoIds(items) {
81
136
  return items.filter((item) => typeof item.id === "string" && item.id.length > 0).length;
82
137
  }
138
+ async function emitPlanStateUpdate(options, agentId, planState) {
139
+ await options.saveRequestPlanState?.(options.sessionId, options.requestId, planState);
140
+ return [{
141
+ type: "plan-state",
142
+ sessionId: options.sessionId,
143
+ requestId: options.requestId,
144
+ agentId,
145
+ planState,
146
+ }];
147
+ }
148
+ async function refreshPlanStateFromPersistence(options, currentPlanState) {
149
+ const persistedPlanState = await options.loadRequestPlanState?.(options.sessionId, options.requestId);
150
+ if (!persistedPlanState) {
151
+ return currentPlanState;
152
+ }
153
+ if (!currentPlanState || persistedPlanState.version >= currentPlanState.version) {
154
+ return persistedPlanState;
155
+ }
156
+ return currentPlanState;
157
+ }
83
158
  function shouldEmitPlanState(currentPlanState, nextPlanState) {
84
159
  if (!currentPlanState || currentPlanState.items.length === 0) {
85
160
  return true;
@@ -210,7 +285,6 @@ function summarizePlanState(planState) {
210
285
  planState.summary.inProgress > 0 ? `${planState.summary.inProgress} in progress` : "",
211
286
  planState.summary.pending > 0 ? `${planState.summary.pending} pending` : "",
212
287
  planState.summary.completed > 0 ? `${planState.summary.completed} completed` : "",
213
- planState.summary.blocked > 0 ? `${planState.summary.blocked} blocked` : "",
214
288
  planState.summary.failed > 0 ? `${planState.summary.failed} failed` : "",
215
289
  ].filter((value) => value.length > 0);
216
290
  if (counts.length === 0) {
@@ -228,6 +302,9 @@ function createSurfaceCommentary(surfaceItem) {
228
302
  return `Running tool ${name}.`;
229
303
  }
230
304
  if (surfaceItem.status === "completed") {
305
+ if (name.toLowerCase() === "task") {
306
+ return null;
307
+ }
231
308
  return `Tool ${name} completed.`;
232
309
  }
233
310
  if (surfaceItem.status === "failed") {
@@ -252,7 +329,7 @@ function createSurfaceCommentary(surfaceItem) {
252
329
  return `Delegating work to ${name}.`;
253
330
  }
254
331
  if (surfaceItem.status === "completed") {
255
- return `Delegation to ${name} completed.`;
332
+ return null;
256
333
  }
257
334
  if (surfaceItem.status === "failed") {
258
335
  return `Delegation to ${name} failed.`;
@@ -360,6 +437,7 @@ export async function* streamHarnessRun(options) {
360
437
  let streamActivityObserved = false;
361
438
  let nonUpstreamStreamActivityObserved = false;
362
439
  let assistantOutput = "";
440
+ let assistantOutputCameFromInvokeFallback = false;
363
441
  const bufferAssistantTextUntilCompletion = true;
364
442
  let currentAgentId = options.selectedAgentId;
365
443
  let currentAgentName = formatAgentName(options.selectedAgentId);
@@ -425,19 +503,32 @@ export async function* streamHarnessRun(options) {
425
503
  planStateVersion = upstreamPlanState.version;
426
504
  lastPlanStateSignature = signature;
427
505
  currentPlanState = upstreamPlanState;
428
- yield {
429
- type: "plan-state",
430
- sessionId: options.sessionId,
431
- requestId: options.requestId,
432
- agentId: currentAgentId,
433
- planState: upstreamPlanState,
434
- };
506
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, upstreamPlanState)) {
507
+ yield item;
508
+ }
435
509
  const commentary = summarizePlanState(upstreamPlanState);
436
510
  if (commentary) {
437
511
  yield* emitCommentary(commentary);
438
512
  }
439
513
  }
440
514
  }
515
+ const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.event);
516
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
517
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
518
+ const signature = buildPlanStateSignature(reconciledPlanState);
519
+ if (signature !== lastPlanStateSignature) {
520
+ planStateVersion = reconciledPlanState.version;
521
+ lastPlanStateSignature = signature;
522
+ currentPlanState = reconciledPlanState;
523
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
524
+ yield item;
525
+ }
526
+ const commentary = summarizePlanState(reconciledPlanState);
527
+ if (commentary) {
528
+ yield* emitCommentary(commentary);
529
+ }
530
+ }
531
+ }
441
532
  upstreamEventOrdinal += 1;
442
533
  const projectionBinding = options.getBinding(currentAgentId) ?? options.binding;
443
534
  const surfaceProjection = projectRuntimeSurfaceFromSingleUpstreamEvent({
@@ -553,7 +644,7 @@ export async function* streamHarnessRun(options) {
553
644
  type: "tool-result",
554
645
  sessionId: options.sessionId,
555
646
  requestId: options.requestId,
556
- agentId: options.selectedAgentId,
647
+ agentId: currentAgentId,
557
648
  toolName: normalizedChunk.toolName,
558
649
  output: normalizedChunk.output,
559
650
  isError: normalizedChunk.isError,
@@ -578,19 +669,32 @@ export async function* streamHarnessRun(options) {
578
669
  if (signature !== lastPlanStateSignature && shouldEmitPlanState(currentPlanState, planState)) {
579
670
  lastPlanStateSignature = signature;
580
671
  currentPlanState = planState;
581
- yield {
582
- type: "plan-state",
583
- sessionId: options.sessionId,
584
- requestId: options.requestId,
585
- agentId: currentAgentId,
586
- planState,
587
- };
672
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, planState)) {
673
+ yield item;
674
+ }
588
675
  const commentary = summarizePlanState(planState);
589
676
  if (commentary) {
590
677
  yield* emitCommentary(commentary);
591
678
  }
592
679
  }
593
680
  }
681
+ const terminalStructuredStatus = readTerminalStructuredStatus(normalizedChunk.output);
682
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
683
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
684
+ const signature = buildPlanStateSignature(reconciledPlanState);
685
+ if (signature !== lastPlanStateSignature) {
686
+ planStateVersion = reconciledPlanState.version;
687
+ lastPlanStateSignature = signature;
688
+ currentPlanState = reconciledPlanState;
689
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
690
+ yield item;
691
+ }
692
+ const commentary = summarizePlanState(reconciledPlanState);
693
+ if (commentary) {
694
+ yield* emitCommentary(commentary);
695
+ }
696
+ }
697
+ }
594
698
  continue;
595
699
  }
596
700
  emitted = true;
@@ -614,6 +718,7 @@ export async function* streamHarnessRun(options) {
614
718
  assistantOutput = toolErrors.join("\n\n");
615
719
  emitted = true;
616
720
  }
721
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
617
722
  if (!assistantOutput) {
618
723
  const actual = await options.invokeWithHistory(options.binding, options.input, options.sessionId, options.requestId);
619
724
  if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
@@ -622,6 +727,7 @@ export async function* streamHarnessRun(options) {
622
727
  if (actual.output) {
623
728
  assistantOutput = actual.output;
624
729
  emitted = true;
730
+ assistantOutputCameFromInvokeFallback = true;
625
731
  }
626
732
  const finalPlanState = getLatestPlanStateFromExecutedToolResults({
627
733
  sessionId: options.sessionId,
@@ -636,19 +742,42 @@ export async function* streamHarnessRun(options) {
636
742
  planStateVersion = finalPlanState.version;
637
743
  lastPlanStateSignature = signature;
638
744
  currentPlanState = finalPlanState;
639
- yield {
640
- type: "plan-state",
641
- sessionId: options.sessionId,
642
- requestId: options.requestId,
643
- agentId: currentAgentId,
644
- planState: finalPlanState,
645
- };
745
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, finalPlanState)) {
746
+ yield item;
747
+ }
646
748
  const commentary = summarizePlanState(finalPlanState);
647
749
  if (commentary) {
648
750
  yield* emitCommentary(commentary);
649
751
  }
650
752
  }
651
753
  }
754
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
755
+ const terminalStructuredStatus = readTerminalStructuredStatus(actual.structuredResponse);
756
+ if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
757
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
758
+ const signature = buildPlanStateSignature(reconciledPlanState);
759
+ if (signature !== lastPlanStateSignature) {
760
+ planStateVersion = reconciledPlanState.version;
761
+ lastPlanStateSignature = signature;
762
+ currentPlanState = reconciledPlanState;
763
+ for (const item of await emitPlanStateUpdate(options, currentAgentId, reconciledPlanState)) {
764
+ yield item;
765
+ }
766
+ const commentary = summarizePlanState(reconciledPlanState);
767
+ if (commentary) {
768
+ yield* emitCommentary(commentary);
769
+ }
770
+ }
771
+ }
772
+ }
773
+ currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
774
+ if (assistantOutputCameFromInvokeFallback
775
+ && nonUpstreamStreamActivityObserved
776
+ && planStateHasActiveItems(currentPlanState)) {
777
+ throw new ExecutionReconciliationError("Agent ended while the streamed plan state still had unfinished work.");
778
+ }
779
+ if (planStateHasActiveItems(currentPlanState)) {
780
+ throw new ExecutionReconciliationError("Agent ended while the streamed plan state still had unfinished work.");
652
781
  }
653
782
  if (assistantOutput && bufferAssistantTextUntilCompletion) {
654
783
  yield {
@@ -686,13 +815,15 @@ export async function* streamHarnessRun(options) {
686
815
  catch (error) {
687
816
  const shouldRetryAfterStreamingCompatibilityError = !assistantOutput &&
688
817
  isOpenAICompatibleStreamingCompatibilityError(options.binding, error);
689
- if ((emitted || streamActivityObserved) && !shouldRetryAfterStreamingCompatibilityError) {
818
+ if ((emitted || streamActivityObserved)
819
+ && !shouldRetryAfterStreamingCompatibilityError) {
690
820
  const runtimeFailure = renderRuntimeFailure(error);
821
+ const detailedError = describeRuntimeError(error);
691
822
  yield {
692
823
  type: "event",
693
824
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
694
825
  previousState: "running",
695
- error: error instanceof Error ? error.message : String(error),
826
+ error: detailedError,
696
827
  }),
697
828
  };
698
829
  yield {
@@ -717,11 +848,12 @@ export async function* streamHarnessRun(options) {
717
848
  }
718
849
  if (error instanceof RuntimeOperationTimeoutError && error.stage === "invoke") {
719
850
  const runtimeFailure = renderRuntimeFailure(error);
851
+ const detailedError = describeRuntimeError(error);
720
852
  yield {
721
853
  type: "event",
722
854
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
723
855
  previousState: "running",
724
- error: error.message,
856
+ error: detailedError,
725
857
  }),
726
858
  };
727
859
  yield {
@@ -744,6 +876,36 @@ export async function* streamHarnessRun(options) {
744
876
  };
745
877
  return;
746
878
  }
879
+ if (error instanceof ExecutionReconciliationError) {
880
+ const runtimeFailure = renderRuntimeFailure(error);
881
+ const detailedError = describeRuntimeError(error);
882
+ yield {
883
+ type: "event",
884
+ event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
885
+ previousState: "running",
886
+ error: detailedError,
887
+ }),
888
+ };
889
+ yield {
890
+ type: "content",
891
+ sessionId: options.sessionId,
892
+ requestId: options.requestId,
893
+ agentId: options.selectedAgentId,
894
+ content: runtimeFailure,
895
+ };
896
+ yield {
897
+ type: "result",
898
+ result: {
899
+ sessionId: options.sessionId,
900
+ requestId: options.requestId,
901
+ agentId: currentAgentId,
902
+ state: "failed",
903
+ output: runtimeFailure,
904
+ finalMessageText: runtimeFailure,
905
+ },
906
+ };
907
+ return;
908
+ }
747
909
  try {
748
910
  syntheticFallback = {
749
911
  strategy: "stream-to-invoke",
@@ -757,6 +919,19 @@ export async function* streamHarnessRun(options) {
757
919
  if (Array.isArray(actual.contentBlocks) && actual.contentBlocks.length > 0) {
758
920
  yield createContentBlocksItem(options.sessionId, options.requestId, options.selectedAgentId, actual.contentBlocks);
759
921
  }
922
+ if (actual.output) {
923
+ yield {
924
+ type: "event",
925
+ event: await options.emit(options.sessionId, options.requestId, 3, "output.delta", { content: actual.output }),
926
+ };
927
+ yield {
928
+ type: "content",
929
+ sessionId: options.sessionId,
930
+ requestId: options.requestId,
931
+ agentId: currentAgentId,
932
+ content: actual.output,
933
+ };
934
+ }
760
935
  const terminalEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, actual.state, {
761
936
  previousState: "running",
762
937
  });
@@ -798,11 +973,12 @@ export async function* streamHarnessRun(options) {
798
973
  };
799
974
  await options.emitSyntheticFallback(options.sessionId, options.requestId, options.selectedAgentId, syntheticFallback);
800
975
  const runtimeFailure = renderRuntimeFailure(invokeError);
976
+ const detailedError = describeRuntimeError(invokeError);
801
977
  yield {
802
978
  type: "event",
803
979
  event: await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "failed", {
804
980
  previousState: "running",
805
- error: invokeError instanceof Error ? invokeError.message : String(invokeError),
981
+ error: detailedError,
806
982
  }),
807
983
  };
808
984
  yield {
@@ -215,6 +215,7 @@ export class AgentHarnessRuntime {
215
215
  runtimeAdapterOptions: {
216
216
  ...runtimeAdapterOptions,
217
217
  scheduleManager: runtimeAdapterOptions.scheduleManager ?? this.scheduleManager,
218
+ bindingResolver: runtimeAdapterOptions.bindingResolver ?? ((agentId) => this.workspace.bindings.get(agentId)),
218
219
  functionToolContextResolver: runtimeAdapterOptions.functionToolContextResolver ?? ((input) => this.buildFunctionToolContext(input)),
219
220
  },
220
221
  checkpointers: this.checkpointers,
@@ -1303,6 +1304,8 @@ export class AgentHarnessRuntime {
1303
1304
  clearRequestInput: (sessionId, requestId) => this.persistence.clearRequestInput(sessionId, requestId),
1304
1305
  updateRequestInspection: (sessionId, requestId, patch) => this.persistence.updateRequestInspection(sessionId, requestId, patch),
1305
1306
  appendRequestTraceItem: (sessionId, requestId, item) => this.persistence.appendRequestTraceItem(sessionId, requestId, item),
1307
+ loadRequestPlanState: (sessionId, requestId) => this.persistence.getRequestPlanState(sessionId, requestId),
1308
+ saveRequestPlanState: (sessionId, requestId, planState) => this.persistence.saveRequestPlanState(sessionId, requestId, planState),
1306
1309
  emitSyntheticFallback: (sessionId, requestId, selectedAgentId, error) => this.runtimeEventOperations.emitSyntheticFallback(sessionId, requestId, selectedAgentId, error),
1307
1310
  });
1308
1311
  for await (const item of stream) {
@@ -1,5 +1,5 @@
1
1
  import { AIMessage } from "langchain";
2
- import { salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
2
+ import { salvageFunctionLikeToolCall, salvageToolArgs, isLikelyToolArgsObject, normalizeKnownToolArgs, tryParseJson } from "./output-tool-args.js";
3
3
  export function sanitizeVisibleText(value) {
4
4
  return value
5
5
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
@@ -403,19 +403,26 @@ function normalizeAgentMessage(value) {
403
403
  })
404
404
  .filter((toolCall) => toolCall !== null)
405
405
  : [];
406
- const recoveredToolCalls = normalizedInvalidToolCalls.filter((toolCall) => typeof toolCall.args === "object" && !!toolCall.args && !Array.isArray(toolCall.args));
407
406
  const normalizedContent = typeof typed.content === "string" || Array.isArray(typed.content)
408
407
  ? typed.content
409
408
  : typeof typed.content === "object" && typed.content
410
409
  ? readTextContent(typed.content)
411
410
  : "";
411
+ const recoveredToolCalls = normalizedInvalidToolCalls.filter((toolCall) => typeof toolCall.args === "object" && !!toolCall.args && !Array.isArray(toolCall.args));
412
+ const functionLikeToolCall = normalizedToolCalls.length === 0 && recoveredToolCalls.length === 0 && typeof normalizedContent === "string"
413
+ ? salvageFunctionLikeToolCall(normalizedContent)
414
+ : null;
412
415
  return new AIMessage({
413
- content: normalizedContent,
416
+ content: functionLikeToolCall ? "" : normalizedContent,
414
417
  name: typeof typed.name === "string" ? typed.name : undefined,
415
418
  additional_kwargs: typeof typed.additional_kwargs === "object" && typed.additional_kwargs ? typed.additional_kwargs : {},
416
419
  response_metadata: typeof typed.response_metadata === "object" && typed.response_metadata ? typed.response_metadata : {},
417
420
  id: typeof typed.id === "string" ? typed.id : undefined,
418
- tool_calls: [...normalizedToolCalls, ...recoveredToolCalls],
421
+ tool_calls: [
422
+ ...normalizedToolCalls,
423
+ ...recoveredToolCalls,
424
+ ...(functionLikeToolCall ? [{ name: functionLikeToolCall.name, args: functionLikeToolCall.args }] : []),
425
+ ],
419
426
  invalid_tool_calls: normalizedInvalidToolCalls.filter((toolCall) => toolCall.type !== "tool_call"),
420
427
  usage_metadata: typeof typed.usage_metadata === "object" && typed.usage_metadata ? typed.usage_metadata : undefined,
421
428
  });
@@ -11,6 +11,9 @@ export declare function resolveExecutionWithoutToolEvidenceInstruction(request:
11
11
  export declare function resolveExecutionWithoutToolEvidenceTextInstruction(request: unknown, assistantText: string, toolCallEvidence?: boolean, resultEvidence?: {
12
12
  hasWriteTodosEvidence?: boolean;
13
13
  hasToolResultEvidence?: boolean;
14
+ hasIncompletePlanState?: boolean;
15
+ hasOpenTaskDelegation?: boolean;
16
+ hasMissingDelegatedExecutionEvidence?: boolean;
14
17
  }): string | null;
15
18
  export declare function resolveToolCallRecoveryInstruction(error: unknown): string | null;
16
19
  export declare function appendToolRecoveryInstruction(input: unknown, instruction: string): unknown;
@@ -1,5 +1,38 @@
1
- import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, } from "../prompts/runtime-prompts.js";
1
+ import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION, STRICT_TOOL_JSON_INSTRUCTION, WORKSPACE_RELATIVE_PATH_INSTRUCTION, WRITE_TODOS_DESCRIPTIVE_CONTENT_INSTRUCTION, WRITE_TODOS_FULL_ENTRY_INSTRUCTION, WRITE_TODOS_NON_EMPTY_INITIAL_LIST_INSTRUCTION, } from "../prompts/runtime-prompts.js";
2
2
  import { wrapNormalizedMessage, readTextContent } from "./output-content.js";
3
+ function collectRequestMessages(request) {
4
+ if (typeof request !== "object" || !request || Array.isArray(request)) {
5
+ return [];
6
+ }
7
+ const typed = request;
8
+ return Array.isArray(typed.messages)
9
+ ? typed.messages.filter((message) => typeof message === "object" && !!message && !Array.isArray(message))
10
+ : [];
11
+ }
12
+ function readMessageRole(message) {
13
+ return typeof message.role === "string" ? message.role.trim().toLowerCase() : "";
14
+ }
15
+ function readLatestUserRequestText(request) {
16
+ const messages = collectRequestMessages(request);
17
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
18
+ const message = messages[index];
19
+ if (readMessageRole(message) !== "user") {
20
+ continue;
21
+ }
22
+ const content = readTextContent(message.content).trim();
23
+ if (content) {
24
+ return content;
25
+ }
26
+ }
27
+ return "";
28
+ }
29
+ function readSystemInstructionText(request) {
30
+ return collectRequestMessages(request)
31
+ .filter((message) => readMessageRole(message) === "system")
32
+ .map((message) => readTextContent(message.content).trim())
33
+ .filter(Boolean)
34
+ .join("\n\n");
35
+ }
3
36
  function isToolCallParseFailure(error) {
4
37
  return error instanceof Error && /error parsing tool call:/i.test(error.message);
5
38
  }
@@ -72,20 +105,33 @@ export function isRetrySafeInvalidToolSelectionError(value) {
72
105
  return !!text && /is not a valid tool, try one of \[/i.test(text);
73
106
  }
74
107
  export function shouldValidateExecutionWithoutToolEvidence(request) {
75
- void request;
76
- return false;
108
+ const userText = readLatestUserRequestText(request);
109
+ if (userText) {
110
+ return true;
111
+ }
112
+ return readSystemInstructionText(request).length > 0;
77
113
  }
78
114
  export function resolveExecutionWithoutToolEvidenceInstruction(request, result) {
79
- void request;
80
- void result;
81
- return null;
115
+ const assistantText = readTextContent(result).trim();
116
+ return resolveExecutionWithoutToolEvidenceTextInstruction(request, assistantText, false, {});
82
117
  }
83
118
  export function resolveExecutionWithoutToolEvidenceTextInstruction(request, assistantText, toolCallEvidence = false, resultEvidence = {}) {
84
- void request;
85
- void assistantText;
86
- void toolCallEvidence;
87
- void resultEvidence;
88
- return null;
119
+ if (!shouldValidateExecutionWithoutToolEvidence(request)) {
120
+ return null;
121
+ }
122
+ const normalizedText = assistantText.trim();
123
+ const hasUnfinishedExecution = resultEvidence.hasIncompletePlanState === true
124
+ || resultEvidence.hasOpenTaskDelegation === true
125
+ || resultEvidence.hasMissingDelegatedExecutionEvidence === true;
126
+ if (!normalizedText || !hasUnfinishedExecution) {
127
+ return null;
128
+ }
129
+ const hasExecutionEvidence = toolCallEvidence
130
+ || resultEvidence.hasWriteTodosEvidence === true
131
+ || resultEvidence.hasToolResultEvidence === true;
132
+ return hasExecutionEvidence
133
+ ? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
134
+ : EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION;
89
135
  }
90
136
  export function resolveToolCallRecoveryInstruction(error) {
91
137
  if (isRepairableWriteTodosEmptyFailure(error))
@@ -1,4 +1,8 @@
1
1
  export declare function tryParseJson(value: string): unknown | null;
2
+ export declare function salvageFunctionLikeToolCall(value: unknown): {
3
+ name: string;
4
+ args: Record<string, unknown>;
5
+ } | null;
2
6
  export declare function salvageToolArgs(value: unknown): Record<string, unknown> | null;
3
7
  export declare function normalizeKnownToolArgs(toolName: unknown, args: Record<string, unknown>): Record<string, unknown>;
4
8
  export declare function isLikelyToolArgsObject(value: unknown): boolean;