@economic/agents 1.4.1 → 1.5.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 +18 -0
- package/dist/index.d.mts +37 -45
- package/dist/index.mjs +87 -83
- package/package.json +3 -2
- package/schema/chat.sql +12 -2
package/README.md
CHANGED
|
@@ -476,6 +476,24 @@ When the retention period expires, the D1 row is deleted, WebSocket connections
|
|
|
476
476
|
const conversations = await agent.call("getConversations");
|
|
477
477
|
```
|
|
478
478
|
|
|
479
|
+
#### Message Ratings (D1)
|
|
480
|
+
|
|
481
|
+
Users can rate individual messages with thumbs up/down. Ratings are stored in D1 and can be updated if the user changes their mind.
|
|
482
|
+
|
|
483
|
+
```typescript
|
|
484
|
+
// Rate a message (1 = thumbs up, -1 = thumbs down)
|
|
485
|
+
await agent.call("rateMessage", [messageId, 1]);
|
|
486
|
+
|
|
487
|
+
// Change rating
|
|
488
|
+
await agent.call("rateMessage", [messageId, -1]);
|
|
489
|
+
|
|
490
|
+
// Get all ratings for the current conversation
|
|
491
|
+
const ratings = await agent.call("getMessageRatings");
|
|
492
|
+
// Returns: { "message_id_1": 1, "message_id_2": -1, ... }
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
Ratings are stored per message and upserted on conflict — calling `rateMessage` on an already-rated message updates the rating. Requires [`schema/chat.sql`](schema/chat.sql) which includes the `message_ratings` table.
|
|
496
|
+
|
|
479
497
|
---
|
|
480
498
|
|
|
481
499
|
## API Reference
|
package/dist/index.d.mts
CHANGED
|
@@ -1,29 +1,9 @@
|
|
|
1
|
-
import { Agent as Agent$1, AgentOptions, Connection, ConnectionContext } from "agents";
|
|
2
|
-
import { AIChatAgent, OnChatMessageOptions } from "@cloudflare/ai-chat";
|
|
3
1
|
import { LanguageModel, StreamTextOnFinishCallback, ToolSet, UIMessage, generateText, streamText } from "ai";
|
|
2
|
+
import { Agent as Agent$1, AgentOptions, Connection, ConnectionContext } from "agents";
|
|
3
|
+
import { AIChatAgent, ChatResponseResult, OnChatMessageOptions } from "@cloudflare/ai-chat";
|
|
4
4
|
import { JWTPayload } from "jose";
|
|
5
5
|
|
|
6
|
-
//#region src/server/
|
|
7
|
-
/**
|
|
8
|
-
* The context object available throughout an agent's lifetime — passed via
|
|
9
|
-
* `experimental_context` to tool `execute` functions. Contains the typed
|
|
10
|
-
* request body merged with platform capabilities like `logEvent`.
|
|
11
|
-
*
|
|
12
|
-
* Define your own body shape and compose:
|
|
13
|
-
* ```typescript
|
|
14
|
-
* interface MyBody { userTier: "free" | "pro" }
|
|
15
|
-
* type MyContext = AgentToolContext<MyBody>;
|
|
16
|
-
* ```
|
|
17
|
-
*/
|
|
18
|
-
type AgentToolContext<TBody = Record<string, unknown>, TSession = Record<string, unknown> | undefined> = TBody & {
|
|
19
|
-
logEvent: (message: string, payload?: Record<string, unknown>) => void | Promise<void>;
|
|
20
|
-
session?: TSession;
|
|
21
|
-
};
|
|
22
|
-
interface AgentEnv {
|
|
23
|
-
AGENT_DB: D1Database;
|
|
24
|
-
}
|
|
25
|
-
//#endregion
|
|
26
|
-
//#region src/server/features/skills/index.d.ts
|
|
6
|
+
//#region src/server/shared/features/skills/index.d.ts
|
|
27
7
|
/**
|
|
28
8
|
* A named group of related tools that can be loaded together on demand.
|
|
29
9
|
*
|
|
@@ -45,7 +25,7 @@ interface Skill {
|
|
|
45
25
|
tools: ToolSet;
|
|
46
26
|
}
|
|
47
27
|
//#endregion
|
|
48
|
-
//#region src/server/llm.d.ts
|
|
28
|
+
//#region src/server/shared/util/llm.d.ts
|
|
49
29
|
type LLMParams = Parameters<typeof streamText>[0] & Parameters<typeof generateText>[0];
|
|
50
30
|
type BuildLLMParamsConfig = Omit<LLMParams, "prompt"> & {
|
|
51
31
|
/** Skill names loaded in previous turns. Pass `await this.getLoadedSkills()`. */activeSkills?: string[]; /** Skills available for on-demand loading this turn. */
|
|
@@ -65,7 +45,31 @@ type BuildLLMParamsConfig = Omit<LLMParams, "prompt"> & {
|
|
|
65
45
|
*/
|
|
66
46
|
declare function buildLLMParams(config: BuildLLMParamsConfig): LLMParams;
|
|
67
47
|
//#endregion
|
|
68
|
-
//#region src/server/
|
|
48
|
+
//#region src/server/types.d.ts
|
|
49
|
+
/**
|
|
50
|
+
* The context object available throughout an agent's lifetime — passed via
|
|
51
|
+
* `experimental_context` to tool `execute` functions. Contains the typed
|
|
52
|
+
* request body merged with platform capabilities like `logEvent`.
|
|
53
|
+
*
|
|
54
|
+
* Define your own body shape and compose:
|
|
55
|
+
* ```typescript
|
|
56
|
+
* interface MyBody { userTier: "free" | "pro" }
|
|
57
|
+
* type MyContext = AgentToolContext<MyBody>;
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
type AgentToolContext<TBody = Record<string, unknown>, TSession = Record<string, unknown> | undefined> = TBody & {
|
|
61
|
+
logEvent: (message: string, payload?: Record<string, unknown>) => void | Promise<void>;
|
|
62
|
+
session?: TSession;
|
|
63
|
+
};
|
|
64
|
+
interface AgentEnv {
|
|
65
|
+
AGENT_DB: D1Database;
|
|
66
|
+
}
|
|
67
|
+
interface ChatAgentEnv extends AgentEnv {}
|
|
68
|
+
//#endregion
|
|
69
|
+
//#region src/server/route-agent-request.d.ts
|
|
70
|
+
declare function routeAgentRequest<Env>(request: Request, env: Env, options?: AgentOptions<Env>): Promise<Response | null>;
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/server/shared/features/auth/index.d.ts
|
|
69
73
|
interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string, unknown>> {
|
|
70
74
|
/** Issuers whose tokens are accepted (exact string or RegExp). */
|
|
71
75
|
allowedIssuers: readonly (string | RegExp)[];
|
|
@@ -80,7 +84,7 @@ interface JwtAuthConfig<TClaims extends Record<string, unknown> = Record<string,
|
|
|
80
84
|
getClaims: (payload: JWTPayload) => TClaims;
|
|
81
85
|
}
|
|
82
86
|
//#endregion
|
|
83
|
-
//#region src/server/
|
|
87
|
+
//#region src/server/agent/Agent.d.ts
|
|
84
88
|
/**
|
|
85
89
|
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading,
|
|
86
90
|
* audit logging, and `buildLLMParams` wiring.
|
|
@@ -130,7 +134,7 @@ declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env> extend
|
|
|
130
134
|
}): Promise<LLMParams>;
|
|
131
135
|
}
|
|
132
136
|
//#endregion
|
|
133
|
-
//#region src/server/
|
|
137
|
+
//#region src/server/agent-chat/ChatAgent.d.ts
|
|
134
138
|
/**
|
|
135
139
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|
|
136
140
|
* message persistence, compaction, and conversation metadata in D1.
|
|
@@ -142,7 +146,7 @@ declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env> extend
|
|
|
142
146
|
* Skill loading, compaction, and LLM calls use `buildLLMParams` from
|
|
143
147
|
* `@economic/agents` inside `onChatMessage`.
|
|
144
148
|
*/
|
|
145
|
-
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent<Env &
|
|
149
|
+
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent<Env & ChatAgentEnv> {
|
|
146
150
|
/**
|
|
147
151
|
* The binding of the Durable Object instance for this agent.
|
|
148
152
|
*/
|
|
@@ -222,26 +226,17 @@ declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> ex
|
|
|
222
226
|
persistMessages(messages: UIMessage[], excludeBroadcastIds?: string[], options?: {
|
|
223
227
|
_deleteStaleRows?: boolean;
|
|
224
228
|
}): Promise<void>;
|
|
229
|
+
protected onChatResponse(result: ChatResponseResult): Promise<void>;
|
|
230
|
+
rateMessage(messageId: string, rating: number): Promise<void>;
|
|
231
|
+
getMessageRatings(): Promise<Record<string, number>>;
|
|
225
232
|
getConversations(): Promise<Record<string, unknown>[]>;
|
|
226
233
|
deleteConversation(id: string): Promise<boolean>;
|
|
227
234
|
destroy(): Promise<void>;
|
|
228
|
-
/**
|
|
229
|
-
* Records this conversation in the `conversations` D1 table and triggers
|
|
230
|
-
* LLM-based title/summary generation when appropriate. Called automatically
|
|
231
|
-
* from `onFinish` in `buildLLMParams` after each completed LLM turn.
|
|
232
|
-
*
|
|
233
|
-
* On the first turn (no existing row), awaits `generateTitleAndSummary` and
|
|
234
|
-
* inserts the row with title and summary already populated. On subsequent
|
|
235
|
-
* turns, upserts the timestamp and fire-and-forgets a summary refresh every
|
|
236
|
-
* `SUMMARY_CONTEXT_MESSAGES` messages (when the context window fully turns
|
|
237
|
-
* over). Neither path blocks the response to the client.
|
|
238
|
-
*/
|
|
239
|
-
private recordConversation;
|
|
240
235
|
private deleteConversationCallback;
|
|
241
|
-
private
|
|
236
|
+
private scheduleConversationForAutoDeletion;
|
|
242
237
|
}
|
|
243
238
|
//#endregion
|
|
244
|
-
//#region src/server/
|
|
239
|
+
//#region src/server/agent-chat/ChatAgentHarness.d.ts
|
|
245
240
|
declare abstract class ChatAgentHarness<Env extends Cloudflare.Env, RequestBody extends Record<string, unknown> = Record<string, unknown>> extends ChatAgent<Env> {
|
|
246
241
|
get binding(): {
|
|
247
242
|
getByName(name: string): {
|
|
@@ -276,7 +271,4 @@ declare abstract class ChatAgentHarness<Env extends Cloudflare.Env, RequestBody
|
|
|
276
271
|
onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>, options?: OnChatMessageOptions): Promise<Response>;
|
|
277
272
|
}
|
|
278
273
|
//#endregion
|
|
279
|
-
//#region src/server/route-agent-request.d.ts
|
|
280
|
-
declare function routeAgentRequest<Env>(request: Request, env: Env, options?: AgentOptions<Env>): Promise<Response | null>;
|
|
281
|
-
//#endregion
|
|
282
274
|
export { Agent, type AgentToolContext, type BuildLLMParamsConfig, ChatAgent, ChatAgentHarness, type Skill, buildLLMParams, routeAgentRequest };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
|
|
1
2
|
import { Agent as Agent$1, callable, routeAgentRequest as routeAgentRequest$1 } from "agents";
|
|
2
3
|
import { AIChatAgent } from "@cloudflare/ai-chat";
|
|
3
|
-
import { Output, convertToModelMessages, generateText, jsonSchema, stepCountIs, streamText, tool } from "ai";
|
|
4
4
|
import { createRemoteJWKSet, decodeJwt, errors, jwtVerify } from "jose";
|
|
5
|
-
//#region src/server/features/skills/index.ts
|
|
5
|
+
//#region src/server/shared/features/skills/index.ts
|
|
6
6
|
const TOOL_NAME_ACTIVATE_SKILL = "activate_skill";
|
|
7
7
|
const TOOL_NAME_LIST_CAPABILITIES = "list_capabilities";
|
|
8
8
|
const SKILL_STATE_SENTINEL = "\n__SKILLS_STATE__:";
|
|
@@ -115,31 +115,10 @@ function createSkills(config) {
|
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
117
|
}
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
* skill state is persisted to DO SQLite by the CF layer, so these messages
|
|
123
|
-
* are not needed for future turns.
|
|
124
|
-
*
|
|
125
|
-
* If stripping leaves an assistant message with no parts, the entire message
|
|
126
|
-
* is dropped.
|
|
127
|
-
*/
|
|
128
|
-
function filterEphemeralMessages(messages) {
|
|
129
|
-
return messages.flatMap((msg) => {
|
|
130
|
-
if (msg.role !== "assistant" || !msg.parts?.length) return [msg];
|
|
131
|
-
const filtered = msg.parts.filter((part) => {
|
|
132
|
-
if (!("toolCallId" in part)) return true;
|
|
133
|
-
const { type } = part;
|
|
134
|
-
return type !== `tool-list_capabilities` && type !== `tool-activate_skill`;
|
|
135
|
-
});
|
|
136
|
-
if (filtered.length === 0) return [];
|
|
137
|
-
if (filtered.length === msg.parts.length) return [msg];
|
|
138
|
-
return [{
|
|
139
|
-
...msg,
|
|
140
|
-
parts: filtered
|
|
141
|
-
}];
|
|
142
|
-
});
|
|
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`;
|
|
143
122
|
}
|
|
144
123
|
/** Creates the `skill_state` table in DO SQLite if it does not exist yet. */
|
|
145
124
|
function ensureSkillTable(sql) {
|
|
@@ -182,7 +161,7 @@ function saveSkillStateFromMessages(sql, messages) {
|
|
|
182
161
|
sql`INSERT OR REPLACE INTO skill_state(id, active_skills) VALUES(1, ${JSON.stringify(latestSkillState)})`;
|
|
183
162
|
}
|
|
184
163
|
//#endregion
|
|
185
|
-
//#region src/server/llm.ts
|
|
164
|
+
//#region src/server/shared/util/llm.ts
|
|
186
165
|
function buildSystemPromptWithSkills(basePrompt, availableSkillList, loadedGuidance) {
|
|
187
166
|
let prompt = `${basePrompt}
|
|
188
167
|
|
|
@@ -277,7 +256,20 @@ function buildLLMParams(config) {
|
|
|
277
256
|
};
|
|
278
257
|
}
|
|
279
258
|
//#endregion
|
|
280
|
-
//#region src/server/
|
|
259
|
+
//#region src/server/route-agent-request.ts
|
|
260
|
+
async function routeAgentRequest(request, env, options) {
|
|
261
|
+
const response = await routeAgentRequest$1(request, env, options);
|
|
262
|
+
if (!response) return null;
|
|
263
|
+
const protocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
264
|
+
if (response.status === 101 && protocol) {
|
|
265
|
+
const newResponse = new Response(null, response);
|
|
266
|
+
newResponse.headers.set("Sec-WebSocket-Protocol", protocol.split(",")[0].trim());
|
|
267
|
+
return newResponse;
|
|
268
|
+
}
|
|
269
|
+
return response;
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region src/server/shared/features/audit/audit.ts
|
|
281
273
|
/**
|
|
282
274
|
* Inserts a single audit event row into the shared `audit_events` D1 table.
|
|
283
275
|
*
|
|
@@ -369,7 +361,7 @@ function buildTurnLogPayload(event) {
|
|
|
369
361
|
};
|
|
370
362
|
}
|
|
371
363
|
//#endregion
|
|
372
|
-
//#region src/server/features/auth/index.ts
|
|
364
|
+
//#region src/server/shared/features/auth/index.ts
|
|
373
365
|
const jwksByIssuer = /* @__PURE__ */ new Map();
|
|
374
366
|
function getJwksForIssuer(issuer) {
|
|
375
367
|
const normalized = issuer.replace(/\/$/, "");
|
|
@@ -469,7 +461,7 @@ async function verifyJwt(request, config) {
|
|
|
469
461
|
};
|
|
470
462
|
}
|
|
471
463
|
//#endregion
|
|
472
|
-
//#region src/server/
|
|
464
|
+
//#region src/server/agent/Agent.ts
|
|
473
465
|
/**
|
|
474
466
|
* Base agent for Cloudflare Agents SDK Durable Objects with lazy skill loading,
|
|
475
467
|
* audit logging, and `buildLLMParams` wiring.
|
|
@@ -668,7 +660,16 @@ async function compactIfNeeded(messages, model, tailSize) {
|
|
|
668
660
|
return compactMessages(messages, model, tailSize);
|
|
669
661
|
}
|
|
670
662
|
//#endregion
|
|
671
|
-
//#region src/server/features/conversations/conversations.ts
|
|
663
|
+
//#region src/server/agent-chat/features/conversations/conversations.ts
|
|
664
|
+
async function recordConversationSummary(db, durableObjectName, messages, model) {
|
|
665
|
+
if (!await getConversationSummary(db, durableObjectName)) {
|
|
666
|
+
const { title, summary } = await generateTitleAndSummary(messages, model);
|
|
667
|
+
await upsertConversationSummary(db, durableObjectName, title, summary);
|
|
668
|
+
} else {
|
|
669
|
+
await upsertConversationSummary(db, durableObjectName);
|
|
670
|
+
if (messages.length % SUMMARY_CONTEXT_MESSAGES === 0) await generateConversationSummary(db, durableObjectName, messages, model);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
672
673
|
/**
|
|
673
674
|
* Records a conversation row in the `conversations` D1 table.
|
|
674
675
|
*
|
|
@@ -679,7 +680,7 @@ async function compactIfNeeded(messages, model, tailSize) {
|
|
|
679
680
|
* `created_at`, `title`, and `summary` are never overwritten, preserving
|
|
680
681
|
* any user edits.
|
|
681
682
|
*/
|
|
682
|
-
async function
|
|
683
|
+
async function upsertConversationSummary(db, durableObjectName, title, summary) {
|
|
683
684
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
684
685
|
await db.prepare(`INSERT INTO conversations (durable_object_name, title, summary, created_at, updated_at)
|
|
685
686
|
VALUES (?, ?, ?, ?, ?)
|
|
@@ -687,6 +688,12 @@ async function recordConversation(db, durableObjectName, title, summary) {
|
|
|
687
688
|
updated_at = excluded.updated_at`).bind(durableObjectName, title ?? null, summary ?? null, now, now).run();
|
|
688
689
|
}
|
|
689
690
|
/**
|
|
691
|
+
* Number of recent messages passed to `generateSummary` for rolling
|
|
692
|
+
* summarization. Keeping this bounded prevents the prompt growing
|
|
693
|
+
* unboundedly regardless of conversation length.
|
|
694
|
+
*/
|
|
695
|
+
const SUMMARY_CONTEXT_MESSAGES = 30;
|
|
696
|
+
/**
|
|
690
697
|
* Returns the current `title` and `summary` for a conversation row,
|
|
691
698
|
* or `null` if the row does not exist yet.
|
|
692
699
|
*/
|
|
@@ -724,7 +731,7 @@ async function updateConversationSummary(db, durableObjectName, title, summary)
|
|
|
724
731
|
* prompt bounded regardless of total conversation length.
|
|
725
732
|
*/
|
|
726
733
|
async function generateTitleAndSummary(messages, model, existingSummary) {
|
|
727
|
-
const recentMessages = await convertToModelMessages(messages.slice(-
|
|
734
|
+
const recentMessages = await convertToModelMessages(messages.slice(-SUMMARY_CONTEXT_MESSAGES));
|
|
728
735
|
const previousContext = existingSummary ? `Previous summary: ${existingSummary}\n\nMost recent messages:` : "Conversation:";
|
|
729
736
|
const { output } = await generateText({
|
|
730
737
|
model,
|
|
@@ -766,7 +773,7 @@ async function generateConversationSummary(db, durableObjectName, messages, mode
|
|
|
766
773
|
};
|
|
767
774
|
}
|
|
768
775
|
//#endregion
|
|
769
|
-
//#region src/server/features/conversations/retention.ts
|
|
776
|
+
//#region src/server/agent-chat/features/conversations/retention.ts
|
|
770
777
|
const DELETE_CONVERSATION_CALLBACK = "deleteConversationCallback";
|
|
771
778
|
const CONVERSATION_EXPIRED_CLOSE_CODE = 3001;
|
|
772
779
|
const CONVERSATION_EXPIRED_CLOSE_REASON = "Conversation expired due to inactivity.";
|
|
@@ -778,7 +785,34 @@ function getDeleteConversationScheduleIds(schedules) {
|
|
|
778
785
|
return schedules.filter((schedule) => schedule.callback === DELETE_CONVERSATION_CALLBACK).map((schedule) => schedule.id);
|
|
779
786
|
}
|
|
780
787
|
//#endregion
|
|
781
|
-
//#region src/server/
|
|
788
|
+
//#region src/server/shared/util/messages.ts
|
|
789
|
+
function filterEphemeralMessages(messages) {
|
|
790
|
+
return messages.flatMap((message) => {
|
|
791
|
+
if (message.role !== "assistant" || !message.parts?.length) return [message];
|
|
792
|
+
const filteredParts = message.parts.filter((part) => !isEphemeralSkillToolPart(part));
|
|
793
|
+
if (filteredParts.length === 0) return [];
|
|
794
|
+
return [{
|
|
795
|
+
...message,
|
|
796
|
+
parts: filteredParts
|
|
797
|
+
}];
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
//#endregion
|
|
801
|
+
//#region src/server/agent-chat/features/conversations/rating.ts
|
|
802
|
+
async function rateMessage(db, durableObjectName, messageId, rating) {
|
|
803
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
804
|
+
await db.prepare(`INSERT INTO message_ratings (message_id, durable_object_name, rating, created_at, updated_at)
|
|
805
|
+
VALUES (?, ?, ?, ?, ?)
|
|
806
|
+
ON CONFLICT (message_id, durable_object_name) DO UPDATE SET
|
|
807
|
+
rating = excluded.rating,
|
|
808
|
+
updated_at = excluded.updated_at`).bind(messageId, durableObjectName, rating, now, now).run();
|
|
809
|
+
}
|
|
810
|
+
async function getMessageRatings(db, durableObjectName) {
|
|
811
|
+
const result = await db.prepare(`SELECT message_id, rating FROM message_ratings WHERE durable_object_name = ?`).bind(durableObjectName).all();
|
|
812
|
+
return Object.fromEntries(result.results.map((row) => [row.message_id, row.rating]));
|
|
813
|
+
}
|
|
814
|
+
//#endregion
|
|
815
|
+
//#region src/server/agent-chat/ChatAgent.ts
|
|
782
816
|
/**
|
|
783
817
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|
|
784
818
|
* message persistence, compaction, and conversation metadata in D1.
|
|
@@ -886,7 +920,6 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
886
920
|
const processedMessages = fastModel && this.maxMessagesBeforeCompaction !== void 0 ? await compactIfNeeded(messages, fastModel, this.maxMessagesBeforeCompaction) : messages;
|
|
887
921
|
const onFinish = async (event) => {
|
|
888
922
|
this.logEvent("Turn completed", buildTurnLogPayload(event));
|
|
889
|
-
this.recordConversation(filterEphemeralMessages([...this.messages]));
|
|
890
923
|
await config.onFinish?.(event);
|
|
891
924
|
};
|
|
892
925
|
return buildLLMParams({
|
|
@@ -901,7 +934,21 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
901
934
|
const filtered = filterEphemeralMessages(messages);
|
|
902
935
|
await super.persistMessages(filtered, excludeBroadcastIds, options);
|
|
903
936
|
saveSkillStateFromMessages(this.sql.bind(this), messages);
|
|
904
|
-
|
|
937
|
+
await super.persistMessages(filterEphemeralMessages(messages), excludeBroadcastIds, options);
|
|
938
|
+
}
|
|
939
|
+
async onChatResponse(result) {
|
|
940
|
+
if (result.error) {
|
|
941
|
+
console.error("[ChatAgent] Chat response error", result.error);
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
recordConversationSummary(this.env.AGENT_DB, this.name, this.messages, this.getFastModel());
|
|
945
|
+
this.scheduleConversationForAutoDeletion();
|
|
946
|
+
}
|
|
947
|
+
@callable({ description: "Rate a message by its id" }) async rateMessage(messageId, rating) {
|
|
948
|
+
await rateMessage(this.env.AGENT_DB, this.name, messageId, rating);
|
|
949
|
+
}
|
|
950
|
+
@callable({ description: "Returns all message ratings for the current conversation" }) async getMessageRatings() {
|
|
951
|
+
return getMessageRatings(this.env.AGENT_DB, this.name);
|
|
905
952
|
}
|
|
906
953
|
@callable({ description: "Returns all conversations for the current user" }) async getConversations() {
|
|
907
954
|
return getConversations(this.env.AGENT_DB, this.getUserId());
|
|
@@ -938,43 +985,13 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
938
985
|
}
|
|
939
986
|
return super.destroy();
|
|
940
987
|
}
|
|
941
|
-
/**
|
|
942
|
-
* Records this conversation in the `conversations` D1 table and triggers
|
|
943
|
-
* LLM-based title/summary generation when appropriate. Called automatically
|
|
944
|
-
* from `onFinish` in `buildLLMParams` after each completed LLM turn.
|
|
945
|
-
*
|
|
946
|
-
* On the first turn (no existing row), awaits `generateTitleAndSummary` and
|
|
947
|
-
* inserts the row with title and summary already populated. On subsequent
|
|
948
|
-
* turns, upserts the timestamp and fire-and-forgets a summary refresh every
|
|
949
|
-
* `SUMMARY_CONTEXT_MESSAGES` messages (when the context window fully turns
|
|
950
|
-
* over). Neither path blocks the response to the client.
|
|
951
|
-
*/
|
|
952
|
-
async recordConversation(messages) {
|
|
953
|
-
if (!await getConversationSummary(this.env.AGENT_DB, this.name)) {
|
|
954
|
-
const { title, summary } = await generateTitleAndSummary(messages, this.getFastModel());
|
|
955
|
-
await recordConversation(this.env.AGENT_DB, this.name, title, summary);
|
|
956
|
-
this.logEvent("Conversation summary generated", {
|
|
957
|
-
title,
|
|
958
|
-
summary
|
|
959
|
-
});
|
|
960
|
-
} else {
|
|
961
|
-
await recordConversation(this.env.AGENT_DB, this.name);
|
|
962
|
-
if (messages.length % 30 === 0) {
|
|
963
|
-
const { title, summary } = await generateConversationSummary(this.env.AGENT_DB, this.name, messages, this.getFastModel());
|
|
964
|
-
this.logEvent("Conversation summary updated", {
|
|
965
|
-
title,
|
|
966
|
-
summary
|
|
967
|
-
});
|
|
968
|
-
}
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
988
|
async deleteConversationCallback() {
|
|
972
989
|
if (await this.deleteConversation(this.name)) this.logEvent("Conversation deleted due to inactivity", {
|
|
973
990
|
conversationName: this.name,
|
|
974
991
|
retentionDays: this.conversationRetentionDays ?? null
|
|
975
992
|
});
|
|
976
993
|
}
|
|
977
|
-
async
|
|
994
|
+
async scheduleConversationForAutoDeletion() {
|
|
978
995
|
const retentionMs = getConversationRetentionMs(this.conversationRetentionDays);
|
|
979
996
|
if (retentionMs === null) return;
|
|
980
997
|
const scheduleIds = getDeleteConversationScheduleIds(this.getSchedules());
|
|
@@ -983,7 +1000,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
983
1000
|
}
|
|
984
1001
|
};
|
|
985
1002
|
//#endregion
|
|
986
|
-
//#region src/server/
|
|
1003
|
+
//#region src/server/agent-chat/ChatAgentHarness.ts
|
|
987
1004
|
var ChatAgentHarness = class extends ChatAgent {
|
|
988
1005
|
get binding() {
|
|
989
1006
|
const className = this.constructor.name;
|
|
@@ -1019,17 +1036,4 @@ var ChatAgentHarness = class extends ChatAgent {
|
|
|
1019
1036
|
}
|
|
1020
1037
|
};
|
|
1021
1038
|
//#endregion
|
|
1022
|
-
//#region src/server/route-agent-request.ts
|
|
1023
|
-
async function routeAgentRequest(request, env, options) {
|
|
1024
|
-
const response = await routeAgentRequest$1(request, env, options);
|
|
1025
|
-
if (!response) return null;
|
|
1026
|
-
const protocol = request.headers.get("Sec-WebSocket-Protocol");
|
|
1027
|
-
if (response.status === 101 && protocol) {
|
|
1028
|
-
const newResponse = new Response(null, response);
|
|
1029
|
-
newResponse.headers.set("Sec-WebSocket-Protocol", protocol.split(",")[0].trim());
|
|
1030
|
-
return newResponse;
|
|
1031
|
-
}
|
|
1032
|
-
return response;
|
|
1033
|
-
}
|
|
1034
|
-
//#endregion
|
|
1035
1039
|
export { Agent, ChatAgent, ChatAgentHarness, buildLLMParams, routeAgentRequest };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@economic/agents",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"bin": {
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"@cloudflare/workers-types": "^4.20260412.1",
|
|
34
34
|
"@types/node": "^25.6.0",
|
|
35
35
|
"@typescript/native-preview": "7.0.0-dev.20260412.1",
|
|
36
|
+
"agents": "^0.11.0",
|
|
36
37
|
"ai": "^6.0.158",
|
|
37
38
|
"jose": "^6.2.2",
|
|
38
39
|
"tsdown": "^0.21.7",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
},
|
|
42
43
|
"peerDependencies": {
|
|
43
44
|
"@cloudflare/ai-chat": ">=0.1.0 <1.0.0",
|
|
44
|
-
"agents": ">=0.
|
|
45
|
+
"agents": ">=0.11.0 <1.0.0",
|
|
45
46
|
"ai": "^6.0.0",
|
|
46
47
|
"jose": "^6.0.0"
|
|
47
48
|
},
|
package/schema/chat.sql
CHANGED
|
@@ -21,5 +21,15 @@ CREATE TABLE IF NOT EXISTS conversations (
|
|
|
21
21
|
updated_at TEXT NOT NULL
|
|
22
22
|
);
|
|
23
23
|
|
|
24
|
-
CREATE INDEX IF NOT EXISTS
|
|
25
|
-
|
|
24
|
+
CREATE INDEX IF NOT EXISTS conversations_ts ON conversations(updated_at);
|
|
25
|
+
|
|
26
|
+
-- ─── Message ratings ──────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
CREATE TABLE IF NOT EXISTS message_ratings (
|
|
29
|
+
message_id TEXT NOT NULL,
|
|
30
|
+
durable_object_name TEXT NOT NULL,
|
|
31
|
+
rating INTEGER,
|
|
32
|
+
created_at TEXT NOT NULL,
|
|
33
|
+
updated_at TEXT NOT NULL,
|
|
34
|
+
PRIMARY KEY (message_id, durable_object_name)
|
|
35
|
+
);
|