@ai-sdk/harness 1.0.1 → 1.0.3

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,17 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.3
4
+
5
+ ### Patch Changes
6
+
7
+ - 51d10a0: feat(harness): add `prepareSandboxForHarness` utility to prepare a caller-owned sandbox for one or more harnesses
8
+
9
+ ## 1.0.2
10
+
11
+ ### Patch Changes
12
+
13
+ - ai@7.0.2
14
+
3
15
  ## 1.0.1
4
16
 
5
17
  ### Patch Changes
package/README.md CHANGED
@@ -91,6 +91,18 @@ Use `session.detach()` to park a bridge-backed session for later attach, `sessio
91
91
  - Use `sandboxConfig.onBootstrap` for expensive sandbox setup that should be baked into a reusable snapshot, such as installing tools or cloning a large repository. Provide `sandboxConfig.bootstrapHash` with it and change that value whenever the bootstrap output should invalidate the cached snapshot.
92
92
  - Use `sandboxConfig.workDir` to set a stable working directory for the agent, relative to the sandbox's default working directory; otherwise regular sessions use the existing `<harnessId>-<sessionId>` directory. In that case, the `onBootstrap` callback receives the sandbox's default working directory.
93
93
 
94
+ Use `prepareHarnessSandboxTemplate()` to create or refresh the sandbox provider's
95
+ own reusable template for one harness before serving traffic. This is the
96
+ replacement for `prewarmHarness()`, which remains as a deprecated alias.
97
+
98
+ Use `prepareSandboxForHarness()` when you own an existing sandbox and want to
99
+ prepare it before creating your own snapshot. It applies the selected harness
100
+ bootstrap recipes and `sandboxConfig.onBootstrap`, returns the computed
101
+ preparation identity and per-harness recipe identities, and leaves snapshotting
102
+ or stopping the sandbox to your code. Later, create a sandbox from that snapshot
103
+ and pass the native sandbox object to `createVercelSandbox({ sandbox })` for the
104
+ `HarnessAgent`.
105
+
94
106
  ### Available harnesses
95
107
 
96
108
  See the [harness adapters documentation](https://ai-sdk.dev/v7/docs/ai-sdk-harnesses/harness-adapters).
package/agent/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { HarnessAgent } from '../src/agent/harness-agent';
2
2
  export type {
3
+ HarnessAgentSandboxConfig,
3
4
  HarnessAgentSettings,
4
5
  HarnessAgentToolApprovalConfiguration,
5
6
  } from '../src/agent/harness-agent-settings';
@@ -29,7 +30,14 @@ export {
29
30
  collectHarnessAgentToolApprovalContinuations,
30
31
  type HarnessAgentToolApprovalContinuation,
31
32
  } from '../src/agent/harness-agent-tool-approval-continuation';
32
- export { prewarmHarness } from '../src/agent/prewarm';
33
+ export {
34
+ prepareHarnessSandboxTemplate,
35
+ prewarmHarness,
36
+ } from '../src/agent/prewarm';
37
+ export {
38
+ prepareSandboxForHarness,
39
+ type PrepareSandboxForHarnessResult,
40
+ } from '../src/agent/prepare-sandbox-for-harness';
33
41
  export type {
34
42
  HarnessDebugConfig,
35
43
  HarnessDebugLevel,
@@ -1456,25 +1456,39 @@ declare class HarnessAgent<THarness extends HarnessAgentAdapter<any> = HarnessAg
1456
1456
 
1457
1457
  type SandboxBootstrapSettings = Omit<HarnessAgentSandboxConfig, 'onSession'>;
1458
1458
  /**
1459
- * Pre-build a harness's sandbox template without running an agent. Idempotent:
1460
- * if the template already exists (snapshot present, or marker on a non-snapshot
1459
+ * Prepare a harness's sandbox template without running an agent. Idempotent: if
1460
+ * the template already exists (snapshot present, or marker on a non-snapshot
1461
1461
  * provider), this resolves quickly.
1462
1462
  *
1463
1463
  * Use from a CI/deploy script to amortize the first-session cost so production
1464
1464
  * sessions always resume from snapshot. For adapters without a bootstrap
1465
1465
  * recipe (no `getBootstrap`) this is a no-op.
1466
1466
  *
1467
- * The temporary network sandbox session created during pre-warm is stopped
1467
+ * The temporary network sandbox session created during preparation is stopped
1468
1468
  * before the function resolves; the snapshot/template state persists in the
1469
1469
  * provider's native storage (for Vercel: as the `currentSnapshotId` of the
1470
1470
  * named template sandbox).
1471
1471
  */
1472
- declare function prewarmHarness(options: {
1472
+ declare function prepareHarnessSandboxTemplate(options: {
1473
1473
  readonly harness: HarnessAgentAdapter;
1474
1474
  readonly sandboxProvider: HarnessV1SandboxProvider;
1475
1475
  readonly sandboxConfig?: SandboxBootstrapSettings;
1476
1476
  readonly abortSignal?: AbortSignal;
1477
1477
  }): Promise<void>;
1478
+ /** @deprecated Use `prepareHarnessSandboxTemplate` instead. */
1479
+ declare const prewarmHarness: typeof prepareHarnessSandboxTemplate;
1480
+
1481
+ type PrepareSandboxForHarnessResult = {
1482
+ readonly identity?: string;
1483
+ readonly recipeIdentities: Record<string, string>;
1484
+ readonly skippedHarnessIds: ReadonlyArray<string>;
1485
+ };
1486
+ declare function prepareSandboxForHarness(options: {
1487
+ readonly session: Experimental_SandboxSession;
1488
+ readonly harnesses: ReadonlyArray<HarnessAgentAdapter>;
1489
+ readonly sandboxConfig?: HarnessAgentSandboxConfig;
1490
+ readonly abortSignal?: AbortSignal;
1491
+ }): Promise<PrepareSandboxForHarnessResult>;
1478
1492
 
1479
1493
  declare const symbol$1: unique symbol;
1480
1494
  /**
@@ -1551,4 +1565,4 @@ interface TraceTreeReporterOptions {
1551
1565
  }
1552
1566
  declare function createTraceTreeReporter(options?: TraceTreeReporterOptions): Telemetry;
1553
1567
 
1554
- export { type FileReporter, type FileReporterOptions, HarnessAgent, type HarnessAgentAdapter, type HarnessAgentAdapterSession, type HarnessAgentBuiltinTool, type HarnessAgentBuiltinToolName, type HarnessAgentBuiltinToolUseKind, type HarnessAgentBuiltinTools, type HarnessAgentContinueTurnOptions, type HarnessAgentContinueTurnState, type HarnessAgentLifecycleState, type HarnessAgentPendingToolApproval, type HarnessAgentPermissionMode, type HarnessAgentPrompt, type HarnessAgentPromptControl, type HarnessAgentPromptTurnOptions, type HarnessAgentResumeSessionState, HarnessAgentSession, type HarnessAgentSettings, type HarnessAgentSkill, type HarnessAgentStartOptions, type HarnessAgentStreamPart, type HarnessAgentToolApprovalConfiguration, type HarnessAgentToolApprovalContinuation, type HarnessAgentToolSpec, HarnessCapabilityUnsupportedError, type HarnessDebugConfig, type HarnessDebugLevel, type HarnessDiagnostic, type HarnessDiagnosticConsumer, HarnessError, type TraceTreeReporterOptions, collectHarnessAgentToolApprovalContinuations, createFileReporter, createTraceTreeReporter, prewarmHarness };
1568
+ export { type FileReporter, type FileReporterOptions, HarnessAgent, type HarnessAgentAdapter, type HarnessAgentAdapterSession, type HarnessAgentBuiltinTool, type HarnessAgentBuiltinToolName, type HarnessAgentBuiltinToolUseKind, type HarnessAgentBuiltinTools, type HarnessAgentContinueTurnOptions, type HarnessAgentContinueTurnState, type HarnessAgentLifecycleState, type HarnessAgentPendingToolApproval, type HarnessAgentPermissionMode, type HarnessAgentPrompt, type HarnessAgentPromptControl, type HarnessAgentPromptTurnOptions, type HarnessAgentResumeSessionState, type HarnessAgentSandboxConfig, HarnessAgentSession, type HarnessAgentSettings, type HarnessAgentSkill, type HarnessAgentStartOptions, type HarnessAgentStreamPart, type HarnessAgentToolApprovalConfiguration, type HarnessAgentToolApprovalContinuation, type HarnessAgentToolSpec, HarnessCapabilityUnsupportedError, type HarnessDebugConfig, type HarnessDebugLevel, type HarnessDiagnostic, type HarnessDiagnosticConsumer, HarnessError, type PrepareSandboxForHarnessResult, type TraceTreeReporterOptions, collectHarnessAgentToolApprovalContinuations, createFileReporter, createTraceTreeReporter, prepareHarnessSandboxTemplate, prepareSandboxForHarness, prewarmHarness };
@@ -2916,7 +2916,7 @@ async function cleanupAfterStartFailure(input) {
2916
2916
  }
2917
2917
 
2918
2918
  // src/agent/prewarm.ts
2919
- async function prewarmHarness(options) {
2919
+ async function prepareHarnessSandboxTemplate(options) {
2920
2920
  var _a3, _b3, _c;
2921
2921
  const sandboxConfig = (_a3 = options.sandboxConfig) != null ? _a3 : {};
2922
2922
  validateSandboxBootstrapSettings(sandboxConfig);
@@ -2951,6 +2951,109 @@ async function prewarmHarness(options) {
2951
2951
  });
2952
2952
  }
2953
2953
  }
2954
+ var prewarmHarness = prepareHarnessSandboxTemplate;
2955
+
2956
+ // src/agent/prepare-sandbox-for-harness.ts
2957
+ var PREPARED_SANDBOX_IDENTITY_VERSION = 1;
2958
+ async function prepareSandboxForHarness(options) {
2959
+ var _a3, _b3;
2960
+ const sandboxConfig = (_a3 = options.sandboxConfig) != null ? _a3 : {};
2961
+ validateSandboxBootstrapSettings(sandboxConfig);
2962
+ if (options.harnesses.length === 0) {
2963
+ throw new Error(
2964
+ "prepareSandboxForHarness: at least one harness must be provided."
2965
+ );
2966
+ }
2967
+ const harnesses = [...options.harnesses].sort(
2968
+ (a, b) => a.harnessId.localeCompare(b.harnessId)
2969
+ );
2970
+ assertUniqueHarnessIds(harnesses);
2971
+ const workDir = sandboxConfig.workDir == null ? void 0 : normalizeSandboxWorkDir(sandboxConfig.workDir);
2972
+ const recipeIdentities = {};
2973
+ const skippedHarnessIds = [];
2974
+ for (const harness of harnesses) {
2975
+ const recipe = await ((_b3 = harness.getBootstrap) == null ? void 0 : _b3.call(harness, {
2976
+ abortSignal: options.abortSignal
2977
+ }));
2978
+ if (recipe == null) {
2979
+ skippedHarnessIds.push(harness.harnessId);
2980
+ continue;
2981
+ }
2982
+ const recipeIdentity = await hashHarnessBootstrap(recipe);
2983
+ recipeIdentities[harness.harnessId] = recipeIdentity;
2984
+ await applyBootstrapRecipe(options.session, recipe, recipeIdentity, {
2985
+ abortSignal: options.abortSignal
2986
+ });
2987
+ }
2988
+ if (sandboxConfig.onBootstrap != null) {
2989
+ await runSandboxBootstrap({
2990
+ session: options.session,
2991
+ workDir,
2992
+ onBootstrap: sandboxConfig.onBootstrap,
2993
+ abortSignal: options.abortSignal
2994
+ });
2995
+ }
2996
+ const identity = await resolvePreparedSandboxIdentity({
2997
+ recipeIdentities,
2998
+ bootstrapHash: sandboxConfig.bootstrapHash,
2999
+ workDir
3000
+ });
3001
+ return {
3002
+ ...identity != null ? { identity } : {},
3003
+ recipeIdentities,
3004
+ skippedHarnessIds
3005
+ };
3006
+ }
3007
+ function assertUniqueHarnessIds(harnesses) {
3008
+ const seen = /* @__PURE__ */ new Set();
3009
+ for (const harness of harnesses) {
3010
+ if (seen.has(harness.harnessId)) {
3011
+ throw new Error(
3012
+ `prepareSandboxForHarness: duplicate harness id "${harness.harnessId}".`
3013
+ );
3014
+ }
3015
+ seen.add(harness.harnessId);
3016
+ }
3017
+ }
3018
+ async function resolvePreparedSandboxIdentity({
3019
+ recipeIdentities,
3020
+ bootstrapHash,
3021
+ workDir
3022
+ }) {
3023
+ const entries = Object.entries(recipeIdentities).sort(
3024
+ ([a], [b]) => a.localeCompare(b)
3025
+ );
3026
+ if (entries.length === 0 && bootstrapHash == null) {
3027
+ return void 0;
3028
+ }
3029
+ const encoder = new TextEncoder();
3030
+ const chunks = [];
3031
+ const pushString = (value) => {
3032
+ chunks.push(encoder.encode(value));
3033
+ chunks.push(encoder.encode("\0"));
3034
+ };
3035
+ pushString(String(PREPARED_SANDBOX_IDENTITY_VERSION));
3036
+ pushString(workDir != null ? workDir : "");
3037
+ pushString(bootstrapHash != null ? bootstrapHash : "");
3038
+ for (const [harnessId, identity] of entries) {
3039
+ pushString(harnessId);
3040
+ pushString(identity);
3041
+ }
3042
+ const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
3043
+ const buffer = new Uint8Array(totalLength);
3044
+ let offset = 0;
3045
+ for (const chunk of chunks) {
3046
+ buffer.set(chunk, offset);
3047
+ offset += chunk.length;
3048
+ }
3049
+ const digest = await crypto.subtle.digest("SHA-256", buffer);
3050
+ const bytes = new Uint8Array(digest);
3051
+ let hex = "";
3052
+ for (let i = 0; i < 8; i++) {
3053
+ hex += bytes[i].toString(16).padStart(2, "0");
3054
+ }
3055
+ return hex;
3056
+ }
2954
3057
 
2955
3058
  // src/agent/observability/file-reporter.ts
2956
3059
  import { appendFileSync, mkdirSync } from "fs";
@@ -3180,6 +3283,8 @@ export {
3180
3283
  collectHarnessAgentToolApprovalContinuations,
3181
3284
  createFileReporter,
3182
3285
  createTraceTreeReporter,
3286
+ prepareHarnessSandboxTemplate,
3287
+ prepareSandboxForHarness,
3183
3288
  prewarmHarness
3184
3289
  };
3185
3290
  //# sourceMappingURL=index.js.map