@ai-sdk/harness 1.0.74 → 1.0.76

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.
@@ -45,33 +45,6 @@ import {
45
45
  generateId as generateId5
46
46
  } from "@ai-sdk/provider-utils";
47
47
 
48
- // src/agent/internal/bridge-port-registry.ts
49
- var registries = /* @__PURE__ */ new WeakMap();
50
- function acquireBridgePort(options) {
51
- let entry = registries.get(options.poolKey);
52
- if (entry == null) {
53
- entry = { pool: options.pool, leases: /* @__PURE__ */ new Map() };
54
- registries.set(options.poolKey, entry);
55
- }
56
- const existing = entry.leases.get(options.sessionId);
57
- if (existing != null) return existing;
58
- const leased = new Set(entry.leases.values());
59
- for (const port of entry.pool) {
60
- if (!leased.has(port)) {
61
- entry.leases.set(options.sessionId, port);
62
- return port;
63
- }
64
- }
65
- throw new Error(
66
- `No available bridge port \u2014 pool of ${entry.pool.length} ports is fully leased.`
67
- );
68
- }
69
- function releaseBridgePort(options) {
70
- const entry = registries.get(options.poolKey);
71
- if (entry == null) return;
72
- entry.leases.delete(options.sessionId);
73
- }
74
-
75
48
  // src/agent/internal/lifecycle-state-validation.ts
76
49
  import { safeValidateTypes } from "@ai-sdk/provider-utils";
77
50
  async function validateLifecycleStateData(input) {
@@ -759,21 +732,10 @@ var HarnessStreamTextResult = class {
759
732
  return;
760
733
  }
761
734
  case "tool-call":
762
- this.currentStepContent.push({
763
- ...part
764
- });
765
- return;
766
735
  case "tool-approval-request":
767
- this.currentStepContent.push({
768
- ...part
769
- });
770
- return;
771
736
  case "tool-approval-response":
772
- this.currentStepContent.push({
773
- ...part
774
- });
775
- return;
776
737
  case "tool-result":
738
+ case "tool-error":
777
739
  this.currentStepContent.push({
778
740
  ...part
779
741
  });
@@ -870,7 +832,8 @@ function notSupportedYet(feature) {
870
832
 
871
833
  // src/agent/internal/translate-stream-part.ts
872
834
  import { generateId as generateId2 } from "@ai-sdk/provider-utils";
873
- function translateStreamPart(event) {
835
+ function translateStreamPart(event, options = {}) {
836
+ var _a4, _b4;
874
837
  switch (event.type) {
875
838
  case "stream-start":
876
839
  return [];
@@ -929,6 +892,20 @@ function translateStreamPart(event) {
929
892
  case "tool-approval-request":
930
893
  return [];
931
894
  case "tool-result":
895
+ if (event.isError === true && ((_b4 = (_a4 = options.isProviderExecuted) == null ? void 0 : _a4.call(options, event.toolCallId)) != null ? _b4 : true)) {
896
+ return [
897
+ {
898
+ type: "tool-error",
899
+ toolCallId: event.toolCallId,
900
+ toolName: event.toolName,
901
+ input: void 0,
902
+ error: event.result,
903
+ providerExecuted: true,
904
+ ...event.dynamic !== void 0 ? { dynamic: event.dynamic } : {},
905
+ ...event.providerMetadata !== void 0 ? { providerMetadata: event.providerMetadata } : {}
906
+ }
907
+ ];
908
+ }
932
909
  return [
933
910
  {
934
911
  type: "tool-result",
@@ -1575,6 +1552,12 @@ function runPrompt(input) {
1575
1552
  const reader = stream.getReader();
1576
1553
  const toolCallsByToolCallId = /* @__PURE__ */ new Map();
1577
1554
  const rawToolCallsByToolCallId = /* @__PURE__ */ new Map();
1555
+ const translateOptions = {
1556
+ isProviderExecuted: (toolCallId) => {
1557
+ const rawToolCall = rawToolCallsByToolCallId.get(toolCallId);
1558
+ return rawToolCall == null || rawToolCall.providerExecuted === true;
1559
+ }
1560
+ };
1578
1561
  const pendingApprovalsByApprovalId = new Map(
1579
1562
  pendingToolApprovals.map((approval) => [approval.approvalId, approval])
1580
1563
  );
@@ -1956,7 +1939,10 @@ function runPrompt(input) {
1956
1939
  settleFailure(displayValue.error);
1957
1940
  return;
1958
1941
  }
1959
- for (const part of translateStreamPart(displayValue)) {
1942
+ for (const part of translateStreamPart(
1943
+ displayValue,
1944
+ translateOptions
1945
+ )) {
1960
1946
  result.enqueue(part);
1961
1947
  }
1962
1948
  if (displayValue.type === "tool-call") {
@@ -2339,22 +2325,21 @@ var HarnessAgentSession = class {
2339
2325
  this.sessionState = "active";
2340
2326
  this.turnSequence = 0;
2341
2327
  this.activeTurnSequence = 0;
2342
- var _a4, _b4, _c;
2328
+ var _a4, _b4, _c, _d;
2343
2329
  this.sessionId = options.sessionId;
2344
2330
  this.harness = options.harness;
2345
2331
  this.underlyingSession = options.underlyingSession;
2346
2332
  this.sandboxSession = options.sandboxSession;
2347
- this.sandboxProvider = options.sandboxProvider;
2348
- this.leasedBridgePort = options.leasedBridgePort;
2333
+ this.ownsSandboxLifecycle = (_a4 = options.ownsSandboxLifecycle) != null ? _a4 : true;
2349
2334
  this.sessionWorkDir = options.sessionWorkDir;
2350
2335
  this.toolApproval = options.toolApproval;
2351
- for (const approval of (_a4 = options.pendingToolApprovals) != null ? _a4 : []) {
2336
+ for (const approval of (_b4 = options.pendingToolApprovals) != null ? _b4 : []) {
2352
2337
  this.pendingToolApprovals.set(approval.approvalId, approval);
2353
2338
  }
2354
- for (const pendingResult of (_b4 = options.pendingToolResults) != null ? _b4 : []) {
2339
+ for (const pendingResult of (_c = options.pendingToolResults) != null ? _c : []) {
2355
2340
  this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
2356
2341
  }
2357
- this.turnState = (_c = options.turnState) != null ? _c : this.pendingToolApprovals.size > 0 ? "awaiting-approval" : this.pendingToolResults.size > 0 ? "awaiting-tool-result" : "idle";
2342
+ this.turnState = (_d = options.turnState) != null ? _d : this.pendingToolApprovals.size > 0 ? "awaiting-approval" : this.pendingToolResults.size > 0 ? "awaiting-tool-result" : "idle";
2358
2343
  this.isResume = options.underlyingSession.isResume;
2359
2344
  }
2360
2345
  /**
@@ -2534,14 +2519,12 @@ var HarnessAgentSession = class {
2534
2519
  });
2535
2520
  return validated;
2536
2521
  } finally {
2537
- this.endLocalHandle({
2538
- sessionState: "detached",
2539
- releasePortLease: false
2540
- });
2522
+ this.endLocalHandle({ sessionState: "detached" });
2541
2523
  }
2542
2524
  }
2543
2525
  /**
2544
- * Persist enough state to resume later, then stop the runtime and sandbox.
2526
+ * Persist enough state to resume later, then stop the runtime and any
2527
+ * harness-owned sandbox.
2545
2528
  * Returns the resume state for a future
2546
2529
  * `agent.createSession({ sessionId, resumeFrom })` call.
2547
2530
  */
@@ -2567,28 +2550,28 @@ var HarnessAgentSession = class {
2567
2550
  });
2568
2551
  return validated;
2569
2552
  } finally {
2570
- this.endLocalHandle({
2571
- sessionState: "stopped",
2572
- releasePortLease: true
2573
- });
2574
- await Promise.resolve(sandboxSession.stop()).catch(() => {
2575
- });
2553
+ this.endLocalHandle({ sessionState: "stopped" });
2554
+ if (this.ownsSandboxLifecycle) {
2555
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
2556
+ });
2557
+ }
2576
2558
  }
2577
2559
  }
2578
2560
  /**
2579
- * Stop the runtime and discard resumability. The sandbox is destroyed when
2580
- * the provider supports destruction; otherwise it is stopped.
2561
+ * Stop the runtime and discard resumability. A harness-owned sandbox is
2562
+ * destroyed when supported; otherwise it is stopped.
2581
2563
  */
2582
2564
  async destroy() {
2583
2565
  var _a4, _b4;
2584
2566
  if (this.sessionState !== "active") return;
2585
2567
  const session = this.underlyingSession;
2586
2568
  const sandboxSession = this.getSandboxSession();
2587
- this.endLocalHandle({ sessionState: "destroyed", releasePortLease: true });
2569
+ this.endLocalHandle({ sessionState: "destroyed" });
2588
2570
  if (session != null) {
2589
2571
  await Promise.resolve(session.doDestroy()).catch(() => {
2590
2572
  });
2591
2573
  }
2574
+ if (!this.ownsSandboxLifecycle) return;
2592
2575
  await Promise.resolve(
2593
2576
  (_b4 = (_a4 = sandboxSession.destroy) == null ? void 0 : _a4.call(sandboxSession)) != null ? _b4 : sandboxSession.stop()
2594
2577
  ).catch(() => {
@@ -2603,9 +2586,8 @@ var HarnessAgentSession = class {
2603
2586
  *
2604
2587
  * After this call the session is detached. This in-process handle no
2605
2588
  * longer drives turns; a future slice creates a fresh session from the
2606
- * returned state. The sandbox is **not** stopped and no port lease is
2607
- * released, because bridge-backed adapters may still have a live bridge on
2608
- * that port.
2589
+ * returned state. The sandbox is **not** stopped because bridge-backed
2590
+ * adapters may still have a live bridge.
2609
2591
  */
2610
2592
  async suspendTurn() {
2611
2593
  if (this.sessionState !== "active" || this.underlyingSession == null) {
@@ -2622,10 +2604,7 @@ var HarnessAgentSession = class {
2622
2604
  try {
2623
2605
  return await this.suspendCurrentTurn({ session });
2624
2606
  } finally {
2625
- this.endLocalHandle({
2626
- sessionState: "detached",
2627
- releasePortLease: false
2628
- });
2607
+ this.endLocalHandle({ sessionState: "detached" });
2629
2608
  }
2630
2609
  }
2631
2610
  getPendingToolApprovals() {
@@ -2735,17 +2714,6 @@ var HarnessAgentSession = class {
2735
2714
  this.sessionState = options.sessionState;
2736
2715
  this.underlyingSession = void 0;
2737
2716
  this.sandboxSession = void 0;
2738
- if (options.releasePortLease) {
2739
- this.releasePortLease();
2740
- }
2741
- }
2742
- releasePortLease() {
2743
- if (this.leasedBridgePort == null) return;
2744
- releaseBridgePort({
2745
- poolKey: this.sandboxProvider,
2746
- sessionId: this.sessionId
2747
- });
2748
- this.leasedBridgePort = void 0;
2749
2717
  }
2750
2718
  requireReusableSession() {
2751
2719
  if (this.sessionState !== "active" || this.underlyingSession == null) {
@@ -3343,13 +3311,15 @@ var HarnessAgent = class {
3343
3311
  * `session.destroy()`.
3344
3312
  */
3345
3313
  async createSession(options) {
3346
- var _a4;
3314
+ var _a4, _b4;
3347
3315
  const sessionId = (_a4 = options == null ? void 0 : options.sessionId) != null ? _a4 : generateId5();
3348
3316
  const resumeFrom = options == null ? void 0 : options.resumeFrom;
3349
3317
  const continueFrom = options == null ? void 0 : options.continueFrom;
3318
+ const providedSandboxSession = options == null ? void 0 : options.sandboxSession;
3350
3319
  const abortSignal = options == null ? void 0 : options.abortSignal;
3351
3320
  const harness = this.settings.harness;
3352
3321
  const sandboxProvider = this.settings.sandbox;
3322
+ const ownsSandboxLifecycle = providedSandboxSession == null;
3353
3323
  if (resumeFrom != null && continueFrom != null) {
3354
3324
  throw new Error(
3355
3325
  "HarnessAgent.createSession: pass either `resumeFrom` or `continueFrom`, not both."
@@ -3373,44 +3343,99 @@ var HarnessAgent = class {
3373
3343
  }
3374
3344
  const effectiveContinueFrom = validatedContinueFrom != null ? validatedContinueFrom : validatedResumeFrom == null ? void 0 : validatedResumeFrom.continueFrom;
3375
3345
  const isResumedSession = validatedResumeFrom != null || effectiveContinueFrom != null;
3376
- let recipe;
3377
- if (harness.getBootstrap != null) {
3378
- recipe = await harness.getBootstrap({ abortSignal });
3379
- }
3380
- const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
3381
- recipe,
3382
- settings: this.sandboxConfig
3383
- });
3384
- const acquiredSandboxSession = await this._acquireSandbox({
3385
- sandboxProvider,
3386
- sessionId,
3387
- isResume: isResumedSession,
3388
- bootstrapPlan: sandboxBootstrapPlan,
3389
- abortSignal
3390
- });
3391
- const leased = applyPortLease({
3392
- provider: sandboxProvider,
3393
- sandboxSession: acquiredSandboxSession,
3394
- sessionId
3395
- });
3396
- const sandboxSession = leased.sandboxSession;
3397
- const leasedBridgePort = leased.port;
3398
- const sessionWorkDir = resolveSessionWorkDir({
3399
- defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3400
- harnessId: harness.harnessId,
3401
- sessionId,
3402
- workDir: sandboxBootstrapPlan.workDir
3403
- });
3404
- try {
3405
- if (!isResumedSession && sandboxBootstrapPlan.recipe != null && sandboxBootstrapPlan.recipeIdentity != null) {
3406
- await applyBootstrapRecipe({
3407
- session: sandboxSession.restricted(),
3408
- recipe: sandboxBootstrapPlan.recipe,
3409
- identity: sandboxBootstrapPlan.recipeIdentity,
3410
- defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3346
+ let sandboxSession;
3347
+ let sessionWorkDir;
3348
+ if (providedSandboxSession != null) {
3349
+ sandboxSession = providedSandboxSession;
3350
+ sessionWorkDir = resolveSessionWorkDir({
3351
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3352
+ harnessId: harness.harnessId,
3353
+ sessionId,
3354
+ workDir: this.sandboxConfig.workDir
3355
+ });
3356
+ const recipe = await ((_b4 = harness.getBootstrap) == null ? void 0 : _b4.call(harness, { abortSignal }));
3357
+ if (recipe != null) {
3358
+ const recipeIdentity = await hashHarnessBootstrap(recipe);
3359
+ try {
3360
+ await applyBootstrapRecipe({
3361
+ session: sandboxSession.restricted(),
3362
+ recipe,
3363
+ identity: recipeIdentity,
3364
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3365
+ abortSignal
3366
+ });
3367
+ } catch (err) {
3368
+ await cleanupAfterStartFailure({
3369
+ sandboxSession,
3370
+ ownsSandboxLifecycle
3371
+ });
3372
+ throw err;
3373
+ }
3374
+ }
3375
+ } else {
3376
+ if (sandboxProvider == null) {
3377
+ throw new Error(
3378
+ "HarnessAgent.createSession: configure `sandbox` on HarnessAgent or pass `sandboxSession` to createSession()."
3379
+ );
3380
+ }
3381
+ if (isResumedSession) {
3382
+ if (sandboxProvider.resumeSession == null) {
3383
+ throw new HarnessCapabilityUnsupportedError({
3384
+ message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
3385
+ harnessId: harness.harnessId
3386
+ });
3387
+ }
3388
+ sandboxSession = await sandboxProvider.resumeSession({
3389
+ sessionId,
3411
3390
  abortSignal
3412
3391
  });
3392
+ sessionWorkDir = resolveSessionWorkDir({
3393
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3394
+ harnessId: harness.harnessId,
3395
+ sessionId,
3396
+ workDir: this.sandboxConfig.workDir
3397
+ });
3398
+ } else {
3399
+ let recipe;
3400
+ if (harness.getBootstrap != null) {
3401
+ recipe = await harness.getBootstrap({ abortSignal });
3402
+ }
3403
+ const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
3404
+ recipe,
3405
+ settings: this.sandboxConfig
3406
+ });
3407
+ sandboxSession = await sandboxProvider.createSession({
3408
+ sessionId,
3409
+ abortSignal,
3410
+ identity: sandboxBootstrapPlan.identity,
3411
+ onFirstCreate: sandboxBootstrapPlan.onFirstCreate
3412
+ });
3413
+ sessionWorkDir = resolveSessionWorkDir({
3414
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3415
+ harnessId: harness.harnessId,
3416
+ sessionId,
3417
+ workDir: sandboxBootstrapPlan.workDir
3418
+ });
3419
+ if (sandboxBootstrapPlan.recipe != null && sandboxBootstrapPlan.recipeIdentity != null) {
3420
+ try {
3421
+ await applyBootstrapRecipe({
3422
+ session: sandboxSession.restricted(),
3423
+ recipe: sandboxBootstrapPlan.recipe,
3424
+ identity: sandboxBootstrapPlan.recipeIdentity,
3425
+ defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3426
+ abortSignal
3427
+ });
3428
+ } catch (err) {
3429
+ await cleanupAfterStartFailure({
3430
+ sandboxSession,
3431
+ ownsSandboxLifecycle
3432
+ });
3433
+ throw err;
3434
+ }
3435
+ }
3413
3436
  }
3437
+ }
3438
+ try {
3414
3439
  await ensureSandboxDirectory({
3415
3440
  session: sandboxSession,
3416
3441
  workDir: sessionWorkDir,
@@ -3425,10 +3450,8 @@ var HarnessAgent = class {
3425
3450
  }
3426
3451
  } catch (err) {
3427
3452
  await cleanupAfterStartFailure({
3428
- sandboxProvider,
3429
3453
  sandboxSession,
3430
- sessionId,
3431
- leasedBridgePort
3454
+ ownsSandboxLifecycle
3432
3455
  });
3433
3456
  throw err;
3434
3457
  }
@@ -3453,8 +3476,7 @@ var HarnessAgent = class {
3453
3476
  harness,
3454
3477
  underlyingSession,
3455
3478
  sandboxSession,
3456
- sandboxProvider,
3457
- leasedBridgePort,
3479
+ ownsSandboxLifecycle,
3458
3480
  sessionWorkDir,
3459
3481
  toolApproval: this.settings.toolApproval,
3460
3482
  pendingToolApprovals: effectiveContinueFrom == null ? void 0 : effectiveContinueFrom.pendingToolApprovals,
@@ -3463,10 +3485,8 @@ var HarnessAgent = class {
3463
3485
  });
3464
3486
  } catch (error) {
3465
3487
  await cleanupAfterStartFailure({
3466
- sandboxProvider,
3467
3488
  sandboxSession,
3468
- sessionId,
3469
- leasedBridgePort
3489
+ ownsSandboxLifecycle
3470
3490
  });
3471
3491
  throw error;
3472
3492
  }
@@ -3580,27 +3600,6 @@ var HarnessAgent = class {
3580
3600
  stopConditions: this.stopConditions
3581
3601
  });
3582
3602
  }
3583
- async _acquireSandbox(input) {
3584
- const { sandboxProvider } = input;
3585
- if (input.isResume) {
3586
- if (sandboxProvider.resumeSession == null) {
3587
- throw new HarnessCapabilityUnsupportedError({
3588
- message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
3589
- harnessId: this.settings.harness.harnessId
3590
- });
3591
- }
3592
- return sandboxProvider.resumeSession({
3593
- sessionId: input.sessionId,
3594
- abortSignal: input.abortSignal
3595
- });
3596
- }
3597
- return sandboxProvider.createSession({
3598
- sessionId: input.sessionId,
3599
- abortSignal: input.abortSignal,
3600
- identity: input.bootstrapPlan.identity,
3601
- onFirstCreate: input.bootstrapPlan.onFirstCreate
3602
- });
3603
- }
3604
3603
  /*
3605
3604
  * Reduce AI SDK input to the single user message the harness should run
3606
3605
  * for this turn. The harness session owns prior-turn state (system
@@ -3767,48 +3766,22 @@ function resolveSandboxConfig(settings) {
3767
3766
  ...((_a4 = settings.sandboxConfig) == null ? void 0 : _a4.onSession) == null && settings.onSandboxSession != null ? { onSession: settings.onSandboxSession } : {}
3768
3767
  };
3769
3768
  }
3770
- function applyPortLease(input) {
3771
- const pool = input.provider.bridgePorts;
3772
- if (pool == null || pool.length === 0) {
3773
- return { sandboxSession: input.sandboxSession, port: void 0 };
3774
- }
3775
- const port = acquireBridgePort({
3776
- poolKey: input.provider,
3777
- pool,
3778
- sessionId: input.sessionId
3779
- });
3780
- return {
3781
- sandboxSession: narrowNetworkSessionPorts(input.sandboxSession, port),
3782
- port
3783
- };
3784
- }
3785
- function narrowNetworkSessionPorts(sandboxSession, leasedPort) {
3786
- return Object.create(sandboxSession, {
3787
- ports: {
3788
- value: [leasedPort],
3789
- enumerable: true
3790
- }
3791
- });
3792
- }
3793
3769
  async function cleanupAfterStartFailure(input) {
3770
+ if (!input.ownsSandboxLifecycle) return;
3794
3771
  await Promise.resolve(input.sandboxSession.stop()).catch(() => {
3795
3772
  });
3796
- if (input.leasedBridgePort != null) {
3797
- releaseBridgePort({
3798
- poolKey: input.sandboxProvider,
3799
- sessionId: input.sessionId
3800
- });
3801
- }
3802
3773
  }
3803
3774
 
3804
3775
  // src/agent/prepare-harness-sandbox-template.ts
3805
3776
  async function prepareHarnessSandboxTemplate(options) {
3806
- var _a4, _b4, _c;
3777
+ var _a4;
3807
3778
  const sandboxConfig = (_a4 = options.sandboxConfig) != null ? _a4 : {};
3808
3779
  validateSandboxBootstrapSettings(sandboxConfig);
3809
- const recipe = await ((_c = (_b4 = options.harness).getBootstrap) == null ? void 0 : _c.call(_b4, {
3810
- abortSignal: options.abortSignal
3811
- }));
3780
+ const { harness, sandboxProvider, abortSignal } = options;
3781
+ let recipe;
3782
+ if (harness.getBootstrap != null) {
3783
+ recipe = await harness.getBootstrap({ abortSignal });
3784
+ }
3812
3785
  const bootstrapPlan = await createSandboxBootstrapPlan({
3813
3786
  recipe,
3814
3787
  settings: sandboxConfig
@@ -3816,8 +3789,8 @@ async function prepareHarnessSandboxTemplate(options) {
3816
3789
  if (bootstrapPlan.identity == null || bootstrapPlan.onFirstCreate == null) {
3817
3790
  return;
3818
3791
  }
3819
- const sandboxSession = await options.sandboxProvider.createSession({
3820
- abortSignal: options.abortSignal,
3792
+ const sandboxSession = await sandboxProvider.createSession({
3793
+ abortSignal,
3821
3794
  identity: bootstrapPlan.identity,
3822
3795
  onFirstCreate: bootstrapPlan.onFirstCreate
3823
3796
  });
@@ -3828,7 +3801,7 @@ async function prepareHarnessSandboxTemplate(options) {
3828
3801
  recipe: bootstrapPlan.recipe,
3829
3802
  identity: bootstrapPlan.recipeIdentity,
3830
3803
  defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3831
- abortSignal: options.abortSignal
3804
+ abortSignal
3832
3805
  });
3833
3806
  }
3834
3807
  } finally {