@ai-sdk/harness 1.0.71 → 1.0.72

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,16 @@
1
1
  # @ai-sdk/harness
2
2
 
3
+ ## 1.0.72
4
+
5
+ ### Patch Changes
6
+
7
+ - 69bb613: feat(harness): support request transformations in network sandbox abstraction and use it to apply credential brokering when available
8
+ - 52bc889: feat(harness): add `getPortEndpoint()` as more comprehensive replacement to `getPortUrl()` (now deprecated) in `HarnessV1NetworkSandboxSession`
9
+ - 4cd4989: chore(harness): decouple bridge based harness bootstrap recipe logic from dynamic params and enforce unique harnesses in `prepareSandboxForHarness()` helper
10
+ - Updated dependencies [0782259]
11
+ - Updated dependencies [2fd1214]
12
+ - ai@7.0.66
13
+
3
14
  ## 1.0.71
4
15
 
5
16
  ### Patch Changes
@@ -117,6 +117,14 @@ interface HarnessV1Bootstrap {
117
117
  readonly commands: ReadonlyArray<HarnessV1BootstrapCommand>;
118
118
  }
119
119
 
120
+ /**
121
+ * Connection details for a sandbox-exposed port. Headers are scoped to the
122
+ * returned URL and must be included when opening the connection.
123
+ */
124
+ type HarnessV1PortEndpoint = {
125
+ readonly url: string;
126
+ readonly headers?: Readonly<Record<string, string>>;
127
+ };
120
128
  /**
121
129
  * Network sandbox session returned by `HarnessV1SandboxProvider.createSession()`. The
122
130
  * harness keeps this for the lifetime of a session. It is itself a
@@ -125,8 +133,8 @@ interface HarnessV1Bootstrap {
125
133
  *
126
134
  * Code that should only touch the filesystem and spawn processes receives the
127
135
  * reduced view from {@link HarnessV1NetworkSandboxSession.restricted}, never the
128
- * network sandbox session itself — so it cannot stop the sandbox or change its
129
- * network policy.
136
+ * network sandbox session itself — so it cannot stop the sandbox, change
137
+ * network access, or transform requests.
130
138
  */
131
139
  interface HarnessV1NetworkSandboxSession extends Experimental_SandboxSession {
132
140
  /**
@@ -151,12 +159,21 @@ interface HarnessV1NetworkSandboxSession extends Experimental_SandboxSession {
151
159
  * not bake a provider-specific base into their own paths.
152
160
  */
153
161
  readonly defaultWorkingDirectory: string;
154
- /** Ports the sandbox exposes; resolvable to public URLs via `getPortUrl`. */
162
+ /** Ports the sandbox exposes; resolvable via `getPortEndpoint`. */
155
163
  readonly ports: ReadonlyArray<number>;
156
164
  /**
157
- * Resolve a publicly-reachable URL for a sandbox-exposed port. Bridge-backed
165
+ * Resolve the connection details for a sandbox-exposed port. Bridge-backed
158
166
  * adapters call this to open their WebSocket to the in-sandbox bridge.
159
167
  */
168
+ readonly getPortEndpoint: (options: {
169
+ port: number;
170
+ protocol?: 'http' | 'https' | 'ws';
171
+ }) => PromiseLike<HarnessV1PortEndpoint>;
172
+ /**
173
+ * Resolve a publicly-reachable URL for a sandbox-exposed port.
174
+ *
175
+ * @deprecated Use `getPortEndpoint` instead.
176
+ */
160
177
  readonly getPortUrl: (options: {
161
178
  port: number;
162
179
  protocol?: 'http' | 'https' | 'ws';
@@ -176,6 +193,23 @@ interface HarnessV1NetworkSandboxSession extends Experimental_SandboxSession {
176
193
  * missing implementation is a no-op.
177
194
  */
178
195
  readonly setNetworkPolicy?: (policy: HarnessV1NetworkPolicy) => PromiseLike<void>;
196
+ /**
197
+ * Replace the sandbox's outbound request-transformation rules. Optional —
198
+ * implementations expose this only when credentials can be injected outside
199
+ * the sandbox security boundary. Calling this method assumes authority over
200
+ * the complete transformation set; harness adapters should normally use
201
+ * `addRequestTransformations` instead. Adapters may preserve legacy
202
+ * credential-forwarding behavior when additive request transformations are
203
+ * unavailable.
204
+ */
205
+ readonly setRequestTransformations?: (transformations: ReadonlyArray<HarnessV1RequestTransformation>) => PromiseLike<void>;
206
+ /**
207
+ * Add outbound request-transformation rules without replacing rules already
208
+ * managed by the sandbox session. Optional for the same reason as
209
+ * `setRequestTransformations`. Harness adapters should use this additive
210
+ * capability unless they explicitly own the complete transformation set.
211
+ */
212
+ readonly addRequestTransformations?: (transformations: ReadonlyArray<HarnessV1RequestTransformation>) => PromiseLike<void>;
179
213
  /**
180
214
  * Replace the set of ports exposed by the sandbox. Full-replacement
181
215
  * semantics: ports omitted from the array are deregistered. Optional —
@@ -192,7 +226,8 @@ interface HarnessV1NetworkSandboxSession extends Experimental_SandboxSession {
192
226
  *
193
227
  * The returned object points at exactly the same underlying sandbox
194
228
  * resource as the network sandbox session it was produced from; it is only a
195
- * narrower surface over the same resource, not a separate sandbox.
229
+ * narrower surface over the same resource, not a separate sandbox. In
230
+ * particular, it cannot mutate network access or request transformations.
196
231
  */
197
232
  readonly restricted: () => Experimental_SandboxSession;
198
233
  }
@@ -226,6 +261,47 @@ type HarnessV1NetworkPolicy = {
226
261
  allowedCIDRs: ReadonlyArray<string>;
227
262
  deniedCIDRs?: ReadonlyArray<string>;
228
263
  };
264
+ type HarnessV1RequestTransformationPathMatcher = {
265
+ exact: string;
266
+ } | {
267
+ startsWith: string;
268
+ } | {
269
+ regex: string;
270
+ };
271
+ type HarnessV1RequestTransformationKeyValuePartMatcher = {
272
+ exact: string;
273
+ } | {
274
+ startsWith: string;
275
+ } | {
276
+ regex: string;
277
+ };
278
+ type HarnessV1RequestTransformationKeyValueMatcher = {
279
+ readonly key?: HarnessV1RequestTransformationKeyValuePartMatcher;
280
+ readonly value?: HarnessV1RequestTransformationKeyValuePartMatcher;
281
+ };
282
+ /**
283
+ * Outbound HTTPS request transformation applied outside the sandbox security
284
+ * boundary. The host is part of the match so each rule is self-contained and
285
+ * several rules, including several for the same host, can be installed at
286
+ * once.
287
+ *
288
+ * Credential values belong in `transform.headers`, while the sandbox process
289
+ * receives only a non-secret placeholder. Implementations must overwrite
290
+ * matching request headers after the request leaves the sandbox rather than
291
+ * making transformed values available inside it.
292
+ */
293
+ type HarnessV1RequestTransformation = {
294
+ readonly match: {
295
+ readonly host: string;
296
+ readonly path?: HarnessV1RequestTransformationPathMatcher;
297
+ readonly method?: ReadonlyArray<string>;
298
+ readonly queryString?: ReadonlyArray<HarnessV1RequestTransformationKeyValueMatcher>;
299
+ readonly headers?: ReadonlyArray<HarnessV1RequestTransformationKeyValueMatcher>;
300
+ };
301
+ readonly transform: {
302
+ readonly headers: Readonly<Record<string, string>>;
303
+ };
304
+ };
229
305
 
230
306
  /** Severity of a diagnostic, ordered most → least severe. */
231
307
  declare const harnessV1DebugLevelSchema: z.ZodEnum<{
@@ -664,8 +740,10 @@ type HarnessV1StartOptions = {
664
740
  * Network sandbox session the adapter operates against. It is owned and
665
741
  * lifecycled by `HarnessAgent`. Adapters call `restricted()` for the
666
742
  * tool-safe filesystem/exec/spawn surface, and use the infra methods
667
- * (`getPortUrl`, `ports`, `setNetworkPolicy`) for bridge wiring. Adapters
668
- * must not call `stop()` themselves; the agent does that during cleanup.
743
+ * (`getPortEndpoint`, `ports`, `setNetworkPolicy`,
744
+ * `setRequestTransformations`, `addRequestTransformations`) for bridge
745
+ * wiring. Adapters must not call `stop()` themselves; the agent does that
746
+ * during cleanup.
669
747
  */
670
748
  readonly sandboxSession: HarnessV1NetworkSandboxSession;
671
749
  /**
@@ -1606,6 +1684,9 @@ type PrepareSandboxForHarnessResult = {
1606
1684
  * When a later `HarnessAgent` session uses a sandbox created from the persisted
1607
1685
  * artifact, the adapter recomputes the same recipe identity and the existing
1608
1686
  * bootstrap marker makes the bootstrap logic a no-op.
1687
+ *
1688
+ * Repeated harness IDs are prepared once. When multiple adapters use the same
1689
+ * ID, the last adapter in `harnesses` is used.
1609
1690
  */
1610
1691
  declare function prepareSandboxForHarness(options: {
1611
1692
  readonly session: Experimental_SandboxSession;
@@ -1633,8 +1714,8 @@ declare const symbol$1: unique symbol;
1633
1714
  /**
1634
1715
  * Thrown when a caller asks the harness to do something the adapter (or the
1635
1716
  * supplied sandbox) does not support, e.g. requesting manual compaction from
1636
- * an adapter that only auto-compacts, or invoking `getPortUrl` on a sandbox
1637
- * that does not expose one.
1717
+ * an adapter that only auto-compacts, or invoking `getPortEndpoint` on a
1718
+ * sandbox that does not expose one.
1638
1719
  *
1639
1720
  * The caller supplies the full human-readable message. Optional `harnessId`
1640
1721
  * is recorded as structured context for tooling.
@@ -3691,10 +3691,11 @@ async function prepareSandboxForHarness(options) {
3691
3691
  "prepareSandboxForHarness: at least one harness must be provided."
3692
3692
  );
3693
3693
  }
3694
- const harnesses = [...options.harnesses].sort(
3695
- (a, b) => a.harnessId.localeCompare(b.harnessId)
3696
- );
3697
- assertUniqueHarnessIds(harnesses);
3694
+ const harnesses = [
3695
+ ...new Map(
3696
+ options.harnesses.map((harness) => [harness.harnessId, harness])
3697
+ ).values()
3698
+ ].sort((a, b) => a.harnessId.localeCompare(b.harnessId));
3698
3699
  const workDir = sandboxConfig.workDir == null ? void 0 : normalizeSandboxWorkDir(sandboxConfig.workDir);
3699
3700
  const recipeIdentities = {};
3700
3701
  const skippedHarnessIds = [];
@@ -3741,17 +3742,6 @@ async function prepareSandboxForHarness(options) {
3741
3742
  skippedHarnessIds
3742
3743
  };
3743
3744
  }
3744
- function assertUniqueHarnessIds(harnesses) {
3745
- const seen = /* @__PURE__ */ new Set();
3746
- for (const harness of harnesses) {
3747
- if (seen.has(harness.harnessId)) {
3748
- throw new Error(
3749
- `prepareSandboxForHarness: duplicate harness id "${harness.harnessId}".`
3750
- );
3751
- }
3752
- seen.add(harness.harnessId);
3753
- }
3754
- }
3755
3745
  async function resolvePreparedSandboxIdentity({
3756
3746
  recipeIdentities,
3757
3747
  bootstrapHash,