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.
Files changed (42) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/bin/ash.js +1 -0
  3. package/dist/docs/internals/mechanical-invariants.md +16 -0
  4. package/dist/docs/public/advanced/instrumentation.md +71 -21
  5. package/dist/docs/public/advanced/typescript-api.md +1 -1
  6. package/dist/docs/public/sandbox.md +38 -0
  7. package/dist/src/channel/compiled-channel.d.ts +4 -1
  8. package/dist/src/channel/compiled-channel.js +1 -1
  9. package/dist/src/channel/routes.d.ts +8 -10
  10. package/dist/src/compiled/.vendor-stamp.json +1 -1
  11. package/dist/src/compiled/@vercel/sandbox/index.d.ts +12 -19
  12. package/dist/src/compiled/@vercel/sandbox/network-policy.d.ts +161 -0
  13. package/dist/src/compiled/just-bash/index.d.ts +15 -2
  14. package/dist/src/compiled/just-bash/network/types.d.ts +155 -0
  15. package/dist/src/compiler/artifacts.d.ts +1 -0
  16. package/dist/src/compiler/artifacts.js +1 -1
  17. package/dist/src/compiler/channel-instrumentation-types.d.ts +8 -0
  18. package/dist/src/compiler/channel-instrumentation-types.js +2 -0
  19. package/dist/src/execution/sandbox/bindings/local.js +1 -1
  20. package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
  21. package/dist/src/execution/sandbox/session.d.ts +6 -1
  22. package/dist/src/execution/sandbox/session.js +1 -1
  23. package/dist/src/internal/application/package.js +1 -1
  24. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  25. package/dist/src/packages/ash-scaffold/src/web-template.js +1 -0
  26. package/dist/src/public/channels/discord/discordChannel.d.ts +5 -2
  27. package/dist/src/public/channels/index.d.ts +1 -1
  28. package/dist/src/public/channels/slack/slackChannel.d.ts +6 -8
  29. package/dist/src/public/channels/teams/teamsChannel.d.ts +5 -2
  30. package/dist/src/public/channels/telegram/telegramChannel.d.ts +5 -2
  31. package/dist/src/public/channels/twilio/twilioChannel.d.ts +6 -3
  32. package/dist/src/public/definitions/defineChannel.d.ts +6 -3
  33. package/dist/src/public/definitions/instrumentation.d.ts +1 -152
  34. package/dist/src/public/definitions/instrumentation.js +1 -1
  35. package/dist/src/public/instrumentation/index.d.ts +178 -1
  36. package/dist/src/public/instrumentation/index.js +1 -1
  37. package/dist/src/public/sandbox/index.d.ts +1 -0
  38. package/dist/src/runtime/resolve-channel.js +1 -1
  39. package/dist/src/shared/sandbox-network-policy.d.ts +23 -0
  40. package/dist/src/shared/sandbox-network-policy.js +1 -0
  41. package/dist/src/shared/sandbox-session.d.ts +15 -0
  42. package/package.json +1 -1
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Network configuration types
3
+ *
4
+ * Network access is disabled by default. To enable network access (e.g., for curl),
5
+ * you must explicitly configure allowed URLs.
6
+ */
7
+ /**
8
+ * DNS lookup result used for private IP resolution checks
9
+ */
10
+ export interface DnsLookupResult {
11
+ address: string;
12
+ family: number;
13
+ }
14
+ /**
15
+ * HTTP methods that can be allowed
16
+ */
17
+ export type HttpMethod = "GET" | "HEAD" | "POST" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";
18
+ /**
19
+ * Header transform applied at the fetch boundary.
20
+ * Headers specified here override any user-supplied headers with the same name.
21
+ */
22
+ export interface RequestTransform {
23
+ headers: Record<string, string>;
24
+ }
25
+ /**
26
+ * An allowed URL entry with optional header transforms.
27
+ * Transforms are applied at the fetch boundary so secrets never enter the sandbox.
28
+ */
29
+ export interface AllowedUrl {
30
+ url: string;
31
+ transform?: RequestTransform[];
32
+ }
33
+ /**
34
+ * An entry in the allowedUrlPrefixes list: either a plain URL string or
35
+ * an object with a URL and optional transforms.
36
+ */
37
+ export type AllowedUrlEntry = string | AllowedUrl;
38
+ /**
39
+ * Configuration for network access
40
+ */
41
+ export interface NetworkConfig {
42
+ /**
43
+ * List of allowed URL prefixes. Each entry must be a full origin (scheme + host),
44
+ * optionally followed by a path prefix:
45
+ * - An origin: "https://api.example.com" - allows all paths on this origin
46
+ * - An origin + path prefix: "https://api.example.com/v1/" - allows only paths starting with /v1/
47
+ *
48
+ * Entries can be plain strings or objects with transforms for credentials brokering:
49
+ * ```
50
+ * allowedUrlPrefixes: [
51
+ * "https://other-api.com",
52
+ * {
53
+ * url: "https://ai-gateway.vercel.sh",
54
+ * transform: [{ headers: { "Authorization": "Bearer secret" } }],
55
+ * },
56
+ * ]
57
+ * ```
58
+ *
59
+ * The check is performed on the full URL, so "https://api.example.com/v1" will allow:
60
+ * - https://api.example.com/v1
61
+ * - https://api.example.com/v1/users
62
+ * - https://api.example.com/v1/users/123
63
+ *
64
+ * But NOT:
65
+ * - https://api.example.com/v10
66
+ * - https://api.example.com/v1-admin
67
+ * - https://api.example.com/v2/users
68
+ * - https://api.example.org/v1/users (different origin)
69
+ * - URLs that rely on ambiguous encoded separators like %2f or %5c
70
+ *
71
+ * Invalid entries (missing scheme, missing host, relative paths) will throw an error.
72
+ */
73
+ allowedUrlPrefixes?: AllowedUrlEntry[];
74
+ /**
75
+ * List of allowed HTTP methods. Defaults to ["GET", "HEAD"] for safety.
76
+ * dangerouslyAllowFullInternetAccess to enables all methods.
77
+ */
78
+ allowedMethods?: HttpMethod[];
79
+ /**
80
+ * Bypass the allow-list and allow all URLs and methods.
81
+ * DANGEROUS: Only use this in trusted environments.
82
+ */
83
+ dangerouslyAllowFullInternetAccess?: boolean;
84
+ /**
85
+ * Maximum number of redirects to follow (default: 20)
86
+ */
87
+ maxRedirects?: number;
88
+ /**
89
+ * Request timeout in milliseconds (default: 30000)
90
+ */
91
+ timeoutMs?: number;
92
+ /**
93
+ * Maximum response body size in bytes (default: 10MB).
94
+ * Responses larger than this will be rejected with ResponseTooLargeError.
95
+ */
96
+ maxResponseSize?: number;
97
+ /**
98
+ * Reject URLs with private/loopback IP addresses as hostnames.
99
+ * Performs both lexical hostname checks and DNS resolution to catch
100
+ * domains that resolve to private IPs (e.g., DNS rebinding attacks).
101
+ * Useful for mitigating SSRF attacks. Default: false (opt-in).
102
+ *
103
+ * When enabled, the private IP check is enforced even when
104
+ * `dangerouslyAllowFullInternetAccess` is true, ensuring that
105
+ * internal/loopback addresses are never reachable.
106
+ */
107
+ denyPrivateRanges?: boolean;
108
+ /**
109
+ * @internal Override DNS resolution for testing.
110
+ * When set, used instead of the default `dns.lookup` for the
111
+ * denyPrivateRanges DNS rebinding check.
112
+ */
113
+ _dnsResolve?: (hostname: string) => Promise<DnsLookupResult[]>;
114
+ }
115
+ /**
116
+ * Result of a network fetch operation
117
+ */
118
+ export interface FetchResult {
119
+ status: number;
120
+ statusText: string;
121
+ headers: Record<string, string>;
122
+ /** Raw response bytes (never decoded as UTF-8 text). */
123
+ body: Uint8Array;
124
+ url: string;
125
+ }
126
+ /**
127
+ * Error thrown when a URL is not allowed
128
+ */
129
+ export declare class NetworkAccessDeniedError extends Error {
130
+ constructor(url: string, reason?: string);
131
+ }
132
+ /**
133
+ * Error thrown when too many redirects occur
134
+ */
135
+ export declare class TooManyRedirectsError extends Error {
136
+ constructor(maxRedirects: number);
137
+ }
138
+ /**
139
+ * Error thrown when a redirect target is not allowed
140
+ */
141
+ export declare class RedirectNotAllowedError extends Error {
142
+ constructor(url: string);
143
+ }
144
+ /**
145
+ * Error thrown when an HTTP method is not allowed
146
+ */
147
+ export declare class MethodNotAllowedError extends Error {
148
+ constructor(method: string, allowedMethods: string[]);
149
+ }
150
+ /**
151
+ * Error thrown when a response body exceeds the maximum allowed size
152
+ */
153
+ export declare class ResponseTooLargeError extends Error {
154
+ constructor(maxSize: number);
155
+ }
@@ -22,6 +22,7 @@ export declare const COMPILE_METADATA_VERSION = 5;
22
22
  */
23
23
  export interface CompilerArtifactPaths {
24
24
  appRoot: string;
25
+ channelInstrumentationTypesPath: string;
25
26
  compiledManifestPath: string;
26
27
  compileDirectoryPath: string;
27
28
  compileMetadataPath: string;
@@ -1 +1 @@
1
- import{join,relative,resolve}from"node:path";import{mkdir,writeFile}from"node:fs/promises";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{createHash}from"node:crypto";import{summarizeDiscoverDiagnostics}from"#discover/diagnostics.js";import{normalizeLogicalPath}from"#discover/filesystem.js";import{createCompiledModuleMapSource}from"#compiler/module-map.js";import{compileAgentManifest}from"#compiler/normalize-manifest.js";import{materializeWorkspaceResources}from"#compiler/workspace-resources.js";const COMPILE_METADATA_KIND=`ash-compile-metadata`,COMPILE_METADATA_VERSION=5;function resolveCompilerArtifactPaths(t){let r=resolve(t),i=join(r,`.ash`,`discovery`),a=join(r,`.ash`,`compile`);return{appRoot:r,compiledManifestPath:join(a,`compiled-agent-manifest.json`),compileDirectoryPath:a,compileMetadataPath:join(a,`compile-metadata.json`),diagnosticsPath:join(i,`diagnostics.json`),discoveryManifestPath:join(i,`agent-discovery-manifest.json`),discoveryDirectoryPath:i,moduleMapPath:join(a,`module-map.mjs`)}}function createDiscoveryDiagnosticsArtifact(e){return{diagnostics:[...e],kind:`ash-discovery-diagnostics`,summary:summarizeDiscoverDiagnostics(e),version:1}}function createCompileMetadata(e){let t=resolveInstalledPackageInfo(),n=createContentHash(e.discoveryManifestJson),r=createContentHash(e.diagnosticsArtifactJson),i=createContentHash(e.moduleMapSource);return{compile:{moduleMap:{path:toArtifactRelativePath(e.appRoot,e.paths.moduleMapPath),sha256:i}},discovery:{diagnostics:{path:toArtifactRelativePath(e.appRoot,e.paths.diagnosticsPath),sha256:r},manifest:{path:toArtifactRelativePath(e.appRoot,e.paths.discoveryManifestPath),sha256:n},sourceGraphHash:createContentHash(`${n}:${r}:${i}`),summary:e.diagnosticsSummary},generator:{name:t.name,version:t.version},kind:COMPILE_METADATA_KIND,status:e.diagnosticsSummary.errors>0?`failed`:`ready`,version:5}}async function writeCompilerArtifacts(e){let t=resolveCompilerArtifactPaths(e.appRoot),n=createDiscoveryDiagnosticsArtifact(e.diagnostics),a=await materializeWorkspaceResources({compileDirectoryPath:t.compileDirectoryPath,manifest:await compileAgentManifest(e.manifest)}),o=serializeArtifactJson(a),s=serializeArtifactJson(e.manifest),c=serializeArtifactJson(n),l=createCompiledModuleMapSource({manifest:a,moduleMapPath:t.moduleMapPath}),u=createCompileMetadata({appRoot:e.appRoot,diagnosticsArtifactJson:c,diagnosticsSummary:n.summary,discoveryManifestJson:s,moduleMapSource:l,paths:t}),d=serializeArtifactJson(u);return await mkdir(t.discoveryDirectoryPath,{recursive:!0}),await mkdir(t.compileDirectoryPath,{recursive:!0}),await Promise.all([writeFile(t.compiledManifestPath,o),writeFile(t.diagnosticsPath,c),writeFile(t.discoveryManifestPath,s),writeFile(t.moduleMapPath,l),writeFile(t.compileMetadataPath,d)]),{compiledManifest:a,diagnosticsArtifact:n,metadata:u,moduleMapSource:l,paths:t}}function createContentHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function serializeArtifactJson(e){return`${JSON.stringify(e,null,2)}\n`}function toArtifactRelativePath(e,r){return normalizeLogicalPath(relative(resolve(e),r))}export{COMPILE_METADATA_KIND,COMPILE_METADATA_VERSION,createCompileMetadata,resolveCompilerArtifactPaths,writeCompilerArtifacts};
1
+ import{join,relative,resolve}from"node:path";import{mkdir,writeFile}from"node:fs/promises";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{createHash}from"node:crypto";import{summarizeDiscoverDiagnostics}from"#discover/diagnostics.js";import{normalizeLogicalPath}from"#discover/filesystem.js";import{CHANNEL_INSTRUMENTATION_TYPES_FILE_NAME,createChannelInstrumentationTypesSource}from"#compiler/channel-instrumentation-types.js";import{createCompiledModuleMapSource}from"#compiler/module-map.js";import{compileAgentManifest}from"#compiler/normalize-manifest.js";import{materializeWorkspaceResources}from"#compiler/workspace-resources.js";const COMPILE_METADATA_KIND=`ash-compile-metadata`,COMPILE_METADATA_VERSION=5;function resolveCompilerArtifactPaths(t){let r=resolve(t),i=join(r,`.ash`,`discovery`),a=join(r,`.ash`,`compile`);return{appRoot:r,channelInstrumentationTypesPath:join(a,CHANNEL_INSTRUMENTATION_TYPES_FILE_NAME),compiledManifestPath:join(a,`compiled-agent-manifest.json`),compileDirectoryPath:a,compileMetadataPath:join(a,`compile-metadata.json`),diagnosticsPath:join(i,`diagnostics.json`),discoveryManifestPath:join(i,`agent-discovery-manifest.json`),discoveryDirectoryPath:i,moduleMapPath:join(a,`module-map.mjs`)}}function createDiscoveryDiagnosticsArtifact(e){return{diagnostics:[...e],kind:`ash-discovery-diagnostics`,summary:summarizeDiscoverDiagnostics(e),version:1}}function createCompileMetadata(e){let t=resolveInstalledPackageInfo(),n=createContentHash(e.discoveryManifestJson),r=createContentHash(e.diagnosticsArtifactJson),i=createContentHash(e.moduleMapSource);return{compile:{moduleMap:{path:toArtifactRelativePath(e.appRoot,e.paths.moduleMapPath),sha256:i}},discovery:{diagnostics:{path:toArtifactRelativePath(e.appRoot,e.paths.diagnosticsPath),sha256:r},manifest:{path:toArtifactRelativePath(e.appRoot,e.paths.discoveryManifestPath),sha256:n},sourceGraphHash:createContentHash(`${n}:${r}:${i}`),summary:e.diagnosticsSummary},generator:{name:t.name,version:t.version},kind:COMPILE_METADATA_KIND,status:e.diagnosticsSummary.errors>0?`failed`:`ready`,version:5}}async function writeCompilerArtifacts(e){let t=resolveCompilerArtifactPaths(e.appRoot),n=createDiscoveryDiagnosticsArtifact(e.diagnostics),a=await materializeWorkspaceResources({compileDirectoryPath:t.compileDirectoryPath,manifest:await compileAgentManifest(e.manifest)}),o=serializeArtifactJson(a),s=serializeArtifactJson(e.manifest),c=serializeArtifactJson(n),l=createCompiledModuleMapSource({manifest:a,moduleMapPath:t.moduleMapPath}),u=createChannelInstrumentationTypesSource({manifest:a,typesPath:t.channelInstrumentationTypesPath}),d=createCompileMetadata({appRoot:e.appRoot,diagnosticsArtifactJson:c,diagnosticsSummary:n.summary,discoveryManifestJson:s,moduleMapSource:l,paths:t}),f=serializeArtifactJson(d);return await mkdir(t.discoveryDirectoryPath,{recursive:!0}),await mkdir(t.compileDirectoryPath,{recursive:!0}),await Promise.all([writeFile(t.compiledManifestPath,o),writeFile(t.diagnosticsPath,c),writeFile(t.discoveryManifestPath,s),writeFile(t.channelInstrumentationTypesPath,u),writeFile(t.moduleMapPath,l),writeFile(t.compileMetadataPath,f)]),{compiledManifest:a,diagnosticsArtifact:n,metadata:d,moduleMapSource:l,paths:t}}function createContentHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function serializeArtifactJson(e){return`${JSON.stringify(e,null,2)}\n`}function toArtifactRelativePath(e,r){return normalizeLogicalPath(relative(resolve(e),r))}export{COMPILE_METADATA_KIND,COMPILE_METADATA_VERSION,createCompileMetadata,resolveCompilerArtifactPaths,writeCompilerArtifacts};
@@ -0,0 +1,8 @@
1
+ import type { CompiledAgentManifest } from "#compiler/manifest.js";
2
+ interface ChannelInstrumentationTypesSourceInput {
3
+ readonly manifest: CompiledAgentManifest;
4
+ readonly typesPath: string;
5
+ }
6
+ export declare const CHANNEL_INSTRUMENTATION_TYPES_FILE_NAME = "channel-instrumentation-types.d.ts";
7
+ export declare function createChannelInstrumentationTypesSource(input: ChannelInstrumentationTypesSourceInput): string;
8
+ export {};
@@ -0,0 +1,2 @@
1
+ import{dirname,join,relative}from"node:path";const CHANNEL_INSTRUMENTATION_TYPES_FILE_NAME=`channel-instrumentation-types.d.ts`;function createChannelInstrumentationTypesSource(e){let t=collectChannelInstrumentationDeclarations(e);return[`// Generated by Ash. Do not edit by hand.`,`import type { InferChannelMetadata } from "experimental-ash/channels";`,``,`declare module "experimental-ash/instrumentation" {`,...t.length===0?[` interface ChannelMetadataMap {}`,` interface ChannelReferenceMap {}`]:[` interface ChannelMetadataMap {`,...t.map(e=>` readonly ${JSON.stringify(e.kind)}: InferChannelMetadata<${renderImportedChannelType(e)}>;`),` }`,` interface ChannelReferenceMap {`,...t.map(e=>` readonly ${JSON.stringify(e.kind)}: ${renderImportedChannelType(e)};`),` }`],`}`,``].join(`
2
+ `)}function collectChannelInstrumentationDeclarations(t){let n=dirname(t.typesPath),r=new Map;for(let e of[...t.manifest.channels].filter(e=>e.kind===`channel`).sort(compareCompiledChannels)){let i=`channel:${e.name}`;r.has(i)||r.set(i,{exportName:e.exportName,importSpecifier:createChannelImportSpecifier({agentRoot:t.manifest.agentRoot,channel:e,fromDirectory:n}),kind:i})}return[...r.values()]}function compareCompiledChannels(e,t){return e.name.localeCompare(t.name)||e.logicalPath.localeCompare(t.logicalPath)||(e.exportName??``).localeCompare(t.exportName??``)||e.sourceId.localeCompare(t.sourceId)||e.method.localeCompare(t.method)||e.urlPath.localeCompare(t.urlPath)}function createChannelImportSpecifier(e){let r=join(e.agentRoot,toRuntimeImportLogicalPath(e.channel.logicalPath)),i=relative(e.fromDirectory,r).replaceAll(`\\`,`/`);return i.startsWith(`.`)?i:`./${i}`}function toRuntimeImportLogicalPath(e){return e.endsWith(`.mts`)?`${e.slice(0,-4)}.mjs`:e.endsWith(`.cts`)?`${e.slice(0,-4)}.cjs`:e.endsWith(`.ts`)?`${e.slice(0,-3)}.js`:e}function renderImportedChannelType(e){let t=e.exportName??`default`,n=`import(${JSON.stringify(e.importSpecifier)})`;return isIdentifierName(t)?`typeof ${n}.${t}`:`typeof ${n}[${JSON.stringify(t)}]`}function isIdentifierName(e){return/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(e)}export{CHANNEL_INSTRUMENTATION_TYPES_FILE_NAME,createChannelInstrumentationTypesSource};
@@ -1 +1 @@
1
- import{dirname,join}from"node:path";import{access,mkdir,readFile,writeFile}from"node:fs/promises";import{resolveSandboxCacheDirectory}from"#internal/application/paths.js";import{shellQuote}from"#execution/sandbox/shell-quote.js";import{SandboxTemplateNotProvisionedError}from"#public/definitions/sandbox-backend.js";import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";import{buildSandboxSession}from"#execution/sandbox/session.js";import{bufferToStream,streamToBuffer}from"#execution/sandbox/stream-utils.js";function createLocalSandboxBackend(e={}){return{name:`local`,async prewarm(e){let t=resolveTemplateSnapshotPath(resolveSandboxCacheDirectory(e.runtimeContext.appRoot),e.templateKey);if(await doesPathExist(t))return;let n=await createBashSandbox({sessionKey:e.templateKey,snapshotPath:t}),r=buildSandboxSession(createLocalInternalSandboxSession(n));try{e.bootstrap!==void 0&&await e.bootstrap({use:async()=>r});for(let t of e.seedFiles)typeof t.content==`string`?await r.writeTextFile({content:t.content,path:t.path}):await r.writeBinaryFile({content:t.content,path:t.path});if(await n.captureSnapshot()===null)throw Error(`Failed to capture local sandbox template state for "${e.templateKey}".`)}finally{await n.dispose()}},async create(e){let t=resolveSandboxCacheDirectory(e.runtimeContext.appRoot),n=await readLocalSnapshot(resolveTemplateSnapshotPath(t,e.templateKey));if(n===null)throw new SandboxTemplateNotProvisionedError({backendName:`local`,templateKey:e.templateKey});let r=getLocalSnapshotPath(e.existingMetadata)??resolveSessionSnapshotPath(t,e.sessionKey);return await doesPathExist(r)||await writeLocalSnapshot(r,n),createHandle(await createBashSandbox({sessionKey:e.sessionKey,snapshotPath:r}))}}}async function createBashSandbox(t){let{InMemoryFs:n,Sandbox:r}=await import(`#compiled/just-bash/index.js`),i=await readLocalSnapshot(t.snapshotPath),a=new n(createInitialFiles(i));await ensureLocalSandboxDirectories(a),await restoreLocalSandboxDirectories(a,i?.entries??[]);let o=await r.create({cwd:WORKSPACE_ROOT,env:i?.env,fs:a,network:{dangerouslyAllowFullInternetAccess:!0}});return{async captureSnapshot(){let e=await captureLocalSnapshot({filesystem:a,sandbox:o});return await writeLocalSnapshot(t.snapshotPath,e),{snapshotPath:t.snapshotPath}},async dispose(){await o.stop()},async readFileBytes(e){let t;try{t=await a.readFileBuffer(e)}catch{return null}return Buffer.from(t)},sessionKey:t.sessionKey,snapshotPath:t.snapshotPath,async spawn(e){if(e.abortSignal?.aborted)throw new DOMException(`The operation was aborted.`,`AbortError`);let t=e.workingDirectory===void 0?e.command:`( cd ${shellQuote(e.workingDirectory)} && ${e.command} )`;return adaptJustBashCommandToSandboxProcess(await o.runCommand({args:[t],cmd:`eval`,detached:!0,signal:e.abortSignal}))},async writeFiles(t){for(let n of t){let t=dirname(n.path);await a.mkdir(t,{recursive:!0}),await a.writeFile(n.path,n.content)}}}}function adaptJustBashCommandToSandboxProcess(e){let t=new TextEncoder,n,r,i=!1,a,o=new ReadableStream({start(e){n=e}}),s=new ReadableStream({start(e){r=e}});return(async()=>{try{for await(let i of e.logs()){let e=t.encode(i.data);i.type===`stdout`?n?.enqueue(e):r?.enqueue(e)}}catch(e){a=e,n?.error(e),r?.error(e)}finally{i=!0,a===void 0&&(n?.close(),r?.close())}})(),{stdout:o,stderr:s,async wait(){let t=await e.wait();for(;!i;)await new Promise(e=>setTimeout(e,0));if(a!==void 0)throw a;return{exitCode:t.exitCode}},async kill(){await e.kill()}}}function createHandle(e){let t=buildSandboxSession(createLocalInternalSandboxSession(e));return{session:t,useSessionFn:async()=>t,async captureState(){return{backendName:`local`,metadata:await e.captureSnapshot()??{},sessionKey:e.sessionKey}},async dispose(){await e.dispose()}}}function createLocalInternalSandboxSession(e){return{id:e.sessionKey,resolvePath:resolveLocalPath,async spawn(t){return await e.spawn(t)},async readFile(t){let n=await e.readFileBytes(t.path);return n===null?null:bufferToStream(n)},async writeFile(t){let n=await streamToBuffer(t.content);await e.writeFiles([{content:n,path:t.path}])}}}function resolveLocalPath(e){return e.startsWith(`/`)?e:`${WORKSPACE_ROOT}/${e}`}function resolveTemplateSnapshotPath(e,n){return join(e,`local`,`templates`,`${n}.json`)}function resolveSessionSnapshotPath(e,n){return join(e,`local`,`sessions`,`${n}.json`)}function createInitialFiles(e){let t={};for(let n of e?.entries??[])n.kind===`file`&&(t[n.path]=Buffer.from(n.contentBase64,`base64`));return t}async function ensureLocalSandboxDirectories(e){await e.mkdir(WORKSPACE_ROOT,{recursive:!0})}async function restoreLocalSandboxDirectories(e,t){let n=t.filter(e=>e.kind===`directory`).map(e=>e.path).sort((e,t)=>e.localeCompare(t));for(let t of n)t!==WORKSPACE_ROOT&&await e.mkdir(t,{recursive:!0})}async function captureLocalSnapshot(e){let t=[],n=e.filesystem.getAllPaths().sort((e,t)=>e.localeCompare(t));for(let r of n){let n=await e.filesystem.stat(r);if(n.isSymbolicLink)continue;if(n.isDirectory){t.push({kind:`directory`,path:r});continue}if(!n.isFile)continue;let i=await e.filesystem.readFileBuffer(r);t.push({contentBase64:Buffer.from(i).toString(`base64`),kind:`file`,path:r})}return{entries:t,env:{...e.sandbox.bashEnvInstance.getEnv()},version:1}}async function readLocalSnapshot(e){if(!await doesPathExist(e))return null;let t=JSON.parse(await readFile(e,`utf8`));return t.version===1?t:null}async function writeLocalSnapshot(t,n){await mkdir(dirname(t),{recursive:!0}),await writeFile(t,`${JSON.stringify(n,null,2)}\n`)}async function doesPathExist(e){try{return await access(e),!0}catch{return!1}}function getLocalSnapshotPath(e){let t=e?.snapshotPath;return typeof t==`string`?t:void 0}export{createLocalSandboxBackend};
1
+ import{dirname,join}from"node:path";import{access,mkdir,readFile,writeFile}from"node:fs/promises";import{resolveSandboxCacheDirectory}from"#internal/application/paths.js";import{shellQuote}from"#execution/sandbox/shell-quote.js";import{SandboxTemplateNotProvisionedError}from"#public/definitions/sandbox-backend.js";import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";import{buildSandboxSession}from"#execution/sandbox/session.js";import{bufferToStream,streamToBuffer}from"#execution/sandbox/stream-utils.js";function createLocalSandboxBackend(e={}){return{name:`local`,async prewarm(e){let t=resolveTemplateSnapshotPath(resolveSandboxCacheDirectory(e.runtimeContext.appRoot),e.templateKey);if(await doesPathExist(t))return;let n=await createBashSandbox({sessionKey:e.templateKey,snapshotPath:t}),r=buildSandboxSession(createLocalInternalSandboxSession(n),localSetNetworkPolicyUnsupported);try{e.bootstrap!==void 0&&await e.bootstrap({use:async()=>r});for(let t of e.seedFiles)typeof t.content==`string`?await r.writeTextFile({content:t.content,path:t.path}):await r.writeBinaryFile({content:t.content,path:t.path});if(await n.captureSnapshot()===null)throw Error(`Failed to capture local sandbox template state for "${e.templateKey}".`)}finally{await n.dispose()}},async create(e){let t=resolveSandboxCacheDirectory(e.runtimeContext.appRoot),n=await readLocalSnapshot(resolveTemplateSnapshotPath(t,e.templateKey));if(n===null)throw new SandboxTemplateNotProvisionedError({backendName:`local`,templateKey:e.templateKey});let r=getLocalSnapshotPath(e.existingMetadata)??resolveSessionSnapshotPath(t,e.sessionKey);return await doesPathExist(r)||await writeLocalSnapshot(r,n),createHandle(await createBashSandbox({sessionKey:e.sessionKey,snapshotPath:r}))}}}async function createBashSandbox(t){let{InMemoryFs:n,Sandbox:r}=await import(`#compiled/just-bash/index.js`),i=await readLocalSnapshot(t.snapshotPath),a=new n(createInitialFiles(i));await ensureLocalSandboxDirectories(a),await restoreLocalSandboxDirectories(a,i?.entries??[]);let o=await r.create({cwd:WORKSPACE_ROOT,env:i?.env,fs:a,network:{dangerouslyAllowFullInternetAccess:!0}});return{async captureSnapshot(){let e=await captureLocalSnapshot({filesystem:a,sandbox:o});return await writeLocalSnapshot(t.snapshotPath,e),{snapshotPath:t.snapshotPath}},async dispose(){await o.stop()},async readFileBytes(e){let t;try{t=await a.readFileBuffer(e)}catch{return null}return Buffer.from(t)},sessionKey:t.sessionKey,snapshotPath:t.snapshotPath,async spawn(e){if(e.abortSignal?.aborted)throw new DOMException(`The operation was aborted.`,`AbortError`);let t=e.workingDirectory===void 0?e.command:`( cd ${shellQuote(e.workingDirectory)} && ${e.command} )`;return adaptJustBashCommandToSandboxProcess(await o.runCommand({args:[t],cmd:`eval`,detached:!0,signal:e.abortSignal}))},async writeFiles(t){for(let n of t){let t=dirname(n.path);await a.mkdir(t,{recursive:!0}),await a.writeFile(n.path,n.content)}}}}function adaptJustBashCommandToSandboxProcess(e){let t=new TextEncoder,n,r,i=!1,a,o=new ReadableStream({start(e){n=e}}),s=new ReadableStream({start(e){r=e}});return(async()=>{try{for await(let i of e.logs()){let e=t.encode(i.data);i.type===`stdout`?n?.enqueue(e):r?.enqueue(e)}}catch(e){a=e,n?.error(e),r?.error(e)}finally{i=!0,a===void 0&&(n?.close(),r?.close())}})(),{stdout:o,stderr:s,async wait(){let t=await e.wait();for(;!i;)await new Promise(e=>setTimeout(e,0));if(a!==void 0)throw a;return{exitCode:t.exitCode}},async kill(){await e.kill()}}}async function localSetNetworkPolicyUnsupported(){throw Error(`setNetworkPolicy() is not supported on the local sandbox backend. just-bash applies its network policy only at sandbox creation (no run-time update) and does not run git or other binaries. Use the Vercel backend for credential brokering and egress control.`)}function createHandle(e){let t=buildSandboxSession(createLocalInternalSandboxSession(e),localSetNetworkPolicyUnsupported);return{session:t,useSessionFn:async()=>t,async captureState(){return{backendName:`local`,metadata:await e.captureSnapshot()??{},sessionKey:e.sessionKey}},async dispose(){await e.dispose()}}}function createLocalInternalSandboxSession(e){return{id:e.sessionKey,resolvePath:resolveLocalPath,async spawn(t){return await e.spawn(t)},async readFile(t){let n=await e.readFileBytes(t.path);return n===null?null:bufferToStream(n)},async writeFile(t){let n=await streamToBuffer(t.content);await e.writeFiles([{content:n,path:t.path}])}}}function resolveLocalPath(e){return e.startsWith(`/`)?e:`${WORKSPACE_ROOT}/${e}`}function resolveTemplateSnapshotPath(e,n){return join(e,`local`,`templates`,`${n}.json`)}function resolveSessionSnapshotPath(e,n){return join(e,`local`,`sessions`,`${n}.json`)}function createInitialFiles(e){let t={};for(let n of e?.entries??[])n.kind===`file`&&(t[n.path]=Buffer.from(n.contentBase64,`base64`));return t}async function ensureLocalSandboxDirectories(e){await e.mkdir(WORKSPACE_ROOT,{recursive:!0})}async function restoreLocalSandboxDirectories(e,t){let n=t.filter(e=>e.kind===`directory`).map(e=>e.path).sort((e,t)=>e.localeCompare(t));for(let t of n)t!==WORKSPACE_ROOT&&await e.mkdir(t,{recursive:!0})}async function captureLocalSnapshot(e){let t=[],n=e.filesystem.getAllPaths().sort((e,t)=>e.localeCompare(t));for(let r of n){let n=await e.filesystem.stat(r);if(n.isSymbolicLink)continue;if(n.isDirectory){t.push({kind:`directory`,path:r});continue}if(!n.isFile)continue;let i=await e.filesystem.readFileBuffer(r);t.push({contentBase64:Buffer.from(i).toString(`base64`),kind:`file`,path:r})}return{entries:t,env:{...e.sandbox.bashEnvInstance.getEnv()},version:1}}async function readLocalSnapshot(e){if(!await doesPathExist(e))return null;let t=JSON.parse(await readFile(e,`utf8`));return t.version===1?t:null}async function writeLocalSnapshot(t,n){await mkdir(dirname(t),{recursive:!0}),await writeFile(t,`${JSON.stringify(n,null,2)}\n`)}async function doesPathExist(e){try{return await access(e),!0}catch{return!1}}function getLocalSnapshotPath(e){let t=e?.snapshotPath;return typeof t==`string`?t:void 0}export{createLocalSandboxBackend};
@@ -1 +1 @@
1
- import{SandboxTemplateNotProvisionedError}from"#public/definitions/sandbox-backend.js";import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";import{buildSandboxSession}from"#execution/sandbox/session.js";import{streamToBuffer}from"#execution/sandbox/stream-utils.js";function createVercelSandboxBackend(t={}){let n=t.loadSandboxModule??(async()=>await import(`#compiled/@vercel/sandbox/index.js`)),r={timeout:DEFAULT_SANDBOX_TIMEOUT_MS,...t.createOptions},i=new Map;return{name:`vercel`,async create(t){let a=resolveVercelSandboxTags(r.tags,t.tags),o;try{o=await readTemplate({loadSandboxModule:n,prewarmedTemplates:i,templateKey:t.templateKey})}catch(n){throw SandboxTemplateNotProvisionedError.is(n)?n:Error(`Failed to read sandbox template "${t.templateKey}": ${errorMessage(n)}`,{cause:n})}let s;try{s=await ensureSession({createOptions:r,existingMetadata:t.existingMetadata,sandboxModule:await n(),sessionKey:t.sessionKey,snapshotId:o.snapshotId,tags:a})}catch(e){throw Error(`Failed to create sandbox session "${t.sessionKey}": ${errorMessage(e)}`,{cause:e})}return createHandle(s,t.sessionKey)},async prewarm(e){let t;try{t=await ensureTemplate({bootstrap:e.bootstrap,createOptions:r,loadSandboxModule:n,seedFiles:e.seedFiles,templateKey:e.templateKey})}catch(t){throw Error(`Failed to prewarm Vercel sandbox template "${e.templateKey}": ${errorMessage(t)}. Run \`vercel login\` and \`vercel link\` so the SDK can authenticate, or set VERCEL_TOKEN.`,{cause:t})}i.set(e.templateKey,t)}}}async function readTemplate(t){let n=t.prewarmedTemplates.get(t.templateKey);if(n!==void 0)return n;let r=await getNamedSandbox(await t.loadSandboxModule(),t.templateKey);if(r===null||typeof r.currentSnapshotId!=`string`)throw new SandboxTemplateNotProvisionedError({backendName:`vercel`,templateKey:t.templateKey});return{sandboxName:r.name,snapshotId:r.currentSnapshotId,templateKey:t.templateKey}}async function ensureTemplate(e){let t=await e.loadSandboxModule(),r=await getNamedSandbox(t,e.templateKey),i=resolveVercelSandboxTags(e.createOptions.tags,e.tags);if(r===null){let n={...e.createOptions,name:e.templateKey,persistent:!1};i!==void 0&&(n.tags=i),r=await t.Sandbox.create(n)}else await ensureVercelSandboxTags(r,i);let a=extractAuthorSnapshotId(e.createOptions);if(typeof r.currentSnapshotId==`string`&&r.currentSnapshotId.length>0&&r.currentSnapshotId!==a)return{sandboxName:r.name,snapshotId:r.currentSnapshotId,templateKey:e.templateKey};await ensureSandboxWorkingDirectory(r,e.createOptions);let o=buildSandboxSession(createVercelInternalSandboxSession(r,e.templateKey));e.bootstrap!==void 0&&await e.bootstrap({use:async e=>(e!==void 0&&await r.update(e),o)});for(let t of e.seedFiles)typeof t.content==`string`?await o.writeTextFile({content:t.content,path:t.path}):await o.writeBinaryFile({content:t.content,path:t.path});let s=await r.snapshot();return{sandboxName:r.name,snapshotId:s.snapshotId,templateKey:e.templateKey}}async function ensureSession(e){let t=getVercelSandboxName(e.existingMetadata)??e.sessionKey,n=await getNamedSandbox(e.sandboxModule,t);if(n!==null)return await ensureVercelSandboxTags(n,e.tags),n;let{runtime:r,source:i,...a}=e.createOptions,o={...a,name:t,persistent:!0,source:{snapshotId:e.snapshotId,type:`snapshot`}};return e.tags!==void 0&&(o.tags=e.tags),await e.sandboxModule.Sandbox.create(o)}function createHandle(e,t){return{session:buildSandboxSession(createVercelInternalSandboxSession(e,t)),useSessionFn:async r=>(r!==void 0&&await e.update(r),buildSandboxSession(createVercelInternalSandboxSession(e,t))),async captureState(){return{backendName:`vercel`,metadata:{sandboxName:e.name},sessionKey:t}},async dispose(){}}}function createVercelInternalSandboxSession(e,n){return{id:n,resolvePath:resolveVercelSandboxPath,async spawn(n){return adaptVercelCommandToSandboxProcess(await e.runCommand({args:[`-lc`,n.command],cmd:`bash`,cwd:n.workingDirectory??WORKSPACE_ROOT,detached:!0,signal:n.abortSignal}))},async readFile(t){return await e.readFile({path:t.path})??null},async writeFile(t){let n=await streamToBuffer(t.content);await e.writeFiles([{content:n,path:t.path}])}}}function adaptVercelCommandToSandboxProcess(e){let t=new TextEncoder,n,r,i=!1,a,o=new ReadableStream({start(e){n=e}}),s=new ReadableStream({start(e){r=e}});return(async()=>{try{for await(let i of e.logs()){let e=t.encode(i.data);i.stream===`stdout`?n?.enqueue(e):r?.enqueue(e)}}catch(e){a=e,n?.error(e),r?.error(e)}finally{i=!0,a===void 0&&(n?.close(),r?.close())}})(),{stdout:o,stderr:s,async wait(){let t=await e.wait();for(;!i;)await new Promise(e=>setTimeout(e,0));if(a!==void 0)throw a;return{exitCode:t.exitCode}},async kill(){await e.kill()}}}function resolveVercelSandboxPath(e){return e.startsWith(`/`)?e:`${WORKSPACE_ROOT}/${e}`}async function ensureSandboxWorkingDirectory(e,n){await runSandboxBootstrapStep(e,{failureMessage:`Failed to initialize Vercel sandbox workspace.`,script:`mkdir -p ${WORKSPACE_ROOT} && chown ${SANDBOX_USER}:${SANDBOX_USER} ${WORKSPACE_ROOT}`}),n.networkPolicy!==`deny-all`&&await runSandboxBootstrapStep(e,{failureMessage:`Failed to install ripgrep in Vercel sandbox.`,script:`command -v rg >/dev/null 2>&1 || { dnf install -y spal-release && dnf install -y ripgrep; }`})}async function runSandboxBootstrapStep(e,t){let n=await e.runCommand({args:[`-lc`,t.script],cmd:`bash`,sudo:!0});if(n.exitCode!==0){let e=await n.stderr();throw Error(`${t.failureMessage} ${e}`.trim())}}const SANDBOX_USER=`vercel-sandbox`;async function getNamedSandbox(e,t){try{return await e.Sandbox.get({name:t})}catch(e){if(isSandboxMissingError(e))return null;throw Error(`Failed to look up Vercel sandbox "${t}": ${errorMessage(e)}`,{cause:e})}}function isSandboxMissingError(e){return e instanceof Error?(e.response?.status??e.cause?.response?.status)===404:!1}function extractAuthorSnapshotId(e){let t=e.source;if(t?.type===`snapshot`&&typeof t.snapshotId==`string`)return t.snapshotId}function getVercelSandboxName(e){let t=e?.sandboxName;return typeof t==`string`?t:void 0}function resolveVercelSandboxTags(e,t){let n={};if(e!==void 0)for(let[t,r]of Object.entries(e))n[t]=r;if(t!==void 0)for(let[e,r]of Object.entries(t))n[e]=r;let r=Object.keys(n).length;if(r!==0){if(r>VERCEL_SANDBOX_TAG_LIMIT)throw Error(`Vercel Sandbox supports at most ${VERCEL_SANDBOX_TAG_LIMIT} tags. Ash reserves "agent", "channel", and "sessionId"; remove or consolidate custom tags passed to vercelBackend().`);return n}}async function ensureVercelSandboxTags(e,t){t===void 0||areVercelSandboxTagsEqual(e.tags,t)||await e.update({tags:t})}function areVercelSandboxTagsEqual(e,t){let n=e??{},r=Object.entries(n),i=Object.entries(t);return r.length===i.length?i.every(([e,t])=>n[e]===t):!1}function errorMessage(e){return e instanceof Error?e.message:String(e)}const DEFAULT_SANDBOX_TIMEOUT_MS=1800*1e3,VERCEL_SANDBOX_TAG_LIMIT=5;export{createVercelSandboxBackend};
1
+ import{SandboxTemplateNotProvisionedError}from"#public/definitions/sandbox-backend.js";import{WORKSPACE_ROOT}from"#runtime/workspace/types.js";import{buildSandboxSession}from"#execution/sandbox/session.js";import{streamToBuffer}from"#execution/sandbox/stream-utils.js";function createVercelSandboxBackend(t={}){let n=t.loadSandboxModule??(async()=>await import(`#compiled/@vercel/sandbox/index.js`)),r={timeout:DEFAULT_SANDBOX_TIMEOUT_MS,...t.createOptions},i=new Map;return{name:`vercel`,async create(t){let a=resolveVercelSandboxTags(r.tags,t.tags),o;try{o=await readTemplate({loadSandboxModule:n,prewarmedTemplates:i,templateKey:t.templateKey})}catch(n){throw SandboxTemplateNotProvisionedError.is(n)?n:Error(`Failed to read sandbox template "${t.templateKey}": ${errorMessage(n)}`,{cause:n})}let s;try{s=await ensureSession({createOptions:r,existingMetadata:t.existingMetadata,sandboxModule:await n(),sessionKey:t.sessionKey,snapshotId:o.snapshotId,tags:a})}catch(e){throw Error(`Failed to create sandbox session "${t.sessionKey}": ${errorMessage(e)}`,{cause:e})}return createHandle(s,t.sessionKey)},async prewarm(e){let t;try{t=await ensureTemplate({bootstrap:e.bootstrap,createOptions:r,loadSandboxModule:n,seedFiles:e.seedFiles,templateKey:e.templateKey})}catch(t){throw Error(`Failed to prewarm Vercel sandbox template "${e.templateKey}": ${errorMessage(t)}. Run \`vercel login\` and \`vercel link\` so the SDK can authenticate, or set VERCEL_TOKEN.`,{cause:t})}i.set(e.templateKey,t)}}}async function readTemplate(t){let n=t.prewarmedTemplates.get(t.templateKey);if(n!==void 0)return n;let r=await getNamedSandbox(await t.loadSandboxModule(),t.templateKey);if(r===null||typeof r.currentSnapshotId!=`string`)throw new SandboxTemplateNotProvisionedError({backendName:`vercel`,templateKey:t.templateKey});return{sandboxName:r.name,snapshotId:r.currentSnapshotId,templateKey:t.templateKey}}async function ensureTemplate(e){let t=await e.loadSandboxModule(),r=await getNamedSandbox(t,e.templateKey),i=resolveVercelSandboxTags(e.createOptions.tags,e.tags);if(r===null){let n={...e.createOptions,name:e.templateKey,persistent:!1};i!==void 0&&(n.tags=i),r=await t.Sandbox.create(n)}else await ensureVercelSandboxTags(r,i);let a=extractAuthorSnapshotId(e.createOptions);if(typeof r.currentSnapshotId==`string`&&r.currentSnapshotId.length>0&&r.currentSnapshotId!==a)return{sandboxName:r.name,snapshotId:r.currentSnapshotId,templateKey:e.templateKey};await ensureSandboxWorkingDirectory(r,e.createOptions);let o=buildSandboxSession(createVercelInternalSandboxSession(r,e.templateKey),createVercelNetworkPolicySetter(r));e.bootstrap!==void 0&&await e.bootstrap({use:async e=>(e!==void 0&&await r.update(e),o)});for(let t of e.seedFiles)typeof t.content==`string`?await o.writeTextFile({content:t.content,path:t.path}):await o.writeBinaryFile({content:t.content,path:t.path});let s=await r.snapshot();return{sandboxName:r.name,snapshotId:s.snapshotId,templateKey:e.templateKey}}async function ensureSession(e){let t=getVercelSandboxName(e.existingMetadata)??e.sessionKey,n=await getNamedSandbox(e.sandboxModule,t);if(n!==null)return await ensureVercelSandboxTags(n,e.tags),n;let{runtime:r,source:i,...a}=e.createOptions,o={...a,name:t,persistent:!0,source:{snapshotId:e.snapshotId,type:`snapshot`}};return e.tags!==void 0&&(o.tags=e.tags),await e.sandboxModule.Sandbox.create(o)}function createHandle(e,t){return{session:buildSandboxSession(createVercelInternalSandboxSession(e,t),createVercelNetworkPolicySetter(e)),useSessionFn:async r=>(r!==void 0&&await e.update(r),buildSandboxSession(createVercelInternalSandboxSession(e,t),createVercelNetworkPolicySetter(e))),async captureState(){return{backendName:`vercel`,metadata:{sandboxName:e.name},sessionKey:t}},async dispose(){}}}function createVercelNetworkPolicySetter(e){return async t=>{await e.update({networkPolicy:t})}}function createVercelInternalSandboxSession(e,n){return{id:n,resolvePath:resolveVercelSandboxPath,async spawn(n){return adaptVercelCommandToSandboxProcess(await e.runCommand({args:[`-lc`,n.command],cmd:`bash`,cwd:n.workingDirectory??WORKSPACE_ROOT,detached:!0,signal:n.abortSignal}))},async readFile(t){return await e.readFile({path:t.path})??null},async writeFile(t){let n=await streamToBuffer(t.content);await e.writeFiles([{content:n,path:t.path}])}}}function adaptVercelCommandToSandboxProcess(e){let t=new TextEncoder,n,r,i=!1,a,o=new ReadableStream({start(e){n=e}}),s=new ReadableStream({start(e){r=e}});return(async()=>{try{for await(let i of e.logs()){let e=t.encode(i.data);i.stream===`stdout`?n?.enqueue(e):r?.enqueue(e)}}catch(e){a=e,n?.error(e),r?.error(e)}finally{i=!0,a===void 0&&(n?.close(),r?.close())}})(),{stdout:o,stderr:s,async wait(){let t=await e.wait();for(;!i;)await new Promise(e=>setTimeout(e,0));if(a!==void 0)throw a;return{exitCode:t.exitCode}},async kill(){await e.kill()}}}function resolveVercelSandboxPath(e){return e.startsWith(`/`)?e:`${WORKSPACE_ROOT}/${e}`}async function ensureSandboxWorkingDirectory(e,n){await runSandboxBootstrapStep(e,{failureMessage:`Failed to initialize Vercel sandbox workspace.`,script:`mkdir -p ${WORKSPACE_ROOT} && chown ${SANDBOX_USER}:${SANDBOX_USER} ${WORKSPACE_ROOT}`}),n.networkPolicy!==`deny-all`&&await runSandboxBootstrapStep(e,{failureMessage:`Failed to install ripgrep in Vercel sandbox.`,script:`command -v rg >/dev/null 2>&1 || { dnf install -y spal-release && dnf install -y ripgrep; }`})}async function runSandboxBootstrapStep(e,t){let n=await e.runCommand({args:[`-lc`,t.script],cmd:`bash`,sudo:!0});if(n.exitCode!==0){let e=await n.stderr();throw Error(`${t.failureMessage} ${e}`.trim())}}const SANDBOX_USER=`vercel-sandbox`;async function getNamedSandbox(e,t){try{return await e.Sandbox.get({name:t})}catch(e){if(isSandboxMissingError(e))return null;throw Error(`Failed to look up Vercel sandbox "${t}": ${errorMessage(e)}`,{cause:e})}}function isSandboxMissingError(e){return e instanceof Error?(e.response?.status??e.cause?.response?.status)===404:!1}function extractAuthorSnapshotId(e){let t=e.source;if(t?.type===`snapshot`&&typeof t.snapshotId==`string`)return t.snapshotId}function getVercelSandboxName(e){let t=e?.sandboxName;return typeof t==`string`?t:void 0}function resolveVercelSandboxTags(e,t){let n={};if(e!==void 0)for(let[t,r]of Object.entries(e))n[t]=r;if(t!==void 0)for(let[e,r]of Object.entries(t))n[e]=r;let r=Object.keys(n).length;if(r!==0){if(r>VERCEL_SANDBOX_TAG_LIMIT)throw Error(`Vercel Sandbox supports at most ${VERCEL_SANDBOX_TAG_LIMIT} tags. Ash reserves "agent", "channel", and "sessionId"; remove or consolidate custom tags passed to vercelBackend().`);return n}}async function ensureVercelSandboxTags(e,t){t===void 0||areVercelSandboxTagsEqual(e.tags,t)||await e.update({tags:t})}function areVercelSandboxTagsEqual(e,t){let n=e??{},r=Object.entries(n),i=Object.entries(t);return r.length===i.length?i.every(([e,t])=>n[e]===t):!1}function errorMessage(e){return e instanceof Error?e.message:String(e)}const DEFAULT_SANDBOX_TIMEOUT_MS=1800*1e3,VERCEL_SANDBOX_TAG_LIMIT=5;export{createVercelSandboxBackend};
@@ -1,4 +1,5 @@
1
1
  import type { InternalSandboxSession, SandboxSession } from "#shared/sandbox-session.js";
2
+ import type { SandboxNetworkPolicy } from "#shared/sandbox-network-policy.js";
2
3
  export type { InternalSandboxSession };
3
4
  /**
4
5
  * Builds a public {@link SandboxSession} from backend-specific primitives.
@@ -8,5 +9,9 @@ export type { InternalSandboxSession };
8
9
  * read/write primitives. `run` is implemented as a thin wrapper over the
9
10
  * backend's `spawn`: collect stdout/stderr to strings, await `wait()`,
10
11
  * then return the combined result.
12
+ *
13
+ * `setNetworkPolicy` applies a firewall policy to the live sandbox. It
14
+ * defaults to a no-op so backends without a firewall (and test doubles)
15
+ * need not supply one; the Vercel backend wires it to `sandbox.update`.
11
16
  */
12
- export declare function buildSandboxSession(primitives: InternalSandboxSession): SandboxSession;
17
+ export declare function buildSandboxSession(primitives: InternalSandboxSession, setNetworkPolicy?: (policy: SandboxNetworkPolicy) => Promise<void>): SandboxSession;
@@ -1,3 +1,3 @@
1
- import{bufferToStream,streamToBuffer}from"./stream-utils.js";function buildSandboxSession(n){async function run(e){let t=await n.spawn(e),[r,i,{exitCode:a}]=await Promise.all([collectStreamToString(t.stdout),collectStreamToString(t.stderr),t.wait()]);return{exitCode:a,stderr:i,stdout:r}}return{id:n.id,resolvePath(e){return n.resolvePath(e)},run,async spawn(e){return await n.spawn(e)},async readFile(e){return await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)})},async readBinaryFile(e){let r=await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)});return r===null?null:await streamToBuffer(r)},async readTextFile(e){validateReadTextFileOptions(e);let r=await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)});return r===null?null:applyLineRange(decodeBytes(await streamToBuffer(r),e.encoding??`utf-8`),e)},async writeFile(e){await n.writeFile({abortSignal:e.abortSignal,content:e.content,path:n.resolvePath(e.path)})},async writeBinaryFile(t){await n.writeFile({abortSignal:t.abortSignal,content:bufferToStream(t.content),path:n.resolvePath(t.path)})},async writeTextFile(t){let r=encodeString(t.content,t.encoding??`utf-8`);await n.writeFile({abortSignal:t.abortSignal,content:bufferToStream(r),path:n.resolvePath(t.path)})}}}async function collectStreamToString(e){let n=await streamToBuffer(e);return new TextDecoder().decode(n)}function validateReadTextFileOptions(e){let{startLine:t,endLine:n}=e;if(t!==void 0&&(!Number.isInteger(t)||t<1))throw Error(`startLine must be a positive integer (1-based).`);if(n!==void 0&&(!Number.isInteger(n)||n<1))throw Error(`endLine must be a positive integer (1-based).`);if(t!==void 0&&n!==void 0&&t>n)throw Error(`startLine must not be greater than endLine.`)}function splitLinesPreservingEndings(e){let t=[],n=0;for(let r=0;r<e.length;r++)e[r]===`\r`?r+1<e.length&&e[r+1]===`
1
+ import{bufferToStream,streamToBuffer}from"./stream-utils.js";function buildSandboxSession(n,r=async()=>{}){async function run(e){let t=await n.spawn(e),[r,i,{exitCode:a}]=await Promise.all([collectStreamToString(t.stdout),collectStreamToString(t.stderr),t.wait()]);return{exitCode:a,stderr:i,stdout:r}}return{id:n.id,resolvePath(e){return n.resolvePath(e)},run,async spawn(e){return await n.spawn(e)},async readFile(e){return await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)})},async readBinaryFile(e){let r=await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)});return r===null?null:await streamToBuffer(r)},async readTextFile(e){validateReadTextFileOptions(e);let r=await n.readFile({abortSignal:e.abortSignal,path:n.resolvePath(e.path)});return r===null?null:applyLineRange(decodeBytes(await streamToBuffer(r),e.encoding??`utf-8`),e)},async writeFile(e){await n.writeFile({abortSignal:e.abortSignal,content:e.content,path:n.resolvePath(e.path)})},async writeBinaryFile(t){await n.writeFile({abortSignal:t.abortSignal,content:bufferToStream(t.content),path:n.resolvePath(t.path)})},async writeTextFile(t){let r=encodeString(t.content,t.encoding??`utf-8`);await n.writeFile({abortSignal:t.abortSignal,content:bufferToStream(r),path:n.resolvePath(t.path)})},setNetworkPolicy:r}}async function collectStreamToString(e){let n=await streamToBuffer(e);return new TextDecoder().decode(n)}function validateReadTextFileOptions(e){let{startLine:t,endLine:n}=e;if(t!==void 0&&(!Number.isInteger(t)||t<1))throw Error(`startLine must be a positive integer (1-based).`);if(n!==void 0&&(!Number.isInteger(n)||n<1))throw Error(`endLine must be a positive integer (1-based).`);if(t!==void 0&&n!==void 0&&t>n)throw Error(`startLine must not be greater than endLine.`)}function splitLinesPreservingEndings(e){let t=[],n=0;for(let r=0;r<e.length;r++)e[r]===`\r`?r+1<e.length&&e[r+1]===`
2
2
  `?(t.push(e.slice(n,r+2)),n=r+2,r++):(t.push(e.slice(n,r+1)),n=r+1):e[r]===`
3
3
  `&&(t.push(e.slice(n,r+1)),n=r+1);return n<e.length&&t.push(e.slice(n)),t}function applyLineRange(e,t){if(t.startLine===void 0&&t.endLine===void 0)return e;let n=splitLinesPreservingEndings(e),r=n.length,i=t.startLine??1,a=Math.min(t.endLine??r,r);return i>r?``:n.slice(i-1,a).join(``)}function decodeBytes(e,t){return t===`utf-8`||t===`utf8`?new TextDecoder(`utf-8`,{fatal:!0}).decode(e):Buffer.from(e.buffer,e.byteOffset,e.byteLength).toString(t)}function encodeString(e,t){return t===`utf-8`||t===`utf8`?new TextEncoder().encode(e):Buffer.from(e,t)}export{buildSandboxSession};
@@ -1 +1 @@
1
- import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.43.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.44.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
@@ -1,4 +1,4 @@
1
- import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.43.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
1
+ import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.44.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
2
2
  import { slackChannel } from "experimental-ash/channels/slack";
3
3
 
4
4
  export default slackChannel({
@@ -4705,6 +4705,7 @@ export default config;
4705
4705
  "next-env.d.ts",
4706
4706
  "**/*.ts",
4707
4707
  "**/*.tsx",
4708
+ ".ash/**/*.d.ts",
4708
4709
  ".next/types/**/*.ts",
4709
4710
  ".next/dev/types/**/*.ts"
4710
4711
  ],
@@ -17,9 +17,12 @@ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessa
17
17
  export interface DiscordContext {
18
18
  readonly discord: DiscordHandle;
19
19
  }
20
- /** Event-handler Discord context, including mutable per-conversation state. */
21
- export interface DiscordEventContext extends DiscordContext, ChannelSessionOps {
20
+ /** Channel-owned Discord context returned by `context()`. */
21
+ export type DiscordChannelContext = DiscordContext & {
22
22
  state: DiscordChannelState;
23
+ };
24
+ /** Event-handler Discord context, including session operations. */
25
+ export interface DiscordEventContext extends DiscordChannelContext, ChannelSessionOps {
23
26
  }
24
27
  /** JSON-serializable Discord channel state. */
25
28
  export interface DiscordChannelState {
@@ -1 +1 @@
1
- export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
1
+ export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
@@ -30,15 +30,13 @@ export interface SlackContext {
30
30
  readonly thread: SlackThread;
31
31
  readonly slack: SlackHandle;
32
32
  }
33
- /**
34
- * Event-handler Slack context — extends {@link SlackContext} with the
35
- * runtime's mutable channel state. Handlers can read or mutate
36
- * `state` directly; the runtime auto-snapshots the result at step
37
- * boundaries.
38
- */
39
- export interface SlackEventContext extends SlackContext, ChannelSessionOps {
33
+ /** Channel-owned Slack context returned by `context()`. */
34
+ export interface SlackChannelContext extends SlackContext {
40
35
  state: SlackChannelState;
41
36
  }
37
+ /** Event-handler Slack context, including session operations. */
38
+ export interface SlackEventContext extends SlackChannelContext, ChannelSessionOps {
39
+ }
42
40
  export type { SlackApiResponse, SlackBotToken, SlackHandle, SlackThread, } from "#public/channels/slack/api.js";
43
41
  export type { SlackWebhookVerifier } from "#public/channels/slack/verify.js";
44
42
  type SlackEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: SlackEventContext, ctx: SessionContext) => void | Promise<void>;
@@ -294,7 +292,7 @@ export interface SlackChannelConfig {
294
292
  * default-export a `slackChannel(...)` call under `declaration: true`
295
293
  * without TypeScript falling back to an internal path for `Channel`.
296
294
  */
297
- export interface SlackChannel extends Channel<SlackChannelState, SlackReceiveArgs> {
295
+ export interface SlackChannel extends Channel<SlackChannelState, SlackReceiveArgs, SlackInstrumentationMetadata> {
298
296
  }
299
297
  /**
300
298
  * Slack channel factory. Wires up the Slack webhook route, mention
@@ -19,11 +19,14 @@ export interface TeamsContext {
19
19
  readonly teams: TeamsHandle;
20
20
  readonly thread: TeamsThread;
21
21
  }
22
- /** Event-handler Teams context, including mutable per-conversation state. */
23
- export interface TeamsEventContext extends TeamsContext, ChannelSessionOps {
22
+ /** Channel-owned Teams context returned by `context()`. */
23
+ export interface TeamsChannelContext extends TeamsContext {
24
24
  readonly adaptiveCardVersion: string;
25
25
  state: TeamsChannelState;
26
26
  }
27
+ /** Event-handler Teams context, including session operations. */
28
+ export interface TeamsEventContext extends TeamsChannelContext, ChannelSessionOps {
29
+ }
27
30
  /** JSON-serializable Teams channel state. */
28
31
  export interface TeamsChannelState {
29
32
  /** Bot account captured from the inbound activity recipient. */
@@ -19,10 +19,13 @@ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessa
19
19
  export interface TelegramContext {
20
20
  readonly telegram: TelegramHandle;
21
21
  }
22
- /** Event-handler Telegram context, including mutable per-conversation state. */
23
- export interface TelegramEventContext extends TelegramContext, ChannelSessionOps {
22
+ /** Channel-owned Telegram context returned by `context()`. */
23
+ export interface TelegramChannelContext extends TelegramContext {
24
24
  state: TelegramChannelState;
25
25
  }
26
+ /** Event-handler Telegram context, including session operations. */
27
+ export interface TelegramEventContext extends TelegramChannelContext, ChannelSessionOps {
28
+ }
26
29
  /** JSON-serializable Telegram channel state. */
27
30
  export interface TelegramChannelState extends TelegramHitlState {
28
31
  /** Telegram bot username used for group mention detection, when configured. */
@@ -15,10 +15,13 @@ type EventData<T extends HandleMessageStreamEvent["type"]> = Extract<HandleMessa
15
15
  export interface TwilioContext {
16
16
  readonly twilio: TwilioHandle;
17
17
  }
18
- /** Event-handler Twilio context, including mutable per-phone channel state. */
19
- export interface TwilioEventContext extends TwilioContext, ChannelSessionOps {
18
+ /** Channel-owned Twilio context returned by `context()`. */
19
+ export interface TwilioChannelContext extends TwilioContext {
20
20
  state: TwilioChannelState;
21
21
  }
22
+ /** Event-handler Twilio context, including session operations. */
23
+ export interface TwilioEventContext extends TwilioChannelContext, ChannelSessionOps {
24
+ }
22
25
  /** JSON-serializable state for the phone-number conversation. */
23
26
  export interface TwilioChannelState {
24
27
  /** Caller / sender phone number. */
@@ -180,7 +183,7 @@ export interface TwilioSendMessageOptions {
180
183
  readonly statusCallbackUrl?: string;
181
184
  }
182
185
  /** Concrete return type of {@link twilioChannel}. */
183
- export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveArgs> {
186
+ export interface TwilioChannel extends Channel<TwilioChannelState, TwilioReceiveArgs, TwilioInstrumentationMetadata> {
184
187
  }
185
188
  /** Twilio channel factory for SMS and speech-transcribed inbound calls. */
186
189
  export declare function twilioChannel(config: TwilioChannelConfig): TwilioChannel;
@@ -6,6 +6,7 @@ import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
6
  import type { SessionContext } from "#public/definitions/callback-context.js";
7
7
  import type { RouteDefinition, SendFn } from "#channel/routes.js";
8
8
  import type { Session, SessionHandle } from "#channel/session.js";
9
+ declare const CHANNEL_METADATA_TYPE: unique symbol;
9
10
  export type { Session, SessionHandle } from "#channel/session.js";
10
11
  export { GET, POST, PUT, PATCH, DELETE } from "#channel/routes.js";
11
12
  export type { RouteDefinition, RouteHandlerArgs, SendFn, SendOptions, SendPayload, GetSessionFn, } from "#channel/routes.js";
@@ -68,7 +69,7 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
68
69
  * (with {@link ChannelSessionOps} injected) and passes
69
70
  * {@link SessionContext} as a separate `ctx` argument.
70
71
  */
71
- context?(state: NonNullable<TState>, session: SessionHandle): Omit<TCtx, keyof ChannelSessionOps>;
72
+ context?(state: NonNullable<TState>, session: SessionHandle): TCtx;
72
73
  readonly routes: readonly RouteDefinition<TState>[];
73
74
  receive?(input: ReceiveInput<TReceiveArgs>, args: {
74
75
  send: SendFn<TState>;
@@ -107,8 +108,9 @@ export interface ChannelDefinition<TState = undefined, TCtx = void, TReceiveArgs
107
108
  */
108
109
  readonly kindHint?: string;
109
110
  }
110
- export interface Channel<TState = undefined, TReceiveArgs = Record<string, unknown>> extends TypedReceiveRoute<TReceiveArgs> {
111
+ export interface Channel<TState = undefined, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>> extends TypedReceiveRoute<TReceiveArgs> {
111
112
  readonly __kind: typeof CHANNEL_SENTINEL;
113
+ readonly [CHANNEL_METADATA_TYPE]?: TMetadata;
112
114
  readonly routes: readonly {
113
115
  method: string;
114
116
  path: string;
@@ -117,4 +119,5 @@ export interface Channel<TState = undefined, TReceiveArgs = Record<string, unkno
117
119
  send: SendFn<TState>;
118
120
  }) => Promise<Session>;
119
121
  }
120
- export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs, TMetadata>): Channel<TState, TReceiveArgs>;
122
+ export type InferChannelMetadata<TChannel> = TChannel extends Channel<any, any, infer TMetadata> ? TMetadata : Record<string, unknown>;
123
+ export declare function defineChannel<TState = undefined, TCtx = void, TReceiveArgs = Record<string, unknown>, TMetadata extends Record<string, unknown> = Record<string, unknown>>(definition: ChannelDefinition<TState, TCtx, TReceiveArgs, TMetadata>): Channel<TState, TReceiveArgs, TMetadata>;