@economic/agents 2.2.3 → 2.3.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 +139 -27
- package/dist/index.d.mts +52 -42
- package/dist/index.mjs +129 -114
- package/dist/providers.d.mts +36 -0
- package/dist/providers.mjs +46 -0
- package/dist/v1.d.mts +3 -3
- package/dist/v1.mjs +16 -16
- package/package.json +14 -13
- /package/dist/{route-agent-request-DmwIOBJS.d.mts → route-agent-request-CbjgNl2B.d.mts} +0 -0
- /package/dist/{route-agent-request-DKDvDYnR.mjs → route-agent-request-lVm3eus2.mjs} +0 -0
package/README.md
CHANGED
|
@@ -4,27 +4,28 @@ Our agents SDK for building AI agents on Cloudflare Workers. Each agent is a Dur
|
|
|
4
4
|
|
|
5
5
|
React client: [`@economic/agents-react`](../react/README.md).
|
|
6
6
|
|
|
7
|
-
> The v1 (`@economic/agents/v1`) API (`ChatAgentHarness`, `buildLLMParams`) is deprecated, kept only for migration, and removed in v3.
|
|
7
|
+
> The v1 (`@economic/agents/v1`) API (`ChatAgentHarness`, `buildLLMParams`) is deprecated, kept only for migration, and will be removed in v3.
|
|
8
8
|
|
|
9
9
|
## The three classes
|
|
10
10
|
|
|
11
11
|
- **`Agent`** — the core. Runs the agent loop and keeps message history. Drive it over a WebSocket or programmatically (schedule, alarm, RPC). Most agents stop here.
|
|
12
|
-
- **`ChatAgent`** — `Agent` plus
|
|
13
|
-
- **`Assistant`** — per-user shell over `ChatAgent`: create/list/delete
|
|
12
|
+
- **`ChatAgent`** — `Agent` plus chat features: compaction and message feedback.
|
|
13
|
+
- **`Assistant`** — per-user shell over `ChatAgent`: create/list/delete chats, titles, summaries, retention.
|
|
14
14
|
|
|
15
15
|
```
|
|
16
16
|
Agent ← LLM + tools + skills (most agents stop here)
|
|
17
|
-
└─ ChatAgent ← one persistent
|
|
18
|
-
└─ Assistant ← one user, many
|
|
17
|
+
└─ ChatAgent ← one persistent chat
|
|
18
|
+
└─ Assistant ← one user, many chats
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Install
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
|
-
npm install @economic/agents
|
|
24
|
+
npm install @economic/agents @cloudflare/think agents
|
|
25
|
+
npm install -D wrangler vite @cloudflare/vite-plugin @cloudflare/worker-bundler
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
`
|
|
28
|
+
`@economic/agents` provides the agent runtime. Worker apps install the Cloudflare/Agents host packages they import directly, including the Vite plugin stack used by native skills.
|
|
28
29
|
|
|
29
30
|
## Quick start: an agent
|
|
30
31
|
|
|
@@ -125,7 +126,7 @@ You can also drive turns server-side — call `saveMessages` from a schedule or
|
|
|
125
126
|
|
|
126
127
|
## A chat agent
|
|
127
128
|
|
|
128
|
-
For a chat UI, extend `ChatAgent` instead of `Agent`. It adds compaction and message feedback, and connects to a browser with [`useChat`](../react/README.md#usechat) — no `Assistant` required. The DO name is the
|
|
129
|
+
For a chat UI, extend `ChatAgent` instead of `Agent`. It adds compaction and message feedback, and connects to a browser with [`useChat`](../react/README.md#usechat) — no `Assistant` required. The DO name is the chat; address one per user, ticket, or whatever fits.
|
|
129
130
|
|
|
130
131
|
```typescript
|
|
131
132
|
import { openai } from "@ai-sdk/openai";
|
|
@@ -158,18 +159,18 @@ const { chat } = useChat({
|
|
|
158
159
|
What `ChatAgent` adds over `Agent`:
|
|
159
160
|
|
|
160
161
|
- **Compaction** — past 100,000 tokens, older messages are summarised (with `getModel()`) while recent ones are kept verbatim. Storage keeps the full history.
|
|
161
|
-
- **Message feedback** — thumbs up/down with an optional comment, in the
|
|
162
|
+
- **Message feedback** — thumbs up/down with an optional comment, in the chat's SQLite (`assistant_messages_feedback`, created automatically):
|
|
162
163
|
|
|
163
164
|
| Method | Description |
|
|
164
165
|
| ---------------------------------------------------- | ------------------------------------------------------------ |
|
|
165
166
|
| `submitMessageFeedback(messageId, rating, comment?)` | `rating` is `1` (up) or `-1` (down). Upserts on the message. |
|
|
166
|
-
| `getMessageFeedback()` | All feedback for the
|
|
167
|
+
| `getMessageFeedback()` | All feedback for the chat, keyed by message id. |
|
|
167
168
|
|
|
168
169
|
Surfaced by the client as `chat.submitMessageFeedback` / `chat.getMessageFeedback`.
|
|
169
170
|
|
|
170
171
|
## Many chats per user: Assistant
|
|
171
172
|
|
|
172
|
-
When one user needs a list of
|
|
173
|
+
When one user needs a list of chats — a sidebar, titles, summaries — add an `Assistant`. It owns the chat list and routes to a `ChatAgent` per chat.
|
|
173
174
|
|
|
174
175
|
```typescript
|
|
175
176
|
import { openai } from "@ai-sdk/openai";
|
|
@@ -182,7 +183,7 @@ export class MyAssistant extends Assistant {
|
|
|
182
183
|
}
|
|
183
184
|
```
|
|
184
185
|
|
|
185
|
-
Bind both classes (binding name = class name) with a `new_sqlite_classes` migration each. On the client, use [`useAssistant`](../react/README.md#useassistant) — it manages the chat list and connects to the active
|
|
186
|
+
Bind both classes (binding name = class name) with a `new_sqlite_classes` migration each. On the client, use [`useAssistant`](../react/README.md#useassistant) — it manages the chat list and connects to the active chat:
|
|
186
187
|
|
|
187
188
|
```tsx
|
|
188
189
|
const { status, chats, assistant, chat } = useAssistant({
|
|
@@ -194,14 +195,14 @@ const { status, chats, assistant, chat } = useAssistant({
|
|
|
194
195
|
|
|
195
196
|
The `Assistant` keeps the chat list in its own SQLite (a `chats` table, created automatically) and exposes three callable methods:
|
|
196
197
|
|
|
197
|
-
| Method | Returns | Description
|
|
198
|
-
| ---------------- | -------- |
|
|
199
|
-
| `createChat()` | `string` | Creates a
|
|
200
|
-
| `getChats()` | `Chat[]` | The user's
|
|
201
|
-
| `deleteChat(id)` | `void` | Deletes a
|
|
198
|
+
| Method | Returns | Description |
|
|
199
|
+
| ---------------- | -------- | ---------------------------------------------- |
|
|
200
|
+
| `createChat()` | `string` | Creates a chat and returns its id. |
|
|
201
|
+
| `getChats()` | `Chat[]` | The user's chats, most recently updated first. |
|
|
202
|
+
| `deleteChat(id)` | `void` | Deletes a chat and its record. |
|
|
202
203
|
|
|
203
|
-
- **Titles and summaries** — generated with `fastModel` after each turn (on the first turn, then refreshed as the
|
|
204
|
-
- **Retention** — inactive
|
|
204
|
+
- **Titles and summaries** — generated with `fastModel` after each turn (on the first turn, then refreshed as the chat grows).
|
|
205
|
+
- **Retention** — inactive chats are deleted after 90 days, via a Durable Object alarm.
|
|
205
206
|
|
|
206
207
|
## Bindings
|
|
207
208
|
|
|
@@ -212,6 +213,7 @@ Checked in `Agent.onStart` when a connection opens:
|
|
|
212
213
|
| `AGENTS_AUDIT_LOGS` | R2 | Yes | Connection rejected if missing. One [audit log](#observability) per turn. |
|
|
213
214
|
| `AGENTS_ANALYTICS` | Analytics Engine | No | Per-turn/per-tool [analytics](#observability). Warns if missing. |
|
|
214
215
|
| `SKILLS_BUCKET` | R2 | No | Source for [remote skills](#remote-skills). |
|
|
216
|
+
| `LOADER` | Worker Loader | No | Enables [native skill scripts](#skill-scripts) when bound. |
|
|
215
217
|
|
|
216
218
|
## Tools
|
|
217
219
|
|
|
@@ -261,7 +263,16 @@ const call_api = tool({
|
|
|
261
263
|
|
|
262
264
|
## Skills
|
|
263
265
|
|
|
264
|
-
|
|
266
|
+
The SDK supports two skill styles:
|
|
267
|
+
|
|
268
|
+
- **Code-defined skills** with the local `skill()` helper.
|
|
269
|
+
- **Native Agent Skills** loaded from `SKILL.md` folders via `agents:skills` or remote sources.
|
|
270
|
+
|
|
271
|
+
Use skills when a capability should be loaded on demand instead of living in the always-on system prompt.
|
|
272
|
+
|
|
273
|
+
### Code-defined skills
|
|
274
|
+
|
|
275
|
+
A code-defined skill bundles markdown `instructions` with optional tools. Only `description` sits in the system prompt; the model loads the instructions and tools on demand.
|
|
265
276
|
|
|
266
277
|
```typescript
|
|
267
278
|
import { skill, tool } from "@economic/agents";
|
|
@@ -293,17 +304,119 @@ Use this skill whenever the user asks about current conditions or a forecast.
|
|
|
293
304
|
|
|
294
305
|
Return skills from `getSkills()`. Add `authorize(ctx)` to gate a skill (and its tools). `skill()` throws if `description` or `instructions` is missing.
|
|
295
306
|
|
|
307
|
+
### Native Agent Skills
|
|
308
|
+
|
|
309
|
+
Native Agent Skills are folders containing a `SKILL.md` file, with optional references and scripts:
|
|
310
|
+
|
|
311
|
+
```text
|
|
312
|
+
src/skills/top-customers-by-revenue/
|
|
313
|
+
├── SKILL.md
|
|
314
|
+
└── scripts/
|
|
315
|
+
└── top-customers.py
|
|
316
|
+
|
|
317
|
+
src/skills/vat-code-review/
|
|
318
|
+
├── SKILL.md
|
|
319
|
+
├── references/
|
|
320
|
+
│ └── accounting-rules.md
|
|
321
|
+
└── scripts/
|
|
322
|
+
└── review-invoice-vat.ts
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Load bundled skills with the Agents Vite plugin:
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
import bundledSkills from "agents:skills";
|
|
329
|
+
|
|
330
|
+
getSkills() {
|
|
331
|
+
return [bundledSkills];
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
`SKILL.md` should describe the business workflow, not the implementation mechanism:
|
|
336
|
+
|
|
337
|
+
```md
|
|
338
|
+
---
|
|
339
|
+
name: top-customers-by-revenue
|
|
340
|
+
description: Find the top customers by revenue from customer and sales data.
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
# Top Customers By Revenue
|
|
344
|
+
|
|
345
|
+
1. Call `request` with `{ "endpoint": "customers" }`.
|
|
346
|
+
2. Call `request` with `{ "endpoint": "sales" }`.
|
|
347
|
+
3. Run `scripts/top-customers.py` with the returned customer and sales arrays.
|
|
348
|
+
4. Summarize the top customers by revenue.
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Skill scripts
|
|
352
|
+
|
|
353
|
+
Skill scripts are enabled when a Worker Loader binding named `LOADER` is present:
|
|
354
|
+
|
|
355
|
+
```jsonc
|
|
356
|
+
{
|
|
357
|
+
"worker_loaders": [{ "binding": "LOADER" }],
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
`Agent` automatically creates a script runner when `env.LOADER` is bound. Without the binding, script execution is unavailable.
|
|
362
|
+
|
|
363
|
+
TypeScript scripts use the function-style contract:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
import type { SkillRunContext } from "@cloudflare/think";
|
|
367
|
+
|
|
368
|
+
export default async function run(input: unknown, ctx: SkillRunContext) {
|
|
369
|
+
const rules = ctx.files["references/accounting-rules.md"];
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
ok: true,
|
|
373
|
+
rulesLoaded: Boolean(rules),
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Python scripts use the path-based Dynamic Workers contract:
|
|
379
|
+
|
|
380
|
+
```python
|
|
381
|
+
import json
|
|
382
|
+
from pathlib import Path
|
|
383
|
+
|
|
384
|
+
input_data = json.loads(Path("/input.json").read_text())
|
|
385
|
+
|
|
386
|
+
print(json.dumps({
|
|
387
|
+
"ok": True,
|
|
388
|
+
"result": input_data,
|
|
389
|
+
}))
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
Use tools for I/O and scripts for deterministic processing. For example, a skill can call a shared `request` tool to fetch customers and sales, then pass the returned arrays into a Python script that ranks customers by revenue.
|
|
393
|
+
|
|
296
394
|
### Remote skills
|
|
297
395
|
|
|
298
|
-
Store skills in R2 to edit without redeploying and share across agents.
|
|
396
|
+
Store skills in R2 to edit without redeploying and share complex skills across agents. Add an R2 skill source from `getSkills()`:
|
|
299
397
|
|
|
300
398
|
```typescript
|
|
301
|
-
|
|
302
|
-
|
|
399
|
+
import { skills } from "@cloudflare/think";
|
|
400
|
+
|
|
401
|
+
getSkills() {
|
|
402
|
+
return [
|
|
403
|
+
skills.r2(this.env.SKILLS_BUCKET, {
|
|
404
|
+
skills: ["top-customers-by-revenue", "vat-code-review"],
|
|
405
|
+
}),
|
|
406
|
+
];
|
|
303
407
|
}
|
|
304
408
|
```
|
|
305
409
|
|
|
306
|
-
|
|
410
|
+
Remote skills use the same folder shape as bundled skills under the configured R2 prefix:
|
|
411
|
+
|
|
412
|
+
```text
|
|
413
|
+
skills/top-customers-by-revenue/
|
|
414
|
+
├── SKILL.md
|
|
415
|
+
└── scripts/
|
|
416
|
+
└── top-customers.py
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
Source order matters: if two sources define the same skill name, the first source wins.
|
|
307
420
|
|
|
308
421
|
## Authentication
|
|
309
422
|
|
|
@@ -359,7 +472,7 @@ Imported from `@economic/agents` unless noted.
|
|
|
359
472
|
| ----------- | -------------------------------------------------------- |
|
|
360
473
|
| `Agent` | Core agent base. Implement `getModel`/`getSystemPrompt`. |
|
|
361
474
|
| `ChatAgent` | `Agent` + compaction and message feedback. |
|
|
362
|
-
| `Assistant` | Per-user manager of `ChatAgent`
|
|
475
|
+
| `Assistant` | Per-user manager of `ChatAgent` chats. |
|
|
363
476
|
|
|
364
477
|
### Helpers and types
|
|
365
478
|
|
|
@@ -379,13 +492,12 @@ Imported from `@economic/agents` unless noted.
|
|
|
379
492
|
| `getSystemPrompt(ctx?)` | `Agent` | Required. System prompt. |
|
|
380
493
|
| `getTools()` | `Agent` | Always-on tools (default `{}`). |
|
|
381
494
|
| `getSkills()` | `Agent` | Local skills (default `[]`). |
|
|
382
|
-
| `getRemoteSkills()` | `Agent` | R2 skill keys (default `[]`). |
|
|
383
495
|
| `static getJwtAuthConfig(env)` | `Agent` | Optional JWT verification config. |
|
|
384
496
|
| `getUserContext(jwtToken)` | `Agent` | Optional per-user context after auth. |
|
|
385
497
|
| `submitMessageFeedback` / `getMessageFeedback` | `ChatAgent` | Callable message feedback. |
|
|
386
498
|
| `agent` | `Assistant` | Required. Your `ChatAgent` subclass. |
|
|
387
499
|
| `fastModel` | `Assistant` | Required. Cheap model for titles/summaries. |
|
|
388
|
-
| `createChat` / `getChats` / `deleteChat` | `Assistant` | Callable
|
|
500
|
+
| `createChat` / `getChats` / `deleteChat` | `Assistant` | Callable chat management. |
|
|
389
501
|
|
|
390
502
|
### Package root (`@economic/agents`)
|
|
391
503
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-
|
|
1
|
+
import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-CbjgNl2B.mjs";
|
|
2
2
|
import { Agent as Agent$1, Connection, ConnectionContext, SubAgentClass } from "agents";
|
|
3
|
-
import { ChatResponseResult,
|
|
3
|
+
import { ChatResponseResult, PrepareStepContext, Session, Think, ToolCallContext, ToolCallDecision, TurnConfig, TurnContext, skills } from "@cloudflare/think";
|
|
4
4
|
import { JSONValue, LanguageModel, Tool as Tool$1, UIMessage } from "ai";
|
|
5
|
+
import { createCompactFunction } from "agents/experimental/memory/utils";
|
|
6
|
+
import { SkillSource as SkillSource$1 } from "agents/skills";
|
|
7
|
+
|
|
5
8
|
//#region src/server/types.d.ts
|
|
6
9
|
/**
|
|
7
10
|
* The context object available throughout an agent's lifetime — passed via
|
|
@@ -31,20 +34,10 @@ type AgentConnectionState = {
|
|
|
31
34
|
//#region src/server/util/tools.d.ts
|
|
32
35
|
type ToolSet = Record<string, Tool>;
|
|
33
36
|
type Tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any> = Tool$1<INPUT, OUTPUT> & {
|
|
34
|
-
authorize?: (ctx
|
|
37
|
+
authorize?: (ctx?: Context) => boolean;
|
|
35
38
|
};
|
|
36
39
|
declare function tool<Context extends Record<string, unknown> = Record<string, unknown>, INPUT extends JSONValue | unknown | never = any, OUTPUT extends JSONValue | unknown | never = any>(tool: Tool<Context, INPUT, OUTPUT>): Tool$1<INPUT, OUTPUT>;
|
|
37
40
|
//#endregion
|
|
38
|
-
//#region src/server/util/skills.d.ts
|
|
39
|
-
interface Skill<Context extends Record<string, unknown> = Record<string, unknown>> {
|
|
40
|
-
name: string;
|
|
41
|
-
description: string;
|
|
42
|
-
instructions: string;
|
|
43
|
-
tools?: Record<string, Tool<Context>>;
|
|
44
|
-
authorize?: (ctx: Context) => boolean;
|
|
45
|
-
}
|
|
46
|
-
declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): Skill<Context>;
|
|
47
|
-
//#endregion
|
|
48
41
|
//#region src/server/agents/Agent.d.ts
|
|
49
42
|
declare abstract class Agent<RequestContext extends Record<string, unknown> = Record<string, unknown>, UserContext extends Record<string, unknown> = Record<string, unknown>> extends Think<Cloudflare.Env & AgentEnv, AgentConnectionState> {
|
|
50
43
|
initialState: AgentConnectionState;
|
|
@@ -61,6 +54,8 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
|
|
|
61
54
|
onClose(): Promise<void>;
|
|
62
55
|
onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
|
|
63
56
|
configureSession(session: Session): Session;
|
|
57
|
+
private _toolsForCurrentTurn;
|
|
58
|
+
private _toolContextForCurrentTurn?;
|
|
64
59
|
/**
|
|
65
60
|
* Merges the client request `body` into `experimental_context` for tools
|
|
66
61
|
* returned from {@link getTools} only (Think-internal tools are unchanged).
|
|
@@ -70,28 +65,29 @@ declare abstract class Agent<RequestContext extends Record<string, unknown> = Re
|
|
|
70
65
|
*/
|
|
71
66
|
beforeTurn(ctx: TurnContext): Promise<void | TurnConfig>;
|
|
72
67
|
/**
|
|
73
|
-
* Sets active tools based on skills that might be loaded
|
|
68
|
+
* Sets active tools based on skills that might be loaded during the current turn.
|
|
74
69
|
*
|
|
75
70
|
* @param ctx - The prepare step context.
|
|
76
71
|
* @returns The step config.
|
|
77
72
|
*/
|
|
78
|
-
beforeStep(): Promise<
|
|
73
|
+
beforeStep(ctx: PrepareStepContext): Promise<{
|
|
74
|
+
experimental_context: {};
|
|
75
|
+
activeTools?: undefined;
|
|
76
|
+
} | {
|
|
77
|
+
activeTools: string[];
|
|
78
|
+
experimental_context: {};
|
|
79
|
+
}>;
|
|
79
80
|
beforeToolCall(ctx: ToolCallContext): Promise<ToolCallDecision | void>;
|
|
80
|
-
private _buildToolContext;
|
|
81
81
|
getTools(): ToolSet;
|
|
82
|
+
workspaceBash: boolean;
|
|
83
|
+
getSkillScriptRunner(): skills.SkillScriptRunner | null;
|
|
82
84
|
/**
|
|
83
85
|
* Returns the remote skills to be loaded from SKILLS_BUCKET.
|
|
84
86
|
* @returns The remote skills to be loaded from SKILLS_BUCKET.
|
|
85
87
|
*/
|
|
86
|
-
|
|
87
|
-
* Returns the skills to load for the agent.
|
|
88
|
-
* @returns The skills to load for the agent.
|
|
89
|
-
*/
|
|
90
|
-
protected getSkills(): Skill[];
|
|
88
|
+
protected getRemoteSkills(): string[];
|
|
91
89
|
private _getAuthorizedTools;
|
|
92
|
-
private
|
|
93
|
-
/** Store the pending user context request to defer awaiting it until after the connection is established */
|
|
94
|
-
private _requestContext?;
|
|
90
|
+
private _getLastActivatedSkillName;
|
|
95
91
|
/**
|
|
96
92
|
* Override to enable JWT authentication on WebSocket connections.
|
|
97
93
|
* Return the auth config based on the incoming request, or undefined to skip auth.
|
|
@@ -235,16 +231,31 @@ declare abstract class Assistant extends Agent$1<Cloudflare.Env & AgentEnv, Agen
|
|
|
235
231
|
onStart(): void;
|
|
236
232
|
onClose(): Promise<void>;
|
|
237
233
|
onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
|
|
234
|
+
createChat(): Promise<string>;
|
|
235
|
+
deleteChat(id: string): Promise<void>;
|
|
236
|
+
getChats(): Promise<Chat[]>;
|
|
237
|
+
recordChatTurn(durableObjectName: string, messages: UIMessage[]): Promise<void>;
|
|
238
|
+
private [DELETE_CHAT_CALLBACK];
|
|
239
|
+
private scheduleChatForAutoDeletion;
|
|
240
|
+
/**
|
|
241
|
+
* Self-registers this Assistant in the global `agent_registry`. An Assistant
|
|
242
|
+
* is a top-level DO keyed directly by user id, so the DO name *is* the actor.
|
|
243
|
+
*
|
|
244
|
+
* Runs pre-auth in onStart (the DO has already persisted state by this point,
|
|
245
|
+
* so it must be tracked regardless of whether auth later succeeds).
|
|
246
|
+
* Best-effort and idempotent: no-ops on conflict, retried on the next start.
|
|
247
|
+
*/
|
|
248
|
+
private registerInstance;
|
|
238
249
|
/**
|
|
239
250
|
* Binding of the legacy v1 chat Durable Object class, used to migrate a
|
|
240
251
|
* user's v1 chats into facets the first time they connect. Set this on the
|
|
241
252
|
* concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
|
|
242
253
|
* disable it (e.g. for greenfield deployments with no v1 data).
|
|
243
254
|
*/
|
|
244
|
-
protected
|
|
255
|
+
protected getMigrationBinding(_env: Cloudflare.Env & AgentEnv): {
|
|
245
256
|
binding: DurableObjectNamespace;
|
|
246
257
|
db: D1Database;
|
|
247
|
-
};
|
|
258
|
+
} | undefined;
|
|
248
259
|
/** In-flight migration, shared across concurrent connections to this DO. */
|
|
249
260
|
private _migrationPromise?;
|
|
250
261
|
/**
|
|
@@ -255,24 +266,23 @@ declare abstract class Assistant extends Agent$1<Cloudflare.Env & AgentEnv, Agen
|
|
|
255
266
|
*/
|
|
256
267
|
private ensureMigrated;
|
|
257
268
|
private runMigration;
|
|
258
|
-
createChat(): Promise<string>;
|
|
259
|
-
deleteChat(id: string): Promise<void>;
|
|
260
|
-
getChats(): Promise<Chat[]>;
|
|
261
|
-
recordChatTurn(durableObjectName: string, messages: UIMessage[]): Promise<void>;
|
|
262
|
-
private [DELETE_CHAT_CALLBACK];
|
|
263
|
-
private scheduleChatForAutoDeletion;
|
|
264
|
-
/**
|
|
265
|
-
* Self-registers this Assistant in the global `agent_registry`. An Assistant
|
|
266
|
-
* is a top-level DO keyed directly by user id, so the DO name *is* the actor.
|
|
267
|
-
*
|
|
268
|
-
* Runs pre-auth in onStart (the DO has already persisted state by this point,
|
|
269
|
-
* so it must be tracked regardless of whether auth later succeeds).
|
|
270
|
-
* Best-effort and idempotent: no-ops on conflict, retried on the next start.
|
|
271
|
-
*/
|
|
272
|
-
private registerInstance;
|
|
273
269
|
}
|
|
274
270
|
//#endregion
|
|
271
|
+
//#region src/server/util/skills.d.ts
|
|
272
|
+
interface Skill<Context extends Record<string, unknown> = Record<string, unknown>> {
|
|
273
|
+
name: string;
|
|
274
|
+
description: string;
|
|
275
|
+
instructions: string;
|
|
276
|
+
tools?: Record<string, Tool<Context>>;
|
|
277
|
+
authorize?: (ctx?: Context) => boolean;
|
|
278
|
+
}
|
|
279
|
+
interface SkillSource<Context extends Record<string, unknown> = Record<string, unknown>> extends SkillSource$1 {
|
|
280
|
+
tools?: Record<string, Tool<Context>>;
|
|
281
|
+
authorize?: (ctx?: Context | undefined) => boolean;
|
|
282
|
+
}
|
|
283
|
+
declare function skill<Context extends Record<string, unknown> = Record<string, unknown>>(definition: Skill<Context>): SkillSource<Context>;
|
|
284
|
+
//#endregion
|
|
275
285
|
//#region src/server/index.d.ts
|
|
276
286
|
declare function getCurrentToolContext(): any;
|
|
277
287
|
//#endregion
|
|
278
|
-
export { Agent, type AgentConnectionState, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type Tool, type ToolContext, type ToolSet, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
|
|
288
|
+
export { Agent, type AgentConnectionState, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type FacetStub, type LegacyChatStub, type LegacyMessageFeedback, type MigrationDeps, type Skill, type SkillSource, type Tool, type ToolContext, type ToolSet, getCurrentToolContext, migrateUserFromV1, routeAgentRequest, skill, tool };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { i as verifyJwt, n as createAgentTracer, r as extractTokenFromConnectRequest, t as routeAgentRequest } from "./route-agent-request-
|
|
1
|
+
import { i as verifyJwt, n as createAgentTracer, r as extractTokenFromConnectRequest, t as routeAgentRequest } from "./route-agent-request-lVm3eus2.mjs";
|
|
2
2
|
import { Agent as Agent$1, callable, getCurrentAgent } from "agents";
|
|
3
|
-
import { Think } from "@cloudflare/think";
|
|
3
|
+
import { Think, skills } from "@cloudflare/think";
|
|
4
4
|
import { Output, convertToModelMessages, generateText, jsonSchema, pruneMessages, tool as tool$1 } from "ai";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import { createCompactFunction } from "agents/experimental/memory/utils";
|
|
@@ -23,24 +23,6 @@ const TURN_PROTOCOL_RULES_PROMPT = `These rules are specific for responding to u
|
|
|
23
23
|
- Prefer direct tools over code execution. Never use code execution for a single API call — that always belongs in the direct request tool. A small number of sequential direct calls (typically one to three) is also preferred over code execution. Use code execution only when direct tools cannot satisfy the request, for example variable-length loops, pagination across many calls, substantial joining/grouping/calculation, or transformation that would be unreliable to do mentally.
|
|
24
24
|
- Default to making a fresh data call for every new data request, even if similar data was just fetched — data may have changed and freshness matters more than saving a call. Only reuse earlier tool results when the user is explicitly referring back to a specific prior result, for example asking to summarize it or asking about a specific value inside it ("what was the third one called?", "show that as a table"). Any rephrasing, filter, count, or related follow-up that asks for data is a new data request and requires a fresh call.`;
|
|
25
25
|
//#endregion
|
|
26
|
-
//#region src/server/features/session.ts
|
|
27
|
-
var LocalSkillProvider = class {
|
|
28
|
-
skills;
|
|
29
|
-
constructor(skills) {
|
|
30
|
-
this.skills = new Map(skills.map((skill) => [skill.name, skill]));
|
|
31
|
-
}
|
|
32
|
-
async get() {
|
|
33
|
-
const entries = [...this.skills.values()].map((skill) => `- ${skill.name}: ${skill.description}`);
|
|
34
|
-
if (entries.length === 0) return null;
|
|
35
|
-
return entries.join("\n");
|
|
36
|
-
}
|
|
37
|
-
async load(key) {
|
|
38
|
-
const skill = this.skills.get(key);
|
|
39
|
-
if (!skill) return null;
|
|
40
|
-
return skill.instructions;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
//#endregion
|
|
44
26
|
//#region src/server/features/registry.ts
|
|
45
27
|
/**
|
|
46
28
|
* Registers a top-level DO in the global registry.
|
|
@@ -48,7 +30,7 @@ var LocalSkillProvider = class {
|
|
|
48
30
|
* Idempotent: keyed on `(agent_name, durable_object_name)`, so repeated calls
|
|
49
31
|
* across cold starts no-op and `created_at` reflects the first registration.
|
|
50
32
|
*/
|
|
51
|
-
async function
|
|
33
|
+
async function registerAgentInstance(db, input) {
|
|
52
34
|
await db.prepare(`INSERT INTO agent_registry (agent_name, durable_object_name, actor_id, created_at)
|
|
53
35
|
VALUES (?, ?, ?, ?)
|
|
54
36
|
ON CONFLICT (agent_name, durable_object_name) DO NOTHING`).bind(input.agentName, input.durableObjectName, input.actorId, input.createdAt).run();
|
|
@@ -149,13 +131,12 @@ var Agent = class extends Think {
|
|
|
149
131
|
});
|
|
150
132
|
}
|
|
151
133
|
configureSession(session) {
|
|
152
|
-
|
|
153
|
-
return this.getSystemPrompt(this.
|
|
154
|
-
} } }).withContext("critical-rules", { provider: { get: async () => SECURITY_RULES_PROMPT } }).withContext("turn-protocol-rules", { provider: { get: async () => TURN_PROTOCOL_RULES_PROMPT } });
|
|
155
|
-
const localSkills = this.getSkills();
|
|
156
|
-
if (localSkills.length) configuredSession = configuredSession.withContext("local-skills", { provider: new LocalSkillProvider(localSkills) });
|
|
157
|
-
return configuredSession.withCachedPrompt();
|
|
134
|
+
return session.withContext("soul", { provider: { get: async () => {
|
|
135
|
+
return this.getSystemPrompt(this._toolContextForCurrentTurn !== void 0 ? this._toolContextForCurrentTurn : void 0);
|
|
136
|
+
} } }).withContext("critical-rules", { provider: { get: async () => SECURITY_RULES_PROMPT } }).withContext("turn-protocol-rules", { provider: { get: async () => TURN_PROTOCOL_RULES_PROMPT } }).withCachedPrompt();
|
|
158
137
|
}
|
|
138
|
+
_toolsForCurrentTurn = {};
|
|
139
|
+
_toolContextForCurrentTurn;
|
|
159
140
|
/**
|
|
160
141
|
* Merges the client request `body` into `experimental_context` for tools
|
|
161
142
|
* returned from {@link getTools} only (Think-internal tools are unchanged).
|
|
@@ -165,11 +146,16 @@ var Agent = class extends Think {
|
|
|
165
146
|
*/
|
|
166
147
|
async beforeTurn(ctx) {
|
|
167
148
|
if (this._pendingUserContextRequest) await this._pendingUserContextRequest;
|
|
168
|
-
this.
|
|
149
|
+
this._toolContextForCurrentTurn = {
|
|
150
|
+
...ctx.body ?? {},
|
|
151
|
+
_userContext: this._userContext
|
|
152
|
+
};
|
|
169
153
|
await this.session.refreshSystemPrompt();
|
|
170
|
-
|
|
154
|
+
this._toolsForCurrentTurn = ctx.tools;
|
|
155
|
+
const activeSkillName = this._getLastActivatedSkillName(ctx.messages);
|
|
156
|
+
const { tools, activeTools } = await this._getAuthorizedTools(ctx.tools, activeSkillName);
|
|
171
157
|
return {
|
|
172
|
-
model: this.getModel(this.
|
|
158
|
+
model: this.getModel(this._toolContextForCurrentTurn),
|
|
173
159
|
messages: pruneMessages({
|
|
174
160
|
messages: ctx.messages,
|
|
175
161
|
toolCalls: [{
|
|
@@ -177,7 +163,7 @@ var Agent = class extends Think {
|
|
|
177
163
|
tools: []
|
|
178
164
|
}, {
|
|
179
165
|
type: "before-last-5-messages",
|
|
180
|
-
tools: ["
|
|
166
|
+
tools: ["activate_skill"]
|
|
181
167
|
}]
|
|
182
168
|
}),
|
|
183
169
|
tools,
|
|
@@ -201,72 +187,81 @@ var Agent = class extends Think {
|
|
|
201
187
|
};
|
|
202
188
|
}
|
|
203
189
|
/**
|
|
204
|
-
* Sets active tools based on skills that might be loaded
|
|
190
|
+
* Sets active tools based on skills that might be loaded during the current turn.
|
|
205
191
|
*
|
|
206
192
|
* @param ctx - The prepare step context.
|
|
207
193
|
* @returns The step config.
|
|
208
194
|
*/
|
|
209
|
-
async beforeStep() {
|
|
210
|
-
const
|
|
195
|
+
async beforeStep(ctx) {
|
|
196
|
+
const activeSkillName = this._getLastActivatedSkillName(ctx.steps);
|
|
197
|
+
if (!activeSkillName) return { experimental_context: this._toolContextForCurrentTurn ?? {} };
|
|
198
|
+
const { activeTools } = await this._getAuthorizedTools(this._toolsForCurrentTurn, activeSkillName);
|
|
211
199
|
return {
|
|
212
200
|
activeTools,
|
|
213
|
-
experimental_context: this.
|
|
201
|
+
experimental_context: this._toolContextForCurrentTurn ?? {}
|
|
214
202
|
};
|
|
215
203
|
}
|
|
216
204
|
async beforeToolCall(ctx) {
|
|
217
|
-
if (ctx.toolName === "
|
|
218
|
-
if (
|
|
205
|
+
if (ctx.toolName === "activate_skill") {
|
|
206
|
+
if ((await this.getSkills()).find((source) => source.id === ctx.input["name"])?.authorize?.(this._toolContextForCurrentTurn ?? {}) === false) return {
|
|
219
207
|
action: "block",
|
|
220
208
|
reason: "Unauthorized skill"
|
|
221
209
|
};
|
|
222
210
|
}
|
|
223
211
|
}
|
|
224
|
-
_buildToolContext() {
|
|
225
|
-
return {
|
|
226
|
-
...this._requestContext,
|
|
227
|
-
_userContext: this._userContext
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
212
|
getTools() {
|
|
231
213
|
return {};
|
|
232
214
|
}
|
|
215
|
+
workspaceBash = false;
|
|
216
|
+
getSkillScriptRunner() {
|
|
217
|
+
if (!("LOADER" in this.env)) return null;
|
|
218
|
+
return skills.runner({ loader: this.env.LOADER });
|
|
219
|
+
}
|
|
233
220
|
/**
|
|
234
221
|
* Returns the remote skills to be loaded from SKILLS_BUCKET.
|
|
235
222
|
* @returns The remote skills to be loaded from SKILLS_BUCKET.
|
|
236
223
|
*/
|
|
237
|
-
|
|
238
|
-
* Returns the skills to load for the agent.
|
|
239
|
-
* @returns The skills to load for the agent.
|
|
240
|
-
*/
|
|
241
|
-
getSkills() {
|
|
224
|
+
getRemoteSkills() {
|
|
242
225
|
return [];
|
|
243
226
|
}
|
|
244
|
-
async _getAuthorizedTools() {
|
|
245
|
-
const
|
|
246
|
-
const
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
...agentTools
|
|
250
|
-
};
|
|
251
|
-
const activeTools = [...Object.keys(sessionTools), ...Object.keys(agentTools)];
|
|
252
|
-
const skills = this._getAuthorizedSkills();
|
|
253
|
-
const activeSkills = await this.session.getLoadedSkillKeys();
|
|
254
|
-
for (const skill of skills) {
|
|
227
|
+
async _getAuthorizedTools(availableTools, activeSkillName) {
|
|
228
|
+
const tools = { ...availableTools };
|
|
229
|
+
const activeTools = Object.keys(availableTools);
|
|
230
|
+
const authorizedSkills = (await this.getSkills()).filter((skill) => !skill.authorize || skill.authorize?.(this._toolContextForCurrentTurn) !== false);
|
|
231
|
+
for (const skill of authorizedSkills) {
|
|
255
232
|
if (!skill.tools || Object.keys(skill.tools).length === 0) continue;
|
|
256
233
|
Object.assign(tools, skill.tools);
|
|
257
|
-
if (
|
|
234
|
+
if (skill.id === activeSkillName) activeTools.push(...Object.keys(skill.tools));
|
|
258
235
|
}
|
|
259
236
|
return {
|
|
260
237
|
tools,
|
|
261
|
-
activeTools: activeTools.filter((toolName) => tools[toolName]?.authorize?.(this.
|
|
238
|
+
activeTools: activeTools.filter((toolName) => tools[toolName]?.authorize?.(this._toolContextForCurrentTurn) !== false)
|
|
262
239
|
};
|
|
263
240
|
}
|
|
264
|
-
|
|
265
|
-
|
|
241
|
+
_getLastActivatedSkillName(items = []) {
|
|
242
|
+
for (let index = items.length - 1; index >= 0; index--) {
|
|
243
|
+
const item = items[index];
|
|
244
|
+
if (!item || typeof item !== "object") continue;
|
|
245
|
+
const record = item;
|
|
246
|
+
const input = record.input;
|
|
247
|
+
if ((record.toolName === "activate_skill" || record.type === "tool-activate_skill") && input && typeof input === "object") {
|
|
248
|
+
const { name } = input;
|
|
249
|
+
if (typeof name === "string") return name;
|
|
250
|
+
}
|
|
251
|
+
for (const key of [
|
|
252
|
+
"toolResults",
|
|
253
|
+
"parts",
|
|
254
|
+
"content"
|
|
255
|
+
]) {
|
|
256
|
+
const nestedItems = record[key];
|
|
257
|
+
if (Array.isArray(nestedItems)) {
|
|
258
|
+
const skillName = this._getLastActivatedSkillName(nestedItems);
|
|
259
|
+
if (skillName) return skillName;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
266
263
|
}
|
|
267
264
|
/** Store the pending user context request to defer awaiting it until after the connection is established */
|
|
268
|
-
_requestContext;
|
|
269
|
-
/** Store the pending user context request to defer awaiting it until after the connection is established */
|
|
270
265
|
_pendingUserContextRequest;
|
|
271
266
|
/**
|
|
272
267
|
* The user context for the session.
|
|
@@ -285,7 +280,7 @@ var Agent = class extends Think {
|
|
|
285
280
|
async registerInstance() {
|
|
286
281
|
if (this.parentPath.length > 0) return;
|
|
287
282
|
try {
|
|
288
|
-
await
|
|
283
|
+
await registerAgentInstance(this.env.AGENTS_DB, {
|
|
289
284
|
agentName: this.constructor.name,
|
|
290
285
|
durableObjectName: this.name,
|
|
291
286
|
actorId: this.getActorIdFromDurableObjectName(),
|
|
@@ -367,7 +362,7 @@ async function summariseChatWithAI(sql, durableObjectName, messages, model) {
|
|
|
367
362
|
const { output: { title, summary } } = await generateText({
|
|
368
363
|
model,
|
|
369
364
|
system: systemPrompt,
|
|
370
|
-
messages: await convertToModelMessages(messages.slice(-
|
|
365
|
+
messages: await convertToModelMessages(messages.slice(-20)),
|
|
371
366
|
output: Output.object({ schema: jsonSchema({
|
|
372
367
|
type: "object",
|
|
373
368
|
properties: {
|
|
@@ -559,51 +554,6 @@ var Assistant = class extends Agent$1 {
|
|
|
559
554
|
subAgentName: this.agent.name
|
|
560
555
|
});
|
|
561
556
|
}
|
|
562
|
-
/**
|
|
563
|
-
* Binding of the legacy v1 chat Durable Object class, used to migrate a
|
|
564
|
-
* user's v1 chats into facets the first time they connect. Set this on the
|
|
565
|
-
* concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
|
|
566
|
-
* disable it (e.g. for greenfield deployments with no v1 data).
|
|
567
|
-
*/
|
|
568
|
-
migrationBinding;
|
|
569
|
-
/** In-flight migration, shared across concurrent connections to this DO. */
|
|
570
|
-
_migrationPromise;
|
|
571
|
-
/**
|
|
572
|
-
* Runs the lazy v1 -> v2 migration for this user. Concurrent connections to
|
|
573
|
-
* this DO share a single in-flight run. Idempotency across runs/restarts is
|
|
574
|
-
* handled by `migrateUserFromV1` deleting each chat's v1 `conversations` row,
|
|
575
|
-
* so an already-migrated chat is never re-enumerated.
|
|
576
|
-
*/
|
|
577
|
-
async ensureMigrated() {
|
|
578
|
-
if (!this.migrationBinding) return;
|
|
579
|
-
this._migrationPromise ??= this.runMigration().finally(() => {
|
|
580
|
-
this._migrationPromise = void 0;
|
|
581
|
-
});
|
|
582
|
-
await this._migrationPromise;
|
|
583
|
-
}
|
|
584
|
-
async runMigration() {
|
|
585
|
-
if (!this.migrationBinding) return;
|
|
586
|
-
try {
|
|
587
|
-
const result = await migrateUserFromV1({
|
|
588
|
-
sql: this.sql.bind(this),
|
|
589
|
-
db: this.migrationBinding.db,
|
|
590
|
-
userId: this.name,
|
|
591
|
-
durableObjectBinding: this.migrationBinding.binding,
|
|
592
|
-
createFacet: async (chatId) => {
|
|
593
|
-
return await this.subAgent(this.agent, chatId);
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
if (result.migrated > 0 || result.failed > 0) console.info("[Assistant] v1 -> v2 migration complete", {
|
|
597
|
-
userId: this.name,
|
|
598
|
-
...result
|
|
599
|
-
});
|
|
600
|
-
} catch (error) {
|
|
601
|
-
console.error("[Assistant] v1 -> v2 migration failed", {
|
|
602
|
-
userId: this.name,
|
|
603
|
-
error
|
|
604
|
-
});
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
557
|
@callable() async createChat() {
|
|
608
558
|
const id = nanoid();
|
|
609
559
|
const now = Date.now();
|
|
@@ -642,7 +592,7 @@ var Assistant = class extends Agent$1 {
|
|
|
642
592
|
*/
|
|
643
593
|
async registerInstance() {
|
|
644
594
|
try {
|
|
645
|
-
await
|
|
595
|
+
await registerAgentInstance(this.env.AGENTS_DB, {
|
|
646
596
|
agentName: this.constructor.name,
|
|
647
597
|
durableObjectName: this.name,
|
|
648
598
|
actorId: this.name,
|
|
@@ -652,6 +602,52 @@ var Assistant = class extends Agent$1 {
|
|
|
652
602
|
console.error("[Assistant] Failed to register in agent_registry", error);
|
|
653
603
|
}
|
|
654
604
|
}
|
|
605
|
+
/**
|
|
606
|
+
* Binding of the legacy v1 chat Durable Object class, used to migrate a
|
|
607
|
+
* user's v1 chats into facets the first time they connect. Set this on the
|
|
608
|
+
* concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
|
|
609
|
+
* disable it (e.g. for greenfield deployments with no v1 data).
|
|
610
|
+
*/
|
|
611
|
+
getMigrationBinding(_env) {}
|
|
612
|
+
/** In-flight migration, shared across concurrent connections to this DO. */
|
|
613
|
+
_migrationPromise;
|
|
614
|
+
/**
|
|
615
|
+
* Runs the lazy v1 -> v2 migration for this user. Concurrent connections to
|
|
616
|
+
* this DO share a single in-flight run. Idempotency across runs/restarts is
|
|
617
|
+
* handled by `migrateUserFromV1` deleting each chat's v1 `conversations` row,
|
|
618
|
+
* so an already-migrated chat is never re-enumerated.
|
|
619
|
+
*/
|
|
620
|
+
async ensureMigrated() {
|
|
621
|
+
if (!this.getMigrationBinding(this.env)) return;
|
|
622
|
+
this._migrationPromise ??= this.runMigration().finally(() => {
|
|
623
|
+
this._migrationPromise = void 0;
|
|
624
|
+
});
|
|
625
|
+
await this._migrationPromise;
|
|
626
|
+
}
|
|
627
|
+
async runMigration() {
|
|
628
|
+
const migrationBinding = this.getMigrationBinding(this.env);
|
|
629
|
+
if (!migrationBinding) return;
|
|
630
|
+
try {
|
|
631
|
+
const result = await migrateUserFromV1({
|
|
632
|
+
sql: this.sql.bind(this),
|
|
633
|
+
db: migrationBinding.db,
|
|
634
|
+
userId: this.name,
|
|
635
|
+
durableObjectBinding: migrationBinding.binding,
|
|
636
|
+
createFacet: async (chatId) => {
|
|
637
|
+
return await this.subAgent(this.agent, chatId);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
if (result.migrated > 0 || result.failed > 0) console.info("[Assistant] v1 -> v2 migration complete", {
|
|
641
|
+
userId: this.name,
|
|
642
|
+
...result
|
|
643
|
+
});
|
|
644
|
+
} catch (error) {
|
|
645
|
+
console.error("[Assistant] v1 -> v2 migration failed", {
|
|
646
|
+
userId: this.name,
|
|
647
|
+
error
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
}
|
|
655
651
|
};
|
|
656
652
|
//#endregion
|
|
657
653
|
//#region src/server/features/messages.ts
|
|
@@ -771,7 +767,26 @@ function skill(definition) {
|
|
|
771
767
|
if (!definition.name) throw new Error("Skill name is required");
|
|
772
768
|
if (!definition.description) throw new Error("Skill description is required");
|
|
773
769
|
if (!definition.instructions) throw new Error("Skill content is required");
|
|
774
|
-
return
|
|
770
|
+
return {
|
|
771
|
+
id: definition.name,
|
|
772
|
+
fingerprint: definition.name,
|
|
773
|
+
async list() {
|
|
774
|
+
return [{
|
|
775
|
+
name: definition.name,
|
|
776
|
+
description: definition.description
|
|
777
|
+
}];
|
|
778
|
+
},
|
|
779
|
+
async load(name) {
|
|
780
|
+
if (name !== definition.name) return null;
|
|
781
|
+
return {
|
|
782
|
+
name: definition.name,
|
|
783
|
+
description: definition.description,
|
|
784
|
+
body: definition.instructions
|
|
785
|
+
};
|
|
786
|
+
},
|
|
787
|
+
tools: definition.tools,
|
|
788
|
+
authorize: definition.authorize
|
|
789
|
+
};
|
|
775
790
|
}
|
|
776
791
|
//#endregion
|
|
777
792
|
//#region src/server/index.ts
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as _$_ai_sdk_anthropic_internal0 from "@ai-sdk/anthropic/internal";
|
|
2
|
+
import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
3
|
+
import * as _$_ai_sdk_google0 from "@ai-sdk/google";
|
|
4
|
+
import { GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
|
5
|
+
import { AnthropicProvider } from "@ai-sdk/anthropic";
|
|
6
|
+
|
|
7
|
+
//#region src/providers/anthropic.d.ts
|
|
8
|
+
type AnthropicVertexModelId = Parameters<AnthropicProvider>[0];
|
|
9
|
+
interface AnthropicVertexProviderOptions {
|
|
10
|
+
cloudflareAccountId: string;
|
|
11
|
+
cloudflareAiGatewayId: string;
|
|
12
|
+
cloudflareApiToken: string;
|
|
13
|
+
googleCloudProjectId: string;
|
|
14
|
+
location?: string;
|
|
15
|
+
anthropicVersion?: string;
|
|
16
|
+
}
|
|
17
|
+
declare function createAnthropicVertexProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId) => AnthropicMessagesLanguageModel;
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/providers/gemini.d.ts
|
|
20
|
+
type GeminiModelId = Parameters<GoogleGenerativeAIProvider>[0];
|
|
21
|
+
interface GeminiProviderOptions {
|
|
22
|
+
cloudflareAccountId: string;
|
|
23
|
+
cloudflareAiGatewayId: string;
|
|
24
|
+
cloudflareApiToken: string;
|
|
25
|
+
googleApiKey?: string;
|
|
26
|
+
}
|
|
27
|
+
declare function createGeminiProvider(options: GeminiProviderOptions): GoogleGenerativeAIProvider;
|
|
28
|
+
//#endregion
|
|
29
|
+
//#region src/providers/index.d.ts
|
|
30
|
+
interface AiGatewayVertexProvidersOptions extends AnthropicVertexProviderOptions, GeminiProviderOptions {}
|
|
31
|
+
declare function createAiGatewayVertexProviders(options: AiGatewayVertexProvidersOptions): {
|
|
32
|
+
anthropic: (modelId: AnthropicVertexModelId) => _$_ai_sdk_anthropic_internal0.AnthropicMessagesLanguageModel;
|
|
33
|
+
gemini: _$_ai_sdk_google0.GoogleGenerativeAIProvider;
|
|
34
|
+
};
|
|
35
|
+
//#endregion
|
|
36
|
+
export { AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders, createAnthropicVertexProvider, createGeminiProvider };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
2
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
3
|
+
//#region src/providers/anthropic.ts
|
|
4
|
+
function createAnthropicVertexProvider(options) {
|
|
5
|
+
const { anthropicVersion = "vertex-2023-10-16", cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1" } = options;
|
|
6
|
+
const anthropicBaseURL = `${`${`https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/`}google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/`}anthropic/models/`;
|
|
7
|
+
return function anthropic(modelId) {
|
|
8
|
+
return new AnthropicMessagesLanguageModel(modelId, {
|
|
9
|
+
provider: "vertex.anthropic.messages",
|
|
10
|
+
baseURL: anthropicBaseURL,
|
|
11
|
+
headers: { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` },
|
|
12
|
+
buildRequestUrl: (baseURL, isStreaming) => `${baseURL}${modelId}:${isStreaming ? "streamRawPredict" : "rawPredict"}`,
|
|
13
|
+
transformRequestBody: (args) => {
|
|
14
|
+
const { model, ...rest } = args;
|
|
15
|
+
return {
|
|
16
|
+
...rest,
|
|
17
|
+
anthropic_version: anthropicVersion
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
supportedUrls: () => ({}),
|
|
21
|
+
supportsNativeStructuredOutput: false,
|
|
22
|
+
supportsStrictTools: false
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/providers/gemini.ts
|
|
28
|
+
function createGeminiProvider(options) {
|
|
29
|
+
const { cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleApiKey } = options;
|
|
30
|
+
return createGoogleGenerativeAI({
|
|
31
|
+
apiKey: googleApiKey ?? "unused",
|
|
32
|
+
baseURL: `https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/google-ai-studio/v1beta`,
|
|
33
|
+
headers: { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` },
|
|
34
|
+
name: "gateway.google.generative-ai"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//#endregion
|
|
38
|
+
//#region src/providers/index.ts
|
|
39
|
+
function createAiGatewayVertexProviders(options) {
|
|
40
|
+
return {
|
|
41
|
+
anthropic: createAnthropicVertexProvider(options),
|
|
42
|
+
gemini: createGeminiProvider(options)
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { createAiGatewayVertexProviders, createAnthropicVertexProvider, createGeminiProvider };
|
package/dist/v1.d.mts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-
|
|
1
|
+
import { n as JwtAuthConfig, t as routeAgentRequest } from "./route-agent-request-CbjgNl2B.mjs";
|
|
2
2
|
import { Agent as Agent$1, Connection, ConnectionContext } from "agents";
|
|
3
3
|
import { LanguageModel, StreamTextOnFinishCallback, ToolSet, UIMessage, generateText, streamText } from "ai";
|
|
4
4
|
import { AIChatAgent, ChatResponseResult, OnChatMessageOptions } from "@cloudflare/ai-chat";
|
|
5
5
|
|
|
6
|
-
//#region src/server/features/skills/index.d.ts
|
|
6
|
+
//#region src/server/v1/features/skills/index.d.ts
|
|
7
7
|
/**
|
|
8
8
|
* A named group of related tools that can be loaded together on demand.
|
|
9
9
|
*
|
|
@@ -25,7 +25,7 @@ interface Skill {
|
|
|
25
25
|
tools: ToolSet;
|
|
26
26
|
}
|
|
27
27
|
//#endregion
|
|
28
|
-
//#region src/server/util/llm.d.ts
|
|
28
|
+
//#region src/server/v1/util/llm.d.ts
|
|
29
29
|
type LLMParams = Parameters<typeof streamText>[0] & Parameters<typeof generateText>[0];
|
|
30
30
|
type BuildLLMParamsConfig = Omit<LLMParams, "prompt"> & {
|
|
31
31
|
/** Skill names loaded in previous turns. Pass `await this.getLoadedSkills()`. */activeSkills?: string[]; /** Skills available for on-demand loading this turn. */
|
package/dist/v1.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { i as verifyJwt, n as createAgentTracer, r as extractTokenFromConnectRequest, t as routeAgentRequest } from "./route-agent-request-
|
|
1
|
+
import { i as verifyJwt, n as createAgentTracer, r as extractTokenFromConnectRequest, t as routeAgentRequest } from "./route-agent-request-lVm3eus2.mjs";
|
|
2
2
|
import { Agent as Agent$1, callable, getCurrentAgent } from "agents";
|
|
3
3
|
import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
|
|
4
4
|
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
5
|
-
//#region src/server/features/skills/index.ts
|
|
6
|
-
const TOOL_NAME_ACTIVATE_SKILL = "activate_skill";
|
|
7
|
-
const TOOL_NAME_LIST_CAPABILITIES = "list_capabilities";
|
|
5
|
+
//#region src/server/v1/features/skills/index.ts
|
|
6
|
+
const TOOL_NAME_ACTIVATE_SKILL$1 = "activate_skill";
|
|
7
|
+
const TOOL_NAME_LIST_CAPABILITIES$1 = "list_capabilities";
|
|
8
8
|
const SKILL_STATE_SENTINEL = "\n__SKILLS_STATE__:";
|
|
9
9
|
function buildActivateSkillDescription(skills) {
|
|
10
10
|
return [
|
|
@@ -37,8 +37,8 @@ function createSkills(config) {
|
|
|
37
37
|
for (const skill of skills) Object.assign(allTools, skill.tools);
|
|
38
38
|
function getActiveToolNames() {
|
|
39
39
|
const names = [
|
|
40
|
-
TOOL_NAME_ACTIVATE_SKILL,
|
|
41
|
-
TOOL_NAME_LIST_CAPABILITIES,
|
|
40
|
+
TOOL_NAME_ACTIVATE_SKILL$1,
|
|
41
|
+
TOOL_NAME_LIST_CAPABILITIES$1,
|
|
42
42
|
...Object.keys(alwaysOnTools)
|
|
43
43
|
];
|
|
44
44
|
for (const skillName of loadedSkills) {
|
|
@@ -57,7 +57,7 @@ function createSkills(config) {
|
|
|
57
57
|
if (sections.length === 0) return "";
|
|
58
58
|
return sections.join("\n\n");
|
|
59
59
|
}
|
|
60
|
-
allTools[TOOL_NAME_ACTIVATE_SKILL] = tool({
|
|
60
|
+
allTools[TOOL_NAME_ACTIVATE_SKILL$1] = tool({
|
|
61
61
|
description: buildActivateSkillDescription(skills),
|
|
62
62
|
inputSchema: jsonSchema({
|
|
63
63
|
type: "object",
|
|
@@ -83,7 +83,7 @@ function createSkills(config) {
|
|
|
83
83
|
return "All requested skills were already loaded.";
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
|
-
allTools[TOOL_NAME_LIST_CAPABILITIES] = tool({
|
|
86
|
+
allTools[TOOL_NAME_LIST_CAPABILITIES$1] = tool({
|
|
87
87
|
description: LIST_CAPABILITIES_DESCRIPTION,
|
|
88
88
|
inputSchema: jsonSchema({
|
|
89
89
|
type: "object",
|
|
@@ -115,11 +115,6 @@ function createSkills(config) {
|
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
function isEphemeralSkillToolPart(part) {
|
|
119
|
-
if (!part || typeof part !== "object" || !("toolCallId" in part) || !("type" in part)) return false;
|
|
120
|
-
const { type } = part;
|
|
121
|
-
return type === `tool-list_capabilities` || type === `tool-activate_skill`;
|
|
122
|
-
}
|
|
123
118
|
/** Creates the `skill_state` table in DO SQLite if it does not exist yet. */
|
|
124
119
|
function ensureSkillTable(sql) {
|
|
125
120
|
sql`CREATE TABLE IF NOT EXISTS skill_state (id INTEGER PRIMARY KEY, active_skills TEXT NOT NULL DEFAULT '[]')`;
|
|
@@ -161,7 +156,7 @@ function saveSkillStateFromMessages(sql, messages) {
|
|
|
161
156
|
sql`INSERT OR REPLACE INTO skill_state(id, active_skills) VALUES(1, ${JSON.stringify(latestSkillState)})`;
|
|
162
157
|
}
|
|
163
158
|
//#endregion
|
|
164
|
-
//#region src/server/util/llm.ts
|
|
159
|
+
//#region src/server/v1/util/llm.ts
|
|
165
160
|
function buildSystemPromptWithSkills(basePrompt, availableSkillList, loadedGuidance) {
|
|
166
161
|
let prompt = `${basePrompt}
|
|
167
162
|
|
|
@@ -556,7 +551,7 @@ async function updateConversationSummary(db, durableObjectName, title, summary)
|
|
|
556
551
|
* prompt bounded regardless of total conversation length.
|
|
557
552
|
*/
|
|
558
553
|
async function generateTitleAndSummary(messages, model, existingSummary) {
|
|
559
|
-
const recentMessages = await convertToModelMessages(messages.slice(-
|
|
554
|
+
const recentMessages = await convertToModelMessages(messages.slice(-15));
|
|
560
555
|
const previousContext = existingSummary ? `Previous summary:\n${existingSummary}\n\nMost recent messages:` : "Conversation:";
|
|
561
556
|
const { output } = await generateText({
|
|
562
557
|
model,
|
|
@@ -610,7 +605,12 @@ function getDeleteConversationScheduleIds(schedules) {
|
|
|
610
605
|
return schedules.filter((schedule) => schedule.callback === DELETE_CONVERSATION_CALLBACK).map((schedule) => schedule.id);
|
|
611
606
|
}
|
|
612
607
|
//#endregion
|
|
613
|
-
//#region src/server/util/messages.ts
|
|
608
|
+
//#region src/server/v1/util/messages.ts
|
|
609
|
+
function isEphemeralSkillToolPart(part) {
|
|
610
|
+
if (!part || typeof part !== "object" || !("toolCallId" in part) || !("type" in part)) return false;
|
|
611
|
+
const { type } = part;
|
|
612
|
+
return type === `tool-list_capabilities` || type === `tool-activate_skill`;
|
|
613
|
+
}
|
|
614
614
|
function filterEphemeralMessages(messages) {
|
|
615
615
|
return messages.flatMap((message) => {
|
|
616
616
|
if (message.role !== "assistant" || !message.parts?.length) return [message];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"exports": {
|
|
16
16
|
".": "./dist/index.mjs",
|
|
17
17
|
"./cli": "./dist/cli.mjs",
|
|
18
|
+
"./providers": "./dist/providers.mjs",
|
|
18
19
|
"./v1": "./dist/v1.mjs",
|
|
19
20
|
"./package.json": "./package.json"
|
|
20
21
|
},
|
|
@@ -26,30 +27,30 @@
|
|
|
26
27
|
"prepublishOnly": "npm run build"
|
|
27
28
|
},
|
|
28
29
|
"dependencies": {
|
|
30
|
+
"@ai-sdk/anthropic": "^3.0.77",
|
|
31
|
+
"@ai-sdk/google": "^3.0.73",
|
|
29
32
|
"@clack/prompts": "^1.2.0",
|
|
30
|
-
"@cloudflare/ai-chat": "^0.
|
|
31
|
-
"@cloudflare/think": "^0.
|
|
33
|
+
"@cloudflare/ai-chat": "^0.8.3",
|
|
34
|
+
"@cloudflare/think": "^0.8.4",
|
|
32
35
|
"@opentelemetry/sdk-trace-base": "^2.7.1",
|
|
33
|
-
"agents": "^0.
|
|
36
|
+
"agents": "^0.14.3",
|
|
37
|
+
"ai": "^6.0.197",
|
|
38
|
+
"jose": "^6.2.3",
|
|
34
39
|
"nanoid": "^5.1.11"
|
|
35
40
|
},
|
|
36
41
|
"devDependencies": {
|
|
37
42
|
"@cloudflare/workers-types": "^4.20260527.1",
|
|
38
43
|
"@types/node": "^25.6.0",
|
|
39
44
|
"@typescript/native-preview": "7.0.0-dev.20260412.1",
|
|
40
|
-
"ai": "^6.0.175",
|
|
41
|
-
"jose": "^6.2.2",
|
|
42
45
|
"tsdown": "^0.22.0",
|
|
43
46
|
"typescript": "^6.0.2",
|
|
44
47
|
"vitest": "^4.1.4"
|
|
45
48
|
},
|
|
46
49
|
"peerDependencies": {
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
52
|
-
"optional": true
|
|
53
|
-
}
|
|
50
|
+
"@cloudflare/think": "^0.8.4",
|
|
51
|
+
"@cloudflare/vite-plugin": "^1.40.0",
|
|
52
|
+
"@cloudflare/worker-bundler": "^0.2.0",
|
|
53
|
+
"agents": "^0.14.3",
|
|
54
|
+
"vite": "^8.0.0"
|
|
54
55
|
}
|
|
55
56
|
}
|
|
File without changes
|
|
File without changes
|