@ai-sdk/harness 1.0.93 → 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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.94
4
+
5
+ ### Patch Changes
6
+
7
+ - 8961fde: feat(harness): allow changing `model` between turns via call options
8
+ - eb59f2a: fix(harness): ensure harness adapters can stream tool input deltas before the complete tool call arrives
9
+ - Updated dependencies [55a9981]
10
+ - Updated dependencies [dd32de2]
11
+ - Updated dependencies [aa45741]
12
+ - Updated dependencies [cc29073]
13
+ - ai@7.0.85
14
+ - @ai-sdk/provider@4.0.9
15
+ - @ai-sdk/provider-utils@5.0.34
16
+
3
17
  ## 1.0.93
4
18
 
5
19
  ### Patch Changes
@@ -2,7 +2,7 @@ import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
2
2
  import { Experimental_SandboxSession, UserModelMessage, ToolSet, FlexibleSchema, Tool, Context, MaybePromiseLike, Arrayable, ToolApprovalResponse, ModelMessage } from '@ai-sdk/provider-utils';
3
3
  import { OutputInterface, AgentCallParameters, Prompt, StopCondition, ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
4
4
  import { z } from 'zod/v4';
5
- import { JSONSchema7, JSONValue, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, AISDKError } from '@ai-sdk/provider';
5
+ import { JSONSchema7, JSONValue, LanguageModelV4StreamPart, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, AISDKError } from '@ai-sdk/provider';
6
6
 
7
7
  /**
8
8
  * One file to write into the sandbox as part of an adapter's bootstrap recipe.
@@ -472,6 +472,12 @@ type HarnessV1PendingToolResult = {
472
472
  * so a resumed continuation cannot pick up configuration from a later turn.
473
473
  */
474
474
  type HarnessV1TurnSettings = {
475
+ /**
476
+ * Model identifier selected for this turn. Adapters interpret this value
477
+ * according to the underlying harness runtime. Rerun-based continuations
478
+ * reuse it when reconstructing the turn.
479
+ */
480
+ readonly model?: string;
475
481
  /**
476
482
  * Skills made available to the underlying runtime for this turn. Adapters
477
483
  * must replace skills from the preceding completed turn before starting a
@@ -630,7 +636,13 @@ type HarnessV1StreamPart = {
630
636
  type: 'reasoning-end';
631
637
  id: string;
632
638
  harnessMetadata?: HarnessV1Metadata;
633
- } | (LanguageModelV4ToolCall & {
639
+ } | Extract<LanguageModelV4StreamPart, {
640
+ type: 'tool-input-start';
641
+ }> | Extract<LanguageModelV4StreamPart, {
642
+ type: 'tool-input-delta';
643
+ }> | Extract<LanguageModelV4StreamPart, {
644
+ type: 'tool-input-end';
645
+ }> | (LanguageModelV4ToolCall & {
634
646
  nativeName?: string;
635
647
  /**
636
648
  * Total tool calls in the current model step, when known before tool
@@ -683,11 +695,6 @@ type HarnessV1BuiltinToolFiltering = {
683
695
  * calling the adapter, so adapters never need to derive provider-specific paths.
684
696
  */
685
697
  type HarnessV1StartOptions = {
686
- /**
687
- * Model identifier selected by the consumer. Adapters interpret this value
688
- * according to the underlying harness runtime.
689
- */
690
- readonly model?: string;
691
698
  /**
692
699
  * Stable identifier for this harness session. Used as the underlying
693
700
  * resource name where the adapter has a notion of a named session
@@ -815,13 +822,6 @@ type HarnessV1Session = {
815
822
  * sessions report `false`; resumed sessions report `true`.
816
823
  */
817
824
  readonly isResume: boolean;
818
- /**
819
- * The model id the underlying runtime is configured to use, if the adapter
820
- * knows it (e.g. from its settings). Surfaced into telemetry as
821
- * `gen_ai.request.model` and the trace span labels. Omitted when the adapter
822
- * defers to the runtime's own default and has no concrete id.
823
- */
824
- readonly modelId?: string;
825
825
  /**
826
826
  * Run one prompt turn. Returns a control handle the host uses to feed
827
827
  * tool results, approvals, and user messages back into the turn while it
@@ -1250,8 +1250,8 @@ type HarnessTools<TOOLS extends ToolSet> = ActiveTools<NoInfer<TOOLS>>;
1250
1250
  *
1251
1251
  * Prompt, abortSignal, callbacks, and custom call options belong on the
1252
1252
  * `AgentCallParameters` / `AgentStreamParameters` passed to `generate` /
1253
- * `stream`. `prepareCall` can derive turn-scoped skills, instructions, and
1254
- * tools from those custom call options.
1253
+ * `stream`. `prepareCall` can derive turn-scoped model, skills, instructions,
1254
+ * and tools from those custom call options.
1255
1255
  */
1256
1256
  type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> = {
1257
1257
  /**
@@ -1281,8 +1281,9 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1281
1281
  */
1282
1282
  readonly id?: string;
1283
1283
  /**
1284
- * Model identifier passed to the harness adapter when a session starts.
1285
- * Supported values are defined by the selected harness.
1284
+ * Model identifier used by the harness adapter. Supported values are
1285
+ * defined by the selected harness. `prepareCall` can replace it between
1286
+ * completed turns.
1286
1287
  */
1287
1288
  readonly model?: string;
1288
1289
  /**
@@ -1326,7 +1327,7 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1326
1327
  * })
1327
1328
  * ```
1328
1329
  */
1329
- readonly prepareCall?: (options: Omit<AgentCallParameters<CALL_OPTIONS, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT>, 'abortSignal' | 'timeout' | 'onStart' | 'experimental_onStart' | 'onStepStart' | 'experimental_onStepStart' | 'onToolExecutionStart' | 'experimental_onToolCallStart' | 'onToolExecutionEnd' | 'experimental_onToolCallFinish' | 'onStepEnd' | 'onStepFinish' | 'onEnd' | 'onFinish' | 'experimental_sandbox'> & Pick<HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, NoInfer<OUTPUT>, CALL_OPTIONS>, 'skills' | 'instructions' | 'tools'>) => MaybePromiseLike<Pick<HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, NoInfer<OUTPUT>, CALL_OPTIONS>, 'skills' | 'instructions' | 'tools'> & Omit<Prompt, 'system' | 'instructions' | 'allowSystemInMessages'>>;
1330
+ readonly prepareCall?: (options: Omit<AgentCallParameters<CALL_OPTIONS, HarnessAllTools<THarness, TUserTools>, RUNTIME_CONTEXT>, 'abortSignal' | 'timeout' | 'onStart' | 'experimental_onStart' | 'onStepStart' | 'experimental_onStepStart' | 'onToolExecutionStart' | 'experimental_onToolCallStart' | 'onToolExecutionEnd' | 'experimental_onToolCallFinish' | 'onStepEnd' | 'onStepFinish' | 'onEnd' | 'onFinish' | 'experimental_sandbox'> & Pick<HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, NoInfer<OUTPUT>, CALL_OPTIONS>, 'model' | 'skills' | 'instructions' | 'tools'>) => MaybePromiseLike<Pick<HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, NoInfer<OUTPUT>, CALL_OPTIONS>, 'model' | 'skills' | 'instructions' | 'tools'> & Omit<Prompt, 'system' | 'instructions' | 'allowSystemInMessages'>>;
1330
1331
  /**
1331
1332
  * Optional specification for generating typed output. The same output
1332
1333
  * requirement is active for every turn run by this agent.
@@ -1490,6 +1491,7 @@ declare class HarnessAgentSession {
1490
1491
  hasUnfinishedTurn(): boolean;
1491
1492
  promptTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context, OUTPUT extends OutputInterface>(options: {
1492
1493
  prompt: HarnessAgentPrompt;
1494
+ model: string | undefined;
1493
1495
  skills: ReadonlyArray<HarnessV1Skill>;
1494
1496
  instructions: string | undefined;
1495
1497
  tools: TOOLS;
@@ -1504,6 +1506,7 @@ declare class HarnessAgentSession {
1504
1506
  stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
1505
1507
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT, OUTPUT>;
1506
1508
  continueTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context, OUTPUT extends OutputInterface>(options: {
1509
+ model: string | undefined;
1507
1510
  skills: ReadonlyArray<HarnessV1Skill>;
1508
1511
  instructions: string | undefined;
1509
1512
  tools: TOOLS;
@@ -889,6 +889,10 @@ function translateStreamPart(event, options = {}) {
889
889
  providerMetadata: event.harnessMetadata
890
890
  }
891
891
  ];
892
+ case "tool-input-start":
893
+ case "tool-input-delta":
894
+ case "tool-input-end":
895
+ return [event];
892
896
  case "tool-call":
893
897
  return [];
894
898
  case "tool-approval-request":
@@ -988,9 +992,45 @@ function translateStreamPart(event, options = {}) {
988
992
  }
989
993
 
990
994
  // src/agent/internal/strip-work-dir.ts
995
+ function createToolInputWorkDirStripper({
996
+ sessionWorkDir
997
+ }) {
998
+ const pendingByToolCallId = /* @__PURE__ */ new Map();
999
+ return (part) => {
1000
+ var _a4;
1001
+ if (sessionWorkDir.length === 0) return [part];
1002
+ if (part.type === "tool-input-start") {
1003
+ pendingByToolCallId.set(part.id, "");
1004
+ return [part];
1005
+ }
1006
+ if (part.type === "tool-input-delta") {
1007
+ const stripped2 = stripStreamingString({
1008
+ value: ((_a4 = pendingByToolCallId.get(part.id)) != null ? _a4 : "") + part.delta,
1009
+ workDir: sessionWorkDir,
1010
+ final: false
1011
+ });
1012
+ pendingByToolCallId.set(part.id, stripped2.pending);
1013
+ return stripped2.output.length === 0 ? [] : [{ ...part, delta: stripped2.output }];
1014
+ }
1015
+ const pending = pendingByToolCallId.get(part.id);
1016
+ pendingByToolCallId.delete(part.id);
1017
+ if (pending == null || pending.length === 0) return [part];
1018
+ const stripped = stripStreamingString({
1019
+ value: pending,
1020
+ workDir: sessionWorkDir,
1021
+ final: true
1022
+ });
1023
+ return stripped.output.length === 0 ? [part] : [
1024
+ { type: "tool-input-delta", id: part.id, delta: stripped.output },
1025
+ part
1026
+ ];
1027
+ };
1028
+ }
991
1029
  function stripWorkDir(part, sessionWorkDir) {
992
1030
  if (sessionWorkDir.length === 0) return part;
993
1031
  switch (part.type) {
1032
+ case "tool-input-delta":
1033
+ return { ...part, delta: stripString(part.delta, sessionWorkDir) };
994
1034
  case "tool-call":
995
1035
  return { ...part, input: stripString(part.input, sessionWorkDir) };
996
1036
  case "tool-result":
@@ -1007,6 +1047,42 @@ function stripWorkDir(part, sessionWorkDir) {
1007
1047
  function stripString(value, workDir) {
1008
1048
  return value.split(`${workDir}/`).join("").split(workDir).join(".");
1009
1049
  }
1050
+ function stripStreamingString({
1051
+ value,
1052
+ workDir,
1053
+ final
1054
+ }) {
1055
+ let remaining = value;
1056
+ let output = "";
1057
+ while (remaining.length > 0) {
1058
+ const matchIndex = remaining.indexOf(workDir);
1059
+ if (matchIndex >= 0) {
1060
+ output += remaining.slice(0, matchIndex);
1061
+ const followingIndex = matchIndex + workDir.length;
1062
+ if (followingIndex === remaining.length && !final) {
1063
+ return { output, pending: remaining.slice(matchIndex) };
1064
+ }
1065
+ if (remaining[followingIndex] === "/") {
1066
+ remaining = remaining.slice(followingIndex + 1);
1067
+ } else {
1068
+ output += ".";
1069
+ remaining = remaining.slice(followingIndex);
1070
+ }
1071
+ continue;
1072
+ }
1073
+ if (final) return { output: output + remaining, pending: "" };
1074
+ let pendingLength = Math.min(remaining.length, workDir.length - 1);
1075
+ while (pendingLength > 0 && !workDir.startsWith(remaining.slice(-pendingLength))) {
1076
+ pendingLength -= 1;
1077
+ }
1078
+ const outputLength = remaining.length - pendingLength;
1079
+ return {
1080
+ output: output + remaining.slice(0, outputLength),
1081
+ pending: remaining.slice(outputLength)
1082
+ };
1083
+ }
1084
+ return { output, pending: "" };
1085
+ }
1010
1086
  function stripDeep(value, workDir) {
1011
1087
  if (typeof value === "string") return stripString(value, workDir);
1012
1088
  if (Array.isArray(value)) return value.map((item) => stripDeep(item, workDir));
@@ -1493,7 +1569,7 @@ function runPrompt(input) {
1493
1569
  const telemetry = createTurnTelemetry({
1494
1570
  telemetry: input.telemetry,
1495
1571
  harnessId: input.harness.harnessId,
1496
- modelId: input.session.modelId,
1572
+ modelId: input.model,
1497
1573
  instructions: input.instructions,
1498
1574
  promptText: input.prompt != null ? promptToText(input.prompt) : "",
1499
1575
  runtimeContext: input.runtimeContext
@@ -1520,6 +1596,7 @@ function runPrompt(input) {
1520
1596
  invoke: input.mode === "continue" ? (emit) => {
1521
1597
  var _a6;
1522
1598
  return input.session.doContinueTurn({
1599
+ model: input.model,
1523
1600
  skills: (_a6 = input.skills) != null ? _a6 : [],
1524
1601
  responseFormat: input.responseFormat,
1525
1602
  tools: input.toolSpecs,
@@ -1536,6 +1613,7 @@ function runPrompt(input) {
1536
1613
  }
1537
1614
  return input.session.doPromptTurn({
1538
1615
  prompt: input.prompt,
1616
+ model: input.model,
1539
1617
  skills: (_a6 = input.skills) != null ? _a6 : [],
1540
1618
  responseFormat: input.responseFormat,
1541
1619
  tools: input.toolSpecs,
@@ -1559,6 +1637,9 @@ function runPrompt(input) {
1559
1637
  const { stream, control } = bridge;
1560
1638
  (_a5 = input.onPromptControlAvailable) == null ? void 0 : _a5.call(input, control);
1561
1639
  const reader = stream.getReader();
1640
+ const stripToolInputWorkDir = createToolInputWorkDirStripper({
1641
+ sessionWorkDir: input.sessionWorkDir
1642
+ });
1562
1643
  const toolCallsByToolCallId = /* @__PURE__ */ new Map();
1563
1644
  const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
1564
1645
  const translateOptions = {
@@ -1803,7 +1884,7 @@ function runPrompt(input) {
1803
1884
  toolName: approval.toolName,
1804
1885
  input: approval.input
1805
1886
  };
1806
- await telemetry.start(input.session.modelId);
1887
+ await telemetry.start(input.model);
1807
1888
  await telemetry.toolStart({
1808
1889
  toolCallId: rawToolCall.toolCallId,
1809
1890
  toolName: rawToolCall.toolName,
@@ -1902,11 +1983,25 @@ function runPrompt(input) {
1902
1983
  }
1903
1984
  }
1904
1985
  if (value.type === "stream-start") {
1905
- await telemetry.start((_e2 = value.modelId) != null ? _e2 : input.session.modelId);
1986
+ await telemetry.start((_e2 = value.modelId) != null ? _e2 : input.model);
1906
1987
  }
1907
1988
  if (value.type !== "stream-start" && value.type !== "finish-step" && value.type !== "finish" && value.type !== "error") {
1908
1989
  await telemetry.ensureStepOpen();
1909
1990
  }
1991
+ if (value.type === "tool-input-start" || value.type === "tool-input-delta" || value.type === "tool-input-end") {
1992
+ if (settledHostToolCallIds.has(value.id) || settledBuiltinApprovalToolCallIds.has(value.id)) {
1993
+ continue;
1994
+ }
1995
+ for (const displayValue2 of stripToolInputWorkDir(value)) {
1996
+ for (const part of translateStreamPart(
1997
+ displayValue2,
1998
+ translateOptions
1999
+ )) {
2000
+ result.enqueue(part);
2001
+ }
2002
+ }
2003
+ continue;
2004
+ }
1910
2005
  const displayValue = stripWorkDir(value, input.sessionWorkDir);
1911
2006
  const settledHostInputReplay = (displayValue.type === "tool-call" || displayValue.type === "tool-result" || displayValue.type === "tool-approval-request") && settledHostToolCallIds.has(displayValue.toolCallId);
1912
2007
  const settledBuiltinApprovalReplay = (displayValue.type === "tool-call" || displayValue.type === "tool-approval-request") && settledBuiltinApprovalToolCallIds.has(displayValue.toolCallId);
@@ -2411,6 +2506,7 @@ var HarnessAgentSession = class {
2411
2506
  const session = this.requireReusableSession();
2412
2507
  this.requirePromptableTurn();
2413
2508
  this.persistedTurnSettings = {
2509
+ ...options.model == null ? {} : { model: options.model },
2414
2510
  skills: options.skills,
2415
2511
  ...options.instructions == null ? {} : { instructions: options.instructions },
2416
2512
  tools: options.toolSpecs
@@ -2428,6 +2524,7 @@ var HarnessAgentSession = class {
2428
2524
  harness: this.harness,
2429
2525
  session,
2430
2526
  prompt: options.prompt,
2527
+ model: options.model,
2431
2528
  skills: options.skills,
2432
2529
  instructions: options.instructions,
2433
2530
  tools: options.tools,
@@ -2484,6 +2581,7 @@ var HarnessAgentSession = class {
2484
2581
  const session = this.requireReusableSession();
2485
2582
  this.requireContinuableTurn();
2486
2583
  const turnSettings = this.resolveActiveTurnSettings({
2584
+ model: options.model,
2487
2585
  skills: options.skills,
2488
2586
  instructions: options.instructions,
2489
2587
  tools: options.tools,
@@ -2498,6 +2596,7 @@ var HarnessAgentSession = class {
2498
2596
  harness: this.harness,
2499
2597
  session,
2500
2598
  mode: "continue",
2599
+ model: turnSettings.persisted.model,
2501
2600
  skills: turnSettings.persisted.skills,
2502
2601
  instructions: turnSettings.persisted.instructions,
2503
2602
  tools: turnSettings.tools,
@@ -2863,6 +2962,7 @@ var HarnessAgentSession = class {
2863
2962
  return this.activeTurnSettings;
2864
2963
  }
2865
2964
  const persisted = (_a4 = this.persistedTurnSettings) != null ? _a4 : {
2965
+ ...options.model == null ? {} : { model: options.model },
2866
2966
  skills: options.skills,
2867
2967
  ...options.instructions == null ? {} : { instructions: options.instructions },
2868
2968
  tools: options.toolSpecs
@@ -3663,7 +3763,6 @@ var HarnessAgent = class {
3663
3763
  }
3664
3764
  try {
3665
3765
  const baseStartOptions = {
3666
- model: this.settings.model,
3667
3766
  sessionId,
3668
3767
  resumeFrom: validatedResumeFrom,
3669
3768
  continueFrom: effectiveContinueFrom,
@@ -3805,6 +3904,7 @@ var HarnessAgent = class {
3805
3904
  }
3806
3905
  _buildTurnOptions(input) {
3807
3906
  return {
3907
+ model: input.turnSettings.model,
3808
3908
  skills: input.turnSettings.skills,
3809
3909
  instructions: input.turnSettings.instructions,
3810
3910
  tools: input.turnSettings.tools,
@@ -3894,6 +3994,7 @@ var HarnessAgent = class {
3894
3994
  ...promptOptions
3895
3995
  } = callOptions;
3896
3996
  const baseCallArgs = {
3997
+ model: this.settings.model,
3897
3998
  skills: this.settings.skills,
3898
3999
  instructions: this.settings.instructions,
3899
4000
  tools: this.settings.tools,
@@ -3911,6 +4012,7 @@ var HarnessAgent = class {
3911
4012
  return {
3912
4013
  prompt: this._resolvePromptTurnInput(preparedCallArgs),
3913
4014
  ...this._prepareTurnSettings({
4015
+ model: preparedCallArgs.model,
3914
4016
  skills: preparedCallArgs.skills,
3915
4017
  instructions: preparedCallArgs.instructions,
3916
4018
  tools: preparedCallArgs.tools
@@ -3921,6 +4023,7 @@ var HarnessAgent = class {
3921
4023
  var _a4, _b4;
3922
4024
  return {
3923
4025
  ...this._prepareTurnSettings({
4026
+ model: this.settings.model,
3924
4027
  skills: this.settings.skills,
3925
4028
  instructions: this.settings.instructions,
3926
4029
  tools: this.settings.tools
@@ -3944,6 +4047,7 @@ var HarnessAgent = class {
3944
4047
  inactiveTools: this.settings.inactiveTools
3945
4048
  });
3946
4049
  return {
4050
+ model: options.model,
3947
4051
  skills: (_b4 = options.skills) != null ? _b4 : [],
3948
4052
  instructions: options.instructions,
3949
4053
  tools,