@economic/agents 0.0.1-alpha.18 → 0.0.1-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,7 @@ npm install @economic/agents ai @cloudflare/ai-chat
13
13
  `@economic/agents` provides:
14
14
 
15
15
  - **`AIChatAgent`** — an abstract Cloudflare Durable Object base class. Implement `onChatMessage`, call `this.buildLLMParams()`, and pass the result to `streamText` from the AI SDK.
16
+ - **`guard`** — optional TypeScript 5+ method decorator for `onChatMessage`. Runs your function with `options.body`; return a `Response` to short-circuit (e.g. auth), or nothing to continue.
16
17
  - **`buildLLMParams`** — the standalone version of the above, for use outside of `AIChatAgent` or in custom agent implementations.
17
18
 
18
19
  Skills and compaction are AI SDK concerns — they control what goes to the LLM. The CF layer is responsible for WebSockets, Durable Objects, and message persistence. These are kept separate.
@@ -121,6 +122,43 @@ Protected method on `AIChatAgent`. Wraps the standalone `buildLLMParams` functio
121
122
 
122
123
  Config is everything accepted by the standalone `buildLLMParams` except `messages`, `activeSkills`, and `fastModel`.
123
124
 
125
+ ### `guard`
126
+
127
+ Method decorator (TypeScript 5+ stage-3) for handlers shaped like `onChatMessage(onFinish, options?)`. Before your method runs, it calls your `GuardFn` with `options?.body` (the same custom body the client sends via `useAgentChat` / `body` on the chat request).
128
+
129
+ - Return **`undefined` / nothing** — the decorated method runs as usual.
130
+ - Return a **`Response`** — that response is returned immediately; `onChatMessage` is not called.
131
+
132
+ All policy (tokens, tiers, rate limits) lives in the guard function; the decorator only forwards `body` and branches on whether a `Response` was returned.
133
+
134
+ ```typescript
135
+ import { streamText } from "ai";
136
+ import { openai } from "@ai-sdk/openai";
137
+ import { AIChatAgent, guard, type GuardFn } from "@economic/agents";
138
+
139
+ const requireToken: GuardFn = async (body) => {
140
+ const token = body?.token;
141
+ if (typeof token !== "string" || !(await isValidToken(token))) {
142
+ return new Response("Unauthorized", { status: 401 });
143
+ }
144
+ };
145
+
146
+ export class ChatAgent extends AIChatAgent<Env> {
147
+ protected fastModel = openai("gpt-4o-mini");
148
+
149
+ @guard(requireToken)
150
+ async onChatMessage(onFinish, options) {
151
+ const params = await this.buildLLMParams({
152
+ options,
153
+ onFinish,
154
+ model: openai("gpt-4o"),
155
+ system: "You are a helpful assistant.",
156
+ });
157
+ return streamText(params).toUIMessageStreamResponse();
158
+ }
159
+ }
160
+ ```
161
+
124
162
  ### `fastModel` property
125
163
 
126
164
  Override `fastModel` on your subclass to enable automatic compaction and future background conversation summarization:
@@ -503,17 +541,19 @@ If `userId` is not set on the request body, the upsert is skipped and a `console
503
541
 
504
542
  ### Functions
505
543
 
506
- | Export | Signature | Description |
507
- | ---------------- | -------------------------------------- | -------------------------------------------------------------------- |
508
- | `buildLLMParams` | `async (config) => Promise<LLMParams>` | Builds the full parameter object for `streamText` or `generateText`. |
544
+ | Export | Signature | Description |
545
+ | ---------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------ |
546
+ | `guard` | `(guardFn: GuardFn)` | Method decorator: runs `guardFn` with `options.body`; a returned `Response` short-circuits the method. |
547
+ | `buildLLMParams` | `async (config) => Promise<LLMParams>` | Builds the full parameter object for `streamText` or `generateText`. |
509
548
 
510
549
  ### Types
511
550
 
512
- | Export | Description |
513
- | ---------------------- | ------------------------------------------------------------------------------- |
514
- | `Skill` | A named group of tools with optional guidance. |
515
- | `AgentContext<TBody>` | Request body type merged with `log`. Use as the type of `experimental_context`. |
516
- | `BuildLLMParamsConfig` | Config type for the standalone `buildLLMParams` function. |
551
+ | Export | Description |
552
+ | ---------------------- | ---------------------------------------------------------------------------------------------------------------- |
553
+ | `GuardFn` | `(body) => Response \| void \| Promise<...>`. Receives chat request `body`; return `Response` to block the turn. |
554
+ | `Skill` | A named group of tools with optional guidance. |
555
+ | `AgentContext<TBody>` | Request body type merged with `log`. Use as the type of `experimental_context`. |
556
+ | `BuildLLMParamsConfig` | Config type for the standalone `buildLLMParams` function. |
517
557
 
518
558
  ---
519
559
 
package/dist/index.d.mts CHANGED
@@ -172,13 +172,46 @@ declare abstract class AIChatAgent<Env extends Cloudflare.Env = Cloudflare.Env>
172
172
  }): Promise<void>;
173
173
  }
174
174
  //#endregion
175
- //#region src/features/compaction/index.d.ts
175
+ //#region src/decorators/guard.d.ts
176
176
  /**
177
- * Number of recent messages to keep verbatim when compaction runs.
178
- * Older messages beyond this count are summarised into a single system message.
179
- * Used as the default when `maxMessagesBeforeCompaction` is not provided to `buildLLMParams`.
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`).
180
+ *
181
+ * Return a {@link Response} to short-circuit and skip the decorated method.
182
+ * Return nothing (or `undefined`) to allow the method to run.
183
+ *
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.
186
+ */
187
+ type GuardFn = (body: Record<string, unknown> | undefined) => Response | void | Promise<Response | void>;
188
+ /**
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.
192
+ *
193
+ * Intended for `onChatMessage(onFinish, options?)` on subclasses of
194
+ * `AIChatAgent`; `options` is read as `{ body?: Record<string, unknown> }`.
195
+ *
196
+ * @param guardFn - Called with `options?.body` before the method body.
197
+ *
198
+ * @example
199
+ * ```ts
200
+ * const requireToken: GuardFn = async (body) => {
201
+ * if (!await isValidToken(body?.token)) {
202
+ * return new Response("Unauthorized", { status: 401 });
203
+ * }
204
+ * };
205
+ *
206
+ * class MyAgent extends AIChatAgent<Env> {
207
+ * @guard(requireToken)
208
+ * async onChatMessage(onFinish, options) {
209
+ * // ...
210
+ * }
211
+ * }
212
+ * ```
180
213
  */
181
- declare const DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION = 15;
214
+ declare function guard(guardFn: GuardFn): (target: Function, _context: ClassMethodDecoratorContext) => (this: unknown, ...args: unknown[]) => Promise<unknown>;
182
215
  //#endregion
183
216
  //#region src/types.d.ts
184
217
  /**
@@ -196,4 +229,4 @@ type AgentContext<TBody = Record<string, unknown>> = TBody & {
196
229
  log: (message: string, payload?: Record<string, unknown>) => void | Promise<void>;
197
230
  };
198
231
  //#endregion
199
- export { AIChatAgent, type AgentContext, type BuildLLMParamsConfig, DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION, type Skill, buildLLMParams };
232
+ export { AIChatAgent, type AgentContext, type BuildLLMParamsConfig, type GuardFn, type Skill, buildLLMParams, guard };
package/dist/index.mjs CHANGED
@@ -174,14 +174,6 @@ function filterEphemeralMessages(messages) {
174
174
  }];
175
175
  });
176
176
  }
177
- //#endregion
178
- //#region src/features/compaction/index.ts
179
- /**
180
- * Number of recent messages to keep verbatim when compaction runs.
181
- * Older messages beyond this count are summarised into a single system message.
182
- * Used as the default when `maxMessagesBeforeCompaction` is not provided to `buildLLMParams`.
183
- */
184
- const DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION = 15;
185
177
  const TOOL_RESULT_PREVIEW_CHARS = 200;
186
178
  const SUMMARY_MAX_TOKENS = 4e3;
187
179
  /**
@@ -3819,7 +3811,7 @@ var AIChatAgent = class extends AIChatAgent$1 {
3819
3811
  const db = this.env.AGENT_DB;
3820
3812
  if (!db) return null;
3821
3813
  if (!this._userId) {
3822
- console.error("[AIChatAgent] D1 write skipped: userId not set on request body");
3814
+ console.error("[AIChatAgent] Logging & conversation tracking skipped: userId not set on request body");
3823
3815
  return null;
3824
3816
  }
3825
3817
  return {
@@ -3957,4 +3949,42 @@ var AIChatAgent = class extends AIChatAgent$1 {
3957
3949
  }
3958
3950
  };
3959
3951
  //#endregion
3960
- export { AIChatAgent, DEFAULT_MAX_MESSAGES_BEFORE_COMPACTION, buildLLMParams };
3952
+ //#region src/decorators/guard.ts
3953
+ /**
3954
+ * Method decorator (TypeScript 5+ stage-3) that runs `guardFn` with the second
3955
+ * argument's `body` (the chat request body). If `guardFn` returns a
3956
+ * {@link Response}, that value is returned and the original method is not called.
3957
+ *
3958
+ * Intended for `onChatMessage(onFinish, options?)` on subclasses of
3959
+ * `AIChatAgent`; `options` is read as `{ body?: Record<string, unknown> }`.
3960
+ *
3961
+ * @param guardFn - Called with `options?.body` before the method body.
3962
+ *
3963
+ * @example
3964
+ * ```ts
3965
+ * const requireToken: GuardFn = async (body) => {
3966
+ * if (!await isValidToken(body?.token)) {
3967
+ * return new Response("Unauthorized", { status: 401 });
3968
+ * }
3969
+ * };
3970
+ *
3971
+ * class MyAgent extends AIChatAgent<Env> {
3972
+ * @guard(requireToken)
3973
+ * async onChatMessage(onFinish, options) {
3974
+ * // ...
3975
+ * }
3976
+ * }
3977
+ * ```
3978
+ */
3979
+ function guard(guardFn) {
3980
+ return function(target, _context) {
3981
+ return async function(...args) {
3982
+ const options = args[1];
3983
+ const result = await guardFn(options?.body);
3984
+ if (result instanceof Response) return result;
3985
+ return target.apply(this, args);
3986
+ };
3987
+ };
3988
+ }
3989
+ //#endregion
3990
+ export { AIChatAgent, buildLLMParams, guard };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@economic/agents",
3
- "version": "0.0.1-alpha.18",
3
+ "version": "0.0.1-alpha.19",
4
4
  "description": "A starter for creating a TypeScript package.",
5
5
  "homepage": "https://github.com/author/library#readme",
6
6
  "bugs": {