@economic/agents 2.6.1 → 2.7.1
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 +15 -7
- package/dist/index.d.mts +3 -83
- package/dist/index.mjs +43 -190
- package/dist/providers.d.mts +12 -4
- package/dist/providers.mjs +13 -8
- package/dist/v1.d.mts +0 -11
- package/dist/v1.mjs +0 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Our agents SDK for building AI agents on Cloudflare Workers. Each agent is a Dur
|
|
|
4
4
|
|
|
5
5
|
React client: [`@economic/agents-react`](../react/README.md).
|
|
6
6
|
|
|
7
|
-
> The v1 (`@economic/agents/v1`) API (`ChatAgentHarness`, `buildLLMParams`) is deprecated
|
|
7
|
+
> The v1 (`@economic/agents/v1`) API (`ChatAgentHarness`, `buildLLMParams`) is deprecated and will be removed in v3.
|
|
8
8
|
|
|
9
9
|
## The three classes
|
|
10
10
|
|
|
@@ -83,9 +83,15 @@ const { anthropic, gemini } = createAiGatewayVertexProviders({
|
|
|
83
83
|
cloudflareAiGatewayId: env.AI_GATEWAY_ID,
|
|
84
84
|
cloudflareApiToken: env.CLOUDFLARE_API_TOKEN,
|
|
85
85
|
googleCloudProjectId: env.GOOGLE_CLOUD_PROJECT_ID,
|
|
86
|
+
// Included in cf-aig-metadata on every request, including background calls.
|
|
87
|
+
metadata: { agentId: "support" },
|
|
86
88
|
});
|
|
87
89
|
```
|
|
88
90
|
|
|
91
|
+
Set a stable `agentId` when multiple agents may use the same gateway. AI Gateway
|
|
92
|
+
stores this custom metadata with each request, allowing cost queries to filter
|
|
93
|
+
by agent without copying cost data into another store.
|
|
94
|
+
|
|
89
95
|
## Quick start: an agent
|
|
90
96
|
|
|
91
97
|
Subclass `Agent` and implement `getModel` and `getSystemPrompt`. Add tools with `getTools`, skills with `getSkills`. Expose a `@callable` method to run a turn — `saveMessages` injects a message, runs the turn, and persists the result:
|
|
@@ -252,15 +258,17 @@ const { status, chats, assistant, chat } = useAssistant({
|
|
|
252
258
|
});
|
|
253
259
|
```
|
|
254
260
|
|
|
255
|
-
The `Assistant` keeps the chat list in its own SQLite (a `chats` table, created automatically) and exposes
|
|
261
|
+
The `Assistant` keeps the chat list in its own SQLite (a `chats` table, created automatically) and exposes four callable methods. A newly created chat facet is added to this table when its first turn starts.
|
|
256
262
|
|
|
257
|
-
| Method | Returns | Description
|
|
258
|
-
| ---------------- | -------- |
|
|
259
|
-
| `createChat()` | `string` | Creates a chat and returns its id.
|
|
260
|
-
| `getChats()` | `Chat[]` |
|
|
261
|
-
| `deleteChat(id)` | `void` | Deletes a chat and its record.
|
|
263
|
+
| Method | Returns | Description |
|
|
264
|
+
| ---------------- | -------- | ------------------------------------------- |
|
|
265
|
+
| `createChat()` | `string` | Creates a chat facet and returns its id. |
|
|
266
|
+
| `getChats()` | `Chat[]` | Started chats, most recently updated first. |
|
|
267
|
+
| `deleteChat(id)` | `void` | Deletes a chat facet and its record. |
|
|
268
|
+
| `renameChat(id)` | `void` | Renames a registered chat. |
|
|
262
269
|
|
|
263
270
|
- **Titles and summaries** — generated with `fastModel` after each turn (on the first turn, then refreshed as the chat grows).
|
|
271
|
+
- **Empty chat cleanup** — a newly created facet is scheduled for deletion after one day. Its first completed turn replaces that schedule with the normal sliding retention period.
|
|
264
272
|
- **Retention** — inactive chats are deleted after 90 days, via a Durable Object alarm.
|
|
265
273
|
|
|
266
274
|
## Bindings
|
package/dist/index.d.mts
CHANGED
|
@@ -22,7 +22,6 @@ interface AgentEnv {
|
|
|
22
22
|
SKILLS_BUCKET: R2Bucket;
|
|
23
23
|
LOCAL_AGENT_DB?: D1Database;
|
|
24
24
|
}
|
|
25
|
-
type SqlFn = InstanceType<typeof Agent$1>["sql"];
|
|
26
25
|
type AgentConnectionStatus = "connecting" | "connected" | "disconnected" | "unauthorized" | "error";
|
|
27
26
|
type AgentConnectionType = "agent" | "chat" | "assistant";
|
|
28
27
|
//#endregion
|
|
@@ -287,59 +286,12 @@ type MessageFeedback = {
|
|
|
287
286
|
updated_at: number;
|
|
288
287
|
};
|
|
289
288
|
//#endregion
|
|
290
|
-
//#region src/server/features/migration.d.ts
|
|
291
|
-
/** Per-message feedback carried over from a v1 conversation during migration. */
|
|
292
|
-
type LegacyMessageFeedback = {
|
|
293
|
-
messageId: string;
|
|
294
|
-
rating: number;
|
|
295
|
-
comment?: string;
|
|
296
|
-
};
|
|
297
|
-
/** Minimal RPC surface of a v1 chat DO needed to read its history. */
|
|
298
|
-
interface LegacyChatStub {
|
|
299
|
-
exportForMigration(): Promise<{
|
|
300
|
-
messages: UIMessage[];
|
|
301
|
-
}>;
|
|
302
|
-
}
|
|
303
|
-
/** Minimal RPC surface of a freshly created v2 chat facet needed to seed history. */
|
|
304
|
-
interface FacetStub {
|
|
305
|
-
importLegacyMessages(messages: UIMessage[], feedback: LegacyMessageFeedback[]): Promise<void>;
|
|
306
|
-
}
|
|
307
|
-
/**
|
|
308
|
-
* Dependencies for {@link migrateUserFromV1}, injected by the `Assistant` so
|
|
309
|
-
* this module never needs to touch the DO's protected members directly.
|
|
310
|
-
*/
|
|
311
|
-
type MigrationDeps = {
|
|
312
|
-
/** The `Assistant` DO's bound `sql` tag (used to register migrated chats). */sql: SqlFn; /** Shared D1 database holding v1 `conversations` and `message_ratings`. */
|
|
313
|
-
db: D1Database; /** The user being migrated (the `Assistant` DO name). */
|
|
314
|
-
userId: string; /** Resolves a v1 chat DO stub by its `userId:chatId` name. */
|
|
315
|
-
durableObjectBinding: DurableObjectNamespace; /** Creates (or resolves) a v2 chat facet for the given chat id and returns its stub. */
|
|
316
|
-
createFacet: (chatId: string) => Promise<FacetStub>;
|
|
317
|
-
};
|
|
318
|
-
/**
|
|
319
|
-
* Migrates all of a user's v1 chats into v2 facets, lazily and idempotently.
|
|
320
|
-
*
|
|
321
|
-
* v1 chats are enumerated from the shared D1 `conversations` table (DOs cannot
|
|
322
|
-
* be listed directly). For each chat we read its persisted messages from the v1
|
|
323
|
-
* DO over RPC, create a fresh facet, seed its history and feedback, register it
|
|
324
|
-
* on the parent `Assistant` with its original title/summary/timestamps, and
|
|
325
|
-
* finally delete the v1 `conversations` (+ `message_ratings`) rows.
|
|
326
|
-
*
|
|
327
|
-
* The conversations row IS the to-do marker: deleting it last means a migrated
|
|
328
|
-
* chat never reappears, while a chat that errored keeps its row and is simply
|
|
329
|
-
* retried on the next connection. No extra bookkeeping tables are needed. The
|
|
330
|
-
* v1 DO is left untouched and self-expires via v1 retention, so its messages
|
|
331
|
-
* remain as a backstop until then.
|
|
332
|
-
*/
|
|
333
|
-
declare function migrateUserFromV1(deps: MigrationDeps): Promise<{
|
|
334
|
-
migrated: number;
|
|
335
|
-
failed: number;
|
|
336
|
-
}>;
|
|
337
|
-
//#endregion
|
|
338
289
|
//#region src/server/agents/ChatAgent.d.ts
|
|
339
290
|
declare abstract class ChatAgent<RequestContext extends Record<string, unknown> = Record<string, unknown>, UserContext extends Record<string, unknown> = Record<string, unknown>, State = unknown> extends Agent<RequestContext, UserContext, State> {
|
|
340
291
|
protected agentType: AgentConnectionType;
|
|
341
292
|
onStart(): Promise<void>;
|
|
342
293
|
configureSession(session: Session): Session;
|
|
294
|
+
beforeTurn(ctx: TurnContext): Promise<void | TurnConfig>;
|
|
343
295
|
onChatResponse(_result: ChatResponseResult): Promise<void>;
|
|
344
296
|
/**
|
|
345
297
|
* Submit feedback for a message by its id.
|
|
@@ -353,19 +305,6 @@ declare abstract class ChatAgent<RequestContext extends Record<string, unknown>
|
|
|
353
305
|
* @returns All message feedback for the current chat.
|
|
354
306
|
*/
|
|
355
307
|
getMessageFeedback(): Promise<Record<string, MessageFeedback>>;
|
|
356
|
-
/**
|
|
357
|
-
* Imports a v1 conversation's history into this facet's session storage.
|
|
358
|
-
*
|
|
359
|
-
* Called over DO RPC by the v2 `Assistant` during the lazy v1 -> v2
|
|
360
|
-
* migration. Messages are appended in order as a single linear thread
|
|
361
|
-
* (each message parented to the previous one) using
|
|
362
|
-
* `appendMessageToHistory`, which writes durably to the session WITHOUT
|
|
363
|
-
* triggering a model turn. Any carried-over feedback is then written to
|
|
364
|
-
* `assistant_messages_feedback`.
|
|
365
|
-
*
|
|
366
|
-
* Safe to skip persisting if there is nothing to import.
|
|
367
|
-
*/
|
|
368
|
-
importLegacyMessages(messages: UIMessage[], feedback?: LegacyMessageFeedback[]): Promise<void>;
|
|
369
308
|
}
|
|
370
309
|
//#endregion
|
|
371
310
|
//#region src/server/features/chats.d.ts
|
|
@@ -393,6 +332,7 @@ declare abstract class Assistant<State = unknown> extends Agent$1<Cloudflare.Env
|
|
|
393
332
|
renameChat(id: string, title: string): Promise<void>;
|
|
394
333
|
getChats(): Promise<Chat[]>;
|
|
395
334
|
getChatAgentClassName(): Promise<string>;
|
|
335
|
+
registerChat(durableObjectName: string): Promise<void>;
|
|
396
336
|
recordChatTurn(durableObjectName: string, messages: UIMessage[]): Promise<void>;
|
|
397
337
|
recordChatFeedback(durableObjectName: string, messageId: string, rating: number): Promise<void>;
|
|
398
338
|
private [DELETE_CHAT_CALLBACK];
|
|
@@ -413,26 +353,6 @@ declare abstract class Assistant<State = unknown> extends Agent$1<Cloudflare.Env
|
|
|
413
353
|
* Best-effort and non-blocking.
|
|
414
354
|
*/
|
|
415
355
|
private touchInstance;
|
|
416
|
-
/**
|
|
417
|
-
* Binding of the legacy v1 chat Durable Object class, used to migrate a
|
|
418
|
-
* user's v1 chats into facets the first time they connect. Set this on the
|
|
419
|
-
* concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
|
|
420
|
-
* disable it (e.g. for greenfield deployments with no v1 data).
|
|
421
|
-
*/
|
|
422
|
-
protected getMigrationBinding(_env: Cloudflare.Env & AgentEnv): {
|
|
423
|
-
binding: DurableObjectNamespace;
|
|
424
|
-
db: D1Database;
|
|
425
|
-
} | undefined;
|
|
426
|
-
/** In-flight migration, shared across concurrent connections to this DO. */
|
|
427
|
-
private _migrationPromise?;
|
|
428
|
-
/**
|
|
429
|
-
* Runs the lazy v1 -> v2 migration for this user. Concurrent connections to
|
|
430
|
-
* this DO share a single in-flight run. Idempotency across runs/restarts is
|
|
431
|
-
* handled by `migrateUserFromV1` deleting each chat's v1 `conversations` row,
|
|
432
|
-
* so an already-migrated chat is never re-enumerated.
|
|
433
|
-
*/
|
|
434
|
-
private ensureMigrated;
|
|
435
|
-
private runMigration;
|
|
436
356
|
}
|
|
437
357
|
//#endregion
|
|
438
358
|
//#region src/server/util/skills.d.ts
|
|
@@ -452,4 +372,4 @@ declare function skill<Context extends Record<string, unknown> = Record<string,
|
|
|
452
372
|
//#region src/server/index.d.ts
|
|
453
373
|
declare function getCurrentToolContext(): any;
|
|
454
374
|
//#endregion
|
|
455
|
-
export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type ExchangedToken, type
|
|
375
|
+
export { Agent, type AgentConnectionStatus, type AgentConnectionType, type AgentEnv, Assistant, ChatAgent, type ExchangedToken, type Skill, type SkillSource, type TokenExchangeConfig, type TokenExchangeResult, type Tool, type ToolContext, type ToolSet, type TurnSnapshot, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
|
package/dist/index.mjs
CHANGED
|
@@ -743,21 +743,7 @@ function ensureChatMessageFeedbackTableExists(sql) {
|
|
|
743
743
|
}
|
|
744
744
|
function registerChat(sql, durableObjectName, dateTime) {
|
|
745
745
|
sql`INSERT INTO chats (durable_object_name, created_at, updated_at)
|
|
746
|
-
VALUES (${durableObjectName}, ${dateTime}, ${dateTime})
|
|
747
|
-
}
|
|
748
|
-
/**
|
|
749
|
-
* Registers a chat while preserving metadata carried over from a v1
|
|
750
|
-
* conversation during migration: its title, summary, and original
|
|
751
|
-
* timestamps. Used so migrated chats keep their titles instead of waiting
|
|
752
|
-
* for the AI summariser to regenerate them.
|
|
753
|
-
*
|
|
754
|
-
* Idempotent: an existing row for the same chat id is left untouched.
|
|
755
|
-
*/
|
|
756
|
-
function registerChatWithMetadata(sql, durableObjectName, metadata) {
|
|
757
|
-
const { title, summary, createdAt } = metadata;
|
|
758
|
-
const updatedAt = metadata.updatedAt ?? createdAt;
|
|
759
|
-
sql`INSERT INTO chats (durable_object_name, title, summary, created_at, updated_at)
|
|
760
|
-
VALUES (${durableObjectName}, ${title ?? null}, ${summary ?? null}, ${createdAt}, ${updatedAt})
|
|
746
|
+
VALUES (${durableObjectName}, ${dateTime}, ${dateTime})
|
|
761
747
|
ON CONFLICT (durable_object_name) DO NOTHING`;
|
|
762
748
|
}
|
|
763
749
|
/**
|
|
@@ -800,8 +786,8 @@ function recordChatFeedback(sql, durableObjectName, messageId, rating, dateTime
|
|
|
800
786
|
negative_feedback_count = ${Number(negativeRows[0]?.count ?? 0)}
|
|
801
787
|
WHERE durable_object_name = ${durableObjectName}`;
|
|
802
788
|
}
|
|
803
|
-
function getDeleteChatScheduleIds(schedules) {
|
|
804
|
-
return schedules.filter((schedule) => schedule.callback ===
|
|
789
|
+
function getDeleteChatScheduleIds(schedules, durableObjectName) {
|
|
790
|
+
return schedules.filter((schedule) => schedule.callback === "deleteChatCallback" && schedule.payload === durableObjectName).map((schedule) => schedule.id);
|
|
805
791
|
}
|
|
806
792
|
/**
|
|
807
793
|
* Number of recent messages passed to `generateSummary` for rolling
|
|
@@ -862,92 +848,6 @@ function getChatRetentionMs(days) {
|
|
|
862
848
|
return Math.floor(days * 24 * 60 * 60 * 1e3);
|
|
863
849
|
}
|
|
864
850
|
//#endregion
|
|
865
|
-
//#region src/server/features/migration.ts
|
|
866
|
-
async function listLegacyConversations(db, userId) {
|
|
867
|
-
try {
|
|
868
|
-
const { results } = await db.prepare(`SELECT durable_object_name, title, summary, created_at, updated_at
|
|
869
|
-
FROM conversations WHERE durable_object_name LIKE ? ORDER BY updated_at ASC`).bind(`${userId}:%`).all();
|
|
870
|
-
return results ?? [];
|
|
871
|
-
} catch (error) {
|
|
872
|
-
if (error instanceof Error && error.message.includes(`no such table: conversations`)) {
|
|
873
|
-
console.warn("[Migration] Legacy conversations table missing; skipping v1 migration", { userId });
|
|
874
|
-
return [];
|
|
875
|
-
}
|
|
876
|
-
throw error;
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
async function listLegacyFeedback(db, durableObjectName) {
|
|
880
|
-
const { results } = await db.prepare(`SELECT message_id, rating, comment FROM message_ratings WHERE durable_object_name = ?`).bind(durableObjectName).all();
|
|
881
|
-
return (results ?? []).map((row) => ({
|
|
882
|
-
messageId: row.message_id,
|
|
883
|
-
rating: row.rating,
|
|
884
|
-
...row.comment != null ? { comment: row.comment } : {}
|
|
885
|
-
}));
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Removes a migrated v1 chat's D1 rows. Deleting the `conversations` row is what
|
|
889
|
-
* makes the migration idempotent: it drops out of the enumeration so it is never
|
|
890
|
-
* migrated again. The v1 DO itself is left to self-expire via v1 retention.
|
|
891
|
-
*/
|
|
892
|
-
async function deleteLegacyConversation(db, durableObjectName) {
|
|
893
|
-
await db.prepare(`DELETE FROM conversations WHERE durable_object_name = ?`).bind(durableObjectName).run();
|
|
894
|
-
await db.prepare(`DELETE FROM message_ratings WHERE durable_object_name = ?`).bind(durableObjectName).run();
|
|
895
|
-
}
|
|
896
|
-
function toEpochMs(value) {
|
|
897
|
-
if (!value) return Date.now();
|
|
898
|
-
const parsed = Date.parse(value);
|
|
899
|
-
return Number.isNaN(parsed) ? Date.now() : parsed;
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* Migrates all of a user's v1 chats into v2 facets, lazily and idempotently.
|
|
903
|
-
*
|
|
904
|
-
* v1 chats are enumerated from the shared D1 `conversations` table (DOs cannot
|
|
905
|
-
* be listed directly). For each chat we read its persisted messages from the v1
|
|
906
|
-
* DO over RPC, create a fresh facet, seed its history and feedback, register it
|
|
907
|
-
* on the parent `Assistant` with its original title/summary/timestamps, and
|
|
908
|
-
* finally delete the v1 `conversations` (+ `message_ratings`) rows.
|
|
909
|
-
*
|
|
910
|
-
* The conversations row IS the to-do marker: deleting it last means a migrated
|
|
911
|
-
* chat never reappears, while a chat that errored keeps its row and is simply
|
|
912
|
-
* retried on the next connection. No extra bookkeeping tables are needed. The
|
|
913
|
-
* v1 DO is left untouched and self-expires via v1 retention, so its messages
|
|
914
|
-
* remain as a backstop until then.
|
|
915
|
-
*/
|
|
916
|
-
async function migrateUserFromV1(deps) {
|
|
917
|
-
const { sql, db, userId, durableObjectBinding, createFacet } = deps;
|
|
918
|
-
const conversations = await listLegacyConversations(db, userId);
|
|
919
|
-
let migrated = 0;
|
|
920
|
-
let failed = 0;
|
|
921
|
-
for (const conversation of conversations) {
|
|
922
|
-
const legacyName = conversation.durable_object_name;
|
|
923
|
-
try {
|
|
924
|
-
const { messages } = await durableObjectBinding.getByName(legacyName).exportForMigration();
|
|
925
|
-
const feedback = await listLegacyFeedback(db, legacyName);
|
|
926
|
-
const newChatId = nanoid();
|
|
927
|
-
const facet = await createFacet(newChatId);
|
|
928
|
-
if (messages.length > 0) await facet.importLegacyMessages(messages, feedback);
|
|
929
|
-
registerChatWithMetadata(sql, newChatId, {
|
|
930
|
-
title: conversation.title ?? void 0,
|
|
931
|
-
summary: conversation.summary ?? void 0,
|
|
932
|
-
createdAt: toEpochMs(conversation.created_at),
|
|
933
|
-
updatedAt: toEpochMs(conversation.updated_at)
|
|
934
|
-
});
|
|
935
|
-
await deleteLegacyConversation(db, legacyName);
|
|
936
|
-
migrated++;
|
|
937
|
-
} catch (error) {
|
|
938
|
-
failed++;
|
|
939
|
-
console.error("[Migration] Failed to migrate v1 chat", {
|
|
940
|
-
legacyName,
|
|
941
|
-
error
|
|
942
|
-
});
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
return {
|
|
946
|
-
migrated,
|
|
947
|
-
failed
|
|
948
|
-
};
|
|
949
|
-
}
|
|
950
|
-
//#endregion
|
|
951
851
|
//#region src/server/agents/Assistant.ts
|
|
952
852
|
let _initProto$1;
|
|
953
853
|
function _applyDecs$1(e, t, n, r, o, i) {
|
|
@@ -1144,14 +1044,25 @@ var Assistant = class extends Agent$1 {
|
|
|
1144
1044
|
}
|
|
1145
1045
|
}
|
|
1146
1046
|
}
|
|
1147
|
-
await this.ensureMigrated();
|
|
1148
1047
|
sendConnectionStatus(connection, "connected");
|
|
1149
1048
|
}
|
|
1150
1049
|
async createChat() {
|
|
1151
1050
|
const id = nanoid();
|
|
1152
1051
|
const now = Date.now();
|
|
1153
1052
|
await this.subAgent(this.agent, id);
|
|
1154
|
-
|
|
1053
|
+
try {
|
|
1054
|
+
await this.scheduleChatForAutoDeletion(id, 1);
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
try {
|
|
1057
|
+
await this.deleteSubAgent(this.agent, id);
|
|
1058
|
+
} catch (cleanupError) {
|
|
1059
|
+
console.error("[Assistant] Failed to clean up an unscheduled chat facet", {
|
|
1060
|
+
durableObjectName: id,
|
|
1061
|
+
error: cleanupError
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1064
|
+
throw error;
|
|
1065
|
+
}
|
|
1155
1066
|
this.touchInstance(now);
|
|
1156
1067
|
return id;
|
|
1157
1068
|
}
|
|
@@ -1168,9 +1079,17 @@ var Assistant = class extends Agent$1 {
|
|
|
1168
1079
|
async getChatAgentClassName() {
|
|
1169
1080
|
return this.agent.name;
|
|
1170
1081
|
}
|
|
1082
|
+
async registerChat(durableObjectName) {
|
|
1083
|
+
registerChat(this.sql.bind(this), durableObjectName, Date.now());
|
|
1084
|
+
}
|
|
1171
1085
|
async recordChatTurn(durableObjectName, messages) {
|
|
1172
1086
|
summariseChatWithAI(this.sql.bind(this), durableObjectName, messages, this.fastModel);
|
|
1173
|
-
this.scheduleChatForAutoDeletion(durableObjectName)
|
|
1087
|
+
this.scheduleChatForAutoDeletion(durableObjectName).catch((error) => {
|
|
1088
|
+
console.error("[Assistant] Failed to reschedule chat deletion", {
|
|
1089
|
+
durableObjectName,
|
|
1090
|
+
error
|
|
1091
|
+
});
|
|
1092
|
+
});
|
|
1174
1093
|
}
|
|
1175
1094
|
async recordChatFeedback(durableObjectName, messageId, rating) {
|
|
1176
1095
|
recordChatFeedback(this.sql.bind(this), durableObjectName, messageId, rating);
|
|
@@ -1178,12 +1097,19 @@ var Assistant = class extends Agent$1 {
|
|
|
1178
1097
|
async [DELETE_CHAT_CALLBACK](durableObjectName) {
|
|
1179
1098
|
await this.deleteChat(durableObjectName);
|
|
1180
1099
|
}
|
|
1181
|
-
async scheduleChatForAutoDeletion(durableObjectName) {
|
|
1182
|
-
const retentionMs = getChatRetentionMs(
|
|
1100
|
+
async scheduleChatForAutoDeletion(durableObjectName, retentionDays = 90) {
|
|
1101
|
+
const retentionMs = getChatRetentionMs(retentionDays);
|
|
1183
1102
|
if (retentionMs === null) return;
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1103
|
+
let lastError;
|
|
1104
|
+
for (let attempt = 0; attempt < 3; attempt++) try {
|
|
1105
|
+
const scheduleIds = getDeleteChatScheduleIds(await this.listSchedules(), durableObjectName);
|
|
1106
|
+
await Promise.all(scheduleIds.map((scheduleId) => this.cancelSchedule(scheduleId)));
|
|
1107
|
+
await this.schedule(new Date(Date.now() + retentionMs), DELETE_CHAT_CALLBACK, durableObjectName, { idempotent: true });
|
|
1108
|
+
return;
|
|
1109
|
+
} catch (error) {
|
|
1110
|
+
lastError = error;
|
|
1111
|
+
}
|
|
1112
|
+
throw lastError;
|
|
1187
1113
|
}
|
|
1188
1114
|
/**
|
|
1189
1115
|
* Self-registers this Assistant in the global `agent_registry`. An Assistant
|
|
@@ -1222,52 +1148,6 @@ var Assistant = class extends Agent$1 {
|
|
|
1222
1148
|
console.error("[Assistant] Failed to update agent_registry activity", error);
|
|
1223
1149
|
}
|
|
1224
1150
|
}
|
|
1225
|
-
/**
|
|
1226
|
-
* Binding of the legacy v1 chat Durable Object class, used to migrate a
|
|
1227
|
-
* user's v1 chats into facets the first time they connect. Set this on the
|
|
1228
|
-
* concrete subclass to enable lazy v1 -> v2 migration; leave undefined to
|
|
1229
|
-
* disable it (e.g. for greenfield deployments with no v1 data).
|
|
1230
|
-
*/
|
|
1231
|
-
getMigrationBinding(_env) {}
|
|
1232
|
-
/** In-flight migration, shared across concurrent connections to this DO. */
|
|
1233
|
-
_migrationPromise;
|
|
1234
|
-
/**
|
|
1235
|
-
* Runs the lazy v1 -> v2 migration for this user. Concurrent connections to
|
|
1236
|
-
* this DO share a single in-flight run. Idempotency across runs/restarts is
|
|
1237
|
-
* handled by `migrateUserFromV1` deleting each chat's v1 `conversations` row,
|
|
1238
|
-
* so an already-migrated chat is never re-enumerated.
|
|
1239
|
-
*/
|
|
1240
|
-
async ensureMigrated() {
|
|
1241
|
-
if (!this.getMigrationBinding(this.env)) return;
|
|
1242
|
-
this._migrationPromise ??= this.runMigration().finally(() => {
|
|
1243
|
-
this._migrationPromise = void 0;
|
|
1244
|
-
});
|
|
1245
|
-
await this._migrationPromise;
|
|
1246
|
-
}
|
|
1247
|
-
async runMigration() {
|
|
1248
|
-
const migrationBinding = this.getMigrationBinding(this.env);
|
|
1249
|
-
if (!migrationBinding) return;
|
|
1250
|
-
try {
|
|
1251
|
-
const result = await migrateUserFromV1({
|
|
1252
|
-
sql: this.sql.bind(this),
|
|
1253
|
-
db: migrationBinding.db,
|
|
1254
|
-
userId: this.name,
|
|
1255
|
-
durableObjectBinding: migrationBinding.binding,
|
|
1256
|
-
createFacet: async (chatId) => {
|
|
1257
|
-
return await this.subAgent(this.agent, chatId);
|
|
1258
|
-
}
|
|
1259
|
-
});
|
|
1260
|
-
if (result.migrated > 0 || result.failed > 0) console.info("[Assistant] v1 -> v2 migration complete", {
|
|
1261
|
-
userId: this.name,
|
|
1262
|
-
...result
|
|
1263
|
-
});
|
|
1264
|
-
} catch (error) {
|
|
1265
|
-
console.error("[Assistant] v1 -> v2 migration failed", {
|
|
1266
|
-
userId: this.name,
|
|
1267
|
-
error
|
|
1268
|
-
});
|
|
1269
|
-
}
|
|
1270
|
-
}
|
|
1271
1151
|
};
|
|
1272
1152
|
//#endregion
|
|
1273
1153
|
//#region src/server/features/messages.ts
|
|
@@ -1477,6 +1357,11 @@ var ChatAgent = class extends Agent {
|
|
|
1477
1357
|
configureSession(session) {
|
|
1478
1358
|
return super.configureSession(session).onCompaction(createCompactFn(this.getModel())).compactAfter(COMPACTION_TOKEN_THRESHOLD);
|
|
1479
1359
|
}
|
|
1360
|
+
async beforeTurn(ctx) {
|
|
1361
|
+
const parent = await this.getParentAgent();
|
|
1362
|
+
if (parent?.registerChat) await parent.registerChat(this.name);
|
|
1363
|
+
return super.beforeTurn(ctx);
|
|
1364
|
+
}
|
|
1480
1365
|
async onChatResponse(_result) {
|
|
1481
1366
|
const parent = await this.getParentAgent();
|
|
1482
1367
|
if (parent?.recordChatTurn) await parent.recordChatTurn(this.name, this.messages);
|
|
@@ -1513,38 +1398,6 @@ var ChatAgent = class extends Agent {
|
|
|
1513
1398
|
async getMessageFeedback() {
|
|
1514
1399
|
return getMessageFeedback(this.sql.bind(this));
|
|
1515
1400
|
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Imports a v1 conversation's history into this facet's session storage.
|
|
1518
|
-
*
|
|
1519
|
-
* Called over DO RPC by the v2 `Assistant` during the lazy v1 -> v2
|
|
1520
|
-
* migration. Messages are appended in order as a single linear thread
|
|
1521
|
-
* (each message parented to the previous one) using
|
|
1522
|
-
* `appendMessageToHistory`, which writes durably to the session WITHOUT
|
|
1523
|
-
* triggering a model turn. Any carried-over feedback is then written to
|
|
1524
|
-
* `assistant_messages_feedback`.
|
|
1525
|
-
*
|
|
1526
|
-
* Safe to skip persisting if there is nothing to import.
|
|
1527
|
-
*/
|
|
1528
|
-
async importLegacyMessages(messages, feedback = []) {
|
|
1529
|
-
let parentId = null;
|
|
1530
|
-
const parent = await this.getParentAgent();
|
|
1531
|
-
for (const message of messages) {
|
|
1532
|
-
await this.appendMessageToHistory(message, parentId);
|
|
1533
|
-
parentId = message.id;
|
|
1534
|
-
}
|
|
1535
|
-
for (const item of feedback) {
|
|
1536
|
-
submitMessageFeedback(this.sql.bind(this), item.messageId, item.rating, item.comment);
|
|
1537
|
-
try {
|
|
1538
|
-
await parent?.recordChatFeedback?.(this.name, item.messageId, item.rating);
|
|
1539
|
-
} catch (error) {
|
|
1540
|
-
console.error("[ChatAgent] Failed to import parent chat feedback summary", {
|
|
1541
|
-
durableObjectName: this.name,
|
|
1542
|
-
messageId: item.messageId,
|
|
1543
|
-
error
|
|
1544
|
-
});
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
1401
|
};
|
|
1549
1402
|
//#endregion
|
|
1550
1403
|
//#region src/server/util/skills.ts
|
|
@@ -1579,4 +1432,4 @@ function getCurrentToolContext() {
|
|
|
1579
1432
|
return getCurrentAgent().agent._lastBody;
|
|
1580
1433
|
}
|
|
1581
1434
|
//#endregion
|
|
1582
|
-
export { Agent, Assistant, ChatAgent, exchangeToken, getCurrentToolContext,
|
|
1435
|
+
export { Agent, Assistant, ChatAgent, exchangeToken, getCurrentToolContext, routeAgentRequest, skill, tool };
|
package/dist/providers.d.mts
CHANGED
|
@@ -2,6 +2,10 @@ import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
|
2
2
|
import { GoogleGenerativeAIProvider } from "@ai-sdk/google";
|
|
3
3
|
import { AnthropicProvider } from "@ai-sdk/anthropic";
|
|
4
4
|
|
|
5
|
+
//#region src/providers/metadata.d.ts
|
|
6
|
+
/** Custom metadata serialized into Cloudflare's `cf-aig-metadata` header. */
|
|
7
|
+
type AiGatewayMetadata = Record<string, unknown>;
|
|
8
|
+
//#endregion
|
|
5
9
|
//#region src/providers/anthropic.d.ts
|
|
6
10
|
type AnthropicVertexModelId = Parameters<AnthropicProvider>[0];
|
|
7
11
|
interface AnthropicVertexProviderOptions {
|
|
@@ -11,8 +15,10 @@ interface AnthropicVertexProviderOptions {
|
|
|
11
15
|
googleCloudProjectId: string;
|
|
12
16
|
location?: string;
|
|
13
17
|
anthropicVersion?: string;
|
|
18
|
+
/** Metadata included on every request sent through AI Gateway. */
|
|
19
|
+
metadata?: AiGatewayMetadata;
|
|
14
20
|
}
|
|
15
|
-
declare function createAnthropicProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId, metadata?:
|
|
21
|
+
declare function createAnthropicProvider(options: AnthropicVertexProviderOptions): (modelId: AnthropicVertexModelId, metadata?: AiGatewayMetadata) => AnthropicMessagesLanguageModel;
|
|
16
22
|
//#endregion
|
|
17
23
|
//#region src/providers/gemini.d.ts
|
|
18
24
|
type GeminiModelId = Parameters<GoogleGenerativeAIProvider>[0];
|
|
@@ -22,14 +28,16 @@ interface GeminiProviderOptions {
|
|
|
22
28
|
cloudflareApiToken: string;
|
|
23
29
|
googleCloudProjectId: string;
|
|
24
30
|
location?: string;
|
|
31
|
+
/** Metadata included on every request sent through AI Gateway. */
|
|
32
|
+
metadata?: AiGatewayMetadata;
|
|
25
33
|
}
|
|
26
|
-
declare function createGeminiProvider(options: GeminiProviderOptions, metadata?:
|
|
34
|
+
declare function createGeminiProvider(options: GeminiProviderOptions, metadata?: AiGatewayMetadata): GoogleGenerativeAIProvider;
|
|
27
35
|
//#endregion
|
|
28
36
|
//#region src/providers/index.d.ts
|
|
29
37
|
interface AiGatewayVertexProvidersOptions extends AnthropicVertexProviderOptions, GeminiProviderOptions {}
|
|
30
38
|
declare function createAiGatewayVertexProviders(options: AiGatewayVertexProvidersOptions): {
|
|
31
|
-
anthropic: (modelId: AnthropicVertexModelId, metadata?:
|
|
39
|
+
anthropic: (modelId: AnthropicVertexModelId, metadata?: AiGatewayMetadata) => import("@ai-sdk/anthropic/internal").AnthropicMessagesLanguageModel;
|
|
32
40
|
gemini: import("@ai-sdk/google").GoogleGenerativeAIProvider;
|
|
33
41
|
};
|
|
34
42
|
//#endregion
|
|
35
|
-
export { AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
|
|
43
|
+
export { type AiGatewayMetadata, AiGatewayVertexProvidersOptions, type AnthropicVertexModelId, type AnthropicVertexProviderOptions, type GeminiModelId, type GeminiProviderOptions, createAiGatewayVertexProviders, createAnthropicProvider, createGeminiProvider };
|
package/dist/providers.mjs
CHANGED
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
import { AnthropicMessagesLanguageModel } from "@ai-sdk/anthropic/internal";
|
|
2
2
|
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
3
|
+
//#region src/providers/metadata.ts
|
|
4
|
+
function createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, requestMetadata) {
|
|
5
|
+
const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
|
|
6
|
+
const metadata = { ...defaultMetadata };
|
|
7
|
+
for (const [key, value] of Object.entries(requestMetadata ?? {})) if (!(key in metadata)) metadata[key] = value;
|
|
8
|
+
if (Object.values(metadata).some((value) => value !== void 0)) headers["cf-aig-metadata"] = JSON.stringify(metadata);
|
|
9
|
+
return headers;
|
|
10
|
+
}
|
|
11
|
+
//#endregion
|
|
3
12
|
//#region src/providers/anthropic.ts
|
|
4
13
|
function createAnthropicProvider(options) {
|
|
5
|
-
const { anthropicVersion = "vertex-2023-10-16", cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1" } = options;
|
|
14
|
+
const { anthropicVersion = "vertex-2023-10-16", cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1", metadata: defaultMetadata } = options;
|
|
6
15
|
const anthropicBaseURL = `${`${`https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/`}google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/`}anthropic/models/`;
|
|
7
16
|
return function anthropic(modelId, metadata) {
|
|
8
|
-
const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
|
|
9
|
-
if (metadata) headers["cf-aig-metadata"] = JSON.stringify(metadata);
|
|
10
17
|
return new AnthropicMessagesLanguageModel(modelId, {
|
|
11
18
|
provider: "vertex.anthropic.messages",
|
|
12
19
|
baseURL: anthropicBaseURL,
|
|
13
|
-
headers,
|
|
20
|
+
headers: createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, metadata),
|
|
14
21
|
buildRequestUrl: (baseURL, isStreaming) => `${baseURL}${modelId}:${isStreaming ? "streamRawPredict" : "rawPredict"}`,
|
|
15
22
|
transformRequestBody: (args) => {
|
|
16
23
|
const { model, ...rest } = args;
|
|
@@ -28,13 +35,11 @@ function createAnthropicProvider(options) {
|
|
|
28
35
|
//#endregion
|
|
29
36
|
//#region src/providers/gemini.ts
|
|
30
37
|
function createGeminiProvider(options, metadata) {
|
|
31
|
-
const { cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1" } = options;
|
|
32
|
-
const headers = { "cf-aig-authorization": `Bearer ${cloudflareApiToken}` };
|
|
33
|
-
if (metadata) headers["cf-aig-metadata"] = JSON.stringify(metadata);
|
|
38
|
+
const { cloudflareAccountId, cloudflareAiGatewayId, cloudflareApiToken, googleCloudProjectId, location = "europe-west1", metadata: defaultMetadata } = options;
|
|
34
39
|
return createGoogleGenerativeAI({
|
|
35
40
|
apiKey: "unused",
|
|
36
41
|
baseURL: `https://gateway.ai.cloudflare.com/v1/${cloudflareAccountId}/${cloudflareAiGatewayId}/google-vertex-ai/v1/projects/${googleCloudProjectId}/locations/${location}/publishers/google/models`,
|
|
37
|
-
headers,
|
|
42
|
+
headers: createAiGatewayHeaders(cloudflareApiToken, defaultMetadata, metadata),
|
|
38
43
|
name: "gateway.google.vertex-ai"
|
|
39
44
|
});
|
|
40
45
|
}
|
package/dist/v1.d.mts
CHANGED
|
@@ -220,17 +220,6 @@ declare abstract class ChatAgent<Env extends Cloudflare.Env = Cloudflare.Env, TU
|
|
|
220
220
|
rateMessage(messageId: string, rating: number, comment?: string): Promise<void>;
|
|
221
221
|
getMessageRatings(): Promise<Record<string, MessageRating>>;
|
|
222
222
|
getConversations(): Promise<Record<string, unknown>[]>;
|
|
223
|
-
/**
|
|
224
|
-
* Exports this conversation's persisted message history for the v1 -> v2
|
|
225
|
-
* migration. Called over DO RPC by the v2 `Assistant` while migrating a
|
|
226
|
-
* user's chats into facets. `this.messages` is loaded from the
|
|
227
|
-
* `cf_ai_chat_agent_messages` table when the DO wakes.
|
|
228
|
-
*
|
|
229
|
-
* Read-only: does not mutate or delete any state.
|
|
230
|
-
*/
|
|
231
|
-
exportForMigration(): Promise<{
|
|
232
|
-
messages: UIMessage[];
|
|
233
|
-
}>;
|
|
234
223
|
deleteConversation(id: string): Promise<boolean>;
|
|
235
224
|
destroy(): Promise<void>;
|
|
236
225
|
private deleteConversationCallback;
|
package/dist/v1.mjs
CHANGED
|
@@ -977,17 +977,6 @@ var ChatAgent = class extends AIChatAgent {
|
|
|
977
977
|
async getConversations() {
|
|
978
978
|
return getConversations(this.env.AGENT_DB, this.getUserId());
|
|
979
979
|
}
|
|
980
|
-
/**
|
|
981
|
-
* Exports this conversation's persisted message history for the v1 -> v2
|
|
982
|
-
* migration. Called over DO RPC by the v2 `Assistant` while migrating a
|
|
983
|
-
* user's chats into facets. `this.messages` is loaded from the
|
|
984
|
-
* `cf_ai_chat_agent_messages` table when the DO wakes.
|
|
985
|
-
*
|
|
986
|
-
* Read-only: does not mutate or delete any state.
|
|
987
|
-
*/
|
|
988
|
-
async exportForMigration() {
|
|
989
|
-
return { messages: this.messages };
|
|
990
|
-
}
|
|
991
980
|
async deleteConversation(id) {
|
|
992
981
|
if (!id.startsWith(`${this.getUserId()}:`)) {
|
|
993
982
|
console.error("[Agent] Failed to delete conversation: Not owned by current user", {
|