experimental-ash 0.49.0 → 0.51.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 CHANGED
@@ -1,5 +1,18 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.51.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fabc717: Add `onMessage` and `events` support to `ashChannel(...)` so the default HTTP channel can choose runtime auth with `defaultAshAuth`, append authenticated pre-dispatch context, and observe runtime stream events with the same handler shapes as custom channels.
8
+ - cab8481: Remove `retentionPolicy` from the tool definition API. The reactive pruning system now always uses the default placeholder for old tool results — per-tool "keep" and summary-function policies are no longer supported.
9
+
10
+ ## 0.50.0
11
+
12
+ ### Minor Changes
13
+
14
+ - a47fb68: Framework dynamic tools now work with session.started and turn.started events, not just step.started. Their execute closures are auto-registered in the step function registry so durable metadata can replay them across steps the same way as authored (bundler-transformed) dynamic tools.
15
+
3
16
  ## 0.49.0
4
17
 
5
18
  ### Minor Changes
@@ -84,7 +84,8 @@ helpers. Each route handler receives the raw `Request` and a helpers object:
84
84
  - `waitUntil(promise)` -- extend the request lifetime for background work.
85
85
 
86
86
  Event handlers like `"message.completed"` are declared under the `events` key on the config object.
87
- They receive `(eventData, ctx)` where `ctx` carries platform handles and `ctx.session`. Use
87
+ They receive `(eventData, channel, ctx)` where `channel` carries platform handles and session
88
+ continuation operations, and `ctx` is the Ash `SessionContext`. Use
88
89
  `toolResultFrom` from `experimental-ash/tools` to narrow tool results (see
89
90
  [Hooks — Narrowing tool results](../hooks.mdx#narrowing-tool-results)).
90
91
 
@@ -92,7 +93,7 @@ They receive `(eventData, ctx)` where `ctx` carries platform handles and `ctx.se
92
93
 
93
94
  The framework ships its canonical session protocol on `experimental-ash/channels/ash`:
94
95
 
95
- - `ashChannel({ auth })`
96
+ - `ashChannel({ auth, onMessage?, events? })`
96
97
 
97
98
  This is the channel the Ash dev client and any deployed-agent SDK talk to — it mounts the routes
98
99
  under `/ash/v1/session*` documented in [Ash External Agent Protocol](../../external-agent-protocol.md).
@@ -111,6 +112,45 @@ export default ashChannel({
111
112
  });
112
113
  ```
113
114
 
115
+ Pass `onMessage` to add authenticated context before the Ash HTTP channel dispatches a user
116
+ message. The hook runs after `auth` succeeds and body parsing completes, so `ctx.ash.caller` is
117
+ available for caller-specific lookups. Return `{ auth, context }` to choose the runtime session
118
+ auth and append context strings as user messages before the inbound message, or `null` to accept
119
+ the request without dispatching a turn. When `onMessage` is omitted, Ash dispatches with
120
+ `defaultAshAuth(ctx)`.
121
+
122
+ ```ts
123
+ import { ashChannel, defaultAshAuth } from "experimental-ash/channels/ash";
124
+
125
+ export default ashChannel({
126
+ auth: [localDev(), vercelOidc()],
127
+ async onMessage(ctx, message) {
128
+ const auth = defaultAshAuth(ctx);
129
+ const profile = auth ? await loadCallerProfile(auth.principalId) : null;
130
+
131
+ return {
132
+ auth,
133
+ context: profile ? [`Caller profile:\n${JSON.stringify(profile)}`] : [],
134
+ };
135
+ },
136
+ });
137
+ ```
138
+
139
+ Pass `events` to observe runtime stream events on the default HTTP channel. The Ash channel has no
140
+ platform-specific context, so `channel` exposes session continuation operations and `ctx` exposes
141
+ the current session context:
142
+
143
+ ```ts
144
+ export default ashChannel({
145
+ auth: [localDev(), vercelOidc()],
146
+ events: {
147
+ "turn.started"(_event, _channel, ctx) {
148
+ auditTurn(ctx.session.id);
149
+ },
150
+ },
151
+ });
152
+ ```
153
+
114
154
  `pnpm create experimental-ash-agent` scaffolds the Web Chat example channel at
115
155
  `agent/channels/ash.ts`. It permits Vercel OIDC and localhost requests and includes an
116
156
  `exampleProductionAuth()` placeholder that throws in production until you replace it
@@ -233,6 +273,7 @@ export default slackChannel({
233
273
 
234
274
  ```text
235
275
  defineChannel({ routes: [...], events: {...} }) -- raw: you handle HTTP
276
+ ashChannel({ auth, onMessage?, events? }) -- ash: default HTTP protocol
236
277
  slackChannel({ onAppMention?, onDirectMessage?, events?, onInteraction? }) -- slack: everything wired, override as needed
237
278
  ```
238
279
 
@@ -8,15 +8,14 @@ Tools are Ash's typed executable integrations.
8
8
  Use a tool when the model should call real code with structured input and get structured output
9
9
  back.
10
10
 
11
- <CopyPrompt text="Add a typed Ash tool to the user's project. Ash tools live under agent/tools, and the model-facing tool name comes from the filename. Inspect existing agent/tools and agent/lib, then create or update the smallest agent/tools/snake_case_name.ts file using defineTool from experimental-ash/tools with a Zod or Standard Schema inputSchema and an inline execute(input, ctx). Use ctx.session, ctx.getSandbox(), or ctx.getSkill only inside runtime execution when needed, keep secrets in environment variables, bound large outputs or use toModelOutput and retentionPolicy, verify with the project's typecheck and tests, and do not commit unless the user asks.">
11
+ <CopyPrompt text="Add a typed Ash tool to the user's project. Ash tools live under agent/tools, and the model-facing tool name comes from the filename. Inspect existing agent/tools and agent/lib, then create or update the smallest agent/tools/snake_case_name.ts file using defineTool from experimental-ash/tools with a Zod or Standard Schema inputSchema and an inline execute(input, ctx). Use ctx.session, ctx.getSandbox(), or ctx.getSkill only inside runtime execution when needed, keep secrets in environment variables, bound large outputs or use toModelOutput, verify with the project's typecheck and tests, and do not commit unless the user asks.">
12
12
  Add a typed Ash tool to the user's project. Ash tools live under agent/tools, and the model-facing
13
13
  tool name comes from the filename. Inspect existing agent/tools and agent/lib, then create or
14
14
  update the smallest agent/tools/snake_case_name.ts file using defineTool from
15
15
  experimental-ash/tools with a Zod or Standard Schema inputSchema and an inline execute(input,
16
16
  ctx). Use ctx.session, ctx.getSandbox(), or ctx.getSkill only inside runtime execution when
17
- needed, keep secrets in environment variables, bound large outputs or use toModelOutput and
18
- retentionPolicy, verify with the project's typecheck and tests, and do not commit unless the user
19
- asks.
17
+ needed, keep secrets in environment variables, bound large outputs or use toModelOutput, verify
18
+ with the project's typecheck and tests, and do not commit unless the user asks.
20
19
  </CopyPrompt>
21
20
 
22
21
  ## The Main API
@@ -289,40 +288,6 @@ export default defineTool({
289
288
  Framework tools (`bash`, `read_file`, `grep`, `glob`, `web_fetch`) all do this internally using the
290
289
  shared helpers in `truncate-output.ts`.
291
290
 
292
- ### `retentionPolicy` — Between-Step Retention
293
-
294
- Controls what happens to this tool's old results when they age outside the reactive pruning
295
- protection window (~40 K tokens of the most recent tool results). Three forms are supported:
296
-
297
- | Value | Behavior |
298
- | --------------------------------- | -------------------------------------------------------------------------------------------------------------- |
299
- | `"auto"` (default) | Old results are replaced with a fixed placeholder. |
300
- | `"keep"` | Old results are never replaced. Use for system-critical tools whose results must persist (e.g. loaded skills). |
301
- | `(output) => { summary: string }` | The function is called with the original output; its `summary` text replaces the result in history. |
302
-
303
- The function form receives the tool's `execute` return value (strongly typed, no casts needed) and
304
- returns a `RetentionSummary` object. The `summary` field is the compact text the model will see in
305
- place of the full result.
306
-
307
- ```ts
308
- import { defineTool } from "experimental-ash/tools";
309
- import { z } from "zod";
310
-
311
- export default defineTool({
312
- description: "Run a SQL query against the warehouse.",
313
- inputSchema: z.object({ sql: z.string() }),
314
- async execute({ sql }) {
315
- return await queryWarehouse(sql); // { rows: Row[]; columns: string[] }
316
- },
317
- retentionPolicy: (output) => ({
318
- summary: `[old result: ${output.rows.length} rows, columns ${output.columns.join(", ")}]`,
319
- }),
320
- });
321
- ```
322
-
323
- `retentionPolicy` runs every time an older result of this tool exits the protection window. The
324
- `summary` text replaces the original tool-result output in history.
325
-
326
291
  ### `toModelOutput` — Controlling What The Model Sees
327
292
 
328
293
  By default, the model sees the full return value of `execute`. When a tool returns rich structured
@@ -376,8 +341,8 @@ Use `toolResultFrom(result, toolDefinition)` to narrow an `action.result` to a s
376
341
  get typed output. See [Hooks — Narrowing tool results](./hooks.mdx#narrowing-tool-results) for the
377
342
  full API.
378
343
 
379
- Note: `retentionPolicy` and `onCompact` operate on the projected output (what the model sees),
380
- not the full `execute` return.
344
+ Note: `onCompact` operates on the projected output (what the model sees), not the full `execute`
345
+ return.
381
346
 
382
347
  ## Dynamic Tools
383
348
 
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{jsonSchema,zodSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";const log=createLogger(`dynamic-tools`);function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function lookupStepFunction(e){try{let t=globalThis[Symbol.for(`@workflow/core//registeredSteps`)];return t===void 0?null:t.get(e)||null}catch{return null}}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicToolMetadataKey;case`turn.started`:return TurnDynamicToolMetadataKey;default:return}}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let s,c;return isBrandedToolEntry(a)?(s={_single:a},c=!0):(s=a,c=!1),{resolver:t,entries:s,isSingle:c}})),s=[],c=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:a}=e.value,o=qualifyDynamicToolNames(t.slug,a,r);for(let{name:e,entryKey:n,entry:r}of o){c.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0;s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:i?.stepId,closureVars:a===void 0?void 0:safeSerialize(a)})}}return{metadata:s,liveTools:c}}async function dispatchDynamicToolEvent(e){let{ctx:n,resolvers:r,event:i,messages:o}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(i.type))return;let s=r.filter(e=>e.eventNames.includes(i.type));if(s.length===0)return;let{metadata:c,liveTools:l}=await resolveToolsFromEvent(n,s,i,o);if(i.type===`step.started`){n.setVirtualContext(LiveStepToolsKey,l);return}let u=durableKeyForEvent(i.type);if(u===void 0)return;let d=new Set(s.map(e=>e.slug)),f=(n.get(u)??[]).filter(e=>!d.has(e.resolverSlug));n.set(u,[...f,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
1
+ import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_TOOL_EVENTS,isBrandedToolEntry}from"#shared/dynamic-tool-definition.js";import{jsonSchema,zodSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";const log=createLogger(`dynamic-tools`);function toHarnessToolDefinition(e,t){return{description:t.description,execute:e=>t.execute(e,buildCallbackContext()),inputSchema:convertInputSchema(t.inputSchema),name:e,...t.toModelOutput===void 0?{}:{toModelOutput:t.toModelOutput}}}function convertInputSchema(e){return typeof e==`object`&&e&&`~standard`in e?zodSchema(e):jsonSchema(e)}function qualifyDynamicToolNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t)return i.push({name:e,entryKey:r[0],entry:n[r[0]]}),i;for(let t of r)i.push({name:`${e}__${t}`,entryKey:t,entry:n[t]});return i}function replayDynamicSessionTools(e,t){let n=[];for(let t of e){if(!t.executeStepFnName||!t.closureVars){log.warn(`Dynamic tool "${t.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(t.executeStepFnName);if(!e){log.warn(`Dynamic tool "${t.name}" references step function "${t.executeStepFnName}" which is not registered — skipping on this step.`);continue}n.push({description:t.description,execute:n=>e(t.closureVars,n,buildCallbackContext()),inputSchema:jsonSchema(t.inputSchema),name:t.name})}return n}function getStepRegistry(){let e=Symbol.for(`@workflow/core//registeredSteps`),t=globalThis,n=t[e];return n===void 0&&(n=new Map,t[e]=n),n}function lookupStepFunction(e){try{return getStepRegistry().get(e)||null}catch{return null}}function registerStepFunction(e,t){getStepRegistry().set(e,t)}function safeSerialize(e){try{return JSON.parse(JSON.stringify(e))}catch{return{}}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicToolMetadataKey;case`turn.started`:return TurnDynamicToolMetadataKey;default:return}}async function resolveToolsFromEvent(e,t,n,r){let a=await Promise.allSettled(t.map(async t=>{let i=t.events[n.type];if(i===void 0)return null;let a=await i(n,buildResolveContext(e,r));if(a==null)return null;let s,c;return isBrandedToolEntry(a)?(s={_single:a},c=!0):(s=a,c=!1),{resolver:t,entries:s,isSingle:c}})),s=[],c=[];for(let e of a){if(e.status===`rejected`){log.error(`Dynamic tool resolver (${n.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,entries:r,isSingle:a}=e.value,o=qualifyDynamicToolNames(t.slug,a,r);for(let{name:e,entryKey:n,entry:r}of o){c.push(toHarnessToolDefinition(e,r));let i=`__executeStepFn`in r?r.__executeStepFn:void 0,a=`__closureVars`in r?r.__closureVars:void 0,o=i?.stepId,l=a===void 0?void 0:safeSerialize(a);if(o===void 0){let e=`ash:framework-dynamic:${t.slug}:${n}`,i=r.execute.bind(r);registerStepFunction(e,(e,t,n)=>i(t,n)),o=e,l={}}s.push({name:e,description:r.description,inputSchema:normalizeJsonSchemaDefinition(r.inputSchema),resolverSlug:t.slug,entryKey:n,executeStepFnName:o,closureVars:l})}}return{metadata:s,liveTools:c}}async function dispatchDynamicToolEvent(e){let{ctx:n,resolvers:r,event:i,messages:o}=e;if(!ALLOWED_DYNAMIC_TOOL_EVENTS.has(i.type))return;let s=r.filter(e=>e.eventNames.includes(i.type));if(s.length===0)return;let{metadata:c,liveTools:l}=await resolveToolsFromEvent(n,s,i,o);if(i.type===`step.started`){n.setVirtualContext(LiveStepToolsKey,l);return}let u=durableKeyForEvent(i.type);if(u===void 0)return;let d=new Set(s.map(e=>e.slug)),f=(n.get(u)??[]).filter(e=>!d.has(e.resolverSlug));n.set(u,[...f,...c])}export{dispatchDynamicToolEvent,replayDynamicSessionTools};
@@ -1 +1 @@
1
- import{createLogger}from"#internal/logging.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{jsonSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{createToolLoopHarness}from"#harness/tool-loop.js";import{resolveCodeModeEnabled}from"#shared/code-mode.js";import{resolveRuntimeModelReference}from"#runtime/agent/resolve-model.js";import{findRegisteredRuntimeTool}from"#runtime/tools/registry.js";import{createToolCompactionHandler}from"#execution/tool-compaction.js";const log=createLogger(`execution.node-step`);function createExecutionNodeStep(e){let t=createRuntimeModelResolver(e.compiledArtifactsSource),n=createNodeHarnessTools({node:e.node}),r=collectResolvedTools(e.node),o=createToolCompactionHandler(r),s=collectRetentionPolicies(r);return createToolLoopHarness({capabilities:e.capabilities,codeMode:resolveCodeModeEnabled(e.node.agent.config?.experimental?.codeMode),handleEvent:e.handleEvent,mode:e.mode,onCompaction:o,resolveModel:t,retentionPolicies:s,runtimeIdentity:buildRuntimeIdentity(e.node),tools:n})}function buildRuntimeIdentity(e){let n=resolveInstalledPackageInfo(),r={agentId:e.turnAgent.id,agentName:e.agent.config?.name,ashVersion:n.version,modelId:e.turnAgent.model.id},i=process.env.VERCEL_GIT_COMMIT_SHA?.trim(),a=process.env.VERCEL_GIT_COMMIT_REF?.trim(),o=process.env.VERCEL_DEPLOYMENT_CREATED_AT?.trim();return i||a||o?{...r,build:{deployedAt:o||void 0,gitBranch:a||void 0,gitSha:i||void 0}}:r}function createRuntimeModelResolver(e){return t=>resolveRuntimeModelReference(t,{compiledArtifactsSource:e})}function collectResolvedTools(e){return[...e.toolRegistry.toolsByName.values()].map(e=>e.definition)}function collectRetentionPolicies(e){let t=new Map;for(let n of e){let e=n.retentionPolicy;e===void 0||e===`auto`||t.set(n.name,e)}return t}function createNodeHarnessTools(e){let t=new Map;for(let n of e.node.turnAgent.tools){let r=resolveHarnessToolDefinition({node:e.node,tool:n});r!==null&&t.set(n.name,r)}return t}function resolveHarnessToolDefinition(e){if(e.tool.kind===`subagent`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`subagent-call`,nodeId:e.tool.nodeId,subagentName:e.tool.name}};if(e.tool.kind===`remote`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`remote-agent-call`,nodeId:e.tool.nodeId,remoteAgentName:e.tool.name,subagentName:e.tool.name}};let t=findRegisteredRuntimeTool(e.node.toolRegistry,e.tool.name);if(t===null)return log.warn(`declared tool is not registered — omitting from toolset`,{toolName:e.tool.name,nodeId:e.node.nodeId}),null;let i=t.definition,a=i.sourceId.startsWith(`ash:`),o=i.execute;return{approvalKey:i.approvalKey,description:i.description,execute:o===void 0?void 0:a?o:e=>o(e,buildCallbackContext()),inputSchema:i.inputStandardSchema??jsonSchema(i.inputSchema??{}),name:i.name,needsApproval:i.needsApproval,toModelOutput:i.toModelOutput}}export{createExecutionNodeStep,createNodeHarnessTools};
1
+ import{createLogger}from"#internal/logging.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{jsonSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";import{createToolLoopHarness}from"#harness/tool-loop.js";import{resolveCodeModeEnabled}from"#shared/code-mode.js";import{resolveRuntimeModelReference}from"#runtime/agent/resolve-model.js";import{findRegisteredRuntimeTool}from"#runtime/tools/registry.js";import{createToolCompactionHandler}from"#execution/tool-compaction.js";const log=createLogger(`execution.node-step`);function createExecutionNodeStep(e){let t=createRuntimeModelResolver(e.compiledArtifactsSource),n=createNodeHarnessTools({node:e.node}),r=createToolCompactionHandler(collectResolvedTools(e.node));return createToolLoopHarness({capabilities:e.capabilities,codeMode:resolveCodeModeEnabled(e.node.agent.config?.experimental?.codeMode),handleEvent:e.handleEvent,mode:e.mode,onCompaction:r,resolveModel:t,runtimeIdentity:buildRuntimeIdentity(e.node),tools:n})}function buildRuntimeIdentity(e){let n=resolveInstalledPackageInfo(),r={agentId:e.turnAgent.id,agentName:e.agent.config?.name,ashVersion:n.version,modelId:e.turnAgent.model.id},i=process.env.VERCEL_GIT_COMMIT_SHA?.trim(),a=process.env.VERCEL_GIT_COMMIT_REF?.trim(),o=process.env.VERCEL_DEPLOYMENT_CREATED_AT?.trim();return i||a||o?{...r,build:{deployedAt:o||void 0,gitBranch:a||void 0,gitSha:i||void 0}}:r}function createRuntimeModelResolver(e){return t=>resolveRuntimeModelReference(t,{compiledArtifactsSource:e})}function collectResolvedTools(e){return[...e.toolRegistry.toolsByName.values()].map(e=>e.definition)}function createNodeHarnessTools(e){let t=new Map;for(let n of e.node.turnAgent.tools){let r=resolveHarnessToolDefinition({node:e.node,tool:n});r!==null&&t.set(n.name,r)}return t}function resolveHarnessToolDefinition(e){if(e.tool.kind===`subagent`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`subagent-call`,nodeId:e.tool.nodeId,subagentName:e.tool.name}};if(e.tool.kind===`remote`)return{description:e.tool.description??``,inputSchema:jsonSchema(e.tool.inputSchema??{}),name:e.tool.name,runtimeAction:{kind:`remote-agent-call`,nodeId:e.tool.nodeId,remoteAgentName:e.tool.name,subagentName:e.tool.name}};let t=findRegisteredRuntimeTool(e.node.toolRegistry,e.tool.name);if(t===null)return log.warn(`declared tool is not registered — omitting from toolset`,{toolName:e.tool.name,nodeId:e.node.nodeId}),null;let i=t.definition,a=i.sourceId.startsWith(`ash:`),o=i.execute;return{approvalKey:i.approvalKey,description:i.description,execute:o===void 0?void 0:a?o:e=>o(e,buildCallbackContext()),inputSchema:i.inputStandardSchema??jsonSchema(i.inputSchema??{}),name:i.name,needsApproval:i.needsApproval,toModelOutput:i.toModelOutput}}export{createExecutionNodeStep,createNodeHarnessTools};
@@ -1 +1 @@
1
- import{createErrorId,createLogger,formatError,logError,recordErrorOnSpan}from"#internal/logging.js";import{createAuthorizationRequiredEvent,createCompactionCompletedEvent,createCompactionRequestedEvent,createInputRequestedEvent,createResultCompletedEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import{contextStorage}from"#context/container.js";import{ToolLoopAgent,isStepCount}from"ai";import{setAshAttributes}from"#runtime/attributes/emit.js";import{createRuntimeActionRequestFromToolCall,resolvePendingRuntimeActions,setPendingRuntimeActionBatch}from"#harness/runtime-actions.js";import{advanceStep,emitFailedStep,emitRecoverableFailedTurn,emitStepStarted,emitStreamContent,emitTurnEpilogue,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{resolveAssistantStepText}from"#harness/messages.js";import{buildDynamicInstructionMessages}from"#context/dynamic-instruction-lifecycle.js";import{PendingSkillAnnouncementKey}from"#context/dynamic-skill-lifecycle.js";import{consumeDeferredStepInput,getApprovedTools,hasDeferredStepInput,hasStepInput,resolvePendingInput,setPendingInputBatch}from"#harness/input-requests.js";import{isAuthorizationSignal,setPendingAuthorization}from"#harness/authorization.js";import{isCodeModeConnectionAuthInterrupt}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSetWithProviderTools}from"#harness/tools.js";import{ASK_QUESTION_TOOL_NAME}from"#runtime/framework-tools/ask-question.js";import{WEB_SEARCH_TOOL_DEFINITION}from"#runtime/framework-tools/web-search.js";import{extractQuestionInputRequests,extractToolApprovalInputRequests}from"#harness/input-extraction.js";import{applyLastToolCacheBreakpoint,applySystemCacheBreakpoint,detectPromptCachePath,getAnthropicCacheMarker}from"#harness/prompt-cache.js";import{resolveFrameworkToolFromUpstreamType,resolveGatewayPinForWebSearchBackend,resolveWebSearchBackend}from"#harness/provider-tools.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{hydrateSandboxAttachments,stageAttachmentsToSandbox}from"#harness/attachment-staging.js";import{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions}from"#harness/code-mode.js";import{createCodeModeLifecycle}from"#harness/code-mode-lifecycle.js";import{clearPendingCodeModeApproval,getPendingCodeModeApproval,replaceCodeModeApprovalInterruptResult,setPendingCodeModeApproval}from"#harness/code-mode-approval.js";import{compactMessages,getInputTokenCount,resolveCompactionModel,shouldCompact}from"#harness/compaction.js";import{accumulateTurnUsage,getTurnUsageState,setTurnUsageState}from"#harness/turn-tag-state.js";import{buildTelemetryRuntimeContext}from"#harness/instrumentation-metadata.js";import{getInstrumentationConfig}from"#harness/instrumentation-config.js";import{clearPendingCodeModeConnectionAuth,getPendingCodeModeConnectionAuth,setPendingCodeModeConnectionAuth}from"#harness/code-mode-connection-auth-state.js";import{classifyModelCallError,extractModelCallErrorDetails,extractUnsupportedProviderToolTypes,summarizeKnownModelCallConfigError,summarizeKnownModelCallRequestError}from"#harness/model-call-error.js";import{ensureOtelIntegration}from"#harness/otel-integration.js";import{buildStepHooks,emitStepActions,isInvalidToolCall}from"#harness/step-hooks.js";import{pruneToolResults}from"#harness/tool-result-pruning.js";import{FINAL_OUTPUT_TOOL_NAME,buildFinalOutputTool}from"#runtime/framework-tools/final-output.js";const environment=process.env.NODE_ENV??`unknown`,ashVersion=resolveInstalledPackageInfo().version,log=createLogger(`harness.tool-loop`);function logToolExecutionError(e){e.toolOutput.type===`tool-error`&&logError(log,`tool execution failed`,e.toolOutput.error,{toolName:e.toolCall.toolName,toolCallId:e.toolCall.toolCallId})}function enrichTelemetry(e,t,n){if(e===void 0)return;let r={};for(let e of Object.keys(n??{}))r[e]=!0;return{functionId:e.functionId??t,includeRuntimeContext:r,isEnabled:!0,recordInputs:e.recordInputs??!0,recordOutputs:e.recordOutputs??!0}}function resolveGatewayPinForStep(e){if(e.cachePath.kind!==`gateway-auto`||e.tools[WEB_SEARCH_TOOL_DEFINITION.name]===void 0)return;let t=resolveWebSearchBackend(e.modelReference);return t===null?void 0:resolveGatewayPinForWebSearchBackend(t)??void 0}function buildGatewayAttributionHeaders(e,t){if(typeof e!=`string`)return;let n=t?.agentName??t?.agentId,r=process.env.VERCEL_PROJECT_PRODUCTION_URL||process.env.VERCEL_URL,i=r?`https://${r}`:void 0;if(!n&&!i)return;let a={};return n&&(a[`x-title`]=n),i&&(a[`http-referer`]=i),a}const TURN_TRACE_STATE_KEY=`ash.harness.turnTrace`;function getTurnTraceState(e){return e.state?.[TURN_TRACE_STATE_KEY]}function setTurnTraceState(e,t){let n={traceId:t.traceId,spanId:t.spanId,traceFlags:t.traceFlags};return{...e,state:{...e.state,[TURN_TRACE_STATE_KEY]:n}}}function resolveStepOtelContext(e,t,n){if(t)return trace.setSpan(context.active(),t);if(e){let e=getTurnTraceState(n);if(e){let t=trace.wrapSpanContext({traceId:e.traceId,spanId:e.spanId,traceFlags:e.traceFlags});return trace.setSpan(context.active(),t)}}}function createToolLoopHarness(t){let n=t.handleEvent,a=getInstrumentationConfig();a!==void 0&&ensureOtelIntegration();let o=a===void 0?void 0:trace.getTracer(`ash`),s=t.runtimeIdentity?.agentName;async function runStep(e,t){let n;if(o&&hasStepInput(t)){let t=a?.functionId??s,r={"ash.version":ashVersion,"ash.environment":environment,"ash.session.id":e.sessionId};t&&(r[`ai.telemetry.functionId`]=t),n=o.startSpan(`ai.ash.turn`,{attributes:r})}let r=resolveStepOtelContext(o,n,e),executeStep=()=>executeStepBody(e,t,n);try{return r?await context.with(r,executeStep):await executeStep()}finally{n?.end()}}async function executeStepBody(o,c,l){let d=o;l&&(d=setTurnTraceState(d,l.spanContext()));let _=getHarnessEmissionState(d.state),y=consumeDeferredStepInput({input:c,session:d});d=y.session;let b=await resolvePendingRuntimeActions({emit:n,session:d,stepInput:y.input});if(b.outcome===`unresolved`)return{next:null,session:b.session};d=b.session;let T=resolvePendingInput({history:b.messages,resolveApprovalKey:resolveApprovalKeyFromTools(t.tools),session:d,stepInput:y.input});if(T.outcome===`unresolved`)return{next:null,session:T.session};n&&hasStepInput(c)&&(_=await emitTurnPreamble(n,c??{},_,t.runtimeIdentity),d=setHarnessEmissionState(d,_),l&&l.setAttribute(`ash.turn.id`,_.turnId)),d=T.session;let O=T.messages;if(y.input?.context!==void 0)for(let e of y.input.context)O.push({content:e,role:`user`});if(y.input?.message!==void 0&&!T.deferredMessage){let e=await stageAttachmentsToSandbox(y.input.message);O.push({content:e,role:`user`})}let k=await t.resolveModel(d.agent.modelReference),A=detectPromptCachePath(k),j=A.kind===`anthropic-direct`?getAnthropicCacheMarker():void 0,M=buildGatewayAttributionHeaders(k,t.runtimeIdentity);({messages:O,session:d}=await maybeCompact({emit:n,emissionState:_,headers:M,messages:O,model:k,onCompaction:t.onCompaction,resolveModel:t.resolveModel,session:d,telemetry:enrichTelemetry(a,s)??void 0}));let N=getApprovedTools(d),P=contextStorage.getStore(),F=await hydrateSandboxAttachments(O),I=[],L=[];for(let e of F)e.role===`system`?I.push(e):L.push(e);if(P!==void 0){I.push(...buildDynamicInstructionMessages(P));let e=P.get(PendingSkillAnnouncementKey);e!==void 0&&e.length>0&&I.push({role:`system`,content:e})}let R=L,prepareModelCallInput=e=>{let t=e?[{role:`system`,content:e}]:[],n=d.agent.system?[{role:`system`,content:d.agent.system}]:[],r=I.length>0||t.length>0?[...t,...n,...I]:void 0,i=r!==void 0&&j?applySystemCacheBreakpoint(r,j):r??d.agent.system??void 0;return{instructions:i,telemetryRuntimeContext:buildTelemetryRuntimeContext({ashVersion,authored:a,emissionState:_,environment,modelInput:{instructions:i,messages:R},session:d})}},runOneModelCall=async e=>{let{instructions:i,telemetryRuntimeContext:o}=e.preparedInput??prepareModelCallInput(e.extraSystemNote),c=t.codeMode===!0,l=await buildToolSetWithProviderTools({approvedTools:N,capabilities:t.capabilities,disabledProviderTools:e.disabledProviderTools,modelReference:d.agent.modelReference,tools:t.tools});if(P!==void 0){let e=buildDynamicTools(P);for(let t of e)l[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}d.outputSchema!==void 0&&(l[FINAL_OUTPUT_TOOL_NAME]=buildFinalOutputTool(d.outputSchema));let u=c?(await applyCodeModeToToolSet({harnessTools:t.tools,lifecycle:n===void 0?void 0:createCodeModeLifecycle({emit:n,emissionState:_,tools:t.tools}),tools:l})).modelTools:l,f=j?applyLastToolCacheBreakpoint(u,j):u,p=resolveGatewayPinForStep({cachePath:A,modelReference:d.agent.modelReference,tools:f}),g=buildStepHooks({cachePath:A,emit:n,emissionState:_,emitStepStarted:e.suppressStepStartedEmission!==!0,gatewayPinProvider:p,marker:j,session:d}),v=new ToolLoopAgent({headers:M,instructions:i,model:k,onToolExecutionEnd:logToolExecutionError,onError(e){logError(log,`tool-loop stream error`,e.error)},onStepFinish:g.onStepFinish,prepareStep:g.prepareStep,runtimeContext:o,stopWhen:isStepCount(1),telemetry:enrichTelemetry(a,s,o),tools:f});return runModelCallWithRetries(async()=>{if(n){let e=await v.stream({messages:R}),{inlineActionResultCallIds:r,inlineToolResultParts:i}=await emitStreamContent(n,_,e.fullStream),a=await g.stepResult;return await emitStepActions(n,_,a,{excludedActionToolNames:new Set([ASK_QUESTION_TOOL_NAME,CODE_MODE_TOOL_NAME,FINAL_OUTPUT_TOOL_NAME]),inlineActionResultCallIds:r,tools:t.tools}),i.length>0?{content:a.content,finishReason:a.finishReason,response:{...a.response,messages:[{role:`tool`,content:[...i]},...a.response.messages]},text:a.text,toolCalls:a.toolCalls,toolResults:a.toolResults,usage:a.usage}:a}return await v.generate({messages:R}),await g.stepResult},{sessionId:d.sessionId,turnId:_.turnId})},z=prepareModelCallInput();n&&await emitStepStarted(n,_,O);let B=await continuePendingCodeModeConnectionAuth({capabilities:t.capabilities,config:t,emit:n,emissionState:_,messages:O,runStep,session:d});if(B!==null)return B;let V=await continuePendingCodeModeApproval({capabilities:t.capabilities,config:t,emit:n,emissionState:_,messages:O,runStep,session:d});if(V!==null)return V;let H;try{H=await runOneModelCall({preparedInput:z,suppressStepStartedEmission:!0})}catch(t){let r=await attemptUnsupportedProviderToolRecovery({error:t,runOneModelCall,sessionId:d.sessionId,turnId:_.turnId});if(r.outcome===`recovered`)H=r.result;else{let t=r.error;if(l&&recordErrorOnSpan(l,t),!n)throw t;let a=classifyModelCallError(t),o=createErrorId(),s=a===`terminal`?summarizeKnownModelCallConfigError(t):null,c=s===null?summarizeKnownModelCallRequestError(t):null,f=s?.message??c?.message??toErrorMessage(t),p=extractModelCallErrorDetails(t),m=buildModelCallFailureDetails({configSummary:s,error:t,errorId:o,modelCallDetails:p,requestSummary:c}),h=buildModelCallFailureLogFields({error:t,errorId:o,modelCallDetails:p,requestSummary:c,sessionId:d.sessionId,turnId:_.turnId});return a===`terminal`?(s===null?log.error(c?.message??`model call failed terminally`,h):log.error(`${s.name}: ${s.message}`,{errorId:o,sessionId:d.sessionId,turnId:_.turnId}),await emitFailedStep(n,_,{code:`MODEL_CALL_FAILED`,details:m,message:f,sessionId:d.sessionId}),{next:{done:!0,output:``},session:d}):(log.error(c?.message??`model call failed — parking session for retry by the user`,h),_=await emitRecoverableFailedTurn(n,_,{code:`MODEL_CALL_FAILED`,details:m,message:f}),{next:null,session:setHarnessEmissionState(d,_)})}}let U=accumulateTurnUsage({previous:getTurnUsageState(d.state),turnId:_.turnId,usage:H.usage??{}});d=setTurnUsageState(d,U);let W;try{W=formatLanguageModelGatewayId(k)}catch{W=void 0}return await setAshAttributes({"$ash.model":W,"$ash.input_tokens":U.inputTokens,"$ash.output_tokens":U.outputTokens,"$ash.cache_read_tokens":U.cacheReadTokens,"$ash.tool_count":t.tools.size}),handleStepResult({config:t,emit:n,emissionState:_,promptMessages:O,result:H,runStep,session:d})}return runStep}function buildModelCallFailureDetails(e){let{configSummary:t,error:r,errorId:i,modelCallDetails:a,requestSummary:o}=e;return t===null?o===null?{...formatError(r,i),...a}:{errorId:i,message:toErrorMessage(r),name:o.name,...a}:{errorId:i,message:t.message,name:t.name,...a}}function buildModelCallFailureLogFields(e){let t={errorId:e.errorId,sessionId:e.sessionId,turnId:e.turnId};return e.requestSummary===null?{...t,error:e.error}:{...t,details:e.modelCallDetails}}async function attemptUnsupportedProviderToolRecovery(e){let t=extractUnsupportedProviderToolTypes(e.error);if(t.length===0)return{outcome:`failed`,error:e.error};let n=[];for(let e of t){let t=resolveFrameworkToolFromUpstreamType(e);t!==null&&!n.includes(t)&&n.push(t)}if(n.length===0)return{outcome:`failed`,error:e.error};log.warn(`disabling unsupported provider tool(s); retrying step once`,{disabled:n,sessionId:e.sessionId,turnId:e.turnId,upstreamTypes:t});try{return{outcome:`recovered`,result:await e.runOneModelCall({disabledProviderTools:new Set(n),extraSystemNote:buildDisabledToolNote(n),suppressStepStartedEmission:!0})}}catch(e){return{outcome:`failed`,error:e}}}function buildDisabledToolNote(e){let t=e.join(`, `);return`The following ${e.length===1?`tool is`:`tools are`} not available with the current model and has been removed: ${t}. Proceed using the remaining tools or your training knowledge.`}async function handleStepResult(e){let{config:t,emit:n,promptMessages:r,result:i,runStep:o}=e,{emissionState:s,session:l}=e,u=i.response.messages,d=resolveAssistantStepText(u,i.text),f={...l,compaction:createNextCompactionConfig(l.compaction,r,i)};if(t.codeMode===!0){let{getCodeModeInterrupt:e,isCodeModeApprovalInterrupt:a}=await loadCodeModeModule(),o=e(i);if(o!==void 0){if(isCodeModeConnectionAuthInterrupt(o))return parkOnCodeModeConnectionAuth({baseSession:f,config:t,emit:n,emissionState:s,interrupt:o,promptMessages:r,responseMessages:u});if(a(o))return parkOnCodeModeApproval({baseSession:f,config:t,emit:n,emissionState:s,interrupt:o,promptMessages:r,responseMessages:u})}}let p=extractToolApprovalInputRequests({content:i.content??[]}),m=new Set(p.map(e=>e.action.callId)),h=extractQuestionInputRequests({toolCalls:i.toolCalls,excludedCallIds:m}),g=[...p,...h],v=(i.toolCalls??[]).filter(e=>!isInvalidToolCall(e)).filter(e=>t.tools.get(e.toolName)?.runtimeAction!==void 0).map(e=>createRuntimeActionRequestFromToolCall({toolCall:e,tools:t.tools}));if(v.length>0)return{next:null,session:setHarnessEmissionState(setPendingRuntimeActionBatch({actions:v,event:{sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId},responseMessages:u,session:{...f,history:[...r]}}),s)};if(g.length>0){let e=setPendingInputBatch({requests:g,responseMessages:u,session:{...f,history:[...r]}});return n&&(await n(createInputRequestedEvent({requests:g,sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId})),t.mode===`conversation`&&(s=await emitTurnEpilogue(n,s,t.mode),e=setHarnessEmissionState(e,s))),{next:null,session:e}}let x=(i.toolResults??[]).find(e=>isAuthorizationSignal(e.output));if(x&&isAuthorizationSignal(x.output)){let{challenges:e}=x.output;if(n)for(let t of e)await n(createAuthorizationRequiredEvent({authorization:t.challenge,name:t.name,description:t.challenge.instructions??`Authorization required for ${t.name}`,webhookUrl:t.hookUrl,sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId}));return{next:null,session:setHarnessEmissionState({...f,history:[...r],state:setPendingAuthorization(f.state,{challenges:e})},s)}}let S=pruneToolResults(r,t.retentionPolicies),C=S!==r,w=f.compaction;C&&w.lastKnownInputTokens!==void 0&&(w={recentWindowSize:w.recentWindowSize,threshold:w.threshold});let E=[...S,...u],D={...f,compaction:w,history:E};return!(D.outputSchema!==void 0&&extractFinalOutput(i)!==void 0)&&(u.at(-1)?.role===`tool`||hasDeferredStepInput(D))?(n&&(s=advanceStep(s),D=setHarnessEmissionState(D,s)),{next:o,session:D}):t.mode===`task`?finishTaskTurn({emissionState:s,emit:n,prunedHistory:S,result:i,schema:D.outputSchema,session:D,stepOutput:d}):finishConversationTurn({emissionState:s,emit:n,prunedHistory:S,result:i,schema:D.outputSchema,session:D})}const OUTPUT_SCHEMA_NOT_FULFILLED={code:`OUTPUT_SCHEMA_NOT_FULFILLED`,message:`The agent could not produce a result matching the requested schema.`};function extractFinalOutput(e){return(e.toolCalls??[]).find(e=>e.toolName===FINAL_OUTPUT_TOOL_NAME)?.input}function persistStructuredAssistantTurn(e,t,n){return{...e,history:[...t,{content:JSON.stringify(n),role:`assistant`}],outputSchema:void 0}}async function emitStructuredResult(e,t,n,r){return await e(createResultCompletedEvent({result:n,sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId})),emitTurnEpilogue(e,t,r)}async function finishTaskTurn(e){let{emit:t,prunedHistory:n,result:r,schema:i,stepOutput:a}=e,{emissionState:o,session:s}=e;if(i===void 0)return t&&(o=await emitTurnEpilogue(t,o,`task`),s=setHarnessEmissionState(s,o)),{next:{done:!0,output:a??``},session:s};let c=extractFinalOutput(r);return c===void 0?(t&&await emitFailedStep(t,o,{...OUTPUT_SCHEMA_NOT_FULFILLED,sessionId:s.sessionId}),{next:{done:!0,isError:!0,output:OUTPUT_SCHEMA_NOT_FULFILLED.message},session:s}):(s=persistStructuredAssistantTurn(s,n,c),t&&(o=await emitStructuredResult(t,o,c,`task`),s=setHarnessEmissionState(s,o)),{next:{done:!0,output:JSON.stringify(c)},session:s})}async function finishConversationTurn(e){let{emit:t,prunedHistory:n,result:r,schema:i}=e,{emissionState:a,session:o}=e;if(i===void 0)return t&&(a=await emitTurnEpilogue(t,a,`conversation`),o=setHarnessEmissionState(o,a)),{next:null,session:o};let s=extractFinalOutput(r);return s===void 0?(t&&(a=await emitRecoverableFailedTurn(t,a,OUTPUT_SCHEMA_NOT_FULFILLED),o=setHarnessEmissionState(o,a)),{next:null,session:o}):(o=persistStructuredAssistantTurn(o,n,s),t&&(a=await emitStructuredResult(t,a,s,`conversation`),o=setHarnessEmissionState(o,a)),{next:null,session:o})}async function continuePendingCodeModeApproval(e){let t=getPendingCodeModeApproval(e.session.state);if(t===void 0)return null;let{continueCodeModeApproval:n,getCodeModeApprovalResponse:i,isCodeModeApprovalInterrupt:a,unwrapCodeModeResult:o}=await loadCodeModeModule(),s=i([...e.messages],t.interrupt);if(s===void 0)return{next:null,session:e.session};let c=await buildCodeModeHostTools({approvedTools:getApprovedTools(e.session),capabilities:e.capabilities,tools:e.config.tools}),l;try{l=await n({approvalResponse:s,interrupt:t.interrupt,options:createAshCodeModeOptions({lifecycle:e.emit===void 0?void 0:createCodeModeLifecycle({emit:e.emit,emissionState:e.emissionState,skipReplayed:!0,tools:e.config.tools})}),tools:c})}catch(e){logError(log,`code-mode approval continuation failed`,e),l={error:`code_mode_continuation_failed`,message:toErrorMessage(e),retryable:!1}}let d=o(l),f=d.status===`interrupted`?d.interrupt:d.output,p=replaceCodeModeApprovalInterruptResult([...e.session.history,...t.responseMessages],t.interrupt,f),m=clearPendingCodeModeApproval({...e.session,history:p});if(d.status===`interrupted`){if(isCodeModeConnectionAuthInterrupt(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeConnectionAuth({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}if(a(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeApproval({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}}return{next:e.runStep,session:m}}async function parkOnCodeModeConnectionAuth(e){let t=[...e.interrupt.payload.challenges??[]];if(e.emit)for(let n of t)await e.emit(createAuthorizationRequiredEvent({authorization:n.challenge,name:n.name,description:n.challenge.instructions??`Authorization required for ${n.name}`,webhookUrl:n.hookUrl,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId}));return{next:null,session:setPendingCodeModeConnectionAuth({interrupt:e.interrupt,responseMessages:e.responseMessages,session:{...e.baseSession,history:[...e.promptMessages],state:setPendingAuthorization(e.baseSession.state,{challenges:t})}})}}async function continuePendingCodeModeConnectionAuth(e){let t=getPendingCodeModeConnectionAuth(e.session.state);if(t===void 0)return null;let{continueCodeModeInterrupt:n,isCodeModeApprovalInterrupt:i,replaceCodeModeInterruptResult:a,unwrapCodeModeResult:o}=await loadCodeModeModule(),s=await buildCodeModeHostTools({approvedTools:getApprovedTools(e.session),capabilities:e.capabilities,tools:e.config.tools}),c=e.emit===void 0?void 0:createCodeModeLifecycle({emit:e.emit,emissionState:e.emissionState,skipReplayed:!0,tools:e.config.tools}),l;try{l=await n({interrupt:t.interrupt,resolution:{status:`authorized`},tools:s,options:createAshCodeModeOptions({lifecycle:c})})}catch(e){logError(log,`code-mode interrupt continuation failed`,e),l={error:`code_mode_continuation_failed`,message:toErrorMessage(e),retryable:!1}}let d=o(l),f=d.status===`interrupted`?d.interrupt:d.output,p=a([...e.session.history,...t.responseMessages],t.interrupt.pendingContinuation,f),m=clearPendingCodeModeConnectionAuth({...e.session,history:p});if(d.status===`interrupted`){if(isCodeModeConnectionAuthInterrupt(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeConnectionAuth({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}if(i(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeApproval({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}}return{next:e.runStep,session:m}}async function parkOnCodeModeApproval(e){let{toCodeModeApprovalMessages:t}=await loadCodeModeModule(),n=t(e.interrupt),r=extractToolApprovalInputRequests({content:extractAssistantContent(n)}),i=setPendingInputBatch({requests:r,responseMessages:n,session:setPendingCodeModeApproval({interrupt:e.interrupt,responseMessages:e.responseMessages,session:{...e.baseSession,history:[...e.promptMessages]}})});if(e.emit&&(await e.emit(createInputRequestedEvent({requests:r,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId})),e.config.mode===`conversation`)){let t=await emitTurnEpilogue(e.emit,e.emissionState,e.config.mode);i=setHarnessEmissionState(i,t)}return{next:null,session:i}}function extractAssistantContent(e){let t=[];for(let n of e)n.role===`assistant`&&Array.isArray(n.content)&&t.push(...n.content);return t}function createNextCompactionConfig(e,t,n){let r={recentWindowSize:e.recentWindowSize,threshold:e.threshold};return n.usage?.inputTokens!==void 0&&(r.lastKnownInputTokens=n.usage.inputTokens,r.lastKnownPromptMessageCount=t.length),r}async function maybeCompact(e){let{emit:t,emissionState:n}=e,r=e.messages,i=e.session;if(!shouldCompact(r,i.compaction))return{messages:r,session:i};let a=await resolveCompactionModel({compactionModelReference:i.agent.compactionModelReference,model:e.model,modelReference:i.agent.modelReference,resolveModel:e.resolveModel});if(t&&await t(createCompactionRequestedEvent({modelId:formatLanguageModelGatewayId(a.model),sequence:n.sequence,sessionId:i.sessionId,turnId:n.turnId,usageInputTokens:getInputTokenCount(r,i.compaction)})),r=await compactMessages(r,a.model,i.compaction,a.providerOptions,e.telemetry,e.headers),e.onCompaction){let t=await e.onCompaction(i);i=t.session;for(let e of t.messages)r.push(e)}return t&&await t(createCompactionCompletedEvent({modelId:formatLanguageModelGatewayId(a.model),sequence:n.sequence,sessionId:i.sessionId,turnId:n.turnId})),{messages:r,session:i}}function resolveApprovalKeyFromTools(e){return t=>{let n=e.get(t.action.toolName);if(n?.approvalKey!==void 0)return n.approvalKey(t.action.input)}}async function runModelCallWithRetries(e,t){for(let n=1;;n++)try{return await e()}catch(e){if(n===3||classifyModelCallError(e)!==`retry`)throw e;let r=500*2**(n-1)+Math.floor(Math.random()*250);log.warn(`model call failed transiently — retrying`,{attempt:n,delayMs:r,sessionId:t.sessionId,turnId:t.turnId,error:e}),await new Promise(e=>setTimeout(e,r))}}export{createToolLoopHarness};
1
+ import{createErrorId,createLogger,formatError,logError,recordErrorOnSpan}from"#internal/logging.js";import{createAuthorizationRequiredEvent,createCompactionCompletedEvent,createCompactionRequestedEvent,createInputRequestedEvent,createResultCompletedEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{resolveInstalledPackageInfo}from"#internal/application/package.js";import{formatLanguageModelGatewayId}from"#internal/runtime-model.js";import{contextStorage}from"#context/container.js";import{ToolLoopAgent,isStepCount}from"ai";import{setAshAttributes}from"#runtime/attributes/emit.js";import{createRuntimeActionRequestFromToolCall,resolvePendingRuntimeActions,setPendingRuntimeActionBatch}from"#harness/runtime-actions.js";import{advanceStep,emitFailedStep,emitRecoverableFailedTurn,emitStepStarted,emitStreamContent,emitTurnEpilogue,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{resolveAssistantStepText}from"#harness/messages.js";import{buildDynamicInstructionMessages}from"#context/dynamic-instruction-lifecycle.js";import{PendingSkillAnnouncementKey}from"#context/dynamic-skill-lifecycle.js";import{consumeDeferredStepInput,getApprovedTools,hasDeferredStepInput,hasStepInput,resolvePendingInput,setPendingInputBatch}from"#harness/input-requests.js";import{isAuthorizationSignal,setPendingAuthorization}from"#harness/authorization.js";import{isCodeModeConnectionAuthInterrupt}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSetWithProviderTools}from"#harness/tools.js";import{ASK_QUESTION_TOOL_NAME}from"#runtime/framework-tools/ask-question.js";import{WEB_SEARCH_TOOL_DEFINITION}from"#runtime/framework-tools/web-search.js";import{extractQuestionInputRequests,extractToolApprovalInputRequests}from"#harness/input-extraction.js";import{applyLastToolCacheBreakpoint,applySystemCacheBreakpoint,detectPromptCachePath,getAnthropicCacheMarker}from"#harness/prompt-cache.js";import{resolveFrameworkToolFromUpstreamType,resolveGatewayPinForWebSearchBackend,resolveWebSearchBackend}from"#harness/provider-tools.js";import{context,trace}from"#compiled/@opentelemetry/api/index.js";import{hydrateSandboxAttachments,stageAttachmentsToSandbox}from"#harness/attachment-staging.js";import{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions}from"#harness/code-mode.js";import{createCodeModeLifecycle}from"#harness/code-mode-lifecycle.js";import{clearPendingCodeModeApproval,getPendingCodeModeApproval,replaceCodeModeApprovalInterruptResult,setPendingCodeModeApproval}from"#harness/code-mode-approval.js";import{compactMessages,getInputTokenCount,resolveCompactionModel,shouldCompact}from"#harness/compaction.js";import{accumulateTurnUsage,getTurnUsageState,setTurnUsageState}from"#harness/turn-tag-state.js";import{buildTelemetryRuntimeContext}from"#harness/instrumentation-metadata.js";import{getInstrumentationConfig}from"#harness/instrumentation-config.js";import{clearPendingCodeModeConnectionAuth,getPendingCodeModeConnectionAuth,setPendingCodeModeConnectionAuth}from"#harness/code-mode-connection-auth-state.js";import{classifyModelCallError,extractModelCallErrorDetails,extractUnsupportedProviderToolTypes,summarizeKnownModelCallConfigError,summarizeKnownModelCallRequestError}from"#harness/model-call-error.js";import{ensureOtelIntegration}from"#harness/otel-integration.js";import{buildStepHooks,emitStepActions,isInvalidToolCall}from"#harness/step-hooks.js";import{pruneToolResults}from"#harness/tool-result-pruning.js";import{FINAL_OUTPUT_TOOL_NAME,buildFinalOutputTool}from"#runtime/framework-tools/final-output.js";const environment=process.env.NODE_ENV??`unknown`,ashVersion=resolveInstalledPackageInfo().version,log=createLogger(`harness.tool-loop`);function logToolExecutionError(e){e.toolOutput.type===`tool-error`&&logError(log,`tool execution failed`,e.toolOutput.error,{toolName:e.toolCall.toolName,toolCallId:e.toolCall.toolCallId})}function enrichTelemetry(e,t,n){if(e===void 0)return;let r={};for(let e of Object.keys(n??{}))r[e]=!0;return{functionId:e.functionId??t,includeRuntimeContext:r,isEnabled:!0,recordInputs:e.recordInputs??!0,recordOutputs:e.recordOutputs??!0}}function resolveGatewayPinForStep(e){if(e.cachePath.kind!==`gateway-auto`||e.tools[WEB_SEARCH_TOOL_DEFINITION.name]===void 0)return;let t=resolveWebSearchBackend(e.modelReference);return t===null?void 0:resolveGatewayPinForWebSearchBackend(t)??void 0}function buildGatewayAttributionHeaders(e,t){if(typeof e!=`string`)return;let n=t?.agentName??t?.agentId,r=process.env.VERCEL_PROJECT_PRODUCTION_URL||process.env.VERCEL_URL,i=r?`https://${r}`:void 0;if(!n&&!i)return;let a={};return n&&(a[`x-title`]=n),i&&(a[`http-referer`]=i),a}const TURN_TRACE_STATE_KEY=`ash.harness.turnTrace`;function getTurnTraceState(e){return e.state?.[TURN_TRACE_STATE_KEY]}function setTurnTraceState(e,t){let n={traceId:t.traceId,spanId:t.spanId,traceFlags:t.traceFlags};return{...e,state:{...e.state,[TURN_TRACE_STATE_KEY]:n}}}function resolveStepOtelContext(e,t,n){if(t)return trace.setSpan(context.active(),t);if(e){let e=getTurnTraceState(n);if(e){let t=trace.wrapSpanContext({traceId:e.traceId,spanId:e.spanId,traceFlags:e.traceFlags});return trace.setSpan(context.active(),t)}}}function createToolLoopHarness(t){let n=t.handleEvent,a=getInstrumentationConfig();a!==void 0&&ensureOtelIntegration();let o=a===void 0?void 0:trace.getTracer(`ash`),s=t.runtimeIdentity?.agentName;async function runStep(e,t){let n;if(o&&hasStepInput(t)){let t=a?.functionId??s,r={"ash.version":ashVersion,"ash.environment":environment,"ash.session.id":e.sessionId};t&&(r[`ai.telemetry.functionId`]=t),n=o.startSpan(`ai.ash.turn`,{attributes:r})}let r=resolveStepOtelContext(o,n,e),executeStep=()=>executeStepBody(e,t,n);try{return r?await context.with(r,executeStep):await executeStep()}finally{n?.end()}}async function executeStepBody(o,c,l){let d=o;l&&(d=setTurnTraceState(d,l.spanContext()));let _=getHarnessEmissionState(d.state),y=consumeDeferredStepInput({input:c,session:d});d=y.session;let b=await resolvePendingRuntimeActions({emit:n,session:d,stepInput:y.input});if(b.outcome===`unresolved`)return{next:null,session:b.session};d=b.session;let T=resolvePendingInput({history:b.messages,resolveApprovalKey:resolveApprovalKeyFromTools(t.tools),session:d,stepInput:y.input});if(T.outcome===`unresolved`)return{next:null,session:T.session};n&&hasStepInput(c)&&(_=await emitTurnPreamble(n,c??{},_,t.runtimeIdentity),d=setHarnessEmissionState(d,_),l&&l.setAttribute(`ash.turn.id`,_.turnId)),d=T.session;let O=T.messages;if(y.input?.context!==void 0)for(let e of y.input.context)O.push({content:e,role:`user`});if(y.input?.message!==void 0&&!T.deferredMessage){let e=await stageAttachmentsToSandbox(y.input.message);O.push({content:e,role:`user`})}let k=await t.resolveModel(d.agent.modelReference),A=detectPromptCachePath(k),j=A.kind===`anthropic-direct`?getAnthropicCacheMarker():void 0,M=buildGatewayAttributionHeaders(k,t.runtimeIdentity);({messages:O,session:d}=await maybeCompact({emit:n,emissionState:_,headers:M,messages:O,model:k,onCompaction:t.onCompaction,resolveModel:t.resolveModel,session:d,telemetry:enrichTelemetry(a,s)??void 0}));let N=getApprovedTools(d),P=contextStorage.getStore(),F=await hydrateSandboxAttachments(O),I=[],L=[];for(let e of F)e.role===`system`?I.push(e):L.push(e);if(P!==void 0){I.push(...buildDynamicInstructionMessages(P));let e=P.get(PendingSkillAnnouncementKey);e!==void 0&&e.length>0&&I.push({role:`system`,content:e})}let R=L,prepareModelCallInput=e=>{let t=e?[{role:`system`,content:e}]:[],n=d.agent.system?[{role:`system`,content:d.agent.system}]:[],r=I.length>0||t.length>0?[...t,...n,...I]:void 0,i=r!==void 0&&j?applySystemCacheBreakpoint(r,j):r??d.agent.system??void 0;return{instructions:i,telemetryRuntimeContext:buildTelemetryRuntimeContext({ashVersion,authored:a,emissionState:_,environment,modelInput:{instructions:i,messages:R},session:d})}},runOneModelCall=async e=>{let{instructions:i,telemetryRuntimeContext:o}=e.preparedInput??prepareModelCallInput(e.extraSystemNote),c=t.codeMode===!0,l=await buildToolSetWithProviderTools({approvedTools:N,capabilities:t.capabilities,disabledProviderTools:e.disabledProviderTools,modelReference:d.agent.modelReference,tools:t.tools});if(P!==void 0){let e=buildDynamicTools(P);for(let t of e)l[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}d.outputSchema!==void 0&&(l[FINAL_OUTPUT_TOOL_NAME]=buildFinalOutputTool(d.outputSchema));let u=c?(await applyCodeModeToToolSet({harnessTools:t.tools,lifecycle:n===void 0?void 0:createCodeModeLifecycle({emit:n,emissionState:_,tools:t.tools}),tools:l})).modelTools:l,f=j?applyLastToolCacheBreakpoint(u,j):u,p=resolveGatewayPinForStep({cachePath:A,modelReference:d.agent.modelReference,tools:f}),g=buildStepHooks({cachePath:A,emit:n,emissionState:_,emitStepStarted:e.suppressStepStartedEmission!==!0,gatewayPinProvider:p,marker:j,session:d}),v=new ToolLoopAgent({headers:M,instructions:i,model:k,onToolExecutionEnd:logToolExecutionError,onError(e){logError(log,`tool-loop stream error`,e.error)},onStepFinish:g.onStepFinish,prepareStep:g.prepareStep,runtimeContext:o,stopWhen:isStepCount(1),telemetry:enrichTelemetry(a,s,o),tools:f});return runModelCallWithRetries(async()=>{if(n){let e=await v.stream({messages:R}),{inlineActionResultCallIds:r,inlineToolResultParts:i}=await emitStreamContent(n,_,e.fullStream),a=await g.stepResult;return await emitStepActions(n,_,a,{excludedActionToolNames:new Set([ASK_QUESTION_TOOL_NAME,CODE_MODE_TOOL_NAME,FINAL_OUTPUT_TOOL_NAME]),inlineActionResultCallIds:r,tools:t.tools}),i.length>0?{content:a.content,finishReason:a.finishReason,response:{...a.response,messages:[{role:`tool`,content:[...i]},...a.response.messages]},text:a.text,toolCalls:a.toolCalls,toolResults:a.toolResults,usage:a.usage}:a}return await v.generate({messages:R}),await g.stepResult},{sessionId:d.sessionId,turnId:_.turnId})},z=prepareModelCallInput();n&&await emitStepStarted(n,_,O);let B=await continuePendingCodeModeConnectionAuth({capabilities:t.capabilities,config:t,emit:n,emissionState:_,messages:O,runStep,session:d});if(B!==null)return B;let V=await continuePendingCodeModeApproval({capabilities:t.capabilities,config:t,emit:n,emissionState:_,messages:O,runStep,session:d});if(V!==null)return V;let H;try{H=await runOneModelCall({preparedInput:z,suppressStepStartedEmission:!0})}catch(t){let r=await attemptUnsupportedProviderToolRecovery({error:t,runOneModelCall,sessionId:d.sessionId,turnId:_.turnId});if(r.outcome===`recovered`)H=r.result;else{let t=r.error;if(l&&recordErrorOnSpan(l,t),!n)throw t;let a=classifyModelCallError(t),o=createErrorId(),s=a===`terminal`?summarizeKnownModelCallConfigError(t):null,c=s===null?summarizeKnownModelCallRequestError(t):null,f=s?.message??c?.message??toErrorMessage(t),p=extractModelCallErrorDetails(t),m=buildModelCallFailureDetails({configSummary:s,error:t,errorId:o,modelCallDetails:p,requestSummary:c}),h=buildModelCallFailureLogFields({error:t,errorId:o,modelCallDetails:p,requestSummary:c,sessionId:d.sessionId,turnId:_.turnId});return a===`terminal`?(s===null?log.error(c?.message??`model call failed terminally`,h):log.error(`${s.name}: ${s.message}`,{errorId:o,sessionId:d.sessionId,turnId:_.turnId}),await emitFailedStep(n,_,{code:`MODEL_CALL_FAILED`,details:m,message:f,sessionId:d.sessionId}),{next:{done:!0,output:``},session:d}):(log.error(c?.message??`model call failed — parking session for retry by the user`,h),_=await emitRecoverableFailedTurn(n,_,{code:`MODEL_CALL_FAILED`,details:m,message:f}),{next:null,session:setHarnessEmissionState(d,_)})}}let U=accumulateTurnUsage({previous:getTurnUsageState(d.state),turnId:_.turnId,usage:H.usage??{}});d=setTurnUsageState(d,U);let W;try{W=formatLanguageModelGatewayId(k)}catch{W=void 0}return await setAshAttributes({"$ash.model":W,"$ash.input_tokens":U.inputTokens,"$ash.output_tokens":U.outputTokens,"$ash.cache_read_tokens":U.cacheReadTokens,"$ash.tool_count":t.tools.size}),handleStepResult({config:t,emit:n,emissionState:_,promptMessages:O,result:H,runStep,session:d})}return runStep}function buildModelCallFailureDetails(e){let{configSummary:t,error:r,errorId:i,modelCallDetails:a,requestSummary:o}=e;return t===null?o===null?{...formatError(r,i),...a}:{errorId:i,message:toErrorMessage(r),name:o.name,...a}:{errorId:i,message:t.message,name:t.name,...a}}function buildModelCallFailureLogFields(e){let t={errorId:e.errorId,sessionId:e.sessionId,turnId:e.turnId};return e.requestSummary===null?{...t,error:e.error}:{...t,details:e.modelCallDetails}}async function attemptUnsupportedProviderToolRecovery(e){let t=extractUnsupportedProviderToolTypes(e.error);if(t.length===0)return{outcome:`failed`,error:e.error};let n=[];for(let e of t){let t=resolveFrameworkToolFromUpstreamType(e);t!==null&&!n.includes(t)&&n.push(t)}if(n.length===0)return{outcome:`failed`,error:e.error};log.warn(`disabling unsupported provider tool(s); retrying step once`,{disabled:n,sessionId:e.sessionId,turnId:e.turnId,upstreamTypes:t});try{return{outcome:`recovered`,result:await e.runOneModelCall({disabledProviderTools:new Set(n),extraSystemNote:buildDisabledToolNote(n),suppressStepStartedEmission:!0})}}catch(e){return{outcome:`failed`,error:e}}}function buildDisabledToolNote(e){let t=e.join(`, `);return`The following ${e.length===1?`tool is`:`tools are`} not available with the current model and has been removed: ${t}. Proceed using the remaining tools or your training knowledge.`}async function handleStepResult(e){let{config:t,emit:n,promptMessages:r,result:i,runStep:o}=e,{emissionState:s,session:l}=e,u=i.response.messages,d=resolveAssistantStepText(u,i.text),f={...l,compaction:createNextCompactionConfig(l.compaction,r,i)};if(t.codeMode===!0){let{getCodeModeInterrupt:e,isCodeModeApprovalInterrupt:a}=await loadCodeModeModule(),o=e(i);if(o!==void 0){if(isCodeModeConnectionAuthInterrupt(o))return parkOnCodeModeConnectionAuth({baseSession:f,config:t,emit:n,emissionState:s,interrupt:o,promptMessages:r,responseMessages:u});if(a(o))return parkOnCodeModeApproval({baseSession:f,config:t,emit:n,emissionState:s,interrupt:o,promptMessages:r,responseMessages:u})}}let p=extractToolApprovalInputRequests({content:i.content??[]}),m=new Set(p.map(e=>e.action.callId)),h=extractQuestionInputRequests({toolCalls:i.toolCalls,excludedCallIds:m}),g=[...p,...h],v=(i.toolCalls??[]).filter(e=>!isInvalidToolCall(e)).filter(e=>t.tools.get(e.toolName)?.runtimeAction!==void 0).map(e=>createRuntimeActionRequestFromToolCall({toolCall:e,tools:t.tools}));if(v.length>0)return{next:null,session:setHarnessEmissionState(setPendingRuntimeActionBatch({actions:v,event:{sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId},responseMessages:u,session:{...f,history:[...r]}}),s)};if(g.length>0){let e=setPendingInputBatch({requests:g,responseMessages:u,session:{...f,history:[...r]}});return n&&(await n(createInputRequestedEvent({requests:g,sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId})),t.mode===`conversation`&&(s=await emitTurnEpilogue(n,s,t.mode),e=setHarnessEmissionState(e,s))),{next:null,session:e}}let x=(i.toolResults??[]).find(e=>isAuthorizationSignal(e.output));if(x&&isAuthorizationSignal(x.output)){let{challenges:e}=x.output;if(n)for(let t of e)await n(createAuthorizationRequiredEvent({authorization:t.challenge,name:t.name,description:t.challenge.instructions??`Authorization required for ${t.name}`,webhookUrl:t.hookUrl,sequence:s.sequence,stepIndex:s.stepIndex,turnId:s.turnId}));return{next:null,session:setHarnessEmissionState({...f,history:[...r],state:setPendingAuthorization(f.state,{challenges:e})},s)}}let S=pruneToolResults(r),C=S!==r,w=f.compaction;C&&w.lastKnownInputTokens!==void 0&&(w={recentWindowSize:w.recentWindowSize,threshold:w.threshold});let E=[...S,...u],D={...f,compaction:w,history:E};return!(D.outputSchema!==void 0&&extractFinalOutput(i)!==void 0)&&(u.at(-1)?.role===`tool`||hasDeferredStepInput(D))?(n&&(s=advanceStep(s),D=setHarnessEmissionState(D,s)),{next:o,session:D}):t.mode===`task`?finishTaskTurn({emissionState:s,emit:n,prunedHistory:S,result:i,schema:D.outputSchema,session:D,stepOutput:d}):finishConversationTurn({emissionState:s,emit:n,prunedHistory:S,result:i,schema:D.outputSchema,session:D})}const OUTPUT_SCHEMA_NOT_FULFILLED={code:`OUTPUT_SCHEMA_NOT_FULFILLED`,message:`The agent could not produce a result matching the requested schema.`};function extractFinalOutput(e){return(e.toolCalls??[]).find(e=>e.toolName===FINAL_OUTPUT_TOOL_NAME)?.input}function persistStructuredAssistantTurn(e,t,n){return{...e,history:[...t,{content:JSON.stringify(n),role:`assistant`}],outputSchema:void 0}}async function emitStructuredResult(e,t,n,r){return await e(createResultCompletedEvent({result:n,sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId})),emitTurnEpilogue(e,t,r)}async function finishTaskTurn(e){let{emit:t,prunedHistory:n,result:r,schema:i,stepOutput:a}=e,{emissionState:o,session:s}=e;if(i===void 0)return t&&(o=await emitTurnEpilogue(t,o,`task`),s=setHarnessEmissionState(s,o)),{next:{done:!0,output:a??``},session:s};let c=extractFinalOutput(r);return c===void 0?(t&&await emitFailedStep(t,o,{...OUTPUT_SCHEMA_NOT_FULFILLED,sessionId:s.sessionId}),{next:{done:!0,isError:!0,output:OUTPUT_SCHEMA_NOT_FULFILLED.message},session:s}):(s=persistStructuredAssistantTurn(s,n,c),t&&(o=await emitStructuredResult(t,o,c,`task`),s=setHarnessEmissionState(s,o)),{next:{done:!0,output:JSON.stringify(c)},session:s})}async function finishConversationTurn(e){let{emit:t,prunedHistory:n,result:r,schema:i}=e,{emissionState:a,session:o}=e;if(i===void 0)return t&&(a=await emitTurnEpilogue(t,a,`conversation`),o=setHarnessEmissionState(o,a)),{next:null,session:o};let s=extractFinalOutput(r);return s===void 0?(t&&(a=await emitRecoverableFailedTurn(t,a,OUTPUT_SCHEMA_NOT_FULFILLED),o=setHarnessEmissionState(o,a)),{next:null,session:o}):(o=persistStructuredAssistantTurn(o,n,s),t&&(a=await emitStructuredResult(t,a,s,`conversation`),o=setHarnessEmissionState(o,a)),{next:null,session:o})}async function continuePendingCodeModeApproval(e){let t=getPendingCodeModeApproval(e.session.state);if(t===void 0)return null;let{continueCodeModeApproval:n,getCodeModeApprovalResponse:i,isCodeModeApprovalInterrupt:a,unwrapCodeModeResult:o}=await loadCodeModeModule(),s=i([...e.messages],t.interrupt);if(s===void 0)return{next:null,session:e.session};let c=await buildCodeModeHostTools({approvedTools:getApprovedTools(e.session),capabilities:e.capabilities,tools:e.config.tools}),l;try{l=await n({approvalResponse:s,interrupt:t.interrupt,options:createAshCodeModeOptions({lifecycle:e.emit===void 0?void 0:createCodeModeLifecycle({emit:e.emit,emissionState:e.emissionState,skipReplayed:!0,tools:e.config.tools})}),tools:c})}catch(e){logError(log,`code-mode approval continuation failed`,e),l={error:`code_mode_continuation_failed`,message:toErrorMessage(e),retryable:!1}}let d=o(l),f=d.status===`interrupted`?d.interrupt:d.output,p=replaceCodeModeApprovalInterruptResult([...e.session.history,...t.responseMessages],t.interrupt,f),m=clearPendingCodeModeApproval({...e.session,history:p});if(d.status===`interrupted`){if(isCodeModeConnectionAuthInterrupt(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeConnectionAuth({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}if(a(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeApproval({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}}return{next:e.runStep,session:m}}async function parkOnCodeModeConnectionAuth(e){let t=[...e.interrupt.payload.challenges??[]];if(e.emit)for(let n of t)await e.emit(createAuthorizationRequiredEvent({authorization:n.challenge,name:n.name,description:n.challenge.instructions??`Authorization required for ${n.name}`,webhookUrl:n.hookUrl,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId}));return{next:null,session:setPendingCodeModeConnectionAuth({interrupt:e.interrupt,responseMessages:e.responseMessages,session:{...e.baseSession,history:[...e.promptMessages],state:setPendingAuthorization(e.baseSession.state,{challenges:t})}})}}async function continuePendingCodeModeConnectionAuth(e){let t=getPendingCodeModeConnectionAuth(e.session.state);if(t===void 0)return null;let{continueCodeModeInterrupt:n,isCodeModeApprovalInterrupt:i,replaceCodeModeInterruptResult:a,unwrapCodeModeResult:o}=await loadCodeModeModule(),s=await buildCodeModeHostTools({approvedTools:getApprovedTools(e.session),capabilities:e.capabilities,tools:e.config.tools}),c=e.emit===void 0?void 0:createCodeModeLifecycle({emit:e.emit,emissionState:e.emissionState,skipReplayed:!0,tools:e.config.tools}),l;try{l=await n({interrupt:t.interrupt,resolution:{status:`authorized`},tools:s,options:createAshCodeModeOptions({lifecycle:c})})}catch(e){logError(log,`code-mode interrupt continuation failed`,e),l={error:`code_mode_continuation_failed`,message:toErrorMessage(e),retryable:!1}}let d=o(l),f=d.status===`interrupted`?d.interrupt:d.output,p=a([...e.session.history,...t.responseMessages],t.interrupt.pendingContinuation,f),m=clearPendingCodeModeConnectionAuth({...e.session,history:p});if(d.status===`interrupted`){if(isCodeModeConnectionAuthInterrupt(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeConnectionAuth({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}if(i(d.interrupt)){let t=e.session.history.length,n=p.slice(0,t),r=p.slice(t);return m={...m,history:n},parkOnCodeModeApproval({baseSession:m,config:e.config,emit:e.emit,emissionState:e.emissionState,interrupt:d.interrupt,promptMessages:n,responseMessages:r})}}return{next:e.runStep,session:m}}async function parkOnCodeModeApproval(e){let{toCodeModeApprovalMessages:t}=await loadCodeModeModule(),n=t(e.interrupt),r=extractToolApprovalInputRequests({content:extractAssistantContent(n)}),i=setPendingInputBatch({requests:r,responseMessages:n,session:setPendingCodeModeApproval({interrupt:e.interrupt,responseMessages:e.responseMessages,session:{...e.baseSession,history:[...e.promptMessages]}})});if(e.emit&&(await e.emit(createInputRequestedEvent({requests:r,sequence:e.emissionState.sequence,stepIndex:e.emissionState.stepIndex,turnId:e.emissionState.turnId})),e.config.mode===`conversation`)){let t=await emitTurnEpilogue(e.emit,e.emissionState,e.config.mode);i=setHarnessEmissionState(i,t)}return{next:null,session:i}}function extractAssistantContent(e){let t=[];for(let n of e)n.role===`assistant`&&Array.isArray(n.content)&&t.push(...n.content);return t}function createNextCompactionConfig(e,t,n){let r={recentWindowSize:e.recentWindowSize,threshold:e.threshold};return n.usage?.inputTokens!==void 0&&(r.lastKnownInputTokens=n.usage.inputTokens,r.lastKnownPromptMessageCount=t.length),r}async function maybeCompact(e){let{emit:t,emissionState:n}=e,r=e.messages,i=e.session;if(!shouldCompact(r,i.compaction))return{messages:r,session:i};let a=await resolveCompactionModel({compactionModelReference:i.agent.compactionModelReference,model:e.model,modelReference:i.agent.modelReference,resolveModel:e.resolveModel});if(t&&await t(createCompactionRequestedEvent({modelId:formatLanguageModelGatewayId(a.model),sequence:n.sequence,sessionId:i.sessionId,turnId:n.turnId,usageInputTokens:getInputTokenCount(r,i.compaction)})),r=await compactMessages(r,a.model,i.compaction,a.providerOptions,e.telemetry,e.headers),e.onCompaction){let t=await e.onCompaction(i);i=t.session;for(let e of t.messages)r.push(e)}return t&&await t(createCompactionCompletedEvent({modelId:formatLanguageModelGatewayId(a.model),sequence:n.sequence,sessionId:i.sessionId,turnId:n.turnId})),{messages:r,session:i}}function resolveApprovalKeyFromTools(e){return t=>{let n=e.get(t.action.toolName);if(n?.approvalKey!==void 0)return n.approvalKey(t.action.input)}}async function runModelCallWithRetries(e,t){for(let n=1;;n++)try{return await e()}catch(e){if(n===3||classifyModelCallError(e)!==`retry`)throw e;let r=500*2**(n-1)+Math.floor(Math.random()*250);log.warn(`model call failed transiently — retrying`,{attempt:n,delayMs:r,sessionId:t.sessionId,turnId:t.turnId,error:e}),await new Promise(e=>setTimeout(e,r))}}export{createToolLoopHarness};
@@ -1,22 +1,8 @@
1
1
  import type { ModelMessage } from "ai";
2
- import type { ToolRetentionPolicy } from "#public/definitions/tool.js";
3
- /**
4
- * Per-tool retention policy accepted by {@link pruneToolResults}.
5
- *
6
- * This is the harness-layer view of {@link ToolRetentionPolicy} with
7
- * the `"auto"` literal excluded — callers omit `"auto"` entries from
8
- * the map so the default placeholder behavior applies to absent keys.
9
- */
10
- export type RetentionPolicy = Exclude<ToolRetentionPolicy, "auto">;
11
2
  /**
12
3
  * Reactively prunes old tool-result content from conversation history.
13
4
  *
14
5
  * Recent results inside {@link PRUNE_PROTECT_TOKENS} are preserved.
15
- * Older results are kept, summarized, or replaced with
16
- * {@link PRUNED_PLACEHOLDER} based on each tool's retention policy.
17
- *
18
- * @param retentionPolicies - Map from tool name to retention policy.
19
- * Tools absent from the map use the default `"auto"` placeholder
20
- * behavior.
6
+ * Older results are replaced with {@link PRUNED_PLACEHOLDER}.
21
7
  */
22
- export declare function pruneToolResults(messages: readonly ModelMessage[], retentionPolicies?: ReadonlyMap<string, RetentionPolicy>): ModelMessage[];
8
+ export declare function pruneToolResults(messages: readonly ModelMessage[]): ModelMessage[];
@@ -1 +1 @@
1
- import{estimateTokens}from"#harness/compaction.js";function pruneToolResults(e,t){let n=collectToolResultLocations(e);if(n.length===0)return e;let r=0,i=0,a=new Set;for(let e of n)t?.get(e.toolName)!==`keep`&&(r<4e4?r+=e.tokens:(a.add(e.key),i+=e.tokens));return i<2e4?e:e.map((e,n)=>{if(e.role!==`tool`||!Array.isArray(e.content))return e;let r=!1,i=e.content.map((e,i)=>{let o=`${n}:${i}`;if(!a.has(o))return e;r=!0;let s=e,c=t?.get(s.toolName),l=typeof c==`function`?c(s.output).summary:`[Tool result pruned to save context. Call the tool again if needed.]`;return{...e,output:createPrunedToolResultOutput(l)}});return r?{...e,content:i}:e})}function collectToolResultLocations(t){let n=[];for(let r=t.length-1;r>=0;r--){let i=t[r];if(!(i===void 0||i.role!==`tool`||!Array.isArray(i.content)))for(let t=i.content.length-1;t>=0;t--){let a=i.content[t];a?.type===`tool-result`&&n.push({key:`${r}:${t}`,tokens:estimateTokens(a),toolName:a.toolName})}}return n}function createPrunedToolResultOutput(e){return{type:`text`,value:e}}export{pruneToolResults};
1
+ import{estimateTokens}from"#harness/compaction.js";function pruneToolResults(e){let t=collectToolResultLocations(e);if(t.length===0)return e;let n=0,r=0,i=new Set;for(let e of t)n<4e4?n+=e.tokens:(i.add(e.key),r+=e.tokens);return r<2e4?e:e.map((e,t)=>{if(e.role!==`tool`||!Array.isArray(e.content))return e;let n=!1,r=e.content.map((e,r)=>{let a=`${t}:${r}`;return i.has(a)?(n=!0,{...e,output:createPrunedToolResultOutput(`[Tool result pruned to save context. Call the tool again if needed.]`)}):e});return n?{...e,content:r}:e})}function collectToolResultLocations(t){let n=[];for(let r=t.length-1;r>=0;r--){let i=t[r];if(!(i===void 0||i.role!==`tool`||!Array.isArray(i.content)))for(let t=i.content.length-1;t>=0;t--){let a=i.content[t];a?.type===`tool-result`&&n.push({key:`${r}:${t}`,tokens:estimateTokens(a)})}}return n}function createPrunedToolResultOutput(e){return{type:`text`,value:e}}export{pruneToolResults};
@@ -9,7 +9,6 @@ import type { SandboxState } from "#sandbox/state.js";
9
9
  import type { JsonObject } from "#shared/json.js";
10
10
  import type { InternalToolDefinition } from "#shared/tool-definition.js";
11
11
  import type { HarnessToolDefinition } from "#harness/execute-tool.js";
12
- import type { RetentionPolicy } from "#harness/tool-result-pruning.js";
13
12
  /**
14
13
  * Serializable tool definition stored on the session.
15
14
  *
@@ -200,14 +199,6 @@ export interface ToolLoopHarnessConfig {
200
199
  * compacted history.
201
200
  */
202
201
  readonly onCompaction?: (session: HarnessSession) => CompactionCallbackResult | Promise<CompactionCallbackResult>;
203
- /**
204
- * Tool-specific retention policies collected by the execution layer
205
- * from tools that declare `retentionPolicy` as `"keep"` or a function
206
- * on their resolved definitions. Entries with the default `"auto"`
207
- * policy are omitted — the pruning system's default placeholder
208
- * behavior already covers them.
209
- */
210
- readonly retentionPolicies?: ReadonlyMap<string, RetentionPolicy>;
211
202
  readonly resolveModel: (reference: RuntimeModelReference) => Promise<LanguageModel>;
212
203
  /**
213
204
  * Runtime identity metadata attached to the `session.started` event.
@@ -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.49.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.51.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 +1 @@
1
- import{expectFunction,expectObjectRecord,expectOnlyKnownKeys,expectString}from"#internal/authored-module.js";import{isDynamicSentinel}from"#shared/dynamic-tool-definition.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";import{isDisabledToolSentinel}from"#public/definitions/tool.js";function normalizeToolDefinition(t,n){if(isDynamicSentinel(t))return{kind:`dynamic-tool`,eventNames:Object.keys(t.events)};if(isDisabledToolSentinel(t))return{kind:`disabled`};let r=expectObjectRecord(t,n);expectOnlyKnownKeys(r,[`description`,`execute`,`inputSchema`,`needsApproval`,`onCompact`,`retentionPolicy`,`toModelOutput`],n);let i=r.inputSchema===void 0?null:normalizeJsonSchemaDefinition(r.inputSchema),a={description:expectString(r.description,n),execute:expectFunction(r.execute,n),inputSchema:i};if(r.onCompact!==void 0&&expectFunction(r.onCompact,n),r.needsApproval!==void 0&&expectFunction(r.needsApproval,n),r.retentionPolicy!==void 0){let e=r.retentionPolicy;if(e!==`auto`&&e!==`keep`&&typeof e!=`function`)throw Error(`${n} Expected \`retentionPolicy\` to be "auto", "keep", or a function.`)}return r.toModelOutput!==void 0&&expectFunction(r.toModelOutput,n),{kind:`tool`,definition:a}}export{normalizeToolDefinition};
1
+ import{expectFunction,expectObjectRecord,expectOnlyKnownKeys,expectString}from"#internal/authored-module.js";import{isDynamicSentinel}from"#shared/dynamic-tool-definition.js";import{normalizeJsonSchemaDefinition}from"#internal/json-schema.js";import{isDisabledToolSentinel}from"#public/definitions/tool.js";function normalizeToolDefinition(e,t){if(isDynamicSentinel(e))return{kind:`dynamic-tool`,eventNames:Object.keys(e.events)};if(isDisabledToolSentinel(e))return{kind:`disabled`};let n=expectObjectRecord(e,t);expectOnlyKnownKeys(n,[`description`,`execute`,`inputSchema`,`needsApproval`,`onCompact`,`toModelOutput`],t);let r=n.inputSchema===void 0?null:normalizeJsonSchemaDefinition(n.inputSchema),i={description:expectString(n.description,t),execute:expectFunction(n.execute,t),inputSchema:r};return n.onCompact!==void 0&&expectFunction(n.onCompact,t),n.needsApproval!==void 0&&expectFunction(n.needsApproval,t),n.toModelOutput!==void 0&&expectFunction(n.toModelOutput,t),{kind:`tool`,definition:i}}export{normalizeToolDefinition};
@@ -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.49.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
1
+ import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.51.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({
@@ -1,6 +1,51 @@
1
+ import { type UserContent } from "ai";
2
+ import type { SessionAuthContext } from "#channel/types.js";
1
3
  import { type AuthFn } from "#public/channels/auth.js";
2
4
  import { type UploadPolicyInput } from "#public/channels/upload-policy.js";
3
- import { type Channel } from "#public/definitions/defineChannel.js";
5
+ import { type Channel, type ChannelEvents, type ChannelSessionOps } from "#public/definitions/defineChannel.js";
6
+ /**
7
+ * Event-handler channel context exposed by `ashChannel({ events })`.
8
+ *
9
+ * The default Ash HTTP channel has no platform-specific state, so handlers
10
+ * receive session continuation operations plus the separate `SessionContext`
11
+ * third argument supplied by {@link ChannelEvents}.
12
+ */
13
+ export type AshEventContext = ChannelSessionOps;
14
+ /**
15
+ * Runtime stream-event handlers supported by `ashChannel({ events })`.
16
+ */
17
+ export type AshChannelEvents = ChannelEvents<AshEventContext>;
18
+ /** Low-level Ash HTTP handle exposed to `ashChannel({ onMessage })`. */
19
+ export interface AshHandle {
20
+ /**
21
+ * Route-auth result for the inbound HTTP request. `onMessage`
22
+ * chooses runtime session auth by returning `{ auth }`.
23
+ */
24
+ readonly caller: SessionAuthContext | null;
25
+ readonly request: Request;
26
+ /** Existing runtime session id for continuation requests. */
27
+ readonly sessionId?: string;
28
+ }
29
+ /** Pre-dispatch context passed to `ashChannel({ onMessage })`. */
30
+ export interface AshMessageContext {
31
+ readonly ash: AshHandle;
32
+ }
33
+ /**
34
+ * Result of `ashChannel({ onMessage })`. Return an object to dispatch
35
+ * the inbound message, optionally appending `context` strings as user
36
+ * messages before the message. Return `null` to accept the request
37
+ * without dispatching a turn.
38
+ */
39
+ export type AshMessageResult = {
40
+ readonly auth: SessionAuthContext | null;
41
+ readonly context?: readonly string[];
42
+ } | null;
43
+ /**
44
+ * Synchronous or asynchronous `onMessage` result.
45
+ */
46
+ export type AshMessageResultOrPromise = AshMessageResult | Promise<AshMessageResult>;
47
+ /** Default runtime auth projection for the Ash HTTP channel. */
48
+ export declare function defaultAshAuth(ctx: AshMessageContext): SessionAuthContext | null;
4
49
  export interface AshChannelInput {
5
50
  /**
6
51
  * Route auth policy. Accepts a single {@link AuthFn} or an ordered
@@ -12,6 +57,19 @@ export interface AshChannelInput {
12
57
  */
13
58
  readonly auth: AuthFn<Request> | readonly AuthFn<Request>[];
14
59
  readonly uploadPolicy?: UploadPolicyInput;
60
+ /**
61
+ * Pre-dispatch hook for inbound Ash HTTP messages. Runs after route
62
+ * auth and body parsing, and before the runtime dispatch.
63
+ */
64
+ readonly onMessage?: (ctx: AshMessageContext, message: string | UserContent) => AshMessageResultOrPromise;
65
+ /**
66
+ * Runtime stream-event handlers for the default Ash HTTP channel.
67
+ *
68
+ * Handlers receive the event data, {@link AshEventContext}, and
69
+ * `SessionContext` so they can observe events with the same session
70
+ * context shape as custom channel implementations.
71
+ */
72
+ readonly events?: AshChannelEvents;
15
73
  }
16
74
  /**
17
75
  * Concrete return type of {@link ashChannel}. Named so consumers can
@@ -1,2 +1,2 @@
1
- import{ASH_MESSAGE_STREAM_CONTENT_TYPE,ASH_MESSAGE_STREAM_FORMAT,ASH_MESSAGE_STREAM_VERSION,ASH_SESSION_ID_HEADER,ASH_STREAM_FORMAT_HEADER,ASH_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.js";function ashChannel(o){let s=mergeUploadPolicy(o.uploadPolicy);return defineChannel({routes:[POST(`/ash/v1/session`,async(e,{send:t})=>{let n=await routeAuth(e,o.auth);if(n instanceof Response)return n;let i=n,a;try{a=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof a!=`object`||!a)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let c=parseCreateBody(a);if(c instanceof Response)return c;let l=checkUploadPolicy(c,s);if(l!==null)return l;let u=`ash:${crypto.randomUUID()}`,d=await t(createSendPayload(c),{auth:i,callback:c.callback,continuationToken:u,mode:c.mode});return Response.json({continuationToken:d.continuationToken,ok:!0,sessionId:d.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:d.id},status:202})}),POST(`/ash/v1/session/:sessionId`,async(e,{send:t,getSession:n,params:i})=>{let a=await routeAuth(e,o.auth);if(a instanceof Response)return a;let c=a,l=i.sessionId;if(!l)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{n(l)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let u;try{u=await e.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof u!=`object`||!u)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let d=parseContinueBody(u);if(d instanceof Response)return d;let f=checkUploadPolicy(d,s);if(f!==null)return f;let p=await t({inputResponses:d.inputResponses,message:d.message,context:d.context,outputSchema:d.outputSchema},{auth:c,continuationToken:d.continuationToken});return Response.json({ok:!0,sessionId:p.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:p.id},status:200})}),GET(`/ash/v1/session/:sessionId/stream`,async(s,{getSession:c,params:l})=>{let u=await routeAuth(s,o.auth);if(u instanceof Response)return u;let d=l.sessionId;if(!d)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let f=parseStartIndex(s);if(f instanceof Response)return f;try{let o=serializeAsNdjson(await c(d).getEventStream({startIndex:f}));return new Response(o,{headers:{"cache-control":`no-store`,"content-type":ASH_MESSAGE_STREAM_CONTENT_TYPE,[ASH_SESSION_ID_HEADER]:d,[ASH_STREAM_FORMAT_HEADER]:ASH_MESSAGE_STREAM_FORMAT,[ASH_STREAM_VERSION_HEADER]:ASH_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})]})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}function createSendPayload(e){if(e.context===void 0&&e.outputSchema===void 0)return e.message;let t={message:e.message};return e.context!==void 0&&(t.context=e.context),e.outputSchema!==void 0&&(t.outputSchema=e.outputSchema),t}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
2
- ${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number.parseInt(t,10);return!Number.isSafeInteger(n)||n<0?Response.json({error:`Expected startIndex to be a non-negative integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{ashChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{ASH_MESSAGE_STREAM_CONTENT_TYPE,ASH_MESSAGE_STREAM_FORMAT,ASH_MESSAGE_STREAM_VERSION,ASH_SESSION_ID_HEADER,ASH_STREAM_FORMAT_HEADER,ASH_STREAM_VERSION_HEADER}from"#protocol/message.js";import{isInputResponse}from"#runtime/input/types.js";import{parseJsonObject}from"#shared/json.js";import"ai";import{parseSessionCallback}from"#channel/session-callback.js";import{routeAuth}from"#public/channels/auth.js";import{collectUploadPolicyViolations,formatUploadPolicyViolation,mergeUploadPolicy}from"#public/channels/upload-policy.js";import{GET,POST,defineChannel}from"#public/definitions/defineChannel.js";const log=createLogger(`ash.channel`);function defaultAshAuth(e){return e.ash.caller}function ashChannel(e){let t=mergeUploadPolicy(e.uploadPolicy);return defineChannel({routes:[POST(`/ash/v1/session`,async(n,{send:r})=>{let i=await routeAuth(n,e.auth);if(i instanceof Response)return i;let o=i,s;try{s=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof s!=`object`||!s)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let c=parseCreateBody(s);if(c instanceof Response)return c;let l=checkUploadPolicy(c,t);if(l!==null)return l;let u=await resolveOnMessage({auth:o,config:e,message:c.message,request:n});if(u instanceof Response)return u;if(!u.dispatch)return droppedMessageResponse();let d=`ash:${crypto.randomUUID()}`,f=await r(createSendPayload(c,mergeContext(c.context,u.context)),{auth:u.auth,callback:c.callback,continuationToken:d,mode:c.mode});return Response.json({continuationToken:f.continuationToken,ok:!0,sessionId:f.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:f.id},status:202})}),POST(`/ash/v1/session/:sessionId`,async(n,{send:r,getSession:i,params:o})=>{let s=await routeAuth(n,e.auth);if(s instanceof Response)return s;let c=s,l=o.sessionId;if(!l)return Response.json({error:`Missing session id.`,ok:!1},{status:400});try{i(l)}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}let u;try{u=await n.json()}catch{return Response.json({error:`Invalid JSON body.`,ok:!1},{status:400})}if(typeof u!=`object`||!u)return Response.json({error:`Expected a JSON object.`,ok:!1},{status:400});let d=parseContinueBody(u);if(d instanceof Response)return d;let f=checkUploadPolicy(d,t);if(f!==null)return f;let p=d.context,m=c;if(d.message!==void 0){let t=await resolveOnMessage({auth:c,config:e,message:d.message,request:n,sessionId:l});if(t instanceof Response)return t;if(!t.dispatch)return droppedMessageResponse();p=mergeContext(d.context,t.context),m=t.auth}let h=await r({inputResponses:d.inputResponses,message:d.message,context:p,outputSchema:d.outputSchema},{auth:m,continuationToken:d.continuationToken});return Response.json({ok:!0,sessionId:h.id},{headers:{"cache-control":`no-store`,[ASH_SESSION_ID_HEADER]:h.id},status:200})}),GET(`/ash/v1/session/:sessionId/stream`,async(t,{getSession:c,params:l})=>{let u=await routeAuth(t,e.auth);if(u instanceof Response)return u;let d=l.sessionId;if(!d)return Response.json({error:`Missing session id.`,ok:!1},{status:400});let f=parseStartIndex(t);if(f instanceof Response)return f;try{let e=serializeAsNdjson(await c(d).getEventStream({startIndex:f}));return new Response(e,{headers:{"cache-control":`no-store`,"content-type":ASH_MESSAGE_STREAM_CONTENT_TYPE,[ASH_SESSION_ID_HEADER]:d,[ASH_STREAM_FORMAT_HEADER]:ASH_MESSAGE_STREAM_FORMAT,[ASH_STREAM_VERSION_HEADER]:ASH_MESSAGE_STREAM_VERSION}})}catch{return Response.json({error:`Session not found.`,ok:!1},{status:404})}})],events:e.events})}async function resolveOnMessage(e){let n=e.config.onMessage??defaultOnMessage,r;try{r=await n({ash:e.sessionId===void 0?{caller:e.auth,request:e.request}:{caller:e.auth,request:e.request,sessionId:e.sessionId}},e.message)}catch(n){let r=logError(log,`onMessage handler failed`,n,{sessionId:e.sessionId});return Response.json({error:`onMessage handler failed.`,errorId:r,ok:!1},{status:500})}return r==null?{dispatch:!1}:r.context===void 0?{auth:r.auth,dispatch:!0}:{auth:r.auth,context:r.context,dispatch:!0}}function defaultOnMessage(e){return{auth:defaultAshAuth(e)}}function droppedMessageResponse(){return new Response(null,{headers:{"cache-control":`no-store`},status:204})}function parseCreateBody(e){let t=parseMessageField(e.message);if(t instanceof Response)return t;let n=parseClientContextField(e.clientContext);if(n instanceof Response)return n;let r=parseCallbackField(e.callback);if(r instanceof Response)return r;let i=parseModeField(e.mode);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:t===void 0?Response.json({error:`Missing or empty 'message' field.`,ok:!1},{status:400}):{callback:r,message:t,mode:i,context:n,outputSchema:a}}function parseContinueBody(e){let t=typeof e.continuationToken==`string`&&e.continuationToken.length>0?e.continuationToken:void 0;if(t===void 0)return Response.json({error:`Missing or empty 'continuationToken' field.`,ok:!1},{status:400});let n=parseMessageField(e.message);if(n instanceof Response)return n;let r=parseInputResponses(e.inputResponses);if(r instanceof Response)return r;let i=parseClientContextField(e.clientContext);if(i instanceof Response)return i;let a=parseOutputSchemaField(e.outputSchema);return a instanceof Response?a:n===void 0&&r===void 0?Response.json({error:`Expected a non-empty 'message', a non-empty 'inputResponses' array, or both.`,ok:!1},{status:400}):{message:n,continuationToken:t,inputResponses:r,context:i,outputSchema:a}}function createSendPayload(e,t=e.context){if(t===void 0&&e.outputSchema===void 0)return e.message;let n={message:e.message};return t!==void 0&&(n.context=t),e.outputSchema!==void 0&&(n.outputSchema=e.outputSchema),n}function parseOutputSchemaField(e){if(e!==void 0)try{return parseJsonObject(e)}catch{return Response.json({error:`Expected 'outputSchema' to be a JSON-serializable object.`,ok:!1},{status:400})}}function parseCallbackField(e){if(e===void 0)return;let t=parseSessionCallback(e);return t.ok?t.callback:Response.json({error:t.message,ok:!1},{status:400})}function parseModeField(e){if(e!==void 0)return e===`conversation`||e===`task`?e:Response.json({error:`Expected 'mode' to be either 'conversation' or 'task'.`,ok:!1},{status:400})}function parseMessageField(e){if(e===void 0)return;if(typeof e==`string`)return e.length>0?e:void 0;if(!Array.isArray(e))return Response.json({error:`Expected 'message' to be a string or an array of text/file parts.`,ok:!1},{status:400});if(e.length===0)return;let t=[];for(let n of e){let e=parseMessagePart(n);if(e instanceof Response)return e;t.push(e)}return t}function parseMessagePart(e){if(typeof e!=`object`||!e)return Response.json({error:`Expected each message part to be an object.`,ok:!1},{status:400});let t=e;if(t.type===`text`)return typeof t.text!=`string`||t.text.length===0?Response.json({error:`Text parts require a non-empty 'text' string.`,ok:!1},{status:400}):{type:`text`,text:t.text};if(t.type===`file`){if(typeof t.mediaType!=`string`||t.mediaType.length===0)return Response.json({error:`File parts require a non-empty 'mediaType' string.`,ok:!1},{status:400});if(typeof t.data!=`string`)return Response.json({error:`File parts require a 'data' string (base64, data URL, or URL).`,ok:!1},{status:400});let e={type:`file`,mediaType:t.mediaType,data:t.data};return typeof t.filename==`string`&&t.filename.length>0&&(e.filename=t.filename),e}return Response.json({error:`Unsupported message part type "${String(t.type)}". Use 'text' or 'file'.`,ok:!1},{status:400})}function checkUploadPolicy(e,t){if(!e.message)return null;let n=collectUploadPolicyViolations(e.message,t);if(n.length===0)return null;let[r]=n;if(!r)return null;let i=r.kind===`too-large`?413:415;return Response.json({error:formatUploadPolicyViolation(r),ok:!1,violations:n.map(e=>e.kind===`too-large`?{byteLength:e.byteLength,filename:e.filename,kind:e.kind,limit:e.limit,mediaType:e.mediaType}:{allowedMediaTypes:e.allowedMediaTypes,filename:e.filename,kind:e.kind,mediaType:e.mediaType})},{status:i})}function parseInputResponses(e){if(e===void 0)return;if(!Array.isArray(e)||e.length===0)return Response.json({error:`Expected 'inputResponses' to be a non-empty array.`,ok:!1},{status:400});let t=e.filter(isInputResponse);return t.length===e.length?t:Response.json({error:`Expected every 'inputResponses' entry to match the HITL response schema.`,ok:!1},{status:400})}function mergeContext(e,t){return e===void 0?t:t===void 0?e:[...e,...t]}function parseClientContextField(e){if(e!==void 0){if(typeof e==`string`)return e.length>0?[toClientContextMessage(e)]:void 0;if(Array.isArray(e))return e.length===0?void 0:e.every(e=>typeof e==`string`&&e.length>0)?e.map(e=>toClientContextMessage(e)):Response.json({error:`Expected 'clientContext' array entries to be non-empty strings.`,ok:!1},{status:400});if(typeof e!=`object`||!e)return Response.json({error:`Expected 'clientContext' to be a string, string array, or JSON object.`,ok:!1},{status:400});try{let t=parseJsonObject(e);return[toClientContextMessage(JSON.stringify(t))]}catch{return Response.json({error:`Expected 'clientContext' to be a JSON-serializable object.`,ok:!1},{status:400})}}}function toClientContextMessage(e){return`Client context:
2
+ ${e}`}function parseStartIndex(e){let t=new URL(e.url).searchParams.get(`startIndex`);if(t===null)return;let n=Number.parseInt(t,10);return!Number.isSafeInteger(n)||n<0?Response.json({error:`Expected startIndex to be a non-negative integer.`,ok:!1},{status:400}):n}function serializeAsNdjson(e){let t=new TextEncoder;return e.pipeThrough(new TransformStream({transform(e,n){n.enqueue(t.encode(`${JSON.stringify(e)}\n`))}}))}export{ashChannel,defaultAshAuth};
@@ -40,36 +40,6 @@ export interface NeedsApprovalContext<TInput = Record<string, unknown>> {
40
40
  readonly toolName: string;
41
41
  }
42
42
  export type { ToolModelOutput } from "#shared/tool-definition.js";
43
- /**
44
- * Result returned from a {@link ToolRetentionPolicy} function.
45
- *
46
- * Always a named object — the field name carries the intent so the
47
- * pruner and readers of the policy can't confuse it with a raw output
48
- * replacement. Room exists to grow the object (token hints, placeholder
49
- * overrides) without breaking existing policies.
50
- */
51
- export interface RetentionSummary {
52
- /**
53
- * Compact text that replaces the original tool-result output in
54
- * conversation history. Should be short and information-dense; the
55
- * model reads it as prose, not as structured data.
56
- */
57
- readonly summary: string;
58
- }
59
- /**
60
- * Between-step retention policy for a tool's results.
61
- *
62
- * - `"auto"` (default): the pruning system replaces old results with
63
- * a fixed placeholder describing that the result was dropped.
64
- * - `"keep"`: the pruning system never replaces results from this tool.
65
- * - `(output) => RetentionSummary`: the pruning system calls this
66
- * function with the original tool output and substitutes the
67
- * returned `summary` text in place of the full result.
68
- *
69
- * The function runs synchronously during reactive pruning and must be
70
- * pure — no external I/O.
71
- */
72
- export type ToolRetentionPolicy<TOutput = unknown> = "auto" | "keep" | ((output: TOutput) => RetentionSummary);
73
43
  /**
74
44
  * Authored tool context. Passed as the last argument to
75
45
  * {@link ToolDefinition.execute}.
@@ -97,14 +67,6 @@ export type ToolDefinition<TInput = unknown, TOutput = unknown> = PublicToolDefi
97
67
  * - {@link once} — require approval only the first time per session
98
68
  */
99
69
  needsApproval?: (ctx: NeedsApprovalContext<ApprovalContextInput<TInput>>) => boolean;
100
- /**
101
- * Optional between-step retention policy for this tool's results.
102
- *
103
- * The function form receives the awaited return value of
104
- * {@link execute} — strongly typed as `TOutput`. See
105
- * {@link ToolRetentionPolicy}.
106
- */
107
- retentionPolicy?: ToolRetentionPolicy<TOutput>;
108
70
  /**
109
71
  * Optional projection that controls what the model sees as the tool
110
72
  * result. Receives the full `TOutput` from {@link execute} and returns
@@ -141,7 +103,6 @@ export declare function defineTool<TSchema extends StandardJSONSchemaV1<unknown,
141
103
  inputSchema: TSchema;
142
104
  execute(input: StandardJSONSchemaV1.InferOutput<TSchema>, ctx: ToolContext): Promise<TOutput> | TOutput;
143
105
  needsApproval?: ToolDefinition<StandardJSONSchemaV1.InferOutput<TSchema>, unknown>["needsApproval"];
144
- retentionPolicy?: ToolDefinition<unknown, TOutput>["retentionPolicy"];
145
106
  toModelOutput?: ToolDefinition<unknown, TOutput>["toModelOutput"];
146
107
  onCompact?: ToolDefinition<unknown, unknown>["onCompact"];
147
108
  }): ToolDefinition<StandardJSONSchemaV1.InferOutput<TSchema>, TOutput>;
@@ -150,7 +111,6 @@ export declare function defineTool<TOutput>(definition: {
150
111
  inputSchema: JsonObject;
151
112
  execute(input: Record<string, unknown>, ctx: ToolContext): Promise<TOutput> | TOutput;
152
113
  needsApproval?: ToolDefinition<Record<string, unknown>, unknown>["needsApproval"];
153
- retentionPolicy?: ToolDefinition<unknown, TOutput>["retentionPolicy"];
154
114
  toModelOutput?: ToolDefinition<unknown, TOutput>["toModelOutput"];
155
115
  onCompact?: ToolDefinition<unknown, unknown>["onCompact"];
156
116
  }): ToolDefinition<Record<string, unknown>, TOutput>;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Tool authoring helpers for `agent/tools/*.ts` files.
3
3
  */
4
- export { type CompactionInput, type CompactionHookResult, type DisabledToolSentinel, defineDynamic, defineTool, disableTool, isDisabledToolSentinel, type NeedsApprovalContext, type RetentionSummary, type ToolDefinition, type ToolContext, type ToolModelOutput, type ToolRetentionPolicy, } from "#public/definitions/tool.js";
4
+ export { type CompactionInput, type CompactionHookResult, type DisabledToolSentinel, defineDynamic, defineTool, disableTool, isDisabledToolSentinel, type NeedsApprovalContext, type ToolDefinition, type ToolContext, type ToolModelOutput, } from "#public/definitions/tool.js";
5
5
  export type { DynamicToolEntry, DynamicEvents, DynamicToolEvents, DynamicResolveContext, DynamicSentinel, DynamicToolSet, DynamicToolResult, } from "#shared/dynamic-tool-definition.js";
6
6
  export { type SessionContext } from "#public/definitions/callback-context.js";
7
7
  export { toolResultFrom, type MatchedConnectionResult, type MatchedToolResult, type ToolResultFromFn, } from "#public/tool-result-narrowing.js";
@@ -1 +1 @@
1
- import{SandboxKey}from"#context/keys.js";import{loadContext}from"#context/container.js";import{loadSkillFromSandbox}from"#runtime/skills/sandbox-access.js";async function executeLoadSkillTool(t){let n=loadContext().get(SandboxKey);if(n===void 0)throw Error(`The load_skill tool requires sandbox access on the runtime context. Ensure the step is running inside a managed runtime context with sandbox support.`);let{skill:r}=t;return await loadSkillFromSandbox(n,r)}const SKILL_TOOL_DEFINITION={description:[`Load the full instructions for one available skill by name or id.`,`Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill.`,`Loading adds the skill instructions to the current turn.`,`Choose the "skill" value from the Available skills block.`].join(` `),execute:e=>executeLoadSkillTool(e),inputSchema:{additionalProperties:!1,properties:{skill:{description:`Available skill name or id.`,type:`string`}},required:[`skill`],type:`object`},logicalPath:`ash:framework/load-skill`,name:`load_skill`,retentionPolicy:`keep`,sourceId:`ash:load-skill-tool`,sourceKind:`module`};export{SKILL_TOOL_DEFINITION};
1
+ import{SandboxKey}from"#context/keys.js";import{loadContext}from"#context/container.js";import{loadSkillFromSandbox}from"#runtime/skills/sandbox-access.js";async function executeLoadSkillTool(t){let n=loadContext().get(SandboxKey);if(n===void 0)throw Error(`The load_skill tool requires sandbox access on the runtime context. Ensure the step is running inside a managed runtime context with sandbox support.`);let{skill:r}=t;return await loadSkillFromSandbox(n,r)}const SKILL_TOOL_DEFINITION={description:[`Load the full instructions for one available skill by name or id.`,`Use this tool when the request clearly matches a listed skill description or when the user explicitly asks for that skill.`,`Loading adds the skill instructions to the current turn.`,`Choose the "skill" value from the Available skills block.`].join(` `),execute:e=>executeLoadSkillTool(e),inputSchema:{additionalProperties:!1,properties:{skill:{description:`Available skill name or id.`,type:`string`}},required:[`skill`],type:`object`},logicalPath:`ash:framework/load-skill`,name:`load_skill`,sourceId:`ash:load-skill-tool`,sourceKind:`module`};export{SKILL_TOOL_DEFINITION};
@@ -5,7 +5,7 @@ import type { ResolvedToolDefinition } from "#runtime/types.js";
5
5
  * Resolves one compiled authored tool into a runtime-owned definition
6
6
  * with live callbacks reattached from the authored module.
7
7
  *
8
- * Optional hooks (`onCompact`, `needsApproval`, `retentionPolicy`,
8
+ * Optional hooks (`onCompact`, `needsApproval`,
9
9
  * plus an optional Standard Schema `inputSchema`) are extracted when
10
10
  * declared and validated to have the expected shape; any type mismatch
11
11
  * raises a {@link ResolveAgentError} so typos surface at resolve time
@@ -1 +1 @@
1
- import{expectFunction,expectObjectRecord}from"#internal/authored-module.js";import{toErrorMessage}from"#shared/errors.js";import{ResolveAgentError,loadResolvedModuleExport}from"#runtime/resolve-helpers.js";import{registerDefinitionSource,stampDefinitionKey}from"#public/tool-result-narrowing.js";async function resolveToolDefinition(a,o,s){try{let n=expectObjectRecord(await loadResolvedModuleExport({definition:a,kindLabel:`tool`,moduleMap:o,nodeId:s}),describe(a,`to return an object`)),r={kind:`tool`,logicalPath:a.logicalPath,name:a.name},c=`tool-source:${a.sourceId}`;stampDefinitionKey(n,c),registerDefinitionSource(c,r),registerDefinitionSource(`tool:${n.description}`,r);let l=expectFunction(n.execute,describe(a,`to provide an execute function`));return{description:a.description,execute:l,exportName:a.exportName,inputSchema:a.inputSchema,logicalPath:a.logicalPath,name:a.name,sourceId:a.sourceId,sourceKind:`module`,...extractOptionalHooks(n,a)}}catch(e){throw e instanceof ResolveAgentError?e:new ResolveAgentError(`Failed to attach the tool execute function from "${a.logicalPath}": ${toErrorMessage(e)}`,{logicalPath:a.logicalPath,sourceId:a.sourceId})}}function extractOptionalHooks(t,n){let r={};t.onCompact!==void 0&&(r.onCompact=expectFunction(t.onCompact,describe(n,`to provide an onCompact function`))),t.needsApproval!==void 0&&(r.needsApproval=expectFunction(t.needsApproval,describe(n,`to provide a needsApproval function`)));let i=extractRetentionPolicy(t,n);return i!==void 0&&(r.retentionPolicy=i),t.toModelOutput!==void 0&&(r.toModelOutput=expectFunction(t.toModelOutput,describe(n,`to provide a toModelOutput function`))),t.inputSchema!==void 0&&isFlexibleSchema(t.inputSchema)&&(r.inputStandardSchema=t.inputSchema),r}function extractRetentionPolicy(e,t){let n=e.retentionPolicy;if(n!==void 0){if(n===`auto`||n===`keep`||typeof n==`function`)return n;throw new ResolveAgentError(describe(t,`to set retentionPolicy to "auto", "keep", or a function`),{logicalPath:t.logicalPath,sourceId:t.sourceId})}}function describe(e,t){return`Expected the tool export "${e.exportName??`default`}" from "${e.logicalPath}" ${t}.`}function isFlexibleSchema(e){return typeof e==`object`&&!!e&&`~standard`in e&&typeof e[`~standard`]==`object`}export{resolveToolDefinition};
1
+ import{expectFunction,expectObjectRecord}from"#internal/authored-module.js";import{toErrorMessage}from"#shared/errors.js";import{ResolveAgentError,loadResolvedModuleExport}from"#runtime/resolve-helpers.js";import{registerDefinitionSource,stampDefinitionKey}from"#public/tool-result-narrowing.js";async function resolveToolDefinition(i,a,o){try{let n=expectObjectRecord(await loadResolvedModuleExport({definition:i,kindLabel:`tool`,moduleMap:a,nodeId:o}),describe(i,`to return an object`)),r={kind:`tool`,logicalPath:i.logicalPath,name:i.name},s=`tool-source:${i.sourceId}`;stampDefinitionKey(n,s),registerDefinitionSource(s,r),registerDefinitionSource(`tool:${n.description}`,r);let c=expectFunction(n.execute,describe(i,`to provide an execute function`));return{description:i.description,execute:c,exportName:i.exportName,inputSchema:i.inputSchema,logicalPath:i.logicalPath,name:i.name,sourceId:i.sourceId,sourceKind:`module`,...extractOptionalHooks(n,i)}}catch(e){throw e instanceof ResolveAgentError?e:new ResolveAgentError(`Failed to attach the tool execute function from "${i.logicalPath}": ${toErrorMessage(e)}`,{logicalPath:i.logicalPath,sourceId:i.sourceId})}}function extractOptionalHooks(t,n){let r={};return t.onCompact!==void 0&&(r.onCompact=expectFunction(t.onCompact,describe(n,`to provide an onCompact function`))),t.needsApproval!==void 0&&(r.needsApproval=expectFunction(t.needsApproval,describe(n,`to provide a needsApproval function`))),t.toModelOutput!==void 0&&(r.toModelOutput=expectFunction(t.toModelOutput,describe(n,`to provide a toModelOutput function`))),t.inputSchema!==void 0&&isFlexibleSchema(t.inputSchema)&&(r.inputStandardSchema=t.inputSchema),r}function describe(e,t){return`Expected the tool export "${e.exportName??`default`}" from "${e.logicalPath}" ${t}.`}function isFlexibleSchema(e){return typeof e==`object`&&!!e&&`~standard`in e&&typeof e[`~standard`]==`object`}export{resolveToolDefinition};
@@ -7,7 +7,7 @@ import type { ChannelMethod, RouteContext } from "#public/definitions/channel.js
7
7
  import type { RouteHandler } from "#channel/routes.js";
8
8
  import type { OutboundAuthFn } from "#public/agents/auth.js";
9
9
  import type { StreamEventHook } from "#public/definitions/hook.js";
10
- import type { CompactionInput, CompactionHookResult, NeedsApprovalContext, ToolModelOutput, ToolRetentionPolicy } from "#public/definitions/tool.js";
10
+ import type { CompactionInput, CompactionHookResult, NeedsApprovalContext, ToolModelOutput } from "#public/definitions/tool.js";
11
11
  import type { AuthorizationDefinition, HeadersDefinition, ToolFilterDefinition } from "#runtime/connections/types.js";
12
12
  import type { CompiledWorkspaceResourceRoot } from "#compiler/manifest.js";
13
13
  import type { WorkspaceRuntimeSpec } from "#runtime/workspace/types.js";
@@ -125,14 +125,6 @@ export type ResolvedToolDefinition = Readonly<Optional<InternalToolDefinitionWit
125
125
  * than blanket.
126
126
  */
127
127
  readonly approvalKey?: (toolInput: Readonly<Record<string, unknown>>) => string;
128
- /**
129
- * Optional retention policy for old results of this tool. Controls the
130
- * behavior of the reactive pruning system when this tool's results age
131
- * outside the protection window. See {@link ToolRetentionPolicy}.
132
- *
133
- * When omitted, the default `"auto"` policy applies.
134
- */
135
- readonly retentionPolicy?: ToolRetentionPolicy;
136
128
  /**
137
129
  * Optional compaction hook. The execution layer invokes this for every
138
130
  * resolved tool that declares it after the harness compacts message
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "experimental-ash",
3
- "version": "0.49.0",
3
+ "version": "0.51.0",
4
4
  "bin": {
5
5
  "ash": "./bin/ash.js",
6
6
  "experimental-ash": "./bin/ash.js"