@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 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/types.d.ts
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/features/auth/index.d.ts
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/agents/Agent.d.ts
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/agents/ChatAgent.d.ts
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 & AgentEnv> {
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 scheduleConversationForDeletion;
236
+ private scheduleConversationForAutoDeletion;
242
237
  }
243
238
  //#endregion
244
- //#region src/server/harnesses/ChatAgentHarness.d.ts
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
- * Removes ephemeral skill-related messages from a conversation.
120
- *
121
- * Strips both `activate_skill` and `list_capabilities` tool calls entirely
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/features/audit/audit.ts
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/agents/Agent.ts
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 recordConversation(db, durableObjectName, title, summary) {
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(-30));
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/agents/ChatAgent.ts
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
- this.scheduleConversationForDeletion();
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 scheduleConversationForDeletion() {
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/harnesses/ChatAgentHarness.ts
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.4.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.10.2 <1.0.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 conversations_do ON conversations(durable_object_name);
25
- CREATE INDEX IF NOT EXISTS conversations_ts ON conversations(updated_at);
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
+ );