experimental-ash 0.53.0 → 0.55.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 (51) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/docs/public/advanced/runs-and-streaming.md +14 -1
  3. package/dist/docs/public/advanced/vercel-deployment.md +5 -1
  4. package/dist/docs/public/channels/index.md +62 -0
  5. package/dist/docs/public/sandbox.md +12 -6
  6. package/dist/docs/public/tools.mdx +30 -0
  7. package/dist/src/channel/types.d.ts +2 -0
  8. package/dist/src/compiler/channel-instrumentation-types.js +1 -1
  9. package/dist/src/compiler/manifest.d.ts +3 -0
  10. package/dist/src/compiler/manifest.js +1 -1
  11. package/dist/src/compiler/workspace-resources.js +1 -1
  12. package/dist/src/context/dynamic-resolve-context.js +1 -1
  13. package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
  14. package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
  15. package/dist/src/execution/runtime-context.js +1 -1
  16. package/dist/src/execution/sandbox/bindings/local.js +1 -1
  17. package/dist/src/execution/sandbox/bindings/vercel.js +1 -1
  18. package/dist/src/execution/sandbox/ensure.js +1 -1
  19. package/dist/src/execution/sandbox/lazy-backend.js +1 -1
  20. package/dist/src/execution/sandbox/prewarm.d.ts +2 -2
  21. package/dist/src/execution/sandbox/prewarm.js +1 -1
  22. package/dist/src/execution/subagent-tool.d.ts +2 -1
  23. package/dist/src/execution/subagent-tool.js +1 -1
  24. package/dist/src/harness/tool-loop.js +1 -1
  25. package/dist/src/harness/workflow-stream-error.d.ts +29 -0
  26. package/dist/src/harness/workflow-stream-error.js +1 -0
  27. package/dist/src/internal/application/package.js +1 -1
  28. package/dist/src/internal/instrumentation.d.ts +1 -1
  29. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  30. package/dist/src/public/channels/discord/discordChannel.d.ts +2 -1
  31. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  32. package/dist/src/public/channels/discord/index.d.ts +4 -0
  33. package/dist/src/public/channels/index.d.ts +63 -0
  34. package/dist/src/public/channels/index.js +1 -1
  35. package/dist/src/public/channels/teams/index.d.ts +5 -0
  36. package/dist/src/public/channels/teams/teamsChannel.d.ts +2 -1
  37. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  38. package/dist/src/public/channels/telegram/index.d.ts +5 -0
  39. package/dist/src/public/channels/telegram/telegramChannel.d.ts +2 -1
  40. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  41. package/dist/src/public/definitions/sandbox-backend.d.ts +1 -1
  42. package/dist/src/public/instrumentation/index.d.ts +2 -58
  43. package/dist/src/public/instrumentation/index.js +1 -1
  44. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
  45. package/dist/src/runtime/sandbox/keys.d.ts +7 -3
  46. package/dist/src/runtime/sandbox/keys.js +1 -1
  47. package/dist/src/runtime/sandbox/template-plan.d.ts +21 -0
  48. package/dist/src/runtime/sandbox/template-plan.js +1 -0
  49. package/dist/src/shared/dynamic-tool-definition.d.ts +11 -0
  50. package/dist/src/shared/sandbox-backend.d.ts +25 -2
  51. package/package.json +1 -1
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,c=e.tool;_[qualifiedConnectionToolName(n,c)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},async execute(e){let d=loadContext().get(ConnectionRegistryKey),f=d.getConnections().find(e=>e.connectionName===n),p=f?.authorization&&supportsInteractiveAuthorization(f.authorization)?f.authorization:void 0;if(p){let e=getAuthorizationResult(n);if(e){let r=loadContext(),i=resolveConnectionPrincipal(n,p),a=await p.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:f?.url??``},principal:i,request:e.callback,state:e.state});writeCachedToken(r,n,principalKey(i),a)}}try{let t=(await d.getClient(n).getTools())[c];if(!t?.execute)throw Error(`Connection tool "${qualifiedConnectionToolName(n,c)}" has no execute function.`);return t.execute(e,{})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!p)throw e;let t=getHookUrl(n);if(!t)throw e;let r=resolveConnectionPrincipal(n,p),{challenge:i,state:s}=await p.startAuthorization({callbackUrl:t,connection:{url:f?.url??``},principal:r});return requestAuthorization([{name:n,challenge:i,hookUrl:t,state:s}])}}}}return _}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`ash:connection-search-dynamic`,sourceKind:`module`,logicalPath:`ash:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
1
+ import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,d=e.tool,f=c.getConnectionApproval(n);_[qualifiedConnectionToolName(n,d)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},needsApproval:f,async execute(e){let c=loadContext().get(ConnectionRegistryKey),f=c.getConnections().find(e=>e.connectionName===n),p=f?.authorization&&supportsInteractiveAuthorization(f.authorization)?f.authorization:void 0;if(p){let e=getAuthorizationResult(n);if(e){let r=loadContext(),i=resolveConnectionPrincipal(n,p),a=await p.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:f?.url??``},principal:i,request:e.callback,state:e.state});writeCachedToken(r,n,principalKey(i),a)}}try{let t=(await c.getClient(n).getTools())[d];if(!t?.execute)throw Error(`Connection tool "${qualifiedConnectionToolName(n,d)}" has no execute function.`);return t.execute(e,{})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!p)throw e;let t=getHookUrl(n);if(!t)throw e;let r=resolveConnectionPrincipal(n,p),{challenge:i,state:s}=await p.startAuthorization({callbackUrl:t,connection:{url:f?.url??``},principal:r});return requestAuthorization([{name:n,challenge:i,hookUrl:t,state:s}])}}}}return _}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`ash:connection-search-dynamic`,sourceKind:`module`,logicalPath:`ash:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
@@ -1,4 +1,5 @@
1
1
  import { type RuntimeCompiledArtifactsSource } from "#runtime/compiled-artifacts-source.js";
2
+ import type { RuntimeSandboxTemplatePlan } from "#runtime/sandbox/template-plan.js";
2
3
  /**
3
4
  * Input for deriving the stable runtime keys used for one sandbox definition.
4
5
  */
@@ -8,6 +9,7 @@ interface CreateRuntimeSandboxKeysInput {
8
9
  readonly nodeId: string;
9
10
  readonly sessionId: string;
10
11
  readonly sourceId: string;
12
+ readonly templatePlan: RuntimeSandboxTemplatePlan;
11
13
  }
12
14
  /**
13
15
  * Creates the stable runtime template and session keys for one sandbox
@@ -15,10 +17,11 @@ interface CreateRuntimeSandboxKeysInput {
15
17
  */
16
18
  export declare function createRuntimeSandboxKeys(input: CreateRuntimeSandboxKeysInput): Promise<{
17
19
  readonly sessionKey: string;
18
- readonly templateKey: string;
20
+ readonly templateKey: string | null;
19
21
  }>;
20
22
  /**
21
- * Creates the stable reusable template key for one sandbox definition.
23
+ * Creates the stable reusable template key for one sandbox definition,
24
+ * or `null` when the sandbox should start from a fresh backend runtime.
22
25
  *
23
26
  * The template key factors in the graph `nodeId` so that two
24
27
  * runtime agents (root and subagents) do not collide on the same
@@ -30,5 +33,6 @@ export declare function createRuntimeSandboxTemplateKey(input: {
30
33
  readonly compiledArtifactsSource: RuntimeCompiledArtifactsSource;
31
34
  readonly nodeId: string;
32
35
  readonly sourceId: string;
33
- }): Promise<string>;
36
+ readonly templatePlan: RuntimeSandboxTemplatePlan;
37
+ }): Promise<string | null>;
34
38
  export {};
@@ -1 +1 @@
1
- import{realpath}from"node:fs/promises";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{createHash}from"node:crypto";import{getRuntimeCompiledArtifactsAppRoot,getRuntimeCompiledArtifactsCacheKey}from"#runtime/compiled-artifacts-source.js";import{loadCompileMetadata}from"#runtime/loaders/compile-metadata.js";async function createRuntimeSandboxKeys(e){return{sessionKey:await createRuntimeSandboxSessionKey(e),templateKey:await createRuntimeSandboxTemplateKey(e)}}async function createRuntimeSandboxTemplateKey(e){let n=await resolveRuntimeSandboxScope(e.backendName,e.compiledArtifactsSource),r=await resolveRuntimeSandboxVersionHash({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId,sourceId:e.sourceId}),i=createStableHash(`${resolveInstalledPackageInfo().version}:2:${r}`).slice(0,20);return sanitizeRuntimeSandboxKey(`ash-sbx-tpl-${e.backendName}-${n}-${i}`)}async function createRuntimeSandboxSessionKey(e){let t=await resolveRuntimeSandboxScope(e.backendName,e.compiledArtifactsSource),n=sanitizeRuntimeSandboxKey(e.nodeId);return sanitizeRuntimeSandboxKey(`ash-sbx-ses-${e.backendName}-${t}-${e.sessionId}-${n}`)}async function resolveRuntimeSandboxScope(t,n){if(t===`vercel`){let e=process.env.VERCEL_DEPLOYMENT_ID?.trim();if(e!==void 0&&e.length>0)return createStableHash(e).slice(0,16)}let a=getRuntimeCompiledArtifactsAppRoot(n);return a===void 0?createStableHash(getRuntimeCompiledArtifactsCacheKey(n)).slice(0,16):createStableHash(await realpath(a)).slice(0,16)}async function resolveRuntimeSandboxVersionHash(e){return createStableHash(`${(await loadCompileMetadata({compiledArtifactsSource:e.compiledArtifactsSource}))?.discovery.sourceGraphHash??getRuntimeCompiledArtifactsCacheKey(e.compiledArtifactsSource)}:${e.nodeId}:${e.sourceId}`)}function createStableHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function sanitizeRuntimeSandboxKey(e){return e.replaceAll(/[^a-zA-Z0-9._-]+/g,`-`).slice(0,120)}export{createRuntimeSandboxKeys,createRuntimeSandboxTemplateKey};
1
+ import{realpath}from"node:fs/promises";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{createHash}from"node:crypto";import{getRuntimeCompiledArtifactsAppRoot,getRuntimeCompiledArtifactsCacheKey}from"#runtime/compiled-artifacts-source.js";import{loadCompileMetadata}from"#runtime/loaders/compile-metadata.js";async function createRuntimeSandboxKeys(e){return{sessionKey:await createRuntimeSandboxSessionKey(e),templateKey:await createRuntimeSandboxTemplateKey(e)}}async function createRuntimeSandboxTemplateKey(e){if(e.templatePlan.kind===`none`)return null;let n=await resolveRuntimeSandboxScope({backendName:e.backendName,compiledArtifactsSource:e.compiledArtifactsSource,scopeKind:e.templatePlan.kind===`source-graph`?`deployment`:`stable`}),r=await resolveRuntimeSandboxVersionHash({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId,sourceId:e.sourceId,templatePlan:e.templatePlan}),i=createStableHash(`${resolveInstalledPackageInfo().version}:3:${r}`).slice(0,20);return sanitizeRuntimeSandboxKey(`ash-sbx-tpl-${e.backendName}-${n}-${i}`)}async function createRuntimeSandboxSessionKey(e){let t=await resolveRuntimeSandboxScope({backendName:e.backendName,compiledArtifactsSource:e.compiledArtifactsSource,scopeKind:`deployment`}),n=sanitizeRuntimeSandboxKey(e.nodeId);return sanitizeRuntimeSandboxKey(`ash-sbx-ses-${e.backendName}-${t}-${e.sessionId}-${n}`)}async function resolveRuntimeSandboxScope(t){if(t.backendName===`vercel`){if(t.scopeKind===`stable`){let e=resolveVercelProjectScope();if(e!==void 0)return createStableHash(e).slice(0,16)}let e=process.env.VERCEL_DEPLOYMENT_ID?.trim();if(e!==void 0&&e.length>0)return createStableHash(e).slice(0,16)}let n=getRuntimeCompiledArtifactsAppRoot(t.compiledArtifactsSource);return n===void 0?createStableHash(getRuntimeCompiledArtifactsCacheKey(t.compiledArtifactsSource)).slice(0,16):createStableHash(await realpath(n)).slice(0,16)}async function resolveRuntimeSandboxVersionHash(e){return e.templatePlan.kind===`workspace-content`?createStableHash(`workspace-content:${e.templatePlan.contentHash??await resolveSourceGraphHash(e.compiledArtifactsSource)}:${e.nodeId}:${e.sourceId}`):createStableHash(`source-graph:${await resolveSourceGraphHash(e.compiledArtifactsSource)}:${e.nodeId}:${e.sourceId}`)}async function resolveSourceGraphHash(e){return(await loadCompileMetadata({compiledArtifactsSource:e}))?.discovery.sourceGraphHash??getRuntimeCompiledArtifactsCacheKey(e)}function resolveVercelProjectScope(){let e=process.env.VERCEL_PROJECT_ID?.trim();if(e===void 0||e.length===0)return;let t=process.env.VERCEL_TEAM_ID?.trim();return t===void 0||t.length===0?`vercel-project:${e}`:`vercel-project:${t}:${e}`}function createStableHash(e){return createHash(`sha256`).update(e).digest(`hex`)}function sanitizeRuntimeSandboxKey(e){return e.replaceAll(/[^a-zA-Z0-9._-]+/g,`-`).slice(0,120)}export{createRuntimeSandboxKeys,createRuntimeSandboxTemplateKey};
@@ -0,0 +1,21 @@
1
+ import type { CompiledWorkspaceResourceRoot } from "#compiler/manifest.js";
2
+ import type { ResolvedSandboxDefinition } from "#runtime/types.js";
3
+ /**
4
+ * Describes whether one sandbox needs a prewarmed template and, if so,
5
+ * which inputs must participate in the template key.
6
+ */
7
+ export type RuntimeSandboxTemplatePlan = {
8
+ readonly kind: "none";
9
+ } | {
10
+ readonly contentHash?: string;
11
+ readonly kind: "workspace-content";
12
+ } | {
13
+ readonly kind: "source-graph";
14
+ };
15
+ /**
16
+ * Chooses the template strategy for one resolved sandbox definition.
17
+ */
18
+ export declare function createRuntimeSandboxTemplatePlan(input: {
19
+ readonly definition: ResolvedSandboxDefinition;
20
+ readonly workspaceResourceRoot: CompiledWorkspaceResourceRoot;
21
+ }): RuntimeSandboxTemplatePlan;
@@ -0,0 +1 @@
1
+ function createRuntimeSandboxTemplatePlan(e){return e.definition.bootstrap===void 0?e.workspaceResourceRoot.rootEntries.length===0?{kind:`none`}:{contentHash:e.workspaceResourceRoot.contentHash,kind:`workspace-content`}:{kind:`source-graph`}}export{createRuntimeSandboxTemplatePlan};
@@ -1,6 +1,7 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { PublicToolInputSchema, ToolModelOutput } from "#shared/tool-definition.js";
3
3
  import type { SessionContext } from "#public/definitions/callback-context.js";
4
+ import type { NeedsApprovalContext } from "#public/definitions/tool.js";
4
5
  import type { SessionAuth } from "#context/keys.js";
5
6
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
7
  type ToolContext = SessionContext;
@@ -35,6 +36,7 @@ export interface DynamicResolveContext {
35
36
  readonly channel: {
36
37
  readonly kind?: string;
37
38
  readonly continuationToken?: string;
39
+ readonly metadata?: Readonly<Record<string, unknown>>;
38
40
  };
39
41
  readonly messages: readonly ModelMessage[];
40
42
  }
@@ -55,6 +57,15 @@ export interface DynamicToolEntry<TInput = Record<string, unknown>, TOutput = an
55
57
  readonly inputSchema: PublicToolInputSchema<TInput>;
56
58
  execute(input: TInput, ctx: ToolContext): TOutput | Promise<TOutput>;
57
59
  readonly toModelOutput?: (output: TOutput) => ToolModelOutput | Promise<ToolModelOutput>;
60
+ /**
61
+ * Optional per-call approval gate. Mirrors the authored-tool
62
+ * `needsApproval` contract: return `true` to require user approval
63
+ * before the call executes. Only honored for step-scoped dynamic
64
+ * tools (whose live `execute` closures survive into the harness);
65
+ * session/turn-scoped tools replay from durable metadata and cannot
66
+ * carry a function across replay.
67
+ */
68
+ readonly needsApproval?: (ctx: NeedsApprovalContext) => boolean;
58
69
  }
59
70
  /**
60
71
  * A resolved tool set: keys are entry identifiers, values are tool
@@ -59,7 +59,13 @@ export interface SandboxBackendRuntimeContext {
59
59
  * live sandbox session.
60
60
  */
61
61
  export interface SandboxBackendCreateInput {
62
- readonly templateKey: string;
62
+ /**
63
+ * Reusable template key to open this session from. `null` means Ash
64
+ * intentionally skipped template prewarm because the sandbox has no
65
+ * `bootstrap()` and no seed files, so the backend should create a
66
+ * fresh session from its default base runtime.
67
+ */
68
+ readonly templateKey: string | null;
63
69
  readonly sessionKey: string;
64
70
  readonly existingMetadata?: Record<string, unknown>;
65
71
  /**
@@ -85,6 +91,20 @@ export interface SandboxBackendPrewarmInput<BO = Record<string, never>> {
85
91
  readonly runtimeContext: SandboxBackendRuntimeContext;
86
92
  readonly seedFiles: ReadonlyArray<SandboxSeedFile>;
87
93
  }
94
+ /**
95
+ * Outcome of one {@link SandboxBackend.prewarm} call.
96
+ *
97
+ * The build pipeline uses this to report in the build logs whether a
98
+ * template snapshot was reused from a prior deploy or captured fresh, so
99
+ * a cache hit is distinguishable from an expensive rebuild.
100
+ */
101
+ export interface SandboxBackendPrewarmResult {
102
+ /**
103
+ * `true` when an existing template snapshot was reused without
104
+ * rebuilding it; `false` when the backend captured a fresh snapshot.
105
+ */
106
+ readonly reused: boolean;
107
+ }
88
108
  /**
89
109
  * Pluggable sandbox backend.
90
110
  *
@@ -119,6 +139,9 @@ export interface SandboxBackend<BO = Record<string, never>, SO = Record<string,
119
139
  * sandbox in the compiled graph before serving traffic so the backend
120
140
  * can capture a reusable template snapshot. Idempotent against an
121
141
  * existing snapshot keyed by `templateKey`.
142
+ *
143
+ * Returns whether the snapshot was reused from a prior run or captured
144
+ * fresh so the build pipeline can surface that in its logs.
122
145
  */
123
- prewarm(input: SandboxBackendPrewarmInput<BO>): Promise<void>;
146
+ prewarm(input: SandboxBackendPrewarmInput<BO>): Promise<SandboxBackendPrewarmResult>;
124
147
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.53.0",
3
+ "version": "0.55.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"