forked from EduCraft/curriculum-project-hub
fix(hub): Claude Code SDK 经 OpenRouter Anthropic Skin,保留 provider-agnostic
我之前判断错了——OpenRouter 有 Anthropic Messages API 兼容端点 (https://openrouter.ai/api),Claude Code SDK 直连不需要代理。 ANTHROPIC_AUTH_TOKEN=OpenRouter key,ANTHROPIC_API_KEY='' 即可。 而且 ANTHROPIC_DEFAULT_SONNET_MODEL 可设 OpenRouter model ID (z-ai/glm-4.7 等),所以 provider-agnostic 没丢。 ADR-0017 重写:从'Anthropic 专有'改为'Claude Code SDK via OpenRouter, 保留 provider-agnostic'。tsc rc=0。
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
# ADR 0017: Agent Runtime — Claude Code SDK
|
# ADR 0017: Agent Runtime — Claude Code SDK via OpenRouter
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Accepted — supersedes the original ADR-0017 (provider-agnostic session).
|
Accepted.
|
||||||
|
|
||||||
## Context
|
## Context
|
||||||
|
|
||||||
@@ -16,38 +16,42 @@ partial-message streaming, and a battle-tested multi-turn loop. Building these
|
|||||||
ourselves meant re-implementing what the Claude Code SDK already provides.
|
ourselves meant re-implementing what the Claude Code SDK already provides.
|
||||||
|
|
||||||
The Feishu integration also proved costly to hand-roll — card schema quirks,
|
The Feishu integration also proved costly to hand-roll — card schema quirks,
|
||||||
patch limitations, file support — each API quirk cost a round-trip. The
|
patch limitations, file support — each API quirk cost a round-trip.
|
||||||
Claude-to-IM library (2.7K stars) has already solved these, but its
|
|
||||||
LLMProvider interface expects Claude Code SDK's SSE event format. Using it with
|
|
||||||
a generic provider would require a conversion layer.
|
|
||||||
|
|
||||||
## Decision
|
## Decision
|
||||||
|
|
||||||
Adopt `@anthropic-ai/claude-agent-sdk` as the agent runtime. The SDK's `query()`
|
Adopt `@anthropic-ai/claude-agent-sdk` as the agent runtime, routed through
|
||||||
function owns the agent loop (tool dispatch, compaction, streaming). Built-in
|
OpenRouter's **"Anthropic Skin"** (`https://openrouter.ai/api`).
|
||||||
tools (Read, Write, Bash, Glob, Grep) cover the entire tool surface — no custom
|
|
||||||
tools needed. `cph check` / `cph build` run via Bash.
|
|
||||||
|
|
||||||
This means:
|
OpenRouter exposes an Anthropic Messages API-compatible endpoint. Claude Code
|
||||||
- The agent layer is **Anthropic-specific**, not provider-agnostic. Claude Code
|
SDK speaks its native protocol directly to OpenRouter — no proxy, no format
|
||||||
SDK speaks the Anthropic Messages API and cannot be pointed at OpenRouter
|
conversion. The SDK's `query()` owns the agent loop (tool dispatch, compaction,
|
||||||
(which is OpenAI-compatible).
|
streaming). Built-in tools (Read, Write, Bash, Glob, Grep) cover the entire
|
||||||
- An Anthropic API key (or OAuth via Claude Pro/Max) is required.
|
tool surface — no custom tools needed. `cph check` / `cph build` run via Bash.
|
||||||
- Model selection is within the Anthropic model family (Sonnet, Haiku, Opus),
|
|
||||||
not across vendors.
|
Environment variables:
|
||||||
|
```
|
||||||
|
ANTHROPIC_BASE_URL=https://openrouter.ai/api
|
||||||
|
ANTHROPIC_AUTH_TOKEN=<OpenRouter API key>
|
||||||
|
ANTHROPIC_API_KEY="" # must be explicitly empty
|
||||||
|
```
|
||||||
|
|
||||||
|
Model routing: `ANTHROPIC_DEFAULT_SONNET_MODEL` etc. accept OpenRouter model
|
||||||
|
IDs (e.g. `z-ai/glm-4.7`, `anthropic/claude-sonnet-4-20250514`). This means
|
||||||
|
**provider-agnosticism is preserved** — any OpenRouter model that supports tool
|
||||||
|
use can be used, not just Anthropic models.
|
||||||
|
|
||||||
## Consequences
|
## Consequences
|
||||||
|
|
||||||
- We lose the ability to route to GLM or other non-Anthropic models via
|
- Provider-agnosticism preserved via OpenRouter — GLM, Claude, GPT, etc. all
|
||||||
OpenRouter. This is the accepted cost of getting a mature agent loop.
|
work as long as the model supports tool use.
|
||||||
- The Vercel AI SDK dependency is dropped.
|
- The agent loop is Claude Code SDK's (compaction, tool approval, partial
|
||||||
|
message streaming) — not hand-rolled.
|
||||||
- Custom tools (read_file, write_file, cph_check, cph_build) are replaced by
|
- Custom tools (read_file, write_file, cph_check, cph_build) are replaced by
|
||||||
the SDK's built-in Read/Write/Bash/Glob/Grep.
|
the SDK's built-in Read/Write/Bash/Glob/Grep.
|
||||||
- The SSE event format matches Claude-to-IM's expected protocol natively —
|
- The SSE event format matches Claude-to-IM's expected protocol natively.
|
||||||
adopting Claude-to-IM for Feishu integration is a direct fit, no conversion.
|
- Per-run model selection works via role→model mapping; models are OpenRouter
|
||||||
- Per-run model selection still works (role→model mapping), but models are
|
IDs, not Anthropic-only aliases.
|
||||||
Anthropic aliases, not OpenRouter routing IDs.
|
- OpenRouter recommends setting Anthropic as the top-priority provider for
|
||||||
- If provider-agnosticism becomes a hard requirement again, the migration path
|
Claude Code; non-Anthropic models may have reduced compatibility with some
|
||||||
is: replace `query()` with a Vercel AI SDK `streamText` call + an SSE format
|
SDK features (e.g. thinking blocks).
|
||||||
converter (~100 lines). The rest of the architecture (trigger, lock,
|
|
||||||
permission, Feishu client) is provider-agnostic already.
|
|
||||||
|
|||||||
+15
-12
@@ -1,28 +1,31 @@
|
|||||||
# Hub runtime configuration. Copy to .env and fill in.
|
# Hub runtime configuration. Copy to .env and fill in.
|
||||||
#
|
|
||||||
# For local dev: `cp .env.example .env` then edit.
|
|
||||||
# For tests: integration tests read from .env via dotenv.
|
|
||||||
|
|
||||||
# PostgreSQL connection string.
|
# PostgreSQL connection string.
|
||||||
DATABASE_URL="postgresql://paradigm:paradigm@127.0.0.1:5432/paradigm"
|
DATABASE_URL="postgresql://paradigm:paradigm@127.0.0.1:5432/paradigm"
|
||||||
|
|
||||||
# OpenRouter (or any OpenAI-compatible) API key.
|
# OpenRouter API key — used as ANTHROPIC_AUTH_TOKEN for Claude Code SDK.
|
||||||
|
# OpenRouter's "Anthropic Skin" (https://openrouter.ai/api) is Anthropic-API
|
||||||
|
# compatible, so Claude Code SDK speaks its native protocol directly.
|
||||||
# Get one at https://openrouter.ai/keys
|
# Get one at https://openrouter.ai/keys
|
||||||
# The agent layer is provider-agnostic (ADR-0017); OpenRouter routes to any model.
|
|
||||||
OPENROUTER_API_KEY=""
|
OPENROUTER_API_KEY=""
|
||||||
|
|
||||||
# Optional: override the base URL (e.g. direct vendor API, local gateway).
|
# Claude Code SDK auth — derived from OpenRouter. Do NOT set ANTHROPIC_API_KEY
|
||||||
# OPENROUTER_BASE_URL="https://openrouter.ai/api/v1"
|
# to a real value; it must be explicitly empty to avoid auth conflicts.
|
||||||
|
ANTHROPIC_BASE_URL="https://openrouter.ai/api"
|
||||||
|
ANTHROPIC_AUTH_TOKEN="" # Set to same value as OPENROUTER_API_KEY
|
||||||
|
ANTHROPIC_API_KEY=""
|
||||||
|
|
||||||
# Feishu (lark) bot credentials. Create a self-built app at
|
# Model override (optional). Defaults to Claude Sonnet. To use GLM:
|
||||||
# https://open.feishu.cn/app, add the "Bot" capability, subscribe to
|
# ANTHROPIC_DEFAULT_SONNET_MODEL="z-ai/glm-4.7"
|
||||||
# im.message.receive_v1, then fill these in.
|
# ANTHROPIC_DEFAULT_OPUS_MODEL="z-ai/glm-4.7"
|
||||||
|
# ANTHROPIC_DEFAULT_HAIKU_MODEL="z-ai/glm-4.6"
|
||||||
|
|
||||||
|
# Feishu (lark) bot credentials.
|
||||||
FEISHU_APP_ID=""
|
FEISHU_APP_ID=""
|
||||||
FEISHU_APP_SECRET=""
|
FEISHU_APP_SECRET=""
|
||||||
FEISHU_BOT_OPEN_ID=""
|
FEISHU_BOT_OPEN_ID=""
|
||||||
|
|
||||||
# Path to the `cph` binary (the Courseware-side checker, ADR-0016).
|
# Path to the `cph` binary (ADR-0016). Defaults to `cph` on PATH.
|
||||||
# Defaults to `cph` on PATH if unset.
|
|
||||||
# CPH_BIN="/usr/local/bin/cph"
|
# CPH_BIN="/usr/local/bin/cph"
|
||||||
|
|
||||||
# Hub server port. Defaults to 8788.
|
# Hub server port. Defaults to 8788.
|
||||||
|
|||||||
@@ -29,6 +29,14 @@ function requireEnv(name: string): string {
|
|||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const databaseUrl = requireEnv("DATABASE_URL");
|
const databaseUrl = requireEnv("DATABASE_URL");
|
||||||
void databaseUrl;
|
void databaseUrl;
|
||||||
|
|
||||||
|
// Claude Code SDK auth via OpenRouter's Anthropic Skin. The SDK reads
|
||||||
|
// ANTHROPIC_BASE_URL + ANTHROPIC_AUTH_TOKEN from env. ANTHROPIC_API_KEY
|
||||||
|
// must be explicitly empty to avoid auth conflicts.
|
||||||
|
const openrouterKey = requireEnv("OPENROUTER_API_KEY");
|
||||||
|
process.env["ANTHROPIC_BASE_URL"] ??= "https://openrouter.ai/api";
|
||||||
|
process.env["ANTHROPIC_AUTH_TOKEN"] ??= openrouterKey;
|
||||||
|
process.env["ANTHROPIC_API_KEY"] ??= "";
|
||||||
const feishuAppId = requireEnv("FEISHU_APP_ID");
|
const feishuAppId = requireEnv("FEISHU_APP_ID");
|
||||||
const feishuAppSecret = requireEnv("FEISHU_APP_SECRET");
|
const feishuAppSecret = requireEnv("FEISHU_APP_SECRET");
|
||||||
const feishuBotOpenId = requireEnv("FEISHU_BOT_OPEN_ID");
|
const feishuBotOpenId = requireEnv("FEISHU_BOT_OPEN_ID");
|
||||||
|
|||||||
Reference in New Issue
Block a user