experimental-ash 0.62.0 → 0.64.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 +24 -0
- package/dist/docs/public/advanced/auth-and-route-protection.mdx +26 -20
- package/dist/docs/public/advanced/hooks.mdx +8 -2
- package/dist/docs/public/advanced/runs-and-streaming.md +6 -3
- package/dist/docs/public/advanced/typescript-api.md +32 -0
- package/dist/docs/public/channels/ash.mdx +5 -1
- package/dist/docs/public/channels/custom.mdx +23 -0
- package/dist/docs/public/frontend/README.md +8 -4
- package/dist/docs/public/frontend/meta.json +9 -1
- package/dist/docs/public/frontend/nextjs.md +4 -4
- package/dist/docs/public/frontend/nuxt.md +168 -0
- package/dist/docs/public/frontend/sveltekit.md +177 -0
- package/dist/docs/public/frontend/use-ash-agent-svelte.md +185 -0
- package/dist/docs/public/frontend/use-ash-agent-vue.md +236 -0
- package/dist/docs/public/frontend/use-ash-agent.md +14 -14
- package/dist/docs/public/getting-started.mdx +2 -0
- package/dist/src/channel/websocket-upgrade-server.d.ts +26 -0
- package/dist/src/channel/websocket-upgrade-server.js +1 -0
- package/dist/src/chunks/use-ash-agent-DzoSHUCb.js +1197 -0
- package/dist/src/chunks/use-ash-agent-KtjpWd5l.js +1229 -0
- package/dist/src/client/ash-agent-store.d.ts +61 -0
- package/dist/src/client/ash-agent-store.js +2 -0
- package/dist/src/client/client-error.js +1 -1
- package/dist/src/client/index.d.ts +2 -0
- package/dist/src/client/index.js +1 -1
- package/dist/src/compiled/.vendor-stamp.json +3 -3
- package/dist/src/compiled/@chat-adapter/slack/index.js +25 -25
- package/dist/src/compiled/@workflow/core/events-consumer.d.ts +8 -0
- package/dist/src/compiled/@workflow/core/index.js +2 -2
- package/dist/src/compiled/@workflow/core/runtime/constants.d.ts +1 -0
- package/dist/src/compiled/@workflow/core/runtime.js +29 -29
- package/dist/src/compiled/@workflow/core/version.d.ts +1 -1
- package/dist/src/compiled/@workflow/core/workflow.js +1 -1
- package/dist/src/compiled/@workflow/errors/error-codes.d.ts +2 -0
- package/dist/src/compiled/@workflow/errors/index.d.ts +14 -0
- package/dist/src/compiled/@workflow/errors/index.js +1 -1
- package/dist/src/compiled/@workflow/world/queue.d.ts +8 -0
- package/dist/src/compiled/_chunks/workflow/dist-zpK2YVVA.js +3 -0
- package/dist/src/compiled/_chunks/workflow/resume-hook-BFK9mgsb.js +12 -0
- package/dist/src/compiled/_chunks/workflow/{sleep-Bg0t23kF.js → sleep-CeJckNg2.js} +1 -1
- package/dist/src/compiled/_chunks/workflow/{symbols-u476uwyR.js → symbols-BWCAoPHE.js} +1 -1
- package/dist/src/internal/application/package.d.ts +1 -0
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/internal/nitro/host/build-application.js +1 -1
- package/dist/src/internal/nitro/host/channel-routes.js +2 -2
- package/dist/src/internal/workflow-bundle/ash-service-route-output.js +11 -1
- package/dist/src/packages/ash-scaffold/src/channels.js +1 -1
- package/dist/src/packages/ash-scaffold/src/web-template.js +5 -16
- package/dist/src/public/channels/auth.d.ts +45 -3
- package/dist/src/public/channels/auth.js +1 -1
- package/dist/src/public/channels/index.d.ts +1 -0
- package/dist/src/public/channels/index.js +1 -1
- package/dist/src/public/channels/slack/inbound.d.ts +3 -1
- package/dist/src/public/channels/slack/inbound.js +1 -1
- package/dist/src/public/nuxt/dev-server.d.ts +24 -0
- package/dist/src/public/nuxt/dev-server.js +1 -0
- package/dist/src/public/nuxt/index.d.ts +1 -0
- package/dist/src/public/nuxt/index.js +1 -0
- package/dist/src/public/nuxt/module.d.ts +31 -0
- package/dist/src/public/nuxt/module.js +1 -0
- package/dist/src/public/nuxt/routing.d.ts +55 -0
- package/dist/src/public/nuxt/routing.js +1 -0
- package/dist/src/public/nuxt/vercel-json.d.ts +17 -0
- package/dist/src/public/nuxt/vercel-json.js +1 -0
- package/dist/src/public/sveltekit/dev-server.d.ts +24 -0
- package/dist/src/public/sveltekit/dev-server.js +1 -0
- package/dist/src/public/sveltekit/index.d.ts +39 -0
- package/dist/src/public/sveltekit/index.js +1 -0
- package/dist/src/public/sveltekit/routing.d.ts +32 -0
- package/dist/src/public/sveltekit/routing.js +1 -0
- package/dist/src/public/sveltekit/vercel-json.d.ts +17 -0
- package/dist/src/public/sveltekit/vercel-json.js +1 -0
- package/dist/src/react/use-ash-agent.d.ts +5 -27
- package/dist/src/react/use-ash-agent.js +1 -2
- package/dist/src/svelte/index.d.ts +3 -0
- package/dist/src/svelte/index.js +3 -0
- package/dist/src/svelte/use-ash-agent.d.ts +80 -0
- package/dist/src/svelte/use-ash-agent.js +3 -0
- package/dist/src/vue/index.d.ts +3 -0
- package/dist/src/vue/index.js +3 -0
- package/dist/src/vue/use-ash-agent.d.ts +78 -0
- package/dist/src/vue/use-ash-agent.js +3 -0
- package/package.json +51 -6
- package/dist/src/compiled/_chunks/workflow/dist-C4EHshZE.js +0 -3
- package/dist/src/compiled/_chunks/workflow/resume-hook-BlALLgSA.js +0 -12
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "useAshAgent (Svelte)"
|
|
3
|
+
description: "Svelte 5 binding that drives an Ash agent session from the browser."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`useAshAgent()` is a Svelte 5 binding that opens a long-lived Ash session in
|
|
7
|
+
the browser, lets the user send turns, and projects every stream event into
|
|
8
|
+
rune-friendly reactive data for your template.
|
|
9
|
+
|
|
10
|
+
The simplest usage:
|
|
11
|
+
|
|
12
|
+
```svelte
|
|
13
|
+
<script lang="ts">
|
|
14
|
+
import { useAshAgent } from "experimental-ash/svelte";
|
|
15
|
+
|
|
16
|
+
const agent = useAshAgent();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
{#each agent.data.messages as message}
|
|
20
|
+
<p>{message.role}: {JSON.stringify(message.parts)}</p>
|
|
21
|
+
{/each}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## What It Returns
|
|
25
|
+
|
|
26
|
+
`useAshAgent()` returns an object with reactive properties and action methods:
|
|
27
|
+
|
|
28
|
+
| Property | Type | Description |
|
|
29
|
+
| ------------- | -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `data` | `TData` | Projected state built by reducing every stream event through the reducer. With the default reducer this is `AshMessageData` containing a `messages` array. |
|
|
31
|
+
| `status` | `UseAshAgentStatus` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"` |
|
|
32
|
+
| `error` | `Error \| undefined` | Last transport-level error |
|
|
33
|
+
| `events` | `readonly HandleMessageStreamEvent[]` | Raw server events for this session |
|
|
34
|
+
| `session` | `SessionState` | Snapshot of session state |
|
|
35
|
+
| `send` | `(input, options?) => Promise<void>` | Send a turn with full options |
|
|
36
|
+
| `sendMessage` | `(message, options?) => Promise<void>` | Shorthand: send a plain text turn |
|
|
37
|
+
| `stop` | `() => void` | Abort the in-flight request |
|
|
38
|
+
| `reset` | `() => void` | Clear state and start a new session |
|
|
39
|
+
|
|
40
|
+
The state fields are reactive getters. Read them directly from templates,
|
|
41
|
+
`$derived`, or `$effect`; do not prefix them with `$` like Svelte stores.
|
|
42
|
+
|
|
43
|
+
## Send A Message
|
|
44
|
+
|
|
45
|
+
```svelte
|
|
46
|
+
<script lang="ts">
|
|
47
|
+
import { useAshAgent } from "experimental-ash/svelte";
|
|
48
|
+
|
|
49
|
+
const agent = useAshAgent();
|
|
50
|
+
let message = $state("");
|
|
51
|
+
|
|
52
|
+
async function handleSubmit() {
|
|
53
|
+
const text = message.trim();
|
|
54
|
+
if (!text) return;
|
|
55
|
+
message = "";
|
|
56
|
+
await agent.sendMessage(text);
|
|
57
|
+
}
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<form onsubmit={(event) => {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
void handleSubmit();
|
|
63
|
+
}}>
|
|
64
|
+
<input bind:value={message} placeholder="Type a message..." />
|
|
65
|
+
<button type="submit">Send</button>
|
|
66
|
+
</form>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Send Attachments
|
|
70
|
+
|
|
71
|
+
Use `send()` for structured input. Attachments use the AI SDK `UserContent`
|
|
72
|
+
format:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
await agent.send({
|
|
76
|
+
message: [
|
|
77
|
+
{ type: "text", text: "Describe this image." },
|
|
78
|
+
{ type: "file", data: fileBytes, mimeType: "image/png" },
|
|
79
|
+
],
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Respond To Human-In-The-Loop Prompts
|
|
84
|
+
|
|
85
|
+
When the agent requests human input (tool approvals, confirmations), the
|
|
86
|
+
`inputRequest` shows up inside `message.parts` on `dynamic-tool` parts. Send
|
|
87
|
+
`inputResponses` back:
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
await agent.send({
|
|
91
|
+
inputResponses: [{ requestId: inputRequest.requestId, optionId: "approve" }],
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Stop And Reset
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
agent.stop(); // abort the in-flight stream
|
|
99
|
+
agent.reset(); // wipe state and create a new session
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Resumable Sessions
|
|
103
|
+
|
|
104
|
+
Pass `initialSession` to restore a session from a prior page load:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
const agent = useAshAgent({
|
|
108
|
+
initialSession: savedSessionState,
|
|
109
|
+
initialEvents: savedEvents,
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Add Per-Turn Client Context
|
|
114
|
+
|
|
115
|
+
Use `prepareSend` to attach short-lived page state to each turn:
|
|
116
|
+
|
|
117
|
+
```ts
|
|
118
|
+
const agent = useAshAgent({
|
|
119
|
+
prepareSend(input) {
|
|
120
|
+
return {
|
|
121
|
+
...input,
|
|
122
|
+
clientContext: {
|
|
123
|
+
pathname: window.location.pathname,
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Custom Host And Auth
|
|
131
|
+
|
|
132
|
+
Use `host` for non-standard routing, and `auth` or `headers` for credentials:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
const agent = useAshAgent({
|
|
136
|
+
host: "https://agent.example.com",
|
|
137
|
+
headers: async () => ({
|
|
138
|
+
authorization: `Bearer ${await getAccessToken()}`,
|
|
139
|
+
}),
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Custom Reducer
|
|
144
|
+
|
|
145
|
+
Pass a custom `reducer` to control how stream events project into `data`:
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { useAshAgent, type AshAgentReducer } from "experimental-ash/svelte";
|
|
149
|
+
|
|
150
|
+
interface MyData {
|
|
151
|
+
turns: number;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const reducer: AshAgentReducer<MyData> = {
|
|
155
|
+
initial: () => ({ turns: 0 }),
|
|
156
|
+
reduce: (data, event) => {
|
|
157
|
+
if (event.type === "turn.completed") {
|
|
158
|
+
return { turns: data.turns + 1 };
|
|
159
|
+
}
|
|
160
|
+
return data;
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const agent = useAshAgent({ reducer });
|
|
165
|
+
// agent.data is MyData
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Lifecycle Callbacks
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const agent = useAshAgent({
|
|
172
|
+
onError: (error) => console.error(error),
|
|
173
|
+
onEvent: (event) => console.log(event.type),
|
|
174
|
+
onFinish: (snapshot) => console.log("done", snapshot.status),
|
|
175
|
+
onSessionChange: (session) => {
|
|
176
|
+
localStorage.setItem("ash-session", JSON.stringify(session));
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## What To Read Next
|
|
182
|
+
|
|
183
|
+
- [SvelteKit](./sveltekit.md) - framework setup
|
|
184
|
+
- [Sessions And Streaming](../advanced/runs-and-streaming.md) - under the hood
|
|
185
|
+
- [TypeScript API](../advanced/typescript-api.md) - client exports
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "useAshAgent (Vue)"
|
|
3
|
+
description: "Vue composable that drives an Ash agent session from the browser."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`useAshAgent()` is a Vue composable that opens a long-lived Ash session
|
|
7
|
+
in the browser, lets the user send turns, and projects every stream event
|
|
8
|
+
into reactive data for your template.
|
|
9
|
+
|
|
10
|
+
The simplest usage:
|
|
11
|
+
|
|
12
|
+
```vue
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { useAshAgent } from "experimental-ash/vue";
|
|
15
|
+
|
|
16
|
+
const { data } = useAshAgent();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div v-for="message in data.messages" :key="message.id">
|
|
21
|
+
<p>{{ message.role }}: {{ message.parts }}</p>
|
|
22
|
+
</div>
|
|
23
|
+
</template>
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## What It Returns
|
|
27
|
+
|
|
28
|
+
`useAshAgent()` returns an object of computed refs and action methods:
|
|
29
|
+
|
|
30
|
+
| Property | Type | Description |
|
|
31
|
+
| ------------- | -------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
32
|
+
| `data` | `ComputedRef<TData>` | Projected state built by reducing every stream event through the reducer. With the default reducer this is `AshMessageData` containing a `messages` array. |
|
|
33
|
+
| `status` | `ComputedRef<UseAshAgentStatus>` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"` |
|
|
34
|
+
| `error` | `ComputedRef<Error \| undefined>` | Last transport-level error |
|
|
35
|
+
| `events` | `ComputedRef<readonly HandleMessageStreamEvent[]>` | Raw server events for this session |
|
|
36
|
+
| `session` | `ComputedRef<SessionState>` | Snapshot of session state |
|
|
37
|
+
| `send` | `(input, options?) => Promise<void>` | Send a turn with full options |
|
|
38
|
+
| `sendMessage` | `(message, options?) => Promise<void>` | Shorthand: send a plain text turn |
|
|
39
|
+
| `stop` | `() => void` | Abort the in-flight request |
|
|
40
|
+
| `reset` | `() => void` | Clear state and start a new session |
|
|
41
|
+
|
|
42
|
+
The state properties (`data`, `status`, `error`, `events`, `session`) are
|
|
43
|
+
`ComputedRef`s; the rest are methods. Destructure whatever you need — refs keep
|
|
44
|
+
their reactivity through destructuring — and read them with `.value` in
|
|
45
|
+
`<script>` or unwrapped (no `.value`) in `<template>`.
|
|
46
|
+
|
|
47
|
+
## Send A Message
|
|
48
|
+
|
|
49
|
+
```vue
|
|
50
|
+
<script setup lang="ts">
|
|
51
|
+
import { useAshAgent } from "experimental-ash/vue";
|
|
52
|
+
|
|
53
|
+
const { sendMessage } = useAshAgent();
|
|
54
|
+
const message = ref("");
|
|
55
|
+
|
|
56
|
+
async function handleSubmit() {
|
|
57
|
+
const text = message.value.trim();
|
|
58
|
+
if (!text) return;
|
|
59
|
+
message.value = "";
|
|
60
|
+
await sendMessage(text);
|
|
61
|
+
}
|
|
62
|
+
</script>
|
|
63
|
+
|
|
64
|
+
<template>
|
|
65
|
+
<form @submit.prevent="handleSubmit">
|
|
66
|
+
<input v-model="message" placeholder="Type a message..." />
|
|
67
|
+
<button type="submit">Send</button>
|
|
68
|
+
</form>
|
|
69
|
+
</template>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Send Attachments
|
|
73
|
+
|
|
74
|
+
Use `send()` for structured input. Attachments use the AI SDK `UserContent`
|
|
75
|
+
format:
|
|
76
|
+
|
|
77
|
+
```vue
|
|
78
|
+
<script setup lang="ts">
|
|
79
|
+
import { useAshAgent } from "experimental-ash/vue";
|
|
80
|
+
|
|
81
|
+
const { send } = useAshAgent();
|
|
82
|
+
|
|
83
|
+
async function onFileChange(event: Event) {
|
|
84
|
+
const file = (event.target as HTMLInputElement).files?.[0];
|
|
85
|
+
if (!file) return;
|
|
86
|
+
await send({
|
|
87
|
+
message: [
|
|
88
|
+
{ type: "text", text: "Describe this image." },
|
|
89
|
+
{ type: "file", data: new Uint8Array(await file.arrayBuffer()), mediaType: file.type },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
</script>
|
|
94
|
+
|
|
95
|
+
<template>
|
|
96
|
+
<input type="file" accept="image/*" @change="onFileChange" />
|
|
97
|
+
</template>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Respond To Human-In-The-Loop Prompts
|
|
101
|
+
|
|
102
|
+
When the agent requests human input (tool approvals, confirmations),
|
|
103
|
+
the `inputRequest` shows up inside `message.parts` on `dynamic-tool` parts.
|
|
104
|
+
Send `inputResponses` back:
|
|
105
|
+
|
|
106
|
+
```vue
|
|
107
|
+
<script setup lang="ts">
|
|
108
|
+
import { useAshAgent, type AshDynamicToolPart } from "experimental-ash/vue";
|
|
109
|
+
|
|
110
|
+
const props = defineProps<{ part: AshDynamicToolPart }>();
|
|
111
|
+
|
|
112
|
+
const { send } = useAshAgent();
|
|
113
|
+
|
|
114
|
+
const inputRequest = computed(() => props.part.toolMetadata?.ash?.inputRequest);
|
|
115
|
+
|
|
116
|
+
function respond(optionId: string) {
|
|
117
|
+
const request = inputRequest.value;
|
|
118
|
+
if (!request) return;
|
|
119
|
+
void send({ inputResponses: [{ requestId: request.requestId, optionId }] });
|
|
120
|
+
}
|
|
121
|
+
</script>
|
|
122
|
+
|
|
123
|
+
<template>
|
|
124
|
+
<div v-if="inputRequest">
|
|
125
|
+
<p>{{ inputRequest.prompt }}</p>
|
|
126
|
+
<button v-for="option in inputRequest.options" :key="option.id" @click="respond(option.id)">
|
|
127
|
+
{{ option.label }}
|
|
128
|
+
</button>
|
|
129
|
+
</div>
|
|
130
|
+
</template>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Stop And Reset
|
|
134
|
+
|
|
135
|
+
Abort the in-flight stream, or wipe state and start a fresh session:
|
|
136
|
+
|
|
137
|
+
```vue
|
|
138
|
+
<script setup lang="ts">
|
|
139
|
+
import { useAshAgent } from "experimental-ash/vue";
|
|
140
|
+
|
|
141
|
+
const { status, stop, reset } = useAshAgent();
|
|
142
|
+
|
|
143
|
+
const isBusy = computed(() => status.value === "submitted" || status.value === "streaming");
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<template>
|
|
147
|
+
<button :disabled="!isBusy" @click="stop()">Stop</button>
|
|
148
|
+
<button @click="reset()">Reset</button>
|
|
149
|
+
</template>
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Resumable Sessions
|
|
153
|
+
|
|
154
|
+
Pass `initialSession` to restore a session from a prior page load:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
const agent = useAshAgent({
|
|
158
|
+
initialSession: savedSessionState,
|
|
159
|
+
initialEvents: savedEvents,
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Add Per-Turn Client Context
|
|
164
|
+
|
|
165
|
+
Use `prepareSend` to attach short-lived page state to each turn:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const agent = useAshAgent({
|
|
169
|
+
prepareSend(input) {
|
|
170
|
+
return {
|
|
171
|
+
...input,
|
|
172
|
+
clientContext: {
|
|
173
|
+
pathname: window.location.pathname,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Custom Host And Auth
|
|
181
|
+
|
|
182
|
+
Use `host` for non-standard routing, and `auth` or `headers` for
|
|
183
|
+
credentials:
|
|
184
|
+
|
|
185
|
+
```ts
|
|
186
|
+
const agent = useAshAgent({
|
|
187
|
+
host: "https://agent.example.com",
|
|
188
|
+
headers: async () => ({
|
|
189
|
+
authorization: `Bearer ${await getAccessToken()}`,
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Custom Reducer
|
|
195
|
+
|
|
196
|
+
Pass a custom `reducer` to control how stream events project into `data`:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { useAshAgent, type AshAgentReducer } from "experimental-ash/vue";
|
|
200
|
+
|
|
201
|
+
interface MyData {
|
|
202
|
+
turns: number;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const reducer: AshAgentReducer<MyData> = {
|
|
206
|
+
initial: () => ({ turns: 0 }),
|
|
207
|
+
reduce: (data, event) => {
|
|
208
|
+
if (event.type === "turn.completed") {
|
|
209
|
+
return { turns: data.turns + 1 };
|
|
210
|
+
}
|
|
211
|
+
return data;
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const { data } = useAshAgent({ reducer });
|
|
216
|
+
// data.value is MyData
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Lifecycle Callbacks
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
const agent = useAshAgent({
|
|
223
|
+
onError: (error) => console.error(error),
|
|
224
|
+
onEvent: (event) => console.log(event.type),
|
|
225
|
+
onFinish: (snapshot) => console.log("done", snapshot.status),
|
|
226
|
+
onSessionChange: (session) => {
|
|
227
|
+
localStorage.setItem("ash-session", JSON.stringify(session));
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## What To Read Next
|
|
233
|
+
|
|
234
|
+
- [Nuxt](./nuxt.md) — module setup
|
|
235
|
+
- [Sessions And Streaming](../advanced/runs-and-streaming.md) — under the hood
|
|
236
|
+
- [TypeScript API](../advanced/typescript-api.md) — client exports
|
|
@@ -60,17 +60,17 @@ your component can call the agent without configuring a separate URL.
|
|
|
60
60
|
|
|
61
61
|
`useAshAgent()` returns the current UI state plus commands:
|
|
62
62
|
|
|
63
|
-
| Field | What it is
|
|
64
|
-
| ------------- |
|
|
65
|
-
| `data` | Projected UI state from the reducer. Defaults to `{ messages }`.
|
|
66
|
-
| `status` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. Drives the composer.
|
|
67
|
-
| `error` | The last `Error` thrown, if any.
|
|
68
|
-
| `events` | Raw Ash stream events for this session — see [Sessions And Streaming](../runs-and-streaming.md). |
|
|
69
|
-
| `session` | Serializable session cursor (`sessionId`, `continuationToken`, `streamIndex`).
|
|
70
|
-
| `sendMessage` | Send plain text.
|
|
71
|
-
| `send` | Send the full turn payload (multi-part messages, HITL responses).
|
|
72
|
-
| `stop` | Abort the active request.
|
|
73
|
-
| `reset` | Clear local events, data, errors, and the local session cursor.
|
|
63
|
+
| Field | What it is |
|
|
64
|
+
| ------------- | --------------------------------------------------------------------------------------------------------- |
|
|
65
|
+
| `data` | Projected UI state from the reducer. Defaults to `{ messages }`. |
|
|
66
|
+
| `status` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. Drives the composer. |
|
|
67
|
+
| `error` | The last `Error` thrown, if any. |
|
|
68
|
+
| `events` | Raw Ash stream events for this session — see [Sessions And Streaming](../advanced/runs-and-streaming.md). |
|
|
69
|
+
| `session` | Serializable session cursor (`sessionId`, `continuationToken`, `streamIndex`). |
|
|
70
|
+
| `sendMessage` | Send plain text. |
|
|
71
|
+
| `send` | Send the full turn payload (multi-part messages, HITL responses). |
|
|
72
|
+
| `stop` | Abort the active request. |
|
|
73
|
+
| `reset` | Clear local events, data, errors, and the local session cursor. |
|
|
74
74
|
|
|
75
75
|
Most chat UIs only need `data.messages` and `status`. Reach for `events` when
|
|
76
76
|
you want to render lower-level activity (tool calls, reasoning deltas) the
|
|
@@ -286,7 +286,7 @@ const agent = useAshAgent({
|
|
|
286
286
|
```
|
|
287
287
|
|
|
288
288
|
`agent.data.tools` now drives the UI. The reducer sees the full Ash event
|
|
289
|
-
stream — see [Sessions And Streaming](../runs-and-streaming.md) — plus
|
|
289
|
+
stream — see [Sessions And Streaming](../advanced/runs-and-streaming.md) — plus
|
|
290
290
|
client-side projection events (`client.message.submitted`,
|
|
291
291
|
`client.message.failed`, `client.input.responded`) for optimistic updates.
|
|
292
292
|
|
|
@@ -323,6 +323,6 @@ To change credentials without remounting, pass a function to `auth` or
|
|
|
323
323
|
## What To Read Next
|
|
324
324
|
|
|
325
325
|
- [Next.js](./nextjs.md)
|
|
326
|
-
- [Sessions And Streaming](../runs-and-streaming.md)
|
|
326
|
+
- [Sessions And Streaming](../advanced/runs-and-streaming.md)
|
|
327
327
|
- [Human In The Loop](../human-in-the-loop.md)
|
|
328
|
-
- [Hooks](../hooks.
|
|
328
|
+
- [Hooks](../advanced/hooks.mdx)
|
|
@@ -174,3 +174,5 @@ curl -X POST http://127.0.0.1:3000/ash/v1/session/<sessionId> \
|
|
|
174
174
|
- [Skills](./skills.md) for on-demand procedures
|
|
175
175
|
- [Tools](./tools.mdx) for typed integrations
|
|
176
176
|
- [Sessions And Streaming](./runs-and-streaming.md) for the durable session model
|
|
177
|
+
- [Nuxt Integration](./frontend/nuxt.md) for Nuxt apps
|
|
178
|
+
- [`useAshAgent` (Vue)](./frontend/use-ash-agent-vue.md) for Vue composable usage
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import type { WebSocketRouteHandler } from "#channel/routes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Escape hatch for SDKs and frameworks that need a Node HTTP server-shaped
|
|
5
|
+
* upgrade target inside an Ash `WS()` route.
|
|
6
|
+
*
|
|
7
|
+
* Prefer normal `WS()` lifecycle hooks for Ash-owned websocket behavior. Use
|
|
8
|
+
* this bridge only when an integration expects to register
|
|
9
|
+
* `httpServer.on("upgrade", ...)` handlers itself. The server is intentionally
|
|
10
|
+
* not listening on a port; Ash forwards only the matched route's raw Node
|
|
11
|
+
* upgrade into it.
|
|
12
|
+
*/
|
|
13
|
+
export interface WebSocketUpgradeServerBridge {
|
|
14
|
+
readonly route: WebSocketRouteHandler;
|
|
15
|
+
readonly server: Server;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates an escape-hatch Node HTTP server facade plus a `WS()` route handler
|
|
19
|
+
* that forwards matched raw upgrade events into that server.
|
|
20
|
+
*
|
|
21
|
+
* Prefer authoring websocket behavior directly with `WS()` hooks. Reach for
|
|
22
|
+
* this only when an SDK or framework binds to `http.Server` upgrade events,
|
|
23
|
+
* such as `engine.attach(server, path, ...)`. It works only on hosts where
|
|
24
|
+
* Nitro exposes a Node upgrade tuple for the matched WebSocket route.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createWebSocketUpgradeServer(): WebSocketUpgradeServerBridge;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createServer}from"node:http";function createWebSocketUpgradeServer(){let t=createServer();return{route:createWebSocketUpgradeRoute(t),server:t}}function createWebSocketUpgradeRoute(e){return()=>({upgrade(t){return dispatchNodeUpgrade(e,t)}})}async function dispatchNodeUpgrade(e,t){let n=resolveNodeUpgrade(t);if(n===null)return Response.json({error:`This WebSocket route cannot expose a Node upgrade event on the current host.`,ok:!1},{status:501});let r=e.listeners(`upgrade`);if(r.length===0)return Response.json({error:`No upgrade listeners are registered on this WebSocket server bridge.`,ok:!1},{status:500});for(let t of r)await Promise.resolve(t.call(e,n.request,n.socket,n.head));return{handled:!0}}function resolveNodeUpgrade(e){let t=e.runtime?.node,n=t?.req,r=t?.upgrade?.socket,i=t?.upgrade?.head;if(n===void 0||r===void 0||i===void 0)return null;if(typeof n.url!=`string`||n.url.length===0){let t=new URL(e.url);n.url=`${t.pathname}${t.search}`}return{head:i,request:n,socket:r}}export{createWebSocketUpgradeServer};
|