@economic/agents
v2.15.0
Published
A starter for creating a TypeScript package.
Keywords
Readme
@economic/agents
Our agents SDK for building AI agents on Cloudflare Workers. Each agent is a Durable Object running an LLM loop — model, system prompt, tools, skills, auth, telemetry — on @cloudflare/think and the ai SDK.
React client: @economic/agents-react.
The three classes
Agent— the core. Runs the agent loop and keeps message history. Drive it over a WebSocket or programmatically (schedule, alarm, RPC). Most agents stop here.ChatAgent—Agentplus chat features: model-only compaction that preserves the client transcript, and message feedback.Assistant— per-user shell overChatAgent: create/list/delete chats, titles, summaries, retention.
Agent ← LLM + tools + skills (most agents stop here)
└─ ChatAgent ← one persistent chat
└─ Assistant ← one user, many chatsInstall
npm install @economic/agents @cloudflare/think agents
npm install -D wrangler vite @cloudflare/vite-plugin @cloudflare/worker-bundler@economic/agents provides the agent runtime. Worker apps install the Cloudflare/Agents host packages they import directly, including the Vite plugin stack used by native skills.
Providers
@economic/agents/providers ships two pre-configured AI SDK providers that route through Cloudflare AI Gateway to Google Vertex AI and Google AI Studio. Both providers require @ai-sdk/anthropic and @ai-sdk/google to be installed.
Anthropic via Vertex AI
Routes Claude models through Cloudflare AI Gateway → Google Vertex AI.
import { createAnthropicVertexProvider } from "@economic/agents/providers";
const anthropic = createAnthropicVertexProvider({
cloudflareAccountId: env.CLOUDFLARE_ACCOUNT_ID,
cloudflareAiGatewayId: env.AI_GATEWAY_ID,
cloudflareApiToken: env.CLOUDFLARE_API_TOKEN,
googleCloudProjectId: env.GOOGLE_CLOUD_PROJECT_ID,
location: "europe-west1", // optional, defaults to "europe-west1"
});
getModel() {
return anthropic("claude-3-7-sonnet-20250219");
}Gemini via Vertex AI
Routes Gemini models through Cloudflare AI Gateway → Google Vertex AI.
import { createGeminiProvider } from "@economic/agents/providers";
const gemini = createGeminiProvider({
cloudflareAccountId: env.CLOUDFLARE_ACCOUNT_ID,
cloudflareAiGatewayId: env.AI_GATEWAY_ID,
cloudflareApiToken: env.CLOUDFLARE_API_TOKEN,
googleCloudProjectId: env.GOOGLE_CLOUD_PROJECT_ID,
location: "europe-west1", // optional, defaults to "europe-west1"
});
getModel() {
return gemini("gemini-2.0-flash");
}Both together
createAiGatewayVertexProviders returns both providers from a single options object:
import { createAiGatewayVertexProviders } from "@economic/agents/providers";
const { anthropic, gemini } = createAiGatewayVertexProviders({
cloudflareAccountId: env.CLOUDFLARE_ACCOUNT_ID,
cloudflareAiGatewayId: env.AI_GATEWAY_ID,
cloudflareApiToken: env.CLOUDFLARE_API_TOKEN,
googleCloudProjectId: env.GOOGLE_CLOUD_PROJECT_ID,
});When these providers are used inside an SDK agent, the SDK automatically adds
the entry-point agent class as agentId in cf-aig-metadata. This includes
requests made by child chat/facet agents and allows a shared gateway's usage to
be grouped by agent without application configuration or duplicated analytics.
Quick start: an agent
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:
import { openai } from "@ai-sdk/openai";
import { callable } from "agents";
import { Agent, tool, type ToolSet } from "@economic/agents";
import { z } from "zod";
export class SupportAgent extends Agent {
getModel() {
return openai("gpt-4o");
}
getSystemPrompt() {
return "You help customers with their orders. Be concise.";
}
getTools(): ToolSet {
return {
get_order: tool({
description: "Look up an order by id",
inputSchema: z.object({ orderId: z.string() }),
execute: async ({ orderId }) => fetchOrder(orderId),
}),
};
}
@callable()
async checkOrder(orderId: string) {
await this.saveMessages([
{
id: crypto.randomUUID(),
role: "user",
parts: [{ type: "text", text: `What's the status of order ${orderId}?` }],
},
]);
}
}Route requests and export the class:
import { routeAgentRequest } from "@economic/agents";
export { SupportAgent } from "./agent";
export default {
async fetch(request: Request, env: Env): Promise<Response> {
return (await routeAgentRequest(request, env)) ?? new Response("Not found", { status: 404 });
},
};// wrangler.jsonc — binding name must match the class name
{
"compatibility_date": "2026-04-16",
"compatibility_flags": ["nodejs_compat", "experimental"],
"durable_objects": {
"bindings": [{ "name": "SupportAgent", "class_name": "SupportAgent" }],
},
"migrations": [{ "tag": "v1", "new_sqlite_classes": ["SupportAgent"] }],
// see "Bindings"
"d1_databases": [
{
"binding": "AGENTS_DB",
"database_name": "agents",
"database_id": "agents",
"migrations_dir": "node_modules/@economic/agents/schema",
},
],
"r2_buckets": [{ "binding": "AGENTS_AUDIT_LOGS", "bucket_name": "agents-audit-logs" }],
"analytics_engine_datasets": [{ "binding": "AGENTS_ANALYTICS", "dataset": "agents-analytics" }],
}Run wrangler types for a typed Env.
Calling it from the client
Connect with useAgent and invoke any @callable method with agent.call:
const agent = useAgent({
host: "localhost:8787",
agentName: "SupportAgent",
name: `${userId}:support`,
});
await agent.call("checkOrder", ["1234"]);You can also drive turns server-side — call saveMessages from a schedule or alarm.
A chat agent
For a chat UI, extend ChatAgent instead of Agent. It adds model-only compaction (the stored/client transcript remains unchanged) and message feedback, and connects to a browser with useChat — no Assistant required. The DO name is the chat; address one per user, ticket, or whatever fits.
import { openai } from "@ai-sdk/openai";
import { ChatAgent } from "@economic/agents";
import { weatherSkill } from "./skills/weather";
export class MyChatAgent extends ChatAgent {
getModel() {
return openai("gpt-4o");
}
getSystemPrompt() {
return "You are a helpful assistant.";
}
getSkills() {
return [weatherSkill];
}
}const { chat } = useChat({
host: "localhost:8787",
agentName: "MyChatAgent",
name: `${userId}:weather`,
});What ChatAgent adds over Agent:
Compaction — past 100,000 tokens, older messages are summarised (with
getModel()) while recent ones are kept verbatim. Storage keeps the full history.Message feedback — thumbs up/down with an optional comment, in the chat's SQLite (
assistant_messages_feedback, created automatically):| Method | Description | | ---------------------------------------------------- | ------------------------------------------------------------ | |
submitMessageFeedback(messageId, rating, comment?)|ratingis1(up) or-1(down). Upserts on the message. | |getMessageFeedback()| All feedback for the chat, keyed by message id. |Surfaced by the client as
chat.submitMessageFeedback/chat.getMessageFeedback.
Many chats per user: Assistant
When one user needs a list of chats — a sidebar, titles, summaries — add an Assistant. It owns the chat list and routes to a ChatAgent per chat.
import { openai } from "@ai-sdk/openai";
import { Assistant } from "@economic/agents";
import { MyChatAgent } from "./chat-agent";
export class MyAssistant extends Assistant {
protected agent = MyChatAgent;
protected fastModel = openai("gpt-4o-mini"); // titles and summaries
}Bind both classes (binding name = class name) with a new_sqlite_classes migration each. On the client, use useAssistant — it manages the chat list and connects to the active chat:
const { status, chats, assistant, chat } = useAssistant({
host: "localhost:8787",
agentName: "MyAssistant",
name: userId, // the Assistant is keyed by user
});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.
| Method | Returns | Description |
| ---------------- | -------- | ------------------------------------------- |
| createChat() | string | Creates a chat facet and returns its id. |
| getChats() | Chat[] | Started chats, most recently updated first. |
| deleteChat(id) | void | Deletes a chat facet and its record. |
| renameChat(id) | void | Renames a registered chat. |
- Titles and summaries — generated with
fastModelafter each turn (on the first turn, then refreshed as the chat grows). Written in the language of the conversation, following the user's most recent messages when a chat mixes languages. - 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.
- Retention — inactive chats are deleted after 90 days, via a Durable Object alarm.
Bindings
Checked in Agent.onStart when a connection opens:
| Binding | Type | Required | Notes |
| ------------------- | ---------------- | -------- | ------------------------------------------------------------------------- |
| AGENTS_AUDIT_LOGS | R2 | Yes | Connection rejected if missing. One audit log per turn. |
| AGENTS_ANALYTICS | Analytics Engine | No | Per-turn/per-tool analytics. Warns if missing. |
| SKILLS_BUCKET | R2 | No | Source for remote skills. |
| LOADER | Worker Loader | No | Enables native skill scripts when bound. |
Tools
tool() wraps an ai SDK tool with an optional authorize(ctx). Return always-on tools from getTools():
import { tool, type ToolSet } from "@economic/agents";
import { z } from "zod";
getTools(): ToolSet {
return {
search_web: tool({
description: "Search the web",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => search(query),
authorize: (ctx) => ctx._userContext?.canSearch !== false,
}),
};
}authorize returning false hides the tool for that request.
Use approval when an ordinary tool needs the AI SDK's basic approval gate. The SDK translates it to the upstream needsApproval option; needsApproval remains supported for backwards compatibility, but do not supply both.
const export_report = tool({
description: "Export a report",
inputSchema: z.object({ reportId: z.string() }),
approval: true,
execute: async ({ reportId }) => exportReport(reportId),
});Tool context
The tool context is the second argument to execute — experimental_context:
import { tool, type ToolContext } from "@economic/agents";
import { z } from "zod";
interface RequestBody {
tokens: Record<string, string>;
}
const call_api = tool({
description: "Call the API for the user",
inputSchema: z.object({ path: z.string() }),
execute: async ({ path }, { experimental_context }) => {
const ctx = experimental_context as ToolContext<RequestBody>;
return fetchWithAuth(ctx.tokens, path);
},
});ToolContext<RequestContext, UserContext> is RequestContext & { _userContext?: UserContext }. Request context comes from the client (toolContext in the hooks); _userContext comes from getUserContext when using JWT Authentication (via getJwtAuthConfig).
Actions
Use action() instead of tool() for operations with side effects. Actions use Cloudflare Think's durable action ledger for idempotent execution, settled-result replay, pending-action handling, recovery, timeouts, cancellation, and structured errors.
The SDK enables approval by default. approval may be true, false, or a predicate; approvalSummary and approvalRisk provide the metadata shown by an approval UI.
import { action, skill, type ToolContext } from "@economic/agents";
import { z } from "zod";
interface RequestContext extends Record<string, unknown> {
tokens: Record<string, string>;
}
const deleteInput = z.object({ invoiceId: z.string() });
const deleteInvoice = action<ToolContext<RequestContext>, typeof deleteInput>({
description: "Delete a draft invoice",
inputSchema: deleteInput,
// This identifies the business operation across retries. Do not use a
// timestamp, random value, or request id.
idempotencyKey: ({ input }) => `invoice:${input.invoiceId}:delete`,
approvalSummary: "Delete draft invoice",
approvalRisk: "high",
execute: async (input, ctx) => {
const response = await fetch(`/api/invoices/${input.invoiceId}`, {
method: "DELETE",
headers: {
Authorization: ctx.experimental_context.tokens.Authorization ?? "",
// Think uses this key for its ledger. Forward it when the downstream
// API also supports idempotency.
"Idempotency-Key": ctx.idempotencyKey,
},
signal: ctx.signal,
});
return { deleted: response.ok };
},
});
export const invoiceSkill = skill({
name: "invoices",
description: "View and manage invoices.",
instructions: "Use delete_draft_invoice only for draft invoices.",
actions: {
delete_draft_invoice: deleteInvoice,
},
});An action receives the same tool execution data plus Cloudflare's native action fields:
experimental_context— the typedToolContextsupplied to ordinary tools.toolCallId,messages, andabortSignal— the ordinary tool execution fields.idempotencyKey— the resolved domain key, or the tool-call fallback when none was declared.requestId,signal,agent,env, andattachReply— native Think action fields.
authorize and onUnauthorized behave exactly as they do on tool(). Native action options such as permissions, kind, timeoutMs, approval, approvalSummary, and approvalRisk pass through unchanged.
Define an explicit idempotencyKey for actions that must deduplicate across recovery retries. Without one, Think falls back to toolCallId, which only deduplicates that particular tool call. The action ledger does not make an external API idempotent: forward ctx.idempotencyKey to the API, adapting its format if required. If one action performs several writes, derive a stable child key for each write rather than reusing one downstream key.
Cloudflare currently marks Think Actions as experimental, so this SDK surface may evolve with the upstream API.
Skills
The SDK supports two skill styles:
- Code-defined skills with the local
skill()helper. - Native Agent Skills loaded from
SKILL.mdfolders viaagents:skillsor remote sources.
Use skills when a capability should be loaded on demand instead of living in the always-on system prompt.
Code-defined skills
A code-defined skill bundles markdown instructions with optional tools and actions. Only description sits in the system prompt; the model loads the instructions, tools, and actions on demand.
import { skill, tool } from "@economic/agents";
import { z } from "zod";
export const weatherSkill = skill({
name: "weather",
command: "weather",
description: "Look up current weather and forecasts. Use for any weather question.",
instructions: `
# Weather
## When to use
Use this skill whenever the user asks about current conditions or a forecast.
## Workflow
1. Resolve the location to coordinates if needed.
2. Call \`get_forecast\` with the coordinates.
3. Summarise the result; never invent values.
`,
tools: {
get_forecast: tool({
description: "Get the forecast for a set of coordinates",
inputSchema: z.object({ lat: z.number(), lon: z.number() }),
execute: async ({ lat, lon }) => fetchForecast(lat, lon),
}),
},
});Return skills from getSkills(). Add authorize(ctx) to gate a skill (and its tools). Denied skills are hidden from the catalog and activate_skill by default. Set onUnauthorized to a reason string, or a function that returns one, to expose a denied skill with user-facing guidance. A function can return undefined to keep it hidden. skill() throws if description or instructions is missing.
Set command to make a code-defined skill directly invocable from chat. For example,
/weather in Copenhagen loads the weather instructions and enables its tools before the
first model step; the model still interprets in Copenhagen and chooses the appropriate
tool. Commands use lower-case letters, numbers, and hyphens, without the leading /.
ChatAgent also exposes an authenticated GET getInvocableSkills action for command
pickers. It returns the commands available to the current user as
{ command, name, description }[]. The existing skill description is used for both the
model catalog and the human-facing picker.
Native Agent Skills
Native Agent Skills are folders containing a SKILL.md file, with optional references and scripts:
src/skills/top-customers-by-revenue/
├── SKILL.md
└── scripts/
└── top-customers.py
src/skills/vat-code-review/
├── SKILL.md
├── references/
│ └── accounting-rules.md
└── scripts/
└── review-invoice-vat.tsLoad bundled skills with the Agents Vite plugin:
import bundledSkills from "agents:skills";
getSkills() {
return [bundledSkills];
}SKILL.md should describe the business workflow, not the implementation mechanism:
---
name: top-customers-by-revenue
description: Find the top customers by revenue from customer and sales data.
---
# Top Customers By Revenue
1. Call `request` with `{ "endpoint": "customers" }`.
2. Call `request` with `{ "endpoint": "sales" }`.
3. Run `scripts/top-customers.py` with the returned customer and sales arrays.
4. Summarize the top customers by revenue.Skill scripts
Skill scripts are enabled when a Worker Loader binding named LOADER is present:
{
"worker_loaders": [{ "binding": "LOADER" }],
}Agent automatically creates a script runner when env.LOADER is bound. Without the binding, script execution is unavailable.
TypeScript scripts use the function-style contract:
import type { SkillRunContext } from "@cloudflare/think";
export default async function run(input: unknown, ctx: SkillRunContext) {
const rules = ctx.files["references/accounting-rules.md"];
return {
ok: true,
rulesLoaded: Boolean(rules),
};
}Python scripts use the path-based Dynamic Workers contract:
import json
from pathlib import Path
input_data = json.loads(Path("/input.json").read_text())
print(json.dumps({
"ok": True,
"result": input_data,
}))Use tools for I/O and scripts for deterministic processing. For example, a skill can call a shared request tool to fetch customers and sales, then pass the returned arrays into a Python script that ranks customers by revenue.
Remote skills
Store skills in R2 to edit without redeploying and share complex skills across agents. Add an R2 skill source from getSkills():
import { skills } from "@cloudflare/think";
getSkills() {
return [
skills.r2(this.env.SKILLS_BUCKET, {
skills: ["top-customers-by-revenue", "vat-code-review"],
}),
];
}Remote skills use the same folder shape as bundled skills under the configured R2 prefix:
skills/top-customers-by-revenue/
├── SKILL.md
└── scripts/
└── top-customers.pySource order matters: if two sources define the same skill name, the first source wins.
Authentication
JWT
Override the static getJwtAuthConfig(env) to verify a JWT on connect (static so an Assistant can check before routing). Return undefined to skip.
export class SupportAgent extends Agent {
static getJwtAuthConfig(env: Cloudflare.Env) {
return {
allowedIssuers: [env.IDENTITY_ENDPOINT], // strings or RegExp
audience: "my-api",
requiredScopes: ["support.read"],
getClaims: (payload) => ({
userGuid: payload.user_guid as string,
agreementNumber: payload.agreement_number as number,
}),
getActorId: (claims) => `${claims.agreementNumber}_${claims.userGuid}`,
};
}
// ...
}getActorId derives the authenticated actor from verified claims. It must exactly match the
actor that owns the requested Durable Object: the Assistant name, the root parent of a chat
facet, or the prefix in a standalone ${actorId}:${chatId} name. A valid token belonging to
another actor is rejected with 4003 before the connection is established.
On failure the socket closes (4001 unauthorized, 4003 forbidden) and status becomes "unauthorized". The token is read from Authorization: Bearer …, then the Sec-WebSocket-Protocol: bearer, <token> header.
User context
Implement getUserContext(jwtToken) to load per-user data after auth. It's exposed as ctx._userContext in getModel, getSystemPrompt, tools, and skill authorize. Runs only when JWT auth is configured.
protected async getUserContext(jwtToken: string) {
const profile = await fetchProfile(jwtToken);
return { role: profile.role, tokens: profile.tokens };
}If getUserContext throws, the socket closes with 1011 — or, when the thrown Error carries a numeric closeCode in the 4000–4999 range, with that code and an "unauthorized" status frame:
const error = new Error("No Eva access");
(error as Error & { closeCode: number }).closeCode = 4003;
throw error;Token exchange
For agents whose clients connect with a down-scoped token — one only valid for the client ↔ agent hop — override the static getTokenExchangeConfig(env) to exchange it (RFC 8693) at the identity server for a token issued to the agent's own OAuth client. Requires getJwtAuthConfig.
export class SupportAgent extends Agent {
static getTokenExchangeConfig(env: Cloudflare.Env) {
return {
tokenUrl: `${env.IDENTITY_ENDPOINT}/connect/token`,
clientId: env.OAUTH_CLIENT_ID,
clientSecret: env.OAUTH_CLIENT_SECRET, // string or Secrets Store binding
scopes: "api.read api.write", // always sent explicitly
tokenHeader: "X-EconomicToken", // default
};
}
}When configured:
- The verified connect token is exchanged on every connect; the exchanged token is what
getUserContextreceives and what tools see asctx.tokens[tokenHeader]each turn. Client-sent request-body tokens for that key are ignored — tool credentials always derive from the verified connection identity, so clients should stop sending them. - Renewal is connection-based. The client token rides in the WebSocket protocols, so presenting a renewed token always means a reconnect, which re-verifies and re-exchanges. Keep the
authTokenpassed to the hooks current (refresh it before expiry) and the socket rotates silently — theunauthorizedclose (4001) then only happens when the session is genuinely dead (logout, revocation). - The exchanged token expires on the identity server's clock (with a 30s margin). A turn arriving after expiry closes the connection
4001to force a reconnect; a client that refreshes its token on time never hits this. - Exchange failures at connect:
invalid_grant(dead session token) → status"unauthorized"+ close4001; anything else (identity server down, misconfigured client) → status"error"+ close1011. Partial configuration is the app's concern — throw fromgetTokenExchangeConfigif you consider it a deployment fault, or returnundefinedto disable the exchange.
Observability
AI telemetry is emitted per turn from the ai SDK's OpenTelemetry spans:
- Audit logs → one JSON object per turn in
AGENTS_AUDIT_LOGS(model, actor, IP, prompt, response, tool calls). - Analytics → typed per-turn and per-tool events, written to
AGENTS_ANALYTICSusing the existing Analytics Engine schema. - Tracing → completed turns and message feedback, handed to the handlers you return from
getTracingHandlers().
Override getTracingHandlers() to send turns and message feedback to a tracing or analytics
service. Each handler's handle receives a TracingEvent, a turn or a feedback event told
apart by type. Both carry their OpenTelemetry spans, so a handler that only forwards spans
doesn't need to check:
import type { TracingHandler } from "@economic/agents";
const tracing: TracingHandler = {
name: "tracing-service",
handle: (event) => sendToTracingService(event.spans),
};
getTracingHandlers() {
return [tracing];
}A turn event comes when a turn ends: who it was for, its request context (as tools see it) and
spans, the ai SDK's OpenTelemetry spans under the turn's ai.streamText span. A turn
continuing an earlier one, e.g. after a tool approval, sits under that turn in the same trace. A
feedback event comes when a user rates an assistant message: messageId, rating, comment and
spans, the rating as a gen_ai.evaluation.* span under the turn that first answered the message
(none when the rating was removed).
Hand the spans to any OpenTelemetry exporter or span processor as they are; examples/assistant
has a complete handler.
Spans and context can hold prompts, tool results and credentials, so pick what you send. Handlers
run in the background; a failure is logged as agent.tracing.handler.failed and never affects the
chat.
Separately, the SDK writes structured operational logs for completed/failed LLM and tool calls, turn duration, framework failures, and selected recovery warnings. Prompt, response, tool argument, and tool result content is excluded. These records use the stable fields source, event, level, timestamp, agentName, agentClass, durableObjectName, and attributes, while preserving Cloudflare's diagnostic events.
To send Worker logs through an existing Logpush job, enable "logpush": true in the Worker configuration; this does not create the Logpush job. Worker CPU, memory, and resource-limit metrics come from Cloudflare's platform telemetry rather than these SDK logs.
API reference
Imported from @economic/agents unless noted.
Classes
| Export | Description |
| ----------- | -------------------------------------------------------- |
| Agent | Core agent base. Implement getModel/getSystemPrompt. |
| ChatAgent | Agent + model-only compaction and message feedback. |
| Assistant | Per-user manager of ChatAgent chats. |
Helpers and types
| Export | Description |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- |
| tool(def) / Tool / ToolSet | Define a tool with optional approval, authorize, and onUnauthorized. |
| action(def) / Action / RegisteredAction / ActionContext | Define a ledger-backed action with approval and idempotency support. |
| skill(def) / Skill | Define a skill with optional tools, actions, authorize, and onUnauthorized. |
| UnauthorizedPolicy | Shared reason or context-dependent reason for denied tools, actions, and skills. |
| ToolContext | RequestContext & { _userContext?: UserContext }. |
| AgentEnv | The bindings the runtime expects. |
| AgentConnectionState / AgentConnectionStatus / AgentConnectionType | Connection state shared with the client. |
| TracingHandler / TracingEvent | Consumer-defined destinations for completed turns and message feedback. |
Members
| Member | On | Description |
| ---------------------------------------------- | ----------- | ---------------------------------------------------------------------- |
| getModel(ctx?) | Agent | Required. Model for inference. |
| getSystemPrompt(ctx?) | Agent | Required. System prompt. |
| getTools() | Agent | Always-on tools (default {}). |
| getSkills() | Agent | Local skills (default []). |
| getTracingHandlers() | Agent | Destinations for completed turns and message feedback (default: []). |
| getAnalyticsAttributes() | Agent | Additional dimensions included on every analytics event. |
| static getJwtAuthConfig(env) | Agent | Optional JWT verification and actor binding. |
| getUserContext(jwtToken) | Agent | Optional per-user context after auth. |
| submitMessageFeedback / getMessageFeedback | ChatAgent | Callable message feedback. |
| agent | Assistant | Required. Your ChatAgent subclass. |
| fastModel | Assistant | Required. Cheap model for titles/summaries. |
| createChat / getChats / deleteChat | Assistant | Callable chat management. |
Package root (@economic/agents)
| Export | Description |
| ------------------- | ------------------------------------------------------------------------ |
| routeAgentRequest | Routes a request to the right Durable Object; echoes the WS subprotocol. |
Development
npm install
npm test
npm run build