Skip to main content
An agent is a class that extends the abstract Chat base. You describe what the agent is — its model, system prompts, tools, and middleware — and the base class owns how those pieces are wired into the agent loop. You never call the model directly.

The four getters

Every agent implements exactly four abstract getters:
import { Chat, decorator } from "@ooneex/ai";
import type { AiMiddlewareClassType, AiToolClassType } from "@ooneex/ai";

@decorator.chat()
class ResearchChat extends Chat {
  /** OpenRouter `provider/model` identifier. */
  public getModel = (): string => "anthropic/claude-sonnet-4.5";

  /** System prompts prepended to every conversation. */
  public getSystemPrompts = (): string[] => [
    "You are a research assistant.",
    "Always cite your sources.",
  ];

  /** Tool classes the model may call. */
  public getTools = (): AiToolClassType[] => [];

  /** Middleware classes applied to every run. */
  public getMiddlewares = (): AiMiddlewareClassType[] => [];
}
GetterReturnsPurpose
getModelstringOpenRouter model in provider/model form.
getSystemPromptsstring[]Base prompts, prepended before any per-request prompts.
getToolsAiToolClassType[]Tool classes available for function calling.
getMiddlewaresAiMiddlewareClassType[]Middleware classes applied to every run.
Tools and middleware are returned as class references, not instances. The base class resolves each one from the DI container on demand, so any constructor dependencies are injected automatically.
Return empty arrays for getTools and getMiddlewares when you don’t need them — both getters are required even when the agent is a plain chat.

Registering with the container

The decorator.chat() decorator registers the agent class with the container so it can be resolved and have its own dependencies injected:
import { container } from "@ooneex/container";

const chat = container.get(ResearchChat);
const answer = await chat.run<string>({ prompt: "What is RAG?" });
By default the agent is registered as a singleton — one shared instance. Pass a scope to change that:
import { EContainerScope } from "@ooneex/container";

@decorator.chat(EContainerScope.Transient)
class PerCallChat extends Chat {
  // ...
}
ScopeBehavior
Singleton (default)One instance reused everywhere.
RequestOne instance per request scope.
TransientA fresh instance every time it is resolved.

Injecting dependencies

Because the agent is container-managed, you can inject services into its constructor and use them inside the getters — for example to read prompts from configuration or to build the tool list dynamically:
import { inject } from "@ooneex/container";
import { AppEnv } from "@ooneex/app-env";

@decorator.chat()
class ConfiguredChat extends Chat {
  public constructor(@inject(AppEnv) private readonly env: AppEnv) {
    super();
  }

  public getModel = (): string => "anthropic/claude-sonnet-4.5";
  public getSystemPrompts = (): string[] => [`Environment: ${this.env.APP_ENV}`];
  public getTools = (): AiToolClassType[] => [];
  public getMiddlewares = (): AiMiddlewareClassType[] => [];
}

Inspecting an agent

The base class exposes read-only getters that mirror what was configured — handy for tests, logging, or building admin views:
chat.getModel();          // "anthropic/claude-sonnet-4.5"
chat.getSystemPrompts();  // ["You are a research assistant.", ...]
chat.getTools();          // [ ...tool classes ]
chat.getMiddlewares();    // [ ...middleware classes ]
Next, learn how to run the agent and pass per-request input.