Runner
A runner is a backend adapter that turns an agent's instructions and input history into a stream of events. Apeira ships with two built-in runners: responses() for the OpenAI Responses API, and chat() for Chat Completions. You can also write your own.
responses()
Uses the OpenAI Responses API via @xsai-ext/responses.
import { createAgent } from '@apeira/core'
import { responses } from '@apeira/core/responses'
const agent = createAgent({
instructions: 'You are a concise assistant.',
runner: responses({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.openai.com/v1/',
model: 'gpt-5.5',
}),
})Runner options are passed to @xsai-ext/responses. Apeira supplies the request input, instructions, lifecycle hooks, and step loop. Common options include model, apiKey, baseURL, and temperature.
chat()
Uses the Chat Completions API via @xsai/stream-text.
import { createAgent } from '@apeira/core'
import { chat } from '@apeira/core/chat'
const agent = createAgent({
instructions: 'You are a concise assistant.',
runner: chat({
apiKey: process.env.OPENAI_API_KEY,
baseURL: 'https://api.openai.com/v1/',
model: 'gpt-5.5',
}),
})Runner options are passed to @xsai/stream-text. Apeira supplies the messages, lifecycle hooks, and step loop. Common options include model, apiKey, baseURL, temperature, and stop.
Providers
You can use @xsai-ext/providers to avoid writing apiKey and baseURL manually.
Predefined providers
import { createAgent } from '@apeira/core'
import { chat } from '@apeira/core/chat'
import { openai } from '@xsai-ext/providers'
const agent = createAgent({
runner: chat({
...openai('gpt-5.5'),
}),
})Predefined providers read the API key from environment variables (e.g. process.env.OPENAI_API_KEY), so they only work in Node.js.
Create providers
For runtime-agnostic code or explicit keys, use the create entry:
import { createAgent } from '@apeira/core'
import { responses } from '@apeira/core/responses'
import { createGoogle } from '@xsai-ext/providers/create'
const google = createGoogle('foo-bar-baz')
const agent = createAgent({
runner: responses({
...google('gemini-2.5-flash'),
}),
})The spread object includes apiKey, baseURL, and model, so you can mix in extra options:
const agent = createAgent({
runner: chat({
...openai('gpt-5.5'),
temperature: 0.5,
}),
})Choosing a runner
Check whether your provider supports the Responses API. If it does, use responses(). Otherwise, use chat().
responses()— Responses API. Requires provider support.chat()— Chat Completions API. Works with any OpenAI-compatible endpoint, including local models and most third-party providers.
Multi-step turns
Both built-in runners execute one model step at a time. The agent queue starts another step while the model produces tool calls or input is queued during the current step. A turn finishes only when the latest step has no tool call and its pending input is empty. Use agent.abort() or an AbortSignal to stop a turn explicitly.
Custom runners
A runner is any function matching the Runner interface:
import type { Runner } from '@apeira/core'
const myRunner: Runner = async (context) => {
// Implement one model step:
// - send the request to your backend
// - handle streaming or polling
// - emit text.delta / text.start / text.done
// - respect context.abortSignal
// - return this step's output and tool calls
context.channel.emit('apeira', {
turnId: context.turnId,
type: 'text.start',
})
// ... backend interaction ...
return {
output: [],
toolCalls: [],
}
}A custom runner is responsible only for one model step and its model stream events. Apeira executes the returned toolCalls with an empty message context, appends their outputs, and schedules the next step.
Multi-step and turn lifecycle events such as turn.start, turn.done, turn.failed, and turn.aborted are managed by Apeira's queue.
This is useful when you need to integrate a non-OpenAI backend, add custom preprocessing, or implement a mock runner for testing.
