@ai-sdk/harness 1.0.107 → 1.0.109

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,32 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.109
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [615ac89]
8
+ - ai@7.0.99
9
+
10
+ ## 1.0.108
11
+
12
+ ### Patch Changes
13
+
14
+ - 4d1bf28: feat(harness): support `instructions` on `HarnessAgent` to be a `SystemModelMessage` for parity with `ToolLoopAgent`
15
+ - cdc12a1: feat(harness): add support for using adapter-native subscriptions where available
16
+ - d70a334: feat(workflow-harness): automatically persist validated `HarnessAgent` output in workflow results
17
+ - 81ba84c: fix(harness): preserve completed host tool results across turn suspension and session finalization
18
+ - Updated dependencies [5ec21a6]
19
+ - Updated dependencies [db59d78]
20
+ - Updated dependencies [7469a3b]
21
+ - Updated dependencies [f87bf07]
22
+ - Updated dependencies [bc5cb7a]
23
+ - Updated dependencies [a5f449a]
24
+ - Updated dependencies [813bb36]
25
+ - Updated dependencies [c43e4b7]
26
+ - @ai-sdk/provider@4.0.14
27
+ - ai@7.0.98
28
+ - @ai-sdk/provider-utils@5.0.40
29
+
3
30
  ## 1.0.107
4
31
 
5
32
  ### Patch Changes
@@ -1,5 +1,5 @@
1
1
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
2
- import { Experimental_SandboxSession, UserModelMessage, ToolResultPart, ProviderOptions, ToolSet, FlexibleSchema, Tool, Context, MaybePromiseLike, Arrayable, ToolApprovalResponse, ModelMessage } from '@ai-sdk/provider-utils';
2
+ import { Experimental_SandboxSession, UserModelMessage, ToolResultPart, ProviderOptions, ToolSet, FlexibleSchema, Tool, Context, SystemModelMessage, MaybePromiseLike, Arrayable, ToolApprovalResponse, ModelMessage } from '@ai-sdk/provider-utils';
3
3
  import { OutputInterface, AgentCallParameters, Prompt, StopCondition, GenerateTextOnStartCallback, GenerateTextOnStepStartCallback, OnLanguageModelCallStartCallback, OnLanguageModelCallEndCallback, OnToolExecutionStartCallback, OnToolExecutionEndCallback, GenerateTextOnStepEndCallback, GenerateTextOnEndCallback, ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
4
4
  import { z } from 'zod/v4';
5
5
  import { JSONSchema7, JSONValue, LanguageModelV4StreamPart, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, AISDKError } from '@ai-sdk/provider';
@@ -467,6 +467,12 @@ type HarnessV1PendingToolResult = {
467
467
  readonly toolName: string;
468
468
  readonly input: string;
469
469
  readonly providerOptions?: ProviderOptions;
470
+ /** Executed while suspending; submit on resume without running the tool again. */
471
+ readonly completedResult?: {
472
+ readonly output: unknown;
473
+ readonly isError?: boolean;
474
+ readonly toolResult?: ToolResultPart;
475
+ };
470
476
  };
471
477
  /**
472
478
  * Framework-owned settings captured when a turn begins. The same settings are
@@ -1347,9 +1353,10 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1347
1353
  * Instructions for the underlying agent runtime. Adapters append these to a
1348
1354
  * native system or developer prompt when supported. Otherwise, they prepend
1349
1355
  * them to the user message. `prepareCall` can replace them between completed
1350
- * turns.
1356
+ * turns. When a `SystemModelMessage` is provided, only its `content` is
1357
+ * forwarded to the harness adapter.
1351
1358
  */
1352
- readonly instructions?: string;
1359
+ readonly instructions?: string | SystemModelMessage;
1353
1360
  /**
1354
1361
  * Additional HTTP headers to be sent with every model request.
1355
1362
  *
@@ -1521,6 +1528,7 @@ declare class HarnessAgentSession {
1521
1528
  private turnState;
1522
1529
  private turnSequence;
1523
1530
  private activeTurnSequence;
1531
+ private activePromptDone;
1524
1532
  private activePromptControl;
1525
1533
  private suspendedTurnState;
1526
1534
  private activeTurnSettings;
@@ -1633,6 +1641,7 @@ declare class HarnessAgentSession {
1633
1641
  private getPendingToolResults;
1634
1642
  private addPendingToolState;
1635
1643
  private suspendCurrentTurn;
1644
+ private finalizeCurrentTurnSuspension;
1636
1645
  private captureStopConditionBoundary;
1637
1646
  private toResumeStateWithContinuation;
1638
1647
  private requirePromptableTurn;
@@ -1717,6 +1726,8 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1717
1726
  constructor(settings: HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT, OUTPUT, CALL_OPTIONS>);
1718
1727
  /** Identifier of the harness backing this agent. */
1719
1728
  get harnessId(): string;
1729
+ /** Whether this agent parses completed turns with its configured output. */
1730
+ get hasOutput(): boolean;
1720
1731
  /**
1721
1732
  * Start a fresh session, or resume from state previously returned by
1722
1733
  * `session.detach()` or `session.stop()`. The returned
@@ -1795,22 +1795,6 @@ function runPrompt(input) {
1795
1795
  onPendingToolResult(pendingResult);
1796
1796
  return pendingResult;
1797
1797
  };
1798
- const processPendingToolResultContinuation = async (pendingResult, continuation) => {
1799
- const result2 = unwrapToolResultOutput(continuation);
1800
- onToolResultSettled(pendingResult.toolCallId);
1801
- pendingResultsByToolCallId.delete(pendingResult.toolCallId);
1802
- settledHostToolCallIds.add(pendingResult.toolCallId);
1803
- await control.submitToolResult({
1804
- toolCallId: pendingResult.toolCallId,
1805
- output: result2.output,
1806
- isError: result2.isError,
1807
- toolResult: {
1808
- ...continuation,
1809
- toolName: pendingResult.toolName,
1810
- ...continuation.providerOptions == null && pendingResult.providerOptions != null ? { providerOptions: pendingResult.providerOptions } : {}
1811
- }
1812
- });
1813
- };
1814
1798
  const enqueueHostToolOutcome = (options) => {
1815
1799
  if (options.outcome.ok) {
1816
1800
  const stripped = stripWorkDir(
@@ -1839,6 +1823,54 @@ function runPrompt(input) {
1839
1823
  error: options.outcome.error
1840
1824
  });
1841
1825
  };
1826
+ const submitToolResult = async (submission) => {
1827
+ var _a6, _b6, _c3;
1828
+ if (!((_a6 = input.isTurnSuspending) == null ? void 0 : _a6.call(input))) {
1829
+ return control.submitToolResult(submission);
1830
+ }
1831
+ const toolCall = (_c3 = (_b6 = rawToolCallsByToolCallId.get(submission.toolCallId)) != null ? _b6 : pendingResultsByToolCallId.get(submission.toolCallId)) != null ? _c3 : pendingToolApprovals.find(
1832
+ (approval) => approval.toolCallId === submission.toolCallId
1833
+ );
1834
+ if (toolCall == null) {
1835
+ throw new Error(
1836
+ `Unknown suspended tool call '${submission.toolCallId}'.`
1837
+ );
1838
+ }
1839
+ const { toolCallId, ...completedResult } = submission;
1840
+ onPendingToolResult({
1841
+ toolCallId,
1842
+ toolName: toolCall.toolName,
1843
+ input: toolCall.input,
1844
+ completedResult
1845
+ });
1846
+ const parsed = toolCallsByToolCallId.get(toolCallId);
1847
+ if (parsed != null && !settledHostToolCallIds.has(toolCallId)) {
1848
+ bufferedToolOutcomes.push(
1849
+ () => enqueueHostToolOutcome({
1850
+ toolCall: parsed,
1851
+ outcome: submission.isError ? { ok: false, error: submission.output } : { ok: true, output: submission.output }
1852
+ })
1853
+ );
1854
+ }
1855
+ };
1856
+ const processPendingToolResultContinuation = async (pendingResult, continuation) => {
1857
+ const submission = continuation == null ? pendingResult.completedResult : {
1858
+ ...unwrapToolResultOutput(continuation),
1859
+ toolResult: {
1860
+ ...continuation,
1861
+ toolName: pendingResult.toolName,
1862
+ ...continuation.providerOptions == null && pendingResult.providerOptions != null ? { providerOptions: pendingResult.providerOptions } : {}
1863
+ }
1864
+ };
1865
+ if (submission == null) return;
1866
+ settledHostToolCallIds.add(pendingResult.toolCallId);
1867
+ onToolResultSettled(pendingResult.toolCallId);
1868
+ await submitToolResult({
1869
+ toolCallId: pendingResult.toolCallId,
1870
+ ...submission
1871
+ });
1872
+ pendingResultsByToolCallId.delete(pendingResult.toolCallId);
1873
+ };
1842
1874
  const processPendingApprovalContinuation = async (approval, continuation) => {
1843
1875
  var _a6;
1844
1876
  const rawToolCall = (_a6 = rawToolCallsByToolCallId.get(approval.toolCallId)) != null ? _a6 : {
@@ -1877,7 +1909,7 @@ function runPrompt(input) {
1877
1909
  }
1878
1910
  settledHostToolCallIds.add(approval.toolCallId);
1879
1911
  if (!continuation.approved) {
1880
- await control.submitToolResult({
1912
+ await submitToolResult({
1881
1913
  toolCallId: approval.toolCallId,
1882
1914
  output: {
1883
1915
  type: "execution-denied",
@@ -1897,7 +1929,7 @@ function runPrompt(input) {
1897
1929
  wrappedExecuteTool: lifecycle.executeTool,
1898
1930
  sandboxSession: input.sandboxSession,
1899
1931
  abortSignal: input.abortSignal,
1900
- control,
1932
+ submitToolResult,
1901
1933
  onPreliminaryResult: (preliminaryOutput) => {
1902
1934
  const stripped = stripWorkDir(
1903
1935
  {
@@ -1956,12 +1988,12 @@ function runPrompt(input) {
1956
1988
  const continuation = continuationsByToolCallId.get(
1957
1989
  pendingResult.toolCallId
1958
1990
  );
1959
- if (continuation != null) {
1991
+ if (continuation != null || pendingResult.completedResult != null) {
1960
1992
  await processPendingToolResultContinuation(
1961
1993
  pendingResult,
1962
1994
  continuation
1963
1995
  );
1964
- closingResumedStep = true;
1996
+ if (pendingResult.completedResult == null) closingResumedStep = true;
1965
1997
  }
1966
1998
  }
1967
1999
  while (true) {
@@ -2218,7 +2250,7 @@ function runPrompt(input) {
2218
2250
  toolName: toolCall.toolName
2219
2251
  })
2220
2252
  };
2221
- await control.submitToolResult({
2253
+ await submitToolResult({
2222
2254
  toolCallId: toolCall.toolCallId,
2223
2255
  output
2224
2256
  });
@@ -2262,7 +2294,7 @@ function runPrompt(input) {
2262
2294
  type: "execution-denied",
2263
2295
  reason: customToolApprovalDecision.reason
2264
2296
  };
2265
- await control.submitToolResult({
2297
+ await submitToolResult({
2266
2298
  toolCallId: toolCall.toolCallId,
2267
2299
  output
2268
2300
  });
@@ -2343,7 +2375,7 @@ function runPrompt(input) {
2343
2375
  wrappedExecuteTool: lifecycle.executeTool,
2344
2376
  sandboxSession: input.sandboxSession,
2345
2377
  abortSignal: input.abortSignal,
2346
- control,
2378
+ submitToolResult,
2347
2379
  onPreliminaryResult: (preliminaryOutput) => {
2348
2380
  const stripped = stripWorkDir(
2349
2381
  {
@@ -2386,6 +2418,7 @@ function runPrompt(input) {
2386
2418
  await waitForOutstandingHostToolExecutions();
2387
2419
  const isTurnSuspending = ((_m = input.isTurnSuspending) == null ? void 0 : _m.call(input)) === true;
2388
2420
  if (isTurnSuspending) {
2421
+ await publishToolExecutions();
2389
2422
  if (finalFinish == null) {
2390
2423
  result.discardCurrentStepContent();
2391
2424
  }
@@ -2476,13 +2509,13 @@ async function maybeExecuteHostTool(input) {
2476
2509
  return output2;
2477
2510
  }
2478
2511
  });
2479
- await input.control.submitToolResult({
2512
+ await input.submitToolResult({
2480
2513
  toolCallId: input.event.toolCallId,
2481
2514
  output
2482
2515
  });
2483
2516
  return { executed: true, outcome: { ok: true, output } };
2484
2517
  } catch (err) {
2485
- await input.control.submitToolResult({
2518
+ await input.submitToolResult({
2486
2519
  toolCallId: input.event.toolCallId,
2487
2520
  output: { error: String(err) },
2488
2521
  isError: true
@@ -2646,6 +2679,7 @@ var HarnessAgentSession = class {
2646
2679
  isTurnSuspending: () => this.activeTurnSequence === turnId && this.suspendedTurnState != null,
2647
2680
  onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
2648
2681
  });
2682
+ this.activePromptDone = turn.done;
2649
2683
  return {
2650
2684
  ...turn,
2651
2685
  ready: this.waitForPromptControl({ turnId })
@@ -2721,6 +2755,7 @@ var HarnessAgentSession = class {
2721
2755
  isTurnSuspending: () => this.activeTurnSequence === turnId && this.suspendedTurnState != null,
2722
2756
  onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
2723
2757
  });
2758
+ this.activePromptDone = turn.done;
2724
2759
  return {
2725
2760
  ...turn,
2726
2761
  ready: this.waitForPromptControl({ turnId })
@@ -2787,7 +2822,7 @@ var HarnessAgentSession = class {
2787
2822
  try {
2788
2823
  if (this.turnState !== "idle") {
2789
2824
  return this.toResumeStateWithContinuation({
2790
- continueFrom: await this.suspendCurrentTurn({ session })
2825
+ continueFrom: await this.finalizeCurrentTurnSuspension({ session })
2791
2826
  });
2792
2827
  }
2793
2828
  const raw = await session.doDetach();
@@ -2818,7 +2853,7 @@ var HarnessAgentSession = class {
2818
2853
  try {
2819
2854
  if (this.turnState !== "idle") {
2820
2855
  return this.toResumeStateWithContinuation({
2821
- continueFrom: await this.suspendCurrentTurn({ session })
2856
+ continueFrom: await this.finalizeCurrentTurnSuspension({ session })
2822
2857
  });
2823
2858
  }
2824
2859
  const raw = await session.doStop();
@@ -2880,7 +2915,7 @@ var HarnessAgentSession = class {
2880
2915
  }
2881
2916
  const session = this.underlyingSession;
2882
2917
  try {
2883
- return await this.suspendCurrentTurn({ session });
2918
+ return await this.finalizeCurrentTurnSuspension({ session });
2884
2919
  } finally {
2885
2920
  this.endLocalHandle({ sessionState: "detached" });
2886
2921
  }
@@ -2927,6 +2962,11 @@ var HarnessAgentSession = class {
2927
2962
  this.turnState = "suspended";
2928
2963
  return state;
2929
2964
  }
2965
+ async finalizeCurrentTurnSuspension(options) {
2966
+ const state = await this.suspendCurrentTurn(options);
2967
+ await this.activePromptDone;
2968
+ return this.addPendingToolState(state);
2969
+ }
2930
2970
  async captureStopConditionBoundary(options) {
2931
2971
  if (this.sessionState !== "active" || this.activeTurnSequence !== options.turnId) {
2932
2972
  return;
@@ -3639,6 +3679,10 @@ var HarnessAgent = class {
3639
3679
  get harnessId() {
3640
3680
  return this.settings.harness.harnessId;
3641
3681
  }
3682
+ /** Whether this agent parses completed turns with its configured output. */
3683
+ get hasOutput() {
3684
+ return this.settings.output != null;
3685
+ }
3642
3686
  /**
3643
3687
  * Start a fresh session, or resume from state previously returned by
3644
3688
  * `session.detach()` or `session.stop()`. The returned
@@ -4117,7 +4161,7 @@ var HarnessAgent = class {
4117
4161
  };
4118
4162
  }
4119
4163
  _prepareTurnSettings(options) {
4120
- var _a4, _b4;
4164
+ var _a4, _b4, _c;
4121
4165
  const userTools = (_a4 = options.tools) != null ? _a4 : {};
4122
4166
  assertNoReservedQuestionTool({
4123
4167
  harness: this.settings.harness,
@@ -4137,7 +4181,7 @@ var HarnessAgent = class {
4137
4181
  return {
4138
4182
  model: options.model,
4139
4183
  skills: (_b4 = options.skills) != null ? _b4 : [],
4140
- instructions: options.instructions,
4184
+ instructions: typeof options.instructions === "string" ? options.instructions : (_c = options.instructions) == null ? void 0 : _c.content,
4141
4185
  tools,
4142
4186
  activeTools: toolFiltering.activeUserTools,
4143
4187
  toolSpecs: this._toToolSpecs(toolFiltering.activeUserTools),