npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@economic/agents

v2.15.0

Published

A starter for creating a TypeScript package.

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 — Agent plus chat features: model-only compaction that preserves the client transcript, and message feedback.
  • Assistant — per-user shell over ChatAgent: create/list/delete chats, titles, summaries, retention.
Agent            ← LLM + tools + skills (most agents stop here)
 └─ ChatAgent     ← one persistent chat
     └─ Assistant ← one user, many chats

Install

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?) | rating is 1 (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 fastModel after 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 typed ToolContext supplied to ordinary tools.
  • toolCallId, messages, and abortSignal — the ordinary tool execution fields.
  • idempotencyKey — the resolved domain key, or the tool-call fallback when none was declared.
  • requestId, signal, agent, env, and attachReply — 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.md folders via agents:skills or 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.ts

Load 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.py

Source 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 getUserContext receives and what tools see as ctx.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 authToken passed to the hooks current (refresh it before expiry) and the socket rotates silently — the unauthorized close (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 4001 to 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" + close 4001; anything else (identity server down, misconfigured client) → status "error" + close 1011. Partial configuration is the app's concern — throw from getTokenExchangeConfig if you consider it a deployment fault, or return undefined to 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_ANALYTICS using 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