1
0
Files
nex/docs/conversion_anthropic.md

29 KiB
Raw Blame History

Anthropic 协议适配清单

依据 conversion_design.md 附录 D 模板编撰,覆盖 Anthropic API 的全部对接细节。


目录

  1. 协议基本信息
  2. 接口识别
  3. 请求头构建
  4. 核心层 — Chat 请求编解码
  5. 核心层 — Chat 响应编解码
  6. 核心层 — 流式编解码
  7. 扩展层接口
  8. 错误编码
  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 和 RERANKsupportsInterface 返回 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 没有 systemtool 角色。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 角色开始
  • userassistant 角色必须严格交替(除连续 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 请求,支持 limitafter_idbefore_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 错误响应格式

{
    "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 已实现