@economic/agents 0.0.1-alpha.2 → 0.0.1-alpha.21
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 +525 -46
- package/dist/index.d.mts +150 -477
- package/dist/index.mjs +3779 -470
- package/package.json +16 -16
- package/schema/schema.sql +32 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,95 +1,7 @@
|
|
|
1
|
-
import * as ai from "ai";
|
|
2
|
-
import { LanguageModel, ModelMessage, PrepareStepFunction, StreamTextOnFinishCallback, ToolSet, UIMessage } from "ai";
|
|
3
1
|
import { AIChatAgent as AIChatAgent$1, OnChatMessageOptions } from "@cloudflare/ai-chat";
|
|
2
|
+
import { LanguageModel, ToolSet, UIMessage, generateText, streamText } from "ai";
|
|
4
3
|
|
|
5
|
-
//#region
|
|
6
|
-
//#region src/types.d.ts
|
|
7
|
-
type ImmutablePrimitive = undefined | null | boolean | string | number;
|
|
8
|
-
type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
|
|
9
|
-
type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
|
|
10
|
-
type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
|
|
11
|
-
type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
|
|
12
|
-
type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };
|
|
13
|
-
type ConnectionState<T> = ImmutableObject<T> | null;
|
|
14
|
-
type ConnectionSetStateFn<T> = (prevState: ConnectionState<T>) => T;
|
|
15
|
-
type ConnectionContext = {
|
|
16
|
-
request: Request;
|
|
17
|
-
};
|
|
18
|
-
/** A WebSocket connected to the Server */
|
|
19
|
-
type Connection<TState = unknown> = WebSocket & {
|
|
20
|
-
/** Connection identifier */id: string;
|
|
21
|
-
/**
|
|
22
|
-
* Arbitrary state associated with this connection.
|
|
23
|
-
* Read-only — use {@link Connection.setState} to update.
|
|
24
|
-
*
|
|
25
|
-
* This property is configurable, meaning it can be redefined via
|
|
26
|
-
* `Object.defineProperty` by downstream consumers (e.g. the Cloudflare
|
|
27
|
-
* Agents SDK) to namespace or wrap internal state storage.
|
|
28
|
-
*/
|
|
29
|
-
state: ConnectionState<TState>;
|
|
30
|
-
/**
|
|
31
|
-
* Update the state associated with this connection.
|
|
32
|
-
*
|
|
33
|
-
* Accepts either a new state value or an updater function that receives
|
|
34
|
-
* the previous state and returns the next state.
|
|
35
|
-
*
|
|
36
|
-
* This property is configurable, meaning it can be redefined via
|
|
37
|
-
* `Object.defineProperty` by downstream consumers. If you redefine
|
|
38
|
-
* `state` and `setState`, you are responsible for calling
|
|
39
|
-
* `serializeAttachment` / `deserializeAttachment` yourself if you need
|
|
40
|
-
* the state to survive hibernation.
|
|
41
|
-
*/
|
|
42
|
-
setState(state: TState | ConnectionSetStateFn<TState> | null): ConnectionState<TState>;
|
|
43
|
-
/**
|
|
44
|
-
* @deprecated use {@link Connection.setState} instead.
|
|
45
|
-
*
|
|
46
|
-
* Low-level method to persist data in the connection's attachment storage.
|
|
47
|
-
* This property is configurable and can be redefined by downstream
|
|
48
|
-
* consumers that need to wrap or namespace the underlying storage.
|
|
49
|
-
*/
|
|
50
|
-
serializeAttachment<T = unknown>(attachment: T): void;
|
|
51
|
-
/**
|
|
52
|
-
* @deprecated use {@link Connection.state} instead.
|
|
53
|
-
*
|
|
54
|
-
* Low-level method to read data from the connection's attachment storage.
|
|
55
|
-
* This property is configurable and can be redefined by downstream
|
|
56
|
-
* consumers that need to wrap or namespace the underlying storage.
|
|
57
|
-
*/
|
|
58
|
-
deserializeAttachment<T = unknown>(): T | null;
|
|
59
|
-
/**
|
|
60
|
-
* Tags assigned to this connection via {@link Server.getConnectionTags}.
|
|
61
|
-
* Always includes the connection id as the first tag.
|
|
62
|
-
*/
|
|
63
|
-
tags: readonly string[];
|
|
64
|
-
/**
|
|
65
|
-
* @deprecated Use `this.name` on the Server instead.
|
|
66
|
-
* The server name. Populated from `Server.name` after initialization.
|
|
67
|
-
*/
|
|
68
|
-
server: string;
|
|
69
|
-
}; //#endregion
|
|
70
|
-
//#region src/index.d.ts
|
|
71
|
-
//#endregion
|
|
72
|
-
//#region ../../node_modules/zod/v4/core/schemas.d.cts
|
|
73
|
-
declare global {
|
|
74
|
-
interface File {}
|
|
75
|
-
}
|
|
76
|
-
//#endregion
|
|
77
|
-
//#region src/features/skills/types.d.ts
|
|
78
|
-
/**
|
|
79
|
-
* A single tool with a name, JSON Schema parameters, and an execute function.
|
|
80
|
-
*
|
|
81
|
-
* Tools are defined in this SDK-agnostic format and converted to the
|
|
82
|
-
* target SDK's format by the adapter layer (createSkills).
|
|
83
|
-
*/
|
|
84
|
-
interface Tool {
|
|
85
|
-
name: string;
|
|
86
|
-
description: string;
|
|
87
|
-
/** JSON Schema object describing the tool's input parameters */
|
|
88
|
-
parameters: Record<string, unknown>;
|
|
89
|
-
execute(args: Record<string, unknown>, options: {
|
|
90
|
-
toolCallId: string;
|
|
91
|
-
}): Promise<string>;
|
|
92
|
-
}
|
|
4
|
+
//#region src/features/skills/index.d.ts
|
|
93
5
|
/**
|
|
94
6
|
* A named group of related tools that can be loaded together on demand.
|
|
95
7
|
*
|
|
@@ -103,459 +15,220 @@ interface Skill {
|
|
|
103
15
|
description: string;
|
|
104
16
|
/**
|
|
105
17
|
* Guidance text for this skill — e.g. rate limits, preferred patterns,
|
|
106
|
-
* when to use each tool.
|
|
107
|
-
* skill that is loaded, keeping
|
|
18
|
+
* when to use each tool. Appended to the `system` prompt each turn for any
|
|
19
|
+
* skill that is loaded, keeping `system` mostly cacheable.
|
|
108
20
|
*/
|
|
109
21
|
guidance?: string;
|
|
110
|
-
tools:
|
|
22
|
+
tools: ToolSet;
|
|
111
23
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
skills
|
|
120
|
-
/**
|
|
121
|
-
* Skill names that were loaded in previous turns, read from D1 at turn
|
|
122
|
-
* start. Seeds the in-memory loadedSkills set so prior state is restored
|
|
123
|
-
* before the first LLM step.
|
|
124
|
-
*/
|
|
125
|
-
initialLoadedSkills?: string[];
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/llm.d.ts
|
|
26
|
+
type LLMParams = Parameters<typeof streamText>[0] & Parameters<typeof generateText>[0];
|
|
27
|
+
type BuildLLMParamsConfig = Omit<LLMParams, "messages" | "experimental_context" | "abortSignal"> & {
|
|
28
|
+
/** CF options object — extracts `abortSignal` and `experimental_context` (from `body`). */options: OnChatMessageOptions | undefined; /** Conversation history (`this.messages`). Converted to `ModelMessage[]` internally. */
|
|
29
|
+
messages: UIMessage[]; /** Skill names loaded in previous turns. Pass `await this.getLoadedSkills()`. */
|
|
30
|
+
activeSkills?: string[]; /** Skills available for on-demand loading this turn. */
|
|
31
|
+
skills?: Skill[];
|
|
126
32
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
33
|
+
* Number of recent messages to keep verbatim during compaction. Older messages
|
|
34
|
+
* beyond this count are summarised by `fastModel` before being sent to the LLM.
|
|
35
|
+
*
|
|
36
|
+
* Defaults to `DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION` (30) when not provided.
|
|
37
|
+
* Set explicitly to `undefined` to disable compaction entirely.
|
|
38
|
+
*
|
|
39
|
+
* Compaction only runs when `fastModel` is also set on the agent class.
|
|
40
|
+
*
|
|
41
|
+
* @internal Injected by `AIChatAgent.buildLLMParams` — do not set this directly.
|
|
132
42
|
*/
|
|
133
|
-
|
|
43
|
+
maxMessagesBeforeCompaction?: number;
|
|
134
44
|
/**
|
|
135
|
-
*
|
|
136
|
-
*
|
|
45
|
+
* The fast/cheap model used for compaction and background summarization.
|
|
46
|
+
* Provided automatically from `AIChatAgent.fastModel` — do not set this directly.
|
|
47
|
+
*
|
|
48
|
+
* @internal
|
|
137
49
|
*/
|
|
138
|
-
|
|
139
|
-
}
|
|
50
|
+
fastModel?: LanguageModel;
|
|
51
|
+
};
|
|
140
52
|
/**
|
|
141
|
-
*
|
|
53
|
+
* Builds the parameter object for a Vercel AI SDK `streamText` or `generateText` call.
|
|
54
|
+
*
|
|
55
|
+
* Handles message conversion, optional compaction, skill wiring (`activate_skill`,
|
|
56
|
+
* `list_capabilities`, `prepareStep`), and context/abort signal extraction from
|
|
57
|
+
* the Cloudflare Agents SDK `options` object.
|
|
142
58
|
*
|
|
143
|
-
*
|
|
144
|
-
* guidance injected at the correct position:
|
|
59
|
+
* The returned object can be spread directly into `streamText` or `generateText`:
|
|
145
60
|
*
|
|
146
61
|
* ```typescript
|
|
147
|
-
* const {
|
|
148
|
-
* return streamText(
|
|
149
|
-
* model: this.getModel(),
|
|
150
|
-
* system: "Your base prompt — static, never includes guidance",
|
|
151
|
-
* messages,
|
|
152
|
-
* ...skillArgs,
|
|
153
|
-
* onFinish,
|
|
154
|
-
* stopWhen: stepCountIs(20),
|
|
155
|
-
* }).toUIMessageStreamResponse();
|
|
62
|
+
* const params = await buildLLMParams({ ... });
|
|
63
|
+
* return streamText(params).toUIMessageStreamResponse();
|
|
156
64
|
* ```
|
|
157
65
|
*/
|
|
158
|
-
|
|
159
|
-
/** All registered tools — spread into streamText */
|
|
160
|
-
tools: ai.ToolSet;
|
|
161
|
-
/** Currently active tool names — spread into streamText */
|
|
162
|
-
activeTools: string[];
|
|
163
|
-
/**
|
|
164
|
-
* Updates active tools and the guidance system message before each LLM step.
|
|
165
|
-
* Spread into streamText.
|
|
166
|
-
*/
|
|
167
|
-
prepareStep: ai.PrepareStepFunction;
|
|
168
|
-
/**
|
|
169
|
-
* Conversation messages read from D1 with current skill guidance already
|
|
170
|
-
* injected just before the last message (the current user turn). Pass
|
|
171
|
-
* directly as the `messages` param of streamText.
|
|
172
|
-
*/
|
|
173
|
-
messages: ai.ModelMessage[];
|
|
174
|
-
}
|
|
175
|
-
/**
|
|
176
|
-
* The object returned by createSkills().
|
|
177
|
-
* Spread the SDK-specific fields into your streamText call.
|
|
178
|
-
*/
|
|
179
|
-
interface SkillsResult {
|
|
180
|
-
/** Guidance text for all currently-loaded skills */
|
|
181
|
-
getLoadedGuidance(): string;
|
|
182
|
-
/** Current loaded skill names */
|
|
183
|
-
getLoadedSkills(): string[];
|
|
184
|
-
}
|
|
66
|
+
declare function buildLLMParams(config: BuildLLMParamsConfig): Promise<LLMParams>;
|
|
185
67
|
//#endregion
|
|
186
|
-
//#region src/agents/
|
|
68
|
+
//#region src/agents/AIChatAgent.d.ts
|
|
187
69
|
/**
|
|
188
|
-
* Base class for chat agents with lazy skill loading
|
|
70
|
+
* Base class for Cloudflare Agents SDK chat agents with lazy skill loading
|
|
71
|
+
* and built-in audit logging.
|
|
189
72
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
193
|
-
* - Message compaction (LLM summarisation when history exceeds token threshold)
|
|
194
|
-
* - History replay to newly connected clients (onConnect override)
|
|
195
|
-
* - Skill context preparation for use with the @withSkills decorator
|
|
73
|
+
* Handles CF infrastructure concerns only: DO SQLite persistence for loaded
|
|
74
|
+
* skill state, stripping skill meta-tool messages before persistence, history
|
|
75
|
+
* replay to newly connected clients, and writing audit events to D1.
|
|
196
76
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* D1 is written only when skills change (activate_skill was called this turn),
|
|
201
|
-
* not on every turn.
|
|
202
|
-
*
|
|
203
|
-
* ## Usage
|
|
204
|
-
*
|
|
205
|
-
* Extend this class when you want full control over `streamText`. Implement
|
|
206
|
-
* `getTools()`, `getSkills()`, and your own `onChatMessage` decorated with
|
|
207
|
-
* `@withSkills`:
|
|
208
|
-
*
|
|
209
|
-
* ```typescript
|
|
210
|
-
* export class MyAgent extends AIChatAgentBase {
|
|
211
|
-
* getTools() { return []; }
|
|
212
|
-
* getSkills() { return [searchSkill, codeSkill]; }
|
|
213
|
-
* getDB() { return this.env.AGENT_DB; }
|
|
214
|
-
*
|
|
215
|
-
* @withSkills
|
|
216
|
-
* async onChatMessage(onFinish, ctx: SkillContext, options?) {
|
|
217
|
-
* const { messages, ...skillArgs } = ctx;
|
|
218
|
-
* return streamText({
|
|
219
|
-
* model: openai("gpt-4o"),
|
|
220
|
-
* system: "You are a helpful assistant.",
|
|
221
|
-
* messages,
|
|
222
|
-
* ...skillArgs,
|
|
223
|
-
* onFinish,
|
|
224
|
-
* stopWhen: stepCountIs(20),
|
|
225
|
-
* }).toUIMessageStreamResponse();
|
|
226
|
-
* }
|
|
227
|
-
* }
|
|
228
|
-
* ```
|
|
229
|
-
*
|
|
230
|
-
* For a batteries-included experience where the base class owns `onChatMessage`,
|
|
231
|
-
* extend `AIChatAgent` instead.
|
|
77
|
+
* Skill loading, compaction, and LLM communication are delegated to
|
|
78
|
+
* `buildLLMParams` from `@economic/agents`, which you call inside `onChatMessage`.
|
|
232
79
|
*/
|
|
233
|
-
declare abstract class
|
|
80
|
+
declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent$1<Env> {
|
|
234
81
|
/**
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
* is enabled, one slot is reserved for the summary message so the verbatim
|
|
239
|
-
* tail is maxPersistedMessages - 1 recent messages. Raise or lower per agent.
|
|
82
|
+
* Composed user identifier extracted from `options.body.userId` during
|
|
83
|
+
* `buildLLMParams`. Expected format: `{agreementNumber}_{userId}`, e.g. `148583_matt`.
|
|
84
|
+
* Undefined if the client did not include `userId` in the request body.
|
|
240
85
|
*/
|
|
241
|
-
|
|
242
|
-
/** Tools that are always active regardless of loaded skills */
|
|
243
|
-
abstract getTools(): Tool[];
|
|
244
|
-
/** All skills available for on-demand loading */
|
|
245
|
-
abstract getSkills(): Skill[];
|
|
86
|
+
protected _userId: string | undefined;
|
|
246
87
|
/**
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
* Return undefined (default) to disable compaction — messages are kept up
|
|
250
|
-
* to maxPersistedMessages and older ones are dropped by the Cloudflare
|
|
251
|
-
* AIChatAgent's built-in hard cap.
|
|
88
|
+
* Fast/cheap language model used for background tasks: compaction and conversation summarization.
|
|
252
89
|
*
|
|
253
|
-
*
|
|
254
|
-
* compaction in subclasses that do not override it automatically.
|
|
255
|
-
*/
|
|
256
|
-
protected getCompactionModel(): LanguageModel | undefined;
|
|
257
|
-
/**
|
|
258
|
-
* Return the D1 database binding for persisting loaded skill state.
|
|
90
|
+
* Declare this on every subclass:
|
|
259
91
|
*
|
|
260
|
-
* Override in your subclass to return the binding from env:
|
|
261
92
|
* ```typescript
|
|
262
|
-
* protected
|
|
93
|
+
* protected fastModel = google("gemini-2.0-flash");
|
|
263
94
|
* ```
|
|
264
95
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*/
|
|
268
|
-
protected getDB(): D1Database | undefined;
|
|
269
|
-
/**
|
|
270
|
-
* Optional permission hook. Return false to deny the agent access to a
|
|
271
|
-
* skill when activate_skill is called. Defaults to allow-all.
|
|
272
|
-
*/
|
|
273
|
-
protected filterSkill(_skillName: string): Promise<boolean>;
|
|
274
|
-
/**
|
|
275
|
-
* Buffered skill state from the current turn.
|
|
276
|
-
*
|
|
277
|
-
* Set by the onSkillsChanged callback when activate_skill loads new skills
|
|
278
|
-
* mid-turn. Flushed to D1 in persistMessages at turn end — only written
|
|
279
|
-
* when this value is set, so D1 is not touched on turns where no new skills
|
|
280
|
-
* are loaded.
|
|
96
|
+
* To disable compaction for a specific call, pass `maxMessagesBeforeCompaction: undefined`
|
|
97
|
+
* to `buildLLMParams` rather than omitting or nulling out `fastModel`.
|
|
281
98
|
*/
|
|
282
|
-
protected
|
|
99
|
+
protected abstract fastModel: LanguageModel;
|
|
283
100
|
/**
|
|
284
|
-
*
|
|
285
|
-
*
|
|
286
|
-
* Returns an
|
|
287
|
-
* loaded yet). Conversation messages are not read here — the Cloudflare
|
|
288
|
-
* AIChatAgent provides those via this.messages from DO SQLite.
|
|
101
|
+
* Resolves the D1 database binding and userId required for all D1 writes.
|
|
102
|
+
* Returns null and silently no-ops if AGENT_DB is not bound.
|
|
103
|
+
* Returns null and logs an error if userId is missing from the request body.
|
|
289
104
|
*/
|
|
290
|
-
|
|
105
|
+
private resolveD1Context;
|
|
291
106
|
/**
|
|
292
|
-
* Writes
|
|
107
|
+
* Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
|
|
108
|
+
* otherwise silently does nothing.
|
|
293
109
|
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
110
|
+
* Called automatically after every turn (from `persistMessages`) and on
|
|
111
|
+
* non-clean finish reasons (from `buildLLMParams`). Also available via
|
|
112
|
+
* `experimental_context.log` in tool `execute` functions.
|
|
297
113
|
*/
|
|
298
|
-
protected
|
|
114
|
+
protected log(message: string, payload?: Record<string, unknown>): Promise<void>;
|
|
299
115
|
/**
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* connections via persistMessages, but does nothing for connections that
|
|
304
|
-
* arrive after a conversation has ended. Without this override, a page
|
|
305
|
-
* refresh produces an empty UI even though the history is intact in DO SQLite.
|
|
116
|
+
* Records this conversation in the `conversations` D1 table and triggers
|
|
117
|
+
* LLM-based title/summary generation when appropriate. Called automatically
|
|
118
|
+
* from `persistMessages` after every turn.
|
|
306
119
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
120
|
+
* On the first turn (no existing row), awaits `generateTitleAndSummary` and
|
|
121
|
+
* inserts the row with title and summary already populated. On subsequent
|
|
122
|
+
* turns, upserts the timestamp and fire-and-forgets a summary refresh every
|
|
123
|
+
* `SUMMARY_CONTEXT_MESSAGES` messages (when the context window fully turns
|
|
124
|
+
* over). Neither path blocks the response to the client.
|
|
309
125
|
*/
|
|
310
|
-
|
|
126
|
+
private recordConversation;
|
|
311
127
|
/**
|
|
312
|
-
*
|
|
313
|
-
*
|
|
128
|
+
* Builds the parameter object for a `streamText` or `generateText` call,
|
|
129
|
+
* pre-filling `messages`, `activeSkills`, and `fastModel` from this agent instance.
|
|
130
|
+
* Injects `log` into `experimental_context` and logs non-clean finish reasons.
|
|
314
131
|
*
|
|
315
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
132
|
+
* **Compaction** runs automatically when `fastModel` is set on the class, using
|
|
133
|
+
* `DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION` (30) as the threshold. Override the
|
|
134
|
+
* threshold by passing `maxMessagesBeforeCompaction`. Disable compaction entirely
|
|
135
|
+
* by passing `maxMessagesBeforeCompaction: undefined` explicitly.
|
|
318
136
|
*
|
|
319
|
-
*
|
|
137
|
+
* ```typescript
|
|
138
|
+
* // Compaction on (default threshold):
|
|
139
|
+
* const params = await this.buildLLMParams({ options, onFinish, model, system: "..." });
|
|
320
140
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
141
|
+
* // Compaction with custom threshold:
|
|
142
|
+
* const params = await this.buildLLMParams({ options, onFinish, model, maxMessagesBeforeCompaction: 50 });
|
|
323
143
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
144
|
+
* // Compaction off:
|
|
145
|
+
* const params = await this.buildLLMParams({ options, onFinish, model, maxMessagesBeforeCompaction: undefined });
|
|
326
146
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
147
|
+
* return streamText(params).toUIMessageStreamResponse();
|
|
148
|
+
* ```
|
|
329
149
|
*/
|
|
330
|
-
|
|
331
|
-
_deleteStaleRows?: boolean;
|
|
332
|
-
}): Promise<void>;
|
|
150
|
+
protected buildLLMParams(config: Omit<BuildLLMParamsConfig, "messages" | "activeSkills" | "fastModel">): ReturnType<typeof buildLLMParams>;
|
|
333
151
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
* The decorator transforms the consumer's 3-arg form (onFinish, ctx, options) into
|
|
337
|
-
* a 2-arg wrapper at runtime. This declaration widens the base class signature so
|
|
338
|
-
* that TypeScript accepts the consumer's 3-arg override without errors.
|
|
339
|
-
*
|
|
340
|
-
* @ts-ignore — intentional: widens the Cloudflare AIChatAgent's (onFinish, options?) signature.
|
|
152
|
+
* Skill names persisted from previous turns, read from DO SQLite.
|
|
153
|
+
* Returns an empty array if no skills have been loaded yet.
|
|
341
154
|
*/
|
|
342
|
-
|
|
155
|
+
protected getLoadedSkills(): Promise<string[]>;
|
|
343
156
|
/**
|
|
344
|
-
*
|
|
157
|
+
* Extracts skill state from activate_skill results, persists to DO SQLite,
|
|
158
|
+
* logs a turn summary, then strips all skill meta-tool messages before
|
|
159
|
+
* delegating to super.
|
|
345
160
|
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
161
|
+
* 1. Scans activate_skill tool results for SKILL_STATE_SENTINEL. When found,
|
|
162
|
+
* the embedded JSON array of loaded skill names is written to DO SQLite.
|
|
348
163
|
*
|
|
349
|
-
*
|
|
350
|
-
* current user turn — pass it directly as the `messages` param of streamText.
|
|
351
|
-
* Guidance is never stored in DO SQLite, so loaded_skills in D1 is the
|
|
352
|
-
* single source of truth for which skills are active.
|
|
353
|
-
*/
|
|
354
|
-
protected _prepareSkillContext(): Promise<SkillContext>;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Method decorator for use with AIChatAgentBase.
|
|
358
|
-
*
|
|
359
|
-
* Apply to `onChatMessage` to receive a pre-built SkillContext as the second
|
|
360
|
-
* argument. The decorator reads loaded skill state from D1, seeds createSkills,
|
|
361
|
-
* and injects guidance into the conversation history from DO SQLite. Skill state
|
|
362
|
-
* changes are buffered for D1 write at turn end (only when skills actually change).
|
|
363
|
-
* Ephemeral cleanup is handled automatically via the persistMessages override —
|
|
364
|
-
* no wiring needed.
|
|
365
|
-
*
|
|
366
|
-
* ```typescript
|
|
367
|
-
* @withSkills
|
|
368
|
-
* async onChatMessage(
|
|
369
|
-
* onFinish: StreamTextOnFinishCallback<ToolSet>,
|
|
370
|
-
* ctx: SkillContext,
|
|
371
|
-
* options?: OnChatMessageOptions,
|
|
372
|
-
* ) {
|
|
373
|
-
* const { messages, ...skillArgs } = ctx;
|
|
374
|
-
* return streamText({
|
|
375
|
-
* model: this.getModel(),
|
|
376
|
-
* system: "Your base prompt — static, never includes guidance",
|
|
377
|
-
* messages,
|
|
378
|
-
* ...skillArgs,
|
|
379
|
-
* onFinish,
|
|
380
|
-
* stopWhen: stepCountIs(20),
|
|
381
|
-
* }).toUIMessageStreamResponse();
|
|
382
|
-
* }
|
|
383
|
-
* ```
|
|
384
|
-
*/
|
|
385
|
-
type WithSkillsFn = (this: AIChatAgentBase, onFinish: StreamTextOnFinishCallback<ToolSet>, ctx: SkillContext, options?: OnChatMessageOptions) => Promise<Response | undefined>;
|
|
386
|
-
declare function withSkills(fn: WithSkillsFn, _context: ClassMethodDecoratorContext): WithSkillsFn;
|
|
387
|
-
//#endregion
|
|
388
|
-
//#region src/agents/chat/AIChatAgent.d.ts
|
|
389
|
-
/**
|
|
390
|
-
* Batteries-included base class for chat agents with lazy skill loading.
|
|
391
|
-
*
|
|
392
|
-
* Owns the full `onChatMessage` lifecycle. Implement four abstract methods and
|
|
393
|
-
* get lazy skill loading, cross-turn skill persistence, guidance injection,
|
|
394
|
-
* ephemeral message cleanup, and message compaction for free.
|
|
395
|
-
*
|
|
396
|
-
* Conversation messages are stored in Durable Object SQLite by the Cloudflare
|
|
397
|
-
* AIChatAgent automatically — available as this.messages at the start of each
|
|
398
|
-
* turn. Loaded skill state is stored in D1 (via getDB()) and read at turn start.
|
|
399
|
-
* Guidance is injected as a system message just before the current user turn,
|
|
400
|
-
* keeping the `system` param static and cacheable across all turns.
|
|
401
|
-
*
|
|
402
|
-
* ```typescript
|
|
403
|
-
* export class MyAgent extends AIChatAgent {
|
|
404
|
-
* getModel() { return openai("gpt-4o"); }
|
|
405
|
-
* getTools() { return []; }
|
|
406
|
-
* getSkills() { return [searchSkill, codeSkill]; }
|
|
407
|
-
* getSystemPrompt() { return "You are a helpful assistant."; }
|
|
408
|
-
* getDB() { return this.env.AGENT_DB; }
|
|
409
|
-
* }
|
|
410
|
-
* ```
|
|
411
|
-
*
|
|
412
|
-
* If you need full control over the `streamText` call (custom model options,
|
|
413
|
-
* streaming transforms, varying the model per request, etc.) use
|
|
414
|
-
* `AIChatAgentBase` with the `@withSkills` decorator instead.
|
|
415
|
-
*/
|
|
416
|
-
declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgentBase<Env> {
|
|
417
|
-
/** Return the Vercel AI SDK LanguageModel to use for this agent */
|
|
418
|
-
abstract getModel(): LanguageModel;
|
|
419
|
-
/** Tools that are always active regardless of loaded skills */
|
|
420
|
-
abstract getTools(): Tool[];
|
|
421
|
-
/** All skills available for on-demand loading */
|
|
422
|
-
abstract getSkills(): Skill[];
|
|
423
|
-
/**
|
|
424
|
-
* Build the base system prompt. This string is passed to streamText as-is
|
|
425
|
-
* and never modified — skill guidance is injected as a separate system
|
|
426
|
-
* message so this value stays static and cacheable.
|
|
427
|
-
*/
|
|
428
|
-
abstract getSystemPrompt(): string;
|
|
429
|
-
/**
|
|
430
|
-
* Return the model used for compaction summarisation.
|
|
164
|
+
* 2. Logs a turn summary via `log()`. Best-effort: fire-and-forget.
|
|
431
165
|
*
|
|
432
|
-
*
|
|
433
|
-
* enabled automatically. Override to substitute a cheaper or faster model
|
|
434
|
-
* for summarisation (e.g. a smaller model when the primary is expensive).
|
|
166
|
+
* 3. Strips all activate_skill and list_capabilities messages from history.
|
|
435
167
|
*
|
|
436
|
-
*
|
|
168
|
+
* 4. Delegates to super.persistMessages for message storage and WS broadcast.
|
|
437
169
|
*/
|
|
438
|
-
|
|
439
|
-
|
|
170
|
+
persistMessages(messages: UIMessage[], excludeBroadcastIds?: string[], options?: {
|
|
171
|
+
_deleteStaleRows?: boolean;
|
|
172
|
+
}): Promise<void>;
|
|
440
173
|
}
|
|
441
174
|
//#endregion
|
|
442
|
-
//#region src/
|
|
175
|
+
//#region src/decorators/guard.d.ts
|
|
443
176
|
/**
|
|
444
|
-
*
|
|
445
|
-
*
|
|
446
|
-
*
|
|
447
|
-
* activate_skill to load skill tools on demand. Which skills are loaded is
|
|
448
|
-
* persisted to D1 across turns — no message-history parsing required.
|
|
449
|
-
*
|
|
450
|
-
* Guidance from loaded skills is injected as a system message just before
|
|
451
|
-
* the current user turn, keeping the `system` prompt static and cacheable.
|
|
452
|
-
* prepareStep keeps the guidance message updated if new skills load mid-turn.
|
|
453
|
-
*
|
|
454
|
-
* Usage with streamText (ai v6):
|
|
455
|
-
* ```typescript
|
|
456
|
-
* import { streamText, convertToModelMessages, stepCountIs } from "ai";
|
|
177
|
+
* Function run before a guarded handler (e.g. `onChatMessage`). Receives the
|
|
178
|
+
* custom request body from chat options (`options.body`, same shape as
|
|
179
|
+
* `OnChatMessageOptions` from `@cloudflare/ai-chat`).
|
|
457
180
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* // buffers the value and writes it to D1 at turn end in persistMessages.
|
|
461
|
-
* const lt = createSkills({ tools, skills, initialLoadedSkills, onSkillsChanged });
|
|
462
|
-
* const messages = injectGuidance(modelMessages, lt.getLoadedGuidance());
|
|
181
|
+
* Return a {@link Response} to short-circuit and skip the decorated method.
|
|
182
|
+
* Return nothing (or `undefined`) to allow the method to run.
|
|
463
183
|
*
|
|
464
|
-
*
|
|
465
|
-
*
|
|
466
|
-
* system: baseSystemPrompt, // static — never contains guidance, stays cacheable
|
|
467
|
-
* messages,
|
|
468
|
-
* tools: lt.tools,
|
|
469
|
-
* activeTools: lt.activeTools,
|
|
470
|
-
* prepareStep: lt.prepareStep, // keeps guidance message updated mid-turn
|
|
471
|
-
* stopWhen: stepCountIs(20),
|
|
472
|
-
* });
|
|
473
|
-
* ```
|
|
184
|
+
* Typical uses include auth, rate limits, or feature flags — all logic lives here;
|
|
185
|
+
* the `guard` decorator only forwards `body` and handles the return shape.
|
|
474
186
|
*/
|
|
475
|
-
|
|
476
|
-
tools: ToolSet;
|
|
477
|
-
activeTools: string[];
|
|
478
|
-
prepareStep: PrepareStepFunction;
|
|
479
|
-
};
|
|
187
|
+
type GuardFn<TBody = Record<string, unknown>> = (body: TBody | undefined) => Response | void | Promise<Response | void>;
|
|
480
188
|
/**
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
189
|
+
* Method decorator (TypeScript 5+ stage-3) that runs `guardFn` with the second
|
|
190
|
+
* argument's `body` (the chat request body). If `guardFn` returns a
|
|
191
|
+
* {@link Response}, that value is returned and the original method is not called.
|
|
484
192
|
*
|
|
485
|
-
*
|
|
486
|
-
*
|
|
487
|
-
* future turns.
|
|
193
|
+
* Intended for `onChatMessage(onFinish, options?)` on subclasses of
|
|
194
|
+
* `AIChatAgent`; `options` is read as `{ body?: TBody }`.
|
|
488
195
|
*
|
|
489
|
-
*
|
|
490
|
-
* requested skills were already active, or when all were denied. In both
|
|
491
|
-
* cases nothing changed, so persisting the call would only add noise.
|
|
196
|
+
* @param guardFn - Called with `options?.body` (cast to `TBody`) before the method body.
|
|
492
197
|
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
* second source of truth alongside the loaded_skills D1 column.
|
|
497
|
-
*
|
|
498
|
-
* When skills ARE successfully loaded, the short "Loaded: X" result is kept
|
|
499
|
-
* in history for model context — so the model can see what was loaded in
|
|
500
|
-
* prior turns. Skill state is restored from D1 loaded_skills, not from these
|
|
501
|
-
* strings.
|
|
502
|
-
*
|
|
503
|
-
* If stripping leaves an assistant message with no parts, the entire message
|
|
504
|
-
* is dropped (e.g. a step that did nothing but call list_capabilities).
|
|
505
|
-
*/
|
|
506
|
-
declare function filterEphemeralMessages(messages: UIMessage[], guidanceToStrip?: string): UIMessage[];
|
|
507
|
-
/**
|
|
508
|
-
* Injects loaded skill guidance as a system message just before the last
|
|
509
|
-
* message in the array (typically the current user turn).
|
|
198
|
+
* @example
|
|
199
|
+
* ```ts
|
|
200
|
+
* interface RequestBody { token: string }
|
|
510
201
|
*
|
|
511
|
-
*
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
* Pass `previousGuidance` (the string injected on the prior call) to remove
|
|
517
|
-
* the stale guidance message before inserting the updated one. Removal is by
|
|
518
|
-
* exact content match — not by role — so other system messages (memories,
|
|
519
|
-
* user preferences, etc.) are left untouched.
|
|
520
|
-
*
|
|
521
|
-
* At turn start, omit `previousGuidance` — guidance is never persisted to D1
|
|
522
|
-
* (it is stripped by filterEphemeralMessages before saving), so there is
|
|
523
|
-
* nothing to remove. prepareStep uses previousGuidance within a turn to
|
|
524
|
-
* handle guidance updates when new skills are loaded mid-turn.
|
|
525
|
-
*
|
|
526
|
-
* ```typescript
|
|
527
|
-
* // Turn start — just inject
|
|
528
|
-
* const messages = injectGuidance(modelMessages, skills.getLoadedGuidance());
|
|
202
|
+
* const requireToken: GuardFn<RequestBody> = async (body) => {
|
|
203
|
+
* if (!await isValidToken(body?.token)) {
|
|
204
|
+
* return new Response("Unauthorized", { status: 401 });
|
|
205
|
+
* }
|
|
206
|
+
* };
|
|
529
207
|
*
|
|
530
|
-
*
|
|
531
|
-
*
|
|
208
|
+
* class MyAgent extends AIChatAgent<Env> {
|
|
209
|
+
* @guard(requireToken)
|
|
210
|
+
* async onChatMessage(onFinish, options) {
|
|
211
|
+
* // ...
|
|
212
|
+
* }
|
|
213
|
+
* }
|
|
532
214
|
* ```
|
|
533
215
|
*/
|
|
534
|
-
declare function
|
|
216
|
+
declare function guard<TBody = Record<string, unknown>>(guardFn: GuardFn<TBody>): (target: (...args: unknown[]) => Promise<Response>, _context: ClassMethodDecoratorContext) => (this: unknown, ...args: unknown[]) => Promise<Response>;
|
|
535
217
|
//#endregion
|
|
536
|
-
//#region src/
|
|
537
|
-
declare const COMPACT_TOKEN_THRESHOLD = 140000;
|
|
538
|
-
/**
|
|
539
|
-
* Estimates token count for a message array using a 3.5 chars/token
|
|
540
|
-
* approximation — the same heuristic used by slack-bot. Counts text from
|
|
541
|
-
* text parts, tool inputs/outputs, and reasoning parts.
|
|
542
|
-
*/
|
|
543
|
-
declare function estimateMessagesTokens(messages: UIMessage[]): number;
|
|
544
|
-
/**
|
|
545
|
-
* Summarizes older messages into a single system message and appends the
|
|
546
|
-
* recent verbatim tail. Returns messages unchanged if the history is already
|
|
547
|
-
* short enough to fit within tailSize.
|
|
548
|
-
*/
|
|
549
|
-
declare function compactMessages(messages: UIMessage[], model: LanguageModel, tailSize: number): Promise<UIMessage[]>;
|
|
218
|
+
//#region src/types.d.ts
|
|
550
219
|
/**
|
|
551
|
-
*
|
|
220
|
+
* The context object available throughout an agent's lifetime — passed via
|
|
221
|
+
* `experimental_context` to tool `execute` functions. Contains the typed
|
|
222
|
+
* request body merged with platform capabilities like `log`.
|
|
552
223
|
*
|
|
553
|
-
*
|
|
554
|
-
*
|
|
555
|
-
*
|
|
556
|
-
*
|
|
557
|
-
*
|
|
224
|
+
* Define your own body shape and compose:
|
|
225
|
+
* ```typescript
|
|
226
|
+
* interface MyBody { userId: string; userTier: "free" | "pro" }
|
|
227
|
+
* type MyContext = AgentContext<MyBody>;
|
|
228
|
+
* ```
|
|
558
229
|
*/
|
|
559
|
-
|
|
230
|
+
type AgentContext<TBody = Record<string, unknown>> = TBody & {
|
|
231
|
+
log: (message: string, payload?: Record<string, unknown>) => void | Promise<void>;
|
|
232
|
+
};
|
|
560
233
|
//#endregion
|
|
561
|
-
export { AIChatAgent,
|
|
234
|
+
export { AIChatAgent, type AgentContext, type BuildLLMParamsConfig, type GuardFn, type Skill, buildLLMParams, guard };
|