experimental-ash 0.43.0 → 0.44.0
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 +7 -0
- package/bin/ash.js +1 -0
- package/dist/docs/internals/mechanical-invariants.md +16 -0
- package/dist/docs/public/advanced/instrumentation.md +71 -21
- package/dist/docs/public/advanced/typescript-api.md +1 -1
- package/dist/docs/public/sandbox.md +38 -0
- package/dist/src/channel/compiled-channel.d.ts +4 -1
- package/dist/src/channel/compiled-channel.js +1 -1
- package/dist/src/channel/routes.d.ts +8 -10
- package/dist/src/compiled/.vendor-stamp.json +1 -1
- package/dist/src/compiled/@vercel/sandbox/index.d.ts +12 -19
- package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +161 -0
- package/dist/src/compiled/just-bash/index.d.ts +15 -2
- package/dist/src/compiled/just-bash/network/types.d.ts +155 -0
- package/dist/src/compiler/artifacts.d.ts +1 -0
- package/dist/src/compiler/artifacts.js +1 -1
- package/dist/src/compiler/channel-instrumentation-types.d.ts +8 -0
- package/dist/src/compiler/channel-instrumentation-types.js +2 -0
- package/dist/src/execution/sandbox/bindings/local.js +1 -1
- package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
- package/dist/src/execution/sandbox/session.d.ts +6 -1
- package/dist/src/execution/sandbox/session.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/packages/ash-scaffold/src/web-template.js +1 -0
- package/dist/src/public/channels/discord/discordChannel.d.ts +5 -2
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +6 -8
- package/dist/src/public/channels/teams/teamsChannel.d.ts +5 -2
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +5 -2
- package/dist/src/public/channels/twilio/twilioChannel.d.ts +6 -3
- package/dist/src/public/definitions/defineChannel.d.ts +6 -3
- package/dist/src/public/definitions/instrumentation.d.ts +1 -152
- package/dist/src/public/definitions/instrumentation.js +1 -1
- package/dist/src/public/instrumentation/index.d.ts +178 -1
- package/dist/src/public/instrumentation/index.js +1 -1
- package/dist/src/public/sandbox/index.d.ts +1 -0
- package/dist/src/runtime/resolve-channel.js +1 -1
- package/dist/src/shared/sandbox-network-policy.d.ts +23 -0
- package/dist/src/shared/sandbox-network-policy.js +1 -0
- package/dist/src/shared/sandbox-session.d.ts +15 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.44.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- ed89a1b: Generate channel instrumentation metadata typings from authored `agent/channels/*` files so `input.channel.metadata` narrows after checking `input.channel.kind` or calling `isChannel(input.channel, channel)`.
|
|
8
|
+
- 5faaf2b: Add `sandbox.setNetworkPolicy(policy)` to the live sandbox handle returned by `ctx.getSandbox()`, so any tool, hook, or channel event can apply a firewall network policy at run time — for cases where the policy must change _during_ a turn (e.g. brokering a credential resolved mid-turn). Configuring a policy known at session start in the backend factory or `onSession`'s `use()` remains valid and is often preferable; both accept the same `transform` shape for credential brokering, which injects headers at the firewall so secrets authenticate egress without entering the sandbox process. The local backend rejects `setNetworkPolicy` (just-bash applies its network policy only at creation and runs no binaries to govern). The `@vercel/sandbox` and `just-bash` network-policy types are now copied verbatim from the installed packages at vendor time (instead of hand-written stubs), so the brokering surface (`match`/`transform`/`forwardURL`/matchers, and just-bash's `allowedUrlPrefixes` transforms) never drifts.
|
|
9
|
+
|
|
3
10
|
## 0.43.0
|
|
4
11
|
|
|
5
12
|
### Minor Changes
|
package/bin/ash.js
CHANGED
|
@@ -18,6 +18,7 @@ function createBootstrapOptions(overrides = {}) {
|
|
|
18
18
|
overrides.cliEntrypointPath ?? resolve(packageRoot, "dist", "src", "cli", "run.js"),
|
|
19
19
|
packageRoot,
|
|
20
20
|
postBuildScriptPaths: overrides.postBuildScriptPaths ?? [
|
|
21
|
+
resolve(packageRoot, "scripts", "copy-compiled-assets.mjs"),
|
|
21
22
|
resolve(packageRoot, "scripts", "copy-docs.mjs"),
|
|
22
23
|
resolve(packageRoot, "scripts", "stamp-version-tokens.mjs"),
|
|
23
24
|
],
|
|
@@ -49,6 +49,22 @@ This is enforced on three layers:
|
|
|
49
49
|
Markdown frontmatter is treated the same way: `name:` is no longer a
|
|
50
50
|
recognized field on system or skill markdown.
|
|
51
51
|
|
|
52
|
+
## Release Metadata
|
|
53
|
+
|
|
54
|
+
### Changeset Package Keys Match Workspace Names
|
|
55
|
+
|
|
56
|
+
Changeset frontmatter must name the package exactly as it appears in that
|
|
57
|
+
workspace package's `package.json`. Directory names, binary names, and
|
|
58
|
+
marketing names are not valid substitutes; for example, the package under
|
|
59
|
+
`packages/ash` is named `experimental-ash`.
|
|
60
|
+
|
|
61
|
+
Enforcement: rule 29 in `scripts/guard-agents-rules.mjs` parses changeset
|
|
62
|
+
YAML frontmatter with `gray-matter`, reads the package patterns from
|
|
63
|
+
`pnpm-workspace.yaml`, resolves workspace package names from their
|
|
64
|
+
`package.json` files, and fails if any `.changeset/*.md` package key does not
|
|
65
|
+
match one of those names. This keeps invalid release metadata from reaching
|
|
66
|
+
the post-merge `changesets/action` workflow.
|
|
67
|
+
|
|
52
68
|
## Runtime Boundaries
|
|
53
69
|
|
|
54
70
|
### Workflow APIs Stay In Runtime Code
|
|
@@ -61,32 +61,88 @@ Use `metadata["step.started"]` when the metadata depends on the current session,
|
|
|
61
61
|
channel, or model input:
|
|
62
62
|
|
|
63
63
|
```ts
|
|
64
|
-
import
|
|
64
|
+
import { defineInstrumentation } from "experimental-ash/instrumentation";
|
|
65
|
+
|
|
66
|
+
export default defineInstrumentation({
|
|
67
|
+
metadata: {
|
|
68
|
+
"step.started"(input) {
|
|
69
|
+
if (input.channel.kind !== "channel:support") {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
"slack.channel_id": input.channel.metadata.channelId ?? "",
|
|
75
|
+
"slack.user_id": input.channel.metadata.triggeringUserId ?? "",
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For authored channels, TypeScript gets channel metadata types from a compiler-owned declaration
|
|
83
|
+
file. When Ash compiles `agent/channels/support.ts`, it writes
|
|
84
|
+
`.ash/compile/channel-instrumentation-types.d.ts` with a `ChannelMetadataMap` entry for the
|
|
85
|
+
filename-derived kind and a `ChannelReferenceMap` entry for `isChannel`:
|
|
86
|
+
|
|
87
|
+
```ts
|
|
88
|
+
import type { InferChannelMetadata } from "experimental-ash/channels";
|
|
65
89
|
|
|
66
|
-
// A Slack-backed channel authored at `agent/channels/support.ts` has kind
|
|
67
|
-
// `channel:support` (its filename).
|
|
68
90
|
declare module "experimental-ash/instrumentation" {
|
|
69
91
|
interface ChannelMetadataMap {
|
|
70
|
-
readonly "channel:support":
|
|
92
|
+
readonly "channel:support": InferChannelMetadata<
|
|
93
|
+
typeof import("../../agent/channels/support.js").default
|
|
94
|
+
>;
|
|
95
|
+
}
|
|
96
|
+
interface ChannelReferenceMap {
|
|
97
|
+
readonly "channel:support": typeof import("../../agent/channels/support.js").default;
|
|
71
98
|
}
|
|
72
99
|
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Keep `.ash/**/*.d.ts` in your app's `tsconfig.json` `include` list so TypeScript sees that file.
|
|
103
|
+
Then either `input.channel.kind === "channel:support"` or `isChannel(input.channel, supportChannel)`
|
|
104
|
+
narrows `input.channel.metadata` to the return type of the channel's `metadata(state)` function:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
// agent/channels/support.ts
|
|
108
|
+
import { defineChannel, POST } from "experimental-ash/channels";
|
|
109
|
+
|
|
110
|
+
export default defineChannel({
|
|
111
|
+
state: { ticketId: null as string | null },
|
|
112
|
+
// Ash uses this function's runtime value for instrumentation metadata and
|
|
113
|
+
// makes its return type available to instrumentation.ts.
|
|
114
|
+
metadata(state) {
|
|
115
|
+
return { ticketId: state.ticketId };
|
|
116
|
+
},
|
|
117
|
+
routes: [POST("/support", async () => new Response("ok"))],
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// agent/instrumentation.ts
|
|
123
|
+
import { defineInstrumentation, isChannel } from "experimental-ash/instrumentation";
|
|
124
|
+
|
|
125
|
+
import supportChannel from "./channels/support.js";
|
|
73
126
|
|
|
74
127
|
export default defineInstrumentation({
|
|
75
128
|
metadata: {
|
|
76
129
|
"step.started"(input) {
|
|
77
|
-
if (input.channel
|
|
130
|
+
if (!isChannel(input.channel, supportChannel)) {
|
|
78
131
|
return {};
|
|
79
132
|
}
|
|
80
133
|
|
|
81
134
|
return {
|
|
82
|
-
"
|
|
83
|
-
"slack.user_id": input.channel.metadata.triggeringUserId ?? "",
|
|
135
|
+
"support.ticket_id": input.channel.metadata.ticketId ?? "",
|
|
84
136
|
};
|
|
85
137
|
},
|
|
86
138
|
},
|
|
87
139
|
});
|
|
88
140
|
```
|
|
89
141
|
|
|
142
|
+
Built-in wrapper return types expose their concrete metadata too. For example, a file named
|
|
143
|
+
`agent/channels/support.ts` that default-exports `slackChannel()` still narrows under
|
|
144
|
+
`input.channel.kind === "channel:support"` to Slack's projected metadata shape.
|
|
145
|
+
|
|
90
146
|
The callback runs after Ash has assembled the model input for the attempt and before constructing
|
|
91
147
|
the AI SDK call. That timing lets the returned values attach to the AI SDK model-call span and its
|
|
92
148
|
child spans. It runs for each model-call attempt, including a retry of the same logical step when
|
|
@@ -109,31 +165,25 @@ channels it is `channel:<name>`, where `<name>` is the channel's filename under
|
|
|
109
165
|
Channel metadata is channel-owned. Built-in channels expose only the fields they choose to make
|
|
110
166
|
observable; for example, Slack projects `channelId`, `teamId`, `threadTs`, and `triggeringUserId`
|
|
111
167
|
from its durable channel state. User-authored channels expose their own projection by returning
|
|
112
|
-
`metadata(state)` from `defineChannel`.
|
|
113
|
-
|
|
168
|
+
`metadata(state)` from `defineChannel`. Runtime instrumentation never falls back to raw channel
|
|
169
|
+
state.
|
|
114
170
|
|
|
115
171
|
`metadata(state)` must return an object composed of JSON primitives, arrays, and plain objects.
|
|
116
172
|
Ash omits `undefined` object properties and drops projections containing values such as `Date` or
|
|
117
173
|
`Map` with a warning.
|
|
118
174
|
|
|
175
|
+
Manual `ChannelMetadataMap` declaration merging is only the escape hatch for unusual setups where
|
|
176
|
+
the generated `.ash/**/*.d.ts` file is not available to the TypeScript program, or where a channel
|
|
177
|
+
is typed outside the normal `agent/channels/<name>` compile path:
|
|
178
|
+
|
|
119
179
|
```ts
|
|
120
|
-
import {
|
|
180
|
+
import type { SlackInstrumentationMetadata } from "experimental-ash/channels/slack";
|
|
121
181
|
|
|
122
182
|
declare module "experimental-ash/instrumentation" {
|
|
123
183
|
interface ChannelMetadataMap {
|
|
124
|
-
readonly "channel:support":
|
|
125
|
-
readonly triggeringUserId: string | null;
|
|
126
|
-
};
|
|
184
|
+
readonly "channel:support": SlackInstrumentationMetadata;
|
|
127
185
|
}
|
|
128
186
|
}
|
|
129
|
-
|
|
130
|
-
export default defineChannel({
|
|
131
|
-
state: { triggeringUserId: null as string | null },
|
|
132
|
-
metadata(state) {
|
|
133
|
-
return { triggeringUserId: state.triggeringUserId };
|
|
134
|
-
},
|
|
135
|
-
routes: [],
|
|
136
|
-
});
|
|
137
187
|
```
|
|
138
188
|
|
|
139
189
|
Metadata failures are non-destructive. If the callback throws, returns a non-record, or returns
|
|
@@ -58,7 +58,7 @@ Session metadata, sandbox access, and skill access are available through the `ct
|
|
|
58
58
|
passed to tool `execute`, hook handlers, and channel event handlers:
|
|
59
59
|
|
|
60
60
|
- `ctx.session` - current session, turn, auth, and optional parent lineage
|
|
61
|
-
- `ctx.getSandbox()` - live sandbox handle for the current agent
|
|
61
|
+
- `ctx.getSandbox()` - live sandbox handle for the current agent; `sandbox.setNetworkPolicy(policy)` applies a firewall network policy at run time (egress control, credential brokering) without an `onSession` hook
|
|
62
62
|
- `ctx.getSkill(identifier)` - handle for a named skill visible to the current agent
|
|
63
63
|
- `defineState(name, initial)` - typed durable state with `get()` and `update()` (`experimental-ash/context`)
|
|
64
64
|
|
|
@@ -359,6 +359,44 @@ or to apply it only after the template has been built, set it in `onSession`'s `
|
|
|
359
359
|
common pattern: leave the factory open so `bootstrap` can `git clone` and `npm install`, then
|
|
360
360
|
lock down via `onSession`.
|
|
361
361
|
|
|
362
|
+
`onSession`'s `use()` (and the factory) accept the full policy shape, including per-domain
|
|
363
|
+
`transform`s for **credential brokering** — injecting headers at the firewall so a secret
|
|
364
|
+
authenticates egress without ever entering the sandbox process. When the brokered credential is
|
|
365
|
+
known at session start, configuring it up front in `onSession` is the right choice:
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
onSession: async ({ use }) => {
|
|
369
|
+
await use({
|
|
370
|
+
networkPolicy: {
|
|
371
|
+
allow: {
|
|
372
|
+
"github.com": [{ transform: [{ headers: { authorization: "Basic …" } }] }],
|
|
373
|
+
"*": [],
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
};
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
When the policy must change **during a turn** — to broker a credential resolved mid-turn, or to
|
|
381
|
+
tighten egress after fetching data — call `setNetworkPolicy` on the live sandbox handle from a
|
|
382
|
+
tool, hook, or channel event. It accepts the same policy shape:
|
|
383
|
+
|
|
384
|
+
```ts
|
|
385
|
+
const sandbox = await ctx.getSandbox();
|
|
386
|
+
await sandbox.setNetworkPolicy({
|
|
387
|
+
allow: {
|
|
388
|
+
"github.com": [{ transform: [{ headers: { authorization: "Basic …" } }] }],
|
|
389
|
+
"*": [],
|
|
390
|
+
},
|
|
391
|
+
});
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
The `"*": []` catch-all keeps general egress open while the `transform` applies only to
|
|
395
|
+
`github.com`. The policy takes effect from the time the call resolves, so await it before the
|
|
396
|
+
egress you want governed. The local backend rejects `setNetworkPolicy` — just-bash takes its
|
|
397
|
+
network policy only at sandbox creation and runs no binaries to govern — so run-time updates are
|
|
398
|
+
a Vercel-backend capability.
|
|
399
|
+
|
|
362
400
|
### Timeout
|
|
363
401
|
|
|
364
402
|
Sandbox VMs shut down after a configurable timeout. Ash defaults to **30 minutes**. Set a different
|
|
@@ -3,7 +3,7 @@ import type { RouteHandler, SendFn } from "#channel/routes.js";
|
|
|
3
3
|
import type { Session } from "#channel/session.js";
|
|
4
4
|
import type { SessionAuthContext } from "#channel/types.js";
|
|
5
5
|
export declare const CHANNEL_SENTINEL: "ash:channel";
|
|
6
|
-
export interface CompiledChannel<TState = undefined, TReceiveArgs = Record<string, unknown>> {
|
|
6
|
+
export interface CompiledChannel<TState = undefined, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> {
|
|
7
7
|
readonly __kind: typeof CHANNEL_SENTINEL;
|
|
8
8
|
readonly routes: readonly {
|
|
9
9
|
method: string;
|
|
@@ -11,6 +11,7 @@ export interface CompiledChannel<TState = undefined, TReceiveArgs = Record<strin
|
|
|
11
11
|
handler: RouteHandler<TState>;
|
|
12
12
|
}[];
|
|
13
13
|
readonly adapter: ChannelAdapter<any>;
|
|
14
|
+
readonly __metadata?: TMetadata;
|
|
14
15
|
readonly receive?: (input: {
|
|
15
16
|
readonly message: string;
|
|
16
17
|
readonly args: Readonly<TReceiveArgs>;
|
|
@@ -20,3 +21,5 @@ export interface CompiledChannel<TState = undefined, TReceiveArgs = Record<strin
|
|
|
20
21
|
}) => Promise<Session>;
|
|
21
22
|
}
|
|
22
23
|
export declare function isCompiledChannel(value: unknown): value is CompiledChannel;
|
|
24
|
+
export declare function getChannelInstrumentationKind(value: unknown): string | undefined;
|
|
25
|
+
export declare function setChannelInstrumentationKind(channel: CompiledChannel, kind: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const CHANNEL_SENTINEL=`ash:channel
|
|
1
|
+
const CHANNEL_SENTINEL=`ash:channel`,CHANNEL_INSTRUMENTATION_KIND=Symbol.for(`ash.channel.instrumentationKind`);function isCompiledChannel(e){return typeof e==`object`&&!!e&&e.__kind===`ash:channel`}function getChannelInstrumentationKind(e){if(!isCompiledChannel(e))return;let t=Reflect.get(e,CHANNEL_INSTRUMENTATION_KIND);if(typeof t==`string`&&t.length>0)return t;let n=e.adapter.kind;return typeof n==`string`&&n.startsWith(`channel:`)?n:void 0}function setChannelInstrumentationKind(e,t){Object.defineProperty(e,CHANNEL_INSTRUMENTATION_KIND,{configurable:!0,enumerable:!1,value:t})}export{CHANNEL_SENTINEL,getChannelInstrumentationKind,isCompiledChannel,setChannelInstrumentationKind};
|
|
@@ -32,6 +32,12 @@ export interface SendPayload {
|
|
|
32
32
|
readonly modelContext?: readonly ModelMessage[];
|
|
33
33
|
}
|
|
34
34
|
export type SendFn<TState = undefined> = (input: string | UserContent | SendPayload, options: SendOptions<TState>) => Promise<Session>;
|
|
35
|
+
type BaseSendOptions = {
|
|
36
|
+
auth: SessionAuthContext | null;
|
|
37
|
+
callback?: SessionCallback;
|
|
38
|
+
continuationToken: string;
|
|
39
|
+
mode?: RunMode;
|
|
40
|
+
};
|
|
35
41
|
/**
|
|
36
42
|
* Options for {@link SendFn}. The channel owns its continuation-token
|
|
37
43
|
* format — pass the channel-local raw token (the framework prepends
|
|
@@ -39,16 +45,7 @@ export type SendFn<TState = undefined> = (input: string | UserContent | SendPayl
|
|
|
39
45
|
* state via {@link state}, which becomes the new session's `state`
|
|
40
46
|
* on first `runtime.run()` and is ignored on subsequent `deliver`s.
|
|
41
47
|
*/
|
|
42
|
-
export type SendOptions<TState = undefined> = TState extends undefined ? {
|
|
43
|
-
auth: SessionAuthContext | null;
|
|
44
|
-
callback?: SessionCallback;
|
|
45
|
-
continuationToken: string;
|
|
46
|
-
mode?: RunMode;
|
|
47
|
-
} : {
|
|
48
|
-
auth: SessionAuthContext | null;
|
|
49
|
-
callback?: SessionCallback;
|
|
50
|
-
continuationToken: string;
|
|
51
|
-
mode?: RunMode;
|
|
48
|
+
export type SendOptions<TState = undefined> = [TState] extends [undefined] ? BaseSendOptions : BaseSendOptions & {
|
|
52
49
|
state: TState;
|
|
53
50
|
};
|
|
54
51
|
export type GetSessionFn = (sessionId: string) => Session;
|
|
@@ -63,3 +60,4 @@ export declare function POST<TState = undefined>(path: string, handler: RouteHan
|
|
|
63
60
|
export declare function PUT<TState = undefined>(path: string, handler: RouteHandler<TState>): RouteDefinition<TState>;
|
|
64
61
|
export declare function PATCH<TState = undefined>(path: string, handler: RouteHandler<TState>): RouteDefinition<TState>;
|
|
65
62
|
export declare function DELETE<TState = undefined>(path: string, handler: RouteHandler<TState>): RouteDefinition<TState>;
|
|
63
|
+
export {};
|
|
@@ -1,23 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export interface NetworkPolicyRule {
|
|
6
|
-
transform?: NetworkTransformer[] | undefined;
|
|
7
|
-
}
|
|
1
|
+
// The firewall network-policy types are copied verbatim from the installed
|
|
2
|
+
// `@vercel/sandbox` at vendor time (see scripts/vendor-compiled/@vercel/sandbox.mjs)
|
|
3
|
+
// so the credential-brokering surface never drifts from the SDK.
|
|
4
|
+
import type { NetworkPolicy } from "./network-policy.js";
|
|
8
5
|
|
|
9
|
-
export type
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
deny?: string[] | undefined;
|
|
18
|
-
}
|
|
19
|
-
| undefined;
|
|
20
|
-
};
|
|
6
|
+
export type {
|
|
7
|
+
NetworkPolicy,
|
|
8
|
+
NetworkPolicyKeyValueMatcher,
|
|
9
|
+
NetworkPolicyMatch,
|
|
10
|
+
NetworkPolicyMatcher,
|
|
11
|
+
NetworkPolicyRule,
|
|
12
|
+
NetworkTransformer,
|
|
13
|
+
} from "./network-policy.js";
|
|
21
14
|
|
|
22
15
|
export interface SandboxKeepLastSnapshotsConfig {
|
|
23
16
|
count: number;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
//#region src/network-policy.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A transform applied to network requests matching a domain rule.
|
|
4
|
+
*
|
|
5
|
+
* @example
|
|
6
|
+
* {
|
|
7
|
+
* headers: { authorization: "Bearer sk-..." }
|
|
8
|
+
* }
|
|
9
|
+
*/
|
|
10
|
+
type NetworkTransformer = {
|
|
11
|
+
/** Headers to set on the outgoing request. */
|
|
12
|
+
headers?: Record<string, string>;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Defines how a request value is matched.
|
|
16
|
+
*/
|
|
17
|
+
type NetworkPolicyMatcher = {
|
|
18
|
+
/** Match the value exactly. */
|
|
19
|
+
exact?: string;
|
|
20
|
+
} | {
|
|
21
|
+
/** Match values that start with the provided prefix. */
|
|
22
|
+
startsWith?: string;
|
|
23
|
+
} | {
|
|
24
|
+
/** Match values against an RE2 regular expression. */
|
|
25
|
+
regex?: string;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Matcher for key/value request entries such as headers and query parameters.
|
|
29
|
+
*/
|
|
30
|
+
type NetworkPolicyKeyValueMatcher = {
|
|
31
|
+
/** Matcher for the entry key. */
|
|
32
|
+
key?: NetworkPolicyMatcher;
|
|
33
|
+
/** Matcher for the entry value. */
|
|
34
|
+
value?: NetworkPolicyMatcher;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Request matcher for a network policy rule.
|
|
38
|
+
*
|
|
39
|
+
* All specified dimensions must match. Multiple methods are ORed; multiple
|
|
40
|
+
* header and query-string matchers are ANDed.
|
|
41
|
+
*/
|
|
42
|
+
type NetworkPolicyMatch = {
|
|
43
|
+
/** Match on the request path. */
|
|
44
|
+
path?: NetworkPolicyMatcher;
|
|
45
|
+
/** Match on the HTTP method. */
|
|
46
|
+
method?: string[];
|
|
47
|
+
/** Match on query-string entries. */
|
|
48
|
+
queryString?: NetworkPolicyKeyValueMatcher[];
|
|
49
|
+
/** Match on request headers. */
|
|
50
|
+
headers?: NetworkPolicyKeyValueMatcher[];
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* A rule applied to requests matching a domain in the network policy.
|
|
54
|
+
*/
|
|
55
|
+
type NetworkPolicyRule = {
|
|
56
|
+
/**
|
|
57
|
+
* Optional request matcher. When provided, transforms only apply to requests
|
|
58
|
+
* that match every specified dimension.
|
|
59
|
+
*/
|
|
60
|
+
match?: NetworkPolicyMatch;
|
|
61
|
+
/**
|
|
62
|
+
* Transforms to apply to matching requests.
|
|
63
|
+
*/
|
|
64
|
+
transform?: NetworkTransformer[];
|
|
65
|
+
/**
|
|
66
|
+
* HTTPS proxy URL to forward matching requests to. Must not include query string or fragment.
|
|
67
|
+
*
|
|
68
|
+
* You can use the `defineSandboxProxy` helper from `@vercel/sandbox/proxy` to implement the proxy handler
|
|
69
|
+
* automatically, which handles authorization and extracts metadata about the request and sandbox.
|
|
70
|
+
*
|
|
71
|
+
* @see https://vercel.com/docs/vercel-sandbox/concepts/firewall#requests-proxying
|
|
72
|
+
*/
|
|
73
|
+
forwardURL?: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Network policy to define network restrictions for the sandbox.
|
|
77
|
+
*
|
|
78
|
+
* - `"allow-all"`: Full internet access (default). All traffic is allowed.
|
|
79
|
+
* - `"deny-all"`: No internet access. All traffic is denied.
|
|
80
|
+
* - Object: Custom access with explicit allow/deny lists.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* // Full internet access (default)
|
|
84
|
+
* "allow-all"
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* // No external access
|
|
88
|
+
* "deny-all"
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* // Custom access with specific domains (simple list)
|
|
92
|
+
* // All traffic not explicitly allowed is denied.
|
|
93
|
+
* {
|
|
94
|
+
* allow: ["*.npmjs.org", "github.com"],
|
|
95
|
+
* subnets: {
|
|
96
|
+
* allow: ["10.0.0.0/8"],
|
|
97
|
+
* deny: ["10.1.0.0/16"]
|
|
98
|
+
* }
|
|
99
|
+
* }
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* // Custom access with specific domains (record form)
|
|
103
|
+
* {
|
|
104
|
+
* allow: {
|
|
105
|
+
* "*.npmjs.org": [],
|
|
106
|
+
* "github.com": [],
|
|
107
|
+
* }
|
|
108
|
+
* }
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* // Custom access with request transformers
|
|
112
|
+
* {
|
|
113
|
+
* allow: {
|
|
114
|
+
* "ai-gateway.vercel.sh": [
|
|
115
|
+
* {
|
|
116
|
+
* match: {
|
|
117
|
+
* method: ["POST"],
|
|
118
|
+
* path: { startsWith: "/v1/" },
|
|
119
|
+
* headers: [
|
|
120
|
+
* { key: { exact: "x-api-key" }, value: { exact: "placeholder" } }
|
|
121
|
+
* ]
|
|
122
|
+
* },
|
|
123
|
+
* transform: [{
|
|
124
|
+
* headers: { authorization: "Bearer ..." }
|
|
125
|
+
* }]
|
|
126
|
+
* }
|
|
127
|
+
* ],
|
|
128
|
+
* "*": []
|
|
129
|
+
* }
|
|
130
|
+
* }
|
|
131
|
+
*/
|
|
132
|
+
type NetworkPolicy = "allow-all" | "deny-all" | {
|
|
133
|
+
/**
|
|
134
|
+
* Domains to allow traffic to.
|
|
135
|
+
* Use "*" prefix for wildcard matching (e.g., "*.npmjs.org").
|
|
136
|
+
*
|
|
137
|
+
* Accepts either:
|
|
138
|
+
* - `string[]`: A simple list of domains to allow.
|
|
139
|
+
* - `Record<string, NetworkPolicyRule[]>`: A map of domains to rules.
|
|
140
|
+
* An empty array allows traffic with no additional rules.
|
|
141
|
+
*/
|
|
142
|
+
allow?: string[] | Record<string, NetworkPolicyRule[]>;
|
|
143
|
+
/**
|
|
144
|
+
* Subnet-level access control using CIDR notation.
|
|
145
|
+
*/
|
|
146
|
+
subnets?: {
|
|
147
|
+
/**
|
|
148
|
+
* List of CIDRs to allow traffic to.
|
|
149
|
+
* Traffic to these addresses will bypass the domain allowlist.
|
|
150
|
+
*/
|
|
151
|
+
allow?: string[];
|
|
152
|
+
/**
|
|
153
|
+
* List of CIDRs to deny traffic to.
|
|
154
|
+
* These take precedence over allowed domains and CIDRs.
|
|
155
|
+
*/
|
|
156
|
+
deny?: string[];
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
//#endregion
|
|
160
|
+
export { NetworkPolicy, NetworkPolicyKeyValueMatcher, NetworkPolicyMatch, NetworkPolicyMatcher, NetworkPolicyRule, NetworkTransformer };
|
|
161
|
+
//# sourceMappingURL=network-policy.d.ts.map
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
// The network-policy types are copied verbatim from the installed just-bash at
|
|
2
|
+
// vendor time (see scripts/vendor-compiled/just-bash.mjs) so the credential-
|
|
3
|
+
// brokering surface (allowedUrlPrefixes + header transforms) never drifts.
|
|
4
|
+
import type { NetworkConfig } from "./network/types.js";
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
AllowedUrl,
|
|
8
|
+
AllowedUrlEntry,
|
|
9
|
+
HttpMethod,
|
|
10
|
+
NetworkConfig,
|
|
11
|
+
RequestTransform,
|
|
12
|
+
} from "./network/types.js";
|
|
13
|
+
|
|
1
14
|
export type InitialFileContent = string | Uint8Array;
|
|
2
15
|
export type InitialFiles = Record<string, InitialFileContent>;
|
|
3
16
|
|
|
@@ -17,7 +30,7 @@ export interface BashOptions {
|
|
|
17
30
|
cwd: string;
|
|
18
31
|
env?: Readonly<Record<string, string>> | undefined;
|
|
19
32
|
fs: IFileSystem;
|
|
20
|
-
network?:
|
|
33
|
+
network?: NetworkConfig | undefined;
|
|
21
34
|
}
|
|
22
35
|
|
|
23
36
|
export interface BashExecResult {
|
|
@@ -50,7 +63,7 @@ export interface SandboxOptions {
|
|
|
50
63
|
cwd?: string | undefined;
|
|
51
64
|
env?: Record<string, string> | undefined;
|
|
52
65
|
fs?: IFileSystem | undefined;
|
|
53
|
-
network?:
|
|
66
|
+
network?: NetworkConfig | undefined;
|
|
54
67
|
timeoutMs?: number | undefined;
|
|
55
68
|
}
|
|
56
69
|
|