@ai-sdk/harness 1.0.92 → 1.0.94

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.
@@ -15,6 +15,8 @@ import type {
15
15
  HarnessV1NetworkSandboxSession,
16
16
  HarnessV1PromptControl,
17
17
  HarnessV1ResponseFormat,
18
+ HarnessV1Skill,
19
+ HarnessV1TurnSettings,
18
20
  } from '../v1';
19
21
  import { HarnessCapabilityUnsupportedError } from '../errors/harness-capability-unsupported-error';
20
22
  import type {
@@ -40,6 +42,7 @@ type HarnessAgentTurnResult<
40
42
  > = {
41
43
  result: StreamTextResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
42
44
  done: Promise<void>;
45
+ ready: Promise<void>;
43
46
  };
44
47
 
45
48
  type HarnessAgentSessionState = 'active' | 'detached' | 'stopped' | 'destroyed';
@@ -58,6 +61,13 @@ type ActivePromptControl = {
58
61
  settled: boolean;
59
62
  };
60
63
 
64
+ type ActiveTurnSettings = {
65
+ readonly persisted: HarnessV1TurnSettings;
66
+ readonly tools: ToolSet;
67
+ readonly activeTools: ToolSet;
68
+ readonly builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
69
+ };
70
+
61
71
  /**
62
72
  * Live harness session held by the caller.
63
73
  *
@@ -105,6 +115,8 @@ export class HarnessAgentSession {
105
115
  private suspendedTurnState:
106
116
  | Promise<HarnessAgentContinueTurnState>
107
117
  | undefined;
118
+ private activeTurnSettings: ActiveTurnSettings | undefined;
119
+ private persistedTurnSettings: HarnessV1TurnSettings | undefined;
108
120
 
109
121
  /**
110
122
  * Whether this session was created from `resumeFrom` or `continueFrom`.
@@ -122,6 +134,7 @@ export class HarnessAgentSession {
122
134
  toolApproval: HarnessAgentToolApprovalConfiguration | undefined;
123
135
  pendingToolApprovals?: readonly HarnessAgentPendingToolApproval[];
124
136
  pendingToolResults?: readonly HarnessAgentPendingToolResult[];
137
+ turnSettings?: HarnessV1TurnSettings;
125
138
  turnState?: HarnessAgentTurnState;
126
139
  }) {
127
140
  this.sessionId = options.sessionId;
@@ -137,6 +150,7 @@ export class HarnessAgentSession {
137
150
  for (const pendingResult of options.pendingToolResults ?? []) {
138
151
  this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
139
152
  }
153
+ this.persistedTurnSettings = options.turnSettings;
140
154
  this.turnState =
141
155
  options.turnState ??
142
156
  (this.pendingToolApprovals.size > 0
@@ -181,6 +195,8 @@ export class HarnessAgentSession {
181
195
  OUTPUT extends Output,
182
196
  >(options: {
183
197
  prompt: HarnessAgentPrompt;
198
+ model: string | undefined;
199
+ skills: ReadonlyArray<HarnessV1Skill>;
184
200
  instructions: string | undefined;
185
201
  tools: TOOLS;
186
202
  activeTools: ToolSet;
@@ -195,6 +211,20 @@ export class HarnessAgentSession {
195
211
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
196
212
  const session = this.requireReusableSession();
197
213
  this.requirePromptableTurn();
214
+ this.persistedTurnSettings = {
215
+ ...(options.model == null ? {} : { model: options.model }),
216
+ skills: options.skills,
217
+ ...(options.instructions == null
218
+ ? {}
219
+ : { instructions: options.instructions }),
220
+ tools: options.toolSpecs,
221
+ };
222
+ this.activeTurnSettings = {
223
+ persisted: this.persistedTurnSettings,
224
+ tools: options.tools,
225
+ activeTools: options.activeTools,
226
+ builtinToolFiltering: options.builtinToolFiltering,
227
+ };
198
228
  const sandboxSession = this.getSandboxSession();
199
229
  const turnId = this.startTrackedTurn();
200
230
  try {
@@ -202,6 +232,8 @@ export class HarnessAgentSession {
202
232
  harness: this.harness,
203
233
  session,
204
234
  prompt: options.prompt,
235
+ model: options.model,
236
+ skills: options.skills,
205
237
  instructions: options.instructions,
206
238
  tools: options.tools,
207
239
  activeTools: options.activeTools,
@@ -246,7 +278,10 @@ export class HarnessAgentSession {
246
278
  onStopConditionMet: () =>
247
279
  this.captureStopConditionBoundary({ session, turnId }),
248
280
  });
249
- return turn;
281
+ return {
282
+ ...turn,
283
+ ready: this.waitForPromptControl({ turnId }),
284
+ };
250
285
  } catch (error) {
251
286
  this.finishTrackedTurn({ turnId });
252
287
  throw error;
@@ -258,6 +293,8 @@ export class HarnessAgentSession {
258
293
  RUNTIME_CONTEXT extends Context,
259
294
  OUTPUT extends Output,
260
295
  >(options: {
296
+ model: string | undefined;
297
+ skills: ReadonlyArray<HarnessV1Skill>;
261
298
  instructions: string | undefined;
262
299
  tools: TOOLS;
263
300
  activeTools: ToolSet;
@@ -278,6 +315,15 @@ export class HarnessAgentSession {
278
315
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
279
316
  const session = this.requireReusableSession();
280
317
  this.requireContinuableTurn();
318
+ const turnSettings = this.resolveActiveTurnSettings({
319
+ model: options.model,
320
+ skills: options.skills,
321
+ instructions: options.instructions,
322
+ tools: options.tools,
323
+ activeTools: options.activeTools,
324
+ toolSpecs: options.toolSpecs,
325
+ builtinToolFiltering: options.builtinToolFiltering,
326
+ });
281
327
  const sandboxSession = this.getSandboxSession();
282
328
  const turnId = this.startTrackedTurn();
283
329
  try {
@@ -285,11 +331,13 @@ export class HarnessAgentSession {
285
331
  harness: this.harness,
286
332
  session,
287
333
  mode: 'continue',
288
- instructions: options.instructions,
289
- tools: options.tools,
290
- activeTools: options.activeTools,
291
- toolSpecs: options.toolSpecs,
292
- builtinToolFiltering: options.builtinToolFiltering,
334
+ model: turnSettings.persisted.model,
335
+ skills: turnSettings.persisted.skills,
336
+ instructions: turnSettings.persisted.instructions,
337
+ tools: turnSettings.tools as TOOLS,
338
+ activeTools: turnSettings.activeTools,
339
+ toolSpecs: [...turnSettings.persisted.tools],
340
+ builtinToolFiltering: turnSettings.builtinToolFiltering,
293
341
  sandboxSession: getRestrictedSandboxSession(sandboxSession),
294
342
  sessionWorkDir: this.sessionWorkDir,
295
343
  runtimeContext: options.runtimeContext,
@@ -331,7 +379,10 @@ export class HarnessAgentSession {
331
379
  onStopConditionMet: () =>
332
380
  this.captureStopConditionBoundary({ session, turnId }),
333
381
  });
334
- return turn;
382
+ return {
383
+ ...turn,
384
+ ready: this.waitForPromptControl({ turnId }),
385
+ };
335
386
  } catch (error) {
336
387
  this.finishTrackedTurn({ turnId });
337
388
  throw error;
@@ -521,6 +572,7 @@ export class HarnessAgentSession {
521
572
  private addPendingToolState(
522
573
  state: HarnessAgentContinueTurnState,
523
574
  ): HarnessAgentContinueTurnState {
575
+ const turnSettings = this.persistedTurnSettings;
524
576
  const pendingToolApprovals = this.getPendingToolApprovals();
525
577
  const pendingToolResults = this.getPendingToolResults();
526
578
  if (pendingToolApprovals.length === 0 && pendingToolResults.length === 0) {
@@ -529,10 +581,12 @@ export class HarnessAgentSession {
529
581
  harnessId: state.harnessId,
530
582
  specificationVersion: state.specificationVersion,
531
583
  data: state.data,
584
+ ...(turnSettings == null ? {} : { turnSettings }),
532
585
  };
533
586
  }
534
587
  return {
535
588
  ...state,
589
+ ...(turnSettings == null ? {} : { turnSettings }),
536
590
  ...(pendingToolApprovals.length > 0 ? { pendingToolApprovals } : {}),
537
591
  ...(pendingToolResults.length > 0 ? { pendingToolResults } : {}),
538
592
  };
@@ -663,6 +717,14 @@ export class HarnessAgentSession {
663
717
  this.settleActivePromptControl(options.control);
664
718
  }
665
719
 
720
+ private async waitForPromptControl(options: {
721
+ turnId: number;
722
+ }): Promise<void> {
723
+ const activePromptControl = this.activePromptControl;
724
+ if (activePromptControl?.turnId !== options.turnId) return;
725
+ await activePromptControl.promise;
726
+ }
727
+
666
728
  private settleActivePromptControl(
667
729
  control: HarnessV1PromptControl | undefined,
668
730
  ): void {
@@ -691,9 +753,55 @@ export class HarnessAgentSession {
691
753
  this.pendingToolApprovals.clear();
692
754
  this.pendingToolResults.clear();
693
755
  this.suspendedTurnState = undefined;
756
+ this.activeTurnSettings = undefined;
757
+ this.persistedTurnSettings = undefined;
694
758
  this.turnState = 'idle';
695
759
  }
696
760
 
761
+ private resolveActiveTurnSettings(options: {
762
+ model: string | undefined;
763
+ skills: ReadonlyArray<HarnessV1Skill>;
764
+ instructions: string | undefined;
765
+ tools: ToolSet;
766
+ activeTools: ToolSet;
767
+ toolSpecs: HarnessAgentToolSpec[];
768
+ builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
769
+ }): ActiveTurnSettings {
770
+ if (this.activeTurnSettings != null) {
771
+ return this.activeTurnSettings;
772
+ }
773
+ const persisted =
774
+ this.persistedTurnSettings ??
775
+ ({
776
+ ...(options.model == null ? {} : { model: options.model }),
777
+ skills: options.skills,
778
+ ...(options.instructions == null
779
+ ? {}
780
+ : { instructions: options.instructions }),
781
+ tools: options.toolSpecs,
782
+ } satisfies HarnessV1TurnSettings);
783
+ this.persistedTurnSettings = persisted;
784
+
785
+ const activeTools: ToolSet = {};
786
+ for (const toolSpec of persisted.tools) {
787
+ const tool = options.activeTools[toolSpec.name];
788
+ if (tool == null) {
789
+ throw new Error(
790
+ `HarnessAgent cannot continue turn because tool '${toolSpec.name}' is no longer available.`,
791
+ );
792
+ }
793
+ activeTools[toolSpec.name] = tool;
794
+ }
795
+
796
+ this.activeTurnSettings = {
797
+ persisted,
798
+ tools: options.tools,
799
+ activeTools,
800
+ builtinToolFiltering: options.builtinToolFiltering,
801
+ };
802
+ return this.activeTurnSettings;
803
+ }
804
+
697
805
  private endLocalHandle(options: {
698
806
  sessionState: Exclude<HarnessAgentSessionState, 'active'>;
699
807
  }): void {
@@ -12,11 +12,15 @@ import type {
12
12
  Arrayable,
13
13
  Context,
14
14
  Experimental_SandboxSession as SandboxSession,
15
+ FlexibleSchema,
16
+ MaybePromiseLike,
15
17
  ToolSet,
16
18
  } from '@ai-sdk/provider-utils';
17
19
  import type {
18
20
  ActiveTools,
21
+ AgentCallParameters,
19
22
  OutputInterface as Output,
23
+ Prompt,
20
24
  StopCondition,
21
25
  TelemetryOptions,
22
26
  ToolApprovalStatus,
@@ -74,9 +78,10 @@ type HarnessTools<TOOLS extends ToolSet> = ActiveTools<NoInfer<TOOLS>>;
74
78
  /**
75
79
  * Construction-time settings for a `HarnessAgent`.
76
80
  *
77
- * Per-call settings (prompt, abortSignal, callbacks) belong on the
81
+ * Prompt, abortSignal, callbacks, and custom call options belong on the
78
82
  * `AgentCallParameters` / `AgentStreamParameters` passed to `generate` /
79
- * `stream` and are not duplicated here.
83
+ * `stream`. `prepareCall` can derive turn-scoped model, skills, instructions,
84
+ * and tools from those custom call options.
80
85
  */
81
86
  type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> =
82
87
  | {
@@ -101,6 +106,7 @@ export type HarnessAgentSettings<
101
106
  TUserTools extends ToolSet = {},
102
107
  RUNTIME_CONTEXT extends Context = Context,
103
108
  OUTPUT extends Output = never,
109
+ CALL_OPTIONS = never,
104
110
  > = {
105
111
  /**
106
112
  * The harness adapter driving the underlying agent runtime. Its
@@ -115,6 +121,13 @@ export type HarnessAgentSettings<
115
121
  */
116
122
  readonly id?: string;
117
123
 
124
+ /**
125
+ * Model identifier used by the harness adapter. Supported values are
126
+ * defined by the selected harness. `prepareCall` can replace it between
127
+ * completed turns.
128
+ */
129
+ readonly model?: string;
130
+
118
131
  /**
119
132
  * Tools available to the underlying runtime in addition to the harness's
120
133
  * own builtins. The agent forwards each tool to the harness as a
@@ -127,19 +140,86 @@ export type HarnessAgentSettings<
127
140
  readonly tools?: TUserTools;
128
141
 
129
142
  /**
130
- * Skills made available to the underlying runtime for the lifetime of
131
- * the session. Each adapter decides how to surface skills (file in the
132
- * working tree, prompt prefix, …).
143
+ * Skills made available to the underlying runtime. Each adapter decides how
144
+ * to surface skills. `prepareCall` can replace them between completed turns.
133
145
  */
134
146
  readonly skills?: ReadonlyArray<HarnessAgentSkill>;
135
147
 
136
148
  /**
137
- * Instructions for the underlying agent runtime. Adapters append this to a
149
+ * Instructions for the underlying agent runtime. Adapters append these to a
138
150
  * native system or developer prompt when supported. Otherwise, they prepend
139
- * it to the first user message of a fresh session.
151
+ * them to the user message. `prepareCall` can replace them between completed
152
+ * turns.
140
153
  */
141
154
  readonly instructions?: string;
142
155
 
156
+ /**
157
+ * Schema for validating the custom options passed to each agent call.
158
+ */
159
+ readonly callOptionsSchema?: FlexibleSchema<CALL_OPTIONS>;
160
+
161
+ /**
162
+ * Prepares the prompt and the settings that may vary between completed
163
+ * turns. The prepared values are frozen for the lifetime of the turn,
164
+ * including any suspended-turn continuations.
165
+ *
166
+ * Preserve the remaining arguments with the rest-spread pattern when a
167
+ * field should be removable by returning `undefined`:
168
+ *
169
+ * ```ts
170
+ * prepareCall: ({ options, ...rest }) => ({
171
+ * ...rest,
172
+ * instructions: options.instructions,
173
+ * })
174
+ * ```
175
+ */
176
+ readonly prepareCall?: (
177
+ options: Omit<
178
+ AgentCallParameters<
179
+ CALL_OPTIONS,
180
+ HarnessAllTools<THarness, TUserTools>,
181
+ RUNTIME_CONTEXT
182
+ >,
183
+ | 'abortSignal'
184
+ | 'timeout'
185
+ | 'onStart'
186
+ | 'experimental_onStart'
187
+ | 'onStepStart'
188
+ | 'experimental_onStepStart'
189
+ | 'onToolExecutionStart'
190
+ | 'experimental_onToolCallStart'
191
+ | 'onToolExecutionEnd'
192
+ | 'experimental_onToolCallFinish'
193
+ | 'onStepEnd'
194
+ | 'onStepFinish'
195
+ | 'onEnd'
196
+ | 'onFinish'
197
+ | 'experimental_sandbox'
198
+ > &
199
+ Pick<
200
+ HarnessAgentSettings<
201
+ THarness,
202
+ TUserTools,
203
+ RUNTIME_CONTEXT,
204
+ NoInfer<OUTPUT>,
205
+ CALL_OPTIONS
206
+ >,
207
+ 'model' | 'skills' | 'instructions' | 'tools'
208
+ >,
209
+ ) => MaybePromiseLike<
210
+ Pick<
211
+ HarnessAgentSettings<
212
+ THarness,
213
+ TUserTools,
214
+ RUNTIME_CONTEXT,
215
+ NoInfer<OUTPUT>,
216
+ CALL_OPTIONS
217
+ >,
218
+ 'model' | 'skills' | 'instructions' | 'tools'
219
+ > &
220
+ Omit<Prompt, 'system' | 'instructions' | 'allowSystemInMessages'>
221
+ >;
222
+
143
223
  /**
144
224
  * Optional specification for generating typed output. The same output
145
225
  * requirement is active for every turn run by this agent.