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
|
@@ -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}),
|
|
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.
|
|
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.
|
|
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({
|
|
@@ -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
|
-
/**
|
|
21
|
-
export
|
|
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
|
-
|
|
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
|
-
/**
|
|
23
|
-
export interface
|
|
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
|
-
/**
|
|
23
|
-
export interface
|
|
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
|
-
/**
|
|
19
|
-
export interface
|
|
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):
|
|
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
|
|
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>;
|