@ai-sdk/harness 1.0.38 → 1.0.40

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,22 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.40
4
+
5
+ ### Patch Changes
6
+
7
+ - 59a2306: fix(harness): execute host tools through telemetry context wrappers
8
+ - 5f65e61: feat(harness): add support for `stopWhen` control to `HarnessAgent` (e.g. `isStepCount(1)`)
9
+ - Updated dependencies [7f6650b]
10
+ - Updated dependencies [106ea59]
11
+ - ai@7.0.35
12
+
13
+ ## 1.0.39
14
+
15
+ ### Patch Changes
16
+
17
+ - 86a84c9: fix(harness): settle a turn aborted by the caller's abortSignal with an `abort` stream part instead of an AbortError `error` part, matching `streamText`'s abort contract
18
+ - ai@7.0.34
19
+
3
20
  ## 1.0.38
4
21
 
5
22
  ### Patch Changes
@@ -1,6 +1,6 @@
1
1
  import * as _ai_sdk_provider_utils from '@ai-sdk/provider-utils';
2
- import { Experimental_SandboxSession, UserModelMessage, ToolSet, FlexibleSchema, Tool, ToolApprovalResponse, ModelMessage, Context } from '@ai-sdk/provider-utils';
3
- import { ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, AgentCallParameters, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
2
+ import { Experimental_SandboxSession, UserModelMessage, ToolSet, FlexibleSchema, Tool, Context, Arrayable, ToolApprovalResponse, ModelMessage } from '@ai-sdk/provider-utils';
3
+ import { StopCondition, ToolApprovalStatus, TelemetryOptions, ActiveTools, StreamTextResult, Agent, AgentCallParameters, GenerateTextResult, AgentStreamParameters, Telemetry } from 'ai';
4
4
  import { z } from 'zod/v4';
5
5
  import { JSONValue, LanguageModelV4ToolCall, LanguageModelV4ToolApprovalRequest, LanguageModelV4ToolResult, LanguageModelV4FinishReason, LanguageModelV4Usage, JSONSchema7, AISDKError } from '@ai-sdk/provider';
6
6
 
@@ -1143,7 +1143,7 @@ type HarnessAgentToolFilteringSettings<TOOLS extends ToolSet> = {
1143
1143
  */
1144
1144
  readonly inactiveTools?: HarnessTools<TOOLS>;
1145
1145
  };
1146
- type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}> = {
1146
+ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAgentAdapter, TUserTools extends ToolSet = {}, RUNTIME_CONTEXT extends Context = Context> = {
1147
1147
  /**
1148
1148
  * The harness adapter driving the underlying agent runtime. Its
1149
1149
  * `builtinTools` are merged with the user-defined `tools` and exposed to
@@ -1177,6 +1177,16 @@ type HarnessAgentSettings<THarness extends HarnessAgentAdapter<any> = HarnessAge
1177
1177
  * later turns or when resuming a previously ended session.
1178
1178
  */
1179
1179
  readonly instructions?: string;
1180
+ /**
1181
+ * Conditions that stop the current result after a completed harness tool
1182
+ * step that can continue into another model step. The underlying turn remains
1183
+ * unfinished and can be suspended and continued.
1184
+ *
1185
+ * A terminal text-only step finishes naturally and is not stopped early.
1186
+ *
1187
+ * When omitted, the harness runs until the turn naturally finishes or pauses.
1188
+ */
1189
+ readonly stopWhen?: Arrayable<StopCondition<NoInfer<HarnessAllTools<THarness, TUserTools>>, RUNTIME_CONTEXT>>;
1180
1190
  /**
1181
1191
  * Built-in tool permission mode. Defaults to `'allow-all'`, preserving the
1182
1192
  * existing bypass-permissions behavior unless users opt in.
@@ -1303,6 +1313,7 @@ declare class HarnessAgentSession {
1303
1313
  private turnState;
1304
1314
  private turnSequence;
1305
1315
  private activeTurnSequence;
1316
+ private suspendedTurnState;
1306
1317
  /**
1307
1318
  * Whether this session was created from `resumeFrom` or `continueFrom`.
1308
1319
  * Captured at construction so it survives lifecycle cleanup.
@@ -1332,6 +1343,7 @@ declare class HarnessAgentSession {
1332
1343
  runtimeContext: RUNTIME_CONTEXT;
1333
1344
  abortSignal: AbortSignal | undefined;
1334
1345
  telemetry: TelemetryOptions | undefined;
1346
+ stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
1335
1347
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT>;
1336
1348
  continueTurn<TOOLS extends ToolSet, RUNTIME_CONTEXT extends Context>(options: {
1337
1349
  instructions: string | undefined;
@@ -1342,6 +1354,7 @@ declare class HarnessAgentSession {
1342
1354
  runtimeContext: RUNTIME_CONTEXT;
1343
1355
  abortSignal: AbortSignal | undefined;
1344
1356
  telemetry: TelemetryOptions | undefined;
1357
+ stopConditions: ReadonlyArray<StopCondition<TOOLS, RUNTIME_CONTEXT>>;
1345
1358
  toolApprovalContinuations?: readonly HarnessAgentToolApprovalContinuation[] | undefined;
1346
1359
  toolResultContinuations?: readonly HarnessAgentToolResultContinuation[] | undefined;
1347
1360
  }): HarnessAgentTurnResult<TOOLS, RUNTIME_CONTEXT>;
@@ -1392,6 +1405,7 @@ declare class HarnessAgentSession {
1392
1405
  private getPendingToolResults;
1393
1406
  private addPendingToolState;
1394
1407
  private suspendCurrentTurn;
1408
+ private captureStopConditionBoundary;
1395
1409
  private toResumeStateWithContinuation;
1396
1410
  private requirePromptableTurn;
1397
1411
  private requireContinuableTurn;
@@ -1461,11 +1475,12 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1461
1475
  */
1462
1476
  readonly tools: HarnessAllTools<THarness, TUserTools>;
1463
1477
  private readonly settings;
1478
+ private readonly stopConditions;
1464
1479
  private readonly sandboxConfig;
1465
1480
  private readonly activeUserTools;
1466
1481
  private readonly builtinToolFiltering;
1467
1482
  private readonly permissionMode;
1468
- constructor(settings: HarnessAgentSettings<THarness, TUserTools>);
1483
+ constructor(settings: HarnessAgentSettings<THarness, TUserTools, RUNTIME_CONTEXT>);
1469
1484
  /** Identifier of the harness backing this agent. */
1470
1485
  get harnessId(): string;
1471
1486
  /**
@@ -40,6 +40,7 @@ var HarnessCapabilityUnsupportedError = class extends (_b2 = HarnessError, _a2 =
40
40
 
41
41
  // src/agent/harness-agent.ts
42
42
  import {
43
+ asArray as asArray2,
43
44
  asSchema,
44
45
  generateId as generateId5
45
46
  } from "@ai-sdk/provider-utils";
@@ -181,8 +182,11 @@ import {
181
182
  executeTool,
182
183
  generateId as generateId4,
183
184
  isExecutableTool,
184
- safeParseJSON
185
+ safeParseJSON as safeParseJSON2
185
186
  } from "@ai-sdk/provider-utils";
187
+ import {
188
+ getErrorMessage
189
+ } from "@ai-sdk/provider";
186
190
  import { parseToolCall } from "ai/internal";
187
191
 
188
192
  // src/agent/internal/harness-stream-text-result.ts
@@ -457,6 +461,24 @@ var HarnessStreamTextResult = class {
457
461
  });
458
462
  this.fullStreamController.close();
459
463
  }
464
+ /**
465
+ * Settle the turn as user-aborted: emit a final `abort` part — matching
466
+ * `streamText`'s abort contract — and close the stream, instead of
467
+ * surfacing an `error` part. `toUIMessageStream` consumers then observe
468
+ * an `abort` chunk (and `isAborted: true`) rather than a spurious
469
+ * `onError`. The delayed promise accessors still reject with the
470
+ * underlying abort error so awaiting consumers do not hang. Idempotent.
471
+ */
472
+ abort(input) {
473
+ if (this.settled) return;
474
+ this.settled = true;
475
+ this.fullStreamController.enqueue({
476
+ type: "abort",
477
+ ...input.reason !== void 0 ? { reason: input.reason } : {}
478
+ });
479
+ this.fullStreamController.close();
480
+ this.rejectDelayedPromises(input.error);
481
+ }
460
482
  /**
461
483
  * Surface a fatal error as a stream `error` part + reject every delayed
462
484
  * promise so awaiting consumers stop hanging. Idempotent.
@@ -469,6 +491,9 @@ var HarnessStreamTextResult = class {
469
491
  error
470
492
  });
471
493
  this.fullStreamController.close();
494
+ this.rejectDelayedPromises(error);
495
+ }
496
+ rejectDelayedPromises(error) {
472
497
  for (const dp of [
473
498
  this._content,
474
499
  this._text,
@@ -596,6 +621,7 @@ var HarnessStreamTextResult = class {
596
621
  toUIMessageStream({
597
622
  originalMessages,
598
623
  generateMessageId,
624
+ onEnd,
599
625
  onFinish,
600
626
  messageMetadata,
601
627
  sendReasoning,
@@ -610,6 +636,7 @@ var HarnessStreamTextResult = class {
610
636
  tools: this.tools,
611
637
  originalMessages,
612
638
  generateMessageId,
639
+ onEnd,
613
640
  onFinish,
614
641
  messageMetadata,
615
642
  sendReasoning,
@@ -629,6 +656,7 @@ var HarnessStreamTextResult = class {
629
656
  toUIMessageStreamResponse({
630
657
  originalMessages,
631
658
  generateMessageId,
659
+ onEnd,
632
660
  onFinish,
633
661
  messageMetadata,
634
662
  sendReasoning,
@@ -642,6 +670,7 @@ var HarnessStreamTextResult = class {
642
670
  stream: this.toUIMessageStream({
643
671
  originalMessages,
644
672
  generateMessageId,
673
+ onEnd,
645
674
  onFinish,
646
675
  messageMetadata,
647
676
  sendReasoning,
@@ -896,7 +925,10 @@ var NOOP = {
896
925
  },
897
926
  stepFinish() {
898
927
  },
899
- toolStart() {
928
+ async toolStart() {
929
+ },
930
+ async executeTool({ execute }) {
931
+ return await execute();
900
932
  },
901
933
  toolEnd() {
902
934
  },
@@ -1055,11 +1087,12 @@ function createTurnTelemetry(opts) {
1055
1087
  stepOpen = false;
1056
1088
  stepNumber += 1;
1057
1089
  },
1058
- toolStart(call) {
1090
+ async toolStart(call) {
1059
1091
  var _a4;
1060
1092
  ensureStepOpen();
1093
+ if (openTools.has(call.toolCallId)) return;
1061
1094
  openTools.set(call.toolCallId, call);
1062
- (_a4 = dispatcher.onToolExecutionStart) == null ? void 0 : _a4.call(
1095
+ await ((_a4 = dispatcher.onToolExecutionStart) == null ? void 0 : _a4.call(
1063
1096
  dispatcher,
1064
1097
  cast({
1065
1098
  callId,
@@ -1073,7 +1106,11 @@ function createTurnTelemetry(opts) {
1073
1106
  },
1074
1107
  toolContext: void 0
1075
1108
  })
1076
- );
1109
+ ));
1110
+ },
1111
+ async executeTool({ toolCallId, execute }) {
1112
+ if (dispatcher.executeTool == null) return await execute();
1113
+ return await dispatcher.executeTool({ callId, toolCallId, execute });
1077
1114
  },
1078
1115
  toolEnd(toolCallId, output) {
1079
1116
  var _a4;
@@ -1235,6 +1272,20 @@ function logBridgeError({
1235
1272
  }
1236
1273
  }
1237
1274
 
1275
+ // src/utils/sandbox-channel.ts
1276
+ import {
1277
+ safeParseJSON,
1278
+ safeValidateTypes as safeValidateTypes2
1279
+ } from "@ai-sdk/provider-utils";
1280
+ var sandboxChannelEventCheckpointSymbol = /* @__PURE__ */ Symbol.for(
1281
+ "vercel.ai.harness.sandboxChannelEventCheckpoint"
1282
+ );
1283
+ function pinSandboxChannelEventCheckpoint(event) {
1284
+ var _a3;
1285
+ if (event == null || typeof event !== "object") return void 0;
1286
+ return (_a3 = event[sandboxChannelEventCheckpointSymbol]) == null ? void 0 : _a3.pin();
1287
+ }
1288
+
1238
1289
  // src/agent/internal/run-prompt.ts
1239
1290
  function runPrompt(input) {
1240
1291
  var _a3, _b3, _c, _d, _e, _f, _g;
@@ -1265,8 +1316,20 @@ function runPrompt(input) {
1265
1316
  promptText: input.prompt != null ? promptToText(input.prompt) : "",
1266
1317
  runtimeContext: input.runtimeContext
1267
1318
  });
1319
+ const settleFailure = (err) => {
1320
+ var _a4, _b4;
1321
+ (_a4 = input.onTurnFailed) == null ? void 0 : _a4.call(input);
1322
+ if ((_b4 = input.abortSignal) == null ? void 0 : _b4.aborted) {
1323
+ result.abort({
1324
+ error: err,
1325
+ ...input.abortSignal.reason !== void 0 ? { reason: getErrorMessage(input.abortSignal.reason) } : {}
1326
+ });
1327
+ return;
1328
+ }
1329
+ result.fail(err);
1330
+ };
1268
1331
  const done = (async () => {
1269
- var _a4, _b4, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k, _l, _m;
1332
+ var _a4, _b4, _c2, _d2, _e2, _f2, _g2, _h, _i, _j, _k;
1270
1333
  let bridge;
1271
1334
  try {
1272
1335
  bridge = await toHarnessStream({
@@ -1297,8 +1360,7 @@ function runPrompt(input) {
1297
1360
  context: "failed to start harness turn",
1298
1361
  error: err
1299
1362
  });
1300
- (_a4 = input.onTurnFailed) == null ? void 0 : _a4.call(input);
1301
- result.fail(err);
1363
+ settleFailure(err);
1302
1364
  return;
1303
1365
  }
1304
1366
  const { stream, control } = bridge;
@@ -1312,7 +1374,7 @@ function runPrompt(input) {
1312
1374
  pendingToolApprovals.map((approval) => [approval.toolCallId, approval])
1313
1375
  );
1314
1376
  const continuationsByApprovalId = new Map(
1315
- ((_b4 = input.toolApprovalContinuations) != null ? _b4 : []).map((continuation) => [
1377
+ ((_a4 = input.toolApprovalContinuations) != null ? _a4 : []).map((continuation) => [
1316
1378
  continuation.approvalResponse.approvalId,
1317
1379
  continuation
1318
1380
  ])
@@ -1324,14 +1386,21 @@ function runPrompt(input) {
1324
1386
  ])
1325
1387
  );
1326
1388
  const continuationsByToolCallId = new Map(
1327
- ((_c2 = input.toolResultContinuations) != null ? _c2 : []).map((continuation) => [
1389
+ ((_b4 = input.toolResultContinuations) != null ? _b4 : []).map((continuation) => [
1328
1390
  continuation.toolCallId,
1329
1391
  continuation
1330
1392
  ])
1331
1393
  );
1332
1394
  const settledHostToolCallIds = /* @__PURE__ */ new Set();
1333
1395
  let closingResumedStep = false;
1396
+ let pendingStopBoundary;
1334
1397
  let finalFinish;
1398
+ const completedSteps = [];
1399
+ const releasePendingStopBoundary = () => {
1400
+ var _a5;
1401
+ (_a5 = pendingStopBoundary == null ? void 0 : pendingStopBoundary.releaseCheckpoint) == null ? void 0 : _a5.call(pendingStopBoundary);
1402
+ pendingStopBoundary = void 0;
1403
+ };
1335
1404
  let stepText = "";
1336
1405
  let stepReasoning = "";
1337
1406
  let stepToolCalls = [];
@@ -1372,12 +1441,14 @@ function runPrompt(input) {
1372
1441
  content: buildStepContent()
1373
1442
  });
1374
1443
  resetStepContent();
1375
- return result.finishStep({
1444
+ const step = result.finishStep({
1376
1445
  finishReason: input2.finishReason,
1377
1446
  usage: input2.usage,
1378
1447
  providerMetadata: input2.providerMetadata,
1379
1448
  warnings: []
1380
1449
  });
1450
+ completedSteps.push(step);
1451
+ return step;
1381
1452
  };
1382
1453
  const finishForHostInputPause = async (options) => {
1383
1454
  if (options.completeCurrentStep) {
@@ -1478,9 +1549,16 @@ function runPrompt(input) {
1478
1549
  toolName: approval.toolName,
1479
1550
  input: approval.input
1480
1551
  };
1552
+ telemetry.start(input.session.modelId);
1553
+ await telemetry.toolStart({
1554
+ toolCallId: rawToolCall.toolCallId,
1555
+ toolName: rawToolCall.toolName,
1556
+ input: rawToolCall.input
1557
+ });
1481
1558
  const execution = await maybeExecuteHostTool({
1482
1559
  event: rawToolCall,
1483
1560
  tools: activeTools,
1561
+ wrappedExecuteTool: telemetry.executeTool,
1484
1562
  sandboxSession: input.sandboxSession,
1485
1563
  abortSignal: input.abortSignal,
1486
1564
  control,
@@ -1538,8 +1616,29 @@ function runPrompt(input) {
1538
1616
  }
1539
1617
  while (true) {
1540
1618
  const { value, done: done2 } = await reader.read();
1541
- if (done2) break;
1619
+ if (done2) {
1620
+ releasePendingStopBoundary();
1621
+ break;
1622
+ }
1542
1623
  if (value == null) continue;
1624
+ if (pendingStopBoundary != null) {
1625
+ if (value.type === "finish") {
1626
+ releasePendingStopBoundary();
1627
+ } else if ((await Promise.all(
1628
+ input.stopConditions.map(
1629
+ (condition) => condition({ steps: completedSteps })
1630
+ )
1631
+ )).some(Boolean)) {
1632
+ await ((_c2 = input.onStopConditionMet) == null ? void 0 : _c2.call(input));
1633
+ const { finishReason, usage } = pendingStopBoundary;
1634
+ releasePendingStopBoundary();
1635
+ telemetry.end({ finishReason, usage });
1636
+ await result.finish();
1637
+ return;
1638
+ } else {
1639
+ releasePendingStopBoundary();
1640
+ }
1641
+ }
1543
1642
  if (value.type === "stream-start") {
1544
1643
  telemetry.start((_d2 = value.modelId) != null ? _d2 : input.session.modelId);
1545
1644
  }
@@ -1587,6 +1686,17 @@ function runPrompt(input) {
1587
1686
  continue;
1588
1687
  }
1589
1688
  }
1689
+ if (value.type === "error" && displayValue.type === "error") {
1690
+ telemetry.error(value.error);
1691
+ logBridgeError({
1692
+ harnessId: input.harness.harnessId,
1693
+ sessionId: input.session.sessionId,
1694
+ context: "harness stream error",
1695
+ error: value.error
1696
+ });
1697
+ settleFailure(displayValue.error);
1698
+ return;
1699
+ }
1590
1700
  for (const part of translateStreamPart(displayValue)) {
1591
1701
  result.enqueue(part);
1592
1702
  }
@@ -1612,7 +1722,7 @@ function runPrompt(input) {
1612
1722
  toolName: value.toolName,
1613
1723
  input: value.input
1614
1724
  });
1615
- telemetry.toolStart({
1725
+ await telemetry.toolStart({
1616
1726
  toolCallId: value.toolCallId,
1617
1727
  toolName: value.toolName,
1618
1728
  input: value.input
@@ -1675,6 +1785,13 @@ function runPrompt(input) {
1675
1785
  usage: value.usage,
1676
1786
  providerMetadata: value.harnessMetadata
1677
1787
  });
1788
+ if (input.stopConditions != null && input.stopConditions.length > 0) {
1789
+ pendingStopBoundary = {
1790
+ finishReason: value.finishReason,
1791
+ usage: value.usage,
1792
+ releaseCheckpoint: pinSandboxChannelEventCheckpoint(value)
1793
+ };
1794
+ }
1678
1795
  }
1679
1796
  if (value.type === "finish") {
1680
1797
  finalFinish = value;
@@ -1783,6 +1900,7 @@ function runPrompt(input) {
1783
1900
  const execution = await maybeExecuteHostTool({
1784
1901
  event: toolCall,
1785
1902
  tools: activeTools,
1903
+ wrappedExecuteTool: telemetry.executeTool,
1786
1904
  sandboxSession: input.sandboxSession,
1787
1905
  abortSignal: input.abortSignal,
1788
1906
  control,
@@ -1813,23 +1931,11 @@ function runPrompt(input) {
1813
1931
  }
1814
1932
  telemetry.toolEnd(toolCall.toolCallId, execution.outcome);
1815
1933
  }
1816
- if (value.type === "error") {
1817
- telemetry.error(value.error);
1818
- logBridgeError({
1819
- harnessId: input.harness.harnessId,
1820
- sessionId: input.session.sessionId,
1821
- context: "harness stream error",
1822
- error: value.error
1823
- });
1824
- (_j = input.onTurnFailed) == null ? void 0 : _j.call(input);
1825
- result.fail(value.error);
1826
- return;
1827
- }
1828
1934
  }
1829
1935
  if (finalFinish != null) {
1830
- (_k = input.onTurnFinished) == null ? void 0 : _k.call(input);
1936
+ (_j = input.onTurnFinished) == null ? void 0 : _j.call(input);
1831
1937
  } else {
1832
- (_l = input.onTurnFailed) == null ? void 0 : _l.call(input);
1938
+ (_k = input.onTurnFailed) == null ? void 0 : _k.call(input);
1833
1939
  }
1834
1940
  await result.finish(
1835
1941
  finalFinish ? {
@@ -1846,9 +1952,9 @@ function runPrompt(input) {
1846
1952
  context: "harness turn failed",
1847
1953
  error: err
1848
1954
  });
1849
- (_m = input.onTurnFailed) == null ? void 0 : _m.call(input);
1850
- result.fail(err);
1955
+ settleFailure(err);
1851
1956
  } finally {
1957
+ releasePendingStopBoundary();
1852
1958
  reader.releaseLock();
1853
1959
  }
1854
1960
  })();
@@ -1870,28 +1976,34 @@ function hasTool(input) {
1870
1976
  async function maybeExecuteHostTool(input) {
1871
1977
  const tool = input.tools[input.event.toolName];
1872
1978
  if (!isExecutableTool(tool)) return { executed: false };
1873
- const parsed = await safeParseJSON({ text: input.event.input });
1979
+ const parsed = await safeParseJSON2({ text: input.event.input });
1874
1980
  const args = parsed.success ? parsed.value : input.event.input;
1875
1981
  try {
1876
- let output;
1877
- const stream = executeTool({
1878
- tool,
1879
- input: args,
1880
- options: {
1881
- toolCallId: input.event.toolCallId,
1882
- messages: [],
1883
- abortSignal: input.abortSignal,
1884
- context: void 0,
1885
- experimental_sandbox: input.sandboxSession
1982
+ const output = await input.wrappedExecuteTool({
1983
+ toolCallId: input.event.toolCallId,
1984
+ execute: async () => {
1985
+ let output2;
1986
+ const stream = executeTool({
1987
+ tool,
1988
+ input: args,
1989
+ options: {
1990
+ toolCallId: input.event.toolCallId,
1991
+ messages: [],
1992
+ abortSignal: input.abortSignal,
1993
+ context: void 0,
1994
+ experimental_sandbox: input.sandboxSession
1995
+ }
1996
+ });
1997
+ for await (const part of stream) {
1998
+ if (part.type === "preliminary") {
1999
+ input.onPreliminaryResult(part.output);
2000
+ } else {
2001
+ output2 = part.output;
2002
+ }
2003
+ }
2004
+ return output2;
1886
2005
  }
1887
2006
  });
1888
- for await (const part of stream) {
1889
- if (part.type === "preliminary") {
1890
- input.onPreliminaryResult(part.output);
1891
- } else {
1892
- output = part.output;
1893
- }
1894
- }
1895
2007
  await input.control.submitToolResult({
1896
2008
  toolCallId: input.event.toolCallId,
1897
2009
  output
@@ -2009,6 +2121,7 @@ var HarnessAgentSession = class {
2009
2121
  runtimeContext: options.runtimeContext,
2010
2122
  abortSignal: options.abortSignal,
2011
2123
  telemetry: options.telemetry,
2124
+ stopConditions: options.stopConditions,
2012
2125
  toolApproval: this.toolApproval,
2013
2126
  pendingToolApprovals: this.getPendingToolApprovals(),
2014
2127
  pendingToolResults: this.getPendingToolResults(),
@@ -2031,7 +2144,8 @@ var HarnessAgentSession = class {
2031
2144
  },
2032
2145
  onTurnFailed: () => {
2033
2146
  this.finishTrackedTurn({ turnId });
2034
- }
2147
+ },
2148
+ onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
2035
2149
  });
2036
2150
  return turn;
2037
2151
  } catch (error) {
@@ -2059,6 +2173,7 @@ var HarnessAgentSession = class {
2059
2173
  runtimeContext: options.runtimeContext,
2060
2174
  abortSignal: options.abortSignal,
2061
2175
  telemetry: options.telemetry,
2176
+ stopConditions: options.stopConditions,
2062
2177
  toolApproval: this.toolApproval,
2063
2178
  pendingToolApprovals: this.getPendingToolApprovals(),
2064
2179
  pendingToolResults: this.getPendingToolResults(),
@@ -2083,7 +2198,8 @@ var HarnessAgentSession = class {
2083
2198
  },
2084
2199
  onTurnFailed: () => {
2085
2200
  this.finishTrackedTurn({ turnId });
2086
- }
2201
+ },
2202
+ onStopConditionMet: () => this.captureStopConditionBoundary({ session, turnId })
2087
2203
  });
2088
2204
  return turn;
2089
2205
  } catch (error) {
@@ -2249,14 +2365,25 @@ var HarnessAgentSession = class {
2249
2365
  };
2250
2366
  }
2251
2367
  async suspendCurrentTurn(options) {
2252
- const raw = await options.session.doSuspendTurn();
2253
- const validated = await validateLifecycleStateData({
2254
- harness: this.harness,
2255
- state: raw,
2256
- expectedType: "continue-turn"
2257
- });
2368
+ var _a3;
2369
+ (_a3 = this.suspendedTurnState) != null ? _a3 : this.suspendedTurnState = (async () => {
2370
+ const raw = await options.session.doSuspendTurn();
2371
+ const validated = await validateLifecycleStateData({
2372
+ harness: this.harness,
2373
+ state: raw,
2374
+ expectedType: "continue-turn"
2375
+ });
2376
+ return this.addPendingToolState(validated);
2377
+ })();
2378
+ const state = await this.suspendedTurnState;
2258
2379
  this.turnState = "suspended";
2259
- return this.addPendingToolState(validated);
2380
+ return state;
2381
+ }
2382
+ async captureStopConditionBoundary(options) {
2383
+ if (this.sessionState !== "active" || this.activeTurnSequence !== options.turnId) {
2384
+ return;
2385
+ }
2386
+ await this.suspendCurrentTurn({ session: options.session });
2260
2387
  }
2261
2388
  toResumeStateWithContinuation(options) {
2262
2389
  const { continueFrom } = options;
@@ -2305,6 +2432,7 @@ var HarnessAgentSession = class {
2305
2432
  startTrackedTurn() {
2306
2433
  const turnId = ++this.turnSequence;
2307
2434
  this.activeTurnSequence = turnId;
2435
+ this.suspendedTurnState = void 0;
2308
2436
  this.turnState = "running";
2309
2437
  return turnId;
2310
2438
  }
@@ -2313,6 +2441,7 @@ var HarnessAgentSession = class {
2313
2441
  if (this.activeTurnSequence !== options.turnId) return;
2314
2442
  this.pendingToolApprovals.clear();
2315
2443
  this.pendingToolResults.clear();
2444
+ this.suspendedTurnState = void 0;
2316
2445
  this.turnState = "idle";
2317
2446
  }
2318
2447
  endLocalHandle(options) {
@@ -2833,6 +2962,7 @@ var HarnessAgent = class {
2833
2962
  const sandboxConfig = resolveSandboxConfig(settings);
2834
2963
  validateSandboxBootstrapSettings(sandboxConfig);
2835
2964
  this.settings = settings;
2965
+ this.stopConditions = settings.stopWhen == null ? [] : asArray2(settings.stopWhen);
2836
2966
  this.sandboxConfig = sandboxConfig;
2837
2967
  this.id = settings.id;
2838
2968
  const userTools = (_a3 = settings.tools) != null ? _a3 : {};
@@ -3080,6 +3210,7 @@ var HarnessAgent = class {
3080
3210
  runtimeContext: input.runtimeContext,
3081
3211
  abortSignal: input.abortSignal,
3082
3212
  telemetry: this.settings.telemetry,
3213
+ stopConditions: this.stopConditions,
3083
3214
  toolApprovalContinuations: input.turnInput.toolApprovalContinuations,
3084
3215
  toolResultContinuations: input.turnInput.toolResultContinuations
3085
3216
  });
@@ -3093,7 +3224,8 @@ var HarnessAgent = class {
3093
3224
  builtinToolFiltering: this.builtinToolFiltering,
3094
3225
  runtimeContext: input.runtimeContext,
3095
3226
  abortSignal: input.abortSignal,
3096
- telemetry: this.settings.telemetry
3227
+ telemetry: this.settings.telemetry,
3228
+ stopConditions: this.stopConditions
3097
3229
  });
3098
3230
  }
3099
3231
  async _acquireSandbox(input) {