@ai-sdk/harness 1.0.167 → 1.0.169

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-sdk/harness",
3
- "version": "1.0.167",
3
+ "version": "1.0.169",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -44,9 +44,9 @@
44
44
  }
45
45
  },
46
46
  "dependencies": {
47
- "@ai-sdk/provider": "4.0.34",
48
- "@ai-sdk/provider-utils": "5.0.70",
49
- "ai": "7.0.156"
47
+ "@ai-sdk/provider": "4.0.35",
48
+ "@ai-sdk/provider-utils": "5.0.72",
49
+ "ai": "7.0.158"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "ws": "^8.21.0",
@@ -58,7 +58,7 @@
58
58
  }
59
59
  },
60
60
  "devDependencies": {
61
- "@ai-sdk/otel": "1.0.156",
61
+ "@ai-sdk/otel": "1.0.158",
62
62
  "@opentelemetry/sdk-trace-base": "2.7.1",
63
63
  "@types/node": "22.19.19",
64
64
  "@types/ws": "^8.5.13",
@@ -433,8 +433,8 @@ export function runPrompt<
433
433
  * An approval request or a pending client tool result does not end the
434
434
  * response right away: the rest of the step (other tool calls, native
435
435
  * results, host tool executions) still has to be surfaced. The pause
436
- * happens at the next `finish-step`, once every host tool started in
437
- * this response has been echoed back by the runtime.
436
+ * happens at the next `finish-step`. Unless a built-in approval blocks
437
+ * the runtime, host tools must first be echoed back by the runtime.
438
438
  */
439
439
  let hostInputPauseRequested = false;
440
440
  let deferredPauseFinishStep: FinishStepPart | undefined;
@@ -446,6 +446,10 @@ export function runPrompt<
446
446
  */
447
447
  let ignoredFinishStep: FinishStepPart | undefined;
448
448
  const hostToolCallIdsAwaitingEcho = new Set<string>();
449
+ const submittedHostToolResults = new Map<
450
+ string,
451
+ Parameters<HarnessV1PromptControl['submitToolResult']>[0]
452
+ >();
449
453
  const buildModelCallContent = (): ContentPart<TOOLS>[] => {
450
454
  const parts: ContentPart<TOOLS>[] = [];
451
455
  if (stepText) parts.push({ type: 'text', text: stepText });
@@ -517,6 +521,40 @@ export function runPrompt<
517
521
  finishStep: FinishStepPart | undefined,
518
522
  ): Promise<void> => {
519
523
  await waitForOutstandingHostToolExecutions();
524
+ for (const submission of submittedHostToolResults.values()) {
525
+ const { toolCallId, ...completedResult } = submission;
526
+ const rawToolCall = rawToolCallsByToolCallId.get(toolCallId);
527
+ const toolCall = toolCallsByToolCallId.get(toolCallId);
528
+ const pendingResult =
529
+ pendingResultsByToolCallId.get(toolCallId) ??
530
+ (rawToolCall == null
531
+ ? undefined
532
+ : {
533
+ toolCallId,
534
+ toolName: rawToolCall.toolName,
535
+ input: rawToolCall.input,
536
+ ...(rawToolCall.providerMetadata !== undefined
537
+ ? { providerOptions: rawToolCall.providerMetadata }
538
+ : {}),
539
+ });
540
+ if (pendingResult == null) continue;
541
+ onPendingToolResult({ ...pendingResult, completedResult });
542
+ const toolOutput = toolExecutions.get(toolCallId)?.toolOutput;
543
+ if (
544
+ toolCall != null &&
545
+ toolOutput != null &&
546
+ !settledHostToolCallIds.has(toolCallId)
547
+ ) {
548
+ enqueueHostToolOutcome({
549
+ toolCall,
550
+ outcome:
551
+ toolOutput.type === 'tool-error'
552
+ ? { ok: false, error: toolOutput.error }
553
+ : { ok: true, output: toolOutput.output },
554
+ isContinuation: false,
555
+ });
556
+ }
557
+ }
520
558
  closingResumedStep = false;
521
559
  await completeStep({
522
560
  finishReason: toolCallsFinishReason,
@@ -597,7 +635,15 @@ export function runPrompt<
597
635
  const enqueueHostToolOutcome = (options: {
598
636
  toolCall: ToolCallTextStreamPart;
599
637
  outcome: HostToolOutcome;
638
+ isContinuation?: boolean;
600
639
  }): void => {
640
+ const enqueue = (part: TextStreamPart<TOOLS>): void => {
641
+ if (options.isContinuation === false) {
642
+ result.enqueue(part);
643
+ } else {
644
+ result.enqueueContinuation(part);
645
+ }
646
+ };
601
647
  if (options.outcome.ok) {
602
648
  const stripped = stripWorkDir(
603
649
  {
@@ -611,7 +657,7 @@ export function runPrompt<
611
657
  },
612
658
  input.sessionWorkDir,
613
659
  ) as Extract<HarnessV1StreamPart, { type: 'tool-result' }>;
614
- result.enqueueContinuation({
660
+ enqueue({
615
661
  type: 'tool-result',
616
662
  toolCallId: options.toolCall.toolCallId,
617
663
  toolName: options.toolCall.toolName,
@@ -621,7 +667,7 @@ export function runPrompt<
621
667
  return;
622
668
  }
623
669
 
624
- result.enqueueContinuation({
670
+ enqueue({
625
671
  type: 'tool-error',
626
672
  toolCallId: options.toolCall.toolCallId,
627
673
  toolName: options.toolCall.toolName,
@@ -662,6 +708,7 @@ export function runPrompt<
662
708
  if (!settledHostToolCallIds.has(submission.toolCallId)) {
663
709
  hostToolCallIdsAwaitingEcho.add(submission.toolCallId);
664
710
  }
711
+ submittedHostToolResults.set(submission.toolCallId, submission);
665
712
  return control.submitToolResult(submission);
666
713
  }
667
714
  const toolCall =
@@ -719,7 +766,6 @@ export function runPrompt<
719
766
  toolCallId: pendingResult.toolCallId,
720
767
  ...submission,
721
768
  });
722
- pendingResultsByToolCallId.delete(pendingResult.toolCallId);
723
769
  };
724
770
  const processPendingApprovalContinuation = async (
725
771
  approval: HarnessV1PendingToolApproval,
@@ -744,6 +790,7 @@ export function runPrompt<
744
790
  part: await validateToolCall({
745
791
  event: rawToolCall,
746
792
  tools: input.tools,
793
+ harnessId: input.harness.harnessId,
747
794
  }),
748
795
  });
749
796
  toolCall = displayHostToolCall({
@@ -785,11 +832,18 @@ export function runPrompt<
785
832
 
786
833
  settledHostToolCallIds.add(approval.toolCallId);
787
834
  if (!continuation.approved) {
835
+ const output = {
836
+ type: 'execution-denied' as const,
837
+ reason: continuation.reason,
838
+ };
788
839
  await submitToolResult({
789
840
  toolCallId: approval.toolCallId,
790
- output: {
791
- type: 'execution-denied',
792
- reason: continuation.reason,
841
+ output,
842
+ toolResult: {
843
+ type: 'tool-result',
844
+ toolCallId: approval.toolCallId,
845
+ toolName: approval.toolName,
846
+ output,
793
847
  },
794
848
  });
795
849
  return 'continued';
@@ -927,6 +981,8 @@ export function runPrompt<
927
981
  }
928
982
  if (value.type === 'tool-result') {
929
983
  hostToolCallIdsAwaitingEcho.delete(value.toolCallId);
984
+ submittedHostToolResults.delete(value.toolCallId);
985
+ pendingResultsByToolCallId.delete(value.toolCallId);
930
986
  }
931
987
 
932
988
  if (pendingStopBoundary != null) {
@@ -1021,7 +1077,12 @@ export function runPrompt<
1021
1077
  if (value.type === 'finish-step') {
1022
1078
  if (hostInputPauseRequested) {
1023
1079
  await waitForOutstandingHostToolExecutions();
1024
- if (hostToolCallIdsAwaitingEcho.size === 0) {
1080
+ if (
1081
+ hostToolCallIdsAwaitingEcho.size === 0 ||
1082
+ [...pendingApprovalsByApprovalId.values()].some(
1083
+ approval => approval.kind === 'builtin',
1084
+ )
1085
+ ) {
1025
1086
  await pauseForHostInput(value);
1026
1087
  return;
1027
1088
  }
@@ -1178,6 +1239,7 @@ export function runPrompt<
1178
1239
  const parsed = await validateToolCall<TOOLS>({
1179
1240
  event: isHostTool ? value : displayValue,
1180
1241
  tools: isFilteredBuiltinDynamicCall ? undefined : input.tools,
1242
+ harnessId: input.harness.harnessId,
1181
1243
  });
1182
1244
  const parsedToolCall = asToolCallTextStreamPart({ part: parsed });
1183
1245
  validatedHostToolCall = isHostTool ? parsedToolCall : undefined;
@@ -1347,7 +1409,7 @@ export function runPrompt<
1347
1409
  !hasTool({ tools: activeTools, toolName: toolCall.toolName })
1348
1410
  ) {
1349
1411
  const output = {
1350
- type: 'execution-denied',
1412
+ type: 'execution-denied' as const,
1351
1413
  reason: getHarnessV1BuiltinToolFilteringDenialReason({
1352
1414
  toolName: toolCall.toolName,
1353
1415
  }),
@@ -1355,6 +1417,12 @@ export function runPrompt<
1355
1417
  await submitToolResult({
1356
1418
  toolCallId: toolCall.toolCallId,
1357
1419
  output,
1420
+ toolResult: {
1421
+ type: 'tool-result',
1422
+ toolCallId: toolCall.toolCallId,
1423
+ toolName: toolCall.toolName,
1424
+ output,
1425
+ },
1358
1426
  });
1359
1427
  const execution = toolExecutions.get(toolCall.toolCallId);
1360
1428
  if (execution != null) {
@@ -1414,7 +1482,7 @@ export function runPrompt<
1414
1482
  providerExecuted: false,
1415
1483
  });
1416
1484
  const output = {
1417
- type: 'execution-denied',
1485
+ type: 'execution-denied' as const,
1418
1486
  reason: customToolApprovalDecision.reason,
1419
1487
  };
1420
1488
  const execution = toolExecutions.get(toolCall.toolCallId);
@@ -1424,6 +1492,12 @@ export function runPrompt<
1424
1492
  await submitToolResult({
1425
1493
  toolCallId: toolCall.toolCallId,
1426
1494
  output,
1495
+ toolResult: {
1496
+ type: 'tool-result',
1497
+ toolCallId: toolCall.toolCallId,
1498
+ toolName: toolCall.toolName,
1499
+ output,
1500
+ },
1427
1501
  });
1428
1502
  if (execution != null) {
1429
1503
  execution.toolOutput = toToolOutput({
@@ -1819,8 +1893,19 @@ async function maybeExecuteHostTool<TOOLS extends ToolSet>(input: {
1819
1893
  export async function validateToolCall<TOOLS extends ToolSet>(args: {
1820
1894
  event: Extract<HarnessV1StreamPart, { type: 'tool-call' }>;
1821
1895
  tools: TOOLS | undefined;
1896
+ harnessId: string;
1822
1897
  }): Promise<TextStreamPart<TOOLS>> {
1823
- const { event, tools } = args;
1898
+ const { event, tools, harnessId } = args;
1899
+ const providerMetadata =
1900
+ event.nativeName !== undefined && event.nativeName !== event.toolName
1901
+ ? {
1902
+ ...event.providerMetadata,
1903
+ [harnessId]: {
1904
+ ...event.providerMetadata?.[harnessId],
1905
+ nativeToolName: event.nativeName,
1906
+ },
1907
+ }
1908
+ : event.providerMetadata;
1824
1909
  const toolCall: LanguageModelV4ToolCall = {
1825
1910
  type: 'tool-call',
1826
1911
  toolCallId: event.toolCallId,
@@ -1830,9 +1915,7 @@ export async function validateToolCall<TOOLS extends ToolSet>(args: {
1830
1915
  ? { providerExecuted: event.providerExecuted }
1831
1916
  : {}),
1832
1917
  ...(event.dynamic !== undefined ? { dynamic: event.dynamic } : {}),
1833
- ...(event.providerMetadata !== undefined
1834
- ? { providerMetadata: event.providerMetadata }
1835
- : {}),
1918
+ ...(providerMetadata !== undefined ? { providerMetadata } : {}),
1836
1919
  };
1837
1920
 
1838
1921
  const parsed = await parseToolCall<TOOLS>({
@@ -635,7 +635,7 @@ export class SandboxChannel<
635
635
  }
636
636
 
637
637
  private rawSend(text: string): void {
638
- if (this.connected && this.ws) {
638
+ if (this.connected && this.ws && this.ws.readyState === this.ws.OPEN) {
639
639
  this.ws.send(text);
640
640
  } else {
641
641
  this.pendingSends.push(text);
@@ -643,9 +643,20 @@ export class SandboxChannel<
643
643
  }
644
644
 
645
645
  private flushPending(): void {
646
- if (!this.connected || !this.ws) return;
647
- const queued = this.pendingSends.splice(0);
648
- for (const text of queued) this.ws.send(text);
646
+ let sent = 0;
647
+ try {
648
+ while (
649
+ sent < this.pendingSends.length &&
650
+ this.connected &&
651
+ this.ws &&
652
+ this.ws.readyState === this.ws.OPEN
653
+ ) {
654
+ this.ws.send(this.pendingSends[sent]);
655
+ sent++;
656
+ }
657
+ } finally {
658
+ this.pendingSends.splice(0, sent);
659
+ }
649
660
  }
650
661
 
651
662
  private enqueue(work: () => void | Promise<void>): void {