@ai-sdk/harness 1.0.92 → 1.0.93

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,7 @@ export class HarnessAgentSession {
181
195
  OUTPUT extends Output,
182
196
  >(options: {
183
197
  prompt: HarnessAgentPrompt;
198
+ skills: ReadonlyArray<HarnessV1Skill>;
184
199
  instructions: string | undefined;
185
200
  tools: TOOLS;
186
201
  activeTools: ToolSet;
@@ -195,6 +210,19 @@ export class HarnessAgentSession {
195
210
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
196
211
  const session = this.requireReusableSession();
197
212
  this.requirePromptableTurn();
213
+ this.persistedTurnSettings = {
214
+ skills: options.skills,
215
+ ...(options.instructions == null
216
+ ? {}
217
+ : { instructions: options.instructions }),
218
+ tools: options.toolSpecs,
219
+ };
220
+ this.activeTurnSettings = {
221
+ persisted: this.persistedTurnSettings,
222
+ tools: options.tools,
223
+ activeTools: options.activeTools,
224
+ builtinToolFiltering: options.builtinToolFiltering,
225
+ };
198
226
  const sandboxSession = this.getSandboxSession();
199
227
  const turnId = this.startTrackedTurn();
200
228
  try {
@@ -202,6 +230,7 @@ export class HarnessAgentSession {
202
230
  harness: this.harness,
203
231
  session,
204
232
  prompt: options.prompt,
233
+ skills: options.skills,
205
234
  instructions: options.instructions,
206
235
  tools: options.tools,
207
236
  activeTools: options.activeTools,
@@ -246,7 +275,10 @@ export class HarnessAgentSession {
246
275
  onStopConditionMet: () =>
247
276
  this.captureStopConditionBoundary({ session, turnId }),
248
277
  });
249
- return turn;
278
+ return {
279
+ ...turn,
280
+ ready: this.waitForPromptControl({ turnId }),
281
+ };
250
282
  } catch (error) {
251
283
  this.finishTrackedTurn({ turnId });
252
284
  throw error;
@@ -258,6 +290,7 @@ export class HarnessAgentSession {
258
290
  RUNTIME_CONTEXT extends Context,
259
291
  OUTPUT extends Output,
260
292
  >(options: {
293
+ skills: ReadonlyArray<HarnessV1Skill>;
261
294
  instructions: string | undefined;
262
295
  tools: TOOLS;
263
296
  activeTools: ToolSet;
@@ -278,6 +311,14 @@ export class HarnessAgentSession {
278
311
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT> {
279
312
  const session = this.requireReusableSession();
280
313
  this.requireContinuableTurn();
314
+ const turnSettings = this.resolveActiveTurnSettings({
315
+ skills: options.skills,
316
+ instructions: options.instructions,
317
+ tools: options.tools,
318
+ activeTools: options.activeTools,
319
+ toolSpecs: options.toolSpecs,
320
+ builtinToolFiltering: options.builtinToolFiltering,
321
+ });
281
322
  const sandboxSession = this.getSandboxSession();
282
323
  const turnId = this.startTrackedTurn();
283
324
  try {
@@ -285,11 +326,12 @@ export class HarnessAgentSession {
285
326
  harness: this.harness,
286
327
  session,
287
328
  mode: 'continue',
288
- instructions: options.instructions,
289
- tools: options.tools,
290
- activeTools: options.activeTools,
291
- toolSpecs: options.toolSpecs,
292
- builtinToolFiltering: options.builtinToolFiltering,
329
+ skills: turnSettings.persisted.skills,
330
+ instructions: turnSettings.persisted.instructions,
331
+ tools: turnSettings.tools as TOOLS,
332
+ activeTools: turnSettings.activeTools,
333
+ toolSpecs: [...turnSettings.persisted.tools],
334
+ builtinToolFiltering: turnSettings.builtinToolFiltering,
293
335
  sandboxSession: getRestrictedSandboxSession(sandboxSession),
294
336
  sessionWorkDir: this.sessionWorkDir,
295
337
  runtimeContext: options.runtimeContext,
@@ -331,7 +373,10 @@ export class HarnessAgentSession {
331
373
  onStopConditionMet: () =>
332
374
  this.captureStopConditionBoundary({ session, turnId }),
333
375
  });
334
- return turn;
376
+ return {
377
+ ...turn,
378
+ ready: this.waitForPromptControl({ turnId }),
379
+ };
335
380
  } catch (error) {
336
381
  this.finishTrackedTurn({ turnId });
337
382
  throw error;
@@ -521,6 +566,7 @@ export class HarnessAgentSession {
521
566
  private addPendingToolState(
522
567
  state: HarnessAgentContinueTurnState,
523
568
  ): HarnessAgentContinueTurnState {
569
+ const turnSettings = this.persistedTurnSettings;
524
570
  const pendingToolApprovals = this.getPendingToolApprovals();
525
571
  const pendingToolResults = this.getPendingToolResults();
526
572
  if (pendingToolApprovals.length === 0 && pendingToolResults.length === 0) {
@@ -529,10 +575,12 @@ export class HarnessAgentSession {
529
575
  harnessId: state.harnessId,
530
576
  specificationVersion: state.specificationVersion,
531
577
  data: state.data,
578
+ ...(turnSettings == null ? {} : { turnSettings }),
532
579
  };
533
580
  }
534
581
  return {
535
582
  ...state,
583
+ ...(turnSettings == null ? {} : { turnSettings }),
536
584
  ...(pendingToolApprovals.length > 0 ? { pendingToolApprovals } : {}),
537
585
  ...(pendingToolResults.length > 0 ? { pendingToolResults } : {}),
538
586
  };
@@ -663,6 +711,14 @@ export class HarnessAgentSession {
663
711
  this.settleActivePromptControl(options.control);
664
712
  }
665
713
 
714
+ private async waitForPromptControl(options: {
715
+ turnId: number;
716
+ }): Promise<void> {
717
+ const activePromptControl = this.activePromptControl;
718
+ if (activePromptControl?.turnId !== options.turnId) return;
719
+ await activePromptControl.promise;
720
+ }
721
+
666
722
  private settleActivePromptControl(
667
723
  control: HarnessV1PromptControl | undefined,
668
724
  ): void {
@@ -691,9 +747,53 @@ export class HarnessAgentSession {
691
747
  this.pendingToolApprovals.clear();
692
748
  this.pendingToolResults.clear();
693
749
  this.suspendedTurnState = undefined;
750
+ this.activeTurnSettings = undefined;
751
+ this.persistedTurnSettings = undefined;
694
752
  this.turnState = 'idle';
695
753
  }
696
754
 
755
+ private resolveActiveTurnSettings(options: {
756
+ skills: ReadonlyArray<HarnessV1Skill>;
757
+ instructions: string | undefined;
758
+ tools: ToolSet;
759
+ activeTools: ToolSet;
760
+ toolSpecs: HarnessAgentToolSpec[];
761
+ builtinToolFiltering: HarnessV1BuiltinToolFiltering | undefined;
762
+ }): ActiveTurnSettings {
763
+ if (this.activeTurnSettings != null) {
764
+ return this.activeTurnSettings;
765
+ }
766
+ const persisted =
767
+ this.persistedTurnSettings ??
768
+ ({
769
+ skills: options.skills,
770
+ ...(options.instructions == null
771
+ ? {}
772
+ : { instructions: options.instructions }),
773
+ tools: options.toolSpecs,
774
+ } satisfies HarnessV1TurnSettings);
775
+ this.persistedTurnSettings = persisted;
776
+
777
+ const activeTools: ToolSet = {};
778
+ for (const toolSpec of persisted.tools) {
779
+ const tool = options.activeTools[toolSpec.name];
780
+ if (tool == null) {
781
+ throw new Error(
782
+ `HarnessAgent cannot continue turn because tool '${toolSpec.name}' is no longer available.`,
783
+ );
784
+ }
785
+ activeTools[toolSpec.name] = tool;
786
+ }
787
+
788
+ this.activeTurnSettings = {
789
+ persisted,
790
+ tools: options.tools,
791
+ activeTools,
792
+ builtinToolFiltering: options.builtinToolFiltering,
793
+ };
794
+ return this.activeTurnSettings;
795
+ }
796
+
697
797
  private endLocalHandle(options: {
698
798
  sessionState: Exclude<HarnessAgentSessionState, 'active'>;
699
799
  }): 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 skills, instructions, and
84
+ * 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,12 @@ export type HarnessAgentSettings<
115
121
  */
116
122
  readonly id?: string;
117
123
 
124
+ /**
125
+ * Model identifier passed to the harness adapter when a session starts.
126
+ * Supported values are defined by the selected harness.
127
+ */
128
+ readonly model?: string;
129
+
118
130
  /**
119
131
  * Tools available to the underlying runtime in addition to the harness's
120
132
  * own builtins. The agent forwards each tool to the harness as a
@@ -127,19 +139,86 @@ export type HarnessAgentSettings<
127
139
  readonly tools?: TUserTools;
128
140
 
129
141
  /**
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, …).
142
+ * Skills made available to the underlying runtime. Each adapter decides how
143
+ * to surface skills. `prepareCall` can replace them between completed turns.
133
144
  */
134
145
  readonly skills?: ReadonlyArray<HarnessAgentSkill>;
135
146
 
136
147
  /**
137
- * Instructions for the underlying agent runtime. Adapters append this to a
148
+ * Instructions for the underlying agent runtime. Adapters append these to a
138
149
  * native system or developer prompt when supported. Otherwise, they prepend
139
- * it to the first user message of a fresh session.
150
+ * them to the user message. `prepareCall` can replace them between completed
151
+ * turns.
140
152
  */
141
153
  readonly instructions?: string;
142
154
 
155
+ /**
156
+ * Schema for validating the custom options passed to each agent call.
157
+ */
158
+ readonly callOptionsSchema?: FlexibleSchema<CALL_OPTIONS>;
159
+
160
+ /**
161
+ * Prepares the prompt and the settings that may vary between completed
162
+ * turns. The prepared values are frozen for the lifetime of the turn,
163
+ * including any suspended-turn continuations.
164
+ *
165
+ * Preserve the remaining arguments with the rest-spread pattern when a
166
+ * field should be removable by returning `undefined`:
167
+ *
168
+ * ```ts
169
+ * prepareCall: ({ options, ...rest }) => ({
170
+ * ...rest,
171
+ * instructions: options.instructions,
172
+ * })
173
+ * ```
174
+ */
175
+ readonly prepareCall?: (
176
+ options: Omit<
177
+ AgentCallParameters<
178
+ CALL_OPTIONS,
179
+ HarnessAllTools<THarness, TUserTools>,
180
+ RUNTIME_CONTEXT
181
+ >,
182
+ | 'abortSignal'
183
+ | 'timeout'
184
+ | 'onStart'
185
+ | 'experimental_onStart'
186
+ | 'onStepStart'
187
+ | 'experimental_onStepStart'
188
+ | 'onToolExecutionStart'
189
+ | 'experimental_onToolCallStart'
190
+ | 'onToolExecutionEnd'
191
+ | 'experimental_onToolCallFinish'
192
+ | 'onStepEnd'
193
+ | 'onStepFinish'
194
+ | 'onEnd'
195
+ | 'onFinish'
196
+ | 'experimental_sandbox'
197
+ > &
198
+ Pick<
199
+ HarnessAgentSettings<
200
+ THarness,
201
+ TUserTools,
202
+ RUNTIME_CONTEXT,
203
+ NoInfer<OUTPUT>,
204
+ CALL_OPTIONS
205
+ >,
206
+ 'skills' | 'instructions' | 'tools'
207
+ >,
208
+ ) => MaybePromiseLike<
209
+ Pick<
210
+ HarnessAgentSettings<
211
+ THarness,
212
+ TUserTools,
213
+ RUNTIME_CONTEXT,
214
+ NoInfer<OUTPUT>,
215
+ CALL_OPTIONS
216
+ >,
217
+ 'skills' | 'instructions' | 'tools'
218
+ > &
219
+ Omit<Prompt, 'system' | 'instructions' | 'allowSystemInMessages'>
220
+ >;
221
+
143
222
  /**
144
223
  * Optional specification for generating typed output. The same output
145
224
  * requirement is active for every turn run by this agent.