@economic/agents 0.0.1-beta.6 → 1.0.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/README.md +325 -418
- package/bin/cli.mjs +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +561 -0
- package/dist/hono.d.mts +21 -0
- package/dist/hono.mjs +71 -0
- package/dist/index.d.mts +163 -108
- package/dist/index.mjs +501 -327
- package/package.json +21 -8
- package/schema/agent.sql +12 -0
- package/schema/{schema.sql → chat.sql} +1 -5
- package/dist/react.d.mts +0 -25
- package/dist/react.mjs +0 -38
package/README.md
CHANGED
|
@@ -1,62 +1,26 @@
|
|
|
1
1
|
# @economic/agents
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A batteries-included toolkit for building AI agents on Cloudflare Workers. Provides Durable Object base classes for both chat and non-chat agents, with on-demand skill loading, automatic message compaction, conversation management, and audit logging to D1.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
npm install @economic/agents ai @cloudflare/ai-chat agents
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
For React UIs that import `@economic/agents/react`, also install `react`. It is an **optional** peer of the package so Workers-only installs are not required to add React. The hook still needs `agents`, `ai`, and `@cloudflare/ai-chat` at runtime (same as the Cloudflare SDK imports it wraps).
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Overview
|
|
14
|
-
|
|
15
|
-
`@economic/agents` provides:
|
|
16
|
-
|
|
17
|
-
- **`AIChatAgent`** — an abstract Cloudflare Durable Object base class. Implement `onChatMessage`, call `this.buildLLMParams()`, and pass the result to `streamText` from the AI SDK.
|
|
18
|
-
- **`guard`** — optional TypeScript 5+ method decorator for `onChatMessage`. Runs your function with `options.body`; return a `Response` to short-circuit (e.g. auth), or nothing to continue.
|
|
19
|
-
- **`buildLLMParams`** — the standalone version of the above, for use outside of `AIChatAgent` or in custom agent implementations.
|
|
20
|
-
- **`useAIChatAgent`** (subpath `@economic/agents/react`) — React hook that wraps `useAgent` (`agents/react`) and `useAgentChat` (`@cloudflare/ai-chat/react`). Connection status is **callback-only** (`onConnectionStatusChange`). On WebSocket close codes **`>= 3000`**, the hook calls `agent.close()` to stop reconnection, then forwards `onClose` (use `event.reason` for the server message). Pass-through **`onOpen`**, **`onClose`**, **`onError`** are supported.
|
|
5
|
+
For chat agents, extend `ChatAgentHarness` (recommended) or `ChatAgent` (lower-level). For headless agents, extend `Agent`.
|
|
21
6
|
|
|
22
|
-
|
|
7
|
+
For React integration, see [`@economic/agents-react`](../react/README.md).
|
|
23
8
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
import { useAIChatAgent, type AgentConnectionStatus } from "@economic/agents/react";
|
|
28
|
-
import { useState } from "react";
|
|
29
|
-
|
|
30
|
-
const [connectionStatus, setConnectionStatus] = useState<AgentConnectionStatus>("connecting");
|
|
31
|
-
|
|
32
|
-
const { agent, chat } = useAIChatAgent({
|
|
33
|
-
agent: "MyAgent",
|
|
34
|
-
host: "localhost:8787",
|
|
35
|
-
chatId: "session-id",
|
|
36
|
-
toolContext: {},
|
|
37
|
-
connectionParams: { userId: "…" },
|
|
38
|
-
onConnectionStatusChange: setConnectionStatus,
|
|
39
|
-
onOpen: (event) => {},
|
|
40
|
-
onClose: (event) => {},
|
|
41
|
-
onError: (event) => {},
|
|
42
|
-
});
|
|
9
|
+
## Install
|
|
43
10
|
|
|
44
|
-
|
|
11
|
+
```sh
|
|
12
|
+
npm install @economic/agents @cloudflare/ai-chat ai agents
|
|
45
13
|
```
|
|
46
14
|
|
|
47
|
-
|
|
15
|
+
## Quick Start
|
|
48
16
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
## Quick start
|
|
17
|
+
### Server
|
|
52
18
|
|
|
53
19
|
```typescript
|
|
54
|
-
import { streamText } from "ai";
|
|
55
20
|
import { openai } from "@ai-sdk/openai";
|
|
56
21
|
import { tool } from "ai";
|
|
57
22
|
import { z } from "zod";
|
|
58
|
-
import {
|
|
59
|
-
import type { Skill } from "@economic/agents";
|
|
23
|
+
import { ChatAgentHarness, type AgentToolContext, type Skill } from "@economic/agents";
|
|
60
24
|
|
|
61
25
|
const searchSkill: Skill = {
|
|
62
26
|
name: "search",
|
|
@@ -71,32 +35,28 @@ const searchSkill: Skill = {
|
|
|
71
35
|
},
|
|
72
36
|
};
|
|
73
37
|
|
|
74
|
-
export class MyAgent extends
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
async onChatMessage(onFinish, options) {
|
|
79
|
-
const params = await this.buildLLMParams({
|
|
80
|
-
options,
|
|
81
|
-
onFinish,
|
|
82
|
-
model: openai("gpt-4o"),
|
|
83
|
-
system: "You are a helpful assistant.",
|
|
84
|
-
skills: [searchSkill],
|
|
85
|
-
});
|
|
86
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
38
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
39
|
+
getModel(ctx: AgentToolContext) {
|
|
40
|
+
return openai("gpt-4o");
|
|
87
41
|
}
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
42
|
|
|
91
|
-
|
|
43
|
+
getFastModel() {
|
|
44
|
+
return openai("gpt-4o-mini");
|
|
45
|
+
}
|
|
92
46
|
|
|
93
|
-
|
|
47
|
+
getSystemPrompt(ctx: AgentToolContext) {
|
|
48
|
+
return "You are a helpful assistant.";
|
|
49
|
+
}
|
|
94
50
|
|
|
95
|
-
|
|
51
|
+
getSkills(ctx: AgentToolContext) {
|
|
52
|
+
return [searchSkill];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
96
56
|
|
|
97
|
-
|
|
57
|
+
For lower-level control (custom `onChatMessage` implementations), extend `ChatAgent` directly — see [ChatAgent](#chatagent).
|
|
98
58
|
|
|
99
|
-
|
|
59
|
+
### Wrangler Config
|
|
100
60
|
|
|
101
61
|
```jsonc
|
|
102
62
|
{
|
|
@@ -109,184 +69,234 @@ Your agent class is a Durable Object. Declare it in `wrangler.jsonc`:
|
|
|
109
69
|
|
|
110
70
|
Run `wrangler types` after to generate typed `Env` bindings.
|
|
111
71
|
|
|
72
|
+
### Client
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { useAIChatAgent, type AgentConnectionStatus } from "@economic/agents-react";
|
|
76
|
+
import { useState } from "react";
|
|
77
|
+
|
|
78
|
+
const [connectionStatus, setConnectionStatus] = useState<AgentConnectionStatus>("connecting");
|
|
79
|
+
|
|
80
|
+
const { agent, chat } = useAIChatAgent({
|
|
81
|
+
agent: "MyAgent",
|
|
82
|
+
host: "localhost:8787",
|
|
83
|
+
chatId: "user_123:session-1",
|
|
84
|
+
toolContext: {},
|
|
85
|
+
connectionParams: { userId: "…" },
|
|
86
|
+
onConnectionStatusChange: setConnectionStatus,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const { messages, sendMessage, status, stop } = chat;
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`chatId` is the Durable Object name — use `userId:uniqueChatId` (see [Providing userId](#providing-userid)).
|
|
93
|
+
|
|
94
|
+
> **Note:** React hooks are in a separate package. Install with `npm install @economic/agents-react`.
|
|
95
|
+
|
|
112
96
|
---
|
|
113
97
|
|
|
114
|
-
##
|
|
98
|
+
## Harnesses
|
|
99
|
+
|
|
100
|
+
The server-side API is built around Durable Object base classes — `Agent` for headless workflows and `ChatAgent`/`ChatAgentHarness` for conversational UIs — plus a skill system that lets the LLM load tools on demand.
|
|
115
101
|
|
|
116
|
-
|
|
102
|
+
### ChatAgentHarness
|
|
103
|
+
|
|
104
|
+
The recommended starting point for chat agents. Extends `ChatAgent` with an opinionated structure: implement abstract methods for model selection, system prompt, tools, and skills. The harness handles `onChatMessage` for you.
|
|
117
105
|
|
|
118
106
|
```typescript
|
|
119
|
-
import {
|
|
120
|
-
import {
|
|
107
|
+
import { openai } from "@ai-sdk/openai";
|
|
108
|
+
import { ChatAgentHarness, type AgentToolContext, type Skill } from "@economic/agents";
|
|
121
109
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const model = body.userTier === "pro" ? openai("gpt-4o") : openai("gpt-4o-mini");
|
|
110
|
+
interface RequestBody {
|
|
111
|
+
userTier: "free" | "pro";
|
|
112
|
+
}
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
114
|
+
export class MyAgent extends ChatAgentHarness<Env, RequestBody> {
|
|
115
|
+
getModel(ctx: AgentToolContext<RequestBody>) {
|
|
116
|
+
return ctx.userTier === "pro" ? openai("gpt-4o") : openai("gpt-4o-mini");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
getFastModel() {
|
|
120
|
+
return openai("gpt-4o-mini");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getSystemPrompt(ctx: AgentToolContext<RequestBody>) {
|
|
124
|
+
return "You are a helpful assistant.";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
getTools(ctx: AgentToolContext<RequestBody>) {
|
|
128
|
+
return { myTool };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
getSkills(ctx: AgentToolContext<RequestBody>) {
|
|
132
|
+
return [searchSkill, calculatorSkill];
|
|
136
133
|
}
|
|
137
134
|
}
|
|
138
135
|
```
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
- `getModel(ctx)` — returns the primary language model. Context includes request body for tier-based model selection.
|
|
138
|
+
- `getFastModel()` — returns a fast/cheap model for compaction and conversation summarization.
|
|
139
|
+
- `getSystemPrompt(ctx)` — returns the system prompt.
|
|
140
|
+
- `getTools(ctx)` — returns always-on tools (optional, defaults to `{}`).
|
|
141
|
+
- `getSkills(ctx)` — returns skills available for on-demand loading (optional, defaults to `[]`).
|
|
142
|
+
- `conversationRetentionDays` — defaults to 90. Set to `undefined` to disable auto-deletion.
|
|
141
143
|
|
|
142
|
-
|
|
144
|
+
#### Binding Name
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
`ChatAgentHarness` automatically derives the Durable Object binding name from the class name. **The binding name in your `wrangler.jsonc` must exactly match your class name:**
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
// Class name is "MyAgent"
|
|
150
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
151
|
+
/* ... */
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```jsonc
|
|
156
|
+
// wrangler.jsonc — binding name must be "MyAgent" to match
|
|
157
|
+
{
|
|
158
|
+
"durable_objects": {
|
|
159
|
+
"bindings": [{ "name": "MyAgent", "class_name": "MyAgent" }],
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
```
|
|
150
163
|
|
|
151
|
-
|
|
164
|
+
If the names don't match, the harness won't be able to resolve the binding and will throw at runtime. If you need a different binding name, override the `binding` getter:
|
|
152
165
|
|
|
153
|
-
|
|
166
|
+
````typescript
|
|
167
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
168
|
+
protected get binding() {
|
|
169
|
+
return this.env.CUSTOM_BINDING_NAME;
|
|
170
|
+
}
|
|
171
|
+
// ...
|
|
172
|
+
}
|
|
154
173
|
|
|
155
|
-
|
|
174
|
+
---
|
|
156
175
|
|
|
157
|
-
|
|
158
|
-
- Return a **`Response`** — that response is returned immediately; `onChatMessage` is not called.
|
|
176
|
+
### ChatAgent
|
|
159
177
|
|
|
160
|
-
|
|
178
|
+
Lower-level base class for chat agents. Use when you need full control over `onChatMessage` — custom streaming, multiple LLM calls per turn, or non-standard response formats.
|
|
161
179
|
|
|
162
180
|
```typescript
|
|
163
181
|
import { streamText } from "ai";
|
|
164
182
|
import { openai } from "@ai-sdk/openai";
|
|
165
|
-
import {
|
|
183
|
+
import { ChatAgent } from "@economic/agents";
|
|
166
184
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
return new Response("Unauthorized", { status: 401 });
|
|
185
|
+
export class MyAgent extends ChatAgent<Env> {
|
|
186
|
+
protected get binding() {
|
|
187
|
+
return this.env.MyAgent;
|
|
171
188
|
}
|
|
172
|
-
};
|
|
173
189
|
|
|
174
|
-
|
|
175
|
-
|
|
190
|
+
protected getFastModel() {
|
|
191
|
+
return openai("gpt-4o-mini");
|
|
192
|
+
}
|
|
176
193
|
|
|
177
|
-
@guard(requireToken)
|
|
178
194
|
async onChatMessage(onFinish, options) {
|
|
179
195
|
const params = await this.buildLLMParams({
|
|
180
196
|
options,
|
|
181
197
|
onFinish,
|
|
182
198
|
model: openai("gpt-4o"),
|
|
183
199
|
system: "You are a helpful assistant.",
|
|
200
|
+
skills: [searchSkill],
|
|
201
|
+
tools: { alwaysOnTool },
|
|
184
202
|
});
|
|
185
203
|
return streamText(params).toUIMessageStreamResponse();
|
|
186
204
|
}
|
|
187
205
|
}
|
|
188
|
-
|
|
206
|
+
````
|
|
207
|
+
|
|
208
|
+
- `binding` — abstract getter returning the DO namespace binding. Required on every subclass.
|
|
209
|
+
- `getFastModel()` — abstract method returning the fast model for compaction and summarization.
|
|
210
|
+
- `maxMessagesBeforeCompaction` — class property to override the default threshold (15). Set to `undefined` to disable.
|
|
211
|
+
- `conversationRetentionDays` — class property to auto-delete inactive conversations after N days.
|
|
212
|
+
- `this.buildLLMParams()` — pre-fills `messages`, `activeSkills`, and injects `logEvent` into `experimental_context`.
|
|
213
|
+
- `getConversations()` / `deleteConversation(id)` — callable methods for listing/deleting a user's conversations.
|
|
214
|
+
|
|
215
|
+
---
|
|
189
216
|
|
|
190
|
-
###
|
|
217
|
+
### Agent
|
|
191
218
|
|
|
192
|
-
|
|
219
|
+
Abstract Durable Object base for non-chat agents. Use for headless workflows driven from HTTP handlers, schedules, or alarms.
|
|
193
220
|
|
|
194
221
|
```typescript
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
222
|
+
import { generateText } from "ai";
|
|
223
|
+
import { openai } from "@ai-sdk/openai";
|
|
224
|
+
import { callable } from "agents";
|
|
225
|
+
import { Agent } from "@economic/agents";
|
|
226
|
+
|
|
227
|
+
export class MyAgent extends Agent<Env> {
|
|
228
|
+
@callable
|
|
229
|
+
async summarize(document: string) {
|
|
230
|
+
const params = await this.buildLLMParams({
|
|
231
|
+
model: openai("gpt-4o"),
|
|
232
|
+
messages: [{ role: "user", content: `Summarise: ${document}` }],
|
|
233
|
+
system: "You are a helpful assistant.",
|
|
234
|
+
skills: [searchSkill],
|
|
235
|
+
});
|
|
236
|
+
const result = await generateText(params);
|
|
237
|
+
return result.text;
|
|
238
|
+
}
|
|
198
239
|
}
|
|
199
240
|
```
|
|
200
241
|
|
|
201
|
-
|
|
242
|
+
- `this.buildLLMParams()` pre-fills `activeSkills` from DO SQLite and injects `logEvent` into `experimental_context`.
|
|
243
|
+
- `this.logEvent(message, payload?)` writes audit events to D1 when `AGENT_DB` is bound, silent no-op otherwise.
|
|
202
244
|
|
|
203
|
-
|
|
245
|
+
---
|
|
204
246
|
|
|
205
|
-
###
|
|
247
|
+
### Tool context
|
|
206
248
|
|
|
207
|
-
|
|
249
|
+
Pass data via the `body` option of `useAgentChat` (with `useAIChatAgent`, use `toolContext` — it is forwarded as `body`). It arrives as `experimental_context` in tool `execute` functions. Use `AgentToolContext<TBody>` to type it:
|
|
208
250
|
|
|
209
251
|
```typescript
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
- **User scope**: `userId` is taken from the segment before the first `:` in the Durable Object name (`userId:chatId`), the same format enforced in `onConnect`.
|
|
214
|
-
- **Data**: Reads from `AGENT_DB`, returning all `conversations` rows whose `durable_object_name` matches `userId:%`, ordered by `updated_at` descending (newest first). Each row includes `durable_object_name`, `title`, `summary`, `created_at`, and `updated_at`.
|
|
215
|
-
- **No D1**: If `AGENT_DB` is not bound, the method returns `undefined` and does not throw.
|
|
216
|
-
|
|
217
|
-
### `getLoadedSkills()`
|
|
218
|
-
|
|
219
|
-
Protected method on `AIChatAgent`. Returns skill names persisted from previous turns (read from DO SQLite). Used internally by `this.buildLLMParams()`.
|
|
252
|
+
import type { AgentToolContext } from "@economic/agents";
|
|
220
253
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
254
|
+
interface AgentBody {
|
|
255
|
+
authorization: string;
|
|
256
|
+
userId: string;
|
|
257
|
+
}
|
|
224
258
|
|
|
225
|
-
|
|
226
|
-
2. Writes the updated skill name list to DO SQLite (no D1 needed).
|
|
227
|
-
3. Logs a turn summary via `log()`.
|
|
228
|
-
4. Strips all `activate_skill` and `list_capabilities` messages from history.
|
|
229
|
-
5. Delegates to the CF base `persistMessages` for message storage and WS broadcast.
|
|
259
|
+
type ToolContext = AgentToolContext<AgentBody>;
|
|
230
260
|
|
|
231
|
-
|
|
261
|
+
// Tool
|
|
262
|
+
execute: async (args, { experimental_context }) => {
|
|
263
|
+
const ctx = experimental_context as ToolContext;
|
|
264
|
+
await ctx.logEvent("tool called", { userId: ctx.userId });
|
|
265
|
+
return await fetchSomething(ctx.authorization);
|
|
266
|
+
};
|
|
267
|
+
```
|
|
232
268
|
|
|
233
|
-
|
|
269
|
+
`logEvent` is a no-op when `AGENT_DB` is not bound.
|
|
234
270
|
|
|
235
271
|
---
|
|
236
272
|
|
|
237
|
-
|
|
273
|
+
### Source URLs from Tools
|
|
238
274
|
|
|
239
|
-
|
|
275
|
+
Any tool can surface source URLs into the message stream by including a `sources` array in its return value. Detected automatically by `buildLLMParams` — no additional wiring needed.
|
|
240
276
|
|
|
241
277
|
```typescript
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
activeSkills: await this.getLoadedSkills(),
|
|
250
|
-
system: "You are a helpful assistant.",
|
|
251
|
-
skills: [searchSkill, codeSkill],
|
|
252
|
-
tools: { myAlwaysOnTool },
|
|
253
|
-
stopWhen: stepCountIs(20), // defaults to stepCountIs(20)
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
257
|
-
// or: generateText(params);
|
|
278
|
+
execute: async ({ query }) => {
|
|
279
|
+
const data = await fetchResults(query);
|
|
280
|
+
return {
|
|
281
|
+
results: data.results,
|
|
282
|
+
sources: data.results.map((r) => ({ url: r.url, title: r.title })),
|
|
283
|
+
};
|
|
284
|
+
};
|
|
258
285
|
```
|
|
259
286
|
|
|
260
|
-
|
|
261
|
-
| ----------------------------- | ------------------------------------- | -------- | ------------------------------------------------------------------------------------------------- |
|
|
262
|
-
| `options` | `OnChatMessageOptions \| undefined` | Yes | CF options object. Extracts `abortSignal` and `experimental_context`. |
|
|
263
|
-
| `onFinish` | `StreamTextOnFinishCallback<ToolSet>` | Yes | Called when the stream completes. |
|
|
264
|
-
| `model` | `LanguageModel` | Yes | The language model to use. |
|
|
265
|
-
| `messages` | `UIMessage[]` | Yes | Conversation history. Converted to `ModelMessage[]` internally. |
|
|
266
|
-
| `activeSkills` | `string[]` | No | Names of skills loaded in previous turns. Pass `await this.getLoadedSkills()`. |
|
|
267
|
-
| `skills` | `Skill[]` | No | Skills available for on-demand loading. Wires up meta-tools automatically. |
|
|
268
|
-
| `system` | `string` | No | Base system prompt. |
|
|
269
|
-
| `tools` | `ToolSet` | No | Always-on tools, active every turn regardless of loaded skills. |
|
|
270
|
-
| `maxMessagesBeforeCompaction` | `number \| undefined` | No | Verbatim tail kept during compaction. Defaults to `30` when omitted. Pass `undefined` to disable. |
|
|
271
|
-
| `stopWhen` | `StopCondition` | No | Stop condition. Defaults to `stepCountIs(20)`. |
|
|
272
|
-
|
|
273
|
-
When `skills` are provided, `buildLLMParams`:
|
|
274
|
-
|
|
275
|
-
- Registers `activate_skill` and `list_capabilities` meta-tools.
|
|
276
|
-
- Sets initial `activeTools` (meta + always-on + loaded skill tools).
|
|
277
|
-
- Wires up `prepareStep` to update `activeTools` after each step.
|
|
278
|
-
- Composes `system` with guidance from loaded skills.
|
|
287
|
+
Each source entry: `{ url: string, title?: string }`.
|
|
279
288
|
|
|
280
289
|
---
|
|
281
290
|
|
|
282
|
-
|
|
291
|
+
### Skills
|
|
292
|
+
|
|
293
|
+
Named groups of tools loaded on demand by the LLM. The agent starts with only always-on tools active. When the LLM needs more, it calls `activate_skill`.
|
|
283
294
|
|
|
284
295
|
```typescript
|
|
285
296
|
import { tool } from "ai";
|
|
286
297
|
import { z } from "zod";
|
|
287
298
|
import type { Skill } from "@economic/agents";
|
|
288
299
|
|
|
289
|
-
// Skill with guidance — injected into the system prompt when the skill is loaded
|
|
290
300
|
export const calculatorSkill: Skill = {
|
|
291
301
|
name: "calculator",
|
|
292
302
|
description: "Mathematical calculation and expression evaluation",
|
|
@@ -295,7 +305,7 @@ export const calculatorSkill: Skill = {
|
|
|
295
305
|
"Always show the expression you are evaluating.",
|
|
296
306
|
tools: {
|
|
297
307
|
calculate: tool({
|
|
298
|
-
description: "Evaluate a mathematical expression
|
|
308
|
+
description: "Evaluate a mathematical expression",
|
|
299
309
|
inputSchema: z.object({
|
|
300
310
|
expression: z.string().describe('e.g. "2 + 2", "Math.sqrt(144)"'),
|
|
301
311
|
}),
|
|
@@ -306,330 +316,227 @@ export const calculatorSkill: Skill = {
|
|
|
306
316
|
}),
|
|
307
317
|
},
|
|
308
318
|
};
|
|
309
|
-
|
|
310
|
-
// Skill without guidance — tools are self-explanatory
|
|
311
|
-
export const datetimeSkill: Skill = {
|
|
312
|
-
name: "datetime",
|
|
313
|
-
description: "Current date and time information in any timezone",
|
|
314
|
-
tools: {
|
|
315
|
-
get_current_datetime: tool({
|
|
316
|
-
description: "Get the current date and time in an optional IANA timezone.",
|
|
317
|
-
inputSchema: z.object({
|
|
318
|
-
timezone: z.string().optional().describe('e.g. "Europe/Copenhagen"'),
|
|
319
|
-
}),
|
|
320
|
-
execute: async ({ timezone = "UTC" }) =>
|
|
321
|
-
new Date().toLocaleString("en-GB", {
|
|
322
|
-
timeZone: timezone,
|
|
323
|
-
dateStyle: "full",
|
|
324
|
-
timeStyle: "long",
|
|
325
|
-
}),
|
|
326
|
-
}),
|
|
327
|
-
},
|
|
328
|
-
};
|
|
329
|
-
```
|
|
330
|
-
|
|
331
|
-
### `Skill` fields
|
|
332
|
-
|
|
333
|
-
| Field | Type | Required | Description |
|
|
334
|
-
| ------------- | --------- | -------- | ---------------------------------------------------------------------------- |
|
|
335
|
-
| `name` | `string` | Yes | Unique identifier used by `activate_skill` and for DO SQLite persistence. |
|
|
336
|
-
| `description` | `string` | Yes | One-line description shown in the `activate_skill` schema. |
|
|
337
|
-
| `guidance` | `string` | No | Instructions appended to the `system` prompt when this skill is loaded. |
|
|
338
|
-
| `tools` | `ToolSet` | Yes | Record of tool names to `tool()` definitions. Names must be globally unique. |
|
|
339
|
-
|
|
340
|
-
---
|
|
341
|
-
|
|
342
|
-
## Surfacing source URLs from tools
|
|
343
|
-
|
|
344
|
-
Any tool can surface source URLs into the message stream by including a `sources` array in its return value. `buildLLMParams` automatically detects this and emits `source-url` stream parts that the playground's Sources block renders.
|
|
345
|
-
|
|
346
|
-
```typescript
|
|
347
|
-
execute: async ({ query }) => {
|
|
348
|
-
const data = await fetchResults(query);
|
|
349
|
-
return {
|
|
350
|
-
results: data.results, // LLM receives full content
|
|
351
|
-
sources: data.results.map(r => ({ url: r.url, title: r.title })), // rendered as source links
|
|
352
|
-
};
|
|
353
|
-
},
|
|
354
319
|
```
|
|
355
320
|
|
|
356
|
-
|
|
321
|
+
When `skills` are provided to `buildLLMParams`, two meta-tools are registered automatically:
|
|
357
322
|
|
|
358
|
-
|
|
323
|
+
- **`activate_skill`** — loads skills by name, making their tools available for the rest of the conversation. Idempotent. State is persisted to DO SQLite.
|
|
324
|
+
- **`list_capabilities`** — returns active tools, loaded skills, and skills available to load.
|
|
359
325
|
|
|
360
|
-
|
|
361
|
-
| ------- | -------- | -------- | ----------------------- |
|
|
362
|
-
| `url` | `string` | Yes | The URL to link to. |
|
|
363
|
-
| `title` | `string` | No | Display name in the UI. |
|
|
364
|
-
|
|
365
|
-
The playground's `UIMessageRenderer` collects all `source-url` parts from a message and displays them in a collapsible **Sources** block above the response text.
|
|
326
|
+
The `activate_skill` and `list_capabilities` meta-tools are stripped from message history before persistence.
|
|
366
327
|
|
|
367
328
|
---
|
|
368
329
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
When `fastModel` is set on the agent class, compaction runs automatically before each turn:
|
|
330
|
+
### Audit Logging (D1)
|
|
372
331
|
|
|
373
|
-
|
|
374
|
-
2. `fastModel` generates a concise summary of the older window.
|
|
375
|
-
3. That summary + the verbatim tail is what gets sent to the LLM.
|
|
376
|
-
4. Full history in DO SQLite is unaffected — compaction is in-memory only.
|
|
332
|
+
All agent base classes write audit events to a D1 database when `AGENT_DB` is bound. If not bound, `logEvent` is a no-op.
|
|
377
333
|
|
|
378
|
-
|
|
334
|
+
#### D1 Setup
|
|
379
335
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
export class MyAgent extends AIChatAgent<Env> {
|
|
384
|
-
protected fastModel = openai("gpt-4o-mini");
|
|
336
|
+
1. Create a D1 database in the Cloudflare dashboard.
|
|
337
|
+
2. Run the schema in the D1 console. For `Agent`, use [`schema/agent.sql`](schema/agent.sql). For `ChatAgent`/`ChatAgentHarness`, use [`schema/chat.sql`](schema/chat.sql) (includes the conversations table).
|
|
338
|
+
3. Bind it in `wrangler.jsonc`:
|
|
385
339
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
model: openai("gpt-4o"),
|
|
391
|
-
system: "...",
|
|
392
|
-
// No compaction config needed — runs automatically with default threshold
|
|
393
|
-
});
|
|
394
|
-
return streamText(params).toUIMessageStreamResponse();
|
|
395
|
-
}
|
|
396
|
-
}
|
|
340
|
+
```jsonc
|
|
341
|
+
"d1_databases": [
|
|
342
|
+
{ "binding": "AGENT_DB", "database_name": "agents", "database_id": "YOUR_DB_ID" }
|
|
343
|
+
]
|
|
397
344
|
```
|
|
398
345
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
Pass `maxMessagesBeforeCompaction` to override the default of 30:
|
|
402
|
-
|
|
403
|
-
```typescript
|
|
404
|
-
const params = await this.buildLLMParams({
|
|
405
|
-
options,
|
|
406
|
-
onFinish,
|
|
407
|
-
model: openai("gpt-4o"),
|
|
408
|
-
maxMessagesBeforeCompaction: 50, // keep last 50 messages verbatim
|
|
409
|
-
});
|
|
410
|
-
```
|
|
346
|
+
4. For local dev, apply the schema to your local D1 (from your app’s directory), e.g. `wrangler d1 execute <database_name> --local --file=node_modules/@economic/agents/schema/chat.sql`. You can wrap that in a `db:setup` npm script if you prefer.
|
|
411
347
|
|
|
412
|
-
|
|
348
|
+
#### Providing userId
|
|
413
349
|
|
|
414
|
-
|
|
350
|
+
The client's `chatId` becomes the Durable Object name. Use `userId:uniqueChatId` so the first segment is your stable user id (audit and conversations key off `getUserId()`, i.e. the substring before the first `:`). If that segment is empty (e.g. `:chat-1`), the connection is rejected. Same idea as [Quick Start](#quick-start) (`chatId`).
|
|
415
351
|
|
|
416
352
|
```typescript
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
353
|
+
import { useAIChatAgent } from "@economic/agents-react";
|
|
354
|
+
|
|
355
|
+
const { agent, chat } = useAIChatAgent({
|
|
356
|
+
agent: "MyAgent",
|
|
357
|
+
host: "localhost:8787",
|
|
358
|
+
chatId: "148583_matt:conversation-1",
|
|
422
359
|
});
|
|
423
360
|
```
|
|
424
361
|
|
|
425
|
-
Compaction is always off when `fastModel` is `undefined` (the base class default).
|
|
426
|
-
|
|
427
362
|
---
|
|
428
363
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
Two meta tools are automatically registered when `skills` are provided. You do not need to define or wire them.
|
|
432
|
-
|
|
433
|
-
### `activate_skill`
|
|
434
|
-
|
|
435
|
-
Loads one or more skills by name, making their tools available for the rest of the conversation. The LLM calls this when it needs capabilities it does not currently have.
|
|
364
|
+
### Chat Features
|
|
436
365
|
|
|
437
|
-
|
|
438
|
-
- The skills available are exactly those passed as `skills` — filter by request body to control access.
|
|
439
|
-
- When skills are successfully loaded, the new state is embedded in the tool result. `persistMessages` extracts it and writes to DO SQLite.
|
|
440
|
-
- All `activate_skill` messages are stripped from history before persistence — state is restored from DO SQLite, not from message history.
|
|
366
|
+
Compaction and the conversation list (below) require `getFastModel()` on your subclass.
|
|
441
367
|
|
|
442
|
-
|
|
368
|
+
#### Compaction
|
|
443
369
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
---
|
|
447
|
-
|
|
448
|
-
## Passing request context to tools
|
|
449
|
-
|
|
450
|
-
Pass arbitrary data via the `body` option of `useAgentChat`. It arrives as `experimental_context` in tool `execute` functions.
|
|
451
|
-
|
|
452
|
-
When using `this.buildLLMParams()`, the context is automatically composed: your body fields plus a `log` function for writing audit events. Use `AgentContext<TBody>` to type it:
|
|
370
|
+
Compaction summarises older messages before each turn. Full history in DO SQLite is unaffected — compaction is in-memory only. The default threshold is **15** recent messages (`maxMessagesBeforeCompaction` on the class).
|
|
453
371
|
|
|
454
372
|
```typescript
|
|
455
|
-
|
|
456
|
-
|
|
373
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
374
|
+
getModel() {
|
|
375
|
+
return openai("gpt-4o");
|
|
376
|
+
}
|
|
457
377
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
378
|
+
getFastModel() {
|
|
379
|
+
return openai("gpt-4o-mini");
|
|
380
|
+
}
|
|
462
381
|
|
|
463
|
-
|
|
464
|
-
|
|
382
|
+
getSystemPrompt() {
|
|
383
|
+
return "You are a helpful assistant.";
|
|
384
|
+
}
|
|
465
385
|
|
|
466
|
-
|
|
467
|
-
//
|
|
468
|
-
useAgentChat({ body: { authorization: token, userId: "u_123" } });
|
|
386
|
+
// Optional: keep more messages verbatim before summarising (default 15).
|
|
387
|
+
// protected maxMessagesBeforeCompaction = 50;
|
|
469
388
|
|
|
470
|
-
//
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
await ctx.log("tool called", { userId: ctx.userId });
|
|
474
|
-
const data = await fetchSomething(ctx.authorization);
|
|
475
|
-
return data;
|
|
476
|
-
};
|
|
389
|
+
// Optional: disable compaction (still uses fastModel for conversation title/summary).
|
|
390
|
+
// protected maxMessagesBeforeCompaction = undefined;
|
|
391
|
+
}
|
|
477
392
|
```
|
|
478
393
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
---
|
|
482
|
-
|
|
483
|
-
## Audit logging — D1 setup
|
|
394
|
+
#### Conversations (D1)
|
|
484
395
|
|
|
485
|
-
`
|
|
396
|
+
`ChatAgent` and `ChatAgentHarness` maintain a `conversations` table in `AGENT_DB`. One row per Durable Object instance, upserted automatically after every turn. Requires [`schema/chat.sql`](schema/chat.sql).
|
|
486
397
|
|
|
487
|
-
|
|
398
|
+
**Automatic title and summary** — On the first turn, title and summary are generated and inserted. On subsequent turns, only `updated_at` is refreshed. Title and summary are regenerated periodically as the conversation grows.
|
|
488
399
|
|
|
489
|
-
|
|
400
|
+
**Retention** — Set `conversationRetentionDays` to auto-delete inactive conversations:
|
|
490
401
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
402
|
+
```typescript
|
|
403
|
+
export class MyAgent extends ChatAgentHarness<Env> {
|
|
404
|
+
getModel() {
|
|
405
|
+
return openai("gpt-4o");
|
|
406
|
+
}
|
|
407
|
+
getFastModel() {
|
|
408
|
+
return openai("gpt-4o-mini");
|
|
409
|
+
}
|
|
410
|
+
getSystemPrompt() {
|
|
411
|
+
return "You are a helpful assistant.";
|
|
412
|
+
}
|
|
494
413
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
durable_object_id TEXT NOT NULL,
|
|
499
|
-
user_id TEXT NOT NULL,
|
|
500
|
-
message TEXT NOT NULL,
|
|
501
|
-
payload TEXT,
|
|
502
|
-
created_at TEXT NOT NULL
|
|
503
|
-
);
|
|
504
|
-
CREATE INDEX IF NOT EXISTS audit_events_user ON audit_events(user_id);
|
|
505
|
-
CREATE INDEX IF NOT EXISTS audit_events_do ON audit_events(durable_object_id);
|
|
506
|
-
CREATE INDEX IF NOT EXISTS audit_events_ts ON audit_events(created_at);
|
|
507
|
-
|
|
508
|
-
CREATE TABLE IF NOT EXISTS conversations (
|
|
509
|
-
durable_object_id TEXT PRIMARY KEY,
|
|
510
|
-
user_id TEXT NOT NULL,
|
|
511
|
-
title TEXT,
|
|
512
|
-
summary TEXT,
|
|
513
|
-
created_at TEXT NOT NULL,
|
|
514
|
-
updated_at TEXT NOT NULL
|
|
515
|
-
);
|
|
516
|
-
CREATE INDEX IF NOT EXISTS conversations_user ON conversations(user_id);
|
|
517
|
-
CREATE INDEX IF NOT EXISTS conversations_ts ON conversations(updated_at);
|
|
414
|
+
// ChatAgentHarness defaults to 90 days. Override or set to undefined to disable.
|
|
415
|
+
protected conversationRetentionDays = 30;
|
|
416
|
+
}
|
|
518
417
|
```
|
|
519
418
|
|
|
520
|
-
|
|
419
|
+
When the retention period expires, the D1 row is deleted, WebSocket connections are closed, and the DO's SQLite storage is wiped.
|
|
521
420
|
|
|
522
|
-
|
|
421
|
+
**Querying** — From a connected client:
|
|
523
422
|
|
|
524
|
-
```
|
|
525
|
-
"
|
|
526
|
-
{ "binding": "AGENT_DB", "database_name": "agents", "database_id": "YOUR_DB_ID" }
|
|
527
|
-
]
|
|
423
|
+
```typescript
|
|
424
|
+
const conversations = await agent.call("getConversations");
|
|
528
425
|
```
|
|
529
426
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
### 4. Seed local development
|
|
533
|
-
|
|
534
|
-
```bash
|
|
535
|
-
npm run db:setup
|
|
536
|
-
```
|
|
427
|
+
---
|
|
537
428
|
|
|
538
|
-
|
|
429
|
+
## Hono
|
|
539
430
|
|
|
540
|
-
|
|
431
|
+
Hono tooling is exported on `@economic/agents/hono`.
|
|
541
432
|
|
|
542
|
-
###
|
|
433
|
+
### JWT Auth Middleware
|
|
543
434
|
|
|
544
|
-
|
|
435
|
+
Bearer JWT verification middleware for Hono, imported from `@economic/agents/hono`. Verifies tokens via JWKS derived from the token's `iss` claim.
|
|
545
436
|
|
|
546
437
|
```typescript
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
438
|
+
import { jwtAuth } from "@economic/agents/hono";
|
|
439
|
+
|
|
440
|
+
app.use(
|
|
441
|
+
"/api/*",
|
|
442
|
+
jwtAuth({
|
|
443
|
+
allowedIssuers: ["https://login.example.com"],
|
|
444
|
+
audience: "my-api",
|
|
445
|
+
requiredScopes: ["read", "write"],
|
|
446
|
+
}),
|
|
447
|
+
);
|
|
554
448
|
```
|
|
555
449
|
|
|
556
|
-
If the client omits `userId`, the audit insert is skipped and a `console.error` is emitted. This will be visible in Wrangler's output during local development and in Workers Logs in production.
|
|
557
|
-
|
|
558
450
|
---
|
|
559
451
|
|
|
560
|
-
##
|
|
452
|
+
## API Reference
|
|
561
453
|
|
|
562
|
-
|
|
454
|
+
### `@economic/agents`
|
|
563
455
|
|
|
564
|
-
|
|
456
|
+
| Export | Description |
|
|
457
|
+
| ---------------------- | -------------------------------------------------------------------------- |
|
|
458
|
+
| `Agent` | Abstract DO base for non-chat agents with audit logging and buildLLMParams |
|
|
459
|
+
| `ChatAgent` | Abstract chat DO with compaction, conversations, and custom onChatMessage |
|
|
460
|
+
| `ChatAgentHarness` | Opinionated chat harness with getModel/getSystemPrompt/getTools/getSkills |
|
|
461
|
+
| `buildLLMParams` | Standalone function to build streamText/generateText params |
|
|
462
|
+
| `Skill` | Type: named group of tools with optional guidance |
|
|
463
|
+
| `AgentToolContext` | Type: request body merged with `logEvent` for tool context |
|
|
464
|
+
| `OnChatMessageOptions` | Type: options passed to `onChatMessage` |
|
|
465
|
+
| `BuildLLMParamsConfig` | Type: config for standalone `buildLLMParams` |
|
|
565
466
|
|
|
566
|
-
###
|
|
467
|
+
### `@economic/agents-react`
|
|
567
468
|
|
|
568
|
-
|
|
569
|
-
- **Subsequent turns**: only `user_id` and `updated_at` are updated. `created_at`, `title`, and `summary` are never overwritten by the upsert.
|
|
570
|
-
- `title` and `summary` are populated automatically after the conversation goes idle (see below).
|
|
469
|
+
React hooks are in a separate package. See [`@economic/agents-react`](../react/README.md) for full documentation.
|
|
571
470
|
|
|
572
|
-
|
|
471
|
+
| Export | Description |
|
|
472
|
+
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
473
|
+
| `useAIChatAgent` | React hook wrapping `useAgent` + `useAgentChat` |
|
|
474
|
+
| `UseAIChatAgentOptions` | Type: options for `useAIChatAgent` (`agent`, `host`, `chatId`, optional `basePath`, `toolContext`, `connectionParams`, …) |
|
|
475
|
+
| `AgentConnectionStatus` | Type: `"connecting" \| "connected" \| "disconnected" \| "unauthorized"` |
|
|
573
476
|
|
|
574
|
-
|
|
477
|
+
### `@economic/agents/hono`
|
|
575
478
|
|
|
576
|
-
|
|
479
|
+
| Export | Description |
|
|
480
|
+
| --------------- | ------------------------------------------- |
|
|
481
|
+
| `jwtAuth` | Hono middleware for Bearer JWT verification |
|
|
482
|
+
| `JwtAuthConfig` | Type: config for `jwtAuth` |
|
|
577
483
|
|
|
578
|
-
|
|
579
|
-
2. Takes the last 30 messages (`SUMMARY_CONTEXT_MESSAGES`) to keep the prompt bounded.
|
|
580
|
-
3. Calls `fastModel` with `Output.object()` to generate a structured `{ title, summary }`.
|
|
581
|
-
4. If a previous summary exists, it is included in the prompt so the model can detect direction changes.
|
|
582
|
-
5. Writes the result back to the `conversations` row.
|
|
484
|
+
### CLI
|
|
583
485
|
|
|
584
|
-
|
|
486
|
+
| Command | Description |
|
|
487
|
+
| -------------------------------------------- | ---------------------------------------- |
|
|
488
|
+
| `npx @economic/agents generate skill <name>` | Scaffold a new skill with tools |
|
|
489
|
+
| `npx @economic/agents generate tool <name>` | Scaffold a new tool (global or in skill) |
|
|
585
490
|
|
|
586
|
-
|
|
491
|
+
---
|
|
492
|
+
|
|
493
|
+
## CLI
|
|
587
494
|
|
|
588
|
-
|
|
495
|
+
The package includes a CLI for scaffolding skills and tools.
|
|
589
496
|
|
|
590
|
-
|
|
497
|
+
### Generate a Skill
|
|
591
498
|
|
|
592
|
-
```
|
|
593
|
-
|
|
594
|
-
FROM conversations
|
|
595
|
-
WHERE durable_object_name LIKE '148583_matt:%'
|
|
596
|
-
ORDER BY updated_at DESC;
|
|
499
|
+
```bash
|
|
500
|
+
npx @economic/agents generate skill weather
|
|
597
501
|
```
|
|
598
502
|
|
|
599
|
-
|
|
503
|
+
This will:
|
|
600
504
|
|
|
601
|
-
|
|
505
|
+
1. Prompt for a skill description
|
|
506
|
+
2. Ask for initial tool names (comma-separated)
|
|
507
|
+
3. Prompt for each tool's description and whether it needs `AgentToolContext`
|
|
508
|
+
4. Create the skill file at `src/skills/weather/weather.ts`
|
|
509
|
+
5. Create tool files at `src/skills/weather/tools/*.ts`
|
|
510
|
+
6. Auto-register the skill in your agent's `getSkills()` method
|
|
602
511
|
|
|
603
|
-
|
|
512
|
+
### Generate a Tool
|
|
513
|
+
|
|
514
|
+
```bash
|
|
515
|
+
npx @economic/agents generate tool geocode
|
|
516
|
+
```
|
|
604
517
|
|
|
605
|
-
|
|
518
|
+
This will:
|
|
606
519
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
520
|
+
1. Prompt for a tool description
|
|
521
|
+
2. Ask where to create it (global `src/tools/` or within an existing skill)
|
|
522
|
+
3. Ask whether it needs `AgentToolContext`
|
|
523
|
+
4. Create the tool file
|
|
524
|
+
5. Auto-register the tool in your agent's `getTools()` or the skill's `tools` object
|
|
610
525
|
|
|
611
|
-
###
|
|
526
|
+
### Auto-registration
|
|
612
527
|
|
|
613
|
-
|
|
614
|
-
| ---------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
|
615
|
-
| `guard` | `(guardFn: GuardFn)` | Method decorator: runs `guardFn` with `options.body`; a returned `Response` short-circuits the method. |
|
|
616
|
-
| `buildLLMParams` | `async (config) => Promise<LLMParams>` | Builds the full parameter object for `streamText` or `generateText`. |
|
|
528
|
+
The CLI automatically detects agent files by scanning `src/` for classes extending `ChatAgentHarness`, `ChatAgent`, or `Agent` from `@economic/agents`. If one agent is found, it's used automatically. If multiple are found, you'll be prompted to select one.
|
|
617
529
|
|
|
618
|
-
|
|
530
|
+
For `ChatAgentHarness`, the CLI modifies `getSkills()` or `getTools()` methods. For `ChatAgent` or `Agent`, it modifies `buildLLMParams()` calls.
|
|
619
531
|
|
|
620
|
-
|
|
621
|
-
| ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
622
|
-
| `GuardFn` | `(body) => Response \| void \| Promise<...>`. Receives chat request `body`; return `Response` to block the turn. |
|
|
623
|
-
| `Skill` | A named group of tools with optional guidance. |
|
|
624
|
-
| `AgentContext<TBody>` | Request body type merged with `log`. Use as the type of `experimental_context`. |
|
|
625
|
-
| `BuildLLMParamsConfig` | Config type for the standalone `buildLLMParams` function. |
|
|
532
|
+
If the CLI detects complex patterns (spread operators, function calls, variables), it will print manual registration instructions instead.
|
|
626
533
|
|
|
627
534
|
---
|
|
628
535
|
|
|
629
536
|
## Development
|
|
630
537
|
|
|
631
538
|
```bash
|
|
632
|
-
npm install
|
|
633
|
-
npm test
|
|
634
|
-
npm pack
|
|
539
|
+
npm install
|
|
540
|
+
npm test
|
|
541
|
+
npm pack
|
|
635
542
|
```
|