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

@roostjs/ai

v0.3.0

Published

> Laravel-ergonomics AI agents for Cloudflare Workers.

Readme

@roostjs/ai

Laravel-ergonomics AI agents for Cloudflare Workers.

@roostjs/ai is the agent framework for the Roost stack. It blends Laravel 13's AI SDK ergonomics with the Cloudflare Agents SDK primitive semantics, delivered as idiomatic TypeScript for the Workers runtime.

Table of Contents

Introduction

@roostjs/ai gives Roost developers the Laravel AI SDK DX on top of Cloudflare Workers — the same mental model whether you're calling Workers AI directly, routing external providers via AI Gateway, running durable agents on Durable Objects, exposing your agents as MCP servers, queueing long-running work, or streaming SSE to a React client.

Every primitive composes. A StatefulAgent decorated with @Stateful({binding}) gets Sessions, Schedule, Workflows, sub-agents, MCP, and HITL support for free.

Installation

bun add @roostjs/ai

Subpaths (opt-in per feature):

import { Agent } from '@roostjs/ai';
import { RAGPipeline } from '@roostjs/ai/rag';
import { Image, Audio, Transcription } from '@roostjs/ai/media';
import { McpClient, createMcpHandler } from '@roostjs/ai/mcp';
import { useAgent } from '@roostjs/ai/client';
import { requireApproval } from '@roostjs/ai/hitl';
import { chargeForTool } from '@roostjs/ai/payments';

Configuration

Register AiServiceProvider in your Roost app:

// config/app.ts
import { AiServiceProvider, Lab } from '@roostjs/ai';

export default {
  providers: [AiServiceProvider],
  ai: {
    binding: 'AI',
    gateway: { accountId: env.CF_ACCOUNT_ID, gatewayId: 'roost-ai' },
    providers: {
      anthropic: { apiKey: env.ANTHROPIC_KEY },
      openai: { apiKey: env.OPENAI_KEY, organization: env.OPENAI_ORG },
      gemini: { apiKey: env.GEMINI_KEY },
    },
    default: [Lab.Anthropic, Lab.OpenAI],
  },
};

Custom Base URLs

Route external providers through AI Gateway for analytics, caching, and rate limiting:

ai: {
  gateway: {
    accountId: 'your-cf-account-id',
    gatewayId: 'your-gateway-id',
  },
}

When configured, external providers (OpenAI, Anthropic, Gemini) are routed through Gateway by default; Workers AI stays direct.

Provider Support

| Provider | Transport | Streaming | Tools | Structured | |------------|--------------------|-----------|-------|------------| | Workers AI | direct binding | ✓ | ✓ | ✓ | | Anthropic | Gateway or native | ✓ | ✓ | ✓ | | OpenAI | Gateway or native | ✓ | ✓ | ✓ | | Gemini | Gateway or native | ✓ | ✓ | ✓ |

Agents

Prompting

import { Agent } from '@roostjs/ai';

class SupportAgent extends Agent {
  instructions() { return 'You are a support bot.'; }
}

const response = await new SupportAgent().prompt('How do I reset my password?');
console.log(response.text);

Conversation Context (Sessions)

For durable conversation state, extend StatefulAgent and apply RemembersConversations:

import { StatefulAgent, RemembersConversations, Stateful } from '@roostjs/ai';

@Stateful({ binding: 'SUPPORT_AGENT' })
class Support extends RemembersConversations(StatefulAgent) {
  instructions() { return 'help'; }
}

Conversations persist in Durable Object storage with tree-structured message history, compaction, and FTS.

Structured Output

class ReportAgent extends Agent implements HasStructuredOutput {
  instructions() { return 'Extract structured data.'; }
  schema(s) {
    return { summary: s.string(), tags: s.array().items(s.string()) };
  }
}

const { data } = await new ReportAgent().prompt('Analyze: ...');
console.log(data.tags);

Attachments

import { Files } from '@roostjs/ai/rag';

const image = await Files.Image.fromUrl('https://example.com/diagram.png');
await new VisualAgent().prompt('Describe this', { attachments: [image] });

Streaming

for await (const event of new Support().stream('hi')) {
  if (event.type === 'text-delta') process.stdout.write(event.text);
}

Vercel AI SDK protocol:

return new Support().stream('hi').usingVercelDataProtocol();

Broadcasting

await new Support().broadcast('daily-digest', 'Hello everyone');

Connects through @roostjs/broadcast to every connected client.

Queueing

const handle = new ReportAgent()
  .queue('Analyze Q3 data')
  .then((response) => store(response.text));

Bridges to @roostjs/queue. Decorate with @Queue('ai'), @MaxRetries(3), @Backoff('exponential') for queue metadata.

Tools

class LookupTool implements Tool {
  name() { return 'lookup'; }
  description() { return 'Look up a user by id'; }
  schema(s) { return { id: s.string() }; }
  async handle(request) { return queryDB(request.get<string>('id')); }
}

class Support extends Agent implements HasTools {
  tools() { return [new LookupTool(), new WebSearch()]; }
}

Middleware

class Support extends Agent implements HasMiddleware {
  middleware() {
    return [
      async (prompt, next) => { logPrompt(prompt); return next(prompt); },
    ];
  }
}

Anonymous Agents

import { agent } from '@roostjs/ai';

const quick = agent({ instructions: 'Be terse.', tools: [new LookupTool()] });
await quick.prompt('hi');

Agent Configuration (decorators)

@Provider([Lab.Anthropic, Lab.OpenAI])
@Model('anthropic/claude-4-opus')
@MaxTokens(4096)
@Temperature(0.3)
@Timeout(30)
class Precise extends Agent { ... }

Provider Options

class Support extends Agent implements HasProviderOptions {
  providerOptions(provider) {
    if (provider === Lab.Anthropic) return { reasoning: { maxTokens: 8192 } };
    return {};
  }
}

Images

import { Image } from '@roostjs/ai/media/image';

const png = await Image.of('a happy dog').square().quality('high').generate();
await Image.of('portrait').store({ bucket: 'R2_IMAGES' });

Audio (TTS)

import { Audio } from '@roostjs/ai/media/audio';

const mp3 = await Audio.of('Hello world').female().voice('warm').generate();

Transcription (STT)

import { Transcription } from '@roostjs/ai/media/transcription';

const { text, segments } = await Transcription.fromStorage('R2_AUDIO', 'call.wav')
  .diarize()
  .generate();

Embeddings

import { EmbeddingPipeline, Str } from '@roostjs/ai/rag';

const vectors = await new EmbeddingPipeline().cache('30d').embed(['hi', 'bye']);
const vec = await Str.toEmbeddings('single doc');

Reranking

import { Reranking } from '@roostjs/ai/rag';

const reranked = await Reranking.of(hits).usingCohere().rerank();

Files

import { Files } from '@roostjs/ai/rag';

const stored = await Files.store(blob, { filename: 'doc.pdf' });
await stored.delete();

Vector Stores

import { Stores } from '@roostjs/ai/rag';

const store = await Stores.create('legal-docs');
await store.add(stored.id, { metadata: { dept: 'legal' } });
await store.remove(stored.id);

Failover

import { FailoverProvider, AnthropicProvider, WorkersAIProvider } from '@roostjs/ai';

const provider = new FailoverProvider([
  new AnthropicProvider({ apiKey }),
  new WorkersAIProvider(env.AI),
]);

Or via decorator: @Provider([Lab.Anthropic, Lab.WorkersAI]).

Testing

import { Agent, Image, Files } from '@roostjs/ai';

Support.fake(['canned reply']);
Image.fake();
Files.fake();

await new Support().prompt('hi');
Support.assertPrompted('hi');
Image.assertNothingGenerated();

Support.restore();
Image.restore();

Structured-output agents auto-generate schema-valid fake data when fake() is called without explicit responses.

Events

import { Events } from '@roostjs/events';
import { PromptingAgent, AgentPrompted, GeneratingImage } from '@roostjs/ai';

Events.listen(PromptingAgent, (e) => console.log('about to prompt', e.prompt));
Events.listen(AgentPrompted, (e) => metrics.record(e.response.usage));

30+ event classes cover every primitive — see src/events.ts and src/advanced-events.ts.

CF-native additions

Primitives mapped from the Cloudflare Agents SDK — no v0.2 equivalent.

Stateful Agents

@Stateful({ binding: 'SUPPORT_AGENT' })
class Support extends StatefulAgent {
  instructions() { return 'help'; }
}

Schedule

@Scheduled('0 9 * * *')
async sendDigest() { ... }

await agent.schedule(60, 'check', payload);

Workflows

@Workflow({ binding: 'REPORT_FLOW' })
async processReport(step, reportId: string) {
  const data = await step.do('fetch', () => this.fetchData(reportId));
  return data;
}

Sub-agents

const summarizer = this.subAgent(SummarizerAgent);
const summary = await summarizer.summarize(doc);
await summarizer.abort();

MCP

import { McpClient, createMcpHandler } from '@roostjs/ai/mcp';

const github = await McpClient.connect({ url: 'https://mcp.github.com' });
class Bug extends Agent {
  async tools() { return [...await github.tools(), new CustomTool()]; }
}

export default createMcpHandler(Bug, { transport: 'streamable-http' });

HITL

import { requireApproval } from '@roostjs/ai/hitl';

const result = await requireApproval(this, 'charge', { amount: 500 });
if (result.status === 'approved') chargeCustomer();

Memory

agent.memory.context.get('tenant');
await agent.memory.shortForm.set('draft', text);
const hits = await agent.memory.knowledge.query({ query: 'policies' });

Payments

import { chargeForTool } from '@roostjs/ai/payments';

const premium = chargeForTool(new ReportTool(), { amount: 100, currency: 'usd' });

Voice / Email / Browser / CodeMode

See src/voice/, src/email/, src/browser/, src/code-mode/ and the corresponding CHANGELOG entries.

Philosophy

v0.3 integrates the semantics of every Cloudflare Agents SDK primitive via Roost-native implementations rather than by inheriting the SDK's base classes. StatefulAgent implements DurableObject directly; sub-agent RPC uses @roostjs/cloudflare's DurableObjectClient; MCP uses @modelcontextprotocol/sdk transports without the SDK's McpAgent base.

This keeps Roost agents consistent with other Roost DO primitives (ChannelDO, RateLimiterDO) and avoids competing lifecycle assumptions. Users migrating from the Cloudflare Agents SDK gain Laravel DX without losing any CF-native power.

See MIGRATION.md for v0.2 → v0.3 upgrade notes.

License

MIT. See LICENSE.