experimental-ash 0.58.0 → 0.59.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 +21 -0
- package/dist/docs/public/advanced/execution-model-and-security.md +319 -0
- package/dist/docs/public/advanced/meta.json +1 -0
- package/dist/docs/public/tools.mdx +51 -0
- package/dist/skills/v0-ash-onboarding/SKILL.md +308 -0
- package/dist/src/cli/dev/tui/markdown.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +2 -2
- package/dist/src/compiled/experimental-ai-sdk-code-mode/approval-continuation.d.ts +71 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/approval.d.ts +32 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/code-mode-tool.d.ts +16 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/continuation-capability.d.ts +33 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/errors.d.ts +114 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/fetch-policy.d.ts +21 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/host-interrupt.d.ts +25 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/index.d.ts +11 -144
- package/dist/src/compiled/experimental-ai-sdk-code-mode/index.js +10 -73
- package/dist/src/compiled/experimental-ai-sdk-code-mode/interrupt-continuation.d.ts +32 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/options.d.ts +3 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/run-code-mode.d.ts +12 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/guest-sources.d.ts +3 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/manager.d.ts +22 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/max-workers.d.ts +20 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/protocol.d.ts +49 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/worker-source.d.ts +11 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/serialization.d.ts +5 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/source-cache.d.ts +11 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/telemetry.d.ts +20 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-invocation.d.ts +24 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-prompt.d.ts +3 -0
- package/dist/src/compiled/experimental-ai-sdk-code-mode/types.d.ts +802 -0
- package/dist/src/execution/node-step.js +1 -1
- package/dist/src/execution/tool-auth.d.ts +42 -0
- package/dist/src/execution/tool-auth.js +1 -0
- package/dist/src/harness/action-result-helpers.d.ts +17 -0
- package/dist/src/harness/action-result-helpers.js +1 -1
- package/dist/src/harness/code-mode-interrupt-state.d.ts +26 -0
- package/dist/src/harness/code-mode-interrupt-state.js +1 -0
- package/dist/src/harness/code-mode-lifecycle.js +1 -1
- package/dist/src/harness/code-mode.js +1 -1
- package/dist/src/harness/emission.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/authored-definition/schema-backed.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 +2 -2
- package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +1 -1
- package/dist/src/packages/ash-scaffold/src/web-template.js +2 -14
- 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/dist/src/public/connections/index.js +1 -1
- package/dist/src/public/definitions/tool.d.ts +61 -1
- package/dist/src/public/definitions/tool.js +1 -1
- package/dist/src/public/next/server.js +1 -1
- package/dist/src/runtime/connections/mcp-client.js +1 -1
- package/dist/src/runtime/connections/scoped-authorization.d.ts +61 -0
- package/dist/src/runtime/connections/scoped-authorization.js +1 -0
- package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
- package/dist/src/runtime/resolve-tool.js +1 -1
- package/dist/src/runtime/types.d.ts +10 -0
- package/package.json +2 -2
- package/dist/src/harness/code-mode-approval.d.ts +0 -22
- package/dist/src/harness/code-mode-approval.js +0 -1
- package/dist/src/harness/code-mode-connection-auth-state.d.ts +0 -15
- package/dist/src/harness/code-mode-connection-auth-state.js +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# experimental-ash
|
|
2
2
|
|
|
3
|
+
## 0.59.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- c0f795d: Unify code mode's host-interrupt handling and remove Ash's per-callback context shims. `experimental-ai-sdk-code-mode` now re-enters the originating invocation's async context at the worker bridge for every host callback, so the code-mode host-tool wrapper and lifecycle projection are context-transparent (no more pinning `execute`, `needsApproval`, or `emit` to a build-time context). Nested-tool approval and connection auth now ride one generic `CodeModeInterrupt` park/resume path instead of two bespoke ones, so any durable interrupt kind resumes through a single seam.
|
|
8
|
+
- 77aeeee: Add per-tool authorization. `defineTool` now accepts an `auth` field (the same shapes as connection auth: `connect("...")`, a custom interactive definition, or `{ getToken }`), and the tool's `execute` context gains `ctx.getToken()` and `ctx.requireAuth()`. A token cache miss or a thrown `ConnectionAuthorizationRequiredError` suspends the turn, drives the OAuth consent flow on a tool-scoped callback URL, and re-runs the tool after the user authorizes.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- 651b295: Constrain the Next.js dev-server origin parser to loopback URLs so dependency metadata URLs cannot be written to the Ash dev-server registry.
|
|
13
|
+
- c0f795d: Project code-mode nested tool results through the same Ash-owned `action.result` helper as native tool calls. The code-mode lifecycle no longer uses a separate strict-JSON coercion with an error fallback; both paths now funnel through one `createRuntimeToolResultFromValue` helper, so the raw-output (never `toModelOutput`) decision is made once and code-mode nested results carry their raw structured output exactly like native results.
|
|
14
|
+
- b932acd: Fix the dev TUI mangling URLs that contain underscores. The terminal markdown renderer treated `_…_` as italics and stripped the underscores, corrupting Vercel Connect authorization URLs (`sca_…`) and hook callback paths shown in the authorization panel. URLs are now shielded from inline emphasis and render verbatim.
|
|
15
|
+
|
|
16
|
+
## 0.58.1
|
|
17
|
+
|
|
18
|
+
### Patch Changes
|
|
19
|
+
|
|
20
|
+
- 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.
|
|
21
|
+
- f9ba08b: Provide sourcemaps when Ash injects the Node ESM compatibility banner into authored module bundles, suppressing Rolldown sourcemap warnings during agent startup.
|
|
22
|
+
- cac284c: Dedup inbound Slack events by `event_id` within a process so retried webhook deliveries no longer trigger the agent more than once.
|
|
23
|
+
|
|
3
24
|
## 0.58.0
|
|
4
25
|
|
|
5
26
|
### 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.
|
|
@@ -101,6 +101,57 @@ The `ctx` parameter passed to `execute` is the primary way to access runtime sta
|
|
|
101
101
|
|
|
102
102
|
These are available inside `execute` and other active authored runtime execution contexts.
|
|
103
103
|
|
|
104
|
+
## Tool Authorization
|
|
105
|
+
|
|
106
|
+
A tool can declare its own authorization strategy with the `auth` field. Use it when the tool calls
|
|
107
|
+
a service behind OAuth (for example an Okta-protected API) and you want Ash to drive the sign-in
|
|
108
|
+
flow, cache the resulting token, and re-run the tool after the user authorizes — without standing up
|
|
109
|
+
a separate connection.
|
|
110
|
+
|
|
111
|
+
`auth` accepts the same shapes as a connection's `auth`: `connect("...")` from `@vercel/connect/ash`
|
|
112
|
+
for Vercel Connect-managed OAuth, a custom interactive definition, or a plain `{ getToken }` object
|
|
113
|
+
for static or pre-provisioned credentials.
|
|
114
|
+
|
|
115
|
+
`agent/tools/list_okta_groups.ts`
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
import { defineTool } from "experimental-ash/tools";
|
|
119
|
+
import { connect } from "@vercel/connect/ash";
|
|
120
|
+
import { z } from "zod";
|
|
121
|
+
|
|
122
|
+
export default defineTool({
|
|
123
|
+
description: "List the caller's Okta groups.",
|
|
124
|
+
inputSchema: z.object({}),
|
|
125
|
+
auth: connect("okta"),
|
|
126
|
+
async execute(_input, ctx) {
|
|
127
|
+
// Resolves the per-user token. If the user has not signed in, this
|
|
128
|
+
// suspends the turn, the channel shows a "Sign in" affordance, and
|
|
129
|
+
// the tool re-runs after the OAuth callback completes.
|
|
130
|
+
const { token } = await ctx.getToken();
|
|
131
|
+
const res = await fetch("https://api.okta-proxy.internal/groups", {
|
|
132
|
+
headers: { authorization: `Bearer ${token}` },
|
|
133
|
+
});
|
|
134
|
+
return await res.json();
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
When the tool declares `auth`, the `ctx` passed to `execute` gains two accessors:
|
|
140
|
+
|
|
141
|
+
- `ctx.getToken()` resolves the bearer for the declared strategy, consulting the per-step token cache
|
|
142
|
+
before invoking the authored `getToken`. For interactive strategies a cache miss suspends the turn
|
|
143
|
+
on a framework-owned callback URL and re-runs the tool after sign-in.
|
|
144
|
+
- `ctx.requireAuth()` explicitly throws `ConnectionAuthorizationRequiredError` to gate the tool on
|
|
145
|
+
authorization without resolving a token first. The runtime converts it into the same consent
|
|
146
|
+
prompt.
|
|
147
|
+
|
|
148
|
+
Throwing `ConnectionAuthorizationRequiredError` anywhere inside `execute` — directly, via
|
|
149
|
+
`ctx.requireAuth()`, or implicitly from `ctx.getToken()` — triggers the consent flow. The
|
|
150
|
+
authorization state (token cache and callback URL) is keyed by the tool's name, the same way
|
|
151
|
+
connection auth is keyed by the connection name.
|
|
152
|
+
|
|
153
|
+
Calling `ctx.getToken()` or `ctx.requireAuth()` on a tool that does **not** declare `auth` throws.
|
|
154
|
+
|
|
104
155
|
## When A Tool Runs
|
|
105
156
|
|
|
106
157
|
Ash does not execute authored tools during discovery.
|