1
0

docs: 重构 API 参考文档,拆分为独立文件并统一 CLI 风格格式

This commit is contained in:
2026-04-19 14:21:03 +08:00
parent b92974716f
commit 4dc518a5f4
17 changed files with 25227 additions and 8678 deletions

View File

@@ -0,0 +1,786 @@
# Anthropic 协议适配清单
> 依据 [conversion_design.md](./conversion_design.md) 附录 D 模板编撰,覆盖 Anthropic API 的全部对接细节。
---
## 目录
1. [协议基本信息](#1-协议基本信息)
2. [接口识别](#2-接口识别)
3. [请求头构建](#3-请求头构建)
4. [核心层 — Chat 请求编解码](#4-核心层--chat-请求编解码)
5. [核心层 — Chat 响应编解码](#5-核心层--chat-响应编解码)
6. [核心层 — 流式编解码](#6-核心层--流式编解码)
7. [扩展层接口](#7-扩展层接口)
8. [错误编码](#8-错误编码)
9. [自检清单](#9-自检清单)
---
## 1. 协议基本信息
| 项目 | 说明 |
|------|------|
| 协议名称 | `"anthropic"` |
| 协议版本 | `2023-06-01`(通过 `anthropic-version` Header 传递) |
| Base URL | `https://api.anthropic.com` |
| 认证方式 | `x-api-key: <api_key>` |
---
## 2. 接口识别
### 2.1 URL 路径模式
| URL 路径 | InterfaceType |
|----------|---------------|
| `/v1/messages` | CHAT |
| `/v1/models` | MODELS |
| `/v1/models/{model}` | MODEL_INFO |
| `/v1/batches` | 透传 |
| `/v1/messages/count_tokens` | 透传 |
| `/v1/*` | 透传 |
### 2.2 接口能力矩阵
```
Anthropic.supportsInterface(type):
CHAT: return true
MODELS: return true
MODEL_INFO: return true
EMBEDDINGS: return false // Anthropic 无此接口
RERANK: return false // Anthropic 无此接口
default: return false
```
### 2.3 URL 映射表
```
Anthropic.buildUrl(nativePath, interfaceType):
switch interfaceType:
case CHAT: return "/v1/messages"
case MODELS: return "/v1/models"
case MODEL_INFO: return "/v1/models/{modelId}"
default: return nativePath
```
不支持 EMBEDDINGS 和 RERANK`supportsInterface` 返回 false引擎会自动走透传逻辑。
---
## 3. 请求头构建
### 3.1 buildHeaders
```
Anthropic.buildHeaders(provider):
result = {}
result["x-api-key"] = provider.api_key
result["anthropic-version"] = provider.adapter_config["anthropic_version"] ?? "2023-06-01"
if provider.adapter_config["anthropic_beta"]:
result["anthropic-beta"] = provider.adapter_config["anthropic_beta"].join(",")
result["Content-Type"] = "application/json"
return result
```
### 3.2 adapter_config 契约
| Key | 类型 | 必填 | 默认值 | 说明 |
|-----|------|------|--------|------|
| `anthropic_version` | String | 否 | `"2023-06-01"` | API 版本,映射为 `anthropic-version` Header |
| `anthropic_beta` | Array\<String\> | 否 | `[]` | Beta 功能标识列表,逗号拼接为 `anthropic-beta` Header |
---
## 4. 核心层 — Chat 请求编解码
### 4.1 DecoderAnthropic → Canonical
#### 系统消息
```
decodeSystem(system):
if system is None: return None
if system is String: return system
return system.map(s => SystemBlock {text: s.text})
```
Anthropic 使用顶层 `system` 字段String 或 SystemBlock 数组),直接提取为 `canonical.system`
#### 消息角色映射
| Anthropic role | Canonical role | 说明 |
|----------------|---------------|------|
| `user` | `user` | 直接映射;可能包含 tool_result |
| `assistant` | `assistant` | 直接映射 |
**关键差异**Anthropic 没有 `system``tool` 角色。system 通过顶层字段传递tool_result 嵌入 user 消息的 content 数组中。
#### 内容块解码
```
decodeContentBlocks(content):
if content is String: return [{type: "text", text: content}]
return content.map(block => {
switch block.type:
"text" → TextBlock{text: block.text}
"tool_use" → ToolUseBlock{id: block.id, name: block.name, input: block.input}
"tool_result" → ToolResultBlock{tool_use_id: block.tool_use_id, ...}
"thinking" → ThinkingBlock{thinking: block.thinking}
"redacted_thinking" → 丢弃 // 仅 Anthropic 使用,不在中间层保留
})
```
**tool_result 角色转换**
```
decodeMessage(msg):
switch msg.role:
case "user":
blocks = decodeContentBlocks(msg.content)
toolResults = blocks.filter(b => b.type == "tool_result")
others = blocks.filter(b => b.type != "tool_result")
if toolResults.length > 0:
return [
...(others.length > 0 ? [{role: "user", content: others}] : []),
{role: "tool", content: toolResults}]
return [{role: "user", content: blocks}]
case "assistant":
return [{role: "assistant", content: decodeContentBlocks(msg.content)}]
```
Anthropic user 消息中的 `tool_result` 块被拆分为独立的 Canonical `tool` 角色消息。
#### 工具定义
| Anthropic | Canonical | 说明 |
|-----------|-----------|------|
| `tools[].name` | `tools[].name` | 直接映射 |
| `tools[].description` | `tools[].description` | 直接映射 |
| `tools[].input_schema` | `tools[].input_schema` | 字段名相同 |
| `tools[].type` | — | Anthropic 无 function 包装层 |
#### 工具选择
| Anthropic tool_choice | Canonical ToolChoice |
|-----------------------|---------------------|
| `{type: "auto"}` | `{type: "auto"}` |
| `{type: "none"}` | `{type: "none"}` |
| `{type: "any"}` | `{type: "any"}` |
| `{type: "tool", name}` | `{type: "tool", name}` |
#### 参数映射
| Anthropic | Canonical | 说明 |
|-----------|-----------|------|
| `max_tokens` | `parameters.max_tokens` | 直接映射Anthropic 必填 |
| `temperature` | `parameters.temperature` | 直接映射 |
| `top_p` | `parameters.top_p` | 直接映射 |
| `top_k` | `parameters.top_k` | 直接映射 |
| `stop_sequences` (Array) | `parameters.stop_sequences` (Array) | 直接映射 |
| `stream` | `stream` | 直接映射 |
#### 新增公共字段
```
decodeExtras(raw):
user_id = raw.metadata?.user_id
output_format = decodeOutputFormat(raw.output_config)
parallel_tool_use = raw.disable_parallel_tool_use == true ? false : null
thinking = raw.thinking ? ThinkingConfig {
type: raw.thinking.type, // "enabled" | "disabled" | "adaptive"
budget_tokens: raw.thinking.budget_tokens,
effort: raw.output_config?.effort } : null
```
**ThinkingConfig 三种类型解码**
| Anthropic thinking.type | Canonical thinking.type | 说明 |
|-------------------------|----------------------|------|
| `"enabled"` | `"enabled"` | 有 budget_tokens直接映射 |
| `"disabled"` | `"disabled"` | 直接映射 |
| `"adaptive"` | `"adaptive"` | Anthropic 自动决定是否启用思考,映射为 `"adaptive"`(新增 Canonical 值) |
> **注意**`thinking.display``"summarized"` / `"omitted"`)为 Anthropic 特有字段,控制响应中思考内容的显示方式,不晋升为公共字段。
**output_config 解码**
```
decodeOutputFormat(output_config):
if output_config?.format?.type == "json_schema":
return { type: "json_schema", json_schema: { name: "output", schema: output_config.format.schema, strict: true } }
return null
```
| Anthropic | Canonical | 提取规则 |
|-----------|-----------|---------|
| `metadata.user_id` | `user_id` | 从嵌套对象提取 |
| `output_config.format` | `output_format` | 仅支持 `json_schema` 类型;映射为 Canonical OutputFormat |
| `output_config.effort` | `thinking.effort` | `"low"` / `"medium"` / `"high"` / `"xhigh"` / `"max"` 直接映射 |
| `disable_parallel_tool_use` | `parallel_tool_use` | **语义反转**true → false |
| `thinking.type` | `thinking.type` | 直接映射 |
| `thinking.budget_tokens` | `thinking.budget_tokens` | 直接映射 |
#### 协议特有字段
| 字段 | 处理方式 |
|------|---------|
| `cache_control` | 忽略(仅 Anthropic 使用,不晋升为公共字段) |
| `redacted_thinking` | 解码时丢弃,不在中间层保留 |
| `metadata` (除 user_id) | 忽略 |
| `thinking.display` | 忽略(控制响应显示方式,不影响请求语义) |
| `container` | 忽略(容器标识,协议特有) |
| `inference_geo` | 忽略(地理区域控制,协议特有) |
| `service_tier` | 忽略(服务层级选择,协议特有) |
#### 协议约束
- `max_tokens` 为**必填**字段
- messages 必须以 `user` 角色开始
- `user``assistant` 角色必须严格交替(除连续 tool_result 场景)
- tool_result 必须紧跟在包含对应 tool_use 的 assistant 消息之后
### 4.2 EncoderCanonical → Anthropic
#### 模型名称
使用 `provider.model_name` 覆盖 `canonical.model`
#### 系统消息注入
```
encodeSystem(system):
if system is String: return system
return system.map(s => ({text: s.text}))
```
`canonical.system` 编码为 Anthropic 顶层 `system` 字段。
#### 消息编码
**关键差异**Canonical 的 `tool` 角色消息需要合并到 Anthropic 的 `user` 消息中:
```
encodeMessages(canonical):
result = []
for msg in canonical.messages:
switch msg.role:
case "user":
result.append({role: "user", content: encodeContentBlocks(msg.content)})
case "assistant":
result.append({role: "assistant", content: encodeContentBlocks(msg.content)})
case "tool":
// tool 角色转为 Anthropic 的 user 消息内 tool_result 块
toolResults = msg.content.filter(b => b.type == "tool_result")
if result.length > 0 && result.last.role == "user":
result.last.content = result.last.content + toolResults
else:
result.append({role: "user", content: toolResults})
```
#### 角色约束处理
Anthropic 要求 user/assistant 严格交替。编码时需要:
1. 将 Canonical `tool` 角色合并到相邻 `user` 消息中
2. 确保首条消息为 `user` 角色(若无,自动注入空 user 消息)
3. 合并连续同角色消息
#### 工具编码
```
encodeTools(canonical):
if canonical.tools:
result.tools = canonical.tools.map(t => ({
name: t.name, description: t.description, input_schema: t.input_schema}))
encodeToolChoice(choice):
switch choice.type:
"auto" → {type: "auto"}
"none" → {type: "none"}
"any" → {type: "any"}
"tool" → {type: "tool", name: choice.name}
```
#### 公共字段编码
```
encodeRequest(canonical, provider):
result = {
model: provider.model_name,
messages: encodeMessages(canonical),
max_tokens: canonical.parameters.max_tokens,
temperature: canonical.parameters.temperature,
top_p: canonical.parameters.top_p,
top_k: canonical.parameters.top_k,
stream: canonical.stream
}
if canonical.system:
result.system = encodeSystem(canonical.system)
if canonical.parameters.stop_sequences:
result.stop_sequences = canonical.parameters.stop_sequences
if canonical.user_id:
result.metadata = {user_id: canonical.user_id}
if canonical.output_format or canonical.thinking?.effort:
result.output_config = {}
if canonical.output_format:
result.output_config.format = encodeOutputFormat(canonical.output_format)
if canonical.thinking?.effort:
result.output_config.effort = canonical.thinking.effort
if canonical.parallel_tool_use == false:
result.disable_parallel_tool_use = true
if canonical.tools:
result.tools = canonical.tools.map(t => ({
name: t.name, description: t.description, input_schema: t.input_schema}))
if canonical.tool_choice:
result.tool_choice = encodeToolChoice(canonical.tool_choice)
if canonical.thinking:
result.thinking = encodeThinkingConfig(canonical.thinking)
return result
encodeThinkingConfig(canonical):
switch canonical.type:
"enabled":
cfg = {type: "enabled", budget_tokens: canonical.budget_tokens}
return cfg
"disabled":
return {type: "disabled"}
"adaptive":
return {type: "adaptive"}
return {type: "disabled"}
encodeOutputFormat(output_format):
switch output_format.type:
"json_schema":
return {type: "json_schema", schema: output_format.json_schema.schema}
"json_object":
return {type: "json_schema", schema: {type: "object"}}
```
#### 降级处理
对照架构文档 §8.4 三级降级策略,确认每个不支持字段的处理:
| Canonical 字段 | Anthropic 不支持时 | 降级策略 |
|---------------|-------------------|---------|
| `thinking.effort` | Anthropic 通过 `output_config.effort` 传递 | 自动映射为 `output_config.effort` |
| `stop_reason: "content_filter"` | Anthropic 无此值 | 自动映射为 `"end_turn"` |
| `output_format: "text"` | Anthropic 无 text 输出格式 | 丢弃,不设置 output_config |
| `output_format: "json_object"` | Anthropic 用 json_schema 替代 | 替代方案:生成空 schema 的 json_schema |
---
## 5. 核心层 — Chat 响应编解码
逐字段对照 §4.7 CanonicalResponse 确认映射关系。
### 5.1 响应结构
```
Anthropic 响应顶层结构:
{
id: String,
type: "message",
role: "assistant",
model: String,
content: [ContentBlock...],
stop_reason: String,
stop_sequence: String | null,
stop_details: Object | null,
container: Object | null,
usage: { input_tokens, output_tokens, cache_read_input_tokens?, cache_creation_input_tokens?,
cache_creation?, inference_geo?, server_tool_use?, service_tier? }
}
```
**新增字段**(对比 §4.7 CanonicalResponse
| Anthropic 字段 | 说明 |
|----------------|------|
| `stop_details` | 结构化拒绝信息:`{type: "refusal", category, explanation}`,仅 `stop_reason == "refusal"` 时存在 |
| `container` | 容器信息:`{id, expires_at}`,仅使用 code execution 工具时存在 |
### 5.2 DecoderAnthropic → Canonical
```
decodeResponse(anthropicResp):
blocks = []
for block in anthropicResp.content:
switch block.type:
"text" → blocks.append({type: "text", text: block.text})
"tool_use" → blocks.append({type: "tool_use", id: block.id, name: block.name, input: block.input})
"thinking" → blocks.append({type: "thinking", thinking: block.thinking})
"redacted_thinking" → 丢弃 // 仅 Anthropic 使用,不在中间层保留
return CanonicalResponse {id, model, content: blocks, stop_reason: mapStopReason(anthropicResp.stop_reason),
usage: CanonicalUsage {input_tokens, output_tokens,
cache_read_tokens: anthropicResp.usage.cache_read_input_tokens,
cache_creation_tokens: anthropicResp.usage.cache_creation_input_tokens}}
```
**内容块解码**
- `text` → TextBlock直接映射忽略 `citations` 字段)
- `tool_use` → ToolUseBlock直接映射忽略 `caller` 字段)
- `thinking` → ThinkingBlock直接映射忽略 `signature` 字段)
- `redacted_thinking` → 丢弃(协议特有,不晋升为公共字段)
- `server_tool_use` / `web_search_tool_result` / `code_execution_tool_result` 等 → 丢弃(服务端工具块,协议特有)
**停止原因映射**
| Anthropic stop_reason | Canonical stop_reason | 说明 |
|-----------------------|-----------------------|------|
| `"end_turn"` | `"end_turn"` | 直接映射 |
| `"max_tokens"` | `"max_tokens"` | 直接映射 |
| `"tool_use"` | `"tool_use"` | 直接映射 |
| `"stop_sequence"` | `"stop_sequence"` | 直接映射 |
| `"pause_turn"` | `"pause_turn"` | 长轮次暂停,映射为 Canonical 新增值 |
| `"refusal"` | `"refusal"` | 安全拒绝,直接映射 |
**Token 用量映射**
| Anthropic usage | Canonical Usage | 说明 |
|-----------------|-----------------|------|
| `input_tokens` | `input_tokens` | 直接映射 |
| `output_tokens` | `output_tokens` | 直接映射 |
| `cache_read_input_tokens` | `cache_read_tokens` | 字段名映射 |
| `cache_creation_input_tokens` | `cache_creation_tokens` | 字段名映射 |
| `cache_creation` | — | 协议特有(按 TTL 细分),不晋升 |
| `inference_geo` | — | 协议特有,不晋升 |
| `server_tool_use` | — | 协议特有,不晋升 |
| `service_tier` | — | 协议特有,不晋升 |
| — | `reasoning_tokens` | Anthropic 不返回此字段,始终为 null |
**协议特有内容**
| 字段 | 处理方式 |
|------|---------|
| `redacted_thinking` | 解码时丢弃 |
| `stop_sequence` | 解码时忽略Canonical 用 stop_reason 覆盖) |
| `stop_details` | 解码时忽略(协议特有,不晋升) |
| `container` | 解码时忽略(协议特有,不晋升) |
| `text.citations` | 解码时忽略(协议特有,不晋升) |
| `tool_use.caller` | 解码时忽略(协议特有,不晋升) |
| `thinking.signature` | 解码时忽略(协议特有,不晋升;同协议透传时自然保留) |
### 5.3 EncoderCanonical → Anthropic
```
encodeResponse(canonical):
blocks = canonical.content.map(block => {
switch block.type:
"text" → {type: "text", text: block.text}
"tool_use" → {type: "tool_use", id: block.id, name: block.name, input: block.input}
"thinking" → {type: "thinking", thinking: block.thinking}})
return {id: canonical.id, type: "message", role: "assistant", model: canonical.model,
content: blocks,
stop_reason: mapCanonicalStopReason(canonical.stop_reason),
stop_sequence: None,
usage: {input_tokens: canonical.usage.input_tokens, output_tokens: canonical.usage.output_tokens,
cache_read_input_tokens: canonical.usage.cache_read_tokens,
cache_creation_input_tokens: canonical.usage.cache_creation_tokens}}
```
**内容块编码**
- TextBlock → `{type: "text", text}`(直接映射)
- ToolUseBlock → `{type: "tool_use", id, name, input}`(直接映射)
- ThinkingBlock → `{type: "thinking", thinking}`(直接映射)
**停止原因映射**
| Canonical stop_reason | Anthropic stop_reason |
|-----------------------|-----------------------|
| `"end_turn"` | `"end_turn"` |
| `"max_tokens"` | `"max_tokens"` |
| `"tool_use"` | `"tool_use"` |
| `"stop_sequence"` | `"stop_sequence"` |
| `"pause_turn"` | `"pause_turn"` |
| `"refusal"` | `"refusal"` |
| `"content_filter"` | `"end_turn"`(降级) |
**降级处理**
| Canonical 字段 | Anthropic 不支持时 | 降级策略 |
|---------------|-------------------|---------|
| `stop_reason: "content_filter"` | Anthropic 无此值 | 自动映射为 `"end_turn"` |
| `reasoning_tokens` | Anthropic 无此字段 | 丢弃 |
**协议特有内容**
| 字段 | 处理方式 |
|------|---------|
| `redacted_thinking` | 编码时不产出 |
| `stop_sequence` | 编码时始终为 null |
| `stop_details` | 编码时不产出 |
| `container` | 编码时不产出 |
| `text.citations` | 编码时不产出 |
| `thinking.signature` | 编码时不产出(同协议透传时自然保留) |
---
## 6. 核心层 — 流式编解码
### 6.1 SSE 格式
Anthropic 使用命名 SSE 事件,与 CanonicalStreamEvent 几乎 1:1 对应:
```
event: message_start
data: {"type":"message_start","message":{"id":"msg_xxx","model":"claude-4",...}}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hello"}}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn"},"usage":{"output_tokens":10}}
event: message_stop
data: {"type":"message_stop"}
event: ping
data: {"type":"ping"}
```
### 6.2 StreamDecoderAnthropic SSE → Canonical 事件)
Anthropic SSE 事件与 CanonicalStreamEvent 几乎 1:1 映射,状态机最简单:
| Anthropic SSE 事件 | Canonical 事件 | 说明 |
|---|---|---|
| `message_start` | MessageStartEvent | 直接映射 |
| `content_block_start` | ContentBlockStartEvent | 直接映射 content_block |
| `content_block_delta` | ContentBlockDeltaEvent | 见下方 delta 类型映射表 |
| `content_block_stop` | ContentBlockStopEvent | 直接映射 |
| `message_delta` | MessageDeltaEvent | 直接映射 delta 和 usage |
| `message_stop` | MessageStopEvent | 直接映射 |
| `ping` | PingEvent | 直接映射 |
| `error` | ErrorEvent | 直接映射 |
**delta 类型映射**`content_block_delta` 事件内):
| Anthropic delta 类型 | Canonical delta 类型 | 说明 |
|---------------------|---------------------|------|
| `text_delta` | `{type: "text_delta", text}` | 直接映射 |
| `input_json_delta` | `{type: "input_json_delta", partial_json}` | 直接映射 |
| `thinking_delta` | `{type: "thinking_delta", thinking}` | 直接映射 |
| `citations_delta` | 丢弃 | 协议特有,不晋升为公共字段 |
| `signature_delta` | 丢弃 | 协议特有(用于多轮思考签名连续性),不晋升 |
**content_block_start 类型映射**
| Anthropic content_block 类型 | Canonical content_block | 说明 |
|------------------------------|----------------------|------|
| `{type: "text", text: ""}` | `{type: "text", text: ""}` | 直接映射 |
| `{type: "tool_use", id, name, input: {}}` | `{type: "tool_use", id, name, input: {}}` | 直接映射 |
| `{type: "thinking", thinking: ""}` | `{type: "thinking", thinking: ""}` | 直接映射 |
| `{type: "redacted_thinking", data: ""}` | 丢弃整个 block | 跳过后续 delta 直到 content_block_stop |
| `server_tool_use` / `web_search_tool_result` 等 | 丢弃 | 服务端工具块,协议特有 |
### 6.3 StreamDecoder 状态机
```
StreamDecoderState {
messageStarted: Boolean
openBlocks: Set<Integer>
currentBlockType: Map<Integer, String>
currentBlockId: Map<Integer, String>
redactedBlocks: Set<Integer> // 追踪需要丢弃的 redacted_thinking block
utf8Remainder: Option<ByteArray> // UTF-8 跨 chunk 安全
accumulatedUsage: Option<CanonicalUsage>
}
```
Anthropic Decoder 无需 OpenAI 的 `toolCallIdMap` / `toolCallNameMap` / `toolCallArguments`,因为 Anthropic 的事件已经有明确的结构。
**关键处理**
- **`redacted_thinking`**:在 `content_block_start` 事件中检测类型,将 index 加入 `redactedBlocks`,后续 delta 和 stop 事件均丢弃
- **`citations_delta` / `signature_delta`**:在 delta 映射时直接丢弃,不影响 block 生命周期
- **`server_tool_use` 等服务端工具块**:与 `redacted_thinking` 处理方式一致,加入 `redactedBlocks` 丢弃
- **UTF-8 安全**:跨 chunk 截断的 UTF-8 字节需要用 `utf8Remainder` 缓冲
- **usage 累积**`message_delta` 中的 usage 与 `message_start` 中的 usage 合并
### 6.4 StreamEncoderCanonical → Anthropic SSE
| Canonical 事件 | Anthropic SSE 事件 | 说明 |
|---|---|---|
| MessageStartEvent | `event: message_start` | 直接映射 |
| ContentBlockStartEvent | `event: content_block_start` | 直接映射 content_block |
| ContentBlockDeltaEvent | `event: content_block_delta` | 见下方 delta 编码表 |
| ContentBlockStopEvent | `event: content_block_stop` | 直接映射 |
| MessageDeltaEvent | `event: message_delta` | 直接映射 |
| MessageStopEvent | `event: message_stop` | 直接映射 |
| PingEvent | `event: ping` | 直接映射 |
| ErrorEvent | `event: error` | 直接映射 |
**delta 编码表**
| Canonical delta 类型 | Anthropic delta 类型 | 说明 |
|---------------------|---------------------|------|
| `{type: "text_delta", text}` | `text_delta` | 直接映射 |
| `{type: "input_json_delta", partial_json}` | `input_json_delta` | 直接映射 |
| `{type: "thinking_delta", thinking}` | `thinking_delta` | 直接映射 |
**缓冲策略**:无需缓冲,每个 Canonical 事件直接编码为对应的 Anthropic SSE 事件。
**SSE 编码格式**
```
event: <event_type>\n
data: <json_payload>\n
\n
```
---
## 7. 扩展层接口
### 7.1 /models & /models/{model}
**列表接口** `GET /v1/models`
| 项目 | 说明 |
|------|------|
| 接口是否存在 | 是 |
| 请求格式 | GET 请求,支持 `limit``after_id``before_id` 查询参数 |
响应 DecoderAnthropic → Canonical
```
decodeModelsResponse(anthropicResp):
return CanonicalModelList {
models: anthropicResp.data.map(m => CanonicalModel {
id: m.id, name: m.display_name ?? m.id, created: parseTimestamp(m.created_at),
owned_by: "anthropic"})}
parseTimestamp(timestamp):
// Anthropic 返回 RFC 3339 字符串(如 "2025-05-14T00:00:00Z"),需转为 Unix 时间戳
return rfc3339ToUnix(timestamp) ?? 0
```
响应 EncoderCanonical → Anthropic
```
encodeModelsResponse(canonical):
return {data: canonical.models.map(m => ({
id: m.id,
display_name: m.name ?? m.id,
created_at: m.created ? unixToRfc3339(m.created) : epochRfc3339(),
type: "model"})),
has_more: false,
first_id: canonical.models[0]?.id, last_id: canonical.models.last?.id}
```
**详情接口** `GET /v1/models/{model}`
| 项目 | 说明 |
|------|------|
| 接口是否存在 | 是 |
| 请求格式 | GET 请求,路径参数 `model_id` |
响应 DecoderAnthropic → Canonical
```
decodeModelInfoResponse(anthropicResp):
return CanonicalModelInfo {
id: anthropicResp.id, name: anthropicResp.display_name ?? anthropicResp.id,
created: parseTimestamp(anthropicResp.created_at), owned_by: "anthropic" }
```
响应 EncoderCanonical → Anthropic
```
encodeModelInfoResponse(canonical):
return {id: canonical.id,
display_name: canonical.name ?? canonical.id,
created_at: canonical.created ? unixToRfc3339(canonical.created) : epochRfc3339(),
type: "model"}
```
**字段映射**(列表和详情共用):
| Anthropic | Canonical | 说明 |
|-----------|-----------|------|
| `data[].id` | `models[].id` | 直接映射 |
| `data[].display_name` | `models[].name` | Anthropic 特有的显示名称 |
| `data[].created_at` | `models[].created` | **类型转换**Anthropic 为 RFC 3339 字符串Canonical 为 Unix 时间戳 |
| `data[].type: "model"` | — | 固定值 |
| `has_more` | — | 编码时固定为 false |
| `first_id` / `last_id` | — | 从列表提取 |
| `data[].capabilities` | — | 协议特有,不晋升 |
| `data[].max_input_tokens` | — | 协议特有,不晋升 |
| `data[].max_tokens` | — | 协议特有,不晋升 |
**跨协议对接示例**(入站 `/anthropic/v1/models`,目标 OpenAI
```
入站: GET /anthropic/v1/models, x-api-key: sk-ant-xxx
→ client=anthropic, provider=openai
→ URL: /v1/models, Headers: Authorization: Bearer sk-xxx
OpenAI 上游响应: {object: "list", data: [{id: "gpt-4o", object: "model", created: 1700000000, owned_by: "openai"}]}
→ OpenAI.decodeModelsResponse → CanonicalModelList
→ Anthropic.encodeModelsResponse
返回客户端: {data: [{id: "gpt-4o", display_name: "gpt-4o", created_at: "2023-11-04T18:26:40Z", type: "model"}],
has_more: false, first_id: "gpt-4o", last_id: "gpt-4o"}
```
---
## 8. 错误编码
### 8.1 错误响应格式
```json
{
"type": "error",
"error": {
"type": "invalid_request_error",
"message": "Error message"
}
}
```
### 8.2 encodeError
```
Anthropic.encodeError(error):
return {type: "error", error: {type: error.code, message: error.message}}
```
### 8.3 常用 HTTP 状态码
| HTTP Status | 说明 |
|-------------|------|
| 400 | 请求格式错误 |
| 401 | 认证失败(无效 API key |
| 403 | 无权限访问 |
| 404 | 接口不存在 |
| 429 | 速率限制 |
| 500 | 服务器内部错误 |
| 529 | 服务过载 |
---
## 9. 自检清单
| 章节 | 检查项 |
|------|--------|
| §2 | [x] 所有 InterfaceType 的 `supportsInterface` 返回值已确定 |
| §2 | [x] 所有 InterfaceType 的 `buildUrl` 映射已确定 |
| §3 | [x] `buildHeaders(provider)` 已实现adapter_config 契约已文档化 |
| §4 | [x] Chat 请求的 Decoder 和 Encoder 已实现(逐字段对照 §4.1/§4.2 |
| §4 | [x] 角色映射和消息顺序约束已处理tool→user 合并、首消息 user 保证、交替约束) |
| §4 | [x] 工具调用tool_use / tool_result的编解码已处理 |
| §4 | [x] 协议特有字段已识别并确定处理方式cache_control 忽略、redacted_thinking 丢弃) |
| §5 | [x] Chat 响应的 Decoder 和 Encoder 已实现(逐字段对照 §4.7 |
| §5 | [x] stop_reason 映射表已确认 |
| §5 | [x] usage 字段映射已确认input_tokens / cache_read_input_tokens 等) |
| §6 | [x] 流式 StreamDecoder 和 StreamEncoder 已实现(对照 §4.8 |
| §7 | [x] 扩展层接口的编解码已实现(/models、/models/{model} |
| §8 | [x] `encodeError` 已实现 |