Skip to main content
The @talosjs/ai component is a multi-provider AI toolkit. It exposes a single, consistent API for text generation, streaming, structured output, and function calling across 300+ models (OpenAI, Anthropic, Google, Groq, Ollama, and more) through OpenRouter. You build three kinds of classes (chats, tools, and middleware), register them with a decorator, and resolve them from the container.

What you get

Switching providers is a matter of changing the provider/model string passed to getModel(); nothing else moves. Chats, tools, and middleware are all dependency-injected classes you compose as you like, with TypeScript types throughout, schema-validated tool inputs, and structured run output. Tool and function calling, streaming events, and the agentic loop are supported directly. For operations, the component gives you structured exceptions, input validation, and lifecycle hooks you can use for auditing and metrics.

How it works

A chat is the unit you run. When you call run() or stream(), the component:
  1. Builds the message list from your system prompts, history, and the new prompt.
  2. Resolves the chat’s tool and middleware classes from the container.
  3. Creates an OpenRouter adapter from getModel().
  4. Runs the agentic loop. The model may call tools, whose results feed back into the conversation.
  5. Returns the final text, a validated structured object, or a stream of events.
The three building blocks:

Environment variables

The core API talks to OpenRouter; the bundled tools read their own provider keys from the app environment. Keys are read lazily, so a missing key fails when the tool is called rather than at startup.
Tools read keys through the injected app environment, so define them in your .env and inject AppEnv into the tool class.

Decorators and usage

@decorator.chat()

Registers a Chat subclass with the container. You implement four getters: the model, the system prompts, the tools, and the middleware.
Run it by resolving it from the container:
The model id is always in provider/model form, for example anthropic/claude-sonnet-4.5, openai/gpt-4.1, or google/gemini-2.5-pro.

@decorator.tool()

Registers a tool the model can call. Implement getName() (snake_case, the name the model sees), getDescription() (tells the model when to call it), getInputSchema() (validated before your handler runs), and handler().
Add it to a chat’s getTools():
A set of ready-made tools also ships with the component (Bright Data, Exa, Firecrawl, Wikipedia, PubMed, Linear search/create/update/delete). Drop their classes into getTools() to use them.

@decorator.middleware()

Registers middleware that observes the run through lifecycle hooks. Implement only the hooks you need.
Hooks fire across the run in order: setuponConfigonStartonBeforeModel → streaming (onChunk) → onToolPhaseCompleteonUsage, then one terminal hook (onFinish, onError, or onAbort). Add the class to a chat’s getMiddlewares().

Running chats

Structured output

Pass a schema and the run returns a validated object instead of free text.

Streaming

Context, history, and sampling

Exceptions

The component throws AiException for AI-specific failures: a tool handler error, an invalid response, a missing key, or a failed run. It carries a machine-readable key, a human-readable message, and a frozen data object.
When you throw from inside a tool, follow the same shape so callers can branch on key:

Working effectively

Keep model selection in getModel() rather than at the call site, so a single edit swaps providers for every caller of that chat. A tool’s getName() and getDescription() are how the model decides when to call it, so write them for the model: be specific about when to call the tool and what it returns. Because getInputSchema() runs before your handler, you can narrow the input type inside the handler without re-validating. Favor single-purpose tools. One tool does one job and the model composes them, which works better than a single multi-mode tool. Cross-cutting concerns (logging, metrics, auditing, per-iteration tweaks) belong in middleware, not inside tools or chats. Pass per-run data through context instead of globals; it reaches every tool and middleware while keeping them stateless. When you throw AiException, keep the key stable so callers can branch on it, and put the variable detail in data. For results a program will consume, an outputSchema beats parsing free text.

CLI commands

Scaffold each building block with its generator. Each command creates the class and a matching test file under the target module, and installs @talosjs/ai if it is missing.
See ai:chat:create, ai:tool:create, and ai:middleware:create for full command references.

Use with Claude and Codex

Each generator ships a matching skill that runs the scaffold and then guides your AI agent through completing the class: the chat’s model, prompts, tools, and middleware; a tool’s description, schema, and handler; or a middleware’s lifecycle hooks. Initialize the skills once for your agent:
Then ask Claude in natural language. It maps the request to the right generator, runs it, and fills in the implementation:
Prompt
For example, the prompt above maps to ai:chat:create --name=Support, then generates the matching ai:tool:create --name=WebSearch and ai:middleware:create --name=Audit classes and wires them into the chat.