Build Agents

Model

Connect Harness to an OpenAI-compatible, OpenAI Responses, Anthropic Messages, or custom provider.

Harness is provider-neutral. A model adapter maps Harness's canonical prompt and tool contracts to the provider API you choose, then maps its response back to a candidate. Your host code still owns the endpoint, SDK or fetch, credentials, headers, model name, retries, and provider-specific features.

Import the built-in translators from the public subpath:

import {
  anthropicAdapter,
  chatCompletionsAdapter,
  responsesAdapter,
} from "@nylorun/harness/model/adapters";

OpenAI-compatible endpoints

chatCompletionsAdapter is the simplest choice for any provider exposing the Chat Completions shape. It works equally well with a provider's compatible endpoint, a gateway, or a local server.

import { chatCompletionsAdapter } from "@nylorun/harness/model/adapters";

const adapter = chatCompletionsAdapter(async (body, call, { signal }) => {
  const response = await fetch(`${baseUrl}/chat/completions`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${apiKey}`,
      "content-type": "application/json",
      ...providerHeaders,
    },
    body: JSON.stringify({
      model: modelName,
      ...call.model?.config,
      ...body,
    }),
    signal,
  });

  if (!response.ok) throw new Error(await response.text());
  return response.json();
});

Keep baseUrl, apiKey, modelName, and providerHeaders in environment-backed host configuration. The public example uses this boundary and can target OpenAI-compatible providers, gateways, or local endpoints.

Direct OpenAI Responses

Use responsesAdapter when your application calls the OpenAI Responses API directly. The send function receives the translated request, canonical call, and abort signal.

const adapter = responsesAdapter((body, call, { signal }) =>
  openai.responses.create(
    { model: "your-model", ...call.model?.config, ...body },
    { signal },
  ),
);

Direct Anthropic Messages

Use anthropicAdapter for the Messages API. It requires a positive defaultMaxOutputTokens; a per-step maxOutputTokens model control overrides that fallback.

const adapter = anthropicAdapter({
  defaultMaxOutputTokens: 1_024,
  send: (body, call, { signal }) =>
    anthropic.messages.create(
      { model: "your-model", ...call.model?.config, ...body },
      { signal },
    ),
});

Any other provider

Implement ModelAdapter directly when the provider has a different request or response format. The adapter receives a provider-neutral ModelCall (model, prompt, tools, and sessionId) and must resolve to a ModelCandidate, string, or deferred outcome.

import type { ModelAdapter } from "@nylorun/harness";

const adapter: ModelAdapter = async (call, { signal }) => {
  const response = await provider.complete({
    prompt: call.prompt,
    tools: call.tools,
    signal,
  });

  return {
    output: [{ type: "text", text: response.text }],
    finishReason: "stop",
  };
};

Translator boundary

The built-in translators cover text output and JSON-object tool loops. Provider-native continuation state, streaming, images, cache controls, and other provider-specific features remain application integration concerns. Use your provider SDK or a custom adapter when those features are required.

See Model adapters reference for request and response shapes.

On this page