experimental-ash 0.59.0 → 0.61.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 +12 -0
- package/dist/docs/public/advanced/auth-and-route-protection.mdx +8 -6
- package/dist/docs/public/agent-ts.md +17 -47
- package/dist/docs/public/channels/README.md +60 -0
- package/dist/docs/public/channels/ash.mdx +103 -0
- package/dist/docs/public/channels/custom.mdx +288 -0
- package/dist/docs/public/channels/discord.mdx +1 -3
- package/dist/docs/public/channels/github.md +1 -1
- package/dist/docs/public/channels/meta.json +3 -0
- package/dist/docs/public/channels/slack.mdx +29 -1
- package/dist/docs/public/channels/teams.mdx +1 -3
- package/dist/docs/public/channels/telegram.mdx +1 -3
- package/dist/docs/public/channels/twilio.mdx +1 -3
- package/dist/docs/public/frontend/nextjs.md +24 -8
- package/dist/docs/public/onboarding.md +4 -3
- package/dist/docs/public/schedules.mdx +4 -4
- package/dist/docs/public/tools.mdx +1 -1
- package/dist/src/channel/compiled-channel.d.ts +2 -6
- package/dist/src/channel/routes.d.ts +75 -7
- package/dist/src/channel/routes.js +1 -1
- package/dist/src/compiler/manifest.d.ts +4 -4
- package/dist/src/compiler/manifest.js +1 -1
- package/dist/src/execution/dispatch-code-mode-runtime-actions-step.d.ts +21 -0
- package/dist/src/execution/dispatch-code-mode-runtime-actions-step.js +1 -0
- package/dist/src/execution/dispatch-runtime-actions-step.js +1 -1
- package/dist/src/execution/next-driver-action.d.ts +5 -0
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/execution/turn-workflow.js +1 -1
- package/dist/src/execution/workflow-entry.js +1 -1
- package/dist/src/execution/workflow-steps.d.ts +5 -0
- package/dist/src/execution/workflow-steps.js +1 -1
- package/dist/src/harness/code-mode-runtime-action-state.d.ts +6 -0
- package/dist/src/harness/code-mode-runtime-action-state.js +1 -0
- package/dist/src/harness/code-mode.js +1 -1
- package/dist/src/harness/runtime-actions.d.ts +1 -0
- package/dist/src/harness/runtime-actions.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/host/channel-routes.d.ts +2 -2
- package/dist/src/internal/nitro/host/channel-routes.js +2 -1
- package/dist/src/internal/nitro/host/create-application-nitro.js +1 -1
- package/dist/src/internal/nitro/routes/channel-dispatch.d.ts +2 -0
- package/dist/src/internal/nitro/routes/channel-dispatch.js +1 -1
- package/dist/src/internal/vercel-agent-summary.d.ts +2 -2
- package/dist/src/internal/vercel-agent-summary.js +1 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -1
- package/dist/src/public/channels/index.js +1 -1
- package/dist/src/public/definitions/channel.d.ts +7 -0
- package/dist/src/public/definitions/defineChannel.d.ts +3 -6
- package/dist/src/public/definitions/defineChannel.js +1 -1
- package/dist/src/runtime/framework-channels/index.js +1 -1
- package/dist/src/runtime/resolve-channel.js +1 -1
- package/dist/src/runtime/types.d.ts +8 -3
- package/package.json +1 -1
- package/dist/docs/public/channels/attachments.md +0 -71
- package/dist/docs/public/channels/index.md +0 -661
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.61.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 2e3746f: Add WebSocket channel routes via `WS()`, including compiler manifest support, Nitro websocket mounting, and runtime dispatch for custom channel integrations.
|
|
8
|
+
|
|
9
|
+
## 0.60.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 72d63f2: Make runtime action tools (built-in `agent`, declared subagents, remote agents) callable from code mode. When the agent writes `await agent({ message: "..." })` in sandbox code, the QuickJS sandbox interrupts via the existing code mode interrupt mechanism, the harness parks with a `PendingRuntimeActionBatch` for child dispatch, and the sandbox replays with the child's result on resume.
|
|
14
|
+
|
|
3
15
|
## 0.59.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -4,7 +4,9 @@ description: "Protect agent routes with HTTP Basic, JWT, OIDC, and Vercel OIDC."
|
|
|
4
4
|
url: /auth-and-route-protection
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Ash protects its own HTTP routes through the channel layer.
|
|
7
|
+
Ash protects its own HTTP routes through the channel layer. The [Ash channel](/docs/channels/ash)
|
|
8
|
+
is enabled by default and exposes the session API used by local tooling, frontend clients, and other
|
|
9
|
+
HTTP API callers.
|
|
8
10
|
|
|
9
11
|
Route auth and IP policy are not configured in `agent.ts` anymore. They live on the HTTP channel
|
|
10
12
|
factories and helper functions instead.
|
|
@@ -30,8 +32,8 @@ These settings apply to:
|
|
|
30
32
|
## Generated Web Chat Auth
|
|
31
33
|
|
|
32
34
|
`pnpm create experimental-ash-agent` scaffolds `agent/channels/ash.ts` from the Web Chat example.
|
|
33
|
-
|
|
34
|
-
placeholder:
|
|
35
|
+
Creating this file overrides the default Ash channel settings. The generated version permits Vercel
|
|
36
|
+
OIDC and localhost requests and leaves end-user production auth as an explicit placeholder:
|
|
35
37
|
|
|
36
38
|
```ts
|
|
37
39
|
// agent/channels/ash.ts
|
|
@@ -250,6 +252,6 @@ export default ashChannel({
|
|
|
250
252
|
|
|
251
253
|
## What To Read Next
|
|
252
254
|
|
|
253
|
-
- [`agent.ts`](
|
|
254
|
-
- [Session Context](
|
|
255
|
-
- [Vercel Deployment](
|
|
255
|
+
- [`agent.ts`](/docs/agent-ts)
|
|
256
|
+
- [Session Context](/docs/session-context)
|
|
257
|
+
- [Vercel Deployment](/docs/vercel-deployment)
|
|
@@ -3,11 +3,11 @@ title: "agent.ts"
|
|
|
3
3
|
description: "Configure your agent with agent.ts: model, metadata, and runtime options."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`agent.ts`
|
|
6
|
+
`agent.ts` configures the runtime for an Ash agent: model, metadata, build options, and compaction.
|
|
7
|
+
For the agent's behavior and surfaces, see [instructions](./advanced/context-control.md),
|
|
8
|
+
[skills](./skills.md), [tools](./tools.mdx), and [channels](./channels/README.md).
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## The Main API
|
|
10
|
+
## Define An Agent
|
|
11
11
|
|
|
12
12
|
```ts
|
|
13
13
|
import { defineAgent } from "experimental-ash";
|
|
@@ -27,25 +27,6 @@ export default defineAgent({
|
|
|
27
27
|
});
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
-
## What Belongs Here
|
|
31
|
-
|
|
32
|
-
Use `agent.ts` for:
|
|
33
|
-
|
|
34
|
-
- model selection
|
|
35
|
-
- metadata you want preserved in runtime traces
|
|
36
|
-
- hosted-build packaging controls
|
|
37
|
-
- compaction settings
|
|
38
|
-
- provider-specific model options
|
|
39
|
-
|
|
40
|
-
Per-tool approval (formerly "human-in-the-loop policy") lives on each tool via `needsApproval`,
|
|
41
|
-
not in `agent.ts`. See [Human In The Loop](./human-in-the-loop.mdx).
|
|
42
|
-
|
|
43
|
-
For OpenTelemetry configuration, use `instrumentation.ts` instead. See
|
|
44
|
-
[`instrumentation.ts`](./instrumentation.md).
|
|
45
|
-
|
|
46
|
-
Do not use `agent.ts` for long-form instructions. Put those in `instructions.md` (or `instructions.ts`) and
|
|
47
|
-
`skills/`.
|
|
48
|
-
|
|
49
30
|
## `model`
|
|
50
31
|
|
|
51
32
|
Ash accepts either:
|
|
@@ -130,27 +111,7 @@ server output instead of being bundled.
|
|
|
130
111
|
- `model` optionally overrides the model used for compaction summaries
|
|
131
112
|
|
|
132
113
|
The shared per-run workspace is configured on the sandbox, not on `agent.ts`. See
|
|
133
|
-
[Workspace](./workspace.md) and [Sandboxes](./sandbox.md).
|
|
134
|
-
|
|
135
|
-
## Human In The Loop
|
|
136
|
-
|
|
137
|
-
Approval policy lives on individual tools via `needsApproval` in `defineTool({...})`, not on
|
|
138
|
-
`agent.ts`. See [Human In The Loop](./human-in-the-loop.mdx) for the runtime flow, `input.requested`
|
|
139
|
-
events, and HTTP response payloads.
|
|
140
|
-
|
|
141
|
-
## Telemetry
|
|
142
|
-
|
|
143
|
-
OpenTelemetry tracing is configured in `agent/instrumentation.ts`, not in `agent.ts`. See
|
|
144
|
-
[`instrumentation.ts`](./instrumentation.md) for setup.
|
|
145
|
-
|
|
146
|
-
## Route Auth And Network Policy
|
|
147
|
-
|
|
148
|
-
`agent.ts` does not define inbound auth or IP policy anymore.
|
|
149
|
-
|
|
150
|
-
Those concerns live on the HTTP channel layer instead. See:
|
|
151
|
-
|
|
152
|
-
- [Auth And Route Protection](./auth-and-route-protection.mdx)
|
|
153
|
-
- [Channels](./channels/README.md)
|
|
114
|
+
[Workspace](./advanced/workspace.md) and [Sandboxes](./sandbox.md).
|
|
154
115
|
|
|
155
116
|
## A Good Default
|
|
156
117
|
|
|
@@ -166,9 +127,18 @@ export default defineAgent({
|
|
|
166
127
|
|
|
167
128
|
That is enough to start. Add build and compaction settings only when you need them.
|
|
168
129
|
|
|
130
|
+
## Where Other Settings Live
|
|
131
|
+
|
|
132
|
+
A few adjacent concerns live in dedicated files:
|
|
133
|
+
|
|
134
|
+
- Long-form instructions → [`instructions.md`](./advanced/context-control.md)
|
|
135
|
+
- Per-tool approval → [Human In The Loop](./human-in-the-loop.mdx)
|
|
136
|
+
- OpenTelemetry → [`instrumentation.ts`](./advanced/instrumentation.md)
|
|
137
|
+
- Inbound auth → [Auth And Route Protection](./advanced/auth-and-route-protection.mdx)
|
|
138
|
+
|
|
169
139
|
## What To Read Next
|
|
170
140
|
|
|
171
|
-
- [Context Control](./context-control.md)
|
|
141
|
+
- [Context Control](./advanced/context-control.md)
|
|
172
142
|
- [Human In The Loop](./human-in-the-loop.mdx)
|
|
173
|
-
- [Workspace](./workspace.md)
|
|
174
|
-
- [Auth And Route Protection](./auth-and-route-protection.mdx)
|
|
143
|
+
- [Workspace](./advanced/workspace.md)
|
|
144
|
+
- [Auth And Route Protection](./advanced/auth-and-route-protection.mdx)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Overview"
|
|
3
|
+
description: "Deliver your agent over HTTP, Slack, Discord, GitHub, Twilio, Telegram, Microsoft Teams, and custom transports."
|
|
4
|
+
url: /channels
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Channels let Ash receive messages from external systems and deliver responses back to the same
|
|
8
|
+
conversation surface. Each channel owns the platform-specific delivery details, while the runtime and
|
|
9
|
+
harness still own model turns, tool execution, compaction, and session persistence.
|
|
10
|
+
|
|
11
|
+
Most apps start with the Ash channel for web and local clients, then add platform channels when the
|
|
12
|
+
agent should live inside a collaboration or messaging surface.
|
|
13
|
+
|
|
14
|
+
## Define a channel
|
|
15
|
+
|
|
16
|
+
Create a channel file under `agent/channels/` in the root agent:
|
|
17
|
+
|
|
18
|
+
```text
|
|
19
|
+
agent/
|
|
20
|
+
agent.ts
|
|
21
|
+
channels/
|
|
22
|
+
slack.ts
|
|
23
|
+
github.ts
|
|
24
|
+
internal-webhook.ts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The file stem becomes the channel id. For example, `agent/channels/internal-webhook.ts` is addressed
|
|
28
|
+
as `internal-webhook`.
|
|
29
|
+
|
|
30
|
+
## Built-in channels
|
|
31
|
+
|
|
32
|
+
- [Ash](/docs/channels/ash) - the default HTTP session protocol used by the terminal UI, Ash client
|
|
33
|
+
integrations, and deployed web frontends.
|
|
34
|
+
- [Discord](/docs/channels/discord) - Discord HTTP Interactions for slash commands, components, and modals.
|
|
35
|
+
- [Slack](/docs/channels/slack) - Slack app mentions, direct messages, message delivery, typing indicators,
|
|
36
|
+
and HITL interactions.
|
|
37
|
+
- [GitHub](/docs/channels/github) - GitHub App webhooks, PR context, issue and review comments,
|
|
38
|
+
and sandbox checkout.
|
|
39
|
+
- [Microsoft Teams](/docs/channels/teams) - Bot Framework Activity webhooks, Adaptive Cards, and Teams
|
|
40
|
+
replies.
|
|
41
|
+
- [Telegram](/docs/channels/telegram) - Telegram Bot API webhooks, replies, inline keyboards, and group
|
|
42
|
+
messages.
|
|
43
|
+
- [Twilio](/docs/channels/twilio) - inbound SMS and speech-transcribed phone calls.
|
|
44
|
+
|
|
45
|
+
## Need another surface?
|
|
46
|
+
|
|
47
|
+
Use [Custom channels](/docs/channels/custom) when you need to expose your own HTTP or WebSocket
|
|
48
|
+
endpoints, adapt an internal webhook, or connect a platform that does not have a first-class channel
|
|
49
|
+
yet.
|
|
50
|
+
|
|
51
|
+
## What to read next
|
|
52
|
+
|
|
53
|
+
- [Ash channel](/docs/channels/ash)
|
|
54
|
+
- [Slack channel](/docs/channels/slack)
|
|
55
|
+
- [GitHub channel](/docs/channels/github)
|
|
56
|
+
- [Telegram channel](/docs/channels/telegram)
|
|
57
|
+
- [Custom channels](/docs/channels/custom)
|
|
58
|
+
- [Project Layout](/docs/project-layout)
|
|
59
|
+
- [TypeScript API](/docs/typescript-api)
|
|
60
|
+
- [Auth And Route Protection](/docs/auth-and-route-protection)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Ash"
|
|
3
|
+
description: "Expose Ash's default HTTP API for local tools, frontends, and SDK clients."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The Ash channel exposes the framework's default HTTP API for an agent. Use it when something should
|
|
7
|
+
talk to your agent over HTTP: local tooling, a [frontend client](/docs/frontend), or another
|
|
8
|
+
API client that needs to start sessions, send user messages, and stream agent events.
|
|
9
|
+
|
|
10
|
+
`ashChannel()` mounts Ash's canonical session routes under `/ash/v1/session*`. These routes are
|
|
11
|
+
enabled by default, even when `agent/channels/ash.ts` does not exist.
|
|
12
|
+
|
|
13
|
+
## When to use it
|
|
14
|
+
|
|
15
|
+
- You want HTTP/API access to your agent.
|
|
16
|
+
- You are using the terminal UI.
|
|
17
|
+
- You are building a frontend that talks to the agent through [`useAshAgent()`](/docs/frontend/use-ash-agent)
|
|
18
|
+
or Ash's HTTP session API.
|
|
19
|
+
- You want to choose the route authentication policy for the default session API.
|
|
20
|
+
|
|
21
|
+
Most apps do not need much code here. Add `agent/channels/ash.ts` when you want to override the
|
|
22
|
+
default Ash channel settings:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { ashChannel } from "experimental-ash/channels/ash";
|
|
26
|
+
import { localDev, vercelOidc } from "experimental-ash/channels/auth";
|
|
27
|
+
|
|
28
|
+
export default ashChannel({
|
|
29
|
+
auth: [localDev(), vercelOidc()],
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Exposed routes
|
|
34
|
+
|
|
35
|
+
The Ash channel exposes the HTTP routes used to create sessions, send follow-up messages, and stream
|
|
36
|
+
events from a running session:
|
|
37
|
+
|
|
38
|
+
- `GET /ash/v1/health`
|
|
39
|
+
- `POST /ash/v1/session`
|
|
40
|
+
- `POST /ash/v1/session/:sessionId`
|
|
41
|
+
- `GET /ash/v1/session/:sessionId/stream`
|
|
42
|
+
|
|
43
|
+
See [Runs and Streaming](/docs/runs-and-streaming) for the route-level flow.
|
|
44
|
+
|
|
45
|
+
## Authentication
|
|
46
|
+
|
|
47
|
+
The `auth` option controls who can call the Ash HTTP routes. If a browser app, mobile app, or other
|
|
48
|
+
client connects directly to the Ash API, add your own production authentication.
|
|
49
|
+
|
|
50
|
+
The default helpers are meant for development and trusted infrastructure:
|
|
51
|
+
|
|
52
|
+
- `localDev()` accepts requests during local development.
|
|
53
|
+
- `vercelOidc()` lets the locally installed CLI talk to a deployed agent and lets other internal
|
|
54
|
+
Vercel deployments from your team call it.
|
|
55
|
+
|
|
56
|
+
Those defaults do not give browser users or external clients protected production access. For that,
|
|
57
|
+
wire the Ash channel to your app's auth system, such as Clerk, Auth.js, or your own OIDC/JWT
|
|
58
|
+
verification.
|
|
59
|
+
|
|
60
|
+
`pnpm create experimental-ash-agent` scaffolds an example `agent/channels/ash.ts` with a production
|
|
61
|
+
auth placeholder so you can replace it before exposing the API to real users.
|
|
62
|
+
|
|
63
|
+
For the full auth model and helper list, see
|
|
64
|
+
[Auth and Route Protection](/docs/auth-and-route-protection).
|
|
65
|
+
|
|
66
|
+
## Advanced customization
|
|
67
|
+
|
|
68
|
+
Use `onMessage` to add request-specific context before the agent sees the user message, and `events`
|
|
69
|
+
to observe stream events from sessions created through the Ash channel.
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { ashChannel, defaultAshAuth } from "experimental-ash/channels/ash";
|
|
73
|
+
import { localDev, vercelOidc } from "experimental-ash/channels/auth";
|
|
74
|
+
|
|
75
|
+
export default ashChannel({
|
|
76
|
+
auth: [localDev(), vercelOidc()],
|
|
77
|
+
onMessage(ctx, message) {
|
|
78
|
+
const callerId = ctx.ash.caller?.principalId ?? "anonymous";
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
auth: defaultAshAuth(ctx),
|
|
82
|
+
context: [`HTTP caller ${callerId} sent: ${message}`],
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
events: {
|
|
86
|
+
"message.completed"(event, channel, ctx) {
|
|
87
|
+
console.log("Ash response completed", {
|
|
88
|
+
continuationToken: channel.continuationToken,
|
|
89
|
+
message: event.message,
|
|
90
|
+
sessionId: ctx.session.id,
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
});
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Frontend clients
|
|
98
|
+
|
|
99
|
+
Frontend docs cover the client side of this API:
|
|
100
|
+
|
|
101
|
+
- [Frontend overview](/docs/frontend) explains the web integration options.
|
|
102
|
+
- [`useAshAgent`](/docs/frontend/use-ash-agent) shows the React hook that calls the Ash channel from
|
|
103
|
+
browser UI.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Custom channels"
|
|
3
|
+
description: "Author custom HTTP and WebSocket channels with routes, events, metadata, continuation tokens, and file uploads."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Custom channels are for surfaces Ash does not ship as first-class integrations. They let you expose
|
|
7
|
+
HTTP or WebSocket endpoints, parse incoming requests, start or resume sessions, observe runtime
|
|
8
|
+
events, and own delivery back to your platform.
|
|
9
|
+
|
|
10
|
+
## File location and identity
|
|
11
|
+
|
|
12
|
+
Custom channels live in `agent/channels/` at the root agent level. Local subagents do not declare
|
|
13
|
+
channels today.
|
|
14
|
+
|
|
15
|
+
The channel file stem becomes the channel id, so `agent/channels/internal-webhook.ts` is addressed as
|
|
16
|
+
`internal-webhook`. Channel files are module-backed; export the channel definition as the default
|
|
17
|
+
export.
|
|
18
|
+
|
|
19
|
+
## Main API
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
import { defineChannel, GET, POST } from "experimental-ash/channels";
|
|
23
|
+
|
|
24
|
+
export default defineChannel({
|
|
25
|
+
routes: [
|
|
26
|
+
POST("/message", async (req, { send }) => {
|
|
27
|
+
const body = await req.json();
|
|
28
|
+
const session = await send(body.message, {
|
|
29
|
+
auth: null,
|
|
30
|
+
continuationToken: body.token,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return Response.json({ sessionId: session.id });
|
|
34
|
+
}),
|
|
35
|
+
GET("/sessions/:sessionId/stream", async (_req, { getSession, params }) => {
|
|
36
|
+
const session = getSession(params.sessionId);
|
|
37
|
+
const stream = await session.getEventStream();
|
|
38
|
+
|
|
39
|
+
return new Response(stream, {
|
|
40
|
+
headers: { "content-type": "text/event-stream" },
|
|
41
|
+
});
|
|
42
|
+
}),
|
|
43
|
+
],
|
|
44
|
+
events: {
|
|
45
|
+
"message.completed"(event, channel, ctx) {
|
|
46
|
+
// deliver completed messages back to the surface that owns this channel
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`defineChannel({ routes, ... })` defines a channel. Routes are declared with `POST()` and `GET()`
|
|
53
|
+
helpers. Each route handler receives the raw `Request` and a helpers object:
|
|
54
|
+
|
|
55
|
+
- `send(message, { auth, continuationToken, state? })` - start or resume a session. Returns a
|
|
56
|
+
`Session`.
|
|
57
|
+
- `getSession(sessionId)` - look up an existing session. The returned `Session` exposes
|
|
58
|
+
`getEventStream({ startIndex? })` for streaming.
|
|
59
|
+
- `params` - route parameters extracted from the path pattern.
|
|
60
|
+
- `waitUntil(promise)` - extend the request lifetime for background work.
|
|
61
|
+
|
|
62
|
+
Event handlers like `"message.completed"` are declared under the `events` key on the config object.
|
|
63
|
+
They receive `(eventData, channel, ctx)` where `channel` carries platform handles and session
|
|
64
|
+
continuation operations, and `ctx` is the Ash `SessionContext`.
|
|
65
|
+
|
|
66
|
+
## WebSocket routes
|
|
67
|
+
|
|
68
|
+
Use `WS()` when a custom channel needs a WebSocket endpoint. The route handler runs once per upgrade
|
|
69
|
+
request and returns lifecycle hooks for that connection:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
import { defineChannel, WS } from "experimental-ash/channels";
|
|
73
|
+
|
|
74
|
+
export default defineChannel({
|
|
75
|
+
routes: [
|
|
76
|
+
WS("/voice/ws", async (_req, { send }) => ({
|
|
77
|
+
async message(_peer, message) {
|
|
78
|
+
await send(message.text(), {
|
|
79
|
+
auth: null,
|
|
80
|
+
continuationToken: "voice-demo",
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
})),
|
|
84
|
+
],
|
|
85
|
+
});
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`WS()` handlers receive the same helpers as HTTP route handlers: `send`, `getSession`, `receive`,
|
|
89
|
+
`params`, `waitUntil`, and `requestIp`. The returned hooks are Ash-owned structural types compatible
|
|
90
|
+
with Nitro/H3 websocket routing, including `upgrade`, `open`, `message`, `close`, and `error`.
|
|
91
|
+
|
|
92
|
+
## Cross-channel hand-off
|
|
93
|
+
|
|
94
|
+
Route handlers can start a session on a different channel via `args.receive(channel, ...)`. Use this
|
|
95
|
+
when an inbound request on one channel should pivot the conversation onto another, such as an
|
|
96
|
+
incident webhook that should open an investigation thread in Slack.
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { defineChannel, POST } from "experimental-ash/channels";
|
|
100
|
+
import slack from "./slack.js";
|
|
101
|
+
|
|
102
|
+
export default defineChannel({
|
|
103
|
+
routes: [
|
|
104
|
+
POST("/incident", async (req, args) => {
|
|
105
|
+
const incident = await req.json();
|
|
106
|
+
|
|
107
|
+
args.waitUntil(
|
|
108
|
+
args.receive(slack, {
|
|
109
|
+
message: `Investigate ${incident.reference}: ${incident.title}`,
|
|
110
|
+
target: { channelId: "C0123ABC" },
|
|
111
|
+
auth: {
|
|
112
|
+
authenticator: "incidentio",
|
|
113
|
+
principalType: "service",
|
|
114
|
+
principalId: incident.actor.id,
|
|
115
|
+
attributes: { reference: incident.reference, severity: incident.severity },
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return new Response("ok");
|
|
121
|
+
}),
|
|
122
|
+
],
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Semantics:
|
|
127
|
+
|
|
128
|
+
- The target channel's authored `receive(input, { send })` hook owns the continuation-token format
|
|
129
|
+
and initial state. Callers supply only `{ message, target, auth }`.
|
|
130
|
+
- `auth` flows through to `session.auth.initiator` so the target's event handlers and the agent's
|
|
131
|
+
tools can read who started the session.
|
|
132
|
+
- Calling `args.receive(...)` does not also start a session on the current channel. The inbound
|
|
133
|
+
channel's response is whatever the route handler returns explicitly.
|
|
134
|
+
- The first argument is the target channel module's default export. Import it directly from
|
|
135
|
+
`agent/channels/<name>.ts`. Identity is matched by reference.
|
|
136
|
+
|
|
137
|
+
## Channel metadata
|
|
138
|
+
|
|
139
|
+
Channels can project a subset of their adapter state as metadata available to instrumentation
|
|
140
|
+
resolvers, dynamic tool resolvers, and dynamic skill/instruction resolvers. Define a
|
|
141
|
+
`metadata(state)` function on the channel config:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
import { defineChannel, POST } from "experimental-ash/channels";
|
|
145
|
+
|
|
146
|
+
export default defineChannel({
|
|
147
|
+
state: {
|
|
148
|
+
topic: null as string | null,
|
|
149
|
+
contextMessages: [] as string[],
|
|
150
|
+
internalCounter: 0,
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
metadata(state) {
|
|
154
|
+
return {
|
|
155
|
+
topic: state.topic,
|
|
156
|
+
contextMessages: state.contextMessages,
|
|
157
|
+
};
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
routes: [
|
|
161
|
+
POST("/start", async (req, { send }) => {
|
|
162
|
+
const body = await req.json();
|
|
163
|
+
await send(body.message, {
|
|
164
|
+
auth: null,
|
|
165
|
+
continuationToken: body.token,
|
|
166
|
+
state: { topic: body.topic, contextMessages: body.context, internalCounter: 0 },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
return new Response("ok");
|
|
170
|
+
}),
|
|
171
|
+
],
|
|
172
|
+
events: {
|
|
173
|
+
"turn.started"(_event, ctx) {
|
|
174
|
+
ctx.state.internalCounter += 1;
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The projection is re-evaluated whenever adapter state changes after channel event handlers run.
|
|
181
|
+
Dynamic tool resolvers read it via `ctx.channel.metadata` and narrow it with `isChannel`. See
|
|
182
|
+
[Dynamic Tools - Channel Metadata](/docs/tools#channel-metadata) for the full consumption pattern.
|
|
183
|
+
|
|
184
|
+
When a parent agent dispatches a subagent, the framework forwards the parent's channel metadata
|
|
185
|
+
projection to the child. The same `metadata(state)` projector also serves instrumentation metadata
|
|
186
|
+
resolvers.
|
|
187
|
+
|
|
188
|
+
## Continuation tokens
|
|
189
|
+
|
|
190
|
+
Each call to `send(message, { auth, continuationToken, state? })` from a channel route addresses a
|
|
191
|
+
session by its channel-local raw token. The framework prepends the channel name, derived from the
|
|
192
|
+
file stem under `agent/channels/`, before handing the token to the runtime.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { slackContinuationToken } from "experimental-ash/channels/slack";
|
|
196
|
+
import { twilioContinuationToken } from "experimental-ash/channels/twilio";
|
|
197
|
+
|
|
198
|
+
slackContinuationToken("C0123ABC", "1800000000.001234"); // "C0123ABC:1800000000.001234"
|
|
199
|
+
twilioContinuationToken("+15551234567", "+15557654321"); // "+15551234567:+15557654321"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Custom channels write their own function that joins the identity fields. The framework does not
|
|
203
|
+
derive anything for you; the channel owns its token format.
|
|
204
|
+
|
|
205
|
+
When the identity that should address a session is not known until later, the channel can re-key the
|
|
206
|
+
parked session by calling `ctx.session.setContinuationToken(...)` from a handler. Pass the
|
|
207
|
+
channel-local raw token; the runtime preserves the current channel namespace.
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { defineChannel } from "experimental-ash/channels";
|
|
211
|
+
|
|
212
|
+
defineChannel<{ ref: string | null }>({
|
|
213
|
+
state: { ref: null },
|
|
214
|
+
context(state, session) {
|
|
215
|
+
return {
|
|
216
|
+
state,
|
|
217
|
+
registerAnchor(ref: string) {
|
|
218
|
+
state.ref = ref;
|
|
219
|
+
session.setContinuationToken(ref);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
},
|
|
223
|
+
events: {
|
|
224
|
+
"message.completed"(_event, ctx) {
|
|
225
|
+
if (!ctx.state.ref) ctx.registerAnchor(mintRef());
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
routes: [
|
|
229
|
+
/* ... */
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
The workflow runtime disposes the current park hook at the next step boundary and registers a new
|
|
235
|
+
one at the new token. Inbound deliveries already addressed to the old token are dropped, so
|
|
236
|
+
coordinate with your senders to use the new token.
|
|
237
|
+
|
|
238
|
+
## File uploads
|
|
239
|
+
|
|
240
|
+
`send()` accepts `string | UserContent`. To include file attachments, pass a `UserContent` array
|
|
241
|
+
mixing text and file parts:
|
|
242
|
+
|
|
243
|
+
```ts
|
|
244
|
+
await send(
|
|
245
|
+
[
|
|
246
|
+
{ type: "text", text: body.message },
|
|
247
|
+
{ type: "file", data: imageBytes, mediaType: "image/png" },
|
|
248
|
+
],
|
|
249
|
+
{ auth, continuationToken },
|
|
250
|
+
);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
For platforms like Slack where files are behind authenticated URLs, put a `URL` object in
|
|
254
|
+
`FilePart.data` and declare `fetchFile` on the channel config:
|
|
255
|
+
|
|
256
|
+
```ts
|
|
257
|
+
defineChannel({
|
|
258
|
+
fetchFile(url) {
|
|
259
|
+
if (!url.startsWith("https://files.slack.com/")) return null;
|
|
260
|
+
return fetch(url, { headers: { authorization: `Bearer ${token}` } })
|
|
261
|
+
.then((r) => r.arrayBuffer())
|
|
262
|
+
.then((b) => ({ bytes: Buffer.from(b) }));
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
routes: [
|
|
266
|
+
POST("/webhook", async (req, { send }) => {
|
|
267
|
+
await send(
|
|
268
|
+
[
|
|
269
|
+
{ type: "text", text: message.text },
|
|
270
|
+
...message.attachments.map((a) => ({
|
|
271
|
+
type: "file" as const,
|
|
272
|
+
data: new URL(a.url),
|
|
273
|
+
mediaType: a.mediaType,
|
|
274
|
+
})),
|
|
275
|
+
],
|
|
276
|
+
{ auth, continuationToken, state },
|
|
277
|
+
);
|
|
278
|
+
}),
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The `URL` object survives the queue boundary as a string and is reconstituted inside the workflow
|
|
284
|
+
step. The staging pipeline calls `fetchFile(url)`: return bytes to stage the file to the sandbox, or
|
|
285
|
+
return `null` to let the URL pass through to the model provider.
|
|
286
|
+
|
|
287
|
+
The framework handles staging bytes to the sandbox, enforcing upload policy, hydrating files for the
|
|
288
|
+
model call, and reconstituting `URL` objects after queue serialization.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Discord
|
|
2
|
+
title: "Discord"
|
|
3
3
|
description: "Create a Discord-backed Ash channel for HTTP Interactions."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Discord Channel Setup
|
|
7
|
-
|
|
8
6
|
The Discord channel accepts HTTP Interactions: slash/application commands, message components, and
|
|
9
7
|
modal submissions. It verifies Discord's Ed25519 signature headers before parsing the request,
|
|
10
8
|
acknowledges commands immediately, and continues Ash work in the background.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Slack
|
|
2
|
+
title: "Slack"
|
|
3
3
|
description: "Create a Slack-backed Ash channel with Vercel Connect."
|
|
4
4
|
type: integration
|
|
5
5
|
related:
|
|
@@ -255,6 +255,34 @@ export default slackChannel({
|
|
|
255
255
|
`context` strings are appended as user messages to `session.history` before the delivery message.
|
|
256
256
|
They persist across the session and are visible to the model on all subsequent turns.
|
|
257
257
|
|
|
258
|
+
### Thread Anchoring
|
|
259
|
+
|
|
260
|
+
When a Slack session starts without a `threadTs`, such as from `args.receive(slack, ...)` or a
|
|
261
|
+
schedule, the channel auto-anchors on the first agent post. That message becomes the thread root, and
|
|
262
|
+
subsequent posts, typing indicators, and inbound mentions in that thread resume the same Ash session.
|
|
263
|
+
|
|
264
|
+
Pass `initialMessage` when you want a structured anchor card to land before the agent runs:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
import { Card, CardText } from "experimental-ash/channels/slack";
|
|
268
|
+
|
|
269
|
+
await args.receive(slack, {
|
|
270
|
+
message: "Begin investigation",
|
|
271
|
+
target: {
|
|
272
|
+
channelId: "C0123ABC",
|
|
273
|
+
initialMessage: {
|
|
274
|
+
card: Card({ children: [CardText("Investigation Thread for INC-42")] }),
|
|
275
|
+
fallbackText: "Investigation Thread for INC-42",
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
auth,
|
|
279
|
+
});
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`threadTs` and `initialMessage` are mutually exclusive: pass `threadTs` to join an existing thread,
|
|
283
|
+
or `initialMessage` to anchor a new one. With neither, the first agent post anchors the thread
|
|
284
|
+
automatically.
|
|
285
|
+
|
|
258
286
|
### Direct Messages
|
|
259
287
|
|
|
260
288
|
Add `onDirectMessage` alongside `onAppMention` to handle 1:1 DMs:
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Microsoft Teams
|
|
2
|
+
title: "Microsoft Teams"
|
|
3
3
|
description: "Create a Microsoft Teams-backed Ash channel with the Bot Framework Activity protocol."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Microsoft Teams Channel Setup
|
|
7
|
-
|
|
8
6
|
The Teams channel accepts Bot Framework Activity POSTs from Microsoft Teams, verifies Bot
|
|
9
7
|
Connector bearer JWTs, dispatches message activities to Ash, renders HITL prompts as Adaptive
|
|
10
8
|
Cards, and delivers agent responses through the Bot Framework Connector REST API.
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
---
|
|
2
|
-
title: "Telegram
|
|
2
|
+
title: "Telegram"
|
|
3
3
|
description: "Create a Telegram-backed Ash channel for bot webhooks, replies, HITL, and attachments."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Telegram Channel Setup
|
|
7
|
-
|
|
8
6
|
The Telegram channel accepts Telegram Bot API webhooks. It verifies Telegram's
|
|
9
7
|
`X-Telegram-Bot-Api-Secret-Token` header before parsing the update, dispatches private messages and
|
|
10
8
|
addressed group messages, and sends default replies through `sendMessage`.
|