@ai-sdk/harness 1.0.149 → 1.0.151

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,7 +1,7 @@
1
1
  import { AISDKError, getErrorMessage } from "@ai-sdk/provider";
2
- import { DelayedPromise, asArray, asSchema, executeTool, generateId, isExecutableTool, normalizeHeaders, safeParseJSON, safeValidateTypes, validateTypes } from "@ai-sdk/provider-utils";
2
+ import { DelayedPromise, asArray, asSchema, convertInlineFileDataToUint8Array, convertUint8ArrayToBase64, detectMediaType, executeTool, generateId, getTopLevelMediaType, isExecutableTool, isFullMediaType, normalizeHeaders, safeParseJSON, safeValidateTypes, validateTypes } from "@ai-sdk/provider-utils";
3
3
  import path, { posix } from "node:path";
4
- import { DefaultStepResult, addLanguageModelUsage, asLanguageModelUsage, createAsyncIterableStream, createNullLanguageModelUsage, createRestrictedTelemetryDispatcher, mergeCallbacks, parseToolCall, toResponseMessages, validateToolContext } from "ai/internal";
4
+ import { DefaultStepResult, addLanguageModelUsage, asLanguageModelUsage, createAsyncIterableStream, createNullLanguageModelUsage, createRestrictedTelemetryDispatcher, createToolModelOutput, mergeCallbacks, parseToolCall, toResponseMessages, validateToolContext } from "ai/internal";
5
5
  import { NoSuchToolError, createUIMessageStreamResponse, toUIMessageStream } from "ai";
6
6
  import { appendFileSync, mkdirSync } from "node:fs";
7
7
  //#region src/errors/harness-error.ts
@@ -226,6 +226,9 @@ async function toHarnessStream(options) {
226
226
  * and throw a clear error.
227
227
  */
228
228
  var HarnessStreamTextResult = class {
229
+ setToolModelOutput(input) {
230
+ this.toolModelOutputs.set(input.toolCallId, input.output);
231
+ }
229
232
  constructor(options) {
230
233
  this._content = new DelayedPromise();
231
234
  this._text = new DelayedPromise();
@@ -251,9 +254,12 @@ var HarnessStreamTextResult = class {
251
254
  this._providerMetadata = new DelayedPromise();
252
255
  this.stepsBuffer = [];
253
256
  this.currentStepContent = [];
257
+ this.currentStepTextContent = /* @__PURE__ */ new Map();
258
+ this.currentStepReasoningContent = /* @__PURE__ */ new Map();
254
259
  this.currentStepWarnings = [];
255
260
  this.stepNumber = 0;
256
261
  this.stepStarted = false;
262
+ this.toolModelOutputs = /* @__PURE__ */ new Map();
257
263
  this.accumulatedUsage = createNullLanguageModelUsage();
258
264
  this.finalProviderMetadata = void 0;
259
265
  this.finalFinishReason = "other";
@@ -319,6 +325,8 @@ var HarnessStreamTextResult = class {
319
325
  */
320
326
  discardCurrentStepContent() {
321
327
  this.currentStepContent = [];
328
+ this.currentStepTextContent.clear();
329
+ this.currentStepReasoningContent.clear();
322
330
  this.currentStepWarnings = [];
323
331
  this.stepStarted = false;
324
332
  }
@@ -372,6 +380,8 @@ var HarnessStreamTextResult = class {
372
380
  if (this.currentStepWarnings.length > 0) this.aggregateWarnings.push(...this.currentStepWarnings);
373
381
  this.stepNumber += 1;
374
382
  this.currentStepContent = [];
383
+ this.currentStepTextContent.clear();
384
+ this.currentStepReasoningContent.clear();
375
385
  this.currentStepWarnings = [];
376
386
  this.stepStarted = false;
377
387
  return step;
@@ -426,8 +436,8 @@ var HarnessStreamTextResult = class {
426
436
  const aggregatedContent = this.stepsBuffer.flatMap((s) => s.content);
427
437
  this._content.resolve(aggregatedContent);
428
438
  this._text.resolve(finalStep.text);
429
- this._reasoning.resolve([]);
430
- this._reasoningText.resolve(void 0);
439
+ this._reasoning.resolve(finalStep.content.filter((part) => part.type === "reasoning" || part.type === "reasoning-file"));
440
+ this._reasoningText.resolve(finalStep.reasoningText);
431
441
  this._files.resolve(this.stepsBuffer.flatMap((s) => s.files));
432
442
  this._sources.resolve(this.stepsBuffer.flatMap((s) => s.sources));
433
443
  this._toolCalls.resolve(this.stepsBuffer.flatMap((s) => s.toolCalls));
@@ -445,10 +455,20 @@ var HarnessStreamTextResult = class {
445
455
  this._request.resolve(finalStep.request);
446
456
  this._response.resolve(finalStep.response);
447
457
  this._providerMetadata.resolve(this.finalProviderMetadata);
448
- const responseMessages = await toResponseMessages({
449
- content: aggregatedContent,
450
- tools: this.tools
451
- });
458
+ const responseTools = {};
459
+ for (const [name, tool] of Object.entries(this.tools)) responseTools[name] = {
460
+ ...tool,
461
+ toModelOutput: (options) => this.toolModelOutputs.get(options.toolCallId) ?? createToolModelOutput({
462
+ ...options,
463
+ tool,
464
+ errorMode: "none"
465
+ })
466
+ };
467
+ const responseMessages = [];
468
+ for (const step of this.stepsBuffer) responseMessages.push(...await toResponseMessages({
469
+ content: step.content,
470
+ tools: responseTools
471
+ }));
452
472
  this._responseMessages.resolve(responseMessages);
453
473
  this.fullStreamController.enqueue({
454
474
  type: "finish",
@@ -640,12 +660,14 @@ var HarnessStreamTextResult = class {
640
660
  reader.releaseLock();
641
661
  }
642
662
  }
643
- toUIMessageStream({ originalMessages, generateMessageId, onEnd, onFinish, messageMetadata, sendReasoning, sendSources, sendStart, sendFinish, onError } = {}) {
663
+ toUIMessageStream({ originalMessages, generateMessageId, onStepEnd, onStepFinish, onEnd, onFinish, messageMetadata, sendReasoning, sendSources, sendStart, sendFinish, onError } = {}) {
644
664
  return createAsyncIterableStream(toUIMessageStream({
645
665
  stream: this.stream,
646
666
  tools: this.tools,
647
667
  originalMessages,
648
668
  generateMessageId,
669
+ onStepEnd,
670
+ onStepFinish,
649
671
  onEnd,
650
672
  onFinish,
651
673
  messageMetadata,
@@ -662,11 +684,13 @@ var HarnessStreamTextResult = class {
662
684
  pipeTextStreamToResponse() {
663
685
  throw notSupportedYet("pipeTextStreamToResponse");
664
686
  }
665
- toUIMessageStreamResponse({ originalMessages, generateMessageId, onEnd, onFinish, messageMetadata, sendReasoning, sendSources, sendStart, sendFinish, onError, ...init } = {}) {
687
+ toUIMessageStreamResponse({ originalMessages, generateMessageId, onStepEnd, onStepFinish, onEnd, onFinish, messageMetadata, sendReasoning, sendSources, sendStart, sendFinish, onError, ...init } = {}) {
666
688
  return createUIMessageStreamResponse({
667
689
  stream: this.toUIMessageStream({
668
690
  originalMessages,
669
691
  generateMessageId,
692
+ onStepEnd,
693
+ onStepFinish,
670
694
  onEnd,
671
695
  onFinish,
672
696
  messageMetadata,
@@ -684,22 +708,71 @@ var HarnessStreamTextResult = class {
684
708
  }
685
709
  appendToCurrentStepContent(part) {
686
710
  switch (part.type) {
687
- case "text-delta": {
688
- const last = this.currentStepContent[this.currentStepContent.length - 1];
689
- if (last && last.type === "text") last.text += part.text;
690
- else this.currentStepContent.push({
711
+ case "text-start": {
712
+ const content = {
691
713
  type: "text",
692
- text: part.text
693
- });
714
+ text: "",
715
+ ...part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}
716
+ };
717
+ this.currentStepTextContent.set(part.id, content);
718
+ this.currentStepContent.push(content);
694
719
  return;
695
720
  }
696
- case "reasoning-delta": {
697
- const last = this.currentStepContent[this.currentStepContent.length - 1];
698
- if (last && last.type === "reasoning") last.text += part.text;
699
- else this.currentStepContent.push({
721
+ case "text-delta": {
722
+ let content = this.currentStepTextContent.get(part.id);
723
+ if (content == null) {
724
+ content = {
725
+ type: "text",
726
+ text: "",
727
+ ...part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}
728
+ };
729
+ this.currentStepTextContent.set(part.id, content);
730
+ this.currentStepContent.push(content);
731
+ }
732
+ content.text += part.text;
733
+ if (part.providerMetadata != null) content.providerMetadata = part.providerMetadata;
734
+ return;
735
+ }
736
+ case "text-end": {
737
+ const content = this.currentStepTextContent.get(part.id);
738
+ if (content != null) {
739
+ if (part.providerMetadata != null) content.providerMetadata = part.providerMetadata;
740
+ if (part.citations != null) content.citations = part.citations;
741
+ this.currentStepTextContent.delete(part.id);
742
+ }
743
+ return;
744
+ }
745
+ case "reasoning-start": {
746
+ const content = {
700
747
  type: "reasoning",
701
- text: part.text
702
- });
748
+ text: "",
749
+ ...part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}
750
+ };
751
+ this.currentStepReasoningContent.set(part.id, content);
752
+ this.currentStepContent.push(content);
753
+ return;
754
+ }
755
+ case "reasoning-delta": {
756
+ let content = this.currentStepReasoningContent.get(part.id);
757
+ if (content == null) {
758
+ content = {
759
+ type: "reasoning",
760
+ text: "",
761
+ ...part.providerMetadata != null ? { providerMetadata: part.providerMetadata } : {}
762
+ };
763
+ this.currentStepReasoningContent.set(part.id, content);
764
+ this.currentStepContent.push(content);
765
+ }
766
+ content.text += part.text;
767
+ if (part.providerMetadata != null) content.providerMetadata = part.providerMetadata;
768
+ return;
769
+ }
770
+ case "reasoning-end": {
771
+ const content = this.currentStepReasoningContent.get(part.id);
772
+ if (content != null) {
773
+ if (part.providerMetadata != null) content.providerMetadata = part.providerMetadata;
774
+ this.currentStepReasoningContent.delete(part.id);
775
+ }
703
776
  return;
704
777
  }
705
778
  case "tool-call":
@@ -869,11 +942,12 @@ function translateStreamPart(event, options = {}) {
869
942
  case "tool-call": return [];
870
943
  case "tool-approval-request": return [];
871
944
  case "tool-result":
872
- if (event.isError === true && (options.isProviderExecuted?.(event.toolCallId) ?? true)) return [{
945
+ const toolCall = options.getToolCall?.(event.toolCallId);
946
+ if (event.isError === true && (toolCall?.providerExecuted ?? options.isProviderExecuted?.(event.toolCallId) ?? true)) return [{
873
947
  type: "tool-error",
874
948
  toolCallId: event.toolCallId,
875
949
  toolName: event.toolName,
876
- input: void 0,
950
+ input: toolCall?.input,
877
951
  error: event.result,
878
952
  providerExecuted: true,
879
953
  ...event.dynamic !== void 0 ? { dynamic: event.dynamic } : {},
@@ -883,8 +957,9 @@ function translateStreamPart(event, options = {}) {
883
957
  type: "tool-result",
884
958
  toolCallId: event.toolCallId,
885
959
  toolName: event.toolName,
886
- input: void 0,
960
+ input: toolCall?.input,
887
961
  output: event.result,
962
+ ...toolCall?.providerExecuted !== void 0 ? { providerExecuted: toolCall.providerExecuted } : {},
888
963
  ...event.preliminary !== void 0 ? { preliminary: event.preliminary } : {},
889
964
  ...event.dynamic !== void 0 ? { dynamic: event.dynamic } : {},
890
965
  ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
@@ -1438,6 +1513,46 @@ function pinSandboxChannelEventCheckpoint(event) {
1438
1513
  return event[sandboxChannelEventCheckpointSymbol]?.pin();
1439
1514
  }
1440
1515
  //#endregion
1516
+ //#region src/utils/normalize-harness-tool-model-output.ts
1517
+ function normalizeHarnessToolModelOutput({ output }) {
1518
+ try {
1519
+ return output.type !== "content" ? output : {
1520
+ ...output,
1521
+ value: output.value.map((part) => {
1522
+ if (part.type === "text") return part;
1523
+ const file = part.type === "image-data" || part.type === "file-data" ? {
1524
+ ...part,
1525
+ type: "file",
1526
+ data: {
1527
+ type: "data",
1528
+ data: part.data
1529
+ }
1530
+ } : part;
1531
+ if (file.type !== "file" || file.data.type !== "data" || getTopLevelMediaType(file.mediaType) !== "image") throw new Error(`Unsupported content '${part.type}'${file.type === "file" ? ` (${file.mediaType}, ${file.data.type})` : ""}.`);
1532
+ const data = typeof file.data.data === "string" ? file.data.data : convertUint8ArrayToBase64(convertInlineFileDataToUint8Array(file.data));
1533
+ const mediaType = isFullMediaType(file.mediaType) ? file.mediaType : detectMediaType({
1534
+ data,
1535
+ topLevelType: "image"
1536
+ });
1537
+ if (mediaType == null) throw new Error("Cannot detect the inline image media type.");
1538
+ return {
1539
+ ...file,
1540
+ mediaType,
1541
+ data: {
1542
+ type: "data",
1543
+ data
1544
+ }
1545
+ };
1546
+ })
1547
+ };
1548
+ } catch (cause) {
1549
+ throw new HarnessCapabilityUnsupportedError({
1550
+ message: `Harnesses support only text and inline images in tool model output. ${cause instanceof Error ? cause.message : String(cause)}`,
1551
+ cause
1552
+ });
1553
+ }
1554
+ }
1555
+ //#endregion
1441
1556
  //#region src/agent/internal/run-prompt.ts
1442
1557
  const invalidToolInputMessage = "Tool input validation failed.";
1443
1558
  function unwrapToolResultOutput(toolResult) {
@@ -1565,10 +1680,13 @@ function runPrompt(input) {
1565
1680
  const stripToolInputWorkDir = createToolInputWorkDirStripper({ sessionWorkDir: input.sessionWorkDir });
1566
1681
  const toolCallsByToolCallId = /* @__PURE__ */ new Map();
1567
1682
  const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
1568
- const translateOptions = { isProviderExecuted: (toolCallId) => {
1569
- const rawToolCall = rawToolCallsByToolCallId.get(toolCallId);
1570
- return rawToolCall == null || rawToolCall.providerExecuted === true;
1571
- } };
1683
+ const translateOptions = {
1684
+ getToolCall: (toolCallId) => toolCallsByToolCallId.get(toolCallId),
1685
+ isProviderExecuted: (toolCallId) => {
1686
+ const rawToolCall = rawToolCallsByToolCallId.get(toolCallId);
1687
+ return rawToolCall == null || rawToolCall.providerExecuted === true;
1688
+ }
1689
+ };
1572
1690
  const pendingApprovalsByApprovalId = new Map(pendingToolApprovals.map((approval) => [approval.approvalId, approval]));
1573
1691
  const pendingApprovalsByToolCallId = new Map(pendingToolApprovals.map((approval) => [approval.toolCallId, approval]));
1574
1692
  const continuationsByApprovalId = new Map((input.toolApprovalContinuations ?? []).map((continuation) => [continuation.approvalId, continuation]));
@@ -1622,9 +1740,10 @@ function runPrompt(input) {
1622
1740
  toolExecutions.clear();
1623
1741
  bufferedToolOutcomes = [];
1624
1742
  };
1625
- let expectedStepToolCallCount;
1626
- let observedStepToolCallCount = 0;
1627
- let pauseAfterStepToolCalls = false;
1743
+ let hostInputPauseRequested = false;
1744
+ let deferredPauseFinishStep;
1745
+ let ignoredFinishStep;
1746
+ const hostToolCallIdsAwaitingEcho = /* @__PURE__ */ new Set();
1628
1747
  const buildModelCallContent = () => {
1629
1748
  const parts = [];
1630
1749
  if (stepText) parts.push({
@@ -1648,9 +1767,7 @@ function runPrompt(input) {
1648
1767
  stepApprovalRequests = [];
1649
1768
  bufferedToolOutcomes = [];
1650
1769
  toolExecutions.clear();
1651
- expectedStepToolCallCount = void 0;
1652
- observedStepToolCallCount = 0;
1653
- pauseAfterStepToolCalls = false;
1770
+ ignoredFinishStep = void 0;
1654
1771
  };
1655
1772
  const zeroUsage = {
1656
1773
  inputTokens: {
@@ -1687,20 +1804,25 @@ function runPrompt(input) {
1687
1804
  resetStepContent();
1688
1805
  return step;
1689
1806
  };
1690
- const finishForHostInputPause = async (options) => {
1807
+ const finishForHostInputPause = async () => {
1691
1808
  await waitForOutstandingHostToolExecutions();
1692
- if (options.completeCurrentStep) await completeStep({
1693
- finishReason: toolCallsFinishReason,
1694
- usage: zeroUsage,
1695
- providerMetadata: void 0
1696
- });
1697
- else await publishToolExecutions();
1809
+ await publishToolExecutions();
1698
1810
  await lifecycle.end({
1699
1811
  steps: completedSteps,
1700
1812
  usage: asLanguageModelUsage(zeroUsage)
1701
1813
  });
1702
1814
  await result.finish();
1703
1815
  };
1816
+ const pauseForHostInput = async (finishStep) => {
1817
+ await waitForOutstandingHostToolExecutions();
1818
+ closingResumedStep = false;
1819
+ await completeStep({
1820
+ finishReason: toolCallsFinishReason,
1821
+ usage: finishStep?.usage ?? zeroUsage,
1822
+ providerMetadata: finishStep?.harnessMetadata
1823
+ });
1824
+ await finishForHostInputPause();
1825
+ };
1704
1826
  const enqueueApprovalRequest = (approval) => {
1705
1827
  const part = {
1706
1828
  type: "tool-approval-request",
@@ -1768,7 +1890,33 @@ function runPrompt(input) {
1768
1890
  });
1769
1891
  };
1770
1892
  const submitToolResult = async (submission) => {
1771
- if (!input.isTurnSuspending?.()) return control.submitToolResult(submission);
1893
+ if (submission.toolResult != null) {
1894
+ let output;
1895
+ try {
1896
+ output = normalizeHarnessToolModelOutput({ output: submission.toolResult.output });
1897
+ } catch (error) {
1898
+ if (!continuationsByToolCallId.has(submission.toolCallId) || !HarnessCapabilityUnsupportedError.isInstance(error)) throw error;
1899
+ output = {
1900
+ type: "text",
1901
+ value: JSON.stringify(submission.output) ?? "null"
1902
+ };
1903
+ }
1904
+ submission = {
1905
+ ...submission,
1906
+ toolResult: {
1907
+ ...submission.toolResult,
1908
+ output
1909
+ }
1910
+ };
1911
+ result.setToolModelOutput({
1912
+ toolCallId: submission.toolCallId,
1913
+ output
1914
+ });
1915
+ }
1916
+ if (!input.isTurnSuspending?.()) {
1917
+ if (!settledHostToolCallIds.has(submission.toolCallId)) hostToolCallIdsAwaitingEcho.add(submission.toolCallId);
1918
+ return control.submitToolResult(submission);
1919
+ }
1772
1920
  const toolCall = rawToolCallsByToolCallId.get(submission.toolCallId) ?? pendingResultsByToolCallId.get(submission.toolCallId) ?? pendingToolApprovals.find((approval) => approval.toolCallId === submission.toolCallId);
1773
1921
  if (toolCall == null) throw new Error(`Unknown suspended tool call '${submission.toolCallId}'.`);
1774
1922
  const { toolCallId, ...completedResult } = submission;
@@ -1915,7 +2063,7 @@ function runPrompt(input) {
1915
2063
  });
1916
2064
  if (!execution.executed) {
1917
2065
  recordPendingToolResult({ toolCall: rawToolCall });
1918
- await finishForHostInputPause({ completeCurrentStep: false });
2066
+ await finishForHostInputPause();
1919
2067
  return "awaiting-tool-result";
1920
2068
  }
1921
2069
  const toolExecution = toolExecutions.get(rawToolCall.toolCallId);
@@ -1947,8 +2095,8 @@ function runPrompt(input) {
1947
2095
  }
1948
2096
  }
1949
2097
  while (true) {
1950
- if (pauseAfterStepToolCalls && expectedStepToolCallCount != null && observedStepToolCallCount >= expectedStepToolCallCount) {
1951
- await finishForHostInputPause({ completeCurrentStep: true });
2098
+ if (deferredPauseFinishStep != null && hostToolCallIdsAwaitingEcho.size === 0) {
2099
+ await pauseForHostInput(deferredPauseFinishStep);
1952
2100
  return;
1953
2101
  }
1954
2102
  const { value, done } = await reader.read();
@@ -1961,6 +2109,7 @@ function runPrompt(input) {
1961
2109
  result.enqueueRaw(value.rawValue);
1962
2110
  continue;
1963
2111
  }
2112
+ if (value.type === "tool-result") hostToolCallIdsAwaitingEcho.delete(value.toolCallId);
1964
2113
  if (pendingStopBoundary != null) {
1965
2114
  if (value.type === "finish") releasePendingStopBoundary();
1966
2115
  else if ((await Promise.all(input.stopConditions.map((condition) => condition({ steps: completedSteps })))).some(Boolean)) {
@@ -1990,6 +2139,43 @@ function runPrompt(input) {
1990
2139
  const settledHostInputReplay = (displayValue.type === "tool-call" || displayValue.type === "tool-result" || displayValue.type === "tool-approval-request") && settledHostToolCallIds.has(displayValue.toolCallId);
1991
2140
  const settledBuiltinApprovalReplay = (displayValue.type === "tool-call" || displayValue.type === "tool-approval-request") && settledBuiltinApprovalToolCallIds.has(displayValue.toolCallId);
1992
2141
  if (settledHostInputReplay || settledBuiltinApprovalReplay) continue;
2142
+ if (value.type === "finish-step") {
2143
+ if (hostInputPauseRequested) {
2144
+ await waitForOutstandingHostToolExecutions();
2145
+ if (hostToolCallIdsAwaitingEcho.size === 0) {
2146
+ await pauseForHostInput(value);
2147
+ return;
2148
+ }
2149
+ deferredPauseFinishStep = value;
2150
+ continue;
2151
+ }
2152
+ if (hostToolCallIdsAwaitingEcho.size > 0) {
2153
+ ignoredFinishStep = value;
2154
+ continue;
2155
+ }
2156
+ ignoredFinishStep = void 0;
2157
+ }
2158
+ if (value.type === "finish") {
2159
+ if (hostInputPauseRequested) {
2160
+ await pauseForHostInput(deferredPauseFinishStep);
2161
+ return;
2162
+ }
2163
+ if (ignoredFinishStep != null) {
2164
+ const finishStep = ignoredFinishStep;
2165
+ ignoredFinishStep = void 0;
2166
+ await waitForOutstandingHostToolExecutions();
2167
+ if (closingResumedStep) {
2168
+ closingResumedStep = false;
2169
+ await publishToolExecutions();
2170
+ resetStepContent();
2171
+ result.discardCurrentStepContent();
2172
+ } else await completeStep({
2173
+ finishReason: finishStep.finishReason,
2174
+ usage: finishStep.usage,
2175
+ providerMetadata: finishStep.harnessMetadata
2176
+ });
2177
+ }
2178
+ }
1993
2179
  if (displayValue.type === "finish-step" && closingResumedStep) {
1994
2180
  closingResumedStep = false;
1995
2181
  await publishToolExecutions();
@@ -2006,6 +2192,7 @@ function runPrompt(input) {
2006
2192
  toolFiltering: input.builtinToolFiltering
2007
2193
  })) {
2008
2194
  if (control.submitToolApproval == null) throw new Error(`Harness '${input.harness.harnessId}' emitted a built-in tool approval request but does not support approval responses.`);
2195
+ hostToolCallIdsAwaitingEcho.add(displayValue.toolCallId);
2009
2196
  await control.submitToolApproval({
2010
2197
  approvalId: displayValue.approvalId,
2011
2198
  approved: false,
@@ -2063,8 +2250,6 @@ function runPrompt(input) {
2063
2250
  if (value.type === "text-delta") stepText += value.delta;
2064
2251
  else if (value.type === "reasoning-delta") stepReasoning += value.delta;
2065
2252
  if (value.type === "tool-call") {
2066
- observedStepToolCallCount += 1;
2067
- expectedStepToolCallCount ??= value.stepToolCallCount;
2068
2253
  const toolCall = toolCallsByToolCallId.get(value.toolCallId);
2069
2254
  if (toolCall != null) {
2070
2255
  stepToolCalls.push(toolCall);
@@ -2115,8 +2300,8 @@ function runPrompt(input) {
2115
2300
  approvalId: pendingApproval.approvalId,
2116
2301
  toolCall
2117
2302
  });
2118
- await finishForHostInputPause({ completeCurrentStep: true });
2119
- return;
2303
+ hostInputPauseRequested = true;
2304
+ continue;
2120
2305
  }
2121
2306
  if (value.type === "finish-step") {
2122
2307
  await waitForOutstandingHostToolExecutions();
@@ -2167,12 +2352,8 @@ function runPrompt(input) {
2167
2352
  }
2168
2353
  if (isClientExecutedBuiltin) {
2169
2354
  recordPendingToolResult({ toolCall });
2170
- if (expectedStepToolCallCount != null && observedStepToolCallCount < expectedStepToolCallCount) {
2171
- pauseAfterStepToolCalls = true;
2172
- continue;
2173
- }
2174
- await finishForHostInputPause({ completeCurrentStep: true });
2175
- return;
2355
+ hostInputPauseRequested = true;
2356
+ continue;
2176
2357
  }
2177
2358
  if (validatedHostToolCall == null) throw new Error(`Harness '${input.harness.harnessId}' could not validate host tool '${toolCall.toolName}'.`);
2178
2359
  if (validatedHostToolCall.invalid) {
@@ -2260,22 +2441,15 @@ function runPrompt(input) {
2260
2441
  approvalId: pendingApproval.approvalId,
2261
2442
  toolCall: pendingParsedToolCall
2262
2443
  });
2263
- if (expectedStepToolCallCount != null && observedStepToolCallCount < expectedStepToolCallCount) {
2264
- pauseAfterStepToolCalls = true;
2265
- continue;
2266
- }
2267
- await finishForHostInputPause({ completeCurrentStep: true });
2268
- return;
2444
+ hostInputPauseRequested = true;
2445
+ continue;
2269
2446
  }
2270
2447
  if (!isExecutableTool(activeTools[toolCall.toolName])) {
2271
2448
  recordPendingToolResult({ toolCall });
2272
- if (expectedStepToolCallCount != null && observedStepToolCallCount < expectedStepToolCallCount) {
2273
- pauseAfterStepToolCalls = true;
2274
- continue;
2275
- }
2276
- await finishForHostInputPause({ completeCurrentStep: true });
2277
- return;
2449
+ hostInputPauseRequested = true;
2450
+ continue;
2278
2451
  }
2452
+ hostToolCallIdsAwaitingEcho.add(toolCall.toolCallId);
2279
2453
  startHostToolExecution((async () => {
2280
2454
  const toolExecution = toolExecutions.get(toolCall.toolCallId);
2281
2455
  if (toolExecution == null) throw new Error(`Harness '${input.harness.harnessId}' could not track host tool '${toolCall.toolName}'.`);
@@ -2318,6 +2492,10 @@ function runPrompt(input) {
2318
2492
  })());
2319
2493
  }
2320
2494
  }
2495
+ if (hostInputPauseRequested && input.isTurnSuspending?.() !== true) {
2496
+ await pauseForHostInput(deferredPauseFinishStep);
2497
+ return;
2498
+ }
2321
2499
  await waitForOutstandingHostToolExecutions();
2322
2500
  if (input.isTurnSuspending?.() === true) {
2323
2501
  await publishToolExecutions();
@@ -2442,7 +2620,19 @@ async function maybeExecuteHostTool(input) {
2442
2620
  });
2443
2621
  await input.submitToolResult({
2444
2622
  toolCallId: input.event.toolCallId,
2445
- output
2623
+ output,
2624
+ ...tool.toModelOutput == null ? {} : { toolResult: {
2625
+ type: "tool-result",
2626
+ toolCallId: input.event.toolCallId,
2627
+ toolName: input.event.toolName,
2628
+ output: await createToolModelOutput({
2629
+ toolCallId: input.event.toolCallId,
2630
+ input: input.parsedToolCall.input,
2631
+ output,
2632
+ tool,
2633
+ errorMode: "none"
2634
+ })
2635
+ } }
2446
2636
  });
2447
2637
  return {
2448
2638
  executed: true,
@@ -3034,6 +3224,9 @@ var HarnessAgentSession = class {
3034
3224
  * locally to find the original tool call before it can resume the paused turn.
3035
3225
  * Responses that already have a tool result are ignored, because those
3036
3226
  * approvals were already consumed by a prior continuation.
3227
+ * An `execution-denied` result is synthesized by `convertToModelMessages`
3228
+ * for a newly denied approval; it does not mean the harness has received
3229
+ * the decision, so that approval response must still continue the turn.
3037
3230
  */
3038
3231
  function collectHarnessAgentToolApprovalContinuations(input) {
3039
3232
  const lastMessage = input.messages.at(-1);
@@ -3046,7 +3239,7 @@ function collectHarnessAgentToolApprovalContinuations(input) {
3046
3239
  else if (part.type === "tool-approval-request") approvalRequestsByApprovalId.set(part.approvalId, part);
3047
3240
  }
3048
3241
  const toolResultIds = /* @__PURE__ */ new Set();
3049
- for (const part of lastMessage.content) if (part.type === "tool-result") toolResultIds.add(part.toolCallId);
3242
+ for (const part of lastMessage.content) if (part.type === "tool-result" && part.output.type !== "execution-denied") toolResultIds.add(part.toolCallId);
3050
3243
  const continuations = [];
3051
3244
  for (const part of lastMessage.content) {
3052
3245
  if (part.type !== "tool-approval-response") continue;