@ai-sdk/harness 1.0.100 → 1.0.102

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.100",
3
+ "version": "1.0.102",
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.91"
49
+ "ai": "7.0.93"
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.91",
61
+ "@ai-sdk/otel": "1.0.93",
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,11 +31,10 @@ 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';
37
+ import type { HarnessAgentLifecycleCallbacks } from './internal/turn-telemetry';
37
38
 
38
39
  type HarnessAgentTurnResult<
39
40
  TOOLS extends ToolSet,
@@ -207,6 +208,7 @@ export class HarnessAgentSession {
207
208
  responseFormat: HarnessV1ResponseFormat | undefined;
208
209
  output: OUTPUT | undefined;
209
210
  telemetry: TelemetryOptions | undefined;
211
+ callbacks: HarnessAgentLifecycleCallbacks<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
210
212
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
211
213
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
212
214
  const session = this.requireReusableSession();
@@ -246,6 +248,7 @@ export class HarnessAgentSession {
246
248
  responseFormat: options.responseFormat,
247
249
  output: options.output,
248
250
  telemetry: options.telemetry,
251
+ callbacks: options.callbacks,
249
252
  stopConditions: options.stopConditions,
250
253
  toolApproval: this.toolApproval,
251
254
  pendingToolApprovals: this.getPendingToolApprovals(),
@@ -305,13 +308,10 @@ export class HarnessAgentSession {
305
308
  responseFormat: HarnessV1ResponseFormat | undefined;
306
309
  output: OUTPUT | undefined;
307
310
  telemetry: TelemetryOptions | undefined;
311
+ callbacks: HarnessAgentLifecycleCallbacks<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
308
312
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
309
- toolApprovalContinuations?:
310
- | readonly HarnessAgentToolApprovalContinuation[]
311
- | undefined;
312
- toolResultContinuations?:
313
- | readonly HarnessAgentToolResultContinuation[]
314
- | undefined;
313
+ toolApprovalContinuations?: readonly ToolApprovalResponse[] | undefined;
314
+ toolResultContinuations?: readonly ToolResultPart[] | undefined;
315
315
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
316
316
  const session = this.requireReusableSession();
317
317
  this.requireContinuableTurn();
@@ -345,6 +345,7 @@ export class HarnessAgentSession {
345
345
  responseFormat: options.responseFormat,
346
346
  output: options.output,
347
347
  telemetry: options.telemetry,
348
+ callbacks: options.callbacks,
348
349
  stopConditions: options.stopConditions,
349
350
  toolApproval: this.toolApproval,
350
351
  pendingToolApprovals: this.getPendingToolApprovals(),
@@ -19,6 +19,14 @@ import type {
19
19
  import type {
20
20
  ActiveTools,
21
21
  AgentCallParameters,
22
+ GenerateTextOnEndCallback,
23
+ GenerateTextOnStartCallback,
24
+ GenerateTextOnStepEndCallback,
25
+ GenerateTextOnStepStartCallback,
26
+ OnLanguageModelCallEndCallback,
27
+ OnLanguageModelCallStartCallback,
28
+ OnToolExecutionEndCallback,
29
+ OnToolExecutionStartCallback,
22
30
  OutputInterface as Output,
23
31
  Prompt,
24
32
  StopCondition,
@@ -78,10 +86,12 @@ type HarnessTools<TOOLS extends ToolSet> = ActiveTools<NoInfer<TOOLS>>;
78
86
  /**
79
87
  * Construction-time settings for a `HarnessAgent`.
80
88
  *
81
- * Prompt, abortSignal, callbacks, and custom call options belong on the
89
+ * Prompt, abortSignal, and custom call options belong on the
82
90
  * `AgentCallParameters` / `AgentStreamParameters` passed to `generate` /
83
- * `stream`. `prepareCall` can derive turn-scoped model, skills, instructions,
84
- * and tools from those custom call options.
91
+ * `stream`. Lifecycle callbacks can be configured here for every call, while
92
+ * the callbacks supported by `AgentCallParameters` can also be added per call.
93
+ * `prepareCall` can derive turn-scoped model, skills, instructions, and tools
94
+ * from custom call options.
85
95
  */
86
96
  type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> =
87
97
  | {
@@ -153,6 +163,14 @@ export type HarnessAgentSettings<
153
163
  */
154
164
  readonly instructions?: string;
155
165
 
166
+ /**
167
+ * Additional HTTP headers to be sent with every model request.
168
+ *
169
+ * `authorization`, `x-api-key`, `user-agent`, and `x-client-app` are
170
+ * managed by the harness and are not allowed.
171
+ */
172
+ readonly headers?: Record<string, string | undefined>;
173
+
156
174
  /**
157
175
  * Schema for validating the custom options passed to each agent call.
158
176
  */
@@ -242,6 +260,67 @@ export type HarnessAgentSettings<
242
260
  >
243
261
  >;
244
262
 
263
+ /**
264
+ * Called when an agent call begins, before any model steps.
265
+ */
266
+ readonly onStart?: GenerateTextOnStartCallback<
267
+ NoInfer<HarnessAllTools<THarness, TUserTools>>,
268
+ RUNTIME_CONTEXT,
269
+ NoInfer<OUTPUT>
270
+ >;
271
+
272
+ /**
273
+ * Called when a model step begins.
274
+ */
275
+ readonly onStepStart?: GenerateTextOnStepStartCallback<
276
+ NoInfer<HarnessAllTools<THarness, TUserTools>>,
277
+ NoInfer<RUNTIME_CONTEXT>,
278
+ NoInfer<OUTPUT>
279
+ >;
280
+
281
+ /**
282
+ * Called immediately before the harness begins emitting a model response.
283
+ */
284
+ readonly onLanguageModelCallStart?: OnLanguageModelCallStartCallback;
285
+
286
+ /**
287
+ * Called after a model response is complete and before its tool execution
288
+ * lifecycle callbacks are delivered.
289
+ */
290
+ readonly onLanguageModelCallEnd?: OnLanguageModelCallEndCallback<
291
+ NoInfer<HarnessAllTools<THarness, TUserTools>>
292
+ >;
293
+
294
+ /**
295
+ * Called before each harness or host tool execution is reported.
296
+ */
297
+ readonly onToolExecutionStart?: OnToolExecutionStartCallback<
298
+ NoInfer<HarnessAllTools<THarness, TUserTools>>
299
+ >;
300
+
301
+ /**
302
+ * Called after each harness or host tool execution is reported.
303
+ */
304
+ readonly onToolExecutionEnd?: OnToolExecutionEndCallback<
305
+ NoInfer<HarnessAllTools<THarness, TUserTools>>
306
+ >;
307
+
308
+ /**
309
+ * Called after each completed model step.
310
+ */
311
+ readonly onStepEnd?: GenerateTextOnStepEndCallback<
312
+ NoInfer<HarnessAllTools<THarness, TUserTools>>,
313
+ NoInfer<RUNTIME_CONTEXT>
314
+ >;
315
+
316
+ /**
317
+ * Called when an agent call completes successfully.
318
+ */
319
+ readonly onEnd?: GenerateTextOnEndCallback<
320
+ NoInfer<HarnessAllTools<THarness, TUserTools>>,
321
+ NoInfer<RUNTIME_CONTEXT>
322
+ >;
323
+
245
324
  /**
246
325
  * Built-in tool permission mode. Defaults to `'allow-all'`, preserving the
247
326
  * existing bypass-permissions behavior unless users opt in.
@@ -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,
@@ -9,12 +10,16 @@ import {
9
10
  asArray,
10
11
  asSchema,
11
12
  generateId,
13
+ normalizeHeaders,
12
14
  validateTypes,
13
15
  type Context,
14
16
  type Experimental_SandboxSession as SandboxSession,
15
17
  type ModelMessage,
18
+ type ToolApprovalResponse,
19
+ type ToolResultPart,
16
20
  type ToolSet,
17
21
  } from '@ai-sdk/provider-utils';
22
+ import { mergeCallbacks } from 'ai/internal';
18
23
  import type {
19
24
  Agent,
20
25
  AgentCallParameters,
@@ -41,14 +46,8 @@ import type {
41
46
  HarnessAgentSkill,
42
47
  HarnessAgentToolSpec,
43
48
  } 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';
49
+ import { collectHarnessAgentToolApprovalContinuations } from './harness-agent-tool-approval-continuation';
50
+ import { collectHarnessAgentToolResultContinuations } from './harness-agent-tool-result-continuation';
52
51
  import {
53
52
  applyBootstrapRecipe,
54
53
  hashHarnessBootstrap,
@@ -68,6 +67,7 @@ import {
68
67
  import { resolveHarnessAgentToolFiltering } from './internal/tool-filtering';
69
68
  import { resolveSandboxDefaultWorkingDirectory } from '../utils/resolve-sandbox-default-working-directory';
70
69
  import { getRestrictedSandboxSession } from '../utils/get-restricted-sandbox-session';
70
+ import type { HarnessAgentLifecycleCallbacks } from './internal/turn-telemetry';
71
71
 
72
72
  export type { HarnessAllTools } from './harness-agent-tool-types';
73
73
 
@@ -86,8 +86,8 @@ export interface HarnessAgentCallExtensions {
86
86
  }
87
87
 
88
88
  type HarnessAgentContinueTurnInput = {
89
- toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
90
- toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
89
+ toolApprovalContinuations: readonly ToolApprovalResponse[];
90
+ toolResultContinuations: readonly ToolResultPart[];
91
91
  };
92
92
 
93
93
  type PreparedHarnessAgentTurnSettings<
@@ -114,8 +114,8 @@ type PreparedHarnessAgentContinueTurnInput<
114
114
  THarness extends HarnessAgentAdapter<any>,
115
115
  TUserTools extends ToolSet,
116
116
  > = PreparedHarnessAgentTurnSettings<THarness, TUserTools> & {
117
- toolApprovalContinuations: readonly HarnessAgentToolApprovalContinuation[];
118
- toolResultContinuations: readonly HarnessAgentToolResultContinuation[];
117
+ toolApprovalContinuations: readonly ToolApprovalResponse[];
118
+ toolResultContinuations: readonly ToolResultPart[];
119
119
  };
120
120
 
121
121
  type HarnessAgentTurnResult<
@@ -206,6 +206,7 @@ export class HarnessAgent<
206
206
  | HarnessV1BuiltinToolFiltering
207
207
  | undefined;
208
208
  private readonly permissionMode: HarnessAgentPermissionMode;
209
+ private readonly headers: Readonly<Record<string, string>> | undefined;
209
210
 
210
211
  constructor(
211
212
  settings: HarnessAgentSettings<
@@ -222,8 +223,28 @@ export class HarnessAgent<
222
223
  this.stopConditions =
223
224
  settings.stopWhen == null ? [] : asArray(settings.stopWhen);
224
225
  this.sandboxConfig = sandboxConfig;
226
+ const forbiddenHeaders = new Set([
227
+ 'authorization',
228
+ 'x-api-key',
229
+ 'user-agent',
230
+ 'x-client-app',
231
+ ]);
232
+ const forbiddenHeader = Object.keys(settings.headers ?? {})
233
+ .map(name => name.toLowerCase())
234
+ .find(name => forbiddenHeaders.has(name));
235
+ if (forbiddenHeader != null) {
236
+ throw new Error(
237
+ `HarnessAgent: \`headers\` must not include the managed header \`${forbiddenHeader}\`.`,
238
+ );
239
+ }
240
+ const headers = normalizeHeaders(settings.headers);
241
+ this.headers = Object.keys(headers).length === 0 ? undefined : headers;
225
242
  this.id = settings.id;
226
243
  const userTools = settings.tools ?? ({} as TUserTools);
244
+ assertNoReservedQuestionTool({
245
+ harness: settings.harness,
246
+ userTools,
247
+ });
227
248
  this.permissionMode = resolvePermissionMode({
228
249
  permissionMode: settings.permissionMode,
229
250
  });
@@ -501,6 +522,7 @@ export class HarnessAgent<
501
522
  try {
502
523
  const baseStartOptions = {
503
524
  sessionId,
525
+ ...(this.headers == null ? {} : { headers: this.headers }),
504
526
  resumeFrom: validatedResumeFrom,
505
527
  continueFrom: effectiveContinueFrom,
506
528
  permissionMode: this.permissionMode,
@@ -609,8 +631,8 @@ export class HarnessAgent<
609
631
  */
610
632
  async continueGenerate(options: {
611
633
  session: HarnessAgentSession;
612
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
613
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
634
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
635
+ toolResultContinuations?: readonly ToolResultPart[];
614
636
  abortSignal?: AbortSignal;
615
637
  }): Promise<
616
638
  GenerateTextResult<
@@ -643,8 +665,8 @@ export class HarnessAgent<
643
665
  */
644
666
  async continueStream(options: {
645
667
  session: HarnessAgentSession;
646
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
647
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
668
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
669
+ toolResultContinuations?: readonly ToolResultPart[];
648
670
  abortSignal?: AbortSignal;
649
671
  }): Promise<
650
672
  StreamTextResult<
@@ -707,6 +729,7 @@ export class HarnessAgent<
707
729
  runtimeContext: input.runtimeContext,
708
730
  abortSignal: input.options.abortSignal,
709
731
  responseFormat,
732
+ callbacks: this._resolveLifecycleCallbacks(input.options),
710
733
  }),
711
734
  prompt: turnInput.prompt,
712
735
  });
@@ -733,6 +756,7 @@ export class HarnessAgent<
733
756
  runtimeContext: input.runtimeContext,
734
757
  abortSignal: input.abortSignal,
735
758
  responseFormat,
759
+ callbacks: this._resolveLifecycleCallbacks(),
736
760
  }),
737
761
  toolApprovalContinuations: turnInput.toolApprovalContinuations,
738
762
  toolResultContinuations: turnInput.toolResultContinuations,
@@ -744,6 +768,11 @@ export class HarnessAgent<
744
768
  runtimeContext: RUNTIME_CONTEXT;
745
769
  abortSignal: AbortSignal | undefined;
746
770
  responseFormat: HarnessV1ResponseFormat | undefined;
771
+ callbacks: HarnessAgentLifecycleCallbacks<
772
+ HarnessAllTools<THarness, TUserTools>,
773
+ RUNTIME_CONTEXT,
774
+ OUTPUT
775
+ >;
747
776
  }) {
748
777
  return {
749
778
  model: input.turnSettings.model,
@@ -758,10 +787,49 @@ export class HarnessAgent<
758
787
  responseFormat: input.responseFormat,
759
788
  output: this.settings.output,
760
789
  telemetry: this.settings.telemetry,
790
+ callbacks: input.callbacks,
761
791
  stopConditions: this.stopConditions,
762
792
  };
763
793
  }
764
794
 
795
+ private _resolveLifecycleCallbacks(
796
+ call?: AgentCallParameters<
797
+ CALL_OPTIONS,
798
+ HarnessAllTools<THarness, TUserTools>,
799
+ RUNTIME_CONTEXT
800
+ >,
801
+ ): HarnessAgentLifecycleCallbacks<
802
+ HarnessAllTools<THarness, TUserTools>,
803
+ RUNTIME_CONTEXT,
804
+ OUTPUT
805
+ > {
806
+ return {
807
+ onStart: mergeCallbacks(
808
+ this.settings.onStart,
809
+ call?.onStart ?? call?.experimental_onStart,
810
+ ),
811
+ onStepStart: mergeCallbacks(
812
+ this.settings.onStepStart,
813
+ call?.onStepStart ?? call?.experimental_onStepStart,
814
+ ),
815
+ onLanguageModelCallStart: this.settings.onLanguageModelCallStart,
816
+ onLanguageModelCallEnd: this.settings.onLanguageModelCallEnd,
817
+ onToolExecutionStart: mergeCallbacks(
818
+ this.settings.onToolExecutionStart,
819
+ call?.onToolExecutionStart ?? call?.experimental_onToolCallStart,
820
+ ),
821
+ onToolExecutionEnd: mergeCallbacks(
822
+ this.settings.onToolExecutionEnd,
823
+ call?.onToolExecutionEnd ?? call?.experimental_onToolCallFinish,
824
+ ),
825
+ onStepEnd: mergeCallbacks(
826
+ this.settings.onStepEnd,
827
+ call?.onStepEnd ?? call?.onStepFinish,
828
+ ),
829
+ onEnd: mergeCallbacks(this.settings.onEnd, call?.onEnd ?? call?.onFinish),
830
+ };
831
+ }
832
+
765
833
  private _resolveContinueTurnInput(options: {
766
834
  prompt?: string | ModelMessage[];
767
835
  messages?: ModelMessage[];
@@ -903,8 +971,8 @@ export class HarnessAgent<
903
971
  }
904
972
 
905
973
  private _prepareContinueTurnInput(options: {
906
- toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[];
907
- toolResultContinuations?: readonly HarnessAgentToolResultContinuation[];
974
+ toolApprovalContinuations?: readonly ToolApprovalResponse[];
975
+ toolResultContinuations?: readonly ToolResultPart[];
908
976
  }): PreparedHarnessAgentContinueTurnInput<THarness, TUserTools> {
909
977
  return {
910
978
  ...this._prepareTurnSettings({
@@ -925,6 +993,10 @@ export class HarnessAgent<
925
993
  tools?: TUserTools;
926
994
  }): PreparedHarnessAgentTurnSettings<THarness, TUserTools> {
927
995
  const userTools = options.tools ?? ({} as TUserTools);
996
+ assertNoReservedQuestionTool({
997
+ harness: this.settings.harness,
998
+ userTools,
999
+ });
928
1000
  const tools = {
929
1001
  ...this.settings.harness.builtinTools,
930
1002
  ...userTools,
@@ -1028,6 +1100,23 @@ export class HarnessAgent<
1028
1100
  }
1029
1101
  }
1030
1102
 
1103
+ function assertNoReservedQuestionTool(input: {
1104
+ harness: HarnessV1;
1105
+ userTools: ToolSet;
1106
+ }): void {
1107
+ if (
1108
+ Object.prototype.hasOwnProperty.call(
1109
+ input.harness.builtinTools,
1110
+ 'askUserQuestions',
1111
+ ) &&
1112
+ Object.prototype.hasOwnProperty.call(input.userTools, 'askUserQuestions')
1113
+ ) {
1114
+ throw new Error(
1115
+ "HarnessAgent tool name 'askUserQuestions' is reserved for harness question requests.",
1116
+ );
1117
+ }
1118
+ }
1119
+
1031
1120
  /*
1032
1121
  * `GenerateTextResult` view over a drained `streamText` run. Non-deprecated
1033
1122
  * members derive from `steps` (the single source of truth), and the deprecated