experimental-ash 0.58.0 → 0.58.1
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 +8 -0
- package/dist/docs/public/advanced/execution-model-and-security.md +319 -0
- package/dist/docs/public/advanced/meta.json +1 -0
- package/dist/src/harness/emission.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/node-esm-compat-banner.d.ts +11 -1
- package/dist/src/internal/node-esm-compat-banner.js +3 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +2 -0
- package/dist/src/public/channels/slack/slackChannel.js +1 -1
- package/dist/src/public/channels/slack/utils.d.ts +1 -0
- package/dist/src/public/channels/slack/utils.js +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.58.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7362a65: Keep Anthropic provider-executed server-tool results out of the local approval-resume repair so `srvtoolu_*` results stay provider-owned instead of replaying as generic tool results.
|
|
8
|
+
- f9ba08b: Provide sourcemaps when Ash injects the Node ESM compatibility banner into authored module bundles, suppressing Rolldown sourcemap warnings during agent startup.
|
|
9
|
+
- cac284c: Dedup inbound Slack events by `event_id` within a process so retried webhook deliveries no longer trigger the agent more than once.
|
|
10
|
+
|
|
3
11
|
## 0.58.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Execution Model & Security"
|
|
3
|
+
description: "Where your code runs, what has access to what, and how secrets flow through the system."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
Ash agents run across two execution contexts with a trust boundary between them.
|
|
7
|
+
|
|
8
|
+
## Execution Contexts
|
|
9
|
+
|
|
10
|
+
Your agent has two execution contexts: the **app runtime** and the **sandbox**.
|
|
11
|
+
|
|
12
|
+
The **app runtime** is where your agent code runs — tool implementations, model calls, MCP
|
|
13
|
+
connections, state management, observability, and durable execution. It has `process.env`, your
|
|
14
|
+
secrets, and full Node.js. On Vercel, this runs inside
|
|
15
|
+
[Vercel Functions](https://vercel.com/docs/functions).
|
|
16
|
+
|
|
17
|
+
The **sandbox** is an isolated environment. The model runs shell commands here via the built-in
|
|
18
|
+
`bash`, `read_file`, and `write_file` tools. It has its own filesystem (`/workspace`) but no access
|
|
19
|
+
to `process.env`, secrets, or the app runtime. On Vercel, each sandbox runs in a
|
|
20
|
+
[Vercel Sandbox](https://vercel.com/docs/sandbox) microVM with hardware-level isolation. See
|
|
21
|
+
[Sandboxes](../sandbox.md) for the full API.
|
|
22
|
+
|
|
23
|
+
| | App Runtime | Sandbox |
|
|
24
|
+
| ----------------------- | ------------ | --------------------- |
|
|
25
|
+
| `process.env` / secrets | Yes | No |
|
|
26
|
+
| Node.js / your code | Yes | No |
|
|
27
|
+
| Network | Unrestricted | Controlled by policy |
|
|
28
|
+
| Filesystem | App's own | Isolated `/workspace` |
|
|
29
|
+
|
|
30
|
+
Everything except sandbox shell commands runs in the app runtime.
|
|
31
|
+
|
|
32
|
+
## Durable Execution
|
|
33
|
+
|
|
34
|
+
Agent turns run inside [Vercel Workflow](https://vercel.com/docs/workflow):
|
|
35
|
+
|
|
36
|
+
- If the app runtime crashes mid-turn, Workflow resumes from the last step boundary.
|
|
37
|
+
- Durable state is serialized at step boundaries, so sessions survive across invocations.
|
|
38
|
+
- A session can span many requests over days or weeks without losing context.
|
|
39
|
+
|
|
40
|
+
Ash manages the Workflow lifecycle — you don't configure it directly. Sessions are durable by
|
|
41
|
+
default.
|
|
42
|
+
|
|
43
|
+
## The Agent Loop
|
|
44
|
+
|
|
45
|
+
Each turn follows the same cycle: model call, tool calls, results, another model call, until the
|
|
46
|
+
model produces a final response.
|
|
47
|
+
|
|
48
|
+
```mermaid
|
|
49
|
+
sequenceDiagram
|
|
50
|
+
participant M as Model
|
|
51
|
+
participant A as App
|
|
52
|
+
participant S as Sandbox
|
|
53
|
+
M->>A: your tool
|
|
54
|
+
A->>A: execute
|
|
55
|
+
A->>M: result
|
|
56
|
+
M->>A: bash
|
|
57
|
+
A->>S: proxy
|
|
58
|
+
S->>A: stdout
|
|
59
|
+
A->>M: result
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
All tools — including the built-in `bash`, `read_file`, and `write_file` — run in the app runtime.
|
|
63
|
+
The built-in tools proxy requests to the sandbox, where commands execute and files are read and
|
|
64
|
+
written, then return results back to the model. The model sees tool definitions and results — never
|
|
65
|
+
your secrets.
|
|
66
|
+
|
|
67
|
+
## Data Flow Examples
|
|
68
|
+
|
|
69
|
+
### `write_file` — app runtime proxies into sandbox
|
|
70
|
+
|
|
71
|
+
The model writes a file. The tool runs in the app runtime, which authenticates to the sandbox
|
|
72
|
+
automatically via Vercel OIDC and proxies the write.
|
|
73
|
+
|
|
74
|
+
```mermaid
|
|
75
|
+
sequenceDiagram
|
|
76
|
+
participant M as Model
|
|
77
|
+
participant A as App
|
|
78
|
+
participant S as Sandbox
|
|
79
|
+
|
|
80
|
+
M->>A: write_file
|
|
81
|
+
A->>A: validate path
|
|
82
|
+
A->>S: write via OIDC
|
|
83
|
+
S->>S: /workspace
|
|
84
|
+
S->>A: ok
|
|
85
|
+
A->>M: result
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Custom tool — runs in the app runtime
|
|
89
|
+
|
|
90
|
+
Custom tools run in the app runtime as regular JavaScript. They can call external APIs using secrets
|
|
91
|
+
from `process.env`, interact with the sandbox via `ctx.getSandbox()`, or both.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// agent/tools/get_deployments.ts
|
|
95
|
+
import { defineTool } from "experimental-ash/tools";
|
|
96
|
+
import { z } from "zod";
|
|
97
|
+
|
|
98
|
+
export default defineTool({
|
|
99
|
+
description: "List recent deployments for a project.",
|
|
100
|
+
inputSchema: z.object({
|
|
101
|
+
projectId: z.string(),
|
|
102
|
+
}),
|
|
103
|
+
async execute(input) {
|
|
104
|
+
const res = await fetch(`https://api.vercel.com/v6/deployments?projectId=${input.projectId}`, {
|
|
105
|
+
headers: { authorization: `Bearer ${process.env.VERCEL_API_TOKEN}` },
|
|
106
|
+
});
|
|
107
|
+
const { deployments } = await res.json();
|
|
108
|
+
return deployments.map((d: any) => ({
|
|
109
|
+
id: d.uid,
|
|
110
|
+
url: d.url,
|
|
111
|
+
state: d.state,
|
|
112
|
+
}));
|
|
113
|
+
},
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
```mermaid
|
|
118
|
+
sequenceDiagram
|
|
119
|
+
participant M as Model
|
|
120
|
+
participant A as App
|
|
121
|
+
participant API as Vercel API
|
|
122
|
+
|
|
123
|
+
M->>A: get_deployments
|
|
124
|
+
A->>A: read env token
|
|
125
|
+
A->>API: GET with Bearer
|
|
126
|
+
API->>A: deployments
|
|
127
|
+
A->>M: results
|
|
128
|
+
|
|
129
|
+
note over M,A: Model never sees the token
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This is the typical pattern for most integrations. Your tool has full access to secrets and returns
|
|
133
|
+
only what the model needs.
|
|
134
|
+
|
|
135
|
+
### MCP connection — framework manages auth
|
|
136
|
+
|
|
137
|
+
[MCP connections](../connections.mdx) expose third-party tools without writing executor code. The
|
|
138
|
+
framework handles discovery and injects authentication headers automatically.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
// agent/connections/linear.ts
|
|
142
|
+
import { defineMcpClientConnection } from "experimental-ash/connections";
|
|
143
|
+
|
|
144
|
+
export default defineMcpClientConnection({
|
|
145
|
+
url: "https://mcp.linear.app/sse",
|
|
146
|
+
description: "Linear workspace — issues, projects, cycles, and comments.",
|
|
147
|
+
auth: {
|
|
148
|
+
getToken: async () => ({ token: process.env.LINEAR_API_TOKEN! }),
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
```mermaid
|
|
154
|
+
sequenceDiagram
|
|
155
|
+
participant M as Model
|
|
156
|
+
participant A as App
|
|
157
|
+
participant MCP as MCP Server
|
|
158
|
+
|
|
159
|
+
M->>A: connection_search
|
|
160
|
+
A->>A: resolve token
|
|
161
|
+
A->>MCP: listTools + Bearer
|
|
162
|
+
MCP->>A: available tools
|
|
163
|
+
A->>M: tools
|
|
164
|
+
|
|
165
|
+
M->>A: list_issues
|
|
166
|
+
A->>MCP: tools/call + Bearer
|
|
167
|
+
MCP->>A: issues
|
|
168
|
+
A->>M: issues
|
|
169
|
+
|
|
170
|
+
note over A: Token cached per-step
|
|
171
|
+
note over M,A: Model never sees the token
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The model discovers tools via `connection_search` and calls them by name. The framework resolves the
|
|
175
|
+
token from `getToken()`, caches it per-step (never serialized to durable state), and injects it into
|
|
176
|
+
every MCP request.
|
|
177
|
+
|
|
178
|
+
### OAuth via Vercel Connect — interactive auth
|
|
179
|
+
|
|
180
|
+
When a connection needs user authorization, Ash suspends the turn and orchestrates an OAuth flow via
|
|
181
|
+
[Vercel Connect](https://vercel.com/docs/connect).
|
|
182
|
+
|
|
183
|
+
```ts
|
|
184
|
+
// agent/connections/linear.ts
|
|
185
|
+
import { connect } from "@vercel/connect/ash";
|
|
186
|
+
import { defineMcpClientConnection } from "experimental-ash/connections";
|
|
187
|
+
|
|
188
|
+
export default defineMcpClientConnection({
|
|
189
|
+
url: "https://mcp.linear.app/sse",
|
|
190
|
+
description: "Linear workspace — issues, projects, cycles, and comments.",
|
|
191
|
+
auth: connect("linear"),
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```mermaid
|
|
196
|
+
sequenceDiagram
|
|
197
|
+
participant M as Model
|
|
198
|
+
participant A as App
|
|
199
|
+
participant U as User
|
|
200
|
+
participant O as OAuth
|
|
201
|
+
participant MCP as MCP Server
|
|
202
|
+
|
|
203
|
+
M->>A: list_issues
|
|
204
|
+
A->>MCP: tools/call
|
|
205
|
+
MCP->>A: 401
|
|
206
|
+
|
|
207
|
+
A->>A: start auth
|
|
208
|
+
A->>M: auth required
|
|
209
|
+
note over M,A: Turn suspends
|
|
210
|
+
|
|
211
|
+
U->>O: grant access
|
|
212
|
+
O->>A: callback
|
|
213
|
+
A->>A: complete auth
|
|
214
|
+
|
|
215
|
+
note over M,A: Turn resumes
|
|
216
|
+
|
|
217
|
+
A->>MCP: tools/call + Bearer
|
|
218
|
+
MCP->>A: issues
|
|
219
|
+
A->>M: issues
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
The first call returns 401. Ash starts the OAuth flow and suspends the turn. After the user
|
|
223
|
+
authorizes, the OAuth provider redirects back, Ash caches the token, and the tool re-executes. The
|
|
224
|
+
token is never serialized to durable state and never visible to the model.
|
|
225
|
+
|
|
226
|
+
## Sandbox Security
|
|
227
|
+
|
|
228
|
+
The sandbox is isolated by default:
|
|
229
|
+
|
|
230
|
+
| Property | Behavior |
|
|
231
|
+
| -------------- | ---------------------------------------------------------------------------------------- |
|
|
232
|
+
| **Filesystem** | Isolated `/workspace`. Seeded from `agent/sandbox/workspace/`, persists across requests. |
|
|
233
|
+
| **Secrets** | No `process.env`, no app runtime access. Secrets cannot leak to the sandbox. |
|
|
234
|
+
| **Network** | Controlled by policy: `"allow-all"` (default), `"deny-all"`, or per-domain allow-list. |
|
|
235
|
+
|
|
236
|
+
When the model needs authenticated network access from inside the sandbox — for example,
|
|
237
|
+
`git clone` on a private repo — and you can't route the request through a tool or connection, use
|
|
238
|
+
credential brokering.
|
|
239
|
+
|
|
240
|
+
### Credential Brokering
|
|
241
|
+
|
|
242
|
+
When using [Vercel Sandbox](https://vercel.com/docs/sandbox) as your sandbox backend, credential
|
|
243
|
+
brokering injects auth headers at the sandbox's network firewall. The secret stays in the app
|
|
244
|
+
runtime — the sandbox process never sees it.
|
|
245
|
+
|
|
246
|
+
```mermaid
|
|
247
|
+
sequenceDiagram
|
|
248
|
+
participant M as Model
|
|
249
|
+
participant A as App
|
|
250
|
+
participant S as Sandbox
|
|
251
|
+
participant GH as github.com
|
|
252
|
+
|
|
253
|
+
M->>A: bash git clone
|
|
254
|
+
A->>S: run command
|
|
255
|
+
S->>S: egress to github.com
|
|
256
|
+
note over S: Firewall injects auth header
|
|
257
|
+
S->>GH: request + auth
|
|
258
|
+
GH->>S: response
|
|
259
|
+
S->>A: stdout
|
|
260
|
+
A->>M: stdout
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Configure it in the sandbox `onSession` hook. The `transform` injects headers for matching domains
|
|
264
|
+
at the firewall — the sandbox process only sees the response, never the credential:
|
|
265
|
+
|
|
266
|
+
```ts
|
|
267
|
+
// agent/sandbox/sandbox.ts
|
|
268
|
+
import { defineSandbox } from "experimental-ash/sandbox";
|
|
269
|
+
|
|
270
|
+
export default defineSandbox({
|
|
271
|
+
async onSession({ use }) {
|
|
272
|
+
const ghToken = process.env.GITHUB_TOKEN;
|
|
273
|
+
await use({
|
|
274
|
+
networkPolicy: {
|
|
275
|
+
allow: {
|
|
276
|
+
"github.com": [
|
|
277
|
+
{
|
|
278
|
+
transform: [
|
|
279
|
+
{ headers: { authorization: `Basic ${btoa(`x-access-token:${ghToken}`)}` } },
|
|
280
|
+
],
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
"*": [],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
The `"*": []` entry keeps general egress open. The sandbox can `git clone` private repos and `curl`
|
|
292
|
+
the GitHub API without ever seeing the token.
|
|
293
|
+
|
|
294
|
+
When the credential is resolved mid-turn, use `setNetworkPolicy` on the live sandbox handle:
|
|
295
|
+
|
|
296
|
+
```ts
|
|
297
|
+
const sandbox = await ctx.getSandbox();
|
|
298
|
+
await sandbox.setNetworkPolicy({
|
|
299
|
+
allow: {
|
|
300
|
+
"github.com": [
|
|
301
|
+
{
|
|
302
|
+
transform: [{ headers: { authorization: `Basic ${btoa(`x-access-token:${token}`)}` } }],
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
"*": [],
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
See [Sandboxes — Network Policies](../sandbox.md#network-policies) for the full Ash policy API and
|
|
311
|
+
[Vercel Sandbox — Credential Brokering](https://vercel.com/docs/sandbox/concepts/firewall#credentials-brokering)
|
|
312
|
+
for the underlying platform mechanism.
|
|
313
|
+
|
|
314
|
+
## What To Read Next
|
|
315
|
+
|
|
316
|
+
- [Tools](../tools.mdx) — defining typed tools that run in the app runtime.
|
|
317
|
+
- [Connections](../connections.mdx) — MCP server connections with static tokens or OAuth.
|
|
318
|
+
- [Sandboxes](../sandbox.md) — lifecycle, backends, and network policies.
|
|
319
|
+
- [Sessions And Streaming](./runs-and-streaming.md) — the HTTP API and session lifecycle.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createActionResultEvent,createMessageAppendedEvent,createMessageCompletedEvent,createMessageReceivedEvent,createReasoningAppendedEvent,createReasoningCompletedEvent,createSessionCompletedEvent,createSessionFailedEvent,createSessionStartedEvent,createSessionWaitingEvent,createStepFailedEvent,createStepStartedEvent,createTurnCompletedEvent,createTurnFailedEvent,createTurnStartedEvent}from"#protocol/message.js";import{toError}from"#shared/errors.js";import{createRuntimeToolResultFromStepResult}from"#harness/action-result-helpers.js";const HARNESS_EMISSION_STATE_KEY=`ash.harness.emission`,DEFAULT_EMISSION_STATE={sessionStarted:!1,sequence:0,stepIndex:0,turnId:``};function getHarnessEmissionState(e){return e?.[HARNESS_EMISSION_STATE_KEY]??DEFAULT_EMISSION_STATE}function isHarnessBetweenTurns(e){return getHarnessEmissionState(e.state).turnId===``}function setHarnessEmissionState(e,t){return{...e,state:{...e.state,[HARNESS_EMISSION_STATE_KEY]:t}}}async function emitTurnPreamble(e,t,n,i){let a=`turn_${n.sequence}`;return n.sessionStarted||await e(createSessionStartedEvent({runtime:i})),await e(createTurnStartedEvent({sequence:n.sequence,turnId:a})),t.message!==void 0&&await e(createMessageReceivedEvent({message:t.message,sequence:n.sequence,turnId:a})),{sessionStarted:!0,sequence:n.sequence,stepIndex:0,turnId:a}}async function emitStepStarted(e,t,n){await e(createStepStartedEvent({sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId}),n)}async function emitStepAndTurnFailed(e,t,n){await e(createStepFailedEvent({...n,sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId})),await e(createTurnFailedEvent({...n,sequence:t.sequence,turnId:t.turnId}))}async function emitFailedStep(e,t,n){await emitStepAndTurnFailed(e,t,n),await e(createSessionFailedEvent(n))}async function emitRecoverableFailedTurn(e,t,n){return await emitStepAndTurnFailed(e,t,n),await e(createSessionWaitingEvent()),{sessionStarted:t.sessionStarted,sequence:t.sequence+1,stepIndex:0,turnId:``}}function advanceStep(e){return{...e,stepIndex:e.stepIndex+1}}async function emitTurnEpilogue(e,t,n){return await e(createTurnCompletedEvent({sequence:t.sequence,turnId:t.turnId})),n===`conversation`?await e(createSessionWaitingEvent()):await e(createSessionCompletedEvent()),{sessionStarted:t.sessionStarted,sequence:t.sequence+1,stepIndex:0,turnId:``}}function normalizeAssistantStepFinishReason(e){switch(e){case`content-filter`:case`error`:case`length`:case`stop`:case`tool-calls`:return e;default:return`other`}}async function emitStreamContent(r,o,s){let c=``,l=``,u=`stop`,d,f=new Set,p=new Set,m=[],flushCurrentMessage=async()=>{l.length!==0&&(await r(createMessageCompletedEvent({finishReason:`tool-calls`,message:l,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),l=``)};for await(let n of s)if(d===void 0)switch(n.type){case`reasoning-delta`:c+=n.text,await r(createReasoningAppendedEvent({reasoningDelta:n.text,reasoningSoFar:c,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId}));break;case`text-delta`:c.trim().length>0&&(await r(createReasoningCompletedEvent({reasoning:c,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),c=``),l+=n.text,await r(createMessageAppendedEvent({messageDelta:n.text,messageSoFar:l,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId}));break;case`tool-call`:f.add(n.toolCallId);break;case`tool-result`:{if(f.has(n.toolCallId))break;await flushCurrentMessage()
|
|
1
|
+
import{createActionResultEvent,createMessageAppendedEvent,createMessageCompletedEvent,createMessageReceivedEvent,createReasoningAppendedEvent,createReasoningCompletedEvent,createSessionCompletedEvent,createSessionFailedEvent,createSessionStartedEvent,createSessionWaitingEvent,createStepFailedEvent,createStepStartedEvent,createTurnCompletedEvent,createTurnFailedEvent,createTurnStartedEvent}from"#protocol/message.js";import{toError}from"#shared/errors.js";import{createRuntimeToolResultFromStepResult}from"#harness/action-result-helpers.js";const HARNESS_EMISSION_STATE_KEY=`ash.harness.emission`,DEFAULT_EMISSION_STATE={sessionStarted:!1,sequence:0,stepIndex:0,turnId:``};function getHarnessEmissionState(e){return e?.[HARNESS_EMISSION_STATE_KEY]??DEFAULT_EMISSION_STATE}function isHarnessBetweenTurns(e){return getHarnessEmissionState(e.state).turnId===``}function setHarnessEmissionState(e,t){return{...e,state:{...e.state,[HARNESS_EMISSION_STATE_KEY]:t}}}async function emitTurnPreamble(e,t,n,i){let a=`turn_${n.sequence}`;return n.sessionStarted||await e(createSessionStartedEvent({runtime:i})),await e(createTurnStartedEvent({sequence:n.sequence,turnId:a})),t.message!==void 0&&await e(createMessageReceivedEvent({message:t.message,sequence:n.sequence,turnId:a})),{sessionStarted:!0,sequence:n.sequence,stepIndex:0,turnId:a}}async function emitStepStarted(e,t,n){await e(createStepStartedEvent({sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId}),n)}async function emitStepAndTurnFailed(e,t,n){await e(createStepFailedEvent({...n,sequence:t.sequence,stepIndex:t.stepIndex,turnId:t.turnId})),await e(createTurnFailedEvent({...n,sequence:t.sequence,turnId:t.turnId}))}async function emitFailedStep(e,t,n){await emitStepAndTurnFailed(e,t,n),await e(createSessionFailedEvent(n))}async function emitRecoverableFailedTurn(e,t,n){return await emitStepAndTurnFailed(e,t,n),await e(createSessionWaitingEvent()),{sessionStarted:t.sessionStarted,sequence:t.sequence+1,stepIndex:0,turnId:``}}function advanceStep(e){return{...e,stepIndex:e.stepIndex+1}}async function emitTurnEpilogue(e,t,n){return await e(createTurnCompletedEvent({sequence:t.sequence,turnId:t.turnId})),n===`conversation`?await e(createSessionWaitingEvent()):await e(createSessionCompletedEvent()),{sessionStarted:t.sessionStarted,sequence:t.sequence+1,stepIndex:0,turnId:``}}function normalizeAssistantStepFinishReason(e){switch(e){case`content-filter`:case`error`:case`length`:case`stop`:case`tool-calls`:return e;default:return`other`}}async function emitStreamContent(r,o,s){let c=``,l=``,u=`stop`,d,f=new Set,p=new Set,m=[],flushCurrentMessage=async()=>{l.length!==0&&(await r(createMessageCompletedEvent({finishReason:`tool-calls`,message:l,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),l=``)};for await(let n of s)if(d===void 0)switch(n.type){case`reasoning-delta`:c+=n.text,await r(createReasoningAppendedEvent({reasoningDelta:n.text,reasoningSoFar:c,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId}));break;case`text-delta`:c.trim().length>0&&(await r(createReasoningCompletedEvent({reasoning:c,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),c=``),l+=n.text,await r(createMessageAppendedEvent({messageDelta:n.text,messageSoFar:l,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId}));break;case`tool-call`:f.add(n.toolCallId);break;case`tool-result`:{let t=n;if(t.providerExecuted===!0||f.has(n.toolCallId))break;await flushCurrentMessage(),await r(createActionResultEvent({result:createRuntimeToolResultFromStepResult(t),sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),p.add(n.toolCallId);let i=t.output;m.push({type:`tool-result`,toolCallId:t.toolCallId,toolName:t.toolName,output:typeof i==`string`?{type:`text`,value:i}:{type:`json`,value:i??null}});break}case`finish-step`:u=normalizeAssistantStepFinishReason(n.finishReason);break;case`error`:d=toError(n.error);break;default:break}if(d!==void 0)throw d;return c.trim().length>0&&await r(createReasoningCompletedEvent({reasoning:c,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),l.length>0&&await r(createMessageCompletedEvent({finishReason:u,message:l,sequence:o.sequence,stepIndex:o.stepIndex,turnId:o.turnId})),{inlineActionResultCallIds:p,inlineToolResultParts:m}}export{advanceStep,emitFailedStep,emitRecoverableFailedTurn,emitStepStarted,emitStreamContent,emitTurnEpilogue,emitTurnPreamble,getHarnessEmissionState,isHarnessBetweenTurns,normalizeAssistantStepFinishReason,setHarnessEmissionState};
|
|
@@ -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.58.
|
|
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.58.1`}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};
|
|
@@ -23,10 +23,20 @@ interface NodeEsmCompatBannerOptions {
|
|
|
23
23
|
export declare function buildNodeEsmCompatBanner(code: string, options?: NodeEsmCompatBannerOptions): string;
|
|
24
24
|
interface BannerPlugin {
|
|
25
25
|
readonly name: string;
|
|
26
|
-
renderChunk(code: string
|
|
26
|
+
renderChunk(code: string, chunk?: {
|
|
27
|
+
readonly fileName?: string;
|
|
28
|
+
}): {
|
|
27
29
|
code: string;
|
|
30
|
+
map: SourceMap;
|
|
28
31
|
} | null;
|
|
29
32
|
}
|
|
33
|
+
interface SourceMap {
|
|
34
|
+
readonly version: 3;
|
|
35
|
+
readonly sources: readonly string[];
|
|
36
|
+
readonly sourcesContent: readonly string[];
|
|
37
|
+
readonly names: readonly string[];
|
|
38
|
+
readonly mappings: string;
|
|
39
|
+
}
|
|
30
40
|
/**
|
|
31
41
|
* Creates a bundler plugin that prepends the Node ESM compatibility
|
|
32
42
|
* banner to each output chunk, skipping any banner line whose binding
|
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
const BANNER_LINES=[{importLine:`import { fileURLToPath as __ashFileURLToPath } from "node:url";`,declarationLine:`const __filename = __ashFileURLToPath(import.meta.url);`,bindingPattern:/^(?:const|let|var)\s+__filename\b/m},{importLine:`import { dirname as __ashDirname } from "node:path";`,declarationLine:`const __dirname = __ashDirname(__filename);`,bindingPattern:/^(?:const|let|var)\s+__dirname\b/m}],REQUIRE_LINE={importLine:`import { createRequire as __ashCreateRequire } from "node:module";`,declarationLine:`const require = __ashCreateRequire(import.meta.url);`,bindingPattern:/^(?:const|let|var)\s+require\b/m};function buildNodeEsmCompatBanner(n,r={}){let i=[...BANNER_LINES];r.includeRequire===!0&&i.push(REQUIRE_LINE);let a=[],o=[];for(let e of i)e.bindingPattern.test(n)||(a.push(e.importLine),o.push(e.declarationLine));return o.length===0?``:[...a,...o].join(`
|
|
2
|
-
`)}function createNodeEsmCompatBannerPlugin(e={}){return{name:`ash-node-esm-compat-banner`,renderChunk(t){let
|
|
2
|
+
`)}function createNodeEsmCompatBannerPlugin(e={}){return{name:`ash-node-esm-compat-banner`,renderChunk(t,n){let r=buildNodeEsmCompatBanner(t,e);return r===``?null:{code:`${r}\n${t}`,map:createPrependedLineSourceMap({insertedLineCount:r.split(`
|
|
3
|
+
`).length,source:n?.fileName??`ash-node-esm-compat-banner-input`,sourceContent:t})}}}}function createPrependedLineSourceMap({insertedLineCount:e,source:t,sourceContent:n}){let r=n.split(`
|
|
4
|
+
`).length,i=Array.from({length:r},(e,t)=>encodeVlqFields(t===0?[0,0,0,0]:[0,0,1,0]));return{version:3,sources:[t],sourcesContent:[n],names:[],mappings:`${`;`.repeat(e)}${i.join(`;`)}`}}function encodeVlqFields(e){return e.map(e=>encodeVlqInteger(e)).join(``)}function encodeVlqInteger(e){let t=e<0?(-e<<1)+1:e<<1,n=``;do{let e=t&31;t>>>=5,t>0&&(e|=32),n+=`ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/`[e]}while(t>0);return n}export{buildNodeEsmCompatBanner,createNodeEsmCompatBannerPlugin};
|
|
@@ -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.58.
|
|
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.58.1`,aiPackageVersion:e?.aiPackageVersion??`7.0.0-canary.159`,nextPackageVersion:e?.nextPackageVersion??`16.2.6`,reactPackageVersion:e?.reactPackageVersion??`19.2.6`,reactDomPackageVersion:e?.reactDomPackageVersion??`19.2.6`,streamdownPackageVersion:e?.streamdownPackageVersion??`2.5.0`,zodPackageVersion:e?.zodPackageVersion??`4.4.3`,tsgoPackageVersion:e?.tsgoPackageVersion??`7.0.0-dev.20260523.1`,typesNodePackageVersion:e?.typesNodePackageVersion??`25.9.1`,typesReactPackageVersion:e?.typesReactPackageVersion??`19.2.15`,typesReactDomPackageVersion:e?.typesReactDomPackageVersion??`19.2.3`}}function formatAshDependencySpecifier(e){return/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/.test(e)?`^${e}`:e}async function patchWebPackageJson(e,t){if(!await pathExists(e))return[];assertStampedVersion(`ashPackageVersion`,t.ashPackageVersion),assertStampedVersion(`aiPackageVersion`,t.aiPackageVersion),assertStampedVersion(`nextPackageVersion`,t.nextPackageVersion),assertStampedVersion(`reactPackageVersion`,t.reactPackageVersion),assertStampedVersion(`reactDomPackageVersion`,t.reactDomPackageVersion),assertStampedVersion(`streamdownPackageVersion`,t.streamdownPackageVersion),assertStampedVersion(`zodPackageVersion`,t.zodPackageVersion),assertStampedVersion(`tsgoPackageVersion`,t.tsgoPackageVersion),assertStampedVersion(`typesNodePackageVersion`,t.typesNodePackageVersion),assertStampedVersion(`typesReactPackageVersion`,t.typesReactPackageVersion),assertStampedVersion(`typesReactDomPackageVersion`,t.typesReactDomPackageVersion);let r={...WEB_APP_TEMPLATE_PACKAGE_JSON.dependencies,ai:t.aiPackageVersion,"experimental-ash":formatAshDependencySpecifier(t.ashPackageVersion),next:t.nextPackageVersion,react:t.reactPackageVersion,"react-dom":t.reactDomPackageVersion,streamdown:t.streamdownPackageVersion,zod:t.zodPackageVersion},i={...WEB_APP_TEMPLATE_PACKAGE_JSON.devDependencies,"@types/node":t.typesNodePackageVersion,"@types/react":t.typesReactPackageVersion,"@types/react-dom":t.typesReactDomPackageVersion,"@typescript/native-preview":t.tsgoPackageVersion},a=WEB_APP_TEMPLATE_PACKAGE_JSON.scripts;return await patchPackageJson(e,{dependencies:r,devDependencies:i,scripts:a}),[{path:e,dependencies:Object.keys(r),devDependencies:Object.keys(i),scripts:Object.keys(a)}]}function normalizeSlackConnectorSlug(e){return toSlackConnectorSlug((e.trim().replace(/^@/,``).split(`/`).at(-1)??``).toLowerCase().replace(/[^a-z0-9_-]+/g,`-`).replace(/^[^a-z0-9]+/,``).replace(/[^a-z0-9]+$/,``).slice(0,100).replace(/[^a-z0-9]+$/,``)||`my-agent`)}async function deriveSlackConnectorSlug(e,t){if(t!==void 0&&t.length>0&&t!==`.`)return normalizeSlackConnectorSlug(t);try{let t=await readFile(join(e,`package.json`),`utf8`),n=JSON.parse(t);if(typeof n.name==`string`&&n.name.length>0)return normalizeSlackConnectorSlug(n.name)}catch{}return normalizeSlackConnectorSlug(basename(resolve(e))||`my-agent`)}function buildSlackTemplate(e){return`import { connectSlackCredentials } from "@vercel/connect/ash";
|
|
2
2
|
import { slackChannel } from "experimental-ash/channels/slack";
|
|
3
3
|
|
|
4
4
|
export default slackChannel({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{createLogger,logError}from"#internal/logging.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),
|
|
1
|
+
import{markEventHandled}from"./utils.js";import{createLogger,logError}from"#internal/logging.js";import{mergeUploadPolicy}from"#public/channels/upload-policy.js";import{POST,defineChannel}from"#public/definitions/defineChannel.js";import{buildSlackBinding,slackContinuationToken}from"#public/channels/slack/api.js";import{defaultEvents,defaultInputRequestedHandler,defaultOnAppMention,defaultOnDirectMessage}from"#public/channels/slack/defaults.js";import{buildSlackTurnMessage,collectInboundFileParts,createSlackFetchFile}from"#public/channels/slack/attachments.js";import{formatSlackContextBlock,parseAppMentionEvent,parseDirectMessageEvent}from"#public/channels/slack/inbound.js";import{SLACK_CHANNEL_DEFAULT_ROUTE}from"#public/channels/slack/constants.js";import{handleInteractionPost}from"#public/channels/slack/interactions.js";import{verifySlackRequest}from"#public/channels/slack/verify.js";const log=createLogger(`slack.channel`);function rebuildSlackContext(e,t,n){let{thread:r,slack:i}=buildSlackBinding({botToken:n?.botToken,channelId:e.channelId??``,threadTs:e.threadTs??``,teamId:e.teamId??void 0,onThreadTsChanged(n){e.threadTs=n,e.channelId&&t.setContinuationToken(slackContinuationToken(e.channelId,n))}});return{thread:r,slack:i,state:e}}function slackChannel(e={}){let t=mergeUploadPolicy(e.uploadPolicy),n=createSlackFetchFile({botToken:e.credentials?.botToken}),l=e.onAppMention??defaultOnAppMention,u=e.onDirectMessage??defaultOnDirectMessage,d={...defaultEvents,...e.events,"input.requested":e.events?.[`input.requested`]??defaultInputRequestedHandler()},f=new Set;return defineChannel({kindHint:`slack`,state:{channelId:null,threadTs:null,teamId:null,triggeringUserId:null,pendingToolCallMessage:null,pendingAuthMessageTs:{}},fetchFile:n,metadata(e){return{channelId:e.channelId,teamId:e.teamId,threadTs:e.threadTs,triggeringUserId:e.triggeringUserId??null}},context(t,n){return rebuildSlackContext(t,n,e.credentials)},routes:[POST(e.route??SLACK_CHANNEL_DEFAULT_ROUTE,async(n,{send:r,waitUntil:i})=>{let a=await verifyInbound(n,e.credentials);return a===null?new Response(`unauthorized`,{status:401}):shouldDropSlackHttpTimeoutRetry(n.headers)?new Response(`ok`):(n.headers.get(`content-type`)??``).includes(`application/x-www-form-urlencoded`)?handleInteractionPost(a,{send:r,waitUntil:i},{config:e}):handleEventPost({body:a,send:r,waitUntil:i,onAppMention:l,onDirectMessage:u,uploadPolicy:t,handledEvents:f,headers:n.headers,credentials:e.credentials})})],async receive(t,{send:n}){let r=t.target,i=r.channelId;if(!i||typeof i!=`string`)throw Error(`slackChannel().receive requires target.channelId.`);let a=typeof r.threadTs==`string`?r.threadTs:``,s=r.initialMessage;if(s&&a.length>0)throw Error("slackChannel().receive: `threadTs` and `initialMessage` are mutually exclusive.");let c=a;if(s){let{thread:t}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:i,threadTs:``,teamId:void 0}),n={card:s.card};s.fallbackText!==void 0&&(n.fallbackText=s.fallbackText),c=(await t.post(n)).id}return n(t.message,{auth:t.auth,continuationToken:slackContinuationToken(i,c),state:{channelId:i,threadTs:c||null,teamId:null,triggeringUserId:null}})},events:d})}function shouldDropSlackHttpTimeoutRetry(e){return Number(e.get(`x-slack-retry-num`)??`0`)>=1&&e.get(`x-slack-retry-reason`)===`http_timeout`}async function handleEventPost(t){let n;try{n=JSON.parse(t.body)}catch(e){return log.warn(`inbound webhook body is not valid JSON`,{error:e}),new Response(`ok`)}if(typeof n.challenge==`string`)return new Response(n.challenge,{status:200,headers:{"content-type":`text/plain`}});if(n.event_id){if(t.handledEvents.has(n.event_id))return log.warn(`received a duplicate event`,{event_id:n.event_id,event_time:n.event_time,retry_num:t.headers.get(`x-slack-retry-num`)||`(null)`,retry_reason:t.headers.get(`x-slack-retry-reason`)||`(null)`}),new Response(`ok`);markEventHandled(n.event_id,t.handledEvents)}let r=parseAppMentionEvent(n);if(r)return t.waitUntil(dispatchInboundMessage({kind:`app_mention`,message:r,handler:t.onAppMention,send:t.send,uploadPolicy:t.uploadPolicy,credentials:t.credentials})),new Response(`ok`);let i=parseDirectMessageEvent(n);return i&&t.waitUntil(dispatchInboundMessage({kind:`direct_message`,message:i,handler:t.onDirectMessage,send:t.send,uploadPolicy:t.uploadPolicy,credentials:t.credentials})),new Response(`ok`)}async function verifyInbound(e,t){try{return await verifySlackRequest(e,{signingSecret:t?.signingSecret??(t?.webhookVerifier?void 0:process.env.SLACK_SIGNING_SECRET),webhookVerifier:t?.webhookVerifier})}catch(e){return log.warn(`slack inbound verification failed`,{error:e}),null}}async function dispatchInboundMessage(e){let{message:t,kind:r}=e,{thread:i,slack:a}=buildSlackBinding({botToken:e.credentials?.botToken,channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId}),s={thread:i,slack:a},c;try{c=await e.handler(s,t)}catch(e){logError(log,`${r} handler failed`,e,{channelId:t.channelId});return}if(c!=null)try{let n=await collectInboundFileParts({mention:t,thread:i,policy:e.uploadPolicy}),r=buildSlackTurnMessage(t.markdown,n),a={channelId:t.channelId,fullName:t.author?.fullName,teamId:t.teamId,threadTs:t.threadTs,userId:t.author?.userId??``,userName:t.author?.userName},o=c.context??[];await e.send({message:r,context:[formatSlackContextBlock(a),...o]},{auth:c.auth,continuationToken:slackContinuationToken(t.channelId,t.threadTs),state:{channelId:t.channelId,threadTs:t.threadTs,teamId:t.teamId??null,triggeringUserId:a.userId||null}})}catch(e){logError(log,`${r} delivery failed`,e,{channelId:t.channelId})}}export{slackChannel};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function markEventHandled(eventId: string, handledEvents: Set<string>): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const MAX_HANDLED_EVENTS=1e4;function markEventHandled(e,t){if(t.add(e),t.size>MAX_HANDLED_EVENTS)for(;t.size>MAX_HANDLED_EVENTS/2;)t.delete(t.values().next().value)}export{markEventHandled};
|