@economic/agents 0.0.1-alpha.9 → 0.0.1-beta.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/dist/index.d.mts CHANGED
@@ -1,89 +1,14 @@
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";
3
+ import { Connection, ConnectionContext } from "agents";
4
4
 
5
- //#region ../../node_modules/partyserver/dist/index.d.ts
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
5
+ //#region src/server/features/skills/index.d.ts
78
6
  /**
79
7
  * A named group of related tools that can be loaded together on demand.
80
8
  *
81
9
  * The agent starts with only its always-on tools active. When the LLM calls
82
10
  * activate_skill with a skill name, that skill's tools become available for
83
11
  * the rest of the conversation.
84
- *
85
- * Define tools using the AI SDK's `tool()` helper — Zod schemas are supported
86
- * natively via the `parameters` field.
87
12
  */
88
13
  interface Skill {
89
14
  name: string;
@@ -91,477 +16,176 @@ interface Skill {
91
16
  description: string;
92
17
  /**
93
18
  * Guidance text for this skill — e.g. rate limits, preferred patterns,
94
- * when to use each tool. Injected as a system message each turn for any
95
- * skill that is loaded, keeping the `system` prompt static and cacheable.
19
+ * when to use each tool. Appended to the `system` prompt each turn for any
20
+ * skill that is loaded, keeping `system` mostly cacheable.
96
21
  */
97
22
  guidance?: string;
98
23
  tools: ToolSet;
99
24
  }
100
- /**
101
- * Configuration passed to createSkills().
102
- */
103
- interface SkillsConfig {
104
- /** Tools that are always active regardless of loaded skills */
105
- tools: ToolSet;
106
- /** All available skills that can be loaded on demand */
107
- skills: Skill[];
108
- /**
109
- * The base system prompt for the agent. When provided, createSkills uses
110
- * this to compose the full system string (base + guidance) returned by
111
- * getSystem() and from prepareStep. Guidance is kept in the system
112
- * parameter rather than the messages array — Anthropic/Gemini only allow
113
- * system messages at the start of the conversation.
114
- */
115
- systemPrompt?: string;
116
- /**
117
- * Skill names that were loaded in previous turns, read from D1 at turn
118
- * start. Seeds the in-memory loadedSkills set so prior state is restored
119
- * before the first LLM step.
120
- */
121
- initialLoadedSkills?: string[];
25
+ //#endregion
26
+ //#region src/server/llm.d.ts
27
+ type LLMParams = Parameters<typeof streamText>[0] & Parameters<typeof generateText>[0];
28
+ type BuildLLMParamsConfig = Omit<LLMParams, "messages" | "experimental_context" | "abortSignal"> & {
29
+ /** CF options object extracts `abortSignal` and `experimental_context` (from `body`). */options: OnChatMessageOptions | undefined; /** Conversation history (`this.messages`). Converted to `ModelMessage[]` internally. */
30
+ messages: UIMessage[]; /** Skill names loaded in previous turns. Pass `await this.getLoadedSkills()`. */
31
+ activeSkills?: string[]; /** Skills available for on-demand loading this turn. */
32
+ skills?: Skill[];
122
33
  /**
123
- * Called after activate_skill successfully loads new skills.
124
- * Receives the complete current set of loaded skill names. The agent
125
- * buffers this value and writes it to D1 at turn end (in persistMessages),
126
- * keeping the write co-located with message persistence — aligned with
127
- * how slack-bot saves loaded_categories alongside messages.
34
+ * Number of recent messages to keep verbatim during compaction. Older messages
35
+ * beyond this count are summarised by `fastModel` before being sent to the LLM.
36
+ *
37
+ * Defaults to `DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION` (30) when not provided.
38
+ * Set explicitly to `undefined` to disable compaction entirely.
39
+ *
40
+ * Compaction only runs when `fastModel` is also set on the agent class.
41
+ *
42
+ * @internal Injected by `AIChatAgent.buildLLMParams` — do not set this directly.
128
43
  */
129
- onSkillsChanged?: (skills: string[]) => Promise<void>;
44
+ maxMessagesBeforeCompaction?: number;
130
45
  /**
131
- * Optional permission hook. Return false to deny access to a skill.
132
- * Defaults to allow-all if not provided.
46
+ * The fast/cheap model used for compaction and background summarization.
47
+ * Provided automatically from `AIChatAgent.fastModel` — do not set this directly.
48
+ *
49
+ * @internal
133
50
  */
134
- filterSkill?: (skillName: string) => Promise<boolean> | boolean;
135
- }
51
+ fastModel?: LanguageModel;
52
+ };
136
53
  /**
137
- * Skill context injected by the @withSkills decorator.
54
+ * Builds the parameter object for a Vercel AI SDK `streamText` or `generateText` call.
138
55
  *
139
- * Spread the skill fields into streamText:
56
+ * Handles message conversion, optional compaction, skill wiring (`activate_skill`,
57
+ * `list_capabilities`, `prepareStep`), and context/abort signal extraction from
58
+ * the Cloudflare Agents SDK `options` object.
59
+ *
60
+ * The returned object can be spread directly into `streamText` or `generateText`:
140
61
  *
141
62
  * ```typescript
142
- * const { messages, guidance, ...skillArgs } = ctx;
143
- * return streamText({
144
- * model: this.getModel(),
145
- * system: "Your base prompt — static, never includes guidance",
146
- * messages,
147
- * ...skillArgs,
148
- * onFinish,
149
- * stopWhen: stepCountIs(20),
150
- * }).toUIMessageStreamResponse();
63
+ * const params = await buildLLMParams({ ... });
64
+ * return streamText(params).toUIMessageStreamResponse();
151
65
  * ```
152
66
  */
153
- interface SkillContext {
154
- /** All registered tools — spread into streamText */
155
- tools: ai.ToolSet;
156
- /** Currently active tool names — spread into streamText */
157
- activeTools: string[];
158
- /**
159
- * Updates activeTools before each LLM step. Spread into streamText.
160
- */
161
- prepareStep: ai.PrepareStepFunction;
162
- /**
163
- * Guidance text from all currently-loaded skills. Compose your system
164
- * prompt as: `${myBase}${guidance ? '\n\n' + guidance : ''}`
165
- */
166
- guidance: string;
167
- /**
168
- * Plain conversation history from DO SQLite — no guidance injected.
169
- * Pass directly as the `messages` param of streamText.
170
- */
171
- messages: ai.ModelMessage[];
172
- }
173
- /**
174
- * The object returned by createSkills().
175
- * Spread the SDK-specific fields into your streamText call.
176
- */
177
- interface SkillsResult {
178
- /** Guidance text for all currently-loaded skills */
179
- getLoadedGuidance(): string;
180
- /** Current loaded skill names */
181
- getLoadedSkills(): string[];
182
- /**
183
- * Full system string: base system prompt concatenated with guidance from
184
- * all currently-loaded skills. Only meaningful when `systemPrompt` was
185
- * passed to createSkills. Use as the `system` param of streamText.
186
- */
187
- getSystem(): string;
188
- }
67
+ declare function buildLLMParams(config: BuildLLMParamsConfig): Promise<LLMParams>;
189
68
  //#endregion
190
- //#region src/agents/chat/AIChatAgentBase.d.ts
69
+ //#region src/server/agents/AIChatAgent.d.ts
191
70
  /**
192
- * Base class for chat agents with lazy skill loading.
193
- *
194
- * Owns:
195
- * - D1 persistence for loaded skill state (skill names survive DO eviction)
196
- * - Ephemeral message filtering (list_capabilities, no-op activate_skill calls)
197
- * - Message compaction (LLM summarisation when history exceeds token threshold)
198
- * - History replay to newly connected clients (onConnect override)
199
- * - Skill context preparation for use with the @withSkills decorator
71
+ * Base class for Cloudflare Agents SDK chat agents with lazy skill loading
72
+ * and built-in audit logging.
200
73
  *
201
- * Conversation messages are stored in Durable Object SQLite, managed
202
- * automatically by the Cloudflare AIChatAgent no D1 write needed for messages.
74
+ * Handles CF infrastructure concerns only: DO SQLite persistence for loaded
75
+ * skill state, stripping skill meta-tool messages before persistence, history
76
+ * replay to newly connected clients, and writing audit events to D1.
203
77
  *
204
- * D1 is written only when skills change (activate_skill was called this turn),
205
- * not on every turn.
206
- *
207
- * ## Usage
208
- *
209
- * Extend this class when you want full control over `streamText`. Implement
210
- * `getTools()`, `getSkills()`, and your own `onChatMessage` decorated with
211
- * `@withSkills`:
212
- *
213
- * ```typescript
214
- * export class MyAgent extends AIChatAgentBase {
215
- * getTools() { return []; }
216
- * getSkills() { return [searchSkill, codeSkill]; }
217
- * getDB() { return this.env.AGENT_DB; }
218
- *
219
- * @withSkills
220
- * async onChatMessage(onFinish, ctx: SkillContext, options?) {
221
- * const { messages, ...skillArgs } = ctx;
222
- * return streamText({
223
- * model: openai("gpt-4o"),
224
- * system: "You are a helpful assistant.",
225
- * messages,
226
- * ...skillArgs,
227
- * onFinish,
228
- * stopWhen: stepCountIs(20),
229
- * }).toUIMessageStreamResponse();
230
- * }
231
- * }
232
- * ```
233
- *
234
- * For a batteries-included experience where the base class owns `onChatMessage`,
235
- * extend `AIChatAgent` instead.
78
+ * Skill loading, compaction, and LLM communication are delegated to
79
+ * `buildLLMParams` from `@economic/agents`, which you call inside `onChatMessage`.
236
80
  */
237
- declare abstract class AIChatAgentBase<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent$1<Env> {
238
- /**
239
- * Maximum number of messages stored in DO SQLite.
240
- *
241
- * Lowered from the Cloudflare AIChatAgent default of 200. When compaction
242
- * is enabled, one slot is reserved for the summary message so the verbatim
243
- * tail is maxPersistedMessages - 1 recent messages. Raise or lower per agent.
244
- */
245
- maxPersistedMessages: number;
246
- /** Tools that are always active regardless of loaded skills */
247
- abstract getTools(): ToolSet;
248
- /** All skills available for on-demand loading */
249
- abstract getSkills(): Skill[];
81
+ declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgent$1<Env> {
250
82
  /**
251
- * Return a LanguageModel to use for compaction summarisation.
83
+ * Fast/cheap language model used for background tasks: compaction and conversation summarization.
252
84
  *
253
- * Return undefined (default) to disable compaction — messages are kept up
254
- * to maxPersistedMessages and older ones are dropped by the Cloudflare
255
- * AIChatAgent's built-in hard cap.
256
- *
257
- * Override to use a cheaper or faster model for summarisation, or to enable
258
- * compaction in subclasses that do not override it automatically.
259
- */
260
- protected getCompactionModel(): LanguageModel | undefined;
261
- /**
262
- * Return the D1 database binding for persisting loaded skill state.
85
+ * Declare this on every subclass:
263
86
  *
264
- * Override in your subclass to return the binding from env:
265
87
  * ```typescript
266
- * protected getDB() { return this.env.AGENT_DB; }
88
+ * protected fastModel = google("gemini-2.0-flash");
267
89
  * ```
268
90
  *
269
- * Defaults to undefined when undefined, loaded skills reset on every new
270
- * conversation (skills still work within a turn, just not across turns).
271
- */
272
- protected getDB(): D1Database | undefined;
273
- /**
274
- * Optional permission hook. Return false to deny the agent access to a
275
- * skill when activate_skill is called. Defaults to allow-all.
91
+ * To disable compaction for a specific call, pass `maxMessagesBeforeCompaction: undefined`
92
+ * to `buildLLMParams` rather than omitting or nulling out `fastModel`.
276
93
  */
277
- protected filterSkill(_skillName: string): Promise<boolean>;
94
+ protected abstract fastModel: LanguageModel;
95
+ protected getUserId(): string;
96
+ onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
278
97
  /**
279
- * Buffered skill state from the current turn.
280
- *
281
- * Set by the onSkillsChanged callback when activate_skill loads new skills
282
- * mid-turn. Flushed to D1 in persistMessages at turn end — only written
283
- * when this value is set, so D1 is not touched on turns where no new skills
284
- * are loaded.
98
+ * Resolves the D1 database binding required for all D1 writes.
99
+ * Returns null and silently no-ops if AGENT_DB is not bound.
285
100
  */
286
- protected _pendingSkills: string[] | undefined;
101
+ private resolveD1Context;
287
102
  /**
288
- * Reads loaded skill names from D1 for this agent.
289
- *
290
- * Returns an empty array if no record exists (first turn, or no skills
291
- * loaded yet). Conversation messages are not read here — the Cloudflare
292
- * AIChatAgent provides those via this.messages from DO SQLite.
103
+ * Returns all conversations for the current user.
293
104
  */
294
- protected _readSkillState(): Promise<string[]>;
105
+ getConversations(): Promise<Record<string, unknown>[] | undefined>;
295
106
  /**
296
- * Writes loaded skill names to D1 for this agent.
107
+ * Writes an audit event to D1 if `AGENT_DB` is bound on the environment,
108
+ * otherwise silently does nothing.
297
109
  *
298
- * Uses INSERT OR REPLACE so the first skill load creates the row and
299
- * subsequent loads update it. Only called when skills actually changed
300
- * this turn (_pendingSkills is set).
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.
301
113
  */
302
- protected _writeSkillState(skills: string[]): Promise<void>;
114
+ protected log(message: string, payload?: Record<string, unknown>): Promise<void>;
303
115
  /**
304
- * Flush persisted message history to a newly connected client.
305
- *
306
- * The Cloudflare AIChatAgent broadcasts message updates to existing
307
- * connections via persistMessages, but does nothing for connections that
308
- * arrive after a conversation has ended. Without this override, a page
309
- * 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.
310
119
  *
311
- * Skips replay when a stream is active CF_AGENT_STREAM_RESUMING handles
312
- * that case and replays in-progress chunks via its own protocol.
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.
313
125
  */
314
- onConnect(connection: Connection, ctx: ConnectionContext): Promise<void>;
126
+ private recordConversation;
315
127
  /**
316
- * Strips ephemeral content, conditionally saves skill state to D1, then
317
- * delegates to super for DO SQLite persistence and WebSocket broadcast.
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.
318
131
  *
319
- * The Cloudflare AIChatAgent calls persistMessages once per turn after all
320
- * steps complete, so overriding here is the correct place to act — it runs
321
- * after the full assistant message (including all tool results) is assembled.
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.
322
136
  *
323
- * Two things happen here:
137
+ * ```typescript
138
+ * // Compaction on (default threshold):
139
+ * const params = await this.buildLLMParams({ options, onFinish, model, system: "..." });
324
140
  *
325
- * 1. Ephemeral tool calls are stripped — list_capabilities (always) and
326
- * activate_skill when nothing was newly loaded (no state change).
141
+ * // Compaction with custom threshold:
142
+ * const params = await this.buildLLMParams({ options, onFinish, model, maxMessagesBeforeCompaction: 50 });
327
143
  *
328
- * 2. If skills changed this turn (_pendingSkills is set), the updated list
329
- * is written to D1. Turns where no skills were loaded do not touch D1.
144
+ * // Compaction off:
145
+ * const params = await this.buildLLMParams({ options, onFinish, model, maxMessagesBeforeCompaction: undefined });
330
146
  *
331
- * Message persistence itself is handled by super.persistMessages, which
332
- * writes to DO SQLite — no D1 write needed for messages.
147
+ * return streamText(params).toUIMessageStreamResponse();
148
+ * ```
333
149
  */
334
- persistMessages(messages: UIMessage[], excludeBroadcastIds?: string[], options?: {
335
- _deleteStaleRows?: boolean;
336
- }): Promise<void>;
150
+ protected buildLLMParams<TBody = Record<string, unknown>>(config: Omit<BuildLLMParamsConfig, "messages" | "activeSkills" | "fastModel">): ReturnType<typeof buildLLMParams>;
337
151
  /**
338
- * Widened onChatMessage signature that accommodates the @withSkills decorator.
339
- *
340
- * The decorator transforms the consumer's 3-arg form (onFinish, ctx, options) into
341
- * a 2-arg wrapper at runtime. This declaration widens the base class signature so
342
- * that TypeScript accepts the consumer's 3-arg override without errors.
343
- *
344
- * @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.
345
154
  */
346
- onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>, ctxOrOptions?: SkillContext | OnChatMessageOptions): Promise<Response | undefined>;
155
+ protected getLoadedSkills(): Promise<string[]>;
347
156
  /**
348
- * Called by the @withSkills decorator at the start of each turn.
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.
349
160
  *
350
- * Reads loaded skill state from D1, seeds createSkills, and returns a
351
- * SkillContext ready to use in a streamText call.
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.
352
163
  *
353
- * Guidance is exposed as `ctx.guidance` compose your system prompt as:
354
- * `${myBase}${ctx.guidance ? '\n\n' + ctx.guidance : ''}`
164
+ * 2. Logs a turn summary via `log()`. Best-effort: fire-and-forget.
355
165
  *
356
- * Messages are plain (no guidance injected). Guidance stays out of the
357
- * messages array — Anthropic/Gemini only allow system messages at position 0.
358
- */
359
- protected _prepareSkillContext(): Promise<SkillContext>;
360
- }
361
- /**
362
- * Method decorator for use with AIChatAgentBase.
363
- *
364
- * Apply to `onChatMessage` to receive a pre-built SkillContext as the second
365
- * argument. The decorator reads loaded skill state from D1, seeds createSkills,
366
- * and injects guidance into the conversation history from DO SQLite. Skill state
367
- * changes are buffered for D1 write at turn end (only when skills actually change).
368
- * Ephemeral cleanup is handled automatically via the persistMessages override —
369
- * no wiring needed.
370
- *
371
- * ```typescript
372
- * @withSkills
373
- * async onChatMessage(
374
- * onFinish: StreamTextOnFinishCallback<ToolSet>,
375
- * ctx: SkillContext,
376
- * options?: OnChatMessageOptions,
377
- * ) {
378
- * const { messages, guidance, ...skillArgs } = ctx;
379
- * const base = "Your base prompt";
380
- * return streamText({
381
- * model: this.getModel(),
382
- * system: guidance ? `${base}\n\n${guidance}` : base,
383
- * messages,
384
- * ...skillArgs,
385
- * onFinish,
386
- * stopWhen: stepCountIs(20),
387
- * }).toUIMessageStreamResponse();
388
- * }
389
- * ```
390
- */
391
- type WithSkillsFn = (this: AIChatAgentBase, onFinish: StreamTextOnFinishCallback<ToolSet>, ctx: SkillContext, options?: OnChatMessageOptions) => Promise<Response | undefined>;
392
- declare function withSkills(fn: WithSkillsFn, _context: ClassMethodDecoratorContext): WithSkillsFn;
393
- //#endregion
394
- //#region src/agents/chat/AIChatAgent.d.ts
395
- /**
396
- * Batteries-included base class for chat agents with lazy skill loading.
397
- *
398
- * Owns the full `onChatMessage` lifecycle. Implement four abstract methods and
399
- * get lazy skill loading, cross-turn skill persistence, guidance injection,
400
- * ephemeral message cleanup, and message compaction for free.
401
- *
402
- * Conversation messages are stored in Durable Object SQLite by the Cloudflare
403
- * AIChatAgent automatically — available as this.messages at the start of each
404
- * turn. Loaded skill state is stored in D1 (via getDB()) and read at turn start.
405
- * Guidance is injected as a system message just before the current user turn,
406
- * keeping the `system` param static and cacheable across all turns.
407
- *
408
- * ```typescript
409
- * export class MyAgent extends AIChatAgent {
410
- * getModel() { return openai("gpt-4o"); }
411
- * getTools() { return tools; }
412
- * getSkills() { return [searchSkill, codeSkill]; }
413
- * getSystemPrompt() { return "You are a helpful assistant."; }
414
- * getDB() { return this.env.AGENT_DB; }
415
- * }
416
- * ```
417
- *
418
- * If you need full control over the `streamText` call (custom model options,
419
- * streaming transforms, varying the model per request, etc.) use
420
- * `AIChatAgentBase` with the `@withSkills` decorator instead.
421
- */
422
- declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env> extends AIChatAgentBase<Env> {
423
- /** Return the Vercel AI SDK LanguageModel to use for this agent */
424
- abstract getModel(): LanguageModel;
425
- /** Tools that are always active regardless of loaded skills */
426
- abstract getTools(): ToolSet;
427
- /** All skills available for on-demand loading */
428
- abstract getSkills(): Skill[];
429
- /**
430
- * Build the base system prompt. This string is passed to streamText as-is
431
- * and never modified — skill guidance is injected as a separate system
432
- * message so this value stays static and cacheable.
433
- */
434
- abstract getSystemPrompt(): string;
435
- /**
436
- * Return the model used for compaction summarisation.
166
+ * 3. Strips all activate_skill and list_capabilities messages from history.
437
167
  *
438
- * Defaults to getModel() the agent's primary model — so compaction is
439
- * enabled automatically. Override to substitute a cheaper or faster model
440
- * for summarisation (e.g. a smaller model when the primary is expensive).
441
- *
442
- * To opt out of message compaction: override and return undefined.
168
+ * 4. Delegates to super.persistMessages for message storage and WS broadcast.
443
169
  */
444
- protected getCompactionModel(): LanguageModel;
445
- onChatMessage(onFinish: StreamTextOnFinishCallback<ToolSet>, options?: OnChatMessageOptions): Promise<Response | undefined>;
170
+ persistMessages(messages: UIMessage[], excludeBroadcastIds?: string[], options?: {
171
+ _deleteStaleRows?: boolean;
172
+ }): Promise<void>;
446
173
  }
447
174
  //#endregion
448
- //#region src/features/skills/index.d.ts
175
+ //#region src/server/types.d.ts
449
176
  /**
450
- * Creates a skill loading system for use with the Vercel AI SDK.
451
- *
452
- * The agent starts with only its always-on tools active. The LLM can call
453
- * activate_skill to load skill tools on demand. Which skills are loaded is
454
- * persisted to D1 across turns — no message-history parsing required.
177
+ * The context object available throughout an agent's lifetime passed via
178
+ * `experimental_context` to tool `execute` functions. Contains the typed
179
+ * request body merged with platform capabilities like `log`.
455
180
  *
456
- * Guidance from loaded skills is injected as a system message just before
457
- * the current user turn, keeping the `system` prompt static and cacheable.
458
- * prepareStep keeps the guidance message updated if new skills load mid-turn.
459
- *
460
- * Usage with streamText (ai v6):
181
+ * Define your own body shape and compose:
461
182
  * ```typescript
462
- * import { streamText, convertToModelMessages, stepCountIs } from "ai";
463
- *
464
- * // initialLoadedSkills comes from D1 (read at turn start by the agent).
465
- * // onSkillsChanged is called when new skills are loaded; the agent
466
- * // buffers the value and writes it to D1 at turn end in persistMessages.
467
- * const lt = createSkills({ tools, skills, initialLoadedSkills, onSkillsChanged });
468
- * const messages = injectGuidance(modelMessages, lt.getLoadedGuidance());
469
- *
470
- * const result = streamText({
471
- * model,
472
- * system: baseSystemPrompt, // static — never contains guidance, stays cacheable
473
- * messages,
474
- * tools: lt.tools,
475
- * activeTools: lt.activeTools,
476
- * prepareStep: lt.prepareStep, // keeps guidance message updated mid-turn
477
- * stopWhen: stepCountIs(20),
478
- * });
183
+ * interface MyBody { userTier: "free" | "pro" }
184
+ * type MyContext = AgentToolContext<MyBody>;
479
185
  * ```
480
186
  */
481
- declare function createSkills(config: SkillsConfig): SkillsResult & {
482
- tools: ToolSet;
483
- activeTools: string[];
484
- prepareStep: PrepareStepFunction;
187
+ type AgentToolContext<TBody = Record<string, unknown>> = TBody & {
188
+ log: (message: string, payload?: Record<string, unknown>) => void | Promise<void>;
485
189
  };
486
- /**
487
- * Removes ephemeral messages from the conversation before it is saved to D1.
488
- *
489
- * Three kinds of messages are stripped:
490
- *
491
- * 1. list_capabilities tool calls — always stripped. Capability discovery is
492
- * only relevant within the current turn; it adds no useful context for
493
- * future turns.
494
- *
495
- * 2. activate_skill calls when nothing was newly loaded — stripped when all
496
- * requested skills were already active, or when all were denied. In both
497
- * cases nothing changed, so persisting the call would only add noise.
498
- *
499
- * 3. Guidance system messages — stripped by exact content match against the
500
- * provided guidance string. Guidance is always recomputed from loaded skill
501
- * definitions at turn start, so persisting it would create a redundant
502
- * second source of truth alongside the loaded_skills D1 column.
503
- *
504
- * When skills ARE successfully loaded, the short "Loaded: X" result is kept
505
- * in history for model context — so the model can see what was loaded in
506
- * prior turns. Skill state is restored from D1 loaded_skills, not from these
507
- * strings.
508
- *
509
- * If stripping leaves an assistant message with no parts, the entire message
510
- * is dropped (e.g. a step that did nothing but call list_capabilities).
511
- */
512
- declare function filterEphemeralMessages(messages: UIMessage[], guidanceToStrip?: string): UIMessage[];
513
- /**
514
- * Injects loaded skill guidance as a system message just before the last
515
- * message in the array (typically the current user turn).
516
- *
517
- * Guidance is kept separate from the static `system` prompt so that the
518
- * system prompt stays identical on every turn and can be prompt-cached.
519
- * Positioning just before the last message means guidance survives any
520
- * compaction strategy that preserves recent context.
521
- *
522
- * Pass `previousGuidance` (the string injected on the prior call) to remove
523
- * the stale guidance message before inserting the updated one. Removal is by
524
- * exact content match — not by role — so other system messages (memories,
525
- * user preferences, etc.) are left untouched.
526
- *
527
- * At turn start, omit `previousGuidance` — guidance is never persisted to D1
528
- * (it is stripped by filterEphemeralMessages before saving), so there is
529
- * nothing to remove. prepareStep uses previousGuidance within a turn to
530
- * handle guidance updates when new skills are loaded mid-turn.
531
- *
532
- * ```typescript
533
- * // Turn start — just inject
534
- * const messages = injectGuidance(modelMessages, skills.getLoadedGuidance());
535
- *
536
- * // prepareStep — remove stale guidance then re-inject updated guidance
537
- * const messages = injectGuidance(stepMessages, newGuidance, previousGuidance);
538
- * ```
539
- */
540
- declare function injectGuidance(messages: ModelMessage[], guidance: string, previousGuidance?: string): ModelMessage[];
541
- //#endregion
542
- //#region src/agents/chat/compaction/index.d.ts
543
- declare const COMPACT_TOKEN_THRESHOLD = 140000;
544
- /**
545
- * Estimates token count for a message array using a 3.5 chars/token
546
- * approximation — the same heuristic used by slack-bot. Counts text from
547
- * text parts, tool inputs/outputs, and reasoning parts.
548
- */
549
- declare function estimateMessagesTokens(messages: UIMessage[]): number;
550
- /**
551
- * Summarizes older messages into a single system message and appends the
552
- * recent verbatim tail. Returns messages unchanged if the history is already
553
- * short enough to fit within tailSize.
554
- */
555
- declare function compactMessages(messages: UIMessage[], model: LanguageModel, tailSize: number): Promise<UIMessage[]>;
556
- /**
557
- * Entry point called from persistMessages once per turn.
558
- *
559
- * Returns messages unchanged when:
560
- * - model is undefined (compaction disabled on this agent)
561
- * - estimated token count is under COMPACT_TOKEN_THRESHOLD
562
- *
563
- * Otherwise delegates to compactMessages.
564
- */
565
- declare function compactIfNeeded(messages: UIMessage[], model: LanguageModel | undefined, tailSize: number): Promise<UIMessage[]>;
566
190
  //#endregion
567
- export { AIChatAgent, AIChatAgentBase, COMPACT_TOKEN_THRESHOLD, type Skill, type SkillContext, type SkillsConfig, type SkillsResult, compactIfNeeded, compactMessages, createSkills, estimateMessagesTokens, filterEphemeralMessages, injectGuidance, withSkills };
191
+ export { AIChatAgent, type AgentToolContext, type BuildLLMParamsConfig, type Skill, buildLLMParams };