experimental-ash 0.52.0 → 0.54.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 +27 -0
- package/dist/docs/public/advanced/runs-and-streaming.md +14 -1
- package/dist/docs/public/advanced/session-context.md +4 -2
- package/dist/docs/public/channels/index.md +62 -0
- package/dist/docs/public/subagents.mdx +1 -1
- package/dist/docs/public/tools.mdx +30 -0
- package/dist/src/channel/types.d.ts +6 -0
- package/dist/src/context/dynamic-resolve-context.js +1 -1
- package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
- package/dist/src/execution/ash-workflow-attributes.d.ts +18 -0
- package/dist/src/execution/ash-workflow-attributes.js +1 -1
- package/dist/src/execution/create-session-step.js +1 -1
- package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
- package/dist/src/execution/runtime-context.js +1 -1
- package/dist/src/execution/subagent-tool.d.ts +2 -1
- package/dist/src/execution/subagent-tool.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -0
- package/dist/src/public/channels/index.js +1 -1
- package/dist/src/public/instrumentation/index.d.ts +9 -6
- package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
- package/dist/src/shared/dynamic-tool-definition.d.ts +11 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.54.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 8534548: Expose channel `metadata(state)` projection on `DynamicResolveContext.channel.metadata` so dynamic tool resolvers can read channel-produced context. Forward the parent's channel instrumentation projection to subagents via `RunInput` so child agents see the originating channel's kind and metadata.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- d48f2a6: Enforce connection-level `approval` on connection tools. A connection's
|
|
12
|
+
`approval` callback was previously stored but never consulted, so a
|
|
13
|
+
connection tool would run without the user approval its definition
|
|
14
|
+
required. Discovered connection tools now carry that callback through to
|
|
15
|
+
the harness so `needsApproval` is evaluated before the call executes.
|
|
16
|
+
|
|
17
|
+
`DynamicToolEntry` gains an optional `needsApproval` field and the dynamic
|
|
18
|
+
tool materializer propagates it into the harness tool definition. This is
|
|
19
|
+
honored for step-scoped dynamic tools (whose live `execute` closures reach
|
|
20
|
+
the harness), which is the scope connection tools use; session/turn-scoped
|
|
21
|
+
dynamic tools replay from durable metadata and cannot carry a function
|
|
22
|
+
across replay.
|
|
23
|
+
|
|
24
|
+
## 0.53.0
|
|
25
|
+
|
|
26
|
+
### Minor Changes
|
|
27
|
+
|
|
28
|
+
- 3ea7d16: Expose the parent subagent tool call id on `ctx.session.parent.callId` for local subagent sessions. Subagent workflow runs now use `$ash.parent` for the parent session id and add `$ash.parent_call` / `$ash.parent_turn` for lineage queries.
|
|
29
|
+
|
|
3
30
|
## 0.52.0
|
|
4
31
|
|
|
5
32
|
### Minor Changes
|
|
@@ -103,7 +103,20 @@ Ash uses three nested concepts:
|
|
|
103
103
|
That is why tools, the sandbox, and subagents feel synchronous from authored code even though the
|
|
104
104
|
overall session remains durable.
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
### Event dispatch order
|
|
107
|
+
|
|
108
|
+
When the framework emits a stream event (e.g. `turn.started`, `session.started`), four
|
|
109
|
+
things happen in sequence:
|
|
110
|
+
|
|
111
|
+
1. **Channel adapter handler** — the channel's event handler runs and can mutate adapter state.
|
|
112
|
+
2. **Metadata projection** — the framework re-evaluates the channel's `metadata(state)` and stores the result.
|
|
113
|
+
3. **Hook event handlers** — authored hooks subscribed to the event fire.
|
|
114
|
+
4. **Dynamic resolvers** — dynamic tool, skill, and instruction resolvers subscribed to the event fire. `ctx.channel.metadata` contains the freshly projected metadata from step 2.
|
|
115
|
+
|
|
116
|
+
This ordering is structural. By the time a resolver or hook reads channel metadata, the
|
|
117
|
+
channel has already updated its state and the projection is current.
|
|
118
|
+
|
|
119
|
+
### Important behavior
|
|
107
120
|
|
|
108
121
|
- follow-up messages reuse the same session through the continuation token
|
|
109
122
|
- schedules without a channel run in task mode and do not wait for follow-up messages; channel-targeted schedules inherit the channel's mode
|
|
@@ -35,7 +35,8 @@ export default defineTool({
|
|
|
35
35
|
turnSequence: ctx.session.turn.sequence,
|
|
36
36
|
currentCaller: ctx.session.auth.current?.principalId,
|
|
37
37
|
initiator: ctx.session.auth.initiator?.principalId,
|
|
38
|
-
parentSessionId: ctx.session.parent?.
|
|
38
|
+
parentSessionId: ctx.session.parent?.sessionId,
|
|
39
|
+
parentCallId: ctx.session.parent?.callId,
|
|
39
40
|
};
|
|
40
41
|
},
|
|
41
42
|
});
|
|
@@ -56,7 +57,8 @@ Important behavior:
|
|
|
56
57
|
- `auth.initiator` is the caller that started the durable session
|
|
57
58
|
- unprotected agents expose both as `null`
|
|
58
59
|
- top-level schedule sessions expose the framework app principal (`principalId: "ash:app"`, `principalType: "runtime"`)
|
|
59
|
-
- `parent` is present for child subagent sessions
|
|
60
|
+
- `parent` is present for child subagent sessions and includes the parent `callId`, `sessionId`,
|
|
61
|
+
`rootSessionId`, and `turn`
|
|
60
62
|
|
|
61
63
|
## `ctx.getSandbox()`
|
|
62
64
|
|
|
@@ -461,6 +461,68 @@ user messages in session history. See
|
|
|
461
461
|
thread, or `initialMessage` to anchor a new one. With neither, the first agent post anchors
|
|
462
462
|
the thread automatically.
|
|
463
463
|
|
|
464
|
+
## Channel Metadata
|
|
465
|
+
|
|
466
|
+
Channels can project a subset of their adapter state as **metadata** available to
|
|
467
|
+
instrumentation resolvers, dynamic tool resolvers, and dynamic skill/instruction resolvers.
|
|
468
|
+
Define a `metadata(state)` function on the channel config:
|
|
469
|
+
|
|
470
|
+
```ts
|
|
471
|
+
import { defineChannel, POST } from "experimental-ash/channels";
|
|
472
|
+
|
|
473
|
+
export default defineChannel({
|
|
474
|
+
state: {
|
|
475
|
+
topic: null as string | null,
|
|
476
|
+
contextMessages: [] as string[],
|
|
477
|
+
internalCounter: 0,
|
|
478
|
+
},
|
|
479
|
+
|
|
480
|
+
metadata(state) {
|
|
481
|
+
// Curated projection — internalCounter is withheld.
|
|
482
|
+
return {
|
|
483
|
+
topic: state.topic,
|
|
484
|
+
contextMessages: state.contextMessages,
|
|
485
|
+
};
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
routes: [
|
|
489
|
+
POST("/start", async (req, { send }) => {
|
|
490
|
+
const body = await req.json();
|
|
491
|
+
await send(body.message, {
|
|
492
|
+
auth: null,
|
|
493
|
+
continuationToken: body.token,
|
|
494
|
+
state: { topic: body.topic, contextMessages: body.context, internalCounter: 0 },
|
|
495
|
+
});
|
|
496
|
+
return new Response("ok");
|
|
497
|
+
}),
|
|
498
|
+
],
|
|
499
|
+
events: {
|
|
500
|
+
"turn.started"(_event, ctx) {
|
|
501
|
+
ctx.state.internalCounter += 1;
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
});
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
The projection is re-evaluated whenever adapter state changes (after channel event handlers
|
|
508
|
+
run). Dynamic tool resolvers read it via `ctx.channel.metadata` and narrow it with
|
|
509
|
+
`isChannel`. See [Dynamic Tools — Channel Metadata](../tools.mdx#channel-metadata) for the
|
|
510
|
+
full consumption pattern.
|
|
511
|
+
|
|
512
|
+
### Subagent propagation
|
|
513
|
+
|
|
514
|
+
When a parent agent dispatches a subagent, the framework forwards the parent's channel
|
|
515
|
+
metadata projection to the child. The subagent's dynamic tool resolvers see the originating
|
|
516
|
+
channel's `kind` and `metadata` — not `kind: "subagent"` with empty metadata. This lets
|
|
517
|
+
subagents conditionally resolve tools based on which channel is driving the conversation.
|
|
518
|
+
|
|
519
|
+
### Shared with instrumentation
|
|
520
|
+
|
|
521
|
+
The same `metadata(state)` projector serves both dynamic resolvers and the instrumentation
|
|
522
|
+
`metadata["step.started"]` resolver. Each consumer picks what it needs: instrumentation
|
|
523
|
+
flattens to `Record<string, string>` for OTel span attributes; tool resolvers use the full
|
|
524
|
+
structured object.
|
|
525
|
+
|
|
464
526
|
## Continuation Tokens
|
|
465
527
|
|
|
466
528
|
Each call to `send(message, { auth, continuationToken, state? })` from a channel route
|
|
@@ -118,7 +118,7 @@ Subagent execution gets:
|
|
|
118
118
|
- its own tools
|
|
119
119
|
- its own sandbox (independent of the parent's sandbox)
|
|
120
120
|
- its own `skills/` set (independent of the parent's skills)
|
|
121
|
-
- immediate parent lineage in `ctx.session.parent`
|
|
121
|
+
- immediate parent lineage in `ctx.session.parent`, including the parent subagent tool `callId`
|
|
122
122
|
|
|
123
123
|
Skills and sandboxes do not cross the parent/child boundary. The subagent only sees skills
|
|
124
124
|
authored under `agent/subagents/<id>/skills/`; skills under the root `agent/skills/` are not
|
|
@@ -427,6 +427,36 @@ Dynamic tool names are derived from the file path and the definition function:
|
|
|
427
427
|
|
|
428
428
|
`defineDynamic` with a single return always produces one tool named after the file slug — identical to static tools. `defineDynamic` with a map return always uses `slug__key`, even when the resolver returns a single entry. This keeps names stable: adding a second entry to a `defineDynamic` file does not rename the first.
|
|
429
429
|
|
|
430
|
+
### Channel Metadata
|
|
431
|
+
|
|
432
|
+
Dynamic resolvers receive `ctx.channel.metadata` — the channel's `metadata(state)` projection. Use `isChannel` with the channel import to narrow metadata to its declared shape:
|
|
433
|
+
|
|
434
|
+
```ts
|
|
435
|
+
import { isChannel } from "experimental-ash/channels";
|
|
436
|
+
import supportChannel from "../channels/support.js";
|
|
437
|
+
|
|
438
|
+
export default defineDynamic({
|
|
439
|
+
events: {
|
|
440
|
+
"turn.started": (_event, ctx) => {
|
|
441
|
+
if (!isChannel(ctx.channel, supportChannel)) return null;
|
|
442
|
+
|
|
443
|
+
const { topic, contextMessages } = ctx.channel.metadata;
|
|
444
|
+
if (!topic) return null;
|
|
445
|
+
|
|
446
|
+
return defineTool({
|
|
447
|
+
description: `Answer questions about ${topic}.`,
|
|
448
|
+
inputSchema: z.object({ question: z.string() }),
|
|
449
|
+
async execute(input) {
|
|
450
|
+
return lookupAnswer(topic, input.question);
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
The metadata is available in subagents too — the framework forwards the parent's channel metadata projection so the same tool file works in both the root agent and its subagents. See [Channel Metadata](/channels#channel-metadata) for the full pattern including execution order.
|
|
459
|
+
|
|
430
460
|
### Events
|
|
431
461
|
|
|
432
462
|
The `events` object supports three event types:
|
|
@@ -7,6 +7,7 @@ import type { ChannelAdapter } from "#channel/adapter.js";
|
|
|
7
7
|
import type { JsonObject } from "#shared/json.js";
|
|
8
8
|
export type { ContextAccessor } from "#context/key.js";
|
|
9
9
|
export type { ChannelInstrumentationProjection } from "#channel/instrumentation.js";
|
|
10
|
+
import type { ChannelInstrumentationProjection } from "#channel/instrumentation.js";
|
|
10
11
|
/**
|
|
11
12
|
* Identifies one turn within a session.
|
|
12
13
|
*/
|
|
@@ -27,6 +28,10 @@ export interface SessionTurn {
|
|
|
27
28
|
* the same root.
|
|
28
29
|
*/
|
|
29
30
|
export interface SessionParent {
|
|
31
|
+
/**
|
|
32
|
+
* Parent runtime-action tool call id that created this child session.
|
|
33
|
+
*/
|
|
34
|
+
readonly callId: string;
|
|
30
35
|
readonly rootSessionId: string;
|
|
31
36
|
readonly sessionId: string;
|
|
32
37
|
readonly turn: SessionTurn;
|
|
@@ -180,6 +185,7 @@ export interface RunInput {
|
|
|
180
185
|
* adapter kind (`http`, `schedule`, `subagent`) directly.
|
|
181
186
|
*/
|
|
182
187
|
readonly channelName?: string;
|
|
188
|
+
readonly channelMetadata?: ChannelInstrumentationProjection;
|
|
183
189
|
/**
|
|
184
190
|
* Authenticated caller principal for this session. `null` means the
|
|
185
191
|
* request was accepted with no credentials.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ContinuationTokenKey,InitiatorAuthKey,SessionIdKey}from"#context/keys.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";function buildResolveContext(e,t){let n=e.get(SessionIdKey)??``,r=e.get(AuthKey)??null,i=e.get(InitiatorAuthKey)??null,a=e.get(ChannelKey),o=e.get(ContinuationTokenKey);return{session:{id:n,auth:{current:r,initiator:i}},channel:{kind:a===void 0?void 0:getAdapterKind(a),continuationToken:o},messages:t}}export{buildResolveContext};
|
|
1
|
+
import{getAdapterKind}from"#channel/adapter.js";import{AuthKey,ChannelInstrumentationKey,ContinuationTokenKey,InitiatorAuthKey,SessionIdKey}from"#context/keys.js";import{ChannelKey}from"#runtime/sessions/runtime-context-keys.js";function buildResolveContext(e,t){let n=e.get(SessionIdKey)??``,r=e.get(AuthKey)??null,i=e.get(InitiatorAuthKey)??null,a=e.get(ChannelKey),o=e.get(ContinuationTokenKey),s=e.get(ChannelInstrumentationKey);return{session:{id:n,auth:{current:r,initiator:i}},channel:{kind:a===void 0?void 0:getAdapterKind(a),continuationToken:o,metadata:s?.metadata},messages:t}}export{buildResolveContext};
|
|
@@ -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 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
|
+
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,needsApproval:t.needsApproval,...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};
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
* - `$ash.type` — `"session" | "turn" | "subagent"`
|
|
18
18
|
* - `$ash.parent` — sessionId of the **immediate** parent
|
|
19
19
|
* - `$ash.root` — sessionId of the **root** session in the chain
|
|
20
|
+
* - `$ash.parent_call` — parent runtime-action tool call id (subagent rows only)
|
|
21
|
+
* - `$ash.parent_turn` — parent turn id that dispatched the subagent (subagent rows only)
|
|
20
22
|
* - `$ash.subagent` — active compiled graph node id (subagent rows only)
|
|
21
23
|
* - `$ash.trigger` — channel adapter kind (session/subagent rows)
|
|
22
24
|
* - `$ash.title` — truncated session title from the first user message
|
|
@@ -33,12 +35,26 @@ import type { AshAttributeValue } from "#runtime/attributes/emit.js";
|
|
|
33
35
|
export interface SessionIdentitySummary {
|
|
34
36
|
readonly nodeId: string;
|
|
35
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Parent session lineage decoded from the serialized run context.
|
|
40
|
+
*/
|
|
41
|
+
export interface SessionParentLineage {
|
|
42
|
+
readonly callId?: string;
|
|
43
|
+
readonly rootSessionId?: string;
|
|
44
|
+
readonly sessionId?: string;
|
|
45
|
+
readonly turnId?: string;
|
|
46
|
+
}
|
|
36
47
|
/**
|
|
37
48
|
* Reads the active channel kind from a deserialized context map.
|
|
38
49
|
* Returns `undefined` when the channel slot is missing or malformed —
|
|
39
50
|
* tag emission silently drops undefined values.
|
|
40
51
|
*/
|
|
41
52
|
export declare function readChannelKind(serializedContext: Record<string, unknown>): string | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Reads parent session lineage from a deserialized context map. Returns
|
|
55
|
+
* an empty object for top-level runs or malformed delegated contexts.
|
|
56
|
+
*/
|
|
57
|
+
export declare function readParentLineage(serializedContext: Record<string, unknown>): SessionParentLineage;
|
|
42
58
|
/**
|
|
43
59
|
* Reads the immediate parent session id from a deserialized context map.
|
|
44
60
|
* Returns `undefined` when the run is a top-level session.
|
|
@@ -98,7 +114,9 @@ export declare function buildSessionAttributes(input: {
|
|
|
98
114
|
*/
|
|
99
115
|
export declare function buildSubagentRootAttributes(input: {
|
|
100
116
|
readonly identity: SessionIdentitySummary;
|
|
117
|
+
readonly parentCallId?: string;
|
|
101
118
|
readonly parentSessionId: string;
|
|
119
|
+
readonly parentTurnId?: string;
|
|
102
120
|
readonly rootSessionId: string;
|
|
103
121
|
readonly serializedContext: Record<string, unknown>;
|
|
104
122
|
}): Record<string, AshAttributeValue>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function readChannelKind(
|
|
1
|
+
import{isNonEmptyString}from"#shared/guards.js";function readChannelKind(t){let n=t[`ash.channel`]?.kind;return isNonEmptyString(n)?n:void 0}function readParentLineage(t){let n=t[`ash.parentSession`],r=n?.callId,i=n?.rootSessionId,a=n?.sessionId,o=n?.turn?.id;return{callId:isNonEmptyString(r)?r:void 0,rootSessionId:isNonEmptyString(i)?i:void 0,sessionId:isNonEmptyString(a)?a:void 0,turnId:isNonEmptyString(o)?o:void 0}}function readParentSessionId(e){return readParentLineage(e).sessionId}function readRootSessionId(e){return readParentLineage(e).rootSessionId}const ASH_SESSION_TITLE_MAX_CHARS=80;function deriveSessionTitle(e){let t=collectMessageText(e);if(t===void 0||t.length===0)return;let n=t.replace(/\s+/gu,` `).trim();if(n.length===0)return;let r=Array.from(n);return r.length<=80?n:`${r.slice(0,79).join(``)}…`}function collectMessageText(e){if(typeof e==`string`)return e;if(!Array.isArray(e))return;let t=[];for(let n of e)n&&typeof n==`object`&&n.type===`text`&&typeof n.text==`string`&&t.push(n.text);return t.length>0?t.join(` `):void 0}function buildSessionAttributes(e){return{"$ash.type":`session`,"$ash.trigger":readChannelKind(e.serializedContext),"$ash.title":deriveSessionTitle(e.inputMessage)}}function buildSubagentRootAttributes(e){return{"$ash.type":`subagent`,"$ash.parent":e.parentSessionId,"$ash.parent_call":e.parentCallId,"$ash.parent_turn":e.parentTurnId,"$ash.root":e.rootSessionId,"$ash.subagent":e.identity.nodeId,"$ash.trigger":readChannelKind(e.serializedContext)}}function buildTurnAttributes(e){return{"$ash.type":`turn`,"$ash.parent":e.parentSessionId,"$ash.root":e.rootSessionId}}export{ASH_SESSION_TITLE_MAX_CHARS,buildSessionAttributes,buildSubagentRootAttributes,buildTurnAttributes,deriveSessionTitle,readChannelKind,readParentLineage,readParentSessionId,readRootSessionId};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ROOT_RUNTIME_AGENT_NODE_ID}from"#runtime/graph.js";import{getCompiledRuntimeAgentBundle}from"#runtime/sessions/compiled-agent-cache.js";import{createDurableSessionState}from"#execution/durable-session-store.js";import{createSession}from"#execution/session.js";import{buildSessionAttributes,buildSubagentRootAttributes,
|
|
1
|
+
import{ROOT_RUNTIME_AGENT_NODE_ID}from"#runtime/graph.js";import{getCompiledRuntimeAgentBundle}from"#runtime/sessions/compiled-agent-cache.js";import{createDurableSessionState}from"#execution/durable-session-store.js";import{createSession}from"#execution/session.js";import{buildSessionAttributes,buildSubagentRootAttributes,readParentLineage}from"#execution/ash-workflow-attributes.js";import{setAshAttributes}from"#runtime/attributes/emit.js";async function createSessionStep(e){"use step";let t=await getCompiledRuntimeAgentBundle({compiledArtifactsSource:e.compiledArtifactsSource,nodeId:e.nodeId}),n=createDurableSessionState({session:createSession({compactionOverrides:{thresholdPercent:t.resolvedAgent.config.compaction?.thresholdPercent},continuationToken:e.continuationToken,outputSchema:e.outputSchema,rootSessionId:e.rootSessionId,sessionId:e.sessionId,turnAgent:t.turnAgent})}),r={nodeId:t.nodeId??ROOT_RUNTIME_AGENT_NODE_ID},i=readParentLineage(e.serializedContext);return i.sessionId===void 0?await setAshAttributes(buildSessionAttributes({inputMessage:e.inputMessage,serializedContext:e.serializedContext})):await setAshAttributes(buildSubagentRootAttributes({identity:r,parentCallId:i.callId,parentSessionId:i.sessionId,parentTurnId:i.turnId,rootSessionId:e.rootSessionId??i.sessionId,serializedContext:e.serializedContext})),{state:n}}export{createSessionStep};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{AuthKey,CapabilitiesKey,InitiatorAuthKey}from"#context/keys.js";import{createSubagentCalledEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{createDurableSessionState,readDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{deserializeContext}from"#context/serialize.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{getPendingRuntimeActionBatch,recordPendingSubagentChildToken}from"#harness/runtime-actions.js";import{resolveRemoteAgentForAction,startRemoteAgentSession}from"#execution/remote-agent-dispatch.js";import{buildSubagentRunInput}from"#execution/subagent-tool.js";import{createWorkflowRuntime,workflowEntryReference}from"#execution/workflow-runtime.js";const log=createLogger(`execution.dispatch-runtime-actions`);async function dispatchRuntimeActionsStep(e){"use step";let s=await readDurableSession(e.sessionState),
|
|
1
|
+
import{createLogger,logError}from"#internal/logging.js";import{callAdapterEventHandler}from"#channel/adapter.js";import{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,InitiatorAuthKey}from"#context/keys.js";import{createSubagentCalledEvent,encodeMessageStreamEvent,timestampHandleMessageStreamEvent}from"#protocol/message.js";import{toErrorMessage}from"#shared/errors.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{createDurableSessionState,readDurableSession}from"#execution/durable-session-store.js";import{hydrateDurableSession}from"#execution/session.js";import{deserializeContext}from"#context/serialize.js";import{buildAdapterContext}from"#channel/adapter-context.js";import{getPendingRuntimeActionBatch,recordPendingSubagentChildToken}from"#harness/runtime-actions.js";import{resolveRemoteAgentForAction,startRemoteAgentSession}from"#execution/remote-agent-dispatch.js";import{buildSubagentRunInput}from"#execution/subagent-tool.js";import{createWorkflowRuntime,workflowEntryReference}from"#execution/workflow-runtime.js";const log=createLogger(`execution.dispatch-runtime-actions`);async function dispatchRuntimeActionsStep(e){"use step";let s=await readDurableSession(e.sessionState),l=getPendingRuntimeActionBatch(s.state);if(l===void 0||l.actions.length===0)return{results:[],sessionState:e.sessionState};let u=await deserializeContext(e.serializedContext),d=u.require(BundleKey),f=hydrateDurableSession({compactionOverrides:{thresholdPercent:d.resolvedAgent.config.compaction?.thresholdPercent},durable:s,turnAgent:d.turnAgent}),p=u.require(ChannelKey),m=u.get(AuthKey)??null,h=u.get(CapabilitiesKey),g=u.get(ChannelInstrumentationKey),_=u.get(InitiatorAuthKey)??null,v=e.parentWritable.getWriter(),y=buildAdapterContext(p,u),b=f,x=[];try{for(let r of l.actions){let i,a,o,s;switch(r.kind){case`subagent-call`:{let e=createWorkflowRuntime({compiledArtifactsSource:d.compiledArtifactsSource,nodeId:r.nodeId}),{childContinuationToken:t,runInput:n}=buildSubagentRunInput({action:r,auth:m,batchEvent:l.event,capabilities:h,channelMetadata:g,initiatorAuth:_,session:f}),o=await e.run(n);b=recordPendingSubagentChildToken({callId:r.callId,childContinuationToken:t,session:b}),i=o.sessionId,a=r.name,s=r.subagentName;break}case`remote-agent-call`:{let n;try{n=resolveRemoteAgentForAction({nodeId:r.nodeId,remoteAgentName:r.remoteAgentName,registry:d.subagentRegistry.subagentsByNodeId}),i=await startRemoteAgentSession({action:r,callbackBaseUrl:e.callbackBaseUrl,remote:n,session:f})}catch(e){logError(log,`remote agent start failed`,e,{remoteAgentName:r.remoteAgentName,nodeId:r.nodeId,callId:r.callId}),x.push(createRemoteAgentStartFailureResult({action:r,error:e}));continue}a=r.name,o={url:n.url},s=r.remoteAgentName;break}default:throw Error(`Unsupported runtime action kind "${r.kind}" in workflow runtime.`)}let c=await callAdapterEventHandler(p,createSubagentCalledEvent({callId:r.callId,childSessionId:i,name:a,remote:o,sequence:l.event.sequence,sessionId:f.sessionId,toolName:s,turnId:l.event.turnId,workflowId:workflowEntryReference.workflowId}),y);await v.write(encodeMessageStreamEvent(timestampHandleMessageStreamEvent(c)))}}finally{v.releaseLock()}return{results:x,sessionState:b===f?e.sessionState:createDurableSessionState({session:b})}}function createRemoteAgentStartFailureResult(e){return{callId:e.action.callId,isError:!0,kind:`subagent-result`,output:{code:`REMOTE_AGENT_START_FAILED`,message:toErrorMessage(e.error)},subagentName:e.action.remoteAgentName}}export{dispatchRuntimeActionsStep};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{AuthKey,CapabilitiesKey,ContinuationTokenKey,InitiatorAuthKey,ModeKey,ParentSessionKey,SessionCallbackKey}from"#context/keys.js";import{ContextContainer}from"#context/container.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{setChannelContext}from"#execution/channel-context.js";function buildRunContext(
|
|
1
|
+
import{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,ContinuationTokenKey,InitiatorAuthKey,ModeKey,ParentSessionKey,SessionCallbackKey}from"#context/keys.js";import{ContextContainer}from"#context/container.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{setChannelContext}from"#execution/channel-context.js";function buildRunContext(t){let{bundle:n,run:r}=t,i=new ContextContainer,a=r.auth;if(i.set(BundleKey,n),setChannelContext(i,r.adapter,{channelName:r.channelName}),r.channelMetadata!==void 0){let e=i.get(ChannelInstrumentationKey);i.set(ChannelInstrumentationKey,{kind:e?.kind??r.channelMetadata.kind,metadata:r.channelMetadata.metadata})}return i.set(ContinuationTokenKey,r.continuationToken??``),i.set(ModeKey,r.mode),i.set(AuthKey,a),i.set(InitiatorAuthKey,r.initiatorAuth??a),r.capabilities!==void 0&&i.set(CapabilitiesKey,r.capabilities),r.callback!==void 0&&i.set(SessionCallbackKey,r.callback),r.parent!==void 0&&i.set(ParentSessionKey,r.parent),i}export{buildRunContext};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RunInput, SessionAuthContext, SessionCapabilities } from "#channel/types.js";
|
|
1
|
+
import type { ChannelInstrumentationProjection, RunInput, SessionAuthContext, SessionCapabilities } from "#channel/types.js";
|
|
2
2
|
import type { HarnessSession } from "#harness/types.js";
|
|
3
3
|
import type { RuntimeSubagentCallActionRequest } from "#runtime/actions/types.js";
|
|
4
4
|
/**
|
|
@@ -32,6 +32,7 @@ export declare function buildSubagentRunInput(input: {
|
|
|
32
32
|
* parent capabilities produce an undefined child capability set.
|
|
33
33
|
*/
|
|
34
34
|
readonly capabilities?: SessionCapabilities;
|
|
35
|
+
readonly channelMetadata?: ChannelInstrumentationProjection;
|
|
35
36
|
readonly initiatorAuth: SessionAuthContext | null;
|
|
36
37
|
readonly session: HarnessSession;
|
|
37
38
|
}): SubagentRunInputBuild;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mintSubagentContinuationToken}from"#execution/session.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";import{formatSubagentInvocation}from"#execution/subagent-invocation.js";function buildSubagentRunInput(n){let{action:r,auth:i,batchEvent:a,capabilities:o,
|
|
1
|
+
import{mintSubagentContinuationToken}from"#execution/session.js";import{SUBAGENT_ADAPTER_KIND}from"#execution/subagent-adapter.js";import{formatSubagentInvocation}from"#execution/subagent-invocation.js";function buildSubagentRunInput(n){let{action:r,auth:i,batchEvent:a,capabilities:o,channelMetadata:s,initiatorAuth:c,session:l}=n,u=mintSubagentContinuationToken(`${l.sessionId}:${r.callId}`),d=l.rootSessionId??l.sessionId;return{childContinuationToken:u,runInput:{adapter:{kind:SUBAGENT_ADAPTER_KIND,state:{callId:r.callId,parentContinuationToken:l.continuationToken,parentSessionId:l.sessionId,subagentName:r.subagentName}},auth:i,capabilities:o,channelMetadata:s,continuationToken:u,initiatorAuth:c,input:{message:formatSubagentCallInputMessage(r)},mode:`task`,parent:{callId:r.callId,rootSessionId:d,sessionId:l.sessionId,turn:{id:a.turnId,sequence:a.sequence}}}}}function formatSubagentCallInputMessage(e){let{message:t}=e.input;return formatSubagentInvocation({description:e.description,message:t,name:e.subagentName}).message}export{buildSubagentRunInput};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.
|
|
1
|
+
import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.54.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.
|
|
1
|
+
import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.54.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 +1,2 @@
|
|
|
1
1
|
export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelDefinition, type ChannelSessionOps, type ChannelEvents, type InferChannelMetadata, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
|
|
2
|
+
export { isChannel } from "#public/instrumentation/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DELETE,GET,PATCH,POST,PUT,defineChannel}from"#public/definitions/defineChannel.js";export{DELETE,GET,PATCH,POST,PUT,defineChannel};
|
|
1
|
+
import{DELETE,GET,PATCH,POST,PUT,defineChannel}from"#public/definitions/defineChannel.js";import{isChannel}from"#public/instrumentation/index.js";export{DELETE,GET,PATCH,POST,PUT,defineChannel,isChannel};
|
|
@@ -177,12 +177,15 @@ export interface InstrumentationDefinition {
|
|
|
177
177
|
*/
|
|
178
178
|
export declare function defineInstrumentation<T extends InstrumentationDefinition>(definition: ExactDefinition<T, InstrumentationDefinition>): T;
|
|
179
179
|
/**
|
|
180
|
-
* Narrows
|
|
181
|
-
*
|
|
180
|
+
* Narrows a channel by comparing it to an app-owned channel value imported
|
|
181
|
+
* from `agent/channels/*`.
|
|
182
182
|
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
183
|
+
* Works with both instrumentation resolver inputs (`input.channel`) and
|
|
184
|
+
* dynamic resolver inputs (`ctx.channel`). The comparison uses the
|
|
185
|
+
* compiler's path-derived `channel:<slug>` identity. Metadata narrows to
|
|
186
|
+
* the channel's declared `ChannelMetadataMap` entry on success.
|
|
186
187
|
*/
|
|
187
|
-
export declare function isChannel<TChannel extends Channel<any, any, any>>(channel: InstrumentationChannel
|
|
188
|
+
export declare function isChannel<TChannel extends Channel<any, any, any>>(channel: InstrumentationChannel | {
|
|
189
|
+
readonly kind?: string;
|
|
190
|
+
}, target: TChannel): channel is InstrumentationChannelForChannel<TChannel>;
|
|
188
191
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{loadContext}from"#context/container.js";import{ContextKey}from"#context/key.js";import{ConnectionRegistryKey}from"#context/providers/connection.js";import{getAuthorizationResult,getHookUrl,requestAuthorization}from"#harness/authorization.js";import{supportsInteractiveAuthorization}from"#runtime/connections/types.js";import{isConnectionAuthorizationFailedError,isConnectionAuthorizationRequiredError}from"#public/connections/errors.js";import{writeCachedToken}from"#runtime/connections/authorization-tokens.js";import{principalKey,resolveConnectionPrincipal}from"#runtime/connections/principal.js";const logger=createLogger(`framework.connection-search-dynamic`),ConnectionSearchResultsKey=new ContextKey(`ash.connectionSearchResults`);function qualifiedConnectionToolName(e,t){return`${e}__${t}`}function tokenize(e){return e.toLowerCase().split(/[\s_\-./]+/).filter(e=>e.length>1)}function scoreMatch(e,t){let n=tokenize(t.name),r=tokenize(t.description),i=0;for(let t of e){for(let e of n)(e.includes(t)||t.includes(e))&&(i+=3);for(let e of r)(e.includes(t)||t.includes(e))&&(i+=1)}return i}function resolveInteractiveAuth(e,t){let n=e.getConnections().find(e=>e.connectionName===t);if(n?.authorization&&supportsInteractiveAuthorization(n.authorization))return n.authorization}async function completePendingAuthorizations(e){let n=loadContext();for(let t of e.getConnections()){let r=getAuthorizationResult(t.connectionName);if(!r)continue;let a=resolveInteractiveAuth(e,t.connectionName);if(!a)continue;let o=resolveConnectionPrincipal(t.connectionName,a),s=await a.completeAuthorization({callbackUrl:r.hookUrl,connection:{url:t.url??``},principal:o,request:r.callback,state:r.state});writeCachedToken(n,t.connectionName,principalKey(o),s)}}async function executeConnectionSearch(e){let n=loadContext(),i=n.get(ConnectionRegistryKey);if(i===void 0)return[];await completePendingAuthorizations(i);let s=e.limit??10,l=tokenize(e.keywords),u=[],p=[],m=e.connection!==void 0&&e.connection!==``?i.getConnections().filter(t=>t.connectionName===e.connection):i.getConnections(),h=[];for(let e of m){let t;try{t=await i.getClient(e.connectionName).getToolMetadata()}catch(t){if(isConnectionAuthorizationRequiredError(t)){let t=resolveInteractiveAuth(i,e.connectionName);if(t){let n=getHookUrl(e.connectionName);if(n){let r=resolveConnectionPrincipal(e.connectionName,t);try{let{challenge:i,state:a}=await t.startAuthorization({callbackUrl:n,connection:{url:e.url??``},principal:r});h.push({name:e.connectionName,challenge:i,hookUrl:n,state:a})}catch(t){logger.warn(`startAuthorization failed`,{connection:e.connectionName,error:t instanceof Error?t:Error(String(t))})}}}p.push({connection:e.connectionName,description:e.description,needsAuthorization:!0});continue}if(isConnectionAuthorizationFailedError(t)){logger.warn(`connection authorization failed`,{connection:e.connectionName,reason:t.reason,retryable:t.retryable,error:t}),p.push({connection:e.connectionName,description:e.description,error:`Authorization failed for ${e.connectionName}: ${t.message}`});continue}let n=t instanceof Error?t.message:`unknown error`;logger.warn(`failed to load connection tools`,{connection:e.connectionName,error:t instanceof Error?t:Error(n)}),p.push({connection:e.connectionName,description:e.description,error:`Failed to load tools for "${e.connectionName}": ${n}`});continue}for(let n of t){let t=scoreMatch(l,n);t>0&&u.push({item:{connection:e.connectionName,description:n.description,inputSchema:n.inputSchema,qualifiedName:`connection__${qualifiedConnectionToolName(e.connectionName,n.name)}`,tool:n.name},score:t})}}if(h.length>0)return requestAuthorization(h);u.sort((e,t)=>t.score-e.score);let g=u.slice(0,s).map(e=>e.item);if(g.length>0){let e=[...g,...p],t=n.get(ConnectionSearchResultsKey)??[],r=new Map(t.map(e=>[e.qualifiedName,e]));for(let e of g)e.qualifiedName&&r.set(e.qualifiedName,e);return n.set(ConnectionSearchResultsKey,[...r.values()]),e}return m.map(e=>p.find(t=>t.connection===e.connectionName)||{connection:e.connectionName,description:e.description})}function extractDiscoveredTools(e){let t=new Map;for(let n of e){if(n.role!==`tool`)continue;let e=n.content;for(let n of e){if(n.type!==`tool-result`||n.toolName!==`connection__search`)continue;let e=n.output;if(e==null)continue;let r=typeof e==`object`&&`type`in e&&`value`in e?e.value:e;if(Array.isArray(r))for(let e of r)e.tool&&e.qualifiedName&&t.set(e.qualifiedName,e)}}return[...t.values()]}function createConnectionSearchEvents(){return{"step.started":async(e,n)=>{let c=loadContext().get(ConnectionRegistryKey);if(!c||c.getConnections().length===0)return null;let d=c.getConnections().map(e=>e.connectionName),p=extractDiscoveredTools(n.messages),m=loadContext().get(ConnectionSearchResultsKey)??[],h=new Map;for(let e of m)e.qualifiedName&&h.set(e.qualifiedName,e);for(let e of p)e.qualifiedName&&h.set(e.qualifiedName,e);let g=[...h.values()],_={};_.search={description:`Search for tools across your connections. Discovered tools become directly callable by their qualified name (e.g. \`connection__linear__list_issues\`) in your next response. Available connections: ${d.join(`, `)}.`,inputSchema:{type:`object`,additionalProperties:!1,properties:{keywords:{description:`Search keywords and expanded aliases. Distill intent into keywords; avoid stop words like 'a', 'the', 'in'.`,type:`string`},connection:{description:`Optional: limit search to a specific connection name.`,type:`string`},limit:{description:`Max results to return. Default 10.`,type:`number`}},required:[`keywords`]},async execute(e){return executeConnectionSearch(e)}};for(let e of g){let n=e.connection,d=e.tool,f=c.getConnectionApproval(n);_[qualifiedConnectionToolName(n,d)]={description:e.description,inputSchema:e.inputSchema??{type:`object`},needsApproval:f,async execute(e){let c=loadContext().get(ConnectionRegistryKey),f=c.getConnections().find(e=>e.connectionName===n),p=f?.authorization&&supportsInteractiveAuthorization(f.authorization)?f.authorization:void 0;if(p){let e=getAuthorizationResult(n);if(e){let r=loadContext(),i=resolveConnectionPrincipal(n,p),a=await p.completeAuthorization({callbackUrl:e.hookUrl,connection:{url:f?.url??``},principal:i,request:e.callback,state:e.state});writeCachedToken(r,n,principalKey(i),a)}}try{let t=(await c.getClient(n).getTools())[d];if(!t?.execute)throw Error(`Connection tool "${qualifiedConnectionToolName(n,d)}" has no execute function.`);return t.execute(e,{})}catch(e){if(!isConnectionAuthorizationRequiredError(e)||!p)throw e;let t=getHookUrl(n);if(!t)throw e;let r=resolveConnectionPrincipal(n,p),{challenge:i,state:s}=await p.startAuthorization({callbackUrl:t,connection:{url:f?.url??``},principal:r});return requestAuthorization([{name:n,challenge:i,hookUrl:t,state:s}])}}}}return _}}}function createConnectionSearchResolver(){let e=createConnectionSearchEvents();return{slug:`connection`,eventNames:Object.keys(e),events:e,sourceId:`ash:connection-search-dynamic`,sourceKind:`module`,logicalPath:`ash:framework/connection-search-dynamic`}}export{createConnectionSearchEvents,createConnectionSearchResolver,extractDiscoveredTools};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ModelMessage } from "ai";
|
|
2
2
|
import type { PublicToolInputSchema, ToolModelOutput } from "#shared/tool-definition.js";
|
|
3
3
|
import type { SessionContext } from "#public/definitions/callback-context.js";
|
|
4
|
+
import type { NeedsApprovalContext } from "#public/definitions/tool.js";
|
|
4
5
|
import type { SessionAuth } from "#context/keys.js";
|
|
5
6
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
6
7
|
type ToolContext = SessionContext;
|
|
@@ -35,6 +36,7 @@ export interface DynamicResolveContext {
|
|
|
35
36
|
readonly channel: {
|
|
36
37
|
readonly kind?: string;
|
|
37
38
|
readonly continuationToken?: string;
|
|
39
|
+
readonly metadata?: Readonly<Record<string, unknown>>;
|
|
38
40
|
};
|
|
39
41
|
readonly messages: readonly ModelMessage[];
|
|
40
42
|
}
|
|
@@ -55,6 +57,15 @@ export interface DynamicToolEntry<TInput = Record<string, unknown>, TOutput = an
|
|
|
55
57
|
readonly inputSchema: PublicToolInputSchema<TInput>;
|
|
56
58
|
execute(input: TInput, ctx: ToolContext): TOutput | Promise<TOutput>;
|
|
57
59
|
readonly toModelOutput?: (output: TOutput) => ToolModelOutput | Promise<ToolModelOutput>;
|
|
60
|
+
/**
|
|
61
|
+
* Optional per-call approval gate. Mirrors the authored-tool
|
|
62
|
+
* `needsApproval` contract: return `true` to require user approval
|
|
63
|
+
* before the call executes. Only honored for step-scoped dynamic
|
|
64
|
+
* tools (whose live `execute` closures survive into the harness);
|
|
65
|
+
* session/turn-scoped tools replay from durable metadata and cannot
|
|
66
|
+
* carry a function across replay.
|
|
67
|
+
*/
|
|
68
|
+
readonly needsApproval?: (ctx: NeedsApprovalContext) => boolean;
|
|
58
69
|
}
|
|
59
70
|
/**
|
|
60
71
|
* A resolved tool set: keys are entry identifiers, values are tool
|