Getting started#
This page installs Relay, runs a first invocation against a deterministic fake runtime, and then swaps in a real runtime without changing the workload. For what each piece is, read Concepts. For every export and option, read the Reference.
Install#
Relay is published to npm as @kontourai/relay. It is an ES module package
("type": "module", import exports only) and requires Node 22 or newer.
npm install @kontourai/relay
Peer dependencies:
@ai-sdk/provider(>=3.0.0) supplies the types used by the@kontourai/relay/ai-sdkentrypoint.@anthropic-ai/sdk(>=0.40.0) is optional. The@kontourai/relay/anthropicentrypoint loads it only when you do not inject your own client. Install it if you use that runtime without a client.
The local harness runtimes (claude-code, codex, opencode) need the
matching CLI installed and already authenticated. Relay runs the executable;
it never installs it or manages its login.
First invocation#
Products depend on the ModelRuntime interface. FakeModelRuntime implements
it with a queue of prepared results, so you can write and test the workload
before choosing a real runtime.
import { FakeModelRuntime } from "@kontourai/relay";
const runtime = new FakeModelRuntime([{
provider: "fixture",
model: "fixture-1",
modelSource: "configured",
outputText: "",
toolCalls: [{ id: "1", name: "submit", input: { value: 42 } }],
usage: { totalTokens: 10 },
latencyMs: 0,
}]);
const result = await runtime.invoke({
messages: [{ role: "user", content: "Return a structured value." }],
tools: [{ name: "submit", inputSchema: { type: "object" } }],
toolChoice: { type: "tool", name: "submit" },
});
console.log(result.toolCalls[0]?.input); // { value: 42 }
Each invoke() takes the next queued result. When the queue is empty, the fake
throws a RUNTIME_FAILURE. It also records every request it received in
runtime.requests, which is useful for asserting what your code sent.
Check a runtime's contract#
checkRuntimeConformance() reads the runtime's declared capabilities, makes
one small invocation, and returns a report instead of throwing. A fake runtime
needs one queued result for that invocation.
import { FakeModelRuntime, checkRuntimeConformance } from "@kontourai/relay";
const fake = new FakeModelRuntime([{
provider: "fixture",
model: "fixture-1",
modelSource: "configured",
outputText: "ok",
toolCalls: [],
usage: {},
latencyMs: 0,
}]);
const report = await checkRuntimeConformance(fake);
if (!report.passed) console.error(report.checks.filter((check) => !check.passed));
The invocation is real. Against a hosted or harness runtime it calls the model
once, with a one-word prompt and maxOutputTokens: 1.
Use a real runtime#
The workload above does not change when the runtime does. Pick one entrypoint:
// A locally authenticated Claude Code CLI.
import { createClaudeCodeRuntime } from "@kontourai/relay/claude-code";
const runtime = createClaudeCodeRuntime({ model: "sonnet" });
// The Anthropic Messages API, or any Anthropic-compatible endpoint.
import { createAnthropicRuntime } from "@kontourai/relay/anthropic";
const runtime = createAnthropicRuntime({
model: "claude-sonnet-4-6",
apiKey: process.env.ANTHROPIC_API_KEY,
});
// One runtime chosen by a "PROFILE:MODEL" string, for example from configuration.
import { createModelRuntimeProfile, parseModelRuntimeProfile } from "@kontourai/relay/runtime-profile";
const runtime = createModelRuntimeProfile({
...parseModelRuntimeProfile("codex:gpt-5"),
cwd: process.cwd(),
});
The harness runtimes accept text-only requests and requests with exactly one
explicitly selected tool. A request with tools and no toolChoice, or with
toolChoice: { type: "auto" }, is refused with INVALID_REQUEST. The
runtime comparison lists what each runtime
supports.
Handle failures#
Invocation failures reject with a ModelInvocationError. Branch on its code
and retryable flag rather than on the message text.
import { ModelInvocationError } from "@kontourai/relay";
try {
await runtime.invoke(request);
} catch (error) {
if (error instanceof ModelInvocationError && error.retryable) {
// Usually RATE_LIMITED or PROVIDER_UNAVAILABLE: try again later or use another runtime.
}
throw error;
}
Relay itself never retries. The Anthropic runtime even turns off the SDK's own retries by default, so the caller is the only component that decides whether to try again. See Failures and retryability.
Record and replay#
Wrap a runtime in RecordingModelRuntime to capture its invocations, then
serve them back with ReplayModelRuntime in tests that must not call a model.
import { RecordingModelRuntime, ReplayModelRuntime } from "@kontourai/relay";
const recording = new RecordingModelRuntime(runtime);
await recording.invoke(request);
const replay = new ReplayModelRuntime(recording.records);
await replay.invoke(request); // the recorded result
Records contain the full request and result, prompt content included. Apply your own content-handling policy before you store them.
Next steps#
- Concepts explains runtimes, capabilities, model identity, and failures.
- Reference documents every entrypoint and option.
- Domain language defines Relay's terms and its boundaries with other Kontour products.