@ai-sdk/harness 1.0.99 → 1.0.101

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.99",
3
+ "version": "1.0.101",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "sideEffects": false,
@@ -46,7 +46,7 @@
46
46
  "dependencies": {
47
47
  "@ai-sdk/provider": "4.0.10",
48
48
  "@ai-sdk/provider-utils": "5.0.36",
49
- "ai": "7.0.90"
49
+ "ai": "7.0.92"
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.90",
61
+ "@ai-sdk/otel": "1.0.92",
62
62
  "@opentelemetry/sdk-trace-base": "2.7.1",
63
63
  "@types/node": "22.19.19",
64
64
  "@types/ws": "^8.5.13",
@@ -1,6 +1,8 @@
1
1
  import type {
2
2
  Context,
3
3
  Experimental_SandboxSession as SandboxSession,
4
+ ToolApprovalResponse,
5
+ ToolResultPart,
4
6
  ToolSet,
5
7
  } from '@ai-sdk/provider-utils';
6
8
  import type {
@@ -29,8 +31,6 @@ import type {
29
31
  HarnessAgentResumeSessionState,
30
32
  HarnessAgentToolSpec,
31
33
  } from './harness-agent-types';
32
- import type { HarnessAgentToolApprovalContinuation } from './harness-agent-tool-approval-continuation';
33
- import type { HarnessAgentToolResultContinuation } from './harness-agent-tool-result-continuation';
34
34
  import { validateLifecycleStateData } from './internal/lifecycle-state-validation';
35
35
  import { runPrompt } from './internal/run-prompt';
36
36
  import { getRestrictedSandboxSession } from '../utils/get-restricted-sandbox-session';
@@ -306,12 +306,8 @@ export class HarnessAgentSession {
306
306
  output: OUTPUT | undefined;
307
307
  telemetry: TelemetryOptions | undefined;
308
308
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
309
- toolApprovalContinuations?:
310
- | readonly HarnessAgentToolApprovalContinuation[]
311
- | undefined;
312
- toolResultContinuations?:
313
- | readonly HarnessAgentToolResultContinuation[]
314
- | undefined;
309
+ toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
310
+ toolResultContinuations?: readonly ToolResultPart[] | undefined;
315
311
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
316
312
  const session = this.requireReusableSession();
317
313
  this.requireContinuableTurn();
@@ -5,17 +5,6 @@ import type {
5
5
  ToolApprovalResponse,
6
6
  } from '@ai-sdk/provider-utils';
7
7
 
8
- export type HarnessAgentToolApprovalContinuation = {
9
- readonly approvalResponse: ToolApprovalResponse;
10
- readonly toolCall: {
11
- readonly type: 'tool-call';
12
- readonly toolCallId: string;
13
- readonly toolName: string;
14
- readonly input: unknown;
15
- readonly providerExecuted?: boolean;
16
- };
17
- };
18
-
19
8
  /**
20
9
  * Extract approval decisions that should continue a suspended harness turn.
21
10
  *
@@ -28,14 +17,11 @@ export type HarnessAgentToolApprovalContinuation = {
28
17
  */
29
18
  export function collectHarnessAgentToolApprovalContinuations(input: {
30
19
  messages: readonly ModelMessage[];
31
- }): readonly HarnessAgentToolApprovalContinuation[] {
20
+ }): readonly ToolApprovalResponse[] {
32
21
  const lastMessage = input.messages.at(-1);
33
22
  if (lastMessage?.role !== 'tool') return [];
34
23
 
35
- const toolCallsByToolCallId = new Map<
36
- string,
37
- HarnessAgentToolApprovalContinuation['toolCall']
38
- >();
24
+ const toolCallIds = new Set<string>();
39
25
  const approvalRequestsByApprovalId = new Map<string, ToolApprovalRequest>();
40
26
  for (const message of input.messages) {
41
27
  if (message.role !== 'assistant' || typeof message.content === 'string') {
@@ -43,15 +29,7 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
43
29
  }
44
30
  for (const part of message.content) {
45
31
  if (part.type === 'tool-call') {
46
- toolCallsByToolCallId.set(part.toolCallId, {
47
- type: 'tool-call',
48
- toolCallId: part.toolCallId,
49
- toolName: part.toolName,
50
- input: part.input,
51
- ...(part.providerExecuted !== undefined
52
- ? { providerExecuted: part.providerExecuted }
53
- : {}),
54
- });
32
+ toolCallIds.add(part.toolCallId);
55
33
  } else if (part.type === 'tool-approval-request') {
56
34
  approvalRequestsByApprovalId.set(part.approvalId, part);
57
35
  }
@@ -65,7 +43,7 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
65
43
  }
66
44
  }
67
45
 
68
- const continuations: HarnessAgentToolApprovalContinuation[] = [];
46
+ const continuations: ToolApprovalResponse[] = [];
69
47
  for (const part of lastMessage.content) {
70
48
  if (part.type !== 'tool-approval-response') continue;
71
49
 
@@ -77,17 +55,13 @@ export function collectHarnessAgentToolApprovalContinuations(input: {
77
55
  }
78
56
  if (toolResultIds.has(approvalRequest.toolCallId)) continue;
79
57
 
80
- const toolCall = toolCallsByToolCallId.get(approvalRequest.toolCallId);
81
- if (toolCall == null) {
58
+ if (!toolCallIds.has(approvalRequest.toolCallId)) {
82
59
  throw new HarnessError({
83
60
  message: `Tool approval request '${approvalRequest.approvalId}' references unknown tool call '${approvalRequest.toolCallId}'.`,
84
61
  });
85
62
  }
86
63
 
87
- continuations.push({
88
- approvalResponse: part,
89
- toolCall,
90
- });
64
+ continuations.push(part);
91
65
  }
92
66
 
93
67
  return continuations;
@@ -1,62 +1,13 @@
1
- import type { ModelMessage, ToolResultOutput } from '@ai-sdk/provider-utils';
2
-
3
- export type HarnessAgentToolResultContinuation = {
4
- readonly toolCallId: string;
5
- readonly output: unknown;
6
- readonly isError?: boolean;
7
- };
1
+ import type { ModelMessage, ToolResultPart } from '@ai-sdk/provider-utils';
8
2
 
9
3
  /**
10
- * Extract client-provided tool results from the trailing tool message and
11
- * convert AI SDK model-output wrappers back into values a harness runtime can
12
- * return from its pending host-tool invocation.
4
+ * Extract client-provided tool results from the trailing tool message.
13
5
  */
14
6
  export function collectHarnessAgentToolResultContinuations(input: {
15
7
  messages: readonly ModelMessage[];
16
- }): readonly HarnessAgentToolResultContinuation[] {
8
+ }): readonly ToolResultPart[] {
17
9
  const lastMessage = input.messages.at(-1);
18
10
  if (lastMessage?.role !== 'tool') return [];
19
11
 
20
- return lastMessage.content
21
- .filter(part => part.type === 'tool-result')
22
- .map(part =>
23
- toToolResultContinuation({
24
- toolCallId: part.toolCallId,
25
- output: part.output,
26
- }),
27
- );
28
- }
29
-
30
- function toToolResultContinuation(input: {
31
- toolCallId: string;
32
- output: ToolResultOutput;
33
- }): HarnessAgentToolResultContinuation {
34
- switch (input.output.type) {
35
- case 'text':
36
- case 'json':
37
- return {
38
- toolCallId: input.toolCallId,
39
- output: input.output.value,
40
- };
41
- case 'error-text':
42
- case 'error-json':
43
- return {
44
- toolCallId: input.toolCallId,
45
- output: input.output.value,
46
- isError: true,
47
- };
48
- case 'execution-denied':
49
- return {
50
- toolCallId: input.toolCallId,
51
- output: {
52
- type: input.output.type,
53
- reason: input.output.reason,
54
- },
55
- };
56
- case 'content':
57
- return {
58
- toolCallId: input.toolCallId,
59
- output: input.output,
60
- };
61
- }
12
+ return lastMessage.content.filter(part => part.type === 'tool-result');
62
13
  }
@@ -1,5 +1,6 @@
1
1
  import { HarnessCapabilityUnsupportedError } from '../errors/harness-capability-unsupported-error';
2
2
  import type {
3
+ HarnessV1,
3
4
  HarnessV1BuiltinToolFiltering,
4
5
  HarnessV1JSONSchema,
5
6
  HarnessV1NetworkSandboxSession,
@@ -13,6 +14,8 @@ import {
13
14
  type Context,
14
15
  type Experimental_SandboxSession as SandboxSession,
15
16
  type ModelMessage,
17
+ type ToolApprovalResponse,
18
+ type ToolResultPart,
16
19
  type ToolSet,
17
20
  } from '@ai-sdk/provider-utils';
18
21
  import type {
@@ -41,14 +44,8 @@ import type {
41
44
  HarnessAgentSkill,
42
45
  HarnessAgentToolSpec,
43
46
  } from './harness-agent-types';
44
- import {
45
- collectHarnessAgentToolApprovalContinuations,
46
- type HarnessAgentToolApprovalContinuation,
47
- } from './harness-agent-tool-approval-continuation';
48
- import {
49
- collectHarnessAgentToolResultContinuations,
50
- type HarnessAgentToolResultContinuation,
51
- } from './harness-agent-tool-result-continuation';
47
+ import { collectHarnessAgentToolApprovalContinuations } from './harness-agent-tool-approval-continuation';
48
+ import { collectHarnessAgentToolResultContinuations } from './harness-agent-tool-result-continuation';
52
49
  import {
53
50
  applyBootstrapRecipe,
54
51
  hashHarnessBootstrap,
@@ -86,8 +83,8 @@ export interface HarnessAgentCallExtensions {
86
83
  }
87
84
 
88
85
  type HarnessAgentContinueTurnInput = {
89
- toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
90
- toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
86
+ toolApprovalContinuations: readonly ToolApprovalResponse[];
87
+ toolResultContinuations: readonly ToolResultPart[];
91
88
  };
92
89
 
93
90
  type PreparedHarnessAgentTurnSettings<
@@ -114,8 +111,8 @@ type PreparedHarnessAgentContinueTurnInput<
114
111
  THarness extends HarnessAgentAdapter<any>,
115
112
  TUserTools extends ToolSet,
116
113
  > = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
117
- toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
118
- toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
114
+ toolApprovalContinuations: readonly ToolApprovalResponse[];
115
+ toolResultContinuations: readonly ToolResultPart[];
119
116
  };
120
117
 
121
118
  type HarnessAgentTurnResult<
@@ -224,6 +221,10 @@ export class HarnessAgent<
224
221
  this.sandboxConfig = sandboxConfig;
225
222
  this.id = settings.id;
226
223
  const userTools = settings.tools ?? ({} as TUserTools);
224
+ assertNoReservedQuestionTool({
225
+ harness: settings.harness,
226
+ userTools,
227
+ });
227
228
  this.permissionMode = resolvePermissionMode({
228
229
  permissionMode: settings.permissionMode,
229
230
  });
@@ -609,8 +610,8 @@ export class HarnessAgent<
609
610
  */
610
611
  async continueGenerate(options: {
611
612
  session: HarnessAgentSession;
612
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
613
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
613
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
614
+ toolResultContinuations?: readonly ToolResultPart[];
614
615
  abortSignal?: AbortSignal;
615
616
  }): Promise<
616
617
  GenerateTextResult<
@@ -643,8 +644,8 @@ export class HarnessAgent<
643
644
  */
644
645
  async continueStream(options: {
645
646
  session: HarnessAgentSession;
646
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
647
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
647
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
648
+ toolResultContinuations?: readonly ToolResultPart[];
648
649
  abortSignal?: AbortSignal;
649
650
  }): Promise<
650
651
  StreamTextResult<
@@ -903,8 +904,8 @@ export class HarnessAgent<
903
904
  }
904
905
 
905
906
  private _prepareContinueTurnInput(options: {
906
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
907
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
907
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
908
+ toolResultContinuations?: readonly ToolResultPart[];
908
909
  }): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
909
910
  return {
910
911
  ...this._prepareTurnSettings({
@@ -925,6 +926,10 @@ export class HarnessAgent<
925
926
  tools?: TUserTools;
926
927
  }): PreparedHarnessAgentTurnSettings<THarness, TUserTools> {
927
928
  const userTools = options.tools ?? ({} as TUserTools);
929
+ assertNoReservedQuestionTool({
930
+ harness: this.settings.harness,
931
+ userTools,
932
+ });
928
933
  const tools = {
929
934
  ...this.settings.harness.builtinTools,
930
935
  ...userTools,
@@ -1028,6 +1033,23 @@ export class HarnessAgent<
1028
1033
  }
1029
1034
  }
1030
1035
 
1036
+ function assertNoReservedQuestionTool(input: {
1037
+ harness: HarnessV1;
1038
+ userTools: ToolSet;
1039
+ }): void {
1040
+ if (
1041
+ Object.prototype.hasOwnProperty.call(
1042
+ input.harness.builtinTools,
1043
+ 'askUserQuestions',
1044
+ ) &&
1045
+ Object.prototype.hasOwnProperty.call(input.userTools, 'askUserQuestions')
1046
+ ) {
1047
+ throw new Error(
1048
+ "HarnessAgent tool name 'askUserQuestions' is reserved for harness question requests.",
1049
+ );
1050
+ }
1051
+ }
1052
+
1031
1053
  /*
1032
1054
  * `GenerateTextResult` view over a drained `streamText` run. Non-deprecated
1033
1055
  * members derive from `steps` (the single source of truth), and the deprecated
@@ -21,6 +21,8 @@ import {
21
21
  safeParseJSON,
22
22
  type Context,
23
23
  type Experimental_SandboxSession as SandboxSession,
24
+ type ToolApprovalResponse,
25
+ type ToolResultPart,
24
26
  type ToolSet,
25
27
  } from '@ai-sdk/provider-utils';
26
28
  import {
@@ -39,8 +41,6 @@ import type {
39
41
  TelemetryOptions,
40
42
  TextStreamPart,
41
43
  } from 'ai';
42
- import type { HarnessAgentToolApprovalContinuation } from '../harness-agent-tool-approval-continuation';
43
- import type { HarnessAgentToolResultContinuation } from '../harness-agent-tool-result-continuation';
44
44
  import type { HarnessAgentToolApprovalConfiguration } from '../harness-agent-settings';
45
45
  import { HarnessStreamTextResult } from './harness-stream-text-result';
46
46
  import { translateStreamPart } from './translate-stream-part';
@@ -54,6 +54,29 @@ import { resolveCustomToolApproval } from './permission-mode';
54
54
  import { logBridgeError } from '../../utils/bridge-diagnostics';
55
55
  import { pinSandboxChannelEventCheckpoint } from '../../utils/sandbox-channel';
56
56
 
57
+ function unwrapToolResultOutput(toolResult: ToolResultPart): {
58
+ output: unknown;
59
+ isError?: boolean;
60
+ } {
61
+ switch (toolResult.output.type) {
62
+ case 'text':
63
+ case 'json':
64
+ return { output: toolResult.output.value };
65
+ case 'error-text':
66
+ case 'error-json':
67
+ return { output: toolResult.output.value, isError: true };
68
+ case 'execution-denied':
69
+ return {
70
+ output: {
71
+ type: toolResult.output.type,
72
+ reason: toolResult.output.reason,
73
+ },
74
+ };
75
+ case 'content':
76
+ return { output: toolResult.output };
77
+ }
78
+ }
79
+
57
80
  /**
58
81
  * Drive one prompt turn end-to-end:
59
82
  * - call `session.doPromptTurn` via `toHarnessStream`
@@ -99,12 +122,8 @@ export function runPrompt<
99
122
  toolApproval?: HarnessAgentToolApprovalConfiguration | undefined;
100
123
  pendingToolApprovals?: readonly HarnessV1PendingToolApproval[];
101
124
  pendingToolResults?: readonly HarnessV1PendingToolResult[];
102
- toolApprovalContinuations?:
103
- | readonly HarnessAgentToolApprovalContinuation[]
104
- | undefined;
105
- toolResultContinuations?:
106
- | readonly HarnessAgentToolResultContinuation[]
107
- | undefined;
125
+ toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
126
+ toolResultContinuations?: readonly ToolResultPart[] | undefined;
108
127
  onPendingToolApproval?: (approval: HarnessV1PendingToolApproval) => void;
109
128
  onToolApprovalSettled?: (approvalId: string) => void;
110
129
  onPendingToolResult?: (pendingResult: HarnessV1PendingToolResult) => void;
@@ -267,7 +286,7 @@ export function runPrompt<
267
286
  );
268
287
  const continuationsByApprovalId = new Map(
269
288
  (input.toolApprovalContinuations ?? []).map(continuation => [
270
- continuation.approvalResponse.approvalId,
289
+ continuation.approvalId,
271
290
  continuation,
272
291
  ]),
273
292
  );
@@ -433,15 +452,16 @@ export function runPrompt<
433
452
  };
434
453
  const enqueueApprovalResponse = (
435
454
  approval: HarnessV1PendingToolApproval,
436
- continuation: HarnessAgentToolApprovalContinuation,
455
+ continuation: ToolApprovalResponse,
456
+ toolCall: ToolCallTextStreamPart,
437
457
  ): void => {
438
458
  result.enqueueContinuation({
439
459
  type: 'tool-approval-response',
440
460
  approvalId: approval.approvalId,
441
- toolCall: continuation.toolCall,
442
- approved: continuation.approvalResponse.approved,
443
- ...(continuation.approvalResponse.reason !== undefined
444
- ? { reason: continuation.approvalResponse.reason }
461
+ toolCall,
462
+ approved: continuation.approved,
463
+ ...(continuation.reason !== undefined
464
+ ? { reason: continuation.reason }
445
465
  : {}),
446
466
  ...(approval.providerExecuted !== undefined
447
467
  ? { providerExecuted: approval.providerExecuted }
@@ -457,6 +477,9 @@ export function runPrompt<
457
477
  toolCallId: options.toolCall.toolCallId,
458
478
  toolName: options.toolCall.toolName,
459
479
  input: options.toolCall.input,
480
+ ...(options.toolCall.providerMetadata !== undefined
481
+ ? { providerOptions: options.toolCall.providerMetadata }
482
+ : {}),
460
483
  } satisfies HarnessV1PendingToolResult);
461
484
  pendingResultsByToolCallId.set(pendingResult.toolCallId, pendingResult);
462
485
  onPendingToolResult(pendingResult);
@@ -464,19 +487,28 @@ export function runPrompt<
464
487
  };
465
488
  const processPendingToolResultContinuation = async (
466
489
  pendingResult: HarnessV1PendingToolResult,
467
- continuation: HarnessAgentToolResultContinuation,
490
+ continuation: ToolResultPart,
468
491
  ): Promise<void> => {
492
+ const result = unwrapToolResultOutput(continuation);
469
493
  onToolResultSettled(pendingResult.toolCallId);
470
494
  pendingResultsByToolCallId.delete(pendingResult.toolCallId);
471
495
  settledHostToolCallIds.add(pendingResult.toolCallId);
472
496
  await control.submitToolResult({
473
497
  toolCallId: pendingResult.toolCallId,
474
- output: continuation.output,
475
- isError: continuation.isError,
498
+ output: result.output,
499
+ isError: result.isError,
500
+ toolResult: {
501
+ ...continuation,
502
+ toolName: pendingResult.toolName,
503
+ ...(continuation.providerOptions == null &&
504
+ pendingResult.providerOptions != null
505
+ ? { providerOptions: pendingResult.providerOptions }
506
+ : {}),
507
+ },
476
508
  });
477
509
  };
478
510
  const enqueueHostToolOutcome = (options: {
479
- toolCall: HarnessAgentToolApprovalContinuation['toolCall'];
511
+ toolCall: ToolCallTextStreamPart;
480
512
  outcome: HostToolOutcome;
481
513
  }): void => {
482
514
  if (options.outcome.ok) {
@@ -512,9 +544,30 @@ export function runPrompt<
512
544
  };
513
545
  const processPendingApprovalContinuation = async (
514
546
  approval: HarnessV1PendingToolApproval,
515
- continuation: HarnessAgentToolApprovalContinuation,
547
+ continuation: ToolApprovalResponse,
516
548
  ): Promise<'continued' | 'awaiting-tool-result'> => {
517
- enqueueApprovalResponse(approval, continuation);
549
+ const rawToolCall =
550
+ rawToolCallsByToolCallId.get(approval.toolCallId) ??
551
+ ({
552
+ type: 'tool-call',
553
+ toolCallId: approval.toolCallId,
554
+ toolName: approval.toolName,
555
+ input: approval.input,
556
+ providerExecuted: approval.providerExecuted,
557
+ nativeName: approval.nativeName,
558
+ } satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
559
+ const parsedInput = await safeParseJSON({ text: rawToolCall.input });
560
+ const toolCall: ToolCallTextStreamPart = {
561
+ type: 'tool-call',
562
+ toolCallId: rawToolCall.toolCallId,
563
+ toolName: rawToolCall.toolName,
564
+ input: parsedInput.success ? parsedInput.value : rawToolCall.input,
565
+ ...(rawToolCall.providerExecuted !== undefined
566
+ ? { providerExecuted: rawToolCall.providerExecuted }
567
+ : {}),
568
+ };
569
+
570
+ enqueueApprovalResponse(approval, continuation, toolCall);
518
571
  onToolApprovalSettled(approval.approvalId);
519
572
  pendingApprovalsByApprovalId.delete(approval.approvalId);
520
573
  pendingApprovalsByToolCallId.delete(approval.toolCallId);
@@ -528,33 +581,24 @@ export function runPrompt<
528
581
  }
529
582
  await control.submitToolApproval({
530
583
  approvalId: approval.approvalId,
531
- approved: continuation.approvalResponse.approved,
532
- reason: continuation.approvalResponse.reason,
584
+ approved: continuation.approved,
585
+ reason: continuation.reason,
533
586
  });
534
587
  return 'continued';
535
588
  }
536
589
 
537
590
  settledHostToolCallIds.add(approval.toolCallId);
538
- if (!continuation.approvalResponse.approved) {
591
+ if (!continuation.approved) {
539
592
  await control.submitToolResult({
540
593
  toolCallId: approval.toolCallId,
541
594
  output: {
542
595
  type: 'execution-denied',
543
- reason: continuation.approvalResponse.reason,
596
+ reason: continuation.reason,
544
597
  },
545
598
  });
546
599
  return 'continued';
547
600
  }
548
601
 
549
- const rawToolCall =
550
- rawToolCallsByToolCallId.get(approval.toolCallId) ??
551
- ({
552
- type: 'tool-call',
553
- toolCallId: approval.toolCallId,
554
- toolName: approval.toolName,
555
- input: approval.input,
556
- } satisfies Extract<HarnessV1StreamPart, { type: 'tool-call' }>);
557
-
558
602
  await telemetry.start(input.model);
559
603
  await telemetry.toolStart({
560
604
  toolCallId: rawToolCall.toolCallId,
@@ -597,7 +641,7 @@ export function runPrompt<
597
641
  return 'awaiting-tool-result';
598
642
  }
599
643
  enqueueHostToolOutcome({
600
- toolCall: continuation.toolCall,
644
+ toolCall,
601
645
  outcome: execution.outcome,
602
646
  });
603
647
  await telemetry.toolEnd(rawToolCall.toolCallId, execution.outcome);
@@ -940,7 +984,16 @@ export function runPrompt<
940
984
  `Harness '${input.harness.harnessId}' could not find parsed tool call '${toolCall.toolCallId}' for custom tool approval.`,
941
985
  );
942
986
  }
943
- if (!hasTool({ tools: activeTools, toolName: toolCall.toolName })) {
987
+ const isClientExecutedBuiltin =
988
+ toolCall.toolName === 'askUserQuestions' &&
989
+ Object.prototype.hasOwnProperty.call(
990
+ input.harness.builtinTools,
991
+ toolCall.toolName,
992
+ );
993
+ if (
994
+ !isClientExecutedBuiltin &&
995
+ !hasTool({ tools: activeTools, toolName: toolCall.toolName })
996
+ ) {
944
997
  const output = {
945
998
  type: 'execution-denied',
946
999
  reason: getHarnessV1BuiltinToolFilteringDenialReason({
@@ -954,6 +1007,18 @@ export function runPrompt<
954
1007
  await telemetry.toolEnd(toolCall.toolCallId, { ok: true, output });
955
1008
  continue;
956
1009
  }
1010
+ if (isClientExecutedBuiltin) {
1011
+ recordPendingToolResult({ toolCall });
1012
+ if (
1013
+ expectedStepToolCallCount != null &&
1014
+ observedStepToolCallCount < expectedStepToolCallCount
1015
+ ) {
1016
+ pauseAfterStepToolCalls = true;
1017
+ continue;
1018
+ }
1019
+ await finishForHostInputPause({ completeCurrentStep: true });
1020
+ return;
1021
+ }
957
1022
  const customToolApprovalDecision = resolveCustomToolApproval({
958
1023
  toolName: toolCall.toolName,
959
1024
  toolApproval: input.toolApproval,