From 5ea70a110b4188c91e7cd37827a802426cc590fc Mon Sep 17 00:00:00 2001 From: irl-dan <97565471+irl-dan@users.noreply.github.com> Date: Wed, 3 Jun 2026 12:08:44 -0700 Subject: [PATCH] docs: native Anthropic Messages API as the supported route for Anthropic models Anthropic's OpenAI-compat endpoint is documented as testing-only and ignores response_format, so it can't drive Reactor's structured compile/render. Reframe the docs around the supported native route: - sdk/agents: add a "native Anthropic Messages API" section (the @openai/agents AI-SDK adapter over @ai-sdk/anthropic, zero SDK changes); rewrite the callout with Anthropic's own documented stance; flag the compat table row as plain text only. - cli/configuration: provider: anthropic now routes natively and supports structured outputs out of the box (the CLI bundles the adapter); rewrite the warn callout into an info note covering the native route + OpenRouter. - cspell: add aisdk. --- .cspell.json | 1 + content/docs/cli/configuration.mdx | 31 +++++++++---- content/docs/sdk/agents.mdx | 73 +++++++++++++++++++++++++----- 3 files changed, 83 insertions(+), 22 deletions(-) diff --git a/.cspell.json b/.cspell.json index 58a85d9..68c04ea 100644 --- a/.cspell.json +++ b/.cspell.json @@ -13,6 +13,7 @@ "words": [ "OpenProse", "openprose", + "aisdk", "Fumadocs", "fumadocs", "Forme", diff --git a/content/docs/cli/configuration.mdx b/content/docs/cli/configuration.mdx index 8b8ccb9..114b357 100644 --- a/content/docs/cli/configuration.mdx +++ b/content/docs/cli/configuration.mdx @@ -79,9 +79,18 @@ Each built-in provider resolves to an endpoint + a key env var: | --- | --- | --- | --- | | `openrouter` (default) | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | `google/gemini-3.5-flash` | | `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `gpt-4o-mini` | -| `anthropic` | `https://api.anthropic.com/v1/` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` | +| `anthropic` | Anthropic **Messages API** (native, see below) | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` | | `google` | `https://generativelanguage.googleapis.com/v1beta/openai/` | `GEMINI_API_KEY` | `gemini-2.5-flash` | +`openrouter`, `openai`, and `google` are OpenAI-compatible Chat Completions +surfaces. `anthropic` is special: the CLI routes it through Anthropic's **native +Messages API** (via the bundled `@openai/agents` AI-SDK adapter over +`@ai-sdk/anthropic`), because Anthropic's OpenAI-compat endpoint ignores +`response_format` and can't drive Reactor's structured compile/render. You don't +install or wire anything -- `provider: anthropic` just works, structured outputs and +all. An optional `base_url` points the adapter at a proxy in front of the Messages +API. + For any other OpenAI-compatible vendor (or a self-hosted gateway), name it freely and supply both `base_url` and `api_key_env`: @@ -106,15 +115,17 @@ up from the project directory. A missing key fails `compile`/`run`/`serve` with silently misdirects you to OpenRouter, and never exits 0 on an auth dead-end. - -**Claude models: prefer `openrouter` over `anthropic` for now.** Compile + render -use a JSON-schema response format, and Anthropic's direct OpenAI-compatible -endpoint rejects it unless `strict: true` (`400 response_format.json_schema.strict`). -OpenRouter, OpenAI, and Google accept it. To run Claude today, use -`provider: openrouter` with a `render_model`/`compile_model` like -`anthropic/claude-haiku-4-5`. `provider: anthropic` (direct) is wired and -authenticates, but the structured compile/run path is blocked by that endpoint -requirement. + +**Claude, two ways.** `provider: anthropic` drives Claude directly through the +native Messages API and fully supports the structured compile/render path -- it is +the recommended route for Anthropic models. If you already aggregate models through +one gateway, `provider: openrouter` with a `render_model`/`compile_model` like +`anthropic/claude-haiku-4-5` also works. Avoid pointing a `base_url` at Anthropic's +OpenAI-compatible endpoint (`https://api.anthropic.com/v1/`): it ignores +`response_format` and rejects Reactor's JSON-schema with +`400 response_format.json_schema.strict`. See the +[SDK provider guide](/sdk/agents#claude-via-the-native-anthropic-messages-api) +for the underlying `@openai/agents` wiring. ## sandbox diff --git a/content/docs/sdk/agents.mdx b/content/docs/sdk/agents.mdx index 8ddf3c2..3bb3e3e 100644 --- a/content/docs/sdk/agents.mdx +++ b/content/docs/sdk/agents.mdx @@ -258,7 +258,7 @@ Any vendor with an OpenAI-compatible Chat Completions endpoint drops straight in | --- | --- | --- | --- | | OpenRouter (default) | `https://openrouter.ai/api/v1` | `OPENROUTER_API_KEY` | `google/gemini-3.5-flash` | | OpenAI | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `gpt-4o-mini` | -| Anthropic | `https://api.anthropic.com/v1/` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` | +| Anthropic (plain text only -- see below) | `https://api.anthropic.com/v1/` | `ANTHROPIC_API_KEY` | `claude-haiku-4-5` | | Google Gemini | `https://generativelanguage.googleapis.com/v1beta/openai/` | `GEMINI_API_KEY` | `gemini-2.5-flash` | @@ -275,19 +275,68 @@ Anthropic) in `packages/reactor/src/adapters/agent-render/__tests__/provider-byo.live.test.ts`. -**Anthropic's direct endpoint and structured outputs.** Compile sessions and the -render's done/failed signal use a JSON-schema `response_format`. Anthropic's -OpenAI-compatible endpoint requires `strict: true` on that schema and rejects the -default with `400 response_format.json_schema.strict`. OpenAI and Google (and -every model **via OpenRouter**, including Anthropic's) accept it as-is. So for -**Claude models today, route through OpenRouter** (`provider` pointed at -`https://openrouter.ai/api/v1`, model `anthropic/claude-...`); direct -`api.anthropic.com` works for plain-text use but not yet for the structured -compile/render path. +**Don't use Anthropic's OpenAI-compatible endpoint for Claude -- use the native +adapter below.** Compile sessions and the render's done/failed signal use a +JSON-schema `response_format`, and renders drive tools. Anthropic documents its +[OpenAI SDK compatibility layer](https://platform.claude.com/docs/en/api/openai-sdk) +as "primarily for testing" -- it **ignores `response_format`** and rejects our +structured schema with `400 response_format.json_schema.strict`. So +`baseURL: "https://api.anthropic.com/v1/"` (the row above) is fine for plain-text +probes but **not** the structured compile/render path. For Claude with structured +outputs you have two supported routes: the **native Messages API** via the AI-SDK +adapter (next), or **OpenRouter** (`provider` pointed at +`https://openrouter.ai/api/v1`, model `anthropic/claude-...`), where Anthropic's +own models accept the schema as-is. -For an API that is **not** OpenAI-compatible at all (a native Anthropic Messages -client, a local model, a bespoke gateway), drop one level to the +### Claude via the native Anthropic Messages API + +The supported way to drive Claude directly is the official `@openai/agents` route +for non-OpenAI models: the [AI-SDK adapter](https://openai.github.io/openai-agents-js/extensions/ai-sdk/) +over [`@ai-sdk/anthropic`](https://platform.claude.com/docs/en/build-with-claude/structured-outputs), +which hits Anthropic's native Messages API where structured outputs and tools +work. This is **still plain `@openai/agents` configuration** -- `provider` accepts +any `ModelProvider`, so the harness is untouched; only the model session changes. +No SDK changes are needed. + +```sh +npm add @openai/agents-extensions ai @ai-sdk/anthropic +``` + +```ts +import { reactor } from "@openprose/reactor"; +import type { ModelProvider } from "@openai/agents"; +import { aisdk } from "@openai/agents-extensions/ai-sdk"; +import { createAnthropic } from "@ai-sdk/anthropic"; + +// A scoped ModelProvider backed by Anthropic's NATIVE Messages API. +function anthropicNativeProvider(apiKey: string): ModelProvider { + const anthropic = createAnthropic({ apiKey }); + const cache = new Map>(); + return { + getModel(modelName = "claude-haiku-4-5") { + let model = cache.get(modelName); + if (!model) cache.set(modelName, (model = aisdk(anthropic(modelName)))); + return model; + }, + }; +} + +const provider = anthropicNativeProvider(process.env.ANTHROPIC_API_KEY!); +const { reactor: r } = await reactor("./my-project", { + directory: "./state", + render: { provider, model: "claude-haiku-4-5" }, // run-phase renders + compile: { options: { provider, model: "claude-haiku-4-5" } }, // compile sessions +}); +await r.ingest("source", { wake: { source: "external", refs: [] } }); +``` + +The [Reactor CLI](/cli/configuration#choosing-a-model-provider) builds exactly +this provider for you when you set `provider: anthropic` in `reactor.yml` -- you +don't write the adapter wiring yourself there. + +For an API that is **not** OpenAI-compatible at all (a local model, a bespoke +gateway) and has no AI-SDK provider, drop one level to the [`RenderBackend` injection seam](#the-renderbackend-injection-seam) below: you own the whole session and the harness keeps its instruction composition, tools, harvest, and cost capture.