@economic/agents 0.0.1-alpha.25 → 0.0.1-alpha.27

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
@@ -6,6 +6,8 @@ Base class and utilities for building LLM chat agents on Cloudflare's Agents SDK
6
6
  npm install @economic/agents ai @cloudflare/ai-chat agents
7
7
  ```
8
8
 
9
+ For React UIs that import `@economic/agents/react`, also install `react`. It is an **optional** peer of the package so Workers-only installs are not required to add React. The hook still needs `agents`, `ai`, and `@cloudflare/ai-chat` at runtime (same as the Cloudflare SDK imports it wraps).
10
+
9
11
  ---
10
12
 
11
13
  ## Overview
@@ -15,9 +17,35 @@ npm install @economic/agents ai @cloudflare/ai-chat agents
15
17
  - **`AIChatAgent`** — an abstract Cloudflare Durable Object base class. Implement `onChatMessage`, call `this.buildLLMParams()`, and pass the result to `streamText` from the AI SDK.
16
18
  - **`guard`** — optional TypeScript 5+ method decorator for `onChatMessage`. Runs your function with `options.body`; return a `Response` to short-circuit (e.g. auth), or nothing to continue.
17
19
  - **`buildLLMParams`** — the standalone version of the above, for use outside of `AIChatAgent` or in custom agent implementations.
20
+ - **`useAIChatAgent`** (subpath `@economic/agents/react`) — React hook that wraps `useAgent` (`agents/react`) and `useAgentChat` (`@cloudflare/ai-chat/react`). Connection status is **callback-only** (`onConnectionStatusChange`). On WebSocket close codes **`>= 3000`**, the hook calls `agent.close()` to stop reconnection, then forwards `onClose` (use `event.reason` for the server message). Pass-through **`onOpen`**, **`onClose`**, **`onError`** are supported.
18
21
 
19
22
  Skills and compaction are AI SDK concerns — they control what goes to the LLM. The CF layer is responsible for WebSockets, Durable Objects, and message persistence. These are kept separate.
20
23
 
24
+ ### React client
25
+
26
+ ```typescript
27
+ import { useAIChatAgent, type AgentConnectionStatus } from "@economic/agents/react";
28
+ import { useState } from "react";
29
+
30
+ const [connectionStatus, setConnectionStatus] = useState<AgentConnectionStatus>("connecting");
31
+
32
+ const { agent, chat } = useAIChatAgent({
33
+ agent: "MyAgent",
34
+ host: "localhost:8787",
35
+ chatId: "session-id",
36
+ toolContext: {},
37
+ connectionParams: { userId: "…" },
38
+ onConnectionStatusChange: setConnectionStatus,
39
+ onOpen: (event) => {},
40
+ onClose: (event) => {},
41
+ onError: (event) => {},
42
+ });
43
+
44
+ const { messages, sendMessage, status, stop } = chat;
45
+ ```
46
+
47
+ Server-side agent code still imports only from `@economic/agents`; the `/react` entry is a separate build output and does not pull Workers runtime code into the client bundle.
48
+
21
49
  ---
22
50
 
23
51
  ## Quick start
package/dist/index.d.mts CHANGED
@@ -2,7 +2,7 @@ import { AIChatAgent as AIChatAgent$1, OnChatMessageOptions } from "@cloudflare/
2
2
  import { LanguageModel, ToolSet, UIMessage, generateText, streamText } from "ai";
3
3
  import { Connection, ConnectionContext } from "agents";
4
4
 
5
- //#region src/features/skills/index.d.ts
5
+ //#region src/server/features/skills/index.d.ts
6
6
  /**
7
7
  * A named group of related tools that can be loaded together on demand.
8
8
  *
@@ -23,7 +23,7 @@ interface Skill {
23
23
  tools: ToolSet;
24
24
  }
25
25
  //#endregion
26
- //#region src/llm.d.ts
26
+ //#region src/server/llm.d.ts
27
27
  type LLMParams = Parameters<typeof streamText>[0] & Parameters<typeof generateText>[0];
28
28
  type BuildLLMParamsConfig = Omit<LLMParams, "messages" | "experimental_context" | "abortSignal"> & {
29
29
  /** CF options object — extracts `abortSignal` and `experimental_context` (from `body`). */options: OnChatMessageOptions | undefined; /** Conversation history (`this.messages`). Converted to `ModelMessage[]` internally. */
@@ -66,7 +66,7 @@ type BuildLLMParamsConfig = Omit<LLMParams, "messages" | "experimental_context"
66
66
  */
67
67
  declare function buildLLMParams(config: BuildLLMParamsConfig): Promise<LLMParams>;
68
68
  //#endregion
69
- //#region src/agents/AIChatAgent.d.ts
69
+ //#region src/server/agents/AIChatAgent.d.ts
70
70
  /**
71
71
  * Base class for Cloudflare Agents SDK chat agents with lazy skill loading
72
72
  * and built-in audit logging.
@@ -98,13 +98,13 @@ declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env>
98
98
  * to `buildLLMParams` rather than omitting or nulling out `fastModel`.
99
99
  */
100
100
  protected abstract fastModel: LanguageModel;
101
+ onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
101
102
  /**
102
103
  * Resolves the D1 database binding and userId required for all D1 writes.
103
104
  * Returns null and silently no-ops if AGENT_DB is not bound.
104
105
  * Returns null and logs an error if userId is missing.
105
106
  */
106
107
  private resolveD1Context;
107
- onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
108
108
  /**
109
109
  * Returns all conversations for the current user.
110
110
  */
@@ -178,7 +178,7 @@ declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env>
178
178
  }): Promise<void>;
179
179
  }
180
180
  //#endregion
181
- //#region src/types.d.ts
181
+ //#region src/server/types.d.ts
182
182
  /**
183
183
  * The context object available throughout an agent's lifetime — passed via
184
184
  * `experimental_context` to tool `execute` functions. Contains the typed
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { AIChatAgent as AIChatAgent$1 } from "@cloudflare/ai-chat";
2
2
  import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, tool } from "ai";
3
3
  import { callable } from "agents";
4
- //#region src/features/skills/index.ts
4
+ //#region src/server/features/skills/index.ts
5
5
  /** Creates the `skill_state` table in DO SQLite if it does not exist yet. */
6
6
  function ensureSkillTable(sql) {
7
7
  sql`CREATE TABLE IF NOT EXISTS skill_state (id INTEGER PRIMARY KEY, active_skills TEXT NOT NULL DEFAULT '[]')`;
@@ -279,7 +279,7 @@ async function compactIfNeeded(messages, model, tailSize) {
279
279
  return compactMessages(messages, model, tailSize);
280
280
  }
281
281
  //#endregion
282
- //#region src/llm.ts
282
+ //#region src/server/llm.ts
283
283
  /**
284
284
  * Builds the parameter object for a Vercel AI SDK `streamText` or `generateText` call.
285
285
  *
@@ -328,7 +328,7 @@ async function buildLLMParams(config) {
328
328
  };
329
329
  }
330
330
  //#endregion
331
- //#region src/features/audit/index.ts
331
+ //#region src/server/features/audit/index.ts
332
332
  /**
333
333
  * Inserts a single audit event row into the shared `audit_events` D1 table.
334
334
  *
@@ -370,7 +370,7 @@ function extractMessageText(msg) {
370
370
  return msg.parts.filter((p) => p.type === "text").map((p) => p.text).join(" ").trim();
371
371
  }
372
372
  //#endregion
373
- //#region src/features/conversations/index.ts
373
+ //#region src/server/features/conversations/index.ts
374
374
  /**
375
375
  * Records a conversation row in the `conversations` D1 table.
376
376
  *
@@ -459,7 +459,7 @@ async function generateConversationSummary(db, durableObjectId, messages, model)
459
459
  await updateConversationSummary(db, durableObjectId, title, summary);
460
460
  }
461
461
  //#endregion
462
- //#region src/agents/AIChatAgent.ts
462
+ //#region src/server/agents/AIChatAgent.ts
463
463
  /**
464
464
  * Base class for Cloudflare Agents SDK chat agents with lazy skill loading
465
465
  * and built-in audit logging.
@@ -478,6 +478,16 @@ var AIChatAgent = class extends AIChatAgent$1 {
478
478
  * Undefined if the client did not include `userId`.
479
479
  */
480
480
  _userId;
481
+ async onConnect(connection, ctx) {
482
+ super.onConnect(connection, ctx);
483
+ const userId = new URL(ctx.request.url).searchParams.get("userId");
484
+ if (!userId) {
485
+ console.error("[AIChatAgent] Connection rejected: userId query parameter is required");
486
+ connection.close(3e3, "userId query parameter is required");
487
+ return;
488
+ }
489
+ this._userId = userId;
490
+ }
481
491
  /**
482
492
  * Resolves the D1 database binding and userId required for all D1 writes.
483
493
  * Returns null and silently no-ops if AGENT_DB is not bound.
@@ -485,9 +495,12 @@ var AIChatAgent = class extends AIChatAgent$1 {
485
495
  */
486
496
  resolveD1Context() {
487
497
  const db = this.env.AGENT_DB;
488
- if (!db) return null;
498
+ if (!db) {
499
+ console.error("[AIChatAgent] Skipping logging: D1 database not found");
500
+ return null;
501
+ }
489
502
  if (!this._userId) {
490
- console.error("[AIChatAgent] Logging & conversation tracking skipped: userId not provided in connection query params");
503
+ console.error("[AIChatAgent] Skipping logging: userId not provided in connection query params");
491
504
  return null;
492
505
  }
493
506
  return {
@@ -495,15 +508,6 @@ var AIChatAgent = class extends AIChatAgent$1 {
495
508
  userId: this._userId
496
509
  };
497
510
  }
498
- async onConnect(connection, ctx) {
499
- super.onConnect(connection, ctx);
500
- const userId = new URL(ctx.request.url).searchParams.get("userId");
501
- if (!userId) {
502
- connection.close(3e3, "userId query parameter is required");
503
- return;
504
- }
505
- this._userId = userId;
506
- }
507
511
  /**
508
512
  * Returns all conversations for the current user.
509
513
  */
@@ -0,0 +1,25 @@
1
+ import { UIMessage } from "ai";
2
+ import { useAgentChat } from "@cloudflare/ai-chat/react";
3
+ import { useAgent } from "agents/react";
4
+
5
+ //#region src/client/index.d.ts
6
+ type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized";
7
+ interface UseAIChatAgentOptions {
8
+ agent: string;
9
+ host: string;
10
+ basePath?: string;
11
+ chatId: string;
12
+ toolContext?: Record<string, unknown>;
13
+ connectionParams?: Record<string, string>;
14
+ onConnectionStatusChange?: (status: AgentConnectionStatus) => void;
15
+ onOpen?: (event: Event) => void;
16
+ onClose?: (event: CloseEvent) => void;
17
+ onError?: (event: ErrorEvent) => void;
18
+ }
19
+ type UseAIChatAgentResult = {
20
+ agent: ReturnType<typeof useAgent>;
21
+ chat: ReturnType<typeof useAgentChat<unknown, UIMessage>>;
22
+ };
23
+ declare function useAIChatAgent(options: UseAIChatAgentOptions): UseAIChatAgentResult;
24
+ //#endregion
25
+ export { AgentConnectionStatus, UseAIChatAgentOptions, useAIChatAgent };
package/dist/react.mjs ADDED
@@ -0,0 +1,38 @@
1
+ import { useAgentChat } from "@cloudflare/ai-chat/react";
2
+ import { useAgent } from "agents/react";
3
+ //#region src/client/index.ts
4
+ function useAIChatAgent(options) {
5
+ const { agent: agentName, host, basePath, chatId, toolContext, connectionParams, onConnectionStatusChange, onOpen: onOpenProp, onClose: onCloseProp, onError: onErrorProp } = options;
6
+ const agent = useAgent({
7
+ agent: agentName,
8
+ host,
9
+ basePath,
10
+ name: chatId,
11
+ query: connectionParams ?? {},
12
+ onOpen: (event) => {
13
+ onConnectionStatusChange?.("connected");
14
+ onOpenProp?.(event);
15
+ },
16
+ onClose: (event) => {
17
+ if (event.code >= 3e3) {
18
+ onConnectionStatusChange?.("unauthorized");
19
+ agent.close();
20
+ onCloseProp?.(event);
21
+ return;
22
+ }
23
+ onConnectionStatusChange?.("disconnected");
24
+ onCloseProp?.(event);
25
+ },
26
+ onError: onErrorProp
27
+ });
28
+ return {
29
+ agent,
30
+ chat: useAgentChat({
31
+ agent,
32
+ body: toolContext ?? {},
33
+ getInitialMessages: null
34
+ })
35
+ };
36
+ }
37
+ //#endregion
38
+ export { useAIChatAgent };
package/package.json CHANGED
@@ -1,17 +1,8 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "0.0.1-alpha.25",
3
+ "version": "0.0.1-alpha.27",
4
4
  "description": "A starter for creating a TypeScript package.",
5
- "homepage": "https://github.com/author/library#readme",
6
- "bugs": {
7
- "url": "https://github.com/author/library/issues"
8
- },
9
5
  "license": "MIT",
10
- "author": "Author Name <author.name@mail.com>",
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/author/library.git"
14
- },
15
6
  "files": [
16
7
  "dist",
17
8
  "schema"
@@ -19,6 +10,7 @@
19
10
  "type": "module",
20
11
  "exports": {
21
12
  ".": "./dist/index.mjs",
13
+ "./react": "./dist/react.mjs",
22
14
  "./package.json": "./package.json"
23
15
  },
24
16
  "scripts": {
@@ -33,9 +25,11 @@
33
25
  "@cloudflare/ai-chat": "^0.1.9",
34
26
  "@cloudflare/workers-types": "^4.20260317.1",
35
27
  "@types/node": "^22.0.0",
28
+ "@types/react": "^19.0.0",
36
29
  "@typescript/native-preview": "7.0.0-dev.20260316.1",
37
30
  "ai": "^6.0.134",
38
31
  "bumpp": "^11.0.1",
32
+ "react": "^19.0.0",
39
33
  "tsdown": "^0.21.4",
40
34
  "typescript": "^5.9.3",
41
35
  "vitest": "^4.1.0"
@@ -43,6 +37,12 @@
43
37
  "peerDependencies": {
44
38
  "@cloudflare/ai-chat": "^0.1.0",
45
39
  "agents": "^0.7.6",
46
- "ai": "^6.0.0"
40
+ "ai": "^6.0.0",
41
+ "react": ">=18"
42
+ },
43
+ "peerDependenciesMeta": {
44
+ "react": {
45
+ "optional": true
46
+ }
47
47
  }
48
48
  }