@ai-sdk/harness 1.0.75 → 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) {
@@ -2352,22 +2325,21 @@ var HarnessAgentSession = class {
2352
2325
  this.sessionState = "active";
2353
2326
  this.turnSequence = 0;
2354
2327
  this.activeTurnSequence = 0;
2355
- var _a4, _b4, _c;
2328
+ var _a4, _b4, _c, _d;
2356
2329
  this.sessionId = options.sessionId;
2357
2330
  this.harness = options.harness;
2358
2331
  this.underlyingSession = options.underlyingSession;
2359
2332
  this.sandboxSession = options.sandboxSession;
2360
- this.sandboxProvider = options.sandboxProvider;
2361
- this.leasedBridgePort = options.leasedBridgePort;
2333
+ this.ownsSandboxLifecycle = (_a4 = options.ownsSandboxLifecycle) != null ? _a4 : true;
2362
2334
  this.sessionWorkDir = options.sessionWorkDir;
2363
2335
  this.toolApproval = options.toolApproval;
2364
- for (const approval of (_a4 = options.pendingToolApprovals) != null ? _a4 : []) {
2336
+ for (const approval of (_b4 = options.pendingToolApprovals) != null ? _b4 : []) {
2365
2337
  this.pendingToolApprovals.set(approval.approvalId, approval);
2366
2338
  }
2367
- for (const pendingResult of (_b4 = options.pendingToolResults) != null ? _b4 : []) {
2339
+ for (const pendingResult of (_c = options.pendingToolResults) != null ? _c : []) {
2368
2340
  this.pendingToolResults.set(pendingResult.toolCallId, pendingResult);
2369
2341
  }
2370
- 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";
2371
2343
  this.isResume = options.underlyingSession.isResume;
2372
2344
  }
2373
2345
  /**
@@ -2547,14 +2519,12 @@ var HarnessAgentSession = class {
2547
2519
  });
2548
2520
  return validated;
2549
2521
  } finally {
2550
- this.endLocalHandle({
2551
- sessionState: "detached",
2552
- releasePortLease: false
2553
- });
2522
+ this.endLocalHandle({ sessionState: "detached" });
2554
2523
  }
2555
2524
  }
2556
2525
  /**
2557
- * 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.
2558
2528
  * Returns the resume state for a future
2559
2529
  * `agent.createSession({ sessionId, resumeFrom })` call.
2560
2530
  */
@@ -2580,28 +2550,28 @@ var HarnessAgentSession = class {
2580
2550
  });
2581
2551
  return validated;
2582
2552
  } finally {
2583
- this.endLocalHandle({
2584
- sessionState: "stopped",
2585
- releasePortLease: true
2586
- });
2587
- await Promise.resolve(sandboxSession.stop()).catch(() => {
2588
- });
2553
+ this.endLocalHandle({ sessionState: "stopped" });
2554
+ if (this.ownsSandboxLifecycle) {
2555
+ await Promise.resolve(sandboxSession.stop()).catch(() => {
2556
+ });
2557
+ }
2589
2558
  }
2590
2559
  }
2591
2560
  /**
2592
- * Stop the runtime and discard resumability. The sandbox is destroyed when
2593
- * 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.
2594
2563
  */
2595
2564
  async destroy() {
2596
2565
  var _a4, _b4;
2597
2566
  if (this.sessionState !== "active") return;
2598
2567
  const session = this.underlyingSession;
2599
2568
  const sandboxSession = this.getSandboxSession();
2600
- this.endLocalHandle({ sessionState: "destroyed", releasePortLease: true });
2569
+ this.endLocalHandle({ sessionState: "destroyed" });
2601
2570
  if (session != null) {
2602
2571
  await Promise.resolve(session.doDestroy()).catch(() => {
2603
2572
  });
2604
2573
  }
2574
+ if (!this.ownsSandboxLifecycle) return;
2605
2575
  await Promise.resolve(
2606
2576
  (_b4 = (_a4 = sandboxSession.destroy) == null ? void 0 : _a4.call(sandboxSession)) != null ? _b4 : sandboxSession.stop()
2607
2577
  ).catch(() => {
@@ -2616,9 +2586,8 @@ var HarnessAgentSession = class {
2616
2586
  *
2617
2587
  * After this call the session is detached. This in-process handle no
2618
2588
  * longer drives turns; a future slice creates a fresh session from the
2619
- * returned state. The sandbox is **not** stopped and no port lease is
2620
- * released, because bridge-backed adapters may still have a live bridge on
2621
- * that port.
2589
+ * returned state. The sandbox is **not** stopped because bridge-backed
2590
+ * adapters may still have a live bridge.
2622
2591
  */
2623
2592
  async suspendTurn() {
2624
2593
  if (this.sessionState !== "active" || this.underlyingSession == null) {
@@ -2635,10 +2604,7 @@ var HarnessAgentSession = class {
2635
2604
  try {
2636
2605
  return await this.suspendCurrentTurn({ session });
2637
2606
  } finally {
2638
- this.endLocalHandle({
2639
- sessionState: "detached",
2640
- releasePortLease: false
2641
- });
2607
+ this.endLocalHandle({ sessionState: "detached" });
2642
2608
  }
2643
2609
  }
2644
2610
  getPendingToolApprovals() {
@@ -2748,17 +2714,6 @@ var HarnessAgentSession = class {
2748
2714
  this.sessionState = options.sessionState;
2749
2715
  this.underlyingSession = void 0;
2750
2716
  this.sandboxSession = void 0;
2751
- if (options.releasePortLease) {
2752
- this.releasePortLease();
2753
- }
2754
- }
2755
- releasePortLease() {
2756
- if (this.leasedBridgePort == null) return;
2757
- releaseBridgePort({
2758
- poolKey: this.sandboxProvider,
2759
- sessionId: this.sessionId
2760
- });
2761
- this.leasedBridgePort = void 0;
2762
2717
  }
2763
2718
  requireReusableSession() {
2764
2719
  if (this.sessionState !== "active" || this.underlyingSession == null) {
@@ -3356,13 +3311,15 @@ var HarnessAgent = class {
3356
3311
  * `session.destroy()`.
3357
3312
  */
3358
3313
  async createSession(options) {
3359
- var _a4;
3314
+ var _a4, _b4;
3360
3315
  const sessionId = (_a4 = options == null ? void 0 : options.sessionId) != null ? _a4 : generateId5();
3361
3316
  const resumeFrom = options == null ? void 0 : options.resumeFrom;
3362
3317
  const continueFrom = options == null ? void 0 : options.continueFrom;
3318
+ const providedSandboxSession = options == null ? void 0 : options.sandboxSession;
3363
3319
  const abortSignal = options == null ? void 0 : options.abortSignal;
3364
3320
  const harness = this.settings.harness;
3365
3321
  const sandboxProvider = this.settings.sandbox;
3322
+ const ownsSandboxLifecycle = providedSandboxSession == null;
3366
3323
  if (resumeFrom != null && continueFrom != null) {
3367
3324
  throw new Error(
3368
3325
  "HarnessAgent.createSession: pass either `resumeFrom` or `continueFrom`, not both."
@@ -3386,44 +3343,99 @@ var HarnessAgent = class {
3386
3343
  }
3387
3344
  const effectiveContinueFrom = validatedContinueFrom != null ? validatedContinueFrom : validatedResumeFrom == null ? void 0 : validatedResumeFrom.continueFrom;
3388
3345
  const isResumedSession = validatedResumeFrom != null || effectiveContinueFrom != null;
3389
- let recipe;
3390
- if (harness.getBootstrap != null) {
3391
- recipe = await harness.getBootstrap({ abortSignal });
3392
- }
3393
- const sandboxBootstrapPlan = await createSandboxBootstrapPlan({
3394
- recipe,
3395
- settings: this.sandboxConfig
3396
- });
3397
- const acquiredSandboxSession = await this._acquireSandbox({
3398
- sandboxProvider,
3399
- sessionId,
3400
- isResume: isResumedSession,
3401
- bootstrapPlan: sandboxBootstrapPlan,
3402
- abortSignal
3403
- });
3404
- const leased = applyPortLease({
3405
- provider: sandboxProvider,
3406
- sandboxSession: acquiredSandboxSession,
3407
- sessionId
3408
- });
3409
- const sandboxSession = leased.sandboxSession;
3410
- const leasedBridgePort = leased.port;
3411
- const sessionWorkDir = resolveSessionWorkDir({
3412
- defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3413
- harnessId: harness.harnessId,
3414
- sessionId,
3415
- workDir: sandboxBootstrapPlan.workDir
3416
- });
3417
- try {
3418
- if (!isResumedSession && sandboxBootstrapPlan.recipe != null && sandboxBootstrapPlan.recipeIdentity != null) {
3419
- await applyBootstrapRecipe({
3420
- session: sandboxSession.restricted(),
3421
- recipe: sandboxBootstrapPlan.recipe,
3422
- identity: sandboxBootstrapPlan.recipeIdentity,
3423
- 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,
3424
3390
  abortSignal
3425
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
+ }
3426
3436
  }
3437
+ }
3438
+ try {
3427
3439
  await ensureSandboxDirectory({
3428
3440
  session: sandboxSession,
3429
3441
  workDir: sessionWorkDir,
@@ -3438,10 +3450,8 @@ var HarnessAgent = class {
3438
3450
  }
3439
3451
  } catch (err) {
3440
3452
  await cleanupAfterStartFailure({
3441
- sandboxProvider,
3442
3453
  sandboxSession,
3443
- sessionId,
3444
- leasedBridgePort
3454
+ ownsSandboxLifecycle
3445
3455
  });
3446
3456
  throw err;
3447
3457
  }
@@ -3466,8 +3476,7 @@ var HarnessAgent = class {
3466
3476
  harness,
3467
3477
  underlyingSession,
3468
3478
  sandboxSession,
3469
- sandboxProvider,
3470
- leasedBridgePort,
3479
+ ownsSandboxLifecycle,
3471
3480
  sessionWorkDir,
3472
3481
  toolApproval: this.settings.toolApproval,
3473
3482
  pendingToolApprovals: effectiveContinueFrom == null ? void 0 : effectiveContinueFrom.pendingToolApprovals,
@@ -3476,10 +3485,8 @@ var HarnessAgent = class {
3476
3485
  });
3477
3486
  } catch (error) {
3478
3487
  await cleanupAfterStartFailure({
3479
- sandboxProvider,
3480
3488
  sandboxSession,
3481
- sessionId,
3482
- leasedBridgePort
3489
+ ownsSandboxLifecycle
3483
3490
  });
3484
3491
  throw error;
3485
3492
  }
@@ -3593,27 +3600,6 @@ var HarnessAgent = class {
3593
3600
  stopConditions: this.stopConditions
3594
3601
  });
3595
3602
  }
3596
- async _acquireSandbox(input) {
3597
- const { sandboxProvider } = input;
3598
- if (input.isResume) {
3599
- if (sandboxProvider.resumeSession == null) {
3600
- throw new HarnessCapabilityUnsupportedError({
3601
- message: `Sandbox provider '${sandboxProvider.providerId}' does not support resume.`,
3602
- harnessId: this.settings.harness.harnessId
3603
- });
3604
- }
3605
- return sandboxProvider.resumeSession({
3606
- sessionId: input.sessionId,
3607
- abortSignal: input.abortSignal
3608
- });
3609
- }
3610
- return sandboxProvider.createSession({
3611
- sessionId: input.sessionId,
3612
- abortSignal: input.abortSignal,
3613
- identity: input.bootstrapPlan.identity,
3614
- onFirstCreate: input.bootstrapPlan.onFirstCreate
3615
- });
3616
- }
3617
3603
  /*
3618
3604
  * Reduce AI SDK input to the single user message the harness should run
3619
3605
  * for this turn. The harness session owns prior-turn state (system
@@ -3780,48 +3766,22 @@ function resolveSandboxConfig(settings) {
3780
3766
  ...((_a4 = settings.sandboxConfig) == null ? void 0 : _a4.onSession) == null && settings.onSandboxSession != null ? { onSession: settings.onSandboxSession } : {}
3781
3767
  };
3782
3768
  }
3783
- function applyPortLease(input) {
3784
- const pool = input.provider.bridgePorts;
3785
- if (pool == null || pool.length === 0) {
3786
- return { sandboxSession: input.sandboxSession, port: void 0 };
3787
- }
3788
- const port = acquireBridgePort({
3789
- poolKey: input.provider,
3790
- pool,
3791
- sessionId: input.sessionId
3792
- });
3793
- return {
3794
- sandboxSession: narrowNetworkSessionPorts(input.sandboxSession, port),
3795
- port
3796
- };
3797
- }
3798
- function narrowNetworkSessionPorts(sandboxSession, leasedPort) {
3799
- return Object.create(sandboxSession, {
3800
- ports: {
3801
- value: [leasedPort],
3802
- enumerable: true
3803
- }
3804
- });
3805
- }
3806
3769
  async function cleanupAfterStartFailure(input) {
3770
+ if (!input.ownsSandboxLifecycle) return;
3807
3771
  await Promise.resolve(input.sandboxSession.stop()).catch(() => {
3808
3772
  });
3809
- if (input.leasedBridgePort != null) {
3810
- releaseBridgePort({
3811
- poolKey: input.sandboxProvider,
3812
- sessionId: input.sessionId
3813
- });
3814
- }
3815
3773
  }
3816
3774
 
3817
3775
  // src/agent/prepare-harness-sandbox-template.ts
3818
3776
  async function prepareHarnessSandboxTemplate(options) {
3819
- var _a4, _b4, _c;
3777
+ var _a4;
3820
3778
  const sandboxConfig = (_a4 = options.sandboxConfig) != null ? _a4 : {};
3821
3779
  validateSandboxBootstrapSettings(sandboxConfig);
3822
- const recipe = await ((_c = (_b4 = options.harness).getBootstrap) == null ? void 0 : _c.call(_b4, {
3823
- abortSignal: options.abortSignal
3824
- }));
3780
+ const { harness, sandboxProvider, abortSignal } = options;
3781
+ let recipe;
3782
+ if (harness.getBootstrap != null) {
3783
+ recipe = await harness.getBootstrap({ abortSignal });
3784
+ }
3825
3785
  const bootstrapPlan = await createSandboxBootstrapPlan({
3826
3786
  recipe,
3827
3787
  settings: sandboxConfig
@@ -3829,8 +3789,8 @@ async function prepareHarnessSandboxTemplate(options) {
3829
3789
  if (bootstrapPlan.identity == null || bootstrapPlan.onFirstCreate == null) {
3830
3790
  return;
3831
3791
  }
3832
- const sandboxSession = await options.sandboxProvider.createSession({
3833
- abortSignal: options.abortSignal,
3792
+ const sandboxSession = await sandboxProvider.createSession({
3793
+ abortSignal,
3834
3794
  identity: bootstrapPlan.identity,
3835
3795
  onFirstCreate: bootstrapPlan.onFirstCreate
3836
3796
  });
@@ -3841,7 +3801,7 @@ async function prepareHarnessSandboxTemplate(options) {
3841
3801
  recipe: bootstrapPlan.recipe,
3842
3802
  identity: bootstrapPlan.recipeIdentity,
3843
3803
  defaultWorkingDirectory: sandboxSession.defaultWorkingDirectory,
3844
- abortSignal: options.abortSignal
3804
+ abortSignal
3845
3805
  });
3846
3806
  }
3847
3807
  } finally {