experimental-ash 0.36.0 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +7 -7
  3. package/dist/docs/public/README.md +10 -10
  4. package/dist/docs/public/{auth-and-route-protection.md → advanced/auth-and-route-protection.md} +1 -0
  5. package/dist/docs/public/{cli-build-and-debugging.md → advanced/cli-build-and-debugging.md} +1 -0
  6. package/dist/docs/public/{context-control.md → advanced/context-control.md} +1 -0
  7. package/dist/docs/public/{evals.md → advanced/evals.md} +1 -0
  8. package/dist/docs/public/{faqs.md → advanced/faqs.md} +1 -0
  9. package/dist/docs/public/{hooks.md → advanced/hooks.md} +1 -0
  10. package/dist/docs/public/{instrumentation.md → advanced/instrumentation.md} +1 -0
  11. package/dist/docs/public/advanced/meta.json +19 -0
  12. package/dist/docs/public/{project-layout.md → advanced/project-layout.md} +1 -0
  13. package/dist/docs/public/{runs-and-streaming.md → advanced/runs-and-streaming.md} +1 -0
  14. package/dist/docs/public/{session-context.md → advanced/session-context.md} +1 -0
  15. package/dist/docs/public/{typescript-api.md → advanced/typescript-api.md} +1 -0
  16. package/dist/docs/public/{vercel-deployment.md → advanced/vercel-deployment.md} +1 -0
  17. package/dist/docs/public/{workspace.md → advanced/workspace.md} +1 -0
  18. package/dist/docs/public/meta.json +1 -14
  19. package/dist/src/context/accessors.d.ts +0 -25
  20. package/dist/src/context/accessors.js +1 -1
  21. package/dist/src/context/build-callback-context.js +1 -1
  22. package/dist/src/context/keys.d.ts +2 -2
  23. package/dist/src/evals/define-eval-suite.d.ts +3 -2
  24. package/dist/src/evals/types.d.ts +38 -12
  25. package/dist/src/execution/skills/types.d.ts +1 -1
  26. package/dist/src/harness/step-hooks.d.ts +1 -1
  27. package/dist/src/internal/application/package.js +1 -1
  28. package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
  29. package/dist/src/public/channels/discord/defaults.js +2 -2
  30. package/dist/src/public/channels/discord/discordChannel.d.ts +4 -4
  31. package/dist/src/public/channels/discord/discordChannel.js +1 -1
  32. package/dist/src/public/channels/index.d.ts +1 -1
  33. package/dist/src/public/channels/slack/defaults.js +4 -4
  34. package/dist/src/public/channels/slack/slackChannel.d.ts +4 -4
  35. package/dist/src/public/channels/slack/slackChannel.js +1 -1
  36. package/dist/src/public/channels/teams/defaults.js +3 -3
  37. package/dist/src/public/channels/teams/teamsChannel.d.ts +4 -4
  38. package/dist/src/public/channels/teams/teamsChannel.js +1 -1
  39. package/dist/src/public/channels/telegram/defaults.js +2 -2
  40. package/dist/src/public/channels/telegram/telegramChannel.d.ts +4 -4
  41. package/dist/src/public/channels/telegram/telegramChannel.js +1 -1
  42. package/dist/src/public/channels/twilio/defaults.js +2 -2
  43. package/dist/src/public/channels/twilio/twilioChannel.d.ts +4 -4
  44. package/dist/src/public/channels/twilio/twilioChannel.js +1 -1
  45. package/dist/src/public/definitions/agent.d.ts +2 -1
  46. package/dist/src/public/definitions/defineChannel.d.ts +16 -4
  47. package/dist/src/public/definitions/defineChannel.js +1 -1
  48. package/dist/src/public/definitions/exact.d.ts +7 -0
  49. package/dist/src/public/definitions/exact.js +1 -0
  50. package/dist/src/public/definitions/hook.d.ts +2 -1
  51. package/dist/src/public/definitions/instructions.d.ts +2 -1
  52. package/dist/src/public/definitions/instrumentation.d.ts +2 -1
  53. package/dist/src/public/definitions/schedule.d.ts +12 -2
  54. package/dist/src/public/definitions/skill.d.ts +2 -1
  55. package/dist/src/runtime/framework-tools/skill.js +1 -1
  56. package/dist/src/runtime/governance/auth/types.d.ts +1 -1
  57. package/dist/src/sandbox/state.d.ts +1 -1
  58. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # experimental-ash
2
2
 
3
+ ## 0.37.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 25ef58e: Reject extra top-level fields passed to core `define*` helpers while preserving literal inference for valid definitions. TypeScript now also enforces documented exclusivity for schedules (`markdown` or `run`) and eval suites (`load` or `cases`, `task.prompt` or `task.messages`).
8
+
9
+ ### Patch Changes
10
+
11
+ - 8b678cc: Channel event handlers now receive `AshCallbackContext` fields — `ctx.session` (with `turn`/`parent`), `ctx.getSandbox()`, and `ctx.getSkill()`. Channel-specific `continuationToken` and `setContinuationToken` are available at the top level of the event context.
12
+
3
13
  ## 0.36.0
4
14
 
5
15
  ### Minor Changes
package/README.md CHANGED
@@ -61,7 +61,7 @@ Runtime accessors live on the subpath that owns the concern:
61
61
  - `getSkill(identifier)` — handle for a named skill visible to the current agent (`experimental-ash/skills`)
62
62
  - `getContext(key)`, `requireContext(key)`, `hasContext(key)`, `setContext(key)`, `ensureContext(key, factory)` — unified context helpers (`experimental-ash/context`)
63
63
 
64
- The complete API reference, including types and lower-level runtime primitives, is in [`./dist/docs/public/typescript-api.md`](./dist/docs/public/typescript-api.md).
64
+ The complete API reference, including types and lower-level runtime primitives, is in [`./dist/docs/public/advanced/typescript-api.md`](./dist/docs/public/advanced/typescript-api.md).
65
65
 
66
66
  ## Tiny Example
67
67
 
@@ -121,7 +121,7 @@ CLI commands:
121
121
 
122
122
  ## Deploying
123
123
 
124
- Ash is built for Vercel. The runtime is Nitro + Vercel Workflows. Read [`./dist/docs/public/vercel-deployment.md`](./dist/docs/public/vercel-deployment.md) for the deployment path, environment variables, and Vercel-specific configuration.
124
+ Ash is built for Vercel. The runtime is Nitro + Vercel Workflows. Read [`./dist/docs/public/advanced/vercel-deployment.md`](./dist/docs/public/advanced/vercel-deployment.md) for the deployment path, environment variables, and Vercel-specific configuration.
125
125
 
126
126
  ## Read Next
127
127
 
@@ -129,14 +129,14 @@ These files ship inside the installed package at `node_modules/experimental-ash/
129
129
 
130
130
  - [Full docs index](./dist/docs/public/README.md) — recommended entry point
131
131
  - [Getting Started](./dist/docs/public/getting-started.md) — install, scaffold, and run locally
132
- - [Project Layout](./dist/docs/public/project-layout.md) — every authored directory in depth
132
+ - [Project Layout](./dist/docs/public/advanced/project-layout.md) — every authored directory in depth
133
133
  - [`agent.ts`](./dist/docs/public/agent-ts.md) — agent config reference
134
- - [TypeScript API](./dist/docs/public/typescript-api.md) — complete `define*` and runtime helper reference
135
- - [Vercel Deployment](./dist/docs/public/vercel-deployment.md) — deploy to production
134
+ - [TypeScript API](./dist/docs/public/advanced/typescript-api.md) — complete `define*` and runtime helper reference
135
+ - [Vercel Deployment](./dist/docs/public/advanced/vercel-deployment.md) — deploy to production
136
136
 
137
- By authoring concern: [Tools](./dist/docs/public/tools.md) · [Channels](./dist/docs/public/channels/README.md) · [Hooks](./dist/docs/public/hooks.md) · [Skills](./dist/docs/public/skills.md) · [Sandbox](./dist/docs/public/sandbox.md) · [Workspace](./dist/docs/public/workspace.md) · [Connections](./dist/docs/public/connections.md) · [Subagents](./dist/docs/public/subagents.md) · [Schedules](./dist/docs/public/schedules.md) · [Human In The Loop](./dist/docs/public/human-in-the-loop.md) · [Evals](./dist/docs/public/evals.md)
137
+ By authoring concern: [Tools](./dist/docs/public/tools.md) · [Channels](./dist/docs/public/channels/README.md) · [Hooks](./dist/docs/public/advanced/hooks.md) · [Skills](./dist/docs/public/skills.md) · [Sandbox](./dist/docs/public/sandbox.md) · [Workspace](./dist/docs/public/advanced/workspace.md) · [Connections](./dist/docs/public/connections.md) · [Subagents](./dist/docs/public/subagents.md) · [Schedules](./dist/docs/public/schedules.md) · [Human In The Loop](./dist/docs/public/human-in-the-loop.md) · [Evals](./dist/docs/public/advanced/evals.md)
138
138
 
139
- By runtime concern: [Sessions and Streaming](./dist/docs/public/runs-and-streaming.md) · [Session Context](./dist/docs/public/session-context.md) · [Context Control](./dist/docs/public/context-control.md) · [Auth and Route Protection](./dist/docs/public/auth-and-route-protection.md) · [CLI, Build, and Debugging](./dist/docs/public/cli-build-and-debugging.md) · [Instrumentation](./dist/docs/public/instrumentation.md)
139
+ By runtime concern: [Sessions and Streaming](./dist/docs/public/advanced/runs-and-streaming.md) · [Session Context](./dist/docs/public/advanced/session-context.md) · [Context Control](./dist/docs/public/advanced/context-control.md) · [Auth and Route Protection](./dist/docs/public/advanced/auth-and-route-protection.md) · [CLI, Build, and Debugging](./dist/docs/public/advanced/cli-build-and-debugging.md) · [Instrumentation](./dist/docs/public/advanced/instrumentation.md)
140
140
 
141
141
  ## Architecture (Internals)
142
142
 
@@ -16,25 +16,25 @@ Important naming note:
16
16
  Read in this order:
17
17
 
18
18
  1. [Getting Started](./getting-started.md)
19
- 2. [Project Layout](./project-layout.md)
19
+ 2. [Project Layout](./advanced/project-layout.md)
20
20
  3. [`agent.ts`](./agent-ts.md)
21
- 4. [TypeScript API](./typescript-api.md)
22
- 5. [Context Control](./context-control.md)
21
+ 4. [TypeScript API](./advanced/typescript-api.md)
22
+ 5. [Context Control](./advanced/context-control.md)
23
23
  6. [Skills](./skills.md)
24
24
  7. [Tools](./tools.md)
25
25
  8. [Connections](./connections.md)
26
- 9. [Workspace](./workspace.md)
26
+ 9. [Workspace](./advanced/workspace.md)
27
27
  10. [Sandboxes](./sandbox.md)
28
28
  11. [Channels](./channels/README.md)
29
29
  12. [Human In The Loop](./human-in-the-loop.md)
30
- 13. [Session Context](./session-context.md)
31
- 14. [Sessions And Streaming](./runs-and-streaming.md)
30
+ 13. [Session Context](./advanced/session-context.md)
31
+ 14. [Sessions And Streaming](./advanced/runs-and-streaming.md)
32
32
  15. [Subagents](./subagents.md)
33
33
  16. [Schedules](./schedules.md)
34
- 17. [Evals](./evals.md)
35
- 18. [Auth And Route Protection](./auth-and-route-protection.md)
36
- 19. [Vercel Deployment](./vercel-deployment.md)
37
- 20. [CLI, Build, And Debugging](./cli-build-and-debugging.md)
34
+ 17. [Evals](./advanced/evals.md)
35
+ 18. [Auth And Route Protection](./advanced/auth-and-route-protection.md)
36
+ 19. [Vercel Deployment](./advanced/vercel-deployment.md)
37
+ 20. [CLI, Build, And Debugging](./advanced/cli-build-and-debugging.md)
38
38
 
39
39
  ## The Public Mental Model
40
40
 
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Auth And Route Protection"
3
3
  description: "Protect agent routes with HTTP Basic, JWT, OIDC, and Vercel OIDC."
4
+ url: /auth-and-route-protection
4
5
  ---
5
6
 
6
7
  Ash protects its own HTTP routes through the channel layer.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "CLI, Build, And Debugging"
3
3
  description: "CLI commands: dev, build, info, eval. Debugging techniques."
4
+ url: /cli-build-and-debugging
4
5
  ---
5
6
 
6
7
  Ash's CLI gives you the normal day-to-day workflow for authoring, inspecting, and shipping an app.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Context Control"
3
3
  description: "Author always-on instructions with instructions.md or instructions.ts."
4
+ url: /context-control
4
5
  ---
5
6
 
6
7
  Ash gives you a few different levers for controlling what the model sees and when it sees it.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Evals"
3
3
  description: "Define eval suites with test cases, scoring rubrics, and automated runs."
4
+ url: /evals
4
5
  ---
5
6
 
6
7
  Evals let you define repeatable checks for an Ash agent and run them with the `ash eval` CLI
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "FAQs"
3
3
  description: "Frequently asked questions about Ash."
4
+ url: /faqs
4
5
  ---
5
6
 
6
7
  # FAQs
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Hooks"
3
3
  description: "Subscribe to lifecycle moments and runtime stream events from agent/hooks/."
4
+ url: /hooks
4
5
  ---
5
6
 
6
7
  Hooks are Ash's authored extension points for the per-turn lifecycle and
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "instrumentation.ts"
3
3
  description: "Configure OpenTelemetry exporters and AI SDK span behavior for your agent."
4
+ url: /instrumentation
4
5
  ---
5
6
 
6
7
  `instrumentation.ts` is the single place to configure OpenTelemetry for an Ash agent. It controls
@@ -0,0 +1,19 @@
1
+ {
2
+ "title": "Advanced",
3
+ "pages": [
4
+ "project-layout",
5
+ "context-control",
6
+ "hooks",
7
+ "auth-and-route-protection",
8
+ "vercel-deployment",
9
+ "runs-and-streaming",
10
+ "session-context",
11
+ "workspace",
12
+ "evals",
13
+ "instrumentation",
14
+ "cli-build-and-debugging",
15
+ "typescript-api",
16
+ "...",
17
+ "faqs"
18
+ ]
19
+ }
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Project Layout"
3
3
  description: "Directory structure and filesystem conventions."
4
+ url: /project-layout
4
5
  ---
5
6
 
6
7
  Ash supports two layouts:
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Sessions And Streaming"
3
3
  description: "The HTTP API, NDJSON event stream, and session lifecycle."
4
+ url: /runs-and-streaming
4
5
  ---
5
6
 
6
7
  Ash is durable and session-based by default.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Session Context"
3
3
  description: "Runtime helpers: ctx.session, ctx.getSandbox, ctx.getSkill, and defineState."
4
+ url: /session-context
4
5
  ---
5
6
 
6
7
  Ash exposes runtime state through the `ctx` parameter passed to tool `execute`, hook handlers,
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "TypeScript API"
3
3
  description: "TypeScript API: defineAgent, defineTool, defineSandbox, and all runtime helpers."
4
+ url: /typescript-api
4
5
  ---
5
6
 
6
7
  This page is the quick reference for Ash's public TypeScript surface.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Vercel Deployment"
3
3
  description: "Deploy your agent to Vercel."
4
+ url: /vercel-deployment
4
5
  ---
5
6
 
6
7
  Ash is designed to run locally and on Vercel.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  title: "Workspace"
3
3
  description: "Shared filesystem state and the default workspace."
4
+ url: /workspace
4
5
  ---
5
6
 
6
7
  Every Ash session gets a shared runtime workspace exposed to the model as the `bash` tool.
@@ -12,20 +12,7 @@
12
12
  "schedules",
13
13
  "connections",
14
14
  "---",
15
- "project-layout",
16
- "context-control",
17
- "hooks",
18
- "auth-and-route-protection",
19
- "vercel-deployment",
20
- "runs-and-streaming",
21
- "session-context",
22
- "workspace",
23
- "evals",
24
- "instrumentation",
25
- "cli-build-and-debugging",
26
- "typescript-api",
27
- "...",
28
- "faqs",
15
+ "advanced",
29
16
  "---",
30
17
  "research"
31
18
  ]
@@ -1,23 +1,5 @@
1
- import type { SkillHandle } from "#execution/skills/types.js";
2
- import type { SandboxSession } from "#public/definitions/sandbox.js";
3
1
  import type { ContextKey } from "#context/key.js";
4
- import { type Session } from "#context/keys.js";
5
2
  export type { Session, SessionAuth, SessionAuthContext, SessionParent, SessionTurn, } from "#context/keys.js";
6
- /**
7
- * Returns the active session for the current authored async execution chain.
8
- *
9
- * @throws When called outside of a managed step execution.
10
- */
11
- export declare function getSession(): Session;
12
- /**
13
- * Returns the active sandbox session for the current runtime execution.
14
- *
15
- * Each agent owns exactly one sandbox; callers do not pass a name.
16
- *
17
- * @throws When called outside of a managed step execution or when the
18
- * sandbox is not available on the current context.
19
- */
20
- export declare function getSandbox(): Promise<SandboxSession>;
21
3
  /**
22
4
  * Returns the current value of a context key, or `undefined` when unset.
23
5
  *
@@ -56,10 +38,3 @@ export declare function setContext<T>(key: ContextKey<T>, valueOrUpdater: T | ((
56
38
  * @throws When called outside of a managed step execution.
57
39
  */
58
40
  export declare function ensureContext<T>(key: ContextKey<T>, create: () => T): T;
59
- /**
60
- * Returns a handle for the named skill visible to the current agent.
61
- *
62
- * @throws When called outside of a managed step execution or when the
63
- * requested skill is not found.
64
- */
65
- export declare function getSkill(identifier: string): SkillHandle;
@@ -1 +1 @@
1
- import{SandboxKey,SessionKey}from"#context/keys.js";import{createSandboxSkillHandle}from"#runtime/skills/sandbox-access.js";import{loadContext}from"#context/container.js";function getSession(){return loadContext().require(SessionKey)}async function getSandbox(){let t=loadContext().get(SandboxKey);if(t===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call getSandbox() only from authored runtime functions such as tools, steps, and model callbacks.`);let n=await t.get();if(n===null)throw Error(`The sandbox is not available in the current authored runtime context.`);return n}function getContext(e){return loadContext().get(e)}function requireContext(e){return loadContext().require(e)}function hasContext(e){return loadContext().has(e)}function setContext(e,t){return loadContext().set(e,t)}function ensureContext(e,t){return loadContext().ensure(e,t)}function getSkill(t){let r=loadContext().get(SandboxKey);if(r===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call getSkill() only from authored runtime functions such as tools, steps, and model callbacks.`);return createSandboxSkillHandle(r,t)}export{ensureContext,getContext,getSandbox,getSession,getSkill,hasContext,requireContext,setContext};
1
+ import{loadContext}from"#context/container.js";function getContext(e){return loadContext().get(e)}function requireContext(e){return loadContext().require(e)}function hasContext(e){return loadContext().has(e)}function setContext(e,t){return loadContext().set(e,t)}function ensureContext(e,t){return loadContext().ensure(e,t)}export{ensureContext,getContext,hasContext,requireContext,setContext};
@@ -1 +1 @@
1
- import{SandboxKey,SessionKey}from"#context/keys.js";import{createSandboxSkillHandle}from"#runtime/skills/sandbox-access.js";import{loadContext}from"#context/container.js";function buildCallbackContext(){let r=loadContext(),i=r.require(SessionKey);return{session:{id:i.sessionId,auth:i.auth,turn:i.turn,parent:i.parent},getSandbox(){let t=r.get(SandboxKey);if(t===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call ctx.getSandbox() only from authored runtime functions such as tools, hooks, and channel events.`);return t.get().then(e=>{if(e===null)throw Error(`The sandbox is not available in the current authored runtime context.`);return e})},getSkill(t){let n=r.get(SandboxKey);if(n===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call ctx.getSkill() only from authored runtime functions such as tools, hooks, and channel events.`);return createSandboxSkillHandle(n,t)}}}export{buildCallbackContext};
1
+ import{SandboxKey,SessionKey}from"#context/keys.js";import{loadContext}from"#context/container.js";import{createSandboxSkillHandle}from"#runtime/skills/sandbox-access.js";function buildCallbackContext(){let r=loadContext(),i=r.require(SessionKey);return{session:{id:i.sessionId,auth:i.auth,turn:i.turn,parent:i.parent},getSandbox(){let t=r.get(SandboxKey);if(t===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call ctx.getSandbox() only from authored runtime functions such as tools, hooks, and channel events.`);return t.get().then(e=>{if(e===null)throw Error(`The sandbox is not available in the current authored runtime context.`);return e})},getSkill(t){let n=r.get(SandboxKey);if(n===void 0)throw Error(`Ash sandbox runtime access is unavailable in the current async context. Call ctx.getSkill() only from authored runtime functions such as tools, hooks, and channel events.`);return createSandboxSkillHandle(n,t)}}}export{buildCallbackContext};
@@ -19,8 +19,8 @@ export interface SessionAuth {
19
19
  readonly initiator: SessionAuthContext | null;
20
20
  }
21
21
  /**
22
- * Public session metadata exposed to authored code (tools, steps, model
23
- * callbacks) via {@link getSession}.
22
+ * Public session metadata exposed to authored code (tools, hooks,
23
+ * channel events) via `ctx.session`.
24
24
  */
25
25
  export interface Session {
26
26
  readonly auth: SessionAuth;
@@ -1,4 +1,5 @@
1
- import type { AshEvalSuiteDefinition, AshEvalSuiteInput } from "#evals/types.js";
1
+ import type { AshEvalSuiteDefinition, AshEvalSuiteInput, AshEvalSuiteInputFields } from "#evals/types.js";
2
+ import type { ExactDefinition } from "#public/definitions/exact.js";
2
3
  /**
3
4
  * Defines one Ash eval suite.
4
5
  *
@@ -7,4 +8,4 @@ import type { AshEvalSuiteDefinition, AshEvalSuiteInput } from "#evals/types.js"
7
8
  * either `load` (an async function) or `cases` (a static array), but not
8
9
  * both.
9
10
  */
10
- export declare function defineEvalSuite(input: AshEvalSuiteInput): AshEvalSuiteDefinition;
11
+ export declare function defineEvalSuite<TInput extends AshEvalSuiteInput>(input: ExactDefinition<TInput, AshEvalSuiteInputFields>): AshEvalSuiteDefinition;
@@ -84,28 +84,39 @@ export interface AshEvalScorerArgs {
84
84
  * Return `null` to skip scoring for a case (e.g. when expected is absent).
85
85
  */
86
86
  export type AshEvalScorer = (args: AshEvalScorerArgs) => AshEvalScorerResult | Promise<AshEvalScorerResult | null> | null;
87
+ export interface AshEvalTaskFields {
88
+ readonly messages?: (testCase: AshEvalCase) => string[];
89
+ readonly parseOutput?: (result: AshEvalTaskResult) => unknown;
90
+ readonly prompt?: (testCase: AshEvalCase) => string;
91
+ }
87
92
  /**
88
93
  * Declarative task configuration for a suite. The runner owns session
89
94
  * lifecycle, stream capture, and derived metadata. Suites only declare how
90
95
  * to derive messages and parse outputs.
91
96
  */
92
- export interface AshEvalTask {
93
- /**
94
- * Derive a single prompt string from one eval case.
95
- * Mutually exclusive with `messages`.
96
- */
97
- readonly prompt?: (testCase: AshEvalCase) => string;
97
+ export type AshEvalTask = (AshEvalTaskFields & {
98
98
  /**
99
99
  * Derive an ordered list of messages for a multi-turn eval.
100
100
  * Mutually exclusive with `prompt`.
101
101
  */
102
- readonly messages?: (testCase: AshEvalCase) => string[];
102
+ readonly messages: (testCase: AshEvalCase) => string[];
103
+ readonly prompt?: never;
104
+ }) | (AshEvalTaskFields & {
105
+ readonly messages?: never;
106
+ /**
107
+ * Derive a single prompt string from one eval case.
108
+ * Mutually exclusive with `messages`.
109
+ */
110
+ readonly prompt: (testCase: AshEvalCase) => string;
111
+ }) | {
112
+ readonly messages?: never;
103
113
  /**
104
114
  * Transform the raw task result into the scored output value.
105
115
  * When omitted, `result.output` defaults to `result.finalMessage`.
106
116
  */
107
117
  readonly parseOutput?: (result: AshEvalTaskResult) => unknown;
108
- }
118
+ readonly prompt?: never;
119
+ };
109
120
  /**
110
121
  * Eval target descriptor.
111
122
  */
@@ -159,6 +170,18 @@ interface AshEvalSuiteBase {
159
170
  */
160
171
  readonly thresholds?: Readonly<Record<string, number>>;
161
172
  }
173
+ /**
174
+ * Complete top-level key set accepted by {@link defineEvalSuite}.
175
+ *
176
+ * Used by the definition helper to reject unknown authored keys while the
177
+ * stricter {@link AshEvalSuiteInput} union enforces `load`/`cases`
178
+ * exclusivity.
179
+ */
180
+ export interface AshEvalSuiteInputFields extends Omit<AshEvalSuiteBase, "task"> {
181
+ readonly cases?: readonly AshEvalCase[];
182
+ readonly task?: AshEvalTaskFields;
183
+ load?(): Promise<AshEvalCase[]>;
184
+ }
162
185
  /**
163
186
  * Full suite input. Authors pass this to `defineEvalSuite()`.
164
187
  *
@@ -167,12 +190,15 @@ interface AshEvalSuiteBase {
167
190
  * function internally. Suite identity is derived from the file path —
168
191
  * authors do not specify an `id` or `name`.
169
192
  */
170
- export interface AshEvalSuiteInput extends AshEvalSuiteBase {
193
+ export type AshEvalSuiteInput = (AshEvalSuiteBase & {
194
+ readonly cases?: never;
171
195
  /** Load cases dynamically. Mutually exclusive with `cases`. */
172
- load?(): Promise<AshEvalCase[]>;
196
+ load(): Promise<AshEvalCase[]>;
197
+ }) | (AshEvalSuiteBase & {
173
198
  /** Static inline cases. Mutually exclusive with `load`. */
174
- readonly cases?: readonly AshEvalCase[];
175
- }
199
+ readonly cases: readonly AshEvalCase[];
200
+ load?: never;
201
+ });
176
202
  /**
177
203
  * Suite returned by `defineEvalSuite()`. Carries no `id` yet — discovery
178
204
  * stamps the path-derived id on at import time to produce a full
@@ -8,7 +8,7 @@ export interface SkillFile {
8
8
  text(): Promise<string>;
9
9
  }
10
10
  /**
11
- * Handle to one authored skill, returned by {@link getSkill}.
11
+ * Handle to one authored skill, returned by `ctx.getSkill()`.
12
12
  *
13
13
  * Provides metadata and file access for skill packages.
14
14
  */
@@ -71,7 +71,7 @@ interface StepHooks {
71
71
  * own all step-internal work: emission, compaction, and prompt caching.
72
72
  *
73
73
  * The harness passes these hooks to `ToolLoopAgent` and reads the
74
- * results via `getSession()` and `stepResult` after the agent finishes.
74
+ * results via `stepResult` after the agent finishes.
75
75
  */
76
76
  export declare function buildStepHooks(input: StepHooksInput): StepHooks;
77
77
  /**
@@ -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.36.0`}const FALLBACK_PACKAGE_INFO={name:ASH_PACKAGE_NAME,version:resolveFallbackPackageVersion()};function resolveCurrentModulePath(){return typeof __filename==`string`?__filename:resolveCurrentModulePathFromStack()}function resolveCurrentModulePathFromStack(){let e=Error.prepareStackTrace;try{Error.prepareStackTrace=(e,t)=>t;let e=Error().stack?.[0]?.getFileName();if(typeof e!=`string`||e.length===0)throw Error(`Failed to resolve the current module path from the stack trace.`);return e.startsWith(`file:`)?fileURLToPath(e):e}finally{Error.prepareStackTrace=e}}const require=createRequire(resolveCurrentModulePath());function isBuildOutputPackageRoot(e){return basename(e)===`dist`&&existsSync(join(dirname(e),`package.json`))}function resolvePackageBuildRoot(){let e=dirname(realpathSync(resolveCurrentModulePath()));for(;;){if(isBuildOutputPackageRoot(e))return e;let t=dirname(e);if(t===e)return null;e=t}}function findNearestPackageRoot(e){let t=e;for(;;){if(existsSync(join(t,`package.json`))&&!isBuildOutputPackageRoot(t))return t;let r=dirname(t);if(r===t)throw Error(`Failed to resolve package root from "${e}".`);t=r}}function resolvePackageRoot(){return findNearestPackageRoot(dirname(realpathSync(resolveCurrentModulePath())))}function tryResolvePackageRoot(){try{return resolvePackageRoot()}catch{return}}function rewriteSourceFilePathForBuild(e){return e.replace(/\.[cm]?tsx?$/,`.js`)}function resolvePackageSourceFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),e):join(t,rewriteSourceFilePathForBuild(e))}function resolvePackageSourceDirectoryPath(e){let t=resolvePackageBuildRoot();return join(t===null?resolvePackageRoot():t,e)}function resolvePackageCompiledFilePath(e){let t=resolvePackageBuildRoot();return t===null?join(resolvePackageRoot(),`.generated`,`compiled`,e.replace(/^src\/compiled\//,``)):join(t,e)}function normalizeInstalledPackageInfo(e){let t=e;if(!(typeof t.name!=`string`||typeof t.version!=`string`))return{name:t.name,version:t.version}}function tryReadInstalledPackageInfo(e,t){let n=normalizeInstalledPackageInfo(JSON.parse(readFileSync(e,`utf8`)));if(n?.name===t)return n}function resolveInstalledPackageInfo(){if(cachedPackageInfo)return cachedPackageInfo;let e=tryResolvePackageRoot(),t=e===void 0?void 0:tryReadInstalledPackageInfo(join(e,`package.json`),ASH_PACKAGE_NAME);if(t)return cachedPackageInfo=t,cachedPackageInfo;try{let e=tryReadInstalledPackageInfo(require.resolve(`${ASH_PACKAGE_NAME}/package.json`),ASH_PACKAGE_NAME);if(e)return cachedPackageInfo=e,cachedPackageInfo}catch{}return cachedPackageInfo={...FALLBACK_PACKAGE_INFO},cachedPackageInfo}function resolveWorkflowModulePath(e){if(e===`workflow`)return resolvePackageSourceFilePath(`src/internal/workflow/index.ts`);if(e===`workflow/internal/builtins`)return resolvePackageSourceFilePath(`src/internal/workflow/builtins.ts`);let t=WORKFLOW_MODULE_ALIASES[e];return t===void 0?require.resolve(e):resolvePackageCompiledFilePath(t)}export{resolveInstalledPackageInfo,resolvePackageRoot,resolvePackageSourceDirectoryPath,resolvePackageSourceFilePath,resolveWorkflowModulePath};
1
+ import{createRequire}from"node:module";import{basename,dirname,join}from"node:path";import{existsSync,readFileSync,realpathSync}from"node:fs";import{ASH_PACKAGE_NAME}from"#internal/package-name.js";import{fileURLToPath}from"node:url";let cachedPackageInfo;const WORKFLOW_MODULE_ALIASES={"workflow/api":`src/compiled/@workflow/core/runtime.js`,"workflow/errors":`src/compiled/@workflow/errors/index.js`,"workflow/internal/private":`src/compiled/@workflow/core/private.js`,"workflow/runtime":`src/compiled/@workflow/core/runtime.js`};function resolveFallbackPackageVersion(){return`0.37.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.36.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.154`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
1
+ import{getSupportedModuleBaseName,matchesSupportedModuleBaseName}from"./module-files.js";import{pathExists,writeTextFile}from"./files.js";import{PNPM_WORKSPACE_PATH,ensurePnpmWorkspacePolicy}from"./pnpm-workspace.js";import{WEB_APP_TEMPLATE_FILES,WEB_APP_TEMPLATE_PACKAGE_JSON}from"./web-template.js";import"./project.js";import{patchPackageJson}from"./package-json.js";import{basename,join,resolve}from"node:path";import{readFile,readdir,writeFile}from"node:fs/promises";const SLACK_CHANNEL_DEFAULT_ROUTE=`/ash/v1/slack`,DEFAULT_SLACK_CONNECTOR_SLUG=`my-agent`,PACKAGE_DEPENDENCY_FIELDS=[`dependencies`,`devDependencies`,`peerDependencies`,`optionalDependencies`],WEB_VERCEL_JSON_PATH=`vercel.json`,WEB_VERCEL_JSON_SCHEMA=`https://openapi.vercel.sh/vercel.json`,WEB_DEFAULT_VERCEL_SERVICES={web:{entrypoint:`.`,framework:`nextjs`,routePrefix:`/`},ash:{buildCommand:`ash build`,entrypoint:`.`,framework:`ash`,routePrefix:`/_ash_internal/ash`}};function toSlackConnectorSlug(e){return e}function isJsonObject(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}async function readDependencyVersion(e,t){let n=JSON.parse(await readFile(e,`utf8`));if(!isJsonObject(n)||!isJsonObject(n.dependencies))return;let r=n.dependencies[t];return typeof r==`string`?r:void 0}function packageJsonHasDependency(e,t){for(let n of PACKAGE_DEPENDENCY_FIELDS){let r=e[n];if(isJsonObject(r)&&typeof r[t]==`string`)return!0}return!1}async function hasPackageDependency(e,t){if(!await pathExists(e))return!1;let r=JSON.parse(await readFile(e,`utf8`));return isJsonObject(r)&&packageJsonHasDependency(r,t)}async function ensurePackageDependency(e,t,r){return!await pathExists(e)||await readDependencyVersion(e,t)===r?[]:(await patchPackageJson(e,{dependencies:{[t]:r}}),[{path:e,dependencies:[t],devDependencies:[],scripts:[]}])}function resolveWebPackageVersions(e){return{ashPackageVersion:e?.ashPackageVersion??`0.37.0`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.154`,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,3 +1,3 @@
1
- import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{splitDiscordMessageContent}from"#public/channels/discord/api.js";import{renderInputRequestComponents}from"#public/channels/discord/hitl.js";function defaultDiscordAuth(e){let t={channel_id:e.channelId,interaction_id:e.id,user_id:e.user.id,username:e.user.username};return e.guildId!==void 0&&(t.guild_id=e.guildId),e.member?.nick!==void 0&&(t.member_nick=e.member.nick),{attributes:t,authenticator:`discord-interaction`,issuer:e.guildId?`discord:${e.guildId}`:`discord`,principalId:e.guildId?`discord:${e.guildId}:${e.user.id}`:`discord:${e.user.id}`,principalType:e.user.isBot?`service`:`user`}}function defaultOnCommand(e,t){return{auth:defaultDiscordAuth(t)}}const defaultEvents={async"turn.started"(e,t){await t.discord.startTyping()},async"actions.requested"(e,t){await t.discord.startTyping()},async"input.requested"(e,t){for(let i of e.requests){let e=splitDiscordMessageContent(i.prompt)[0]??i.prompt;await t.discord.post({components:renderInputRequestComponents(i),content:e})}},async"message.completed"(e,t){e.finishReason===`tool-calls`||!e.message||await t.discord.post(e.message)},async"session.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.discord.post([`This session could not recover from an error${i}.`,``,`Start a new command to continue.`,...a?[``,`Error id: ${a}`]:[]].join(`
2
- `))},async"turn.failed"(n,r){let i=formatErrorHint(n),a=extractErrorId(n.details);await r.discord.post([`I hit an error while handling your request${i}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...a?[``,`Error id: ${a}`]:[]].join(`
1
+ import{extractErrorId,formatErrorHint}from"#internal/logging.js";import{splitDiscordMessageContent}from"#public/channels/discord/api.js";import{renderInputRequestComponents}from"#public/channels/discord/hitl.js";function defaultDiscordAuth(e){let t={channel_id:e.channelId,interaction_id:e.id,user_id:e.user.id,username:e.user.username};return e.guildId!==void 0&&(t.guild_id=e.guildId),e.member?.nick!==void 0&&(t.member_nick=e.member.nick),{attributes:t,authenticator:`discord-interaction`,issuer:e.guildId?`discord:${e.guildId}`:`discord`,principalId:e.guildId?`discord:${e.guildId}:${e.user.id}`:`discord:${e.user.id}`,principalType:e.user.isBot?`service`:`user`}}function defaultOnCommand(e,t){return{auth:defaultDiscordAuth(t)}}const defaultEvents={async"turn.started"(e,t,n){await t.discord.startTyping()},async"actions.requested"(e,t,n){await t.discord.startTyping()},async"input.requested"(e,t,i){for(let i of e.requests){let e=splitDiscordMessageContent(i.prompt)[0]??i.prompt;await t.discord.post({components:renderInputRequestComponents(i),content:e})}},async"message.completed"(e,t,n){e.finishReason===`tool-calls`||!e.message||await t.discord.post(e.message)},async"session.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.discord.post([`This session could not recover from an error${a}.`,``,`Start a new command to continue.`,...o?[``,`Error id: ${o}`]:[]].join(`
2
+ `))},async"turn.failed"(n,r,i){let a=formatErrorHint(n),o=extractErrorId(n.details);await r.discord.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`Error id: ${o}`]:[]].join(`
3
3
  `))}};export{defaultDiscordAuth,defaultEvents,defaultOnCommand};
@@ -1,7 +1,8 @@
1
1
  import type { ModelMessage } from "ai";
2
2
  import type { TypedReceiveRoute } from "#channel/receive-args.js";
3
- import type { SessionHandle } from "#channel/session.js";
4
3
  import type { SessionAuthContext } from "#channel/types.js";
4
+ import type { AshCallbackContext } from "#public/definitions/callback-context.js";
5
+ import type { ChannelSessionOps } from "#public/definitions/defineChannel.js";
5
6
  import type { HandleMessageStreamEvent } from "#protocol/message.js";
6
7
  import { type DiscordApiOptions, type DiscordApiResponse, type DiscordCredentials, type DiscordMessageBody, type DiscordPostedMessage } from "#public/channels/discord/api.js";
7
8
  import { type DiscordCommandInteraction } from "#public/channels/discord/inbound.js";
@@ -18,8 +19,7 @@ export interface DiscordContext {
18
19
  readonly discord: DiscordHandle;
19
20
  }
20
21
  /** Event-handler Discord context, including mutable per-conversation state. */
21
- export interface DiscordEventContext extends DiscordContext {
22
- readonly session: SessionHandle;
22
+ export interface DiscordEventContext extends DiscordContext, ChannelSessionOps {
23
23
  state: DiscordChannelState;
24
24
  }
25
25
  /** JSON-serializable Discord channel state. */
@@ -62,7 +62,7 @@ export type DiscordCommandResult = {
62
62
  } | null;
63
63
  /** Sync or async {@link DiscordCommandResult}. */
64
64
  export type DiscordCommandResultOrPromise = DiscordCommandResult | Promise<DiscordCommandResult>;
65
- type DiscordEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, ctx: DiscordEventContext) => void | Promise<void>;
65
+ type DiscordEventHandler<T extends HandleMessageStreamEvent["type"]> = (data: EventData<T>, channel: DiscordEventContext, ctx: AshCallbackContext) => void | Promise<void>;
66
66
  /** Event handlers supported by `discordChannel({ events })`. */
67
67
  export interface DiscordChannelEvents {
68
68
  readonly "turn.started"?: DiscordEventHandler<"turn.started">;
@@ -1 +1 @@
1
- import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import"#public/channels/discord/verify.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{buildFreeformModalResponse,deriveComponentInputResponses,deriveModalInputResponses,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{defaultEvents,defaultOnCommand}from"#public/channels/discord/defaults.js";import{DISCORD_INTERACTION_RESPONSE_TYPE,DISCORD_INTERACTION_TYPE,commandInteractionMessage,parseDiscordInteraction,prependDiscordContext}from"#public/channels/discord/inbound.js";import{discordDeferredJson,discordJson,discordJsonBody,readMessageContent}from"#public/channels/discord/responses.js";import{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,a={...defaultEvents,...e.events};return defineChannel({kindHint:`discord`,state:initialDiscordState(),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/ash/v1/discord`,async(r,{send:i,waitUntil:a})=>{let o=await verifyDiscordInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s;try{s=parseJsonObject(JSON.parse(o))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(s.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let c=parseDiscordInteraction(s);return c===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:c,onCommand:t,send:i,waitUntil:a})})],async receive(t,{send:n}){let r=t.args,i=readString(r.channelId);if(!i)throw Error(`discordChannel().receive requires args.channelId.`);let a=readString(r.conversationId),o=r.initialMessage;if(o!==void 0&&a!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=a??``,l=a!==void 0;if(o!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:i}}).sendChannelMessage(o);c=t.id,l=t.id.length>0}return n(t.message,{auth:t.auth,continuationToken:discordContinuationToken(i,c),state:{applicationId:null,channelId:i,conversationId:c||null,guildId:null,hasMessageAnchor:l,initialResponseSent:!0,interactionToken:null}})},events:a})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),session:t,state:e}}function buildDiscordHandle(e){let n=e.config.api,r=e.state,i=mergeCredentials(e.config.credentials,r);function anchor(t){!t.id||r.hasMessageAnchor||(r.conversationId=t.id,r.hasMessageAnchor=!0,r.channelId&&e.session?.setContinuationToken(discordContinuationToken(r.channelId,t.id)))}async function sendViaChannel(e){let t=r.channelId??``;if(!t)throw Error(`discordChannel: missing channel id for outbound message.`);let a=await sendDiscordChannelMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,channelId:t});return anchor(a),a}async function editOriginal(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=await editDiscordOriginalResponse({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return r.initialResponseSent=!0,anchor(a),a}async function followup(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for followup message.`);let a=await createDiscordFollowupMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return anchor(a),a}async function startTyping(){let e=r.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:n?.apiBaseUrl,credentials:i,fetch:n?.fetch,channelId:e})}catch(n){logError(log,`Discord typing indicator failed — swallowed`,n,{channelId:e})}}return{applicationId:r.applicationId??void 0,channelId:r.channelId??``,conversationId:r.conversationId??``,guildId:r.guildId??void 0,interactionToken:r.interactionToken??void 0,request(e,t,r){return callDiscordApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:r?.botAuth===!0?i.botToken:void 0,fetch:n?.fetch,method:r?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),n;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:r});n===void 0&&(n=t)}return n??{id:``,raw:null}},editOriginalResponse:editOriginal,followup,sendChannelMessage:sendViaChannel,startTyping}}async function postOne(e){if(e.state.interactionToken&&e.state.applicationId)try{return e.state.initialResponseSent?await e.followup(e.body):await e.editOriginal(e.body)}catch(e){log.warn(`Discord interaction-token delivery failed, falling back to channel message`,{error:e})}return e.sendViaChannel(e.body)}async function handleInteraction(e){return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,send:e.send,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil}):handleModalSubmitInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil})}async function handleCommandInteraction(e){let t=stateFromInteraction(e.interaction,{conversationId:e.interaction.id,hasMessageAnchor:!1,initialResponseSent:!1}),n={discord:buildDiscordHandle({config:e.config,state:t})},r;try{r=await e.onCommand(n,e.interaction)}catch(e){return log.error(`command handler failed`,{error:e}),discordJson({content:`The Discord command handler failed.`,ephemeral:!0})}return r==null?discordJson({content:`Command ignored.`,ephemeral:!0}):(e.waitUntil(dispatchCommand({interaction:e.interaction,result:r,send:e.send,state:t})),discordDeferredJson(r.ephemeral===!0))}function handleComponentInteraction(e){if(isDiscordFreeformComponent(e.interaction.customId)){let t=readMessageContent(e.interaction.raw);return discordJsonBody(buildFreeformModalResponse({customId:e.interaction.customId,prompt:t}))}let t=deriveComponentInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId,inputResponses:t,interaction:e.interaction,send:e.send})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId??e.interaction.id,inputResponses:t,interaction:e.interaction,send:e.send})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=prependDiscordContext(commandInteractionMessage(e.interaction),{channelId:e.interaction.channelId,commandName:e.interaction.commandName,guildId:e.interaction.guildId,interactionId:e.interaction.id,userId:e.interaction.user.id,username:e.interaction.user.username});try{await e.send({message:t,modelContext:e.result.modelContext},{auth:e.result.auth,continuationToken:discordContinuationToken(e.interaction.channelId,e.interaction.id),state:e.state})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchInputResponses(e){try{await e.send({inputResponses:e.inputResponses},{auth:null,continuationToken:discordContinuationToken(e.interaction.channelId,e.conversationId),state:stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:!0,initialResponseSent:!0})})}catch(e){log.error(`interaction response delivery failed`,{error:e})}}function stateFromInteraction(e,t){return{applicationId:e.applicationId,channelId:e.channelId,conversationId:t.conversationId,guildId:e.guildId??null,hasMessageAnchor:t.hasMessageAnchor,initialResponseSent:t.initialResponseSent,interactionToken:e.token}}function initialDiscordState(){return{applicationId:null,channelId:null,conversationId:null,guildId:null,hasMessageAnchor:!1,initialResponseSent:!1,interactionToken:null}}function mergeCredentials(e,t){return{applicationId:t.applicationId??e?.applicationId,botToken:e?.botToken,publicKey:e?.publicKey,webhookVerifier:e?.webhookVerifier}}function normalizePostInput(e){return typeof e==`string`?{content:e}:e}function expandPostBodies(e){return typeof e.content==`string`?splitDiscordMessageContent(e.content).map((t,n)=>n===0?{...e,content:t}:{allowed_mentions:e.allowed_mentions,content:t}):[e]}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{discordChannel};
1
+ import{createLogger,logError}from"#internal/logging.js";import{parseJsonObject}from"#shared/json.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import"#public/channels/discord/verify.js";import{callDiscordApi,createDiscordFollowupMessage,discordContinuationToken,editDiscordOriginalResponse,sendDiscordChannelMessage,splitDiscordMessageContent,triggerDiscordTypingIndicator}from"#public/channels/discord/api.js";import{buildFreeformModalResponse,deriveComponentInputResponses,deriveModalInputResponses,isDiscordFreeformComponent}from"#public/channels/discord/hitl.js";import{defaultEvents,defaultOnCommand}from"#public/channels/discord/defaults.js";import{DISCORD_INTERACTION_RESPONSE_TYPE,DISCORD_INTERACTION_TYPE,commandInteractionMessage,parseDiscordInteraction,prependDiscordContext}from"#public/channels/discord/inbound.js";import{discordDeferredJson,discordJson,discordJsonBody,readMessageContent}from"#public/channels/discord/responses.js";import{verifyDiscordInbound}from"#public/channels/discord/verifyInbound.js";const log=createLogger(`discord.channel`);function discordChannel(e={}){let t=e.onCommand??defaultOnCommand,a={...defaultEvents,...e.events};return defineChannel({kindHint:`discord`,state:initialDiscordState(),context(t,n){return rebuildDiscordContext(t,n,e)},routes:[POST(e.route??`/ash/v1/discord`,async(r,{send:i,waitUntil:a})=>{let o=await verifyDiscordInbound(r,e.credentials);if(o===null)return new Response(`unauthorized`,{status:401});let s;try{s=parseJsonObject(JSON.parse(o))}catch(e){return log.warn(`inbound Discord body is not valid JSON`,{error:e}),discordJson({content:`invalid request`,ephemeral:!0})}if(s.type===DISCORD_INTERACTION_TYPE.PING)return discordJson({type:DISCORD_INTERACTION_RESPONSE_TYPE.PONG});let c=parseDiscordInteraction(s);return c===null?discordJson({content:`Unsupported Discord interaction.`,ephemeral:!0}):handleInteraction({config:e,interaction:c,onCommand:t,send:i,waitUntil:a})})],async receive(t,{send:n}){let r=t.args,i=readString(r.channelId);if(!i)throw Error(`discordChannel().receive requires args.channelId.`);let a=readString(r.conversationId),o=r.initialMessage;if(o!==void 0&&a!==void 0)throw Error("discordChannel().receive: `conversationId` and `initialMessage` are mutually exclusive.");let c=a??``,l=a!==void 0;if(o!==void 0){let t=await buildDiscordHandle({config:e,state:{...initialDiscordState(),channelId:i}}).sendChannelMessage(o);c=t.id,l=t.id.length>0}return n(t.message,{auth:t.auth,continuationToken:discordContinuationToken(i,c),state:{applicationId:null,channelId:i,conversationId:c||null,guildId:null,hasMessageAnchor:l,initialResponseSent:!0,interactionToken:null}})},events:a})}function rebuildDiscordContext(e,t,n){return{discord:buildDiscordHandle({config:n,session:t,state:e}),state:e}}function buildDiscordHandle(e){let n=e.config.api,r=e.state,i=mergeCredentials(e.config.credentials,r);function anchor(t){!t.id||r.hasMessageAnchor||(r.conversationId=t.id,r.hasMessageAnchor=!0,r.channelId&&e.session?.setContinuationToken(discordContinuationToken(r.channelId,t.id)))}async function sendViaChannel(e){let t=r.channelId??``;if(!t)throw Error(`discordChannel: missing channel id for outbound message.`);let a=await sendDiscordChannelMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,channelId:t});return anchor(a),a}async function editOriginal(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for original response edit.`);let a=await editDiscordOriginalResponse({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return r.initialResponseSent=!0,anchor(a),a}async function followup(e){let t=r.interactionToken??``;if(!t)throw Error(`discordChannel: missing interaction token for followup message.`);let a=await createDiscordFollowupMessage({apiBaseUrl:n?.apiBaseUrl,body:normalizePostInput(e),credentials:i,fetch:n?.fetch,interactionToken:t});return anchor(a),a}async function startTyping(){let e=r.channelId??``;if(e)try{await triggerDiscordTypingIndicator({apiBaseUrl:n?.apiBaseUrl,credentials:i,fetch:n?.fetch,channelId:e})}catch(n){logError(log,`Discord typing indicator failed — swallowed`,n,{channelId:e})}}return{applicationId:r.applicationId??void 0,channelId:r.channelId??``,conversationId:r.conversationId??``,guildId:r.guildId??void 0,interactionToken:r.interactionToken??void 0,request(e,t,r){return callDiscordApi({apiBaseUrl:n?.apiBaseUrl,body:t,botToken:r?.botAuth===!0?i.botToken:void 0,fetch:n?.fetch,method:r?.method,path:e})},async post(e){let t=expandPostBodies(normalizePostInput(e)),n;for(let e of t){let t=await postOne({body:e,editOriginal,followup,sendViaChannel,state:r});n===void 0&&(n=t)}return n??{id:``,raw:null}},editOriginalResponse:editOriginal,followup,sendChannelMessage:sendViaChannel,startTyping}}async function postOne(e){if(e.state.interactionToken&&e.state.applicationId)try{return e.state.initialResponseSent?await e.followup(e.body):await e.editOriginal(e.body)}catch(e){log.warn(`Discord interaction-token delivery failed, falling back to channel message`,{error:e})}return e.sendViaChannel(e.body)}async function handleInteraction(e){return e.interaction.type===DISCORD_INTERACTION_TYPE.APPLICATION_COMMAND?handleCommandInteraction({config:e.config,interaction:e.interaction,onCommand:e.onCommand,send:e.send,waitUntil:e.waitUntil}):e.interaction.type===DISCORD_INTERACTION_TYPE.MESSAGE_COMPONENT?handleComponentInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil}):handleModalSubmitInteraction({interaction:e.interaction,send:e.send,waitUntil:e.waitUntil})}async function handleCommandInteraction(e){let t=stateFromInteraction(e.interaction,{conversationId:e.interaction.id,hasMessageAnchor:!1,initialResponseSent:!1}),n={discord:buildDiscordHandle({config:e.config,state:t})},r;try{r=await e.onCommand(n,e.interaction)}catch(e){return log.error(`command handler failed`,{error:e}),discordJson({content:`The Discord command handler failed.`,ephemeral:!0})}return r==null?discordJson({content:`Command ignored.`,ephemeral:!0}):(e.waitUntil(dispatchCommand({interaction:e.interaction,result:r,send:e.send,state:t})),discordDeferredJson(r.ephemeral===!0))}function handleComponentInteraction(e){if(isDiscordFreeformComponent(e.interaction.customId)){let t=readMessageContent(e.interaction.raw);return discordJsonBody(buildFreeformModalResponse({customId:e.interaction.customId,prompt:t}))}let t=deriveComponentInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId,inputResponses:t,interaction:e.interaction,send:e.send})),discordJsonBody({type:DISCORD_INTERACTION_RESPONSE_TYPE.DEFERRED_UPDATE_MESSAGE})}function handleModalSubmitInteraction(e){let t=deriveModalInputResponses(e.interaction);return t.length>0&&e.waitUntil(dispatchInputResponses({conversationId:e.interaction.messageId??e.interaction.id,inputResponses:t,interaction:e.interaction,send:e.send})),discordJson({content:`Answer received.`,ephemeral:!0})}async function dispatchCommand(e){let t=prependDiscordContext(commandInteractionMessage(e.interaction),{channelId:e.interaction.channelId,commandName:e.interaction.commandName,guildId:e.interaction.guildId,interactionId:e.interaction.id,userId:e.interaction.user.id,username:e.interaction.user.username});try{await e.send({message:t,modelContext:e.result.modelContext},{auth:e.result.auth,continuationToken:discordContinuationToken(e.interaction.channelId,e.interaction.id),state:e.state})}catch(e){log.error(`command delivery failed`,{error:e})}}async function dispatchInputResponses(e){try{await e.send({inputResponses:e.inputResponses},{auth:null,continuationToken:discordContinuationToken(e.interaction.channelId,e.conversationId),state:stateFromInteraction(e.interaction,{conversationId:e.conversationId,hasMessageAnchor:!0,initialResponseSent:!0})})}catch(e){log.error(`interaction response delivery failed`,{error:e})}}function stateFromInteraction(e,t){return{applicationId:e.applicationId,channelId:e.channelId,conversationId:t.conversationId,guildId:e.guildId??null,hasMessageAnchor:t.hasMessageAnchor,initialResponseSent:t.initialResponseSent,interactionToken:e.token}}function initialDiscordState(){return{applicationId:null,channelId:null,conversationId:null,guildId:null,hasMessageAnchor:!1,initialResponseSent:!1,interactionToken:null}}function mergeCredentials(e,t){return{applicationId:t.applicationId??e?.applicationId,botToken:e?.botToken,publicKey:e?.publicKey,webhookVerifier:e?.webhookVerifier}}function normalizePostInput(e){return typeof e==`string`?{content:e}:e}function expandPostBodies(e){return typeof e.content==`string`?splitDiscordMessageContent(e.content).map((t,n)=>n===0?{...e,content:t}:{allowed_mentions:e.allowed_mentions,content:t}):[e]}function readString(e){return typeof e==`string`&&e.length>0?e:void 0}export{discordChannel};
@@ -1 +1 @@
1
- export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelConfig, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
1
+ export { defineChannel, GET, POST, PUT, PATCH, DELETE, type Channel, type ChannelConfig, type ChannelSessionOps, type ChannelEvents, type Session, type SessionHandle, type RouteDefinition, type RouteHandlerArgs, type SendFn, type SendOptions, type SendPayload, type GetSessionFn, } from "#public/definitions/defineChannel.js";
@@ -1,4 +1,4 @@
1
- import{createLogger,extractErrorId,formatErrorHint}from"#internal/logging.js";import{truncateMessageText,truncateTypingStatus}from"#public/channels/slack/limits.js";import{buildAuthCompletedText,buildAuthEphemeralBlocks,buildAuthRequiredPublicText,formatConnectionDisplayName}from"#public/channels/slack/connections.js";import{renderInputRequestBlocks}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.defaults`);function defaultSlackAuth(e,t){let n=e.author;if(!n)return null;let r=e.teamId,i=n.isBot,a=n.userId,o=r?i?`slack:${r}:bot:${a}`:`slack:${r}:${a}`:i?`slack:bot:${a}`:`slack:${a}`,s={author_type:i?`bot`:`user`,channel_id:t.slack.channelId,thread_ts:t.slack.threadTs,user_id:a};return n.userName&&(s.user_name=n.userName),n.fullName&&(s.full_name=n.fullName),r!==void 0&&(s.team_id=r),{attributes:s,authenticator:`slack-webhook`,issuer:r===void 0?`slack`:`slack:${r}`,principalId:o,principalType:i?`service`:`user`}}async function defaultOnAppMention(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}async function defaultOnDirectMessage(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function defaultInputRequestedHandler(){return async(e,t)=>{if(e.requests.length===0)return;let n=truncateMessageText(e.requests.map(e=>e.prompt).join(`
2
- `));await t.thread.post({blocks:e.requests.flatMap(renderInputRequestBlocks),text:n})}}const defaultEvents={async"turn.started"(e,t){t.state.pendingToolCallMessage=null,await t.thread.startTyping(`Working...`)},async"actions.requested"(e,t){let n=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,n){await t.thread.startTyping(truncateTypingStatus(n));return}let r=e.actions.map(e=>e.kind===`tool-call`?e.toolName:e.kind);await t.thread.startTyping(truncateTypingStatus(`Running ${r.join(`, `)}...`))},async"message.completed"(e,t){if(e.finishReason===`tool-calls`){t.state.pendingToolCallMessage=e.message?firstNonEmptyLine(e.message)??null:null;return}t.state.pendingToolCallMessage=null,e.message&&await t.thread.post(e.message)},async"turn.failed"(e,r){let i=formatErrorHint(e),a=extractErrorId(e.details);await r.thread.post([`I hit an error while handling your request${i}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...a?[``,`_Error id: \`${a}\`_`]:[]].join(`
3
- `))},async"session.failed"(e,r){let i=formatErrorHint(e),a=extractErrorId(e.details);await r.thread.post([`This session couldn't recover from an error${i}.`,``,`Start a new thread to continue — I can't pick this one back up.`,...a?[``,`_Error id: \`${a}\`_`]:[]].join(`
4
- `))},async"authorization.required"(e,t){let n=formatConnectionDisplayName(e.name),r=t.state.triggeringUserId??null,i=e.authorization?.url;if(r&&i)try{await t.thread.postEphemeral(r,{blocks:buildAuthEphemeralBlocks({displayName:n,url:i}),text:`Sign in with ${n}: ${i}`})}catch(t){log.error(`Slack auth ephemeral delivery failed`,{name:e.name,error:t})}let a=buildAuthRequiredPublicText({displayName:n,hasUser:r!==null});try{let n=await t.thread.post(a);n.id&&(t.state.pendingAuthMessageTs={...t.state.pendingAuthMessageTs,[e.name]:n.id})}catch(t){log.error(`Slack auth public message delivery failed`,{name:e.name,error:t})}},async"authorization.completed"(e,t){let n=t.state.pendingAuthMessageTs??{},r=n[e.name];if(r===void 0)return;let i=buildAuthCompletedText({displayName:formatConnectionDisplayName(e.name),outcome:e.outcome,reason:e.reason});try{await t.slack.request(`chat.update`,{channel:t.slack.channelId,ts:r,text:i})}catch(t){log.error(`Slack auth status edit failed`,{name:e.name,error:t})}let o={...n};delete o[e.name],t.state.pendingAuthMessageTs=o}};export{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage,defaultSlackAuth};
1
+ import{createLogger,extractErrorId,formatErrorHint}from"#internal/logging.js";import{truncateMessageText,truncateTypingStatus}from"#public/channels/slack/limits.js";import{buildAuthCompletedText,buildAuthEphemeralBlocks,buildAuthRequiredPublicText,formatConnectionDisplayName}from"#public/channels/slack/connections.js";import{renderInputRequestBlocks}from"#public/channels/slack/hitl.js";const log=createLogger(`slack.defaults`);function defaultSlackAuth(e,t){let n=e.author;if(!n)return null;let r=e.teamId,i=n.isBot,a=n.userId,o=r?i?`slack:${r}:bot:${a}`:`slack:${r}:${a}`:i?`slack:bot:${a}`:`slack:${a}`,s={author_type:i?`bot`:`user`,channel_id:t.slack.channelId,thread_ts:t.slack.threadTs,user_id:a};return n.userName&&(s.user_name=n.userName),n.fullName&&(s.full_name=n.fullName),r!==void 0&&(s.team_id=r),{attributes:s,authenticator:`slack-webhook`,issuer:r===void 0?`slack`:`slack:${r}`,principalId:o,principalType:i?`service`:`user`}}async function defaultOnAppMention(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}async function defaultOnDirectMessage(e,t){return await e.thread.startTyping(`Thinking...`),{auth:defaultSlackAuth(t,e)}}function firstNonEmptyLine(e){for(let t of e.split(/\r?\n/u)){let e=t.trim();if(e.length>0)return e}}function defaultInputRequestedHandler(){return async(e,t,n)=>{if(e.requests.length===0)return;let i=truncateMessageText(e.requests.map(e=>e.prompt).join(`
2
+ `));await t.thread.post({blocks:e.requests.flatMap(renderInputRequestBlocks),text:i})}}const defaultEvents={async"turn.started"(e,t,n){t.state.pendingToolCallMessage=null,await t.thread.startTyping(`Working...`)},async"actions.requested"(e,t,n){let r=t.state.pendingToolCallMessage;if(t.state.pendingToolCallMessage=null,r){await t.thread.startTyping(truncateTypingStatus(r));return}let a=e.actions.map(e=>e.kind===`tool-call`?e.toolName:e.kind);await t.thread.startTyping(truncateTypingStatus(`Running ${a.join(`, `)}...`))},async"message.completed"(e,t,n){if(e.finishReason===`tool-calls`){t.state.pendingToolCallMessage=e.message?firstNonEmptyLine(e.message)??null:null;return}t.state.pendingToolCallMessage=null,e.message&&await t.thread.post(e.message)},async"turn.failed"(e,r,i){let a=formatErrorHint(e),o=extractErrorId(e.details);await r.thread.post([`I hit an error while handling your request${a}.`,``,`Please try again, rephrase, or reach out if it keeps failing.`,...o?[``,`_Error id: \`${o}\`_`]:[]].join(`
3
+ `))},async"session.failed"(e,r,i){let a=formatErrorHint(e),o=extractErrorId(e.details);await r.thread.post([`This session couldn't recover from an error${a}.`,``,`Start a new thread to continue — I can't pick this one back up.`,...o?[``,`_Error id: \`${o}\`_`]:[]].join(`
4
+ `))},async"authorization.required"(e,t,n){let r=formatConnectionDisplayName(e.name),i=t.state.triggeringUserId??null,a=e.authorization?.url;if(i&&a)try{await t.thread.postEphemeral(i,{blocks:buildAuthEphemeralBlocks({displayName:r,url:a}),text:`Sign in with ${r}: ${a}`})}catch(t){log.error(`Slack auth ephemeral delivery failed`,{name:e.name,error:t})}let c=buildAuthRequiredPublicText({displayName:r,hasUser:i!==null});try{let n=await t.thread.post(c);n.id&&(t.state.pendingAuthMessageTs={...t.state.pendingAuthMessageTs,[e.name]:n.id})}catch(t){log.error(`Slack auth public message delivery failed`,{name:e.name,error:t})}},async"authorization.completed"(e,t,n){let r=t.state.pendingAuthMessageTs??{},i=r[e.name];if(i===void 0)return;let o=buildAuthCompletedText({displayName:formatConnectionDisplayName(e.name),outcome:e.outcome,reason:e.reason});try{await t.slack.request(`chat.update`,{channel:t.slack.channelId,ts:i,text:o})}catch(t){log.error(`Slack auth status edit failed`,{name:e.name,error:t})}let s={...r};delete s[e.name],t.state.pendingAuthMessageTs=s}};export{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage,defaultSlackAuth};