experimental-ash 0.47.0 → 0.48.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 +25 -0
- package/dist/docs/public/advanced/hooks.mdx +7 -7
- package/dist/docs/public/advanced/typescript-api.md +1 -1
- package/dist/docs/public/channels/index.md +2 -2
- package/dist/docs/public/channels/slack.mdx +12 -17
- package/dist/docs/public/frontend/use-ash-agent.md +13 -17
- package/dist/src/channel/adapter.js +1 -1
- package/dist/src/channel/routes.d.ts +5 -7
- package/dist/src/channel/send.js +1 -1
- package/dist/src/channel/types.d.ts +3 -3
- package/dist/src/context/build-dynamic-tools.d.ts +13 -0
- package/dist/src/context/build-dynamic-tools.js +1 -0
- package/dist/src/context/dynamic-instruction-lifecycle.d.ts +13 -13
- package/dist/src/context/dynamic-instruction-lifecycle.js +1 -1
- package/dist/src/context/dynamic-resolve-context.d.ts +12 -0
- package/dist/src/context/dynamic-resolve-context.js +1 -0
- package/dist/src/context/dynamic-skill-lifecycle.js +1 -1
- package/dist/src/context/dynamic-tool-lifecycle.d.ts +4 -10
- package/dist/src/context/dynamic-tool-lifecycle.js +1 -1
- package/dist/src/context/hook-lifecycle.d.ts +2 -2
- package/dist/src/context/hook-lifecycle.js +1 -1
- package/dist/src/context/keys.d.ts +30 -23
- package/dist/src/context/keys.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/harness/code-mode.js +1 -1
- package/dist/src/harness/compaction.js +1 -1
- package/dist/src/harness/messages.js +1 -1
- package/dist/src/harness/prompt-cache.d.ts +11 -1
- package/dist/src/harness/prompt-cache.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/harness/types.d.ts +4 -5
- 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/ash.js +2 -2
- package/dist/src/public/channels/discord/discordChannel.d.ts +1 -2
- package/dist/src/public/channels/discord/discordChannel.js +1 -1
- package/dist/src/public/channels/slack/slackChannel.d.ts +3 -7
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/teams/teamsChannel.d.ts +1 -2
- package/dist/src/public/channels/teams/teamsChannel.js +1 -1
- package/dist/src/public/channels/telegram/telegramChannel.d.ts +1 -2
- package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
- package/dist/src/public/definitions/instructions.d.ts +7 -12
- package/dist/src/public/definitions/skill.js +1 -1
- package/dist/src/shared/dynamic-tool-definition.d.ts +20 -0
- package/dist/src/shared/dynamic-tool-definition.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.48.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 28259a5: Align dynamic tools, instructions, and skills with consistent additive semantics and strict role boundaries.
|
|
8
|
+
|
|
9
|
+
**Breaking changes:**
|
|
10
|
+
|
|
11
|
+
- `InstructionsDefinition.modelContext` removed — instructions produce system messages only via `markdown`
|
|
12
|
+
- `DeliverPayload.modelContext` replaced with `context: string[]` — channel context is now user messages persisted in session history
|
|
13
|
+
- `StepInput.modelContext` replaced with `context: string[]`
|
|
14
|
+
- `SendPayload.modelContext` replaced with `context: string[]`
|
|
15
|
+
- Dynamic instructions restricted to `session.started` and `turn.started` events (no `step.started`)
|
|
16
|
+
- Dynamic skills restricted to `session.started` and `turn.started` events (no `step.started`)
|
|
17
|
+
- Compaction output changed from `role: "system"` to a user/assistant turn in conversation history
|
|
18
|
+
|
|
19
|
+
**New features:**
|
|
20
|
+
|
|
21
|
+
- `defineInstructions` contributions persist across workflow step boundaries via scoped durable keys
|
|
22
|
+
- `defineSkill` stamps `SKILL_BRAND` for consistent entry detection (replaces duck-typing)
|
|
23
|
+
- System cache breakpoint added (4th Anthropic cache slot) to preserve system prefix when tools change
|
|
24
|
+
- Null-returning tool resolvers correctly clear durable metadata
|
|
25
|
+
- Skill announcements use virtual context with durable backing (rebuild from manifest on step boundaries)
|
|
26
|
+
- Shared `buildResolveContext` extracted for all three dynamic lifecycle dispatchers
|
|
27
|
+
|
|
3
28
|
## 0.47.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
|
@@ -14,15 +14,15 @@ Use a hook when you want to run code **around** a turn (before the model
|
|
|
14
14
|
runs, around stream events) without writing a tool, a context provider,
|
|
15
15
|
or a channel adapter handler.
|
|
16
16
|
|
|
17
|
-
<CopyPrompt text="Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events. Use lifecycle hooks when the model needs
|
|
17
|
+
<CopyPrompt text="Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events. Use lifecycle hooks when the model needs side effects before the next model call (e.g. setting state); use events handlers for observe-only side effects after events are durably recorded. Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use defineState only when durable shared state is needed, wrap best-effort side effects so they do not fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not commit unless the user asks.">
|
|
18
18
|
Add an Ash hook to the user's project. Ash hooks live under agent/hooks, use defineHook from
|
|
19
19
|
experimental-ash/hooks, and can subscribe to lifecycle.session, lifecycle.turn, or stream events.
|
|
20
|
-
Use lifecycle hooks when the model needs
|
|
21
|
-
events handlers for observe-only side effects after events are durably recorded.
|
|
22
|
-
hooks and agent/lib, choose the smallest hook file and path-derived slug, use
|
|
23
|
-
when durable shared state is needed, wrap best-effort side effects so they do not
|
|
24
|
-
accidentally, verify the relevant lifecycle or stream event behavior, and do not
|
|
25
|
-
user asks.
|
|
20
|
+
Use lifecycle hooks when the model needs side effects before the next model call (e.g. setting
|
|
21
|
+
state); use events handlers for observe-only side effects after events are durably recorded.
|
|
22
|
+
Inspect existing hooks and agent/lib, choose the smallest hook file and path-derived slug, use
|
|
23
|
+
defineState only when durable shared state is needed, wrap best-effort side effects so they do not
|
|
24
|
+
fail sessions accidentally, verify the relevant lifecycle or stream event behavior, and do not
|
|
25
|
+
commit unless the user asks.
|
|
26
26
|
</CopyPrompt>
|
|
27
27
|
|
|
28
28
|
## The Main API
|
|
@@ -112,7 +112,7 @@ Channel and Slack types exported from `experimental-ash/channels/slack`:
|
|
|
112
112
|
`since: (message) => boolean` for a custom boundary.
|
|
113
113
|
- `SlackInteractionAction` - action type for `onInteraction`
|
|
114
114
|
- `SlackMentionResult` / `SlackInboundResult` - return type of `onAppMention` / `onDirectMessage`
|
|
115
|
-
(`{ auth,
|
|
115
|
+
(`{ auth, context? } | null`)
|
|
116
116
|
- `defaultSlackAuth(message, ctx)` - default Slack actor-to-session-auth projection
|
|
117
117
|
- `Card`, `Button`, `Actions`, `Section`, `Modal`, `Table`, etc. - card builders re-exported for
|
|
118
118
|
rendering Slack messages
|
|
@@ -412,8 +412,8 @@ await args.receive(slack, {
|
|
|
412
412
|
```
|
|
413
413
|
|
|
414
414
|
For inbound mentions in an existing Slack thread, `onAppMention` and
|
|
415
|
-
`onDirectMessage` may return `
|
|
416
|
-
|
|
415
|
+
`onDirectMessage` may return `context` to inject thread history as
|
|
416
|
+
user messages in session history. See
|
|
417
417
|
[Slack thread context](/docs/channels/slack#thread-context).
|
|
418
418
|
|
|
419
419
|
`threadTs` and `initialMessage` are mutually exclusive: pass `threadTs` to join an existing
|
|
@@ -188,8 +188,8 @@ export default slackChannel({
|
|
|
188
188
|
- **`onAppMention(ctx, message)`** -- decides whether to dispatch a turn for an inbound
|
|
189
189
|
`app_mention`, with what `auth` context, and runs any pre-dispatch side effects (typing
|
|
190
190
|
indicators, logging, feature-flag lookups). Return `{ auth }` to dispatch or `null` to silently
|
|
191
|
-
drop the mention. Return `{ auth,
|
|
192
|
-
|
|
191
|
+
drop the mention. Return `{ auth, context }` to append context strings as user messages in
|
|
192
|
+
session history before the delivery message. May be sync or async; the framework awaits
|
|
193
193
|
the result before dispatching. Thrown errors are caught, logged, and drop the mention -- wrap
|
|
194
194
|
best-effort side effects in `try/catch` if you want them to be non-fatal. The default
|
|
195
195
|
`onAppMention` derives a workspace-scoped auth from the Slack actor and posts a `"Thinking…"`
|
|
@@ -212,19 +212,18 @@ run inside the workflow context, not on the inbound webhook side.
|
|
|
212
212
|
|
|
213
213
|
When the bot is mentioned in an existing Slack thread, the triggering mention is delivered to the
|
|
214
214
|
agent by default, but prior thread replies are not injected automatically. Fetch thread history in
|
|
215
|
-
`onAppMention` or `onDirectMessage` and return `
|
|
216
|
-
background
|
|
215
|
+
`onAppMention` or `onDirectMessage` and return `context` when the agent should see that
|
|
216
|
+
background.
|
|
217
217
|
|
|
218
218
|
Use `since: "last-agent-reply"` for repeated tags in the same thread. It returns only messages
|
|
219
219
|
after the agent's last Slack reply and before the current mention, so the injected context stays
|
|
220
|
-
small
|
|
220
|
+
small.
|
|
221
221
|
|
|
222
222
|
```ts
|
|
223
223
|
import {
|
|
224
224
|
defaultSlackAuth,
|
|
225
225
|
loadThreadContextMessages,
|
|
226
226
|
slackChannel,
|
|
227
|
-
type ModelMessage,
|
|
228
227
|
} from "experimental-ash/channels/slack";
|
|
229
228
|
|
|
230
229
|
export default slackChannel({
|
|
@@ -242,23 +241,19 @@ export default slackChannel({
|
|
|
242
241
|
.map((entry) => `${entry.isMe ? "you" : (entry.user ?? "user")}: ${entry.markdown}`)
|
|
243
242
|
.join("\n");
|
|
244
243
|
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
"Recent Slack thread messages since your last reply, oldest first. " +
|
|
250
|
-
"Use them as background context for the current mention.\n\n" +
|
|
251
|
-
transcript,
|
|
252
|
-
},
|
|
244
|
+
const context = [
|
|
245
|
+
"Recent Slack thread messages since your last reply, oldest first. " +
|
|
246
|
+
"Use them as background context for the current mention.\n\n" +
|
|
247
|
+
transcript,
|
|
253
248
|
];
|
|
254
249
|
|
|
255
|
-
return { auth,
|
|
250
|
+
return { auth, context };
|
|
256
251
|
},
|
|
257
252
|
});
|
|
258
253
|
```
|
|
259
254
|
|
|
260
|
-
`
|
|
261
|
-
|
|
255
|
+
`context` strings are appended as user messages to `session.history` before the delivery message.
|
|
256
|
+
They persist across the session and are visible to the model on all subsequent turns.
|
|
262
257
|
|
|
263
258
|
### Direct Messages
|
|
264
259
|
|
|
@@ -193,26 +193,22 @@ const agent = useAshAgent({
|
|
|
193
193
|
});
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
`clientContext` is
|
|
197
|
-
It
|
|
196
|
+
`clientContext` is appended as user messages to session history before
|
|
197
|
+
the delivery message. It persists across the session and is visible
|
|
198
|
+
to the model on all subsequent turns (until compaction summarizes it).
|
|
198
199
|
|
|
199
|
-
Use
|
|
200
|
+
Use dynamic instructions for server-side system prompt enrichment:
|
|
200
201
|
|
|
201
202
|
```ts
|
|
202
|
-
// agent/
|
|
203
|
-
import {
|
|
204
|
-
|
|
205
|
-
export default
|
|
206
|
-
|
|
207
|
-
async session(
|
|
208
|
-
return {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
role: "system",
|
|
212
|
-
content: `Session ${ctx.session.sessionId} has server-side profile context.`,
|
|
213
|
-
},
|
|
214
|
-
],
|
|
215
|
-
};
|
|
203
|
+
// agent/instructions/profile.ts
|
|
204
|
+
import { defineDynamic, defineInstructions } from "experimental-ash/instructions";
|
|
205
|
+
|
|
206
|
+
export default defineDynamic({
|
|
207
|
+
events: {
|
|
208
|
+
async "session.started"(_event, ctx) {
|
|
209
|
+
return defineInstructions({
|
|
210
|
+
markdown: `Session ${ctx.session.id} has server-side profile context.`,
|
|
211
|
+
});
|
|
216
212
|
},
|
|
217
213
|
},
|
|
218
214
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";const log=createLogger(`channel.adapter`);function defaultDeliverResult(e){
|
|
1
|
+
import{createLogger}from"#internal/logging.js";const log=createLogger(`channel.adapter`);function defaultDeliverResult(e){if(e.message!==void 0)return{inputResponses:e.inputResponses,message:e.message,context:e.context,outputSchema:e.outputSchema};if(e.inputResponses!==void 0&&e.inputResponses.length>0)return{inputResponses:e.inputResponses,context:e.context,outputSchema:e.outputSchema};if(e.context!==void 0&&e.context.length>0)return{context:e.context,outputSchema:e.outputSchema};if(e.outputSchema!==void 0)return{outputSchema:e.outputSchema}}function getAdapterKind(e){return e.kind}async function callAdapterEventHandler(e,t,n){let r=e[t.type];if(r===void 0)return t;try{await r(`data`in t?t.data:void 0,n)}catch(n){log.error(`adapter event handler threw — event swallowed`,{adapterKind:getAdapterKind(e),eventType:t.type,error:n})}return t}export{callAdapterEventHandler,defaultDeliverResult,getAdapterKind};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { UserContent } from "ai";
|
|
2
2
|
import type { CrossChannelReceiveFn } from "#channel/cross-channel-receive.js";
|
|
3
3
|
import type { SessionAuthContext, SessionCallback } from "#channel/types.js";
|
|
4
4
|
import type { InputResponse } from "#runtime/input/types.js";
|
|
@@ -24,13 +24,11 @@ export interface SendPayload {
|
|
|
24
24
|
readonly message?: string | UserContent;
|
|
25
25
|
readonly inputResponses?: readonly InputResponse[];
|
|
26
26
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* for background context that should land before the user message;
|
|
31
|
-
* other roles land after the user message.
|
|
27
|
+
* Context strings contributed by the channel. Each entry is appended
|
|
28
|
+
* as a `role: "user"` message to `session.history` before the
|
|
29
|
+
* delivery message, persisted across the session.
|
|
32
30
|
*/
|
|
33
|
-
readonly
|
|
31
|
+
readonly context?: readonly string[];
|
|
34
32
|
/**
|
|
35
33
|
* Run-scoped JSON schema the turn's result must match. Orthogonal to
|
|
36
34
|
* {@link BaseSendOptions.mode}: a schema is enforced in either mode. Mode
|
package/dist/src/channel/send.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createSession}from"#channel/session.js";import{createLogger}from"#internal/logging.js";import{isRuntimeNoActiveSessionError}from"#execution/runtime-errors.js";import{serializeUrlFilePart}from"#internal/attachments/url-refs.js";const log=createLogger(`channel.send`);function createSendFn(t,r,i){return async(a,o)=>{let s=o.auth,c=o.callback,l=o.mode??`conversation`,u=o.state,d=o.continuationToken,f=`${i}:${d}`,{message:p,inputResponses:m,
|
|
1
|
+
import{createSession}from"#channel/session.js";import{createLogger}from"#internal/logging.js";import{isRuntimeNoActiveSessionError}from"#execution/runtime-errors.js";import{serializeUrlFilePart}from"#internal/attachments/url-refs.js";const log=createLogger(`channel.send`);function createSendFn(t,r,i){return async(a,o)=>{let s=o.auth,c=o.callback,l=o.mode??`conversation`,u=o.state,d=o.continuationToken,f=`${i}:${d}`,{message:p,inputResponses:m,context:h,outputSchema:g}=normalizeSendInput(a),_=serializeUrlFilePartsInMessage(p);try{let{sessionId:n}=await t.deliver({auth:s,continuationToken:f,payload:{inputResponses:m,message:_,context:h,outputSchema:g}});return createSession(n,d,t)}catch(e){isRuntimeNoActiveSessionError(e)||log.warn(`deliver failed, falling back to starting a new session`,{continuationToken:f})}if(m&&m.length>0)throw Error(`Cannot deliver inputResponses — the target session was not found via continuation token.`);let v=u?{...r,state:{...r.state,...u}}:r;return createSession((await t.run({adapter:v,auth:s,capabilities:l===`conversation`?{requestInput:!0}:void 0,channelName:i,callback:c,continuationToken:f,input:{message:_??``,context:h,outputSchema:g},mode:l})).sessionId,d,t)}}function serializeUrlFilePartsInMessage(e){if(e===void 0||typeof e==`string`)return e;let t=!1,n=e.map(e=>e.type===`file`&&e.data instanceof URL&&e.data.protocol!==`data:`?(t=!0,{...e,data:serializeUrlFilePart(e.data)}):e);return t?n:e}function normalizeSendInput(e){return typeof e==`string`||Array.isArray(e)?{message:e}:e}export{createSendFn};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { UserContent } from "ai";
|
|
2
2
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
3
3
|
import type { RunMode } from "#shared/run-mode.js";
|
|
4
4
|
import type { RuntimeActionResult } from "#runtime/actions/types.js";
|
|
@@ -70,7 +70,7 @@ export type EventEmitFn = (event: HandleMessageStreamEvent) => Promise<void>;
|
|
|
70
70
|
export interface DeliverPayload {
|
|
71
71
|
readonly inputResponses?: readonly InputResponse[];
|
|
72
72
|
readonly message?: string | UserContent;
|
|
73
|
-
readonly
|
|
73
|
+
readonly context?: readonly string[];
|
|
74
74
|
readonly outputSchema?: JsonObject;
|
|
75
75
|
readonly [key: string]: unknown;
|
|
76
76
|
}
|
|
@@ -215,7 +215,7 @@ export interface RunInput {
|
|
|
215
215
|
readonly initiatorAuth?: SessionAuthContext | null;
|
|
216
216
|
readonly input: {
|
|
217
217
|
readonly message: string | UserContent;
|
|
218
|
-
readonly
|
|
218
|
+
readonly context?: readonly string[];
|
|
219
219
|
readonly outputSchema?: JsonObject;
|
|
220
220
|
};
|
|
221
221
|
readonly mode: RunMode;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { HarnessToolDefinition } from "#harness/execute-tool.js";
|
|
2
|
+
import type { ContextKey } from "#context/key.js";
|
|
3
|
+
/**
|
|
4
|
+
* Builds live dynamic tool definitions. Narrower scopes appear first
|
|
5
|
+
* so they win on name collision (the tool-loop uses `??=` for dedup).
|
|
6
|
+
*
|
|
7
|
+
* Step tools are live closures (re-resolved every step via
|
|
8
|
+
* `LiveStepToolsKey`). Session/turn tools are replayed from durable
|
|
9
|
+
* metadata via the bundler's registered step functions.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildDynamicTools(ctx: {
|
|
12
|
+
get<T>(key: ContextKey<T>): T | undefined;
|
|
13
|
+
}): readonly HarnessToolDefinition[];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{LiveStepToolsKey,SessionDynamicToolMetadataKey,TurnDynamicToolMetadataKey}from"#context/keys.js";import{jsonSchema}from"ai";import{buildCallbackContext}from"#context/build-callback-context.js";const log=createLogger(`dynamic-tools`);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 replayTools(e){let t=[];for(let n of e){if(!n.executeStepFnName||!n.closureVars){log.warn(`Dynamic tool "${n.name}" has no registered step function — skipping on this step. The bundler transform may not have processed this tool file.`);continue}let e=lookupStepFunction(n.executeStepFnName);if(!e){log.warn(`Dynamic tool "${n.name}" references step function "${n.executeStepFnName}" which is not registered — skipping on this step.`);continue}t.push({description:n.description,execute:t=>e(n.closureVars,t,buildCallbackContext()),inputSchema:jsonSchema(n.inputSchema),name:n.name})}return t}function buildDynamicTools(e){let r=e.get(LiveStepToolsKey)??[],i=replayTools(e.get(TurnDynamicToolMetadataKey)??[]),a=replayTools(e.get(SessionDynamicToolMetadataKey)??[]);return[...r,...i,...a]}export{buildDynamicTools};
|
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type { ModelMessage } from "ai";
|
|
1
|
+
import type { ModelMessage, SystemModelMessage } from "ai";
|
|
2
2
|
import type { HandleMessageStreamEvent } from "#protocol/message.js";
|
|
3
3
|
import type { ResolvedDynamicInstructionsResolver } from "#runtime/types.js";
|
|
4
4
|
import type { ContextContainer } from "#context/container.js";
|
|
5
|
-
import { ContextKey } from "#context/key.js";
|
|
5
|
+
import type { ContextKey } from "#context/key.js";
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* messages. Read by the tool-loop to inject into the next model call.
|
|
10
|
-
*
|
|
11
|
-
* Virtual keys are cleared at workflow step boundaries, so messages
|
|
12
|
-
* are never persisted or duplicated across steps.
|
|
7
|
+
* Builds the flattened system messages from session + turn durable keys.
|
|
8
|
+
* Session-scoped entries appear first.
|
|
13
9
|
*/
|
|
14
|
-
export declare
|
|
10
|
+
export declare function buildDynamicInstructionMessages(ctx: {
|
|
11
|
+
get<T>(key: ContextKey<T>): T | undefined;
|
|
12
|
+
}): SystemModelMessage[];
|
|
15
13
|
/**
|
|
16
|
-
* Dispatches a stream event to dynamic instruction resolvers.
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* Dispatches a stream event to dynamic instruction resolvers.
|
|
15
|
+
*
|
|
16
|
+
* Each resolver's output replaces its own slot (keyed by slug) in the
|
|
17
|
+
* scope-appropriate durable key (session or turn). The tool-loop calls
|
|
18
|
+
* {@link buildDynamicInstructionMessages} to assemble the flattened
|
|
19
|
+
* result for the model call.
|
|
20
20
|
*/
|
|
21
21
|
export declare function dispatchDynamicInstructionEvent(input: {
|
|
22
22
|
readonly ctx: ContextContainer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{SessionDynamicInstructionsKey,TurnDynamicInstructionsKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_INSTRUCTION_EVENTS,isBrandedInstructionsEntry}from"#shared/dynamic-tool-definition.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";const log=createLogger(`dynamic-instructions`);function lowerToSystemMessage(e){let t=e.markdown.trim();if(t.length!==0)return{role:`system`,content:t}}function durableKeyForEvent(e){switch(e){case`session.started`:return SessionDynamicInstructionsKey;case`turn.started`:return TurnDynamicInstructionsKey;default:return}}function buildDynamicInstructionMessages(e){let r=e.get(SessionDynamicInstructionsKey)??{},i=e.get(TurnDynamicInstructionsKey)??{};return[...Object.values(r).flat(),...Object.values(i).flat()]}async function dispatchDynamicInstructionEvent(e){let{ctx:t,resolvers:n,event:a,messages:o}=e;if(!ALLOWED_DYNAMIC_INSTRUCTION_EVENTS.has(a.type))return;let s=n.filter(e=>e.eventNames.includes(a.type));if(s.length===0)return;let c=durableKeyForEvent(a.type);if(c===void 0)return;let l=buildResolveContext(t,o),u=await Promise.allSettled(s.map(async e=>{let t=e.events[a.type];if(t===void 0)return null;let n=await t(a,l);return n==null?{resolver:e,message:void 0}:isBrandedInstructionsEntry(n)?{resolver:e,message:lowerToSystemMessage(n)}:(log.error(`Dynamic instructions resolver "${e.slug}" returned an unbranded value — wrap with defineInstructions().`),null)})),d={...t.get(c)};for(let e of u){if(e.status===`rejected`){log.error(`Dynamic instructions resolver (${a.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}if(e.value===null)continue;let{resolver:t,message:n}=e.value;n===void 0?delete d[t.slug]:d[t.slug]=[n]}t.set(c,d)}export{buildDynamicInstructionMessages,dispatchDynamicInstructionEvent};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
2
|
+
import type { DynamicResolveContext } from "#shared/dynamic-tool-definition.js";
|
|
3
|
+
import type { AlsContext } from "#context/container.js";
|
|
4
|
+
type ReadableContext = Pick<AlsContext, "get">;
|
|
5
|
+
/**
|
|
6
|
+
* Builds the {@link DynamicResolveContext} from the active ALS context.
|
|
7
|
+
*
|
|
8
|
+
* Shared by all three dynamic lifecycle dispatchers (tools, skills,
|
|
9
|
+
* instructions) so resolver handlers receive a consistent context shape.
|
|
10
|
+
*/
|
|
11
|
+
export declare function buildResolveContext(ctx: ReadableContext, messages: readonly ModelMessage[]): DynamicResolveContext;
|
|
12
|
+
export {};
|
|
@@ -0,0 +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 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{
|
|
1
|
+
import{createLogger}from"#internal/logging.js";import{DynamicSkillManifestKey,SandboxKey}from"#context/keys.js";import{toErrorMessage}from"#shared/errors.js";import{ALLOWED_DYNAMIC_SKILL_EVENTS,isBrandedSkillEntry}from"#shared/dynamic-tool-definition.js";import{normalizeSkillPackage,writeSkillPackageToSandbox}from"#shared/skill-package.js";import{ContextKey}from"#context/key.js";import{buildResolveContext}from"#context/dynamic-resolve-context.js";import{BundleKey}from"#runtime/sessions/runtime-context-keys.js";import{formatAvailableSkillsSection}from"#execution/skills/instructions.js";const log=createLogger(`dynamic-skills`);function qualifyDynamicSkillNames(e,t,n){let r=Object.keys(n),i=[];if(r.length===0)return i;if(t||r.length===1)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 formatDynamicSkillAnnouncement(e){return formatAvailableSkillsSection(Object.values(e).flat())??``}const PendingSkillAnnouncementKey=new ContextKey(`ash.pendingSkillAnnouncement`);async function dispatchDynamicSkillEvent(e){let{ctx:a,resolvers:o,event:s,messages:c}=e;if(a.get(PendingSkillAnnouncementKey)===void 0){let e=a.get(DynamicSkillManifestKey);e!==void 0&&Object.keys(e).length>0&&a.setVirtualContext(PendingSkillAnnouncementKey,formatDynamicSkillAnnouncement(e))}if(!ALLOWED_DYNAMIC_SKILL_EVENTS.has(s.type))return;let l=o.filter(e=>e.eventNames.includes(s.type));if(l.length===0)return;let u=buildResolveContext(a,c),d=new Set(a.require(BundleKey).resolvedAgent.skills.map(e=>e.name)),f=a.get(DynamicSkillManifestKey)??{},p=[],m=await Promise.allSettled(l.map(async e=>{let t=e.events[s.type];if(t===void 0)return null;let n=await t(s,u);if(n==null)return{resolver:e,named:[]};let r,i;return isBrandedSkillEntry(n)?(r={_single:n},i=!0):(r=n,i=!1),{resolver:e,named:qualifyDynamicSkillNames(e.slug,i,r)}}));for(let e of m){if(e.status===`rejected`){log.error(`Dynamic skill resolver (${s.type}) threw — skipping.`,{error:toErrorMessage(e.reason)});continue}e.value!==null&&p.push({resolver:e.value.resolver,skills:e.value.named.map(({name:e,entry:t})=>normalizeSkillPackage({...t,name:e}))})}if(p.length===0)return;let h={...f};for(let{resolver:e,skills:t}of p)t.length===0?delete h[e.slug]:h[e.slug]=t.map(e=>({description:e.description,name:e.name}));let g=new Map;for(let[e,t]of Object.entries(h))for(let{name:n}of t){if(d.has(n))throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with an authored skill.`);let t=g.get(n);if(t!==void 0)throw Error(`Dynamic skill "${n}" from resolver "${e}" conflicts with dynamic resolver "${t}".`);g.set(n,e)}let _=await a.require(SandboxKey).get();if(_!==null)for(let{skills:e}of p)for(let t of e)await writeSkillPackageToSandbox({sandbox:_,skill:t});a.set(DynamicSkillManifestKey,h),a.setVirtualContext(PendingSkillAnnouncementKey,formatDynamicSkillAnnouncement(h))}export{PendingSkillAnnouncementKey,dispatchDynamicSkillEvent};
|
|
@@ -12,16 +12,10 @@ import type { DurableDynamicToolMetadata } from "#context/keys.js";
|
|
|
12
12
|
*/
|
|
13
13
|
export declare function replayDynamicSessionTools(metadata: readonly DurableDynamicToolMetadata[], _resolvers: readonly ResolvedDynamicToolResolver[]): readonly HarnessToolDefinition[];
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* the virtual key is empty (workflow step boundary crossed).
|
|
20
|
-
* 2. **Resolve phase**: run resolver handlers for the current event,
|
|
21
|
-
* capture closures as durable metadata, produce live tools.
|
|
22
|
-
*
|
|
23
|
-
* The tool-loop reads {@link DynamicToolsKey} right before each model
|
|
24
|
-
* call. Events update it; the tool-loop picks up whatever is current.
|
|
15
|
+
* Dispatches a stream event to dynamic tool resolvers. Each
|
|
16
|
+
* resolver's metadata replaces its slot (by slug) in the
|
|
17
|
+
* scope-appropriate durable key. The tool-loop calls
|
|
18
|
+
* {@link buildDynamicTools} to assemble the effective toolset.
|
|
25
19
|
*/
|
|
26
20
|
export declare function dispatchDynamicToolEvent(input: {
|
|
27
21
|
readonly ctx: ContextContainer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger}from"#internal/logging.js";import{
|
|
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};
|
|
@@ -5,7 +5,7 @@ import type { RuntimeHookRegistry } from "#runtime/hooks/registry.js";
|
|
|
5
5
|
import type { ContextContainer } from "./container.js";
|
|
6
6
|
/**
|
|
7
7
|
* Outcome of {@link dispatchHookLifecycle}. `proceed` carries the
|
|
8
|
-
* input augmented with merged
|
|
8
|
+
* input augmented with merged context. `turn-failed` means a
|
|
9
9
|
* `lifecycle.turn` hook threw; the recoverable `turn.failed` cascade
|
|
10
10
|
* has already been emitted.
|
|
11
11
|
*/
|
|
@@ -35,7 +35,7 @@ export interface DispatchHookLifecycleInput {
|
|
|
35
35
|
* runs so a thrown hook does not retry. A throw re-propagates and
|
|
36
36
|
* the runtime escalates to terminal `session.failed`.
|
|
37
37
|
* - `lifecycle.turn` runs once per fresh delivery. Each hook may
|
|
38
|
-
* return `{
|
|
38
|
+
* return `{ context }`; context contributions are
|
|
39
39
|
* concatenated in registry order. A thrown hook emits the recoverable
|
|
40
40
|
* `turn.failed` cascade and returns `kind: "turn-failed"`.
|
|
41
41
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ContinuationTokenKey,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{toErrorMessage}from"#shared/errors.js";import{
|
|
1
|
+
import{ContinuationTokenKey,SessionPreparedKey}from"./keys.js";import{createLogger}from"#internal/logging.js";import{getAdapterKind}from"#channel/adapter.js";import{toErrorMessage}from"#shared/errors.js";import{buildCallbackContext}from"#context/build-callback-context.js";import{BundleKey,ChannelKey}from"#runtime/sessions/runtime-context-keys.js";import{emitRecoverableFailedTurn,emitTurnPreamble,getHarnessEmissionState,setHarnessEmissionState}from"#harness/emission.js";const log=createLogger(`hooks.lifecycle`);async function dispatchHookLifecycle(e){let{ctx:n,registry:r,emit:a}=e,o=getHarnessEmissionState(e.session.state),s=buildHookContext(n),u=e.session;if(r.session.length>0&&n.get(SessionPreparedKey)!==!0){n.set(SessionPreparedKey,!0);for(let e of r.session)await e.handler(s)}let d=!1,f=o;try{for(let e of r.turn)await e.handler(s)}catch(t){let n=toErrorMessage(t);try{return d||=(f=await emitTurnPreamble(a,{message:e.input.message},o,e.runtimeIdentity),!0),{kind:`turn-failed`,message:n,nextSession:setHarnessEmissionState(u,await emitRecoverableFailedTurn(a,f,{code:`HOOK_TURN_FAILED`,message:n}))}}catch(e){throw log.error(`Event hook threw while emitting the turn.failed cascade for a lifecycle.turn failure — escalating to session.failed.`,{error:toErrorMessage(e)}),e}}return{kind:`proceed`,input:e.input,nextSession:u}}async function dispatchStreamEventHooks(e){let t=e.registry.streamEventsByType.get(e.event.type)??[],n=e.registry.streamEventsWildcard;if(t.length===0&&n.length===0)return;let r=buildHookContext(e.ctx);for(let n of t)await n.handler(e.event,r);for(let t of n)await t.handler(e.event,r)}function buildHookContext(t){let n=t.require(BundleKey),i=t.get(ChannelKey),c=t.get(ContinuationTokenKey),l=i===void 0?void 0:getAdapterKind(i);return{...buildCallbackContext(),agent:{name:n.resolvedAgent.config.name??`agent`,nodeId:n.nodeId},channel:{kind:l,continuationToken:c}}}async function runHookLifecycleStep(e,t){let n=await dispatchHookLifecycle(e);return n.kind===`turn-failed`?{next:e.mode===`conversation`?null:{done:!0,output:n.message},session:n.nextSession}:t(n.nextSession,n.input)}export{dispatchHookLifecycle,dispatchStreamEventHooks,runHookLifecycleStep};
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* tier. Codec-carrying keys (`ChannelKey`, `BundleKey`) live in
|
|
4
4
|
* `#runtime/sessions/runtime-context-keys.ts`.
|
|
5
5
|
*/
|
|
6
|
+
import type { SystemModelMessage } from "ai";
|
|
7
|
+
import type { JsonObject } from "#shared/json.js";
|
|
6
8
|
import type { ChannelInstrumentationProjection, SessionAuthContext, SessionCallback, SessionCapabilities, SessionParent, SessionTurn } from "#channel/types.js";
|
|
7
9
|
import { ContextKey } from "#context/key.js";
|
|
8
10
|
import type { SandboxAccess } from "#sandbox/state.js";
|
|
@@ -56,37 +58,32 @@ export declare const SessionCallbackKey: ContextKey<SessionCallback>;
|
|
|
56
58
|
export declare const SessionPreparedKey: ContextKey<boolean>;
|
|
57
59
|
export declare const SessionKey: ContextKey<Session>;
|
|
58
60
|
export declare const SandboxKey: ContextKey<SandboxAccess>;
|
|
59
|
-
/**
|
|
60
|
-
* Virtual (non-serialized) live dynamic tool definitions. Updated by
|
|
61
|
-
* {@link dispatchDynamicToolEvent} whenever a subscribed stream event
|
|
62
|
-
* fires. Read by the tool-loop when building the effective toolset.
|
|
63
|
-
*/
|
|
64
|
-
export declare const DynamicToolsKey: ContextKey<import("#harness/execute-tool.js").HarnessToolDefinition[]>;
|
|
65
|
-
/**
|
|
66
|
-
* Durable (serialized) metadata for session-scoped dynamic tools.
|
|
67
|
-
* Set on the first step when resolvers run. Read on subsequent steps
|
|
68
|
-
* to reconstruct tool definitions with lazy execute wrappers.
|
|
69
|
-
*/
|
|
70
61
|
export interface DurableDynamicToolMetadata {
|
|
71
62
|
readonly name: string;
|
|
72
63
|
readonly description: string;
|
|
73
|
-
readonly inputSchema:
|
|
64
|
+
readonly inputSchema: JsonObject;
|
|
74
65
|
readonly resolverSlug: string;
|
|
75
66
|
readonly entryKey: string;
|
|
76
|
-
/**
|
|
77
|
-
* Name of the hoisted execute step function (e.g.
|
|
78
|
-
* `__ash_dynamic_exec_0`). Used to look up the registered step
|
|
79
|
-
* function via `getStepFunction` on replay.
|
|
80
|
-
*/
|
|
81
67
|
readonly executeStepFnName?: string;
|
|
82
|
-
/**
|
|
83
|
-
* Serialized closure variables captured from the resolver scope on
|
|
84
|
-
* the first run. Passed as the `__vars` parameter to the hoisted
|
|
85
|
-
* step function on replay.
|
|
86
|
-
*/
|
|
87
68
|
readonly closureVars?: Record<string, unknown>;
|
|
88
69
|
}
|
|
89
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Session-scoped dynamic tool metadata (from `session.started`).
|
|
72
|
+
* Persists for the session lifetime.
|
|
73
|
+
*/
|
|
74
|
+
export declare const SessionDynamicToolMetadataKey: ContextKey<readonly DurableDynamicToolMetadata[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Turn-scoped dynamic tool metadata (from `turn.started`).
|
|
77
|
+
* Replaced each turn.
|
|
78
|
+
*/
|
|
79
|
+
export declare const TurnDynamicToolMetadataKey: ContextKey<readonly DurableDynamicToolMetadata[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Virtual (non-serialized) live step-scoped tool definitions from
|
|
82
|
+
* `step.started` resolvers. Carries original execute closures so
|
|
83
|
+
* framework tools (which lack bundler step-function metadata) work.
|
|
84
|
+
* Re-resolved every step — no cross-step persistence needed.
|
|
85
|
+
*/
|
|
86
|
+
export declare const LiveStepToolsKey: ContextKey<import("#harness/execute-tool.js").HarnessToolDefinition[]>;
|
|
90
87
|
/**
|
|
91
88
|
* Durable metadata for one session-scoped dynamic skill.
|
|
92
89
|
*/
|
|
@@ -100,3 +97,13 @@ export interface DurableDynamicSkillMetadata {
|
|
|
100
97
|
* and rebuild the model-visible announcement across turns.
|
|
101
98
|
*/
|
|
102
99
|
export declare const DynamicSkillManifestKey: ContextKey<Record<string, readonly DurableDynamicSkillMetadata[]>>;
|
|
100
|
+
/**
|
|
101
|
+
* Durable session-scoped instruction messages (from `session.started`
|
|
102
|
+
* resolvers). Keyed by resolver slug. Persists for the session lifetime.
|
|
103
|
+
*/
|
|
104
|
+
export declare const SessionDynamicInstructionsKey: ContextKey<Record<string, readonly SystemModelMessage[]>>;
|
|
105
|
+
/**
|
|
106
|
+
* Durable turn-scoped instruction messages (from `turn.started`
|
|
107
|
+
* resolvers). Keyed by resolver slug. Replaced each turn.
|
|
108
|
+
*/
|
|
109
|
+
export declare const TurnDynamicInstructionsKey: ContextKey<Record<string, readonly SystemModelMessage[]>>;
|
package/dist/src/context/keys.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ContextKey}from"#context/key.js";const AuthKey=new ContextKey(`ash.auth`),InitiatorAuthKey=new ContextKey(`ash.initiatorAuth`),SessionIdKey=new ContextKey(`ash.sessionId`),ContinuationTokenKey=new ContextKey(`ash.continuationToken`),ChannelInstrumentationKey=new ContextKey(`ash.channelInstrumentation`),ModeKey=new ContextKey(`ash.mode`),ParentSessionKey=new ContextKey(`ash.parentSession`),CapabilitiesKey=new ContextKey(`ash.capabilities`),SessionCallbackKey=new ContextKey(`ash.sessionCallback`),SessionPreparedKey=new ContextKey(`ash.sessionPrepared`),SessionKey=new ContextKey(`ash.session`),SandboxKey=new ContextKey(`ash.sandbox`),
|
|
1
|
+
import{ContextKey}from"#context/key.js";const AuthKey=new ContextKey(`ash.auth`),InitiatorAuthKey=new ContextKey(`ash.initiatorAuth`),SessionIdKey=new ContextKey(`ash.sessionId`),ContinuationTokenKey=new ContextKey(`ash.continuationToken`),ChannelInstrumentationKey=new ContextKey(`ash.channelInstrumentation`),ModeKey=new ContextKey(`ash.mode`),ParentSessionKey=new ContextKey(`ash.parentSession`),CapabilitiesKey=new ContextKey(`ash.capabilities`),SessionCallbackKey=new ContextKey(`ash.sessionCallback`),SessionPreparedKey=new ContextKey(`ash.sessionPrepared`),SessionKey=new ContextKey(`ash.session`),SandboxKey=new ContextKey(`ash.sandbox`),SessionDynamicToolMetadataKey=new ContextKey(`ash.sessionDynamicToolMetadata`),TurnDynamicToolMetadataKey=new ContextKey(`ash.turnDynamicToolMetadata`),LiveStepToolsKey=new ContextKey(`ash.liveStepTools`),DynamicSkillManifestKey=new ContextKey(`ash.dynamicSkillManifest`),SessionDynamicInstructionsKey=new ContextKey(`ash.sessionDynamicInstructions`),TurnDynamicInstructionsKey=new ContextKey(`ash.turnDynamicInstructions`);export{AuthKey,CapabilitiesKey,ChannelInstrumentationKey,ContinuationTokenKey,DynamicSkillManifestKey,InitiatorAuthKey,LiveStepToolsKey,ModeKey,ParentSessionKey,SandboxKey,SessionCallbackKey,SessionDynamicInstructionsKey,SessionDynamicToolMetadataKey,SessionIdKey,SessionKey,SessionPreparedKey,TurnDynamicInstructionsKey,TurnDynamicToolMetadataKey};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{ASH_SESSION_STREAM_NAMESPACE}from"#execution/durable-session-store.js";import{readRootSessionId}from"#execution/ash-workflow-attributes.js";import{accumulateRuntimeActionResults}from"#harness/runtime-actions.js";import{resolveVercelProductionCallbackBaseUrl}from"#execution/workflow-callback-url.js";import{normalizeSerializableError,rebuildSerializableError}from"#execution/workflow-errors.js";import{dispatchTurnStep,emitTerminalSessionFailureStep,routeProxiedDeliverStep,runProxyInputRequestStep}from"#execution/workflow-steps.js";import{createHook,getWorkflowMetadata,getWritable}from"#compiled/@workflow/core/index.js";import{coalesceDeliveries}from"#harness/messages.js";import{notifyDelegatedParentStep}from"#execution/delegated-parent-notification.js";import{createDelegatedSubagentErrorResult,createDelegatedSubagentSuccessResult}from"#execution/delegated-parent-result.js";import{createSessionStep}from"#execution/create-session-step.js";import{dispatchRuntimeActionsStep}from"#execution/dispatch-runtime-actions-step.js";import{fireSessionCallbackStep}from"#execution/session-callback-step.js";async function workflowEntry(n){"use workflow";let{workflowRunId:r}=getWorkflowMetadata(),a=n.serializedContext[`ash.continuationToken`]||``,o=n.serializedContext[`ash.mode`],c=n.serializedContext[`ash.capabilities`],l=n.serializedContext[`ash.bundle`];n.serializedContext[`ash.sessionId`]=r;let u=getWritable(),d=getWritable({namespace:ASH_SESSION_STREAM_NAMESPACE});try{let e=readRootSessionId(n.serializedContext),{state:i}=await createSessionStep({compiledArtifactsSource:l.source,continuationToken:a,inputMessage:n.input.message,nodeId:l.nodeId,outputSchema:n.input.outputSchema,rootSessionId:e,serializedContext:n.serializedContext,sessionId:r,sessionWritable:d});return await runDriverLoop({capabilities:c,driverWritable:u,initialInput:{kind:`deliver`,payloads:[{message:n.input.message,
|
|
1
|
+
import{ASH_SESSION_STREAM_NAMESPACE}from"#execution/durable-session-store.js";import{readRootSessionId}from"#execution/ash-workflow-attributes.js";import{accumulateRuntimeActionResults}from"#harness/runtime-actions.js";import{resolveVercelProductionCallbackBaseUrl}from"#execution/workflow-callback-url.js";import{normalizeSerializableError,rebuildSerializableError}from"#execution/workflow-errors.js";import{dispatchTurnStep,emitTerminalSessionFailureStep,routeProxiedDeliverStep,runProxyInputRequestStep}from"#execution/workflow-steps.js";import{createHook,getWorkflowMetadata,getWritable}from"#compiled/@workflow/core/index.js";import{coalesceDeliveries}from"#harness/messages.js";import{notifyDelegatedParentStep}from"#execution/delegated-parent-notification.js";import{createDelegatedSubagentErrorResult,createDelegatedSubagentSuccessResult}from"#execution/delegated-parent-result.js";import{createSessionStep}from"#execution/create-session-step.js";import{dispatchRuntimeActionsStep}from"#execution/dispatch-runtime-actions-step.js";import{fireSessionCallbackStep}from"#execution/session-callback-step.js";async function workflowEntry(n){"use workflow";let{workflowRunId:r}=getWorkflowMetadata(),a=n.serializedContext[`ash.continuationToken`]||``,o=n.serializedContext[`ash.mode`],c=n.serializedContext[`ash.capabilities`],l=n.serializedContext[`ash.bundle`];n.serializedContext[`ash.sessionId`]=r;let u=getWritable(),d=getWritable({namespace:ASH_SESSION_STREAM_NAMESPACE});try{let e=readRootSessionId(n.serializedContext),{state:i}=await createSessionStep({compiledArtifactsSource:l.source,continuationToken:a,inputMessage:n.input.message,nodeId:l.nodeId,outputSchema:n.input.outputSchema,rootSessionId:e,serializedContext:n.serializedContext,sessionId:r,sessionWritable:d});return await runDriverLoop({capabilities:c,driverWritable:u,initialInput:{kind:`deliver`,payloads:[{message:n.input.message,context:n.input.context,outputSchema:n.input.outputSchema}]},mode:o,serializedContext:n.serializedContext,sessionState:i,sessionWritable:d})}catch(e){throw await emitTerminalSessionFailureStep({error:normalizeSerializableError(e),parentWritable:u,serializedContext:n.serializedContext}),await fireSessionCallbackStep({error:normalizeSerializableError(e),serializedContext:n.serializedContext,status:`failed`}),await notifyDelegatedParentStep({result:createDelegatedSubagentErrorResult(n.serializedContext,e),serializedContext:n.serializedContext}),e}}async function runDriverLoop(e){let t=createHook({token:`${e.sessionState.sessionId}:auth`}),n=t[Symbol.asyncIterator](),i=e.sessionState.continuationToken,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null,c=[],getNextPromise=()=>(s??=o.next(),s),consumeNext=()=>{s=null},rekeyHook=async e=>{e===i||!e||(await closeHookIterator(o),await disposeHook(a),i=e,a=createHook({token:i}),o=a[Symbol.asyncIterator](),s=null)},l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:e.initialInput,mode:e.mode,parentWritable:e.driverWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});if(l.kind===`done`)return await closeHookIterator(n),await disposeHook(t),await closeHookIterator(o),await disposeHook(a),await finalizeDone({action:l,driverWritable:e.driverWritable});if(!l.sessionState.continuationToken)throw Error("Cannot park: no continuation token available. The channel must post the first message during the initial turn (anchoring the session) or `send()` must be called with an explicit continuationToken.");await rekeyHook(l.sessionState.continuationToken);try{for(;;)switch(l.kind){case`done`:return await finalizeDone({action:l,driverWritable:e.driverWritable});case`dispatch-runtime-actions`:{let t=await dispatchRuntimeActionsStep({callbackBaseUrl:resolveVercelProductionCallbackBaseUrl()??getWorkflowMetadata().url,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),n=await waitForPendingRuntimeActionResults({bufferedDeliveries:c,consumeNext,getNextPromise,initialResults:t.results,parentWritable:e.driverWritable,pendingActionKeys:l.pendingActionKeys,rekeyHook,serializedContext:l.serializedContext,sessionState:t.sessionState,sessionWritable:e.sessionWritable});if(n===null)return{output:``};l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`runtime-action-result`,results:n.results},mode:e.mode,parentWritable:e.driverWritable,serializedContext:n.serializedContext,sessionState:n.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}case`park`:{if(l.authorizationNames&&l.authorizationNames.length>0){let t=l.authorizationNames.length,r=[];for(;r.length<t;){let e=await n.next();if(e.done)break;e.value.kind===`deliver`&&r.push(...e.value.payloads)}l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{kind:`deliver`,payloads:r},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}let t=await waitForNextDeliver({bufferedDeliveries:c,consumeNext,getNextPromise});if(t===null)return{output:``};let r=await routeDeliverForChildren({auth:t.auth,parentWritable:e.driverWritable,payloads:t.payloads,sessionState:l.sessionState});if(r===void 0)continue;l=await dispatchAndAwaitTurn({capabilities:e.capabilities,delivery:{auth:t.auth,kind:`deliver`,payloads:[r]},mode:e.mode,parentWritable:e.driverWritable,serializedContext:l.serializedContext,sessionState:l.sessionState,sessionWritable:e.sessionWritable}),await rekeyHook(l.sessionState.continuationToken);break}}}finally{await closeHookIterator(o),await disposeHook(a),await closeHookIterator(n),await disposeHook(t)}}async function finalizeDone(e){let{output:t,serializedContext:n}=e.action,r=e.action.isError===!0;return await fireSessionCallbackStep({error:r?t:void 0,output:r?void 0:t,serializedContext:n,status:r?`failed`:`completed`}),await notifyDelegatedParentStep({result:r?createDelegatedSubagentErrorResult(n,t):createDelegatedSubagentSuccessResult(n,t),serializedContext:n}),{output:t}}async function dispatchAndAwaitTurn(e){let t=createHook(),n=t.token;try{await dispatchTurnStep({capabilities:e.capabilities,completionToken:n,delivery:e.delivery,mode:e.mode,parentWritable:e.parentWritable,serializedContext:e.serializedContext,sessionState:e.sessionState,sessionWritable:e.sessionWritable});let r=await awaitHookPayload(t);if(r.kind===`turn-error`)throw rebuildSerializableError(r.error);return r.action}finally{await disposeHook(t)}}async function awaitHookPayload(e){for await(let t of e)return t;throw Error(`Turn completion hook closed before delivering a result.`)}async function waitForPendingRuntimeActionResults(e){let t=e.sessionState,r=e.serializedContext,i=await accumulateRuntimeActionResults({bufferedDeliveries:e.bufferedDeliveries,async getNext(){for(;;){let n=await e.getNextPromise();if(e.consumeNext(),n.done)return null;let i=n.value;if(i.kind===`deliver`){let n=await routeDeliverForChildren({auth:i.auth,parentWritable:e.parentWritable,payloads:i.payloads,sessionState:t});if(n===void 0)continue;return{kind:`deliver`,value:{...i,payloads:[n]}}}if(i.kind===`runtime-action-result`)return{kind:`runtime-action-result`,results:i.results};let a=await runProxyInputRequestStep({hookPayload:i,parentWritable:e.parentWritable,serializedContext:r,sessionState:t,sessionWritable:e.sessionWritable});t=a.sessionState,r=a.serializedContext,await e.rekeyHook(t.continuationToken)}},initialResults:e.initialResults,pendingActionKeys:e.pendingActionKeys});return i===null?null:{results:i,serializedContext:r,sessionState:t}}async function routeDeliverForChildren(e){let t=coalescePayloads(e.payloads);return e.sessionState.hasProxyInputRequests?(await routeProxiedDeliverStep({auth:e.auth,parentWritable:e.parentWritable,payload:t,sessionState:e.sessionState})).remainder:t}async function waitForNextDeliver(e){if(e.bufferedDeliveries.length>0)return coalesceDeliveries(e.bufferedDeliveries.splice(0));for(;;){let t=await e.getNextPromise();if(e.consumeNext(),t.done)return null;if(t.value.kind!==`deliver`)continue;let n=t.value;for(;;){let t=await takeReadyPayload(e.getNextPromise());if(t===NO_READY_MESSAGE||(e.consumeNext(),t.done))break;t.value.kind===`deliver`&&(n=coalesceDeliveries([n,t.value]))}return n}}function coalescePayloads(e){if(e.length===0)return{};if(e.length===1)return e[0]??{};let t={},n=[];for(let r of e){for(let[e,n]of Object.entries(r))e!==`inputResponses`&&n!==void 0&&(t[e]=n);r.inputResponses!==void 0&&n.push(...r.inputResponses)}return n.length>0&&(t.inputResponses=n),t}const NO_READY_MESSAGE=Symbol(`no-ready-message`);async function takeReadyPayload(e){return await Promise.resolve(),await Promise.race([e,Promise.resolve(NO_READY_MESSAGE)])}async function closeHookIterator(e){typeof e.return==`function`&&await e.return(void 0)}async function disposeHook(e){let t=e.dispose;if(typeof t==`function`){await t.call(e);return}let n=e[Symbol.dispose];typeof n==`function`&&await n.call(e)}export{workflowEntry};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{contextStorage}from"#context/container.js";import"ai";import{CODE_MODE_TOOL_NAME,loadCodeModeModule}from"#shared/code-mode.js";import{isAuthorizationSignal}from"#harness/authorization.js";import{CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,markCodeModeToolExecutionOptions,toCodeModeConnectionAuthArgs}from"#runtime/framework-tools/code-mode-connection-auth.js";import{buildDynamicTools}from"#context/build-dynamic-tools.js";import{buildToolSet}from"#harness/tools.js";function createAshCodeModeOptions(e={}){let t={approval:{mode:`interrupt`}};return e.lifecycle!==void 0&&(t.lifecycle=e.lifecycle),t}async function applyCodeModeToToolSet(e){let r={},i={};for(let[t,n]of Object.entries(e.tools)){if(isDirectTool(n,e.harnessTools.get(t))){i[t]=n;continue}r[t]=wrapHostToolForCodeMode(n)}if(Object.keys(r).length>0){let{createCodeModeTool:a}=await loadCodeModeModule();i[CODE_MODE_TOOL_NAME]=a(r,createAshCodeModeOptions({lifecycle:e.lifecycle}))}return{hostTools:r,modelTools:i}}async function buildCodeModeHostTools(t){let n=buildToolSet({approvedTools:t.approvedTools,capabilities:t.capabilities,tools:t.tools}),r=contextStorage.getStore();if(r!==void 0){let e=buildDynamicTools(r);for(let t of e)n[t.name]??={description:t.description,inputSchema:t.inputSchema,execute:t.execute}}return(await applyCodeModeToToolSet({harnessTools:t.tools,tools:n})).hostTools}function isDirectTool(e,t){return e.execute===void 0||t?.runtimeAction!==void 0}function wrapHostToolForCodeMode(e){let t=e.execute,o=e.toModelOutput;return t===void 0?e:{...e,execute:async(e,s)=>{let c=await resolveExecuteOutput(t(e,markCodeModeToolExecutionOptions(s)));if(isAuthorizationSignal(c)){let{requestCodeModeInterrupt:t}=await loadCodeModeModule(),r=c.challenges[0]?.name;r&&t({args:toCodeModeConnectionAuthArgs(e),challenges:c.challenges,connectionName:r,kind:CODE_MODE_CONNECTION_AUTH_INTERRUPT_KIND,toolName:``})}if(o===void 0)return c;let l=await o({output:c});return isModelOutput(l)?l.value:l}}}async function resolveExecuteOutput(e){if(isAsyncIterable(e)){let t;for await(let n of e)t=n;return t}return await e}function isAsyncIterable(e){return typeof e==`object`&&!!e&&Symbol.asyncIterator in e&&typeof e[Symbol.asyncIterator]==`function`}function isModelOutput(e){if(typeof e!=`object`||!e)return!1;let t=e;return(t.type===`json`||t.type===`text`)&&Object.hasOwn(t,`value`)}export{applyCodeModeToToolSet,buildCodeModeHostTools,createAshCodeModeOptions};
|