Skip to content

feat(llm)!: multi-provider support — Anthropic / OpenAI / xAI / Gemini#27

Merged
paulgnz merged 1 commit into
mainfrom
feat/multi-llm-provider-support
May 18, 2026
Merged

feat(llm)!: multi-provider support — Anthropic / OpenAI / xAI / Gemini#27
paulgnz merged 1 commit into
mainfrom
feat/multi-llm-provider-support

Conversation

@paulgnz
Copy link
Copy Markdown
Collaborator

@paulgnz paulgnz commented May 18, 2026

The agent runner was hardcoded to Anthropic's Messages API. This PR refactors the LLM client out from behind a unified interface and wires four providers. Backwards compatible: existing setups using --api-key sk-ant-... + ANTHROPIC_API_KEY keep working unchanged.

What you can now do

./start.sh --account myagent --api-key sk-ant-xxx --network mainnet  # Anthropic
./start.sh --account myagent --api-key sk-xxx     --network mainnet  # OpenAI
./start.sh --account myagent --api-key xai-xxx    --network mainnet  # xAI
./start.sh --account myagent --api-key AIxxx      --network mainnet  # Gemini

Provider is auto-detected from the API key prefix. Override with --provider <anthropic|openai|xai|gemini> to be explicit.

Architecture

openclaw/starter/agent/src/llm/ — new directory:

  • types.tsLlmClient interface + unified message/tool/response shapes (mirrors Anthropic's Messages API since that's what the runner originally targeted)
  • anthropic.ts — wraps @anthropic-ai/sdk
  • openai.ts — wraps openai SDK. Also serves xAI via the XaiLlmClient subclass which points baseURL at https://api.x.ai/v1 (xAI is OpenAI-API-compatible including tool calling)
  • gemini.ts — wraps @google/generative-ai with function-calling translation
  • factory.tscreateLlmClientFromEnv() resolves provider + model + key from env/flags

The runner only knows about the unified types now. Provider SDKs aren't imported outside llm/<provider>.ts.

Resolution priority

  1. --provider flag → AGENT_LLM_PROVIDER env var
  2. AGENT_LLM_PROVIDER env var
  3. Auto-detect from API key prefix
  4. Per-provider env var presence (ANTHROPIC_API_KEY / OPENAI_API_KEY / XAI_API_KEY / GEMINI_API_KEY)
  5. Fallback: anthropic

Default models (override via --model or AGENT_MODEL)

Provider Default model
anthropic claude-sonnet-4-6
openai gpt-5
xai grok-3-latest
gemini gemini-2.5-flash

Boot signal

[agent-runner] LLM: openai (gpt-5)

(was: [agent-runner] Model: claude-sonnet-4-6)

Versions

  • @xpr-agents/openclaw0.5.0 (minor — no behavior change in the plugin itself; the bundled agent-operator skill stops being Claude-specific)
  • create-xpr-agent0.7.0 (minor — start.sh + .env.example gain new env vars; back-compat preserved)

Frontend

/get-started Step 4 and /register Deploy block both show the four provider variants. /register flags table gets a new --provider row.

Also in this commit

docs/video-script.txt — full video brief ready to hand to a video creator agent. 3:30 long-form + 90s short-form cuts. New sections: "It earns on autopilot — the job board loop" and "Upgrade with skills — turn expertise into revenue."

Caveats

  • Anthropic's built-in web_search_20250305 server-side tool is no longer exposed (no cross-provider equivalent). Skills ship web_fetch / web_search tools that work across all four providers.
  • Gemini's tool_use_id doesn't exist natively; we synthesize stable IDs from {name}-{index}-{timestamp}. Multi-call sequences still work.
  • OpenAI tool-argument JSON parsing is defensive: malformed JSON gets passed as {__raw: "..."} rather than silently dropped.

Verification

  • All 4 provider clients smoke-tested locally — factory resolves correct provider + default model under each env-var combination
  • detectProviderFromKey() returns expected provider for every realistic key prefix
  • Agent runner compiles clean (tsc no errors)
  • 80 openclaw tests pass (plugin code unchanged)
  • Frontend builds clean

Until now the agent runner was hardcoded to Anthropic's Messages
API. Every operator had to choose between "use Claude" and "don't
deploy." That's a lock-in defaults choice we didn't deliberately
make — it was just what we shipped first. This commit refactors the
LLM client out of the runner and behind a unified interface, then
wires four providers behind it.

Backwards compatible: existing setups using `--api-key sk-ant-...`
and ANTHROPIC_API_KEY continue to work unchanged — anthropic is the
auto-detected provider for any sk-ant-* key, and the default when
no provider is specified.

## Architecture

  openclaw/starter/agent/src/llm/
    types.ts       — LlmClient interface, LlmMessage/LlmTool/etc.
    anthropic.ts   — wraps @anthropic-ai/sdk
    openai.ts      — wraps openai SDK (used for OpenAI AND xAI)
    gemini.ts      — wraps @google/generative-ai
    factory.ts     — createLlmClientFromEnv(provider, key, model?)
    index.ts       — re-exports

The unified message/tool shape mirrors Anthropic's Messages API
(that's what the runner originally targeted). OpenAI / xAI / Gemini
impls translate to their own formats inside .complete() and back.

xAI is OpenAI-API-compatible at https://api.x.ai/v1 — same SDK,
different baseURL. The XaiLlmClient extends OpenAiLlmClient with
the right baseURL + provider tag, otherwise identical.

## Provider selection (priority order)

  1. --provider flag (start.sh)              → AGENT_LLM_PROVIDER
  2. AGENT_LLM_PROVIDER env var
  3. Auto-detect from API key prefix:
       sk-ant-...       → anthropic
       xai-...          → xai
       sk-proj-* / sk-* → openai
       AI... (long)     → gemini
  4. Per-provider env var presence
     (ANTHROPIC_API_KEY / OPENAI_API_KEY / XAI_API_KEY / GEMINI_API_KEY)
  5. Fallback: anthropic

## Default models (overridable via --model or AGENT_MODEL)

  anthropic → claude-sonnet-4-6
  openai    → gpt-5
  xai       → grok-3-latest
  gemini    → gemini-2.5-flash

## start.sh

  --provider <name>  new flag, explicit override
  --api-key <key>    same flag, auto-routes to the right env var
                     for the resolved provider

If the operator passes --api-key sk-ant-… start.sh sets
ANTHROPIC_API_KEY. If they pass xai-… it sets XAI_API_KEY. And so
on. The agent runner reads whichever env var matches the resolved
provider, with a fallback to ANTHROPIC_API_KEY for setups that
still only have that one set (back-compat).

Boot log now shows the resolved LLM:

  [agent-runner] LLM: openai (gpt-5)

## .env.example

Renames the LLM-key section to make multi-provider obvious:

  AGENT_LLM_PROVIDER=anthropic
  ANTHROPIC_API_KEY=sk-ant-...
  OPENAI_API_KEY=
  XAI_API_KEY=
  GEMINI_API_KEY=

AGENT_MODEL now defaults to empty — the runner picks the right
default for the resolved provider unless overridden.

## Frontend

Get-Started Step 4 and Register page Deploy block both show the
four provider variants explicitly. The flags table on Register
gets a new --provider row.

## What's NOT changed

The OpenClaw plugin itself (@xpr-agents/openclaw) is unchanged.
Plugin = tools + skills; LLM client lives in the agent runner only.
Harness operators were already provider-agnostic — the harness
chooses the LLM. This change only affects the standalone scaffold.

## Caveats

  - Anthropic's built-in `web_search_20250305` server-side tool is
    no longer exposed (no cross-provider equivalent). Skills ship
    `web_fetch` / `web_search` tools that work across all four
    providers; those replace it.
  - Gemini's tool_use_id doesn't exist natively — we synthesize
    stable IDs from the function name + index. Multi-call sequences
    still work; the IDs are only used internally to pair calls
    with results.
  - JSON parsing of OpenAI tool arguments is defensive: malformed
    JSON gets passed through as `{__raw: "..."}` rather than
    silently dropped. The tool handler can reject it cleanly.

## Versions

  @xpr-agents/openclaw → 0.5.0   (minor — no behavior change in the
                                  plugin itself, but the bundled
                                  agent-operator skill stops being
                                  Claude-specific in its prompt)
  create-xpr-agent     → 0.7.0   (minor — start.sh + .env.example
                                  gain new env vars; --api-key
                                  auto-routes by prefix; old
                                  setups keep working)

## Verification

- All 4 provider clients smoke-tested locally — factory resolves
  correct provider + default model under each env-var combination
- detectProviderFromKey() returns expected provider for every
  realistic key prefix (sk-ant-, xai-, sk-proj-, sk-, AI…)
- Agent runner compiles clean (`tsc` no errors)
- 80 openclaw tests pass (unchanged — plugin code didn't change)
- Frontend builds clean, all 13 routes prerendered

## docs/video-script.txt (also in this commit)

Full video brief for handing to a video creator agent, including
the new autopilot + skills-as-revenue sections and the multi-LLM
provider flag walkthrough in Step 4.
@paulgnz paulgnz merged commit 1743271 into main May 18, 2026
5 checks passed
@paulgnz paulgnz deleted the feat/multi-llm-provider-support branch May 18, 2026 21:31
paulgnz added a commit that referenced this pull request May 18, 2026
PR #27 wired Anthropic / OpenAI / xAI / Gemini support into the agent
runner, start.sh, frontend pages, and the new video script — but
missed every README and QUICKSTART. Those are the surfaces npm
displays on package landing pages, which is where most operators
read first. This commit fixes that gap.

Each README now shows the full four-provider matrix instead of just
Anthropic:

  Provider     | Key prefix    | Default model        | Get a key
  Anthropic    | sk-ant-…      | claude-sonnet-4-6    | console.anthropic.com
  OpenAI       | sk-…/sk-proj- | gpt-5                | platform.openai.com
  xAI          | xai-…         | grok-3-latest        | console.x.ai
  Gemini       | AI…           | gemini-2.5-flash     | aistudio.google.com

And the deploy example block shows all four `./start.sh` variants
in sequence instead of just `sk-ant-`, so operators can immediately
see which one matches the key prefix they have.

## Files updated

- README.md (repo root) — Two-paths table + Deploy block
- openclaw/README.md (@xpr-agents/openclaw npm landing)
- create-xpr-agent/README.md (create-xpr-agent npm landing)
- create-xpr-agent/template/README.md (scaffolded README in
  every new agent directory)
- create-xpr-agent/template/QUICKSTART.md (scaffolded walkthrough)
- openclaw/starter/README.md (synced)
- openclaw/starter/QUICKSTART.md (synced)

## Env-var tables

The template README's env-var table now lists ANTHROPIC_API_KEY,
OPENAI_API_KEY, XAI_API_KEY, GEMINI_API_KEY as "one-of" requirements
(set exactly one), plus AGENT_LLM_PROVIDER for explicit override.
AGENT_MODEL now reads "per-provider default" with the four defaults
named inline.

## start.sh flags table

The QUICKSTART start.sh table now shows --provider as a separate
flag, --api-key as multi-provider, and the per-provider model
defaults under --model.

## Versions

- @xpr-agents/openclaw → 0.5.1 (README content only)
- create-xpr-agent     → 0.7.1 (template README + QUICKSTART
                                + main package README)

## Verification

- All four remaining "Anthropic" / "console.anthropic.com"
  references are intentional — they're rows in the multi-provider
  tables alongside OpenAI/xAI/Gemini
- 80 openclaw tests pass
- CI scaffold-sanity diffs covered (template ↔ openclaw/starter)
paulgnz added a commit that referenced this pull request May 22, 2026
…-4.3 (#31)

Live smoke test of the multi-LLM path (PR #27) caught two real
issues that the type system + factory tests didn't.

## Bug — OpenAI completion fails with 400 on GPT-5

GPT-5 (and the o-series) rejects the legacy `max_tokens` field with:

  400 Unsupported parameter: 'max_tokens' is not supported with this
      model. Use 'max_completion_tokens' instead.

The OpenAI SDK passes the field through unmodified, so `LlmClient`
needed to send the right name. xAI's OpenAI-compatible API still
uses `max_tokens` though, so the fix branches on provider flavor:

  - provider === 'openai' → max_completion_tokens
  - provider === 'xai'    → max_tokens (unchanged)

End-to-end smoke tests now green for both providers:

  [1] factory: openai / gpt-5
  [3] tool-use stop: tool_use, 1 tool call → get_time
  [4] tool-result roundtrip: "It's 07:00 UTC..."

  [1] factory: xai / grok-4.3
  [2] text stop: end_turn, "verified" in 1 token
  [3] tool-use: 1 call to get_time
  [4] roundtrip: "The current time is 2026-05-22T07:00:00Z"

Note for operators on GPT-5: the model uses internal reasoning
tokens that count against `max_tokens`. With a tight ceiling (say
30) it may return empty content with stop_reason=max_tokens. The
default 4096 is fine; just don't lower it aggressively when using
gpt-5.

## Default model bump — grok-3-latest → grok-4.3

xAI released grok-4.3 since we shipped 0.5.0; updating the default
gives new operators the current generation. Existing users who set
AGENT_MODEL=grok-3-latest explicitly are unaffected.

Updated in:
  - openclaw/starter/agent/src/llm/types.ts (DEFAULT_MODELS.xai)
  - README + QUICKSTART tables across all surfaces
  - start.sh help text (3 occurrences per file)

## Versions

  @xpr-agents/openclaw → 0.5.2 (patch — fix only)
  create-xpr-agent     → 0.7.2 (patch — model default in scaffold)

## What's still untested

Gemini path is in code but unverified end-to-end (no key available
during this session). Most-different SDK + message shape of the
four impls; treat as "best-effort" until someone exercises it with
a real key. The structural code path matches the OpenAI one, but
Gemini's `functionResponse` part shape + 'function' role for tool
results is unique to that impl.

## Verification

- 80 openclaw tests pass
- Agent runner builds clean (tsc no errors)
- Frontend rebuild clean (all 13 routes prerendered)
- OpenAI smoke test (gpt-5): factory + text + tool-use + tool-result roundtrip ✓
- xAI smoke test (grok-4.3): same four checkpoints ✓
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant