experimental-ash 0.58.1 → 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.
Files changed (58) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/docs/public/tools.mdx +51 -0
  3. package/dist/skills/v0-ash-onboarding/SKILL.md +308 -0
  4. package/dist/src/cli/dev/tui/markdown.js +1 -1
  5. package/dist/src/compiled/.vendor-stamp.json +2 -2
  6. package/dist/src/compiled/experimental-ai-sdk-code-mode/approval-continuation.d.ts +71 -0
  7. package/dist/src/compiled/experimental-ai-sdk-code-mode/approval.d.ts +32 -0
  8. package/dist/src/compiled/experimental-ai-sdk-code-mode/code-mode-tool.d.ts +16 -0
  9. package/dist/src/compiled/experimental-ai-sdk-code-mode/continuation-capability.d.ts +33 -0
  10. package/dist/src/compiled/experimental-ai-sdk-code-mode/errors.d.ts +114 -0
  11. package/dist/src/compiled/experimental-ai-sdk-code-mode/fetch-policy.d.ts +21 -0
  12. package/dist/src/compiled/experimental-ai-sdk-code-mode/host-interrupt.d.ts +25 -0
  13. package/dist/src/compiled/experimental-ai-sdk-code-mode/index.d.ts +11 -144
  14. package/dist/src/compiled/experimental-ai-sdk-code-mode/index.js +10 -73
  15. package/dist/src/compiled/experimental-ai-sdk-code-mode/interrupt-continuation.d.ts +32 -0
  16. package/dist/src/compiled/experimental-ai-sdk-code-mode/options.d.ts +3 -0
  17. package/dist/src/compiled/experimental-ai-sdk-code-mode/run-code-mode.d.ts +12 -0
  18. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/guest-sources.d.ts +3 -0
  19. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/manager.d.ts +22 -0
  20. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/max-workers.d.ts +20 -0
  21. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/protocol.d.ts +49 -0
  22. package/dist/src/compiled/experimental-ai-sdk-code-mode/runtime/worker-source.d.ts +11 -0
  23. package/dist/src/compiled/experimental-ai-sdk-code-mode/serialization.d.ts +5 -0
  24. package/dist/src/compiled/experimental-ai-sdk-code-mode/source-cache.d.ts +11 -0
  25. package/dist/src/compiled/experimental-ai-sdk-code-mode/telemetry.d.ts +20 -0
  26. package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-invocation.d.ts +24 -0
  27. package/dist/src/compiled/experimental-ai-sdk-code-mode/tool-prompt.d.ts +3 -0
  28. package/dist/src/compiled/experimental-ai-sdk-code-mode/types.d.ts +802 -0
  29. package/dist/src/execution/node-step.js +1 -1
  30. package/dist/src/execution/tool-auth.d.ts +42 -0
  31. package/dist/src/execution/tool-auth.js +1 -0
  32. package/dist/src/harness/action-result-helpers.d.ts +17 -0
  33. package/dist/src/harness/action-result-helpers.js +1 -1
  34. package/dist/src/harness/code-mode-interrupt-state.d.ts +26 -0
  35. package/dist/src/harness/code-mode-interrupt-state.js +1 -0
  36. package/dist/src/harness/code-mode-lifecycle.js +1 -1
  37. package/dist/src/harness/code-mode.js +1 -1
  38. package/dist/src/harness/tool-loop.js +1 -1
  39. package/dist/src/internal/application/package.js +1 -1
  40. package/dist/src/internal/authored-definition/schema-backed.js +1 -1
  41. package/dist/src/packages/ash-scaffold/src/channels.js +2 -2
  42. package/dist/src/packages/ash-scaffold/src/steps/run-add-to-agent.js +1 -1
  43. package/dist/src/packages/ash-scaffold/src/web-template.js +2 -14
  44. package/dist/src/public/connections/index.js +1 -1
  45. package/dist/src/public/definitions/tool.d.ts +61 -1
  46. package/dist/src/public/definitions/tool.js +1 -1
  47. package/dist/src/public/next/server.js +1 -1
  48. package/dist/src/runtime/connections/mcp-client.js +1 -1
  49. package/dist/src/runtime/connections/scoped-authorization.d.ts +61 -0
  50. package/dist/src/runtime/connections/scoped-authorization.js +1 -0
  51. package/dist/src/runtime/framework-tools/connection-search-dynamic.js +1 -1
  52. package/dist/src/runtime/resolve-tool.js +1 -1
  53. package/dist/src/runtime/types.d.ts +10 -0
  54. package/package.json +2 -2
  55. package/dist/src/harness/code-mode-approval.d.ts +0 -22
  56. package/dist/src/harness/code-mode-approval.js +0 -1
  57. package/dist/src/harness/code-mode-connection-auth-state.d.ts +0 -15
  58. package/dist/src/harness/code-mode-connection-auth-state.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
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
+
3
16
  ## 0.58.1
4
17
 
5
18
  ### Patch Changes
@@ -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.
@@ -0,0 +1,308 @@
1
+ ---
2
+ name: v0-ash-onboarding
3
+ description: Use when bootstrapping, testing, or debugging an Ash Web Chat agent inside a v0 preview or v0 sandbox. Runs create-experimental-ash-agent in-place with v0-safe flags, avoids Vercel Services/vercel dev, and verifies that the v0 preview shows the Next.js chat UI.
4
+ ---
5
+
6
+ # v0 Ash onboarding
7
+
8
+ Scaffold an Ash Web Chat agent into the current v0 project root and verify the browser preview. This skill is for v0 dev/preview only.
9
+
10
+ ## Ground rules
11
+
12
+ - Work in the v0 project root. In v0 this is usually `/vercel/share/v0-project`; confirm with `pwd` and `ls`.
13
+ - Do not create a child project directory. Use `--in-place` and omit `<name>`. Do not invent names like `v0-preview`.
14
+ - Do not run `vercel login`, `vercel deploy`, `vercel dev`, `vercel env pull`, or Vercel CLI auth commands.
15
+ - Do not pass `--project`, `--team`, or `--channels slack`.
16
+ - Do not use old flags such as `--local-only` or `--no-deploy`. Use `--skip-vercel`; it also skips deployment.
17
+ - Do not ask for or set any Vercel API token such as `VERCEL_TOKEN` or `VERCEL_API_TOKEN`. It is not required for this v0 dev/preview flow because `--skip-vercel` skips Vercel project provisioning, Services config, env pull, and deployment.
18
+ - Do not use v0's "Add Environment Variable" request UI or "Generate" flow for `AI_GATEWAY_API_KEY`. This flow consumes v0-managed env injection; a missing key is a failed env-injection probe, not a prompt to ask the user for a secret.
19
+ - Do not start a separate `ash dev` process. The Web Chat scaffold uses `next dev`; `withAsh()` starts the app-local Ash dev server and rewrites same-origin `/ash/*` requests.
20
+ - Do not hand-edit `next.config.ts` or `vercel.json` as the normal path. The create CLI should produce the v0-safe config.
21
+ - Do not repair a bad `.ash/next-dev-server.json` origin by setting `ASH_BASE_URL` as the normal path. The registry origin for `withAsh()` development must be loopback; an external origin such as `https://rolldown.rs` means the installed Ash package parsed the wrong URL from dev-server output or a stale registry survived.
22
+
23
+ ## Inputs
24
+
25
+ Collect these before running the CLI:
26
+
27
+ - `model`: the model id to bake into `agent/agent.ts`, for example `openai/gpt-5-mini`.
28
+ - Agent behavior: any instructions, skills, tools, or UI changes the user wants after the base scaffold works.
29
+
30
+ Do not ask the user for an AI Gateway key at model-planning time or after a failed env probe. The sandbox and `/vercel/share/.env.project` may not exist until the first terminal/tool action has initialized the VM, and it may not be reliable until the preview has settled. Treat `AI_GATEWAY_API_KEY` as the final verification input: after the scaffold, route rewrite, and UI checks pass, source `/vercel/share/.env.project` and hydrate a project-local `.env.local` fallback. Only use `--gateway-api-key` when the user explicitly supplies a BYOK key before this flow.
31
+
32
+ Do not ask the user for `VERCEL_TOKEN`, `VERCEL_API_TOKEN`, or another Vercel API token at any point in this flow. If a tool or model says a Vercel token is missing, treat that as using the wrong path and continue with `--skip-vercel` plus the v0-managed `AI_GATEWAY_API_KEY` checks below.
33
+
34
+ ## Dev-server invariant
35
+
36
+ The v0 preview's `next dev` process must start after the scaffold has written `next.config.ts`. Next.js route rewrites are process config, so an already-running pre-scaffold `next dev` process can keep serving stale routes even after the file exists on disk.
37
+
38
+ Make this the normal path:
39
+
40
+ 1. Stop any pre-scaffold `next dev` process.
41
+ 2. Run create in place.
42
+ 3. Verify `next.config.ts` contains `withAsh(nextConfig, { configureVercelJson: false })`.
43
+ 4. Start `pnpm dev` once.
44
+ 5. Probe `/ash/v1/health`.
45
+
46
+ Do not treat a restart after `/ash/v1/session` 404s as the happy path. A restart is only recovery for a missed preflight.
47
+
48
+ ## Step 1. Check the CLI surface
49
+
50
+ Run the help command from the v0 root:
51
+
52
+ ```bash
53
+ npx create-experimental-ash-agent@latest --help
54
+ ```
55
+
56
+ The help output must include `--in-place`, `--skip-vercel`, and `--gateway-api-key`.
57
+
58
+ If any flag is missing, stop and report that the published create CLI is stale for this v0 flow. Do not recreate the old workaround by moving child directories, adding `concurrently`, replacing `next.config.ts`, or writing Vercel Services config by hand.
59
+
60
+ ## Step 2. Scaffold in place
61
+
62
+ Before scaffolding, make sure no stale preview dev server is already running from the pre-scaffold project:
63
+
64
+ ```bash
65
+ pgrep -af "next dev|next/dist/bin/next dev" || true
66
+ ```
67
+
68
+ If that command prints a `next dev` process, stop the preview before running create. Use v0's preview stop/restart control if available; otherwise kill only the printed `next dev` PID. The goal is for the first post-bootstrap `next dev` start to happen after `next.config.ts` already contains `withAsh(...)`. Do not let an already-running Next process carry pre-scaffold config into the chat test.
69
+
70
+ Run create without waiting on env injection:
71
+
72
+ ```bash
73
+ npx create-experimental-ash-agent@latest \
74
+ --in-place \
75
+ --model "$MODEL" \
76
+ --channels web \
77
+ --skip-vercel \
78
+ --disable-git \
79
+ -y \
80
+ --json
81
+ ```
82
+
83
+ With `--skip-vercel`, create may warn that no project or API key is linked. Do not treat that as a failure; the v0-managed env is checked as the final verification step after the preview settles. Do not respond to that warning by asking for a Vercel token.
84
+
85
+ `--disable-git` skips the create CLI's local `git init` and initial commit. v0 owns project history for this workflow.
86
+
87
+ ## Step 3. Verify generated files
88
+
89
+ Check the exact files v0's preview detector sees:
90
+
91
+ ```bash
92
+ test -f agent/agent.ts
93
+ test -f app/page.tsx
94
+ test -f next.config.ts
95
+ node -e 'const p=require("./package.json"); if (p.scripts?.dev !== "next dev") throw new Error(`unexpected dev script: ${p.scripts?.dev}`)'
96
+ grep -q 'configureVercelJson: false' next.config.ts
97
+ ```
98
+
99
+ Then check no stale supported Next config can shadow the generated Ash config:
100
+
101
+ ```bash
102
+ competing_next_configs="$(ls next.config.js next.config.mjs next.config.mts 2>/dev/null || true)"
103
+ if [ -n "$competing_next_configs" ]; then
104
+ echo "competing Next config remains; merge needed settings into next.config.ts and remove the competing file before starting the preview"
105
+ printf "%s\n" "$competing_next_configs"
106
+ exit 1
107
+ fi
108
+ ```
109
+
110
+ If `next.config.mjs` exists beside `next.config.ts`, Next.js loads `next.config.mjs` first and ignores the generated `withAsh(...)` config. That produces a Next.js 404 for `/ash/v1/session` even though `next.config.ts` looks correct on disk.
111
+
112
+ If the create CLI warns about a competing Next config, or the shell check above prints one, inspect each printed file before removing it. Preserve any needed settings in `next.config.ts`; for v0's common defaults that usually means keeping this shape inside the generated file:
113
+
114
+ ```ts
115
+ const nextConfig: NextConfig = {
116
+ images: { unoptimized: true },
117
+ typescript: { ignoreBuildErrors: true },
118
+ };
119
+ ```
120
+
121
+ After preserving needed settings, remove the competing `next.config.js`, `next.config.mjs`, or `next.config.mts` before starting the preview. Do not start `pnpm dev` while more than one supported Next config file remains.
122
+
123
+ Then check Services is absent:
124
+
125
+ ```bash
126
+ if [ -f vercel.json ] && grep -q experimentalServices vercel.json; then
127
+ echo "stale Vercel Services config remains in vercel.json"
128
+ exit 1
129
+ fi
130
+ ```
131
+
132
+ If `vercel.json` still contains `experimentalServices`, this is stale state from a previous attempt or a create CLI bug. Remove that stale Services config before testing the preview, but call it out explicitly.
133
+
134
+ ## Step 4. Install and run
135
+
136
+ Install dependencies if v0 has not already done it:
137
+
138
+ ```bash
139
+ pnpm install
140
+ ```
141
+
142
+ Let v0 run the preview, or start it manually:
143
+
144
+ ```bash
145
+ pnpm dev
146
+ ```
147
+
148
+ Expected dev command: `next dev`. Do not run `vercel dev`. Do not run `ash dev` in a second terminal.
149
+
150
+ If Step 2 stopped the pre-scaffold preview, this is the one clean start that should load the generated `next.config.ts`. Next's route rewrites are config-level state, so do not rely on page HMR if an old process survived the preflight.
151
+
152
+ Verify that the active process is the post-scaffold `next dev` process:
153
+
154
+ ```bash
155
+ pgrep -af "next dev" || true
156
+ ```
157
+
158
+ If an old `next dev` process was already running before the scaffold and was not stopped, stop that process before testing chat. A valid run has a `next dev` process that started after `next.config.ts` was generated.
159
+
160
+ For local v0 source semantics, v0 detects `next dev` as the preview command and exposes the detected port. If live v0 instead requires the app to bind an arbitrary `$DEV_PORT`, report that as a scaffold gap. Do not hide it by hand-patching the dev script unless the user explicitly asks for that experiment.
161
+
162
+ ## Step 5. Verify the Ash route rewrite
163
+
164
+ Before sending a chat message, probe the same-origin Ash health route through the Next preview:
165
+
166
+ ```bash
167
+ curl -i "http://127.0.0.1:${DEV_PORT:-3000}/ash/v1/health"
168
+ ```
169
+
170
+ Expected: an HTTP 2xx response from Ash. A Next.js HTML 404 page for `/ash/v1/health` or `/ash/v1/session` means the active Next server did not load the `withAsh()` rewrite.
171
+
172
+ If the probe returns a Next.js 404, check the generated config and stale Services state:
173
+
174
+ ```bash
175
+ grep -R "withAsh\\|configureVercelJson\\|experimentalServices" next.config.* vercel.json 2>/dev/null || true
176
+ ```
177
+
178
+ Expected:
179
+
180
+ - `next.config.ts` imports `withAsh` from `experimental-ash/next`.
181
+ - `next.config.ts` exports `withAsh(nextConfig, { configureVercelJson: false })`.
182
+ - `vercel.json` is absent or does not contain `experimentalServices`.
183
+
184
+ If the files are correct but the probe still returns a Next.js 404, restart the v0 preview/dev server and run the probe again. Do not debug AI Gateway credentials until this route probe passes; missing credentials do not make Next serve its own 404 page.
185
+
186
+ If the files stay correct and the 404 persists after restart, inspect whether the active server ever resolved the Ash rewrite:
187
+
188
+ ```bash
189
+ pgrep -af "next dev|ash.js dev|ash dev" || true
190
+ test -f .ash/next-dev-server.json && cat .ash/next-dev-server.json || echo "missing .ash/next-dev-server.json"
191
+ ```
192
+
193
+ - If `.ash/next-dev-server.json` is missing after the `/ash/v1/health` probe, the running Next process did not call the `withAsh()` rewrite function. Restart the v0 preview process that owns `${DEV_PORT}`, not a second `pnpm dev` process on a different port.
194
+ - If `.ash/next-dev-server.json` exists, probe the registered Ash origin directly:
195
+
196
+ ```bash
197
+ node -e 'const fs=require("fs"); const r=JSON.parse(fs.readFileSync(".ash/next-dev-server.json","utf8")); console.log(r.origin)'
198
+ node -e 'const fs=require("fs"); const r=JSON.parse(fs.readFileSync(".ash/next-dev-server.json","utf8")); const h=new URL(r.origin).hostname; if (!(h==="localhost" || h==="::1" || h.startsWith("127."))) { console.error(`Ash dev server registry origin is not loopback: ${r.origin}`); process.exit(1); }'
199
+ curl -i "$(node -e 'const fs=require("fs"); const r=JSON.parse(fs.readFileSync(".ash/next-dev-server.json","utf8")); process.stdout.write(r.origin)')/ash/v1/health"
200
+ ```
201
+
202
+ If the loopback-origin check fails, remove `.ash/next-dev-server.json`, reinstall or update `experimental-ash`, and restart the post-scaffold `next dev` process. Do not start a separate fixed-port Ash server and do not patch `ASH_BASE_URL` unless the user explicitly asks for a temporary experiment; that hides the `withAsh()` discovery bug.
203
+
204
+ If direct Ash health passes but same-origin `/ash/v1/health` is still a Next.js 404, the active Next rewrite is missing or stale. If direct Ash health fails, debug the spawned Ash dev server before testing chat.
205
+
206
+ ## Step 6. Browser verification
207
+
208
+ Open the v0 preview, preferably through the v0/browser tool:
209
+
210
+ ```bash
211
+ agent-browser open "http://localhost:${DEV_PORT:-3000}"
212
+ ```
213
+
214
+ The first viewport should show the Ash Web Chat UI, not the Ash backend landing page. A successful compile is not enough.
215
+
216
+ Before moving to the final env step, confirm all of these:
217
+
218
+ - Typing in the composer leaves visible text in the textarea.
219
+ - The send button is clickable after typing text.
220
+
221
+ If the send button stays disabled after typing text, inspect `app/_components/agent-chat.tsx`. Older scaffolds duplicated textarea state in `AgentChat` and used that duplicate state to pass `disabled` to `PromptInputSubmit`. Remove the `inputText` state, render `<PromptInputTextarea placeholder="Send a message…" />`, and render `<PromptInputSubmit onStop={agent.stop} status={agent.status} />`. `PromptInput` already captures the form text and `handleSubmit` already no-ops for empty trimmed messages.
222
+
223
+ If the preview shows "The agent is up and accepting messages", inspect these before changing code:
224
+
225
+ ```bash
226
+ grep -R "experimentalServices\\|vercel dev\\|ash dev\\|concurrently\\|configureVercelJson" package.json next.config.* vercel.json 2>/dev/null || true
227
+ ```
228
+
229
+ Likely causes:
230
+
231
+ - `vercel.json` still contains `experimentalServices`, so v0 selected the Services path.
232
+ - The CLI was stale and did not render `configureVercelJson: false`.
233
+ - The project was not scaffolded in place and v0 is previewing the wrong root.
234
+ - Live v0 is watching a non-3000 `$DEV_PORT` that the generated `next dev` script does not bind. Report this as a failed acceptance check for the scaffold.
235
+
236
+ Do not send the final chat message yet. The AI Gateway key is the last step because v0's managed env file is most reliable after the sandbox and preview have settled.
237
+
238
+ If a route probe or browser request fails with a Next.js HTML 404 for `/ash/v1/session`, return to Step 5. That failure is a missing active rewrite, not a model or credential failure.
239
+
240
+ ## Step 7. Hydrate env and send a message
241
+
242
+ After the preview route and UI checks pass, wait for v0's managed env file, then hydrate `.env.local` without printing the secret:
243
+
244
+ ```bash
245
+ bash -lc 'for i in $(seq 1 60); do set -a; . /vercel/share/.env.project 2>/dev/null; set +a; [ -n "${AI_GATEWAY_API_KEY:-}" ] && break; sleep 1; done; if [ -z "${AI_GATEWAY_API_KEY:-}" ]; then echo "AI_GATEWAY_API_KEY missing from /vercel/share/.env.project after waiting for v0 env injection"; exit 1; fi; umask 077; { grep -v "^AI_GATEWAY_API_KEY=" .env.local 2>/dev/null || true; printf "AI_GATEWAY_API_KEY=%s\n" "$AI_GATEWAY_API_KEY"; } > .env.local.tmp && mv .env.local.tmp .env.local'
246
+ ```
247
+
248
+ If that command reports the key is missing after the wait, report that v0 env injection is not ready or did not provide `AI_GATEWAY_API_KEY`. Do not ask the user for a key, do not open v0's "Add Environment Variable" UI, and do not press "Generate"; stop for user direction or retry the wait if the sandbox was still settling.
249
+
250
+ Do not decide the key is unavailable from a bare `printenv AI_GATEWAY_API_KEY`; generic v0 terminal commands may not source `/vercel/share/.env.project`.
251
+
252
+ Verify the env files without printing secret values:
253
+
254
+ ```bash
255
+ bash -lc 'for f in .env.local /vercel/share/.env.project; do echo "== $f =="; test -f "$f" && grep -E "^(AI_GATEWAY_API_KEY|ANTHROPIC_AUTH_TOKEN)=" "$f" | sed "s/=.*/=<redacted>/" || echo missing; done'
256
+ ```
257
+
258
+ After the hydration command, `.env.local` should contain `AI_GATEWAY_API_KEY` copied from `/vercel/share/.env.project`. If the user explicitly supplied a BYOK key before this flow, `.env.local` may contain that value; otherwise do not request one. If `.env.local` is missing but `/vercel/share/.env.project` has the key, re-run the hydration command.
259
+
260
+ Do not debug `AI_GATEWAY_KEY` as the required variable; v0 and Ash use `AI_GATEWAY_API_KEY`.
261
+
262
+ Then verify v0's source order resolves a usable environment:
263
+
264
+ ```bash
265
+ bash -lc 'set -a; . .env.local 2>/dev/null; . .env.development.local 2>/dev/null; . /vercel/share/.env.project 2>/dev/null; set +a; node -e "console.log({ AI_GATEWAY_API_KEY: Boolean(process.env.AI_GATEWAY_API_KEY), ANTHROPIC_AUTH_TOKEN: Boolean(process.env.ANTHROPIC_AUTH_TOKEN) })"'
266
+ ```
267
+
268
+ If the preview server was already running before `.env.local` existed, restart it once after hydration so `next dev` and the spawned `ash dev` process inherit the key. Then inspect the live process envs, redacted:
269
+
270
+ ```bash
271
+ pgrep -af "next dev|ash.js dev|ash dev" || true
272
+ PID=<pid>
273
+ tr '\0' '\n' < /proc/$PID/environ \
274
+ | grep -E '^(AI_GATEWAY_API_KEY|ANTHROPIC_AUTH_TOKEN)=' \
275
+ | sed 's/=.*/=<redacted>/'
276
+ ```
277
+
278
+ Check both the `next dev` process and the spawned `ash dev` process.
279
+
280
+ - If `/vercel/share/.env.project` has `AI_GATEWAY_API_KEY` but `next dev` does not, v0 likely started the preview before the env file existed or used a start path that skipped `getSourceEnvPrefix()`. Restart the preview and re-check.
281
+ - If `next dev` has `AI_GATEWAY_API_KEY` but the spawned `ash dev` process does not, treat it as an Ash child-process env propagation bug.
282
+ - If `.env.local` has a good `AI_GATEWAY_API_KEY` but `/vercel/share/.env.project` has the same key empty or stale, v0's source order makes `/vercel/share/.env.project` win. Remove or refresh the stale v0-managed value before restarting the preview.
283
+
284
+ Finally, send one browser message:
285
+
286
+ - Sending creates a `POST /ash/v1/session` request.
287
+ - The assistant streams a response.
288
+
289
+ If chat fails with missing AI Gateway credentials, do not guess from an interactive shell. Use the env file and process checks above.
290
+
291
+ If chat fails with a Next.js HTML 404 for `POST /ash/v1/session`, return to Step 5. That failure is a missing active rewrite, not a model or credential failure.
292
+
293
+ ## Step 8. Apply the user's agent changes
294
+
295
+ After the base chat works, edit the Ash project:
296
+
297
+ - `agent/instructions.md` for behavior and personality.
298
+ - `agent/skills/*.md` for durable knowledge and procedures.
299
+ - `agent/tools/*.ts` for tools.
300
+ - `app/**` for UI changes.
301
+
302
+ Tool definitions import from `experimental-ash/tools`:
303
+
304
+ ```ts
305
+ import { defineTool } from "experimental-ash/tools";
306
+ ```
307
+
308
+ Use `inputSchema` for tool input schemas.
@@ -1,3 +1,3 @@
1
1
  import{visibleLength}from"./terminal-text.js";const ansi={bold:`\x1B[1m`,boldOff:`\x1B[22m`,italic:`\x1B[3m`,italicOff:`\x1B[23m`};function renderMarkdown(e){let t=e.split(`
2
2
  `),n=[];for(let e=0;e<t.length;e+=1){let r=parseTable(t,e);if(r!=null){n.push(...renderTable(r)),e=r.endIndex-1;continue}n.push(renderMarkdownLine(t[e]??``))}return n.join(`
3
- `)}function renderMarkdownLine(e){if(e.startsWith(`### `))return renderInlineMarkdown(`▶ ${e.slice(4)}`);if(e.startsWith(`## `))return renderInlineMarkdown(`■ ${e.slice(3)}`);if(e.startsWith(`# `))return renderInlineMarkdown(`█ ${e.slice(2)}`);let t=e.match(/^(\s*)[-+*]\s+(.*)$/);if(t){let[,e,n=``]=t;return renderInlineMarkdown(`${e}•${n.length>0?` ${n}`:``}`)}return/^\d+\. /.test(e)?renderInlineMarkdown(e.replace(/^(\d+)\. /,`$1. `)):e.startsWith(`> `)?renderInlineMarkdown(`│ ${e.slice(2)}`):renderInlineMarkdown(e)}function renderInlineMarkdown(e){return e.replaceAll(/`([^`]+)`/g,`$1`).replaceAll(/\*\*([^*\n]+)\*\*/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/__([^_\n]+)__/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/\*([^*\n]+)\*/g,`${ansi.italic}$1${ansi.italicOff}`).replaceAll(/_([^_\n]+)_/g,`${ansi.italic}$1${ansi.italicOff}`)}function parseTable(e,t){let n=parseTableCells(e[t]??``);if(n==null||n.length<2)return;let r=parseTableCells(e[t+1]??``);if(r==null||r.length!==n.length)return;let i=parseTableAlignments(r);if(i==null)return;let a=[],o=t+2;for(;o<e.length;){let t=parseTableCells(e[o]??``);if(t==null)break;a.push(normalizeTableRow(t,n.length)),o+=1}return{alignments:i,endIndex:o,header:n,rows:a}}function parseTableCells(e){if(!e.includes(`|`))return;let t=e.trim();t.startsWith(`|`)&&(t=t.slice(1)),t.endsWith(`|`)&&!t.endsWith(`\\|`)&&(t=t.slice(0,-1));let n=[],r=``;for(let e=0;e<t.length;e+=1){let i=t[e],a=t[e+1];if(i===`\\`&&a===`|`){r+=`|`,e+=1;continue}if(i===`|`){n.push(r.trim()),r=``;continue}r+=i}return n.push(r.trim()),n}function parseTableAlignments(e){let t=[];for(let n of e){let e=n.match(/^(:)?-{3,}(:)?$/);if(e==null)return;let[,r,i]=e;t.push(r!=null&&i!=null?`center`:i==null?`left`:`right`)}return t}function normalizeTableRow(e,t){return Array.from({length:t},(t,n)=>e[n]??``)}function renderTable(n){let r=n.header.map(e=>`${ansi.bold}${renderInlineMarkdown(e)}${ansi.boldOff}`),i=n.rows.map(e=>e.map(renderInlineMarkdown)),a=[r,...i],o=n.alignments.map((t,n)=>Math.max(3,...a.map(t=>visibleLength(t[n]??``))));return[formatTableRow(r,o,n.alignments),o.map(e=>`─`.repeat(e)).join(` `),...i.map(e=>formatTableRow(e,o,n.alignments))]}function formatTableRow(e,t,n){return e.map((e,r)=>alignTableCell(e,t[r]??0,n[r]??`left`)).join(` `)}function alignTableCell(t,n,r){let i=Math.max(0,n-visibleLength(t));if(r===`right`)return`${` `.repeat(i)}${t}`;if(r===`center`){let e=Math.floor(i/2),n=i-e;return`${` `.repeat(e)}${t}${` `.repeat(n)}`}return`${t}${` `.repeat(i)}`}export{renderMarkdown};
3
+ `)}function renderMarkdownLine(e){if(e.startsWith(`### `))return renderInlineMarkdown(`▶ ${e.slice(4)}`);if(e.startsWith(`## `))return renderInlineMarkdown(`■ ${e.slice(3)}`);if(e.startsWith(`# `))return renderInlineMarkdown(`█ ${e.slice(2)}`);let t=e.match(/^(\s*)[-+*]\s+(.*)$/);if(t){let[,e,n=``]=t;return renderInlineMarkdown(`${e}•${n.length>0?` ${n}`:``}`)}return/^\d+\. /.test(e)?renderInlineMarkdown(e.replace(/^(\d+)\. /,`$1. `)):e.startsWith(`> `)?renderInlineMarkdown(`│ ${e.slice(2)}`):renderInlineMarkdown(e)}function renderInlineMarkdown(e){let n=[];return e.replaceAll(/https?:\/\/\S+/g,e=>{let t=`\uE000${n.length}\uE001`;return n.push(e),t}).replaceAll(/`([^`]+)`/g,`$1`).replaceAll(/\*\*([^*\n]+)\*\*/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/__([^_\n]+)__/g,`${ansi.bold}$1${ansi.boldOff}`).replaceAll(/\*([^*\n]+)\*/g,`${ansi.italic}$1${ansi.italicOff}`).replaceAll(/_([^_\n]+)_/g,`${ansi.italic}$1${ansi.italicOff}`).replaceAll(/\uE000(\d+)\uE001/g,(e,t)=>n[Number(t)]??``)}function parseTable(e,t){let n=parseTableCells(e[t]??``);if(n==null||n.length<2)return;let r=parseTableCells(e[t+1]??``);if(r==null||r.length!==n.length)return;let i=parseTableAlignments(r);if(i==null)return;let a=[],o=t+2;for(;o<e.length;){let t=parseTableCells(e[o]??``);if(t==null)break;a.push(normalizeTableRow(t,n.length)),o+=1}return{alignments:i,endIndex:o,header:n,rows:a}}function parseTableCells(e){if(!e.includes(`|`))return;let t=e.trim();t.startsWith(`|`)&&(t=t.slice(1)),t.endsWith(`|`)&&!t.endsWith(`\\|`)&&(t=t.slice(0,-1));let n=[],r=``;for(let e=0;e<t.length;e+=1){let i=t[e],a=t[e+1];if(i===`\\`&&a===`|`){r+=`|`,e+=1;continue}if(i===`|`){n.push(r.trim()),r=``;continue}r+=i}return n.push(r.trim()),n}function parseTableAlignments(e){let t=[];for(let n of e){let e=n.match(/^(:)?-{3,}(:)?$/);if(e==null)return;let[,r,i]=e;t.push(r!=null&&i!=null?`center`:i==null?`left`:`right`)}return t}function normalizeTableRow(e,t){return Array.from({length:t},(t,n)=>e[n]??``)}function renderTable(n){let r=n.header.map(e=>`${ansi.bold}${renderInlineMarkdown(e)}${ansi.boldOff}`),i=n.rows.map(e=>e.map(renderInlineMarkdown)),a=[r,...i],o=n.alignments.map((t,n)=>Math.max(3,...a.map(t=>visibleLength(t[n]??``))));return[formatTableRow(r,o,n.alignments),o.map(e=>`─`.repeat(e)).join(` `),...i.map(e=>formatTableRow(e,o,n.alignments))]}function formatTableRow(e,t,n){return e.map((e,r)=>alignTableCell(e,t[r]??0,n[r]??`left`)).join(` `)}function alignTableCell(t,n,r){let i=Math.max(0,n-visibleLength(t));if(r===`right`)return`${` `.repeat(i)}${t}`;if(r===`center`){let e=Math.floor(i/2),n=i-e;return`${` `.repeat(e)}${t}${` `.repeat(n)}`}return`${t}${` `.repeat(i)}`}export{renderMarkdown};
@@ -6,7 +6,7 @@
6
6
  "@chat-adapter/state-memory": "4.29.0",
7
7
  "chokidar": "5.0.0",
8
8
  "commander": "14.0.3",
9
- "experimental-ai-sdk-code-mode": "1.0.10",
9
+ "experimental-ai-sdk-code-mode": "1.0.14",
10
10
  "@ai-sdk/google": "4.0.0-canary.75",
11
11
  "gray-matter": "4.0.3",
12
12
  "jose": "6.2.3",
@@ -28,5 +28,5 @@
28
28
  "zod": "4.4.3",
29
29
  "zod-validation-error": "5.0.0"
30
30
  },
31
- "scriptHash": "d058df06982d76ea3f28a17d85e12bfd9d19a58aede5830e27938e6ee911c5f4"
31
+ "scriptHash": "ed1694db63648dbdf127975ce45e76877eb96ced163cb0c22b0d84b23f75fe42"
32
32
  }
@@ -0,0 +1,71 @@
1
+ import { generateText, type ModelMessage, streamText } from "ai";
2
+ import type { CodeModeApprovalInterrupt, CodeModeApprovalResponse, CodeModeOptions, CodeModeToolExecutionOptions, CodeModeToolSet } from "./types.js";
3
+ /**
4
+ * Returns true when a value is a code-mode approval interruption.
5
+ *
6
+ * An approval interruption is a `CodeModeInterrupt` whose payload kind is the
7
+ * reserved approval kind, so this also implies `isCodeModeInterrupt`.
8
+ */
9
+ export declare function isCodeModeApprovalInterrupt(value: unknown): value is CodeModeApprovalInterrupt;
10
+ /**
11
+ * Continues a code-mode invocation that previously returned an approval
12
+ * interruption.
13
+ *
14
+ * This is a thin adapter over `continueCodeModeInterrupt`: it validates the AI
15
+ * SDK approval response, maps it to a boolean approval resolution, and forces
16
+ * interrupt-mode approval so any further nested approvals interrupt as well.
17
+ */
18
+ export declare function continueCodeModeApproval({ interrupt, approvalResponse, tools, options, toolExecutionOptions, }: {
19
+ interrupt: CodeModeApprovalInterrupt;
20
+ approvalResponse: CodeModeApprovalResponse;
21
+ tools: CodeModeToolSet;
22
+ options?: CodeModeOptions;
23
+ toolExecutionOptions?: Partial<CodeModeToolExecutionOptions>;
24
+ }): Promise<unknown>;
25
+ /**
26
+ * Builds AI SDK model messages that expose a code-mode nested approval as an
27
+ * approval request for the original inner tool name and input.
28
+ *
29
+ * The interruption id is used as the AI SDK approval id.
30
+ */
31
+ export declare function toCodeModeApprovalMessages(interrupt: CodeModeApprovalInterrupt): ModelMessage[];
32
+ /**
33
+ * Finds the AI SDK approval response for a stored code-mode approval
34
+ * interruption in a model message list.
35
+ */
36
+ export declare function getCodeModeApprovalResponse(messages: ModelMessage[], interrupt: CodeModeApprovalInterrupt): CodeModeApprovalResponse | undefined;
37
+ /**
38
+ * Finds a code-mode approval interruption in an AI SDK result-like object.
39
+ */
40
+ export declare function getCodeModeApprovalInterrupt(result: unknown): CodeModeApprovalInterrupt | undefined;
41
+ /**
42
+ * Runs `generateText` and annotates the returned result with any code-mode
43
+ * approval interruption and AI SDK approval messages.
44
+ */
45
+ export declare function generateTextWithCodeModeApprovals(options: Parameters<typeof generateText>[0]): Promise<Awaited<ReturnType<typeof generateText>> & {
46
+ codeModeApproval: CodeModeApprovalInterrupt | undefined;
47
+ codeModeApprovalMessages: ModelMessage[];
48
+ }>;
49
+ /**
50
+ * Runs `streamText` and adds a `codeModeApproval` promise that resolves to any
51
+ * approval interruption found in the final tool results.
52
+ */
53
+ export declare function streamTextWithCodeModeApprovals(options: Parameters<typeof streamText>[0]): ReturnType<typeof streamText> & {
54
+ codeModeApproval: Promise<CodeModeApprovalInterrupt | undefined>;
55
+ };
56
+ /**
57
+ * Wraps a ToolLoopAgent-like object so `generate`/`stream` results expose
58
+ * code-mode approval interruption metadata.
59
+ */
60
+ export declare function wrapToolLoopAgentForCodeModeApprovals<T extends {
61
+ generate?: (...args: any[]) => Promise<unknown>;
62
+ stream?: (...args: any[]) => Promise<unknown>;
63
+ }>(agent: T): T;
64
+ /**
65
+ * Adds approval interruption metadata to a result-like object.
66
+ */
67
+ export declare function attachCodeModeApprovalResult<T>(result: T): T & {
68
+ codeModeApproval: CodeModeApprovalInterrupt | undefined;
69
+ codeModeApprovalMessages: ModelMessage[];
70
+ };
71
+ //# sourceMappingURL=approval-continuation.d.ts.map
@@ -0,0 +1,32 @@
1
+ import type { CodeModeApprovalInterruptPayload, CodeModeApprovalResolution, CodeModeApprovalResponse, CodeModeInterruptPayload } from "./types.js";
2
+ /**
3
+ * Reserved interruption payload kind for built-in nested tool approvals.
4
+ *
5
+ * Approval is implemented as a generic code-mode interruption tagged with this
6
+ * kind. Host tools must not use this kind for their own
7
+ * `requestCodeModeInterrupt` payloads.
8
+ *
9
+ * @internal
10
+ */
11
+ export declare const CODE_MODE_TOOL_APPROVAL_KIND = "ai-sdk-code-mode/tool-approval";
12
+ /**
13
+ * Returns true when a generic interruption payload is a built-in approval.
14
+ *
15
+ * @internal
16
+ */
17
+ export declare function isCodeModeApprovalInterruptPayload(payload: CodeModeInterruptPayload): payload is CodeModeApprovalInterruptPayload;
18
+ /**
19
+ * Validates an approval response from model/client history at runtime.
20
+ *
21
+ * @internal
22
+ */
23
+ export declare function assertCodeModeApprovalResponse(value: unknown): asserts value is CodeModeApprovalResponse;
24
+ /**
25
+ * Validates and normalizes the resolution used to resume an approval
26
+ * interruption. The generic interrupt machinery passes this resolution through
27
+ * `continueCodeModeInterrupt`; approval interrupts require a boolean decision.
28
+ *
29
+ * @internal
30
+ */
31
+ export declare function normalizeApprovalResolution(resolution: unknown): CodeModeApprovalResolution;
32
+ //# sourceMappingURL=approval.d.ts.map
@@ -0,0 +1,16 @@
1
+ import type { Tool } from "ai";
2
+ import type { CodeModeOptions, CodeModeToolInput, CodeModeToolSet } from "./types.js";
3
+ /**
4
+ * Creates an AI SDK tool that executes code-mode TypeScript in an isolated
5
+ * sandbox.
6
+ *
7
+ * The generated tool description includes sandbox rules, available host tool
8
+ * signatures, return types when an AI SDK output schema is present, call
9
+ * examples, and fetch policy details when fetch is enabled.
10
+ *
11
+ * @param tools - Host tools that sandboxed code can call through `tools.name(input)`.
12
+ * @param options - Runtime, fetch, and approval options for every invocation of this tool.
13
+ * @returns An AI SDK tool whose input is `{ js: string }` and whose output is the sandbox return value.
14
+ */
15
+ export declare function createCodeModeTool(tools: CodeModeToolSet, options?: CodeModeOptions): Tool<CodeModeToolInput, unknown>;
16
+ //# sourceMappingURL=code-mode-tool.d.ts.map
@@ -0,0 +1,33 @@
1
+ import type { CodeModeContinuation, UnsignedCodeModeContinuation } from "./types.js";
2
+ /**
3
+ * Sets the process-global continuation signing key.
4
+ *
5
+ * Hosts that need approval or interrupt continuations to survive process
6
+ * restarts must configure the same secret before creating and resuming
7
+ * continuations. Calling without arguments restores a random process-local key.
8
+ *
9
+ * @param key - Secret key bytes or UTF-8 string, or `undefined` to reset.
10
+ * @param options - Optional continuation signing policy.
11
+ */
12
+ export declare function setCodeModeContinuationSigningKey(key?: string | Uint8Array, options?: {
13
+ maxAgeMs?: number;
14
+ }): void;
15
+ /**
16
+ * Signs a host-created continuation as an unforgeable bearer capability.
17
+ *
18
+ * @internal
19
+ */
20
+ export declare function signCodeModeContinuation(continuation: UnsignedCodeModeContinuation): CodeModeContinuation;
21
+ /**
22
+ * Verifies that a continuation was issued by this host and has not expired.
23
+ *
24
+ * @internal
25
+ */
26
+ export declare function verifyCodeModeContinuation(continuation: CodeModeContinuation): void;
27
+ /**
28
+ * Returns whether a value is a currently valid signed continuation.
29
+ *
30
+ * @internal
31
+ */
32
+ export declare function hasValidCodeModeContinuationCapability(value: unknown): value is CodeModeContinuation;
33
+ //# sourceMappingURL=continuation-capability.d.ts.map