@economic/agents 1.4.1 → 1.6.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
|
@@ -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,13 @@ 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/features/conversations/rating.d.ts
|
|
138
|
+
interface MessageRating {
|
|
139
|
+
rating: number;
|
|
140
|
+
comment?: string;
|
|
141
|
+
}
|
|
142
|
+
//#endregion
|
|
143
|
+
//#region src/server/agent-chat/ChatAgent.d.ts
|
|
134
144
|
/**
|
|
135
145
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|
|
136
146
|
* message persistence, compaction, and conversation metadata in D1.
|
|
@@ -142,7 +152,7 @@ declare abstract class Agent<Env extends Cloudflare.Env = Cloudflare.Env> extend
|
|
|
142
152
|
* Skill loading, compaction, and LLM calls use `buildLLMParams` from
|
|
143
153
|
* `@economic/agents` inside `onChatMessage`.
|
|
144
154
|
*/
|
|
145
|
-
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent<Env &
|
|
155
|
+
declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent<Env & ChatAgentEnv> {
|
|
146
156
|
/**
|
|
147
157
|
* The binding of the Durable Object instance for this agent.
|
|
148
158
|
*/
|
|
@@ -222,26 +232,17 @@ declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> ex
|
|
|
222
232
|
persistMessages(messages: UIMessage[], excludeBroadcastIds?: string[], options?: {
|
|
223
233
|
_deleteStaleRows?: boolean;
|
|
224
234
|
}): Promise<void>;
|
|
235
|
+
protected onChatResponse(result: ChatResponseResult): Promise<void>;
|
|
236
|
+
rateMessage(messageId: string, rating: number, comment?: string): Promise<void>;
|
|
237
|
+
getMessageRatings(): Promise<Record<string, MessageRating>>;
|
|
225
238
|
getConversations(): Promise<Record<string, unknown>[]>;
|
|
226
239
|
deleteConversation(id: string): Promise<boolean>;
|
|
227
240
|
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
241
|
private deleteConversationCallback;
|
|
241
|
-
private
|
|
242
|
+
private scheduleConversationForAutoDeletion;
|
|
242
243
|
}
|
|
243
244
|
//#endregion
|
|
244
|
-
//#region src/server/
|
|
245
|
+
//#region src/server/agent-chat/ChatAgentHarness.d.ts
|
|
245
246
|
declare abstract class ChatAgentHarness<Env extends Cloudflare.Env, RequestBody extends Record<string, unknown> = Record<string, unknown>> extends ChatAgent<Env> {
|
|
246
247
|
get binding(): {
|
|
247
248
|
getByName(name: string): {
|
|
@@ -276,7 +277,4 @@ declare abstract class ChatAgentHarness<Env extends Cloudflare.Env, RequestBody
|
|
|
276
277
|
onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>, options?: OnChatMessageOptions): Promise<Response>;
|
|
277
278
|
}
|
|
278
279
|
//#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
280
|
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,38 @@ 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, comment) {
|
|
803
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
804
|
+
await db.prepare(`INSERT INTO message_ratings (message_id, durable_object_name, rating, comment, created_at, updated_at)
|
|
805
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
806
|
+
ON CONFLICT (message_id, durable_object_name) DO UPDATE SET
|
|
807
|
+
rating = excluded.rating,
|
|
808
|
+
comment = excluded.comment,
|
|
809
|
+
updated_at = excluded.updated_at`).bind(messageId, durableObjectName, rating, comment ?? null, now, now).run();
|
|
810
|
+
}
|
|
811
|
+
async function getMessageRatings(db, durableObjectName) {
|
|
812
|
+
const result = await db.prepare(`SELECT message_id, rating, comment FROM message_ratings WHERE durable_object_name = ?`).bind(durableObjectName).all();
|
|
813
|
+
return Object.fromEntries(result.results.map((row) => [row.message_id, {
|
|
814
|
+
rating: row.rating,
|
|
815
|
+
...row.comment != null ? { comment: row.comment } : {}
|
|
816
|
+
}]));
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/server/agent-chat/ChatAgent.ts
|
|
782
820
|
/**
|
|
783
821
|
* Chat agent for Cloudflare Agents SDK: lazy skill loading, audit logging,
|
|
784
822
|
* message persistence, compaction, and conversation metadata in D1.
|
|
@@ -886,7 +924,6 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
886
924
|
const processedMessages = fastModel && this.maxMessagesBeforeCompaction !== void 0 ? await compactIfNeeded(messages, fastModel, this.maxMessagesBeforeCompaction) : messages;
|
|
887
925
|
const onFinish = async (event) => {
|
|
888
926
|
this.logEvent("Turn completed", buildTurnLogPayload(event));
|
|
889
|
-
this.recordConversation(filterEphemeralMessages([...this.messages]));
|
|
890
927
|
await config.onFinish?.(event);
|
|
891
928
|
};
|
|
892
929
|
return buildLLMParams({
|
|
@@ -901,7 +938,21 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
901
938
|
const filtered = filterEphemeralMessages(messages);
|
|
902
939
|
await super.persistMessages(filtered, excludeBroadcastIds, options);
|
|
903
940
|
saveSkillStateFromMessages(this.sql.bind(this), messages);
|
|
904
|
-
|
|
941
|
+
await super.persistMessages(filterEphemeralMessages(messages), excludeBroadcastIds, options);
|
|
942
|
+
}
|
|
943
|
+
async onChatResponse(result) {
|
|
944
|
+
if (result.error) {
|
|
945
|
+
console.error("[ChatAgent] Chat response error", result.error);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
recordConversationSummary(this.env.AGENT_DB, this.name, this.messages, this.getFastModel());
|
|
949
|
+
this.scheduleConversationForAutoDeletion();
|
|
950
|
+
}
|
|
951
|
+
@callable({ description: "Rate a message by its id" }) async rateMessage(messageId, rating, comment) {
|
|
952
|
+
await rateMessage(this.env.AGENT_DB, this.name, messageId, rating, comment);
|
|
953
|
+
}
|
|
954
|
+
@callable({ description: "Returns all message ratings for the current conversation" }) async getMessageRatings() {
|
|
955
|
+
return getMessageRatings(this.env.AGENT_DB, this.name);
|
|
905
956
|
}
|
|
906
957
|
@callable({ description: "Returns all conversations for the current user" }) async getConversations() {
|
|
907
958
|
return getConversations(this.env.AGENT_DB, this.getUserId());
|
|
@@ -938,43 +989,13 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
938
989
|
}
|
|
939
990
|
return super.destroy();
|
|
940
991
|
}
|
|
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
992
|
async deleteConversationCallback() {
|
|
972
993
|
if (await this.deleteConversation(this.name)) this.logEvent("Conversation deleted due to inactivity", {
|
|
973
994
|
conversationName: this.name,
|
|
974
995
|
retentionDays: this.conversationRetentionDays ?? null
|
|
975
996
|
});
|
|
976
997
|
}
|
|
977
|
-
async
|
|
998
|
+
async scheduleConversationForAutoDeletion() {
|
|
978
999
|
const retentionMs = getConversationRetentionMs(this.conversationRetentionDays);
|
|
979
1000
|
if (retentionMs === null) return;
|
|
980
1001
|
const scheduleIds = getDeleteConversationScheduleIds(this.getSchedules());
|
|
@@ -983,7 +1004,7 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
983
1004
|
}
|
|
984
1005
|
};
|
|
985
1006
|
//#endregion
|
|
986
|
-
//#region src/server/
|
|
1007
|
+
//#region src/server/agent-chat/ChatAgentHarness.ts
|
|
987
1008
|
var ChatAgentHarness = class extends ChatAgent {
|
|
988
1009
|
get binding() {
|
|
989
1010
|
const className = this.constructor.name;
|
|
@@ -1019,17 +1040,4 @@ var ChatAgentHarness = class extends ChatAgent {
|
|
|
1019
1040
|
}
|
|
1020
1041
|
};
|
|
1021
1042
|
//#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
1043
|
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.6.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
|
},
|
|
@@ -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
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE message_ratings ADD COLUMN comment TEXT;
|
|
File without changes
|