@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 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 conversation features: compaction and message feedback.
13
- - **`Assistant`** — per-user shell over `ChatAgent`: create/list/delete conversations, titles, summaries, retention.
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 conversation
18
- └─ Assistant ← one user, many conversations
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 ai
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
- `jose` is an optional peer dependency, needed only for JWT auth.
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 conversation; address one per user, ticket, or whatever fits.
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 conversation's SQLite (`assistant_messages_feedback`, created automatically):
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 conversation, keyed by message id. |
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 conversations — a sidebar, titles, summaries — add an `Assistant`. It owns the chat list and routes to a `ChatAgent` per conversation.
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 conversation:
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 conversation and returns its id. |
200
- | `getChats()` | `Chat[]` | The user's conversations, most recently updated first. |
201
- | `deleteChat(id)` | `void` | Deletes a conversation and its record. |
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 conversation grows).
204
- - **Retention** — inactive conversations are deleted after 90 days, via a Durable Object alarm.
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
- A skill bundles markdown `instructions` with optional tools. Only `description` sits in the system prompt; the model loads the instructions and tools on demand via the built-in `load_context` tool.
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. Return keys from `getRemoteSkills()`:
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
- protected getRemoteSkills() {
302
- return ["billing.md", "tax.md"];
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
- Loaded from `SKILLS_BUCKET` under the `skills/` prefix the object's `description` metadata shows up front, the body loads on demand. Skipped (with a logged error) if `SKILLS_BUCKET` isn't bound.
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` conversations. |
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 conversation management. |
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-DmwIOBJS.mjs";
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, Session, StepConfig, Think, ToolCallContext, ToolCallDecision, TurnConfig, TurnContext } from "@cloudflare/think";
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: Context) => boolean;
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 in the current turn.
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<StepConfig | void>;
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 _getAuthorizedSkills;
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 migrationBinding?: {
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-DKDvDYnR.mjs";
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 registerAgent(db, input) {
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
- let configuredSession = session.withContext("soul", { provider: { get: async () => {
153
- return this.getSystemPrompt(this._requestContext !== void 0 ? this._buildToolContext() : void 0);
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._requestContext = ctx.body ?? {};
149
+ this._toolContextForCurrentTurn = {
150
+ ...ctx.body ?? {},
151
+ _userContext: this._userContext
152
+ };
169
153
  await this.session.refreshSystemPrompt();
170
- const { tools, activeTools } = await this._getAuthorizedTools();
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._buildToolContext()),
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: ["load_context"]
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 in the current turn.
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 { activeTools } = await this._getAuthorizedTools();
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._buildToolContext()
201
+ experimental_context: this._toolContextForCurrentTurn ?? {}
214
202
  };
215
203
  }
216
204
  async beforeToolCall(ctx) {
217
- if (ctx.toolName === "load_context" && ctx.input["label"] === "local-skills") {
218
- if (!this._getAuthorizedSkills().some((skill) => skill.name === ctx.input["key"])) return {
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 sessionTools = await this.session.tools();
246
- const agentTools = this.getTools();
247
- const tools = {
248
- ...sessionTools,
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 (activeSkills.has(`local-skills:${skill.name}`)) activeTools.push(...Object.keys(skill.tools));
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._buildToolContext()) !== false)
238
+ activeTools: activeTools.filter((toolName) => tools[toolName]?.authorize?.(this._toolContextForCurrentTurn) !== false)
262
239
  };
263
240
  }
264
- _getAuthorizedSkills() {
265
- return this.getSkills().filter((skill) => skill.authorize?.(this._buildToolContext()) !== false);
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 registerAgent(this.env.AGENTS_DB, {
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(-CHAT_RECENT_MESSAGES_COUNT)),
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 registerAgent(this.env.AGENTS_DB, {
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 definition;
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-DmwIOBJS.mjs";
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-DKDvDYnR.mjs";
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(-SUMMARY_CONTEXT_MESSAGES));
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.2.3",
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.7.2",
31
- "@cloudflare/think": "^0.7.3",
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.13.3",
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
- "ai": "^6.0.0",
48
- "jose": "^6.0.0"
49
- },
50
- "peerDependenciesMeta": {
51
- "jose": {
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
  }