eve 0.10.0 → 0.11.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 +11 -0
- package/README.md +14 -0
- package/dist/src/cli/banner.d.ts +6 -3
- package/dist/src/cli/banner.js +1 -1
- package/dist/src/cli/commands/init.js +1 -1
- package/dist/src/cli/dev/tui/agent-header.js +1 -1
- package/dist/src/cli/run.js +1 -1
- package/dist/src/evals/runner/run-evals.d.ts +1 -1
- package/dist/src/harness/input-requests.d.ts +20 -1
- package/dist/src/harness/input-requests.js +1 -1
- package/dist/src/harness/tool-loop.js +1 -1
- package/dist/src/internal/application/package.js +1 -1
- package/dist/src/protocol/message.d.ts +11 -2
- package/dist/src/protocol/message.js +2 -2
- package/dist/src/public/next/index.d.ts +1 -7
- package/dist/src/public/next/index.js +1 -1
- package/dist/src/public/next/vercel-output-config.js +1 -1
- package/dist/src/setup/scaffold/create/project.js +1 -1
- package/dist/src/setup/scaffold/create/web-template.d.ts +1 -1
- package/dist/src/setup/scaffold/create/web-template.js +27 -4
- package/dist/src/setup/scaffold/update/channels.js +1 -1
- package/docs/README.md +19 -5
- package/docs/agent-config.md +16 -19
- package/docs/channels/custom.mdx +24 -18
- package/docs/channels/discord.mdx +25 -13
- package/docs/channels/eve.mdx +24 -6
- package/docs/channels/github.mdx +41 -10
- package/docs/channels/linear.mdx +29 -36
- package/docs/channels/overview.mdx +16 -4
- package/docs/channels/slack.mdx +59 -32
- package/docs/channels/teams.mdx +19 -7
- package/docs/channels/telegram.mdx +23 -9
- package/docs/channels/twilio.mdx +28 -6
- package/docs/concepts/context-control.md +24 -41
- package/docs/concepts/default-harness.md +19 -9
- package/docs/concepts/execution-model-and-durability.md +14 -14
- package/docs/concepts/security-model.md +32 -8
- package/docs/concepts/sessions-runs-and-streaming.md +11 -9
- package/docs/connections.mdx +49 -29
- package/docs/evals/assertions.mdx +30 -29
- package/docs/evals/cases.mdx +10 -10
- package/docs/evals/judge.mdx +14 -14
- package/docs/evals/overview.mdx +15 -17
- package/docs/evals/reporters.mdx +19 -7
- package/docs/evals/running.mdx +2 -2
- package/docs/evals/targets.mdx +15 -4
- package/docs/getting-started.mdx +70 -31
- package/docs/guides/auth-and-route-protection.md +18 -12
- package/docs/guides/client/continuations.mdx +5 -13
- package/docs/guides/client/messages.mdx +2 -2
- package/docs/guides/client/output-schema.mdx +2 -2
- package/docs/guides/client/overview.mdx +4 -4
- package/docs/guides/client/streaming.mdx +6 -5
- package/docs/guides/deployment.md +22 -20
- package/docs/guides/dev-tui.md +85 -13
- package/docs/guides/dynamic-capabilities.md +24 -8
- package/docs/guides/dynamic-workflows.md +33 -12
- package/docs/guides/frontend/nextjs.mdx +28 -14
- package/docs/guides/frontend/nuxt.mdx +21 -4
- package/docs/guides/frontend/overview.mdx +11 -11
- package/docs/guides/frontend/sveltekit.mdx +20 -3
- package/docs/guides/frontend/use-eve-agent-svelte.mdx +106 -14
- package/docs/guides/frontend/use-eve-agent-vue.mdx +108 -15
- package/docs/guides/hooks.md +14 -60
- package/docs/guides/instrumentation.md +24 -18
- package/docs/guides/remote-agents.md +19 -15
- package/docs/guides/session-context.md +12 -12
- package/docs/guides/state.md +25 -9
- package/docs/instructions.mdx +18 -10
- package/docs/introduction.md +12 -12
- package/docs/legal.md +16 -0
- package/docs/meta.json +1 -0
- package/docs/reference/cli.md +101 -53
- package/docs/reference/meta.json +1 -1
- package/docs/reference/project-layout.md +39 -32
- package/docs/reference/typescript-api.md +1 -1
- package/docs/sandbox.mdx +25 -19
- package/docs/schedules.mdx +16 -14
- package/docs/skills.mdx +8 -6
- package/docs/subagents.mdx +39 -13
- package/docs/tools.mdx +13 -9
- package/docs/tutorial/connect-a-warehouse.mdx +16 -7
- package/docs/tutorial/first-agent.mdx +21 -11
- package/docs/tutorial/guard-the-spend.mdx +11 -9
- package/docs/tutorial/how-it-runs.mdx +10 -8
- package/docs/tutorial/query-sample-data.mdx +8 -8
- package/docs/tutorial/remember-definitions.mdx +5 -5
- package/docs/tutorial/run-analysis.mdx +13 -11
- package/docs/tutorial/ship-it.mdx +70 -14
- package/docs/tutorial/team-playbooks.mdx +33 -10
- package/package.json +1 -1
|
@@ -3,7 +3,7 @@ title: "useEveAgent (Svelte)"
|
|
|
3
3
|
description: "Svelte 5 binding that drives an Eve agent session from the browser."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`useEveAgent()` from `eve/svelte` is the browser side of an Eve session in a Svelte 5 app. Call it once
|
|
6
|
+
`useEveAgent()` from `eve/svelte` is the browser side of an Eve session in a Svelte 5 app. Call it once for a long-lived session you can send turns to, with every stream event projected into rune-friendly reactive data. On SvelteKit, the [Vite plugin](./sveltekit) wires up the routes. The [frontend overview](./overview) covers the model shared across frameworks.
|
|
7
7
|
|
|
8
8
|
## Basic usage
|
|
9
9
|
|
|
@@ -23,16 +23,16 @@ Import from `eve/svelte` and read the reactive getters directly. No `$` prefix:
|
|
|
23
23
|
|
|
24
24
|
## What it returns
|
|
25
25
|
|
|
26
|
-
| Property | Type
|
|
27
|
-
| --------- |
|
|
28
|
-
| `data` | `TData`
|
|
29
|
-
| `status` | `UseEveAgentStatus`
|
|
30
|
-
| `error` | `Error \| undefined`
|
|
31
|
-
| `events` | `readonly
|
|
32
|
-
| `session` | `SessionState`
|
|
33
|
-
| `send` | `(input) => Promise
|
|
34
|
-
| `stop` | `() => void`
|
|
35
|
-
| `reset` | `() => void`
|
|
26
|
+
| Property | Type | Description |
|
|
27
|
+
| --------- | ------------------------------------------- | ------------------------------------------------------------------------- |
|
|
28
|
+
| `data` | `TData` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
|
|
29
|
+
| `status` | `UseEveAgentStatus` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
|
|
30
|
+
| `error` | `Error \| undefined` | Last transport-level error. |
|
|
31
|
+
| `events` | `readonly HandleMessageStreamEvent[]` | Raw server events for this session. |
|
|
32
|
+
| `session` | `SessionState` | Snapshot of session state. |
|
|
33
|
+
| `send` | `(input: SendTurnPayload) => Promise<void>` | Send text or a full turn (multi-part, attachments, HITL responses). |
|
|
34
|
+
| `stop` | `() => void` | Abort the in-flight request. |
|
|
35
|
+
| `reset` | `() => void` | Clear state and start a new session. |
|
|
36
36
|
|
|
37
37
|
These state fields are reactive getters, so read them straight from templates, `$derived`, or `$effect`. They are not stores, so don't prefix them with `$`.
|
|
38
38
|
|
|
@@ -62,20 +62,44 @@ These state fields are reactive getters, so read them straight from templates, `
|
|
|
62
62
|
</form>
|
|
63
63
|
```
|
|
64
64
|
|
|
65
|
-
When a turn is more than plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format:
|
|
65
|
+
When a turn is more than plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format. Send file data as a base64 `data:` URL so it survives the JSON transport:
|
|
66
66
|
|
|
67
67
|
```ts
|
|
68
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
69
|
+
const base64 = btoa(String.fromCodePoint(...bytes));
|
|
70
|
+
|
|
68
71
|
await agent.send({
|
|
69
72
|
message: [
|
|
70
73
|
{ type: "text", text: "Describe this image." },
|
|
71
|
-
{ type: "file", data:
|
|
74
|
+
{ type: "file", data: `data:${file.type};base64,${base64}`, mediaType: file.type },
|
|
72
75
|
],
|
|
73
76
|
});
|
|
74
77
|
```
|
|
75
78
|
|
|
76
79
|
## Human-in-the-loop prompts
|
|
77
80
|
|
|
78
|
-
A tool opts into approval with `needsApproval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part of the latest message. Read it, then answer with `agent.send({ inputResponses })
|
|
81
|
+
A tool opts into approval with `needsApproval` ([Tools](../../tools)). When one fires, the pending request rides along on a `dynamic-tool` part of the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `agent.send({ inputResponses })`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import type { EveDynamicToolPart, EveMessagePart } from "eve/svelte";
|
|
85
|
+
|
|
86
|
+
const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
|
|
87
|
+
part.type === "dynamic-tool";
|
|
88
|
+
|
|
89
|
+
const request = agent.data.messages
|
|
90
|
+
.at(-1)
|
|
91
|
+
?.parts.filter(isDynamicToolPart)
|
|
92
|
+
.map((part) => part.toolMetadata?.eve?.inputRequest)
|
|
93
|
+
.find((value) => value !== undefined);
|
|
94
|
+
|
|
95
|
+
if (request) {
|
|
96
|
+
await agent.send({
|
|
97
|
+
inputResponses: [{ requestId: request.requestId, optionId: "approve" }],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
The find-and-answer flow is identical across frameworks. The [React hook reference](./overview) covers the longer walkthrough.
|
|
79
103
|
|
|
80
104
|
## Stop, reset, and resume
|
|
81
105
|
|
|
@@ -104,6 +128,74 @@ const agent = useEveAgent({
|
|
|
104
128
|
});
|
|
105
129
|
```
|
|
106
130
|
|
|
131
|
+
## Attach page context per turn
|
|
132
|
+
|
|
133
|
+
`clientContext` adds ephemeral context for the next model call and nothing more. Strings (or an array of strings) become user-role context messages; an object is JSON-serialized into one. It rides along with a message or HITL response, never dispatches a turn on its own, and never lands in durable session history. Pass it to `send()`:
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
await agent.send({
|
|
137
|
+
message: "What should I do on this screen?",
|
|
138
|
+
clientContext: { route: "/billing", plan: "pro", seatsUsed: 4 },
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
To attach the same context to every turn without threading it through each call site, pass `prepareSend`. It runs right before each send and returns the (possibly augmented) turn:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
const agent = useEveAgent({
|
|
146
|
+
prepareSend: (input) => ({
|
|
147
|
+
...input,
|
|
148
|
+
clientContext: { route: location.pathname },
|
|
149
|
+
}),
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Lifecycle callbacks
|
|
154
|
+
|
|
155
|
+
The binding takes a few per-turn callbacks:
|
|
156
|
+
|
|
157
|
+
- `onEvent(event)`: fires for each Eve stream event as it arrives.
|
|
158
|
+
- `onError(error)`: fires with the last `Error` when a turn fails.
|
|
159
|
+
- `onFinish(snapshot)`: fires with the final snapshot once a turn settles.
|
|
160
|
+
- `onSessionChange(session)`: fires when the session cursor advances. Persist it to resume across reloads.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const agent = useEveAgent({
|
|
164
|
+
onEvent: (event) => console.debug(event.type),
|
|
165
|
+
onError: (error) => console.error(error.message),
|
|
166
|
+
onFinish: (snapshot) => console.log(snapshot.status),
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Two more options tune turn behavior:
|
|
171
|
+
|
|
172
|
+
- `optimistic` (default `true`): projects submitted user messages into `data` before Eve confirms them with a `message.received` event. These are reducer-facing projection events only; `events` stays the authoritative Eve stream.
|
|
173
|
+
- `maxReconnectAttempts` (default `3`): stream reconnection budget per turn.
|
|
174
|
+
|
|
175
|
+
## Custom reducer
|
|
176
|
+
|
|
177
|
+
The default reducer projects events into `{ messages }` (`EveMessageData`). To shape `data` differently, pass a `reducer` implementing `EveAgentReducer<TData>`:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
import { useEveAgent } from "eve/svelte";
|
|
181
|
+
import type { EveAgentReducer } from "eve/svelte";
|
|
182
|
+
|
|
183
|
+
interface ToolLog {
|
|
184
|
+
readonly toolCalls: number;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const toolCounter: EveAgentReducer<ToolLog> = {
|
|
188
|
+
initial: () => ({ toolCalls: 0 }),
|
|
189
|
+
reduce: (data, event) =>
|
|
190
|
+
event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const agent = useEveAgent({ reducer: toolCounter });
|
|
194
|
+
// agent.data is ToolLog
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
`reduce(data, event)` receives both authoritative Eve stream events and client projection events (`client.message.submitted`, `client.message.failed`, `client.input.responded`). Handle the client events too if you want optimistic and HITL state in your projection. Otherwise, return `data` unchanged for them.
|
|
198
|
+
|
|
107
199
|
## What to read next
|
|
108
200
|
|
|
109
201
|
- [SvelteKit](./sveltekit): Vite plugin setup
|
|
@@ -3,7 +3,7 @@ title: "useEveAgent (Vue)"
|
|
|
3
3
|
description: "Vue composable that drives an Eve agent session from the browser."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
`useEveAgent()` from `eve/vue` is how a Vue app talks to an Eve session. It opens a long-lived session
|
|
6
|
+
`useEveAgent()` from `eve/vue` is how a Vue app talks to an Eve session. It opens a long-lived session, sends turns, and folds every stream event into reactive data you can bind in a template. Nuxt auto-imports it through the [module](./nuxt), and the [frontend overview](./overview) covers the shared model.
|
|
7
7
|
|
|
8
8
|
## Basic usage
|
|
9
9
|
|
|
@@ -25,18 +25,18 @@ const { data } = useEveAgent();
|
|
|
25
25
|
|
|
26
26
|
## What it returns
|
|
27
27
|
|
|
28
|
-
| Property | Type
|
|
29
|
-
| --------- |
|
|
30
|
-
| `data` | `ComputedRef<TData>`
|
|
31
|
-
| `status` | `ComputedRef<UseEveAgentStatus>`
|
|
32
|
-
| `error` | `ComputedRef<Error \| undefined>`
|
|
33
|
-
| `events` | `ComputedRef
|
|
34
|
-
| `session` | `ComputedRef<SessionState>`
|
|
35
|
-
| `send` | `(input) => Promise
|
|
36
|
-
| `stop` | `() => void`
|
|
37
|
-
| `reset` | `() => void`
|
|
28
|
+
| Property | Type | Description |
|
|
29
|
+
| --------- | -------------------------------------------------- | ------------------------------------------------------------------------- |
|
|
30
|
+
| `data` | `ComputedRef<TData>` | Projected state. With the default reducer, `EveMessageData` (`messages`). |
|
|
31
|
+
| `status` | `ComputedRef<UseEveAgentStatus>` | `"ready"`, `"submitted"`, `"streaming"`, or `"error"`. |
|
|
32
|
+
| `error` | `ComputedRef<Error \| undefined>` | Last transport-level error. |
|
|
33
|
+
| `events` | `ComputedRef<readonly HandleMessageStreamEvent[]>` | Raw server events for this session. |
|
|
34
|
+
| `session` | `ComputedRef<SessionState>` | Snapshot of session state. |
|
|
35
|
+
| `send` | `(input: SendTurnPayload) => Promise<void>` | Send text or a full turn (multi-part, attachments, HITL responses). |
|
|
36
|
+
| `stop` | `() => void` | Abort the in-flight request. |
|
|
37
|
+
| `reset` | `() => void` | Clear state and start a new session. |
|
|
38
38
|
|
|
39
|
-
The first five are `ComputedRef`s; the rest are methods. Destructure whatever you need
|
|
39
|
+
The first five are `ComputedRef`s; the rest are methods. Destructure whatever you need, since refs keep their reactivity through destructuring. Read them with `.value` in `<script>`, and unwrapped in `<template>`.
|
|
40
40
|
|
|
41
41
|
## Send a message
|
|
42
42
|
|
|
@@ -64,7 +64,7 @@ async function handleSubmit() {
|
|
|
64
64
|
</template>
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
For anything beyond plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format:
|
|
67
|
+
For anything beyond plain text, reach for `send()`. Attachments follow the AI SDK `UserContent` format. Send file data as a base64 `data:` URL so it survives the JSON transport:
|
|
68
68
|
|
|
69
69
|
```vue
|
|
70
70
|
<script setup lang="ts">
|
|
@@ -75,10 +75,12 @@ const { send } = useEveAgent();
|
|
|
75
75
|
async function onFileChange(event: Event) {
|
|
76
76
|
const file = (event.target as HTMLInputElement).files?.[0];
|
|
77
77
|
if (!file) return;
|
|
78
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
79
|
+
const base64 = btoa(String.fromCodePoint(...bytes));
|
|
78
80
|
await send({
|
|
79
81
|
message: [
|
|
80
82
|
{ type: "text", text: "Describe this image." },
|
|
81
|
-
{ type: "file", data:
|
|
83
|
+
{ type: "file", data: `data:${file.type};base64,${base64}`, mediaType: file.type },
|
|
82
84
|
],
|
|
83
85
|
});
|
|
84
86
|
}
|
|
@@ -87,7 +89,30 @@ async function onFileChange(event: Event) {
|
|
|
87
89
|
|
|
88
90
|
## Human-in-the-loop prompts
|
|
89
91
|
|
|
90
|
-
A tool opts into approval with `needsApproval` ([Tools](../../tools)). When it triggers, the pending request shows up as a `dynamic-tool` part on the latest message. Read it
|
|
92
|
+
A tool opts into approval with `needsApproval` ([Tools](../../tools)). When it triggers, the pending request shows up as a `dynamic-tool` part on the latest message at `part.toolMetadata?.eve?.inputRequest`. Read it, then answer through the same session with `send({ inputResponses })`:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import type { EveDynamicToolPart, EveMessagePart } from "eve/vue";
|
|
96
|
+
|
|
97
|
+
const { data, send } = useEveAgent();
|
|
98
|
+
|
|
99
|
+
const isDynamicToolPart = (part: EveMessagePart): part is EveDynamicToolPart =>
|
|
100
|
+
part.type === "dynamic-tool";
|
|
101
|
+
|
|
102
|
+
const request = data.value.messages
|
|
103
|
+
.at(-1)
|
|
104
|
+
?.parts.filter(isDynamicToolPart)
|
|
105
|
+
.map((part) => part.toolMetadata?.eve?.inputRequest)
|
|
106
|
+
.find((value) => value !== undefined);
|
|
107
|
+
|
|
108
|
+
if (request) {
|
|
109
|
+
await send({
|
|
110
|
+
inputResponses: [{ requestId: request.requestId, optionId: "approve" }],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
The find-and-answer flow is the same across every framework. The [React hook reference](./overview) covers the longer walkthrough.
|
|
91
116
|
|
|
92
117
|
## Stop, reset, and resume
|
|
93
118
|
|
|
@@ -116,6 +141,74 @@ const agent = useEveAgent({
|
|
|
116
141
|
});
|
|
117
142
|
```
|
|
118
143
|
|
|
144
|
+
## Attach page context per turn
|
|
145
|
+
|
|
146
|
+
`clientContext` adds ephemeral context for the next model call and nothing more. Strings (or an array of strings) become user-role context messages; an object is JSON-serialized into one. It rides along with a message or HITL response, never dispatches a turn on its own, and never lands in durable session history. Pass it to `send()`:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
await send({
|
|
150
|
+
message: "What should I do on this screen?",
|
|
151
|
+
clientContext: { route: "/billing", plan: "pro", seatsUsed: 4 },
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
To attach the same context to every turn without threading it through each call site, pass `prepareSend`. It runs right before each send and returns the (possibly augmented) turn:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
const agent = useEveAgent({
|
|
159
|
+
prepareSend: (input) => ({
|
|
160
|
+
...input,
|
|
161
|
+
clientContext: { route: location.pathname },
|
|
162
|
+
}),
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Lifecycle callbacks
|
|
167
|
+
|
|
168
|
+
The composable takes a few per-turn callbacks:
|
|
169
|
+
|
|
170
|
+
- `onEvent(event)`: fires for each Eve stream event as it arrives.
|
|
171
|
+
- `onError(error)`: fires with the last `Error` when a turn fails.
|
|
172
|
+
- `onFinish(snapshot)`: fires with the final snapshot once a turn settles.
|
|
173
|
+
- `onSessionChange(session)`: fires when the session cursor advances. Persist it to resume across reloads.
|
|
174
|
+
|
|
175
|
+
```ts
|
|
176
|
+
const agent = useEveAgent({
|
|
177
|
+
onEvent: (event) => console.debug(event.type),
|
|
178
|
+
onError: (error) => console.error(error.message),
|
|
179
|
+
onFinish: (snapshot) => console.log(snapshot.status),
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Two more options tune turn behavior:
|
|
184
|
+
|
|
185
|
+
- `optimistic` (default `true`): projects submitted user messages into `data` before Eve confirms them with a `message.received` event. These are reducer-facing projection events only; `events` stays the authoritative Eve stream.
|
|
186
|
+
- `maxReconnectAttempts` (default `3`): stream reconnection budget per turn.
|
|
187
|
+
|
|
188
|
+
## Custom reducer
|
|
189
|
+
|
|
190
|
+
The default reducer projects events into `{ messages }` (`EveMessageData`). To shape `data` differently, pass a `reducer` implementing `EveAgentReducer<TData>`:
|
|
191
|
+
|
|
192
|
+
```ts
|
|
193
|
+
import { useEveAgent } from "eve/vue";
|
|
194
|
+
import type { EveAgentReducer } from "eve/vue";
|
|
195
|
+
|
|
196
|
+
interface ToolLog {
|
|
197
|
+
readonly toolCalls: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const toolCounter: EveAgentReducer<ToolLog> = {
|
|
201
|
+
initial: () => ({ toolCalls: 0 }),
|
|
202
|
+
reduce: (data, event) =>
|
|
203
|
+
event.type === "actions.requested" ? { toolCalls: data.toolCalls + 1 } : data,
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const agent = useEveAgent({ reducer: toolCounter });
|
|
207
|
+
// agent.data.value is ToolLog
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`reduce(data, event)` receives both authoritative Eve stream events and client projection events (`client.message.submitted`, `client.message.failed`, `client.input.responded`). Handle the client events too if you want optimistic and HITL state in your projection. Otherwise, return `data` unchanged for them.
|
|
211
|
+
|
|
119
212
|
## What to read next
|
|
120
213
|
|
|
121
214
|
- [Nuxt](./nuxt): module setup
|
package/docs/guides/hooks.md
CHANGED
|
@@ -3,7 +3,7 @@ title: "Hooks"
|
|
|
3
3
|
description: "Subscribe to runtime stream events from agent/hooks/."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
Hooks are Eve's authored extension points for the runtime event stream. A hook subscribes to stream events and runs side effects after each event is durably recorded
|
|
6
|
+
Hooks are Eve's authored extension points for the runtime event stream. A hook subscribes to stream events and runs side effects after each event is durably recorded, such as audit logging, metrics and alerting, or persisting every session and message to your own database for analytics. Reach for one to observe what the agent does without writing a tool, a context provider (a value made available across a step), or a channel adapter handler (a handler defined on a channel's adapter; see [Channels](../channels)).
|
|
7
7
|
|
|
8
8
|
## Define a hook
|
|
9
9
|
|
|
@@ -22,31 +22,13 @@ export default defineHook({
|
|
|
22
22
|
});
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
The slug is the path-relative basename
|
|
25
|
+
The slug is the path-relative basename. `agent/hooks/audit.ts` becomes `"audit"`, and `agent/hooks/auth/load-profile.ts` becomes `"auth/load-profile"`.
|
|
26
26
|
|
|
27
|
-
`defineHook`, `HookDefinition`, and `HookContext` live on `eve/hooks`.
|
|
27
|
+
`defineHook`, `HookDefinition`, and `HookContext` live on `eve/hooks`.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
A hook file declares stream-event subscribers under the `events` map, keyed by event type, with `*` matching every event. Subscribe to any event in the runtime stream vocabulary documented in [Sessions, runs and streaming](../concepts/sessions-runs-and-streaming), including the lifecycle events `session.started`, `turn.completed`, `message.completed`, and `action.result`. Handlers are observe-only. They cannot inject model context. To contribute runtime model messages, use `defineDynamic` and `defineInstructions` in `agent/instructions/`.
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
defineHook({
|
|
35
|
-
events: {
|
|
36
|
-
"session.started"(event, ctx) {
|
|
37
|
-
/* ... */
|
|
38
|
-
},
|
|
39
|
-
"turn.completed"(event, ctx) {
|
|
40
|
-
/* ... */
|
|
41
|
-
},
|
|
42
|
-
"*"(event, ctx) {
|
|
43
|
-
/* wildcard: fires for every event */
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
});
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
Handlers are observe-only: they cannot inject model context. To contribute runtime model messages, use `defineDynamic` + `defineInstructions` in `agent/instructions/`.
|
|
31
|
+
## Hook structure and context
|
|
50
32
|
|
|
51
33
|
Every handler receives the same `HookContext`:
|
|
52
34
|
|
|
@@ -89,51 +71,23 @@ export default defineHook({
|
|
|
89
71
|
|
|
90
72
|
Returns `undefined` when the result doesn't match, or when `isError` is `true`. For authored tools the return includes `{ output, toolName, callId }` with `output` typed as the tool's `TOutput`. For connections it includes `{ output, toolName, connectionToolName, callId }` with `output` as `unknown`.
|
|
91
73
|
|
|
92
|
-
##
|
|
93
|
-
|
|
94
|
-
Side-effect-only handlers for accepted runtime events. Subscribe by event type, or use `*` for every event:
|
|
95
|
-
|
|
96
|
-
```ts title="agent/hooks/observability.ts"
|
|
97
|
-
import { defineHook } from "eve/hooks";
|
|
98
|
-
import { datadogCounter } from "../lib/datadog";
|
|
99
|
-
import { postToHoneycomb } from "../lib/honeycomb";
|
|
100
|
-
|
|
101
|
-
export default defineHook({
|
|
102
|
-
events: {
|
|
103
|
-
async "session.started"(_event, ctx) {
|
|
104
|
-
await postToHoneycomb({ event: "session.started", sessionId: ctx.session.id });
|
|
105
|
-
},
|
|
106
|
-
async "message.completed"(event) {
|
|
107
|
-
await postToHoneycomb({ event: "message.completed", payload: event.data });
|
|
108
|
-
},
|
|
109
|
-
async "*"(event) {
|
|
110
|
-
datadogCounter(`eve.event.${event.type}`).increment();
|
|
111
|
-
},
|
|
112
|
-
},
|
|
113
|
-
});
|
|
114
|
-
```
|
|
115
|
-
|
|
116
|
-
### Execution order
|
|
74
|
+
## Execution order
|
|
117
75
|
|
|
118
76
|
When a stream event fires, three things happen in order:
|
|
119
77
|
|
|
120
|
-
1. Emit
|
|
121
|
-
2. Hooks
|
|
122
|
-
3. Dynamic tool resolvers
|
|
78
|
+
1. Emit. The channel adapter handler runs, then the event is written to the durable stream.
|
|
79
|
+
2. Hooks. Stream-event hooks fire (typed handlers first, then the `*` wildcard). Return values are ignored.
|
|
80
|
+
3. Dynamic tool resolvers. Resolvers subscribed to the event type run and update the tool set.
|
|
123
81
|
|
|
124
82
|
Hooks always run after the event is durably recorded, so if a hook throws, the stream stays consistent.
|
|
125
83
|
|
|
126
|
-
##
|
|
127
|
-
|
|
128
|
-
| Stage | On throw |
|
|
129
|
-
| ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
130
|
-
| `events.*` | Propagates through the emit composer and surfaces as `turn.failed`. If a hook subscribed to a failure-cascade event also throws, it escalates to `session.failed`. |
|
|
84
|
+
## What happens when a hook throws
|
|
131
85
|
|
|
132
|
-
For belt-and-suspenders semantics inside a hook, wrap the body in `try`/`catch`. Eve treats a thrown hook as a real failure.
|
|
86
|
+
A thrown handler propagates through the emit composer and surfaces as `turn.failed`. If a hook subscribed to a failure-cascade event also throws, it escalates to `session.failed`. For belt-and-suspenders semantics inside a hook, wrap the body in `try`/`catch`. Eve treats a thrown hook as a real failure.
|
|
133
87
|
|
|
134
88
|
## Subagent isolation
|
|
135
89
|
|
|
136
|
-
Subagents may carry their own `agent/hooks/` directory. Subagent hooks fire only inside the subagent scope
|
|
90
|
+
Subagents may carry their own `agent/hooks/` directory. Subagent hooks fire only inside the subagent scope. Parent-agent hooks do not fire for subagent turns, and subagent hooks see only the subagent's own context.
|
|
137
91
|
|
|
138
92
|
## Hook vs tool vs provider
|
|
139
93
|
|
|
@@ -150,5 +104,5 @@ Stream-event hooks and channel adapter event handlers are structurally identical
|
|
|
150
104
|
|
|
151
105
|
- [Tools](../tools)
|
|
152
106
|
- [Context control](../concepts/context-control)
|
|
153
|
-
- [Session context](
|
|
154
|
-
- [Sessions, runs
|
|
107
|
+
- [Session context](../reference/typescript-api)
|
|
108
|
+
- [Sessions, runs and streaming](../concepts/sessions-runs-and-streaming)
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: "instrumentation.ts"
|
|
3
|
-
description: "Trace an agent with OpenTelemetry, read the workflow run tags Eve emits, and debug discovery with eve info and the
|
|
3
|
+
description: "Trace an agent with OpenTelemetry in instrumentation.ts, read the workflow run tags Eve emits, and debug discovery with eve info and the common-failures table."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
`instrumentation.ts` is where you configure how an Eve agent is observed. The framework auto-discovers `agent/instrumentation.ts` and runs it at server startup before any agent code. Its presence implicitly enables telemetry, so there is no separate `isEnabled` toggle.
|
|
7
7
|
|
|
8
|
+
If you intend to export telemetry, review the exporter destination, data categories, and required legal approvals before enabling telemetry.
|
|
9
|
+
|
|
8
10
|
## Three observability surfaces
|
|
9
11
|
|
|
10
|
-
Eve observes an agent through three distinct surfaces. They do not all live in this file, and they write to different places
|
|
12
|
+
Eve observes an agent through three distinct surfaces. They do not all live in this file, and they write to different places:
|
|
11
13
|
|
|
12
14
|
| Surface | Configured in `instrumentation.ts`? | What it is |
|
|
13
15
|
| -------------------------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -15,7 +17,7 @@ Eve observes an agent through three distinct surfaces. They do not all live in t
|
|
|
15
17
|
| **OpenTelemetry export** | Yes: `setup`, `recordInputs`, `recordOutputs`, `functionId` | Where AI SDK spans are exported and what they record. |
|
|
16
18
|
| **Runtime context events** | Yes: `events["step.started"]` | Per-model-call values written into the AI SDK's runtime context, which the AI SDK carries onto its spans. |
|
|
17
19
|
|
|
18
|
-
The two configurable surfaces send AI SDK spans to your OpenTelemetry backend. Workflow run tags are a separate system
|
|
20
|
+
The two configurable surfaces send AI SDK spans to your OpenTelemetry backend. Workflow run tags are a separate system, queryable in the Workflow dashboard rather than on your OTel spans. The sections below cover what you configure here; [Workflow run tags](#workflow-run-tags) documents what Eve emits on its own.
|
|
19
21
|
|
|
20
22
|
## Define instrumentation
|
|
21
23
|
|
|
@@ -40,7 +42,7 @@ Export the result of `defineInstrumentation` as the default export.
|
|
|
40
42
|
|
|
41
43
|
## OpenTelemetry
|
|
42
44
|
|
|
43
|
-
|
|
45
|
+
Use the `setup` callback to register your OTel provider (for example `registerOTel` from `@vercel/otel`). The framework invokes it at server startup with the resolved agent name. `context.agentName` is resolved at compile time from your project (the package's `name`, falling back to the app directory name), so you never hard-code a service name.
|
|
44
46
|
|
|
45
47
|
Any OTel-compatible backend works (Braintrust, Honeycomb, Datadog, Jaeger). Install the exporter package you need and configure it in the callback.
|
|
46
48
|
|
|
@@ -50,11 +52,15 @@ Three more fields control what the AI SDK records inside those spans (see the AI
|
|
|
50
52
|
- `recordOutputs` records model outputs on spans (defaults to `true`). Set it to `false` to disable output recording.
|
|
51
53
|
- `functionId` overrides the function name on spans (defaults to the agent name).
|
|
52
54
|
|
|
55
|
+
For sensitive, regulated, or production data, set `recordInputs` and `recordOutputs` to `false` unless you have reviewed the exporter and its data-retention path.
|
|
56
|
+
|
|
57
|
+
You are responsible for ensuring any observability or eval provider is approved for the data exported to it.
|
|
58
|
+
|
|
53
59
|
The third configurable surface, [runtime context events](#runtime-context), attaches per-model-call values to these spans.
|
|
54
60
|
|
|
55
61
|
## Runtime context
|
|
56
62
|
|
|
57
|
-
_Runtime context_ is an [AI SDK concept](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text): a user-defined object that flows through a generation lifecycle. Eve exposes it through `events["step.started"]`, a callback that runs once Eve has assembled the model input for an attempt and returns `{ runtimeContext }`. Because Eve registers the AI SDK's OpenTelemetry integration with runtime context enabled, those returned values ride onto the model-call span and its children.
|
|
63
|
+
_Runtime context_ is an [AI SDK concept](https://ai-sdk.dev/docs/reference/ai-sdk-core/stream-text): a user-defined object that flows through a generation lifecycle. Eve exposes it through `events["step.started"]`, a callback that runs once Eve has assembled the model input for an attempt and returns `{ runtimeContext }`. Because Eve registers the AI SDK's OpenTelemetry integration with runtime context enabled, those returned values ride onto the model-call span and its children. The field is named `runtimeContext`, not `metadata`, because AI SDK v7 carries per-call attributes on runtime context rather than a dedicated metadata field.
|
|
58
64
|
|
|
59
65
|
Use it when the values depend on the current session, turn, step, channel, or model input:
|
|
60
66
|
|
|
@@ -80,8 +86,6 @@ export default defineInstrumentation({
|
|
|
80
86
|
});
|
|
81
87
|
```
|
|
82
88
|
|
|
83
|
-
For authored channels, Eve emits compiler-owned typings keyed by the channel filename. A file at `agent/channels/support.ts` narrows as `channel:support`, either by checking `input.channel.kind === "channel:support"` or by using `isChannel(input.channel, supportChannel)`.
|
|
84
|
-
|
|
85
89
|
The callback receives:
|
|
86
90
|
|
|
87
91
|
- `session`: the session id, current and initiator auth, and parent session lineage when this is a child run
|
|
@@ -90,7 +94,7 @@ The callback receives:
|
|
|
90
94
|
- `channel`: the channel's `kind` and the metadata projected by the active channel
|
|
91
95
|
- `modelInput`: the final instructions and messages passed to the model call
|
|
92
96
|
|
|
93
|
-
A channel exposes its identity through `kind`, the discriminant you narrow on. For authored channels it is `channel:<name>`, where `<name>` is the channel's filename under `agent/channels/`, so `agent/channels/support.ts` is `channel:support`. Framework channels use `http`, `schedule`, or `subagent
|
|
97
|
+
A channel exposes its identity through `kind`, the discriminant you narrow on. For authored channels it is `channel:<name>`, where `<name>` is the channel's filename under `agent/channels/`, so `agent/channels/support.ts` is `channel:support`. Framework channels use `http`, `schedule`, or `subagent`, and an unrecognized or absent kind normalizes to `unknown`. The kind is also emitted as the `eve.channel.kind` span attribute. Eve emits compiler-owned typings keyed by the channel filename, so you can narrow either by checking `input.channel.kind === "channel:support"` or by using `isChannel(input.channel, supportChannel)`.
|
|
94
98
|
|
|
95
99
|
Channel metadata is channel-owned. Built-in channels expose only the fields they choose to make observable; Slack, for example, projects `channelId`, `teamId`, `threadTs`, and `triggeringUserId` from its durable channel state. User-authored channels expose their own projection by returning `metadata(state)` from `defineChannel`. Runtime instrumentation never falls back to raw channel state.
|
|
96
100
|
|
|
@@ -115,7 +119,7 @@ Eve creates the `ai.eve.turn` parent span per turn and passes enriched telemetry
|
|
|
115
119
|
|
|
116
120
|
Separately from OpenTelemetry, Eve tags every workflow run with reserved `$eve.*` attributes. These live on the Vercel Workflow run, queryable in the Workflow dashboard, not on OTel spans, and you do not configure them: they are framework-owned and emitted automatically on every session, turn, and subagent run, whether or not an `instrumentation.ts` file is present. Authored code cannot set or override the `$eve.` namespace.
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
They let a dashboard reconstruct the tree of runs behind a single agent invocation and surface model and token usage without reading run bodies.
|
|
119
123
|
|
|
120
124
|
Structural tags describe each run's place in the tree:
|
|
121
125
|
|
|
@@ -134,7 +138,9 @@ Per-turn usage tags are written on each step of a turn, accumulating cumulative
|
|
|
134
138
|
|
|
135
139
|
Tag writes are best-effort: a failure is logged once per process and then swallowed, so a broken tag emit never breaks the agent.
|
|
136
140
|
|
|
137
|
-
These tags power the **Agent Runs** tab in the Vercel dashboard. When you deploy on Vercel, the platform auto-detects `eve` as the framework and surfaces an Agent Runs view under your project's **Observability** tab, where you can browse sessions and drill into each conversation's trace
|
|
141
|
+
These tags power the **Agent Runs** tab in the Vercel dashboard. When you deploy on Vercel, the platform auto-detects `eve` as the framework and surfaces an Agent Runs view under your project's **Observability** tab, where you can browse sessions and drill into each conversation's trace, with no `instrumentation.ts` required. The tab is currently gated per team. See [Deployment](./deployment#view-runs-in-the-dashboard) for enablement. Agent Runs is separate from the OpenTelemetry export above. Use OTel when you want spans in Braintrust, Datadog, or another third-party backend.
|
|
142
|
+
|
|
143
|
+
Note: By default, telemetry records full message history and model outputs You may need to disclose these data flows in your privacy materials if utilized.
|
|
138
144
|
|
|
139
145
|
## Debugging
|
|
140
146
|
|
|
@@ -151,17 +157,17 @@ When `eve build` fails on discovery errors, the CLI prints the full diagnostics
|
|
|
151
157
|
|
|
152
158
|
### Common failures
|
|
153
159
|
|
|
154
|
-
| Symptom | Likely cause and fix
|
|
155
|
-
| --------------------------------------------- |
|
|
156
|
-
| Tool not discovered (the model never sees it) | Run `eve info`. Confirm the file is in the right slot (`agent/tools/<name>.ts`) and default-exports `defineTool(...)`, and check `.eve/diagnostics.json` for shape errors. `schedules/` are root-only.
|
|
157
|
-
| Model won't call a tool it should | Tighten the tool `description` and `inputSchema`; put procedural guidance in a [skill](../skills), not the description. Confirm it's in the active set with `eve info`.
|
|
158
|
-
| Stuck on `session.waiting` | The turn is parked on an approval, a question, or a connection sign-in. Answer it, or POST a follow-up with the `continuationToken` (a stale token is rejected).
|
|
159
|
-
| 401 on production routes | Expected: auth fails closed. Replace `placeholderAuth()`, and set `VERCEL_PROJECT_ID` and environment so `vercelOidc()` accepts user tokens. See [Auth
|
|
160
|
-
| Build fails with discovery errors | Read the printed diagnostics and `.eve/diagnostics.json`; confirm the root-vs-subagent boundary is valid and secrets come from env vars.
|
|
160
|
+
| Symptom | Likely cause and fix |
|
|
161
|
+
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
162
|
+
| Tool not discovered (the model never sees it) | Run `eve info`. Confirm the file is in the right slot (`agent/tools/<name>.ts`) and default-exports `defineTool(...)`, and check `.eve/diagnostics.json` for shape errors. `schedules/` are root-only. |
|
|
163
|
+
| Model won't call a tool it should | Tighten the tool `description` and `inputSchema`; put procedural guidance in a [skill](../skills), not the description. Confirm it's in the active set with `eve info`. |
|
|
164
|
+
| Stuck on `session.waiting` | The turn is parked on an approval, a question, or a connection sign-in. Answer it, or POST a follow-up with the `continuationToken` (a stale token is rejected). |
|
|
165
|
+
| 401 on production routes | Expected: auth fails closed. Replace `placeholderAuth()`, and set `VERCEL_PROJECT_ID` and environment so `vercelOidc()` accepts user tokens. See [Auth and route protection](./auth-and-route-protection). |
|
|
166
|
+
| Build fails with discovery errors | Read the printed diagnostics and `.eve/diagnostics.json`; confirm the root-vs-subagent boundary is valid and secrets come from env vars. |
|
|
161
167
|
|
|
162
168
|
## What to read next
|
|
163
169
|
|
|
164
170
|
- [`agent.ts`](../agent-config)
|
|
165
171
|
- [Hooks](./hooks): observe the runtime event stream
|
|
166
|
-
- [
|
|
172
|
+
- [Local Development](./dev-tui): drive the agent locally
|
|
167
173
|
- [Evals](../evals/overview): repeatable scored checks
|