feat(div-anthropic): LLM 接缝 + 错误层脱离 @anthropic-ai/sdk (Phase 1-6) - #66
Merged
Conversation
基线: typecheck/test/build 全绿, 38 tests 0 fail, bundle 162.6MB 审计: 运行时 SDK import 6 文件, 类型垫片 103 消费者, 5 个 @anthropic-ai/* 依赖 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
新增 src/services/llm/types.ts: provider 中立 LLM 接缝类型, 参考 deepseek-harness - StreamChunk 联合与 claude.ts 现有 SSE switch 分支逐一映射 - LlmFailure 稳定错误码 (AUTH/RATE_LIMIT/...) 替代 instanceof APIError - LlmAdapter 接口: 唯一必需方法 stream() (静态分派, 非 Cordis) 单测: 适配器契约/chunk 穷尽性/错误码稳定性 (4 用例, 38→42) 零行为变更: 纯新增类型层, 运行时仍用 Anthropic SDK checkpoint: typecheck/test/build 全绿 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
去除 Anthropic SDK 的接缝层基础设施 (暂不接入主循环, 纯新增): - src/services/llm/sseStream.ts: parseSseStream — 按 SSE 帧边界 (空行派发, data:/event:/: 注释) 解析 fetch Response.body, 多行 data 用 \n 连接, 跨 chunk 边界 buffer carryover, AbortSignal 中断抛 AbortError。 替代 SDK 的 Stream/BetaMessageStream (claude.ts 已绕过 BetaMessageStream)。 - src/services/llm/errors.ts: classifyError/isRetryable/LlmRequestError — provider 中立错误码 (AUTH/RATE_LIMIT/INVALID_REQUEST/SERVER/TIMEOUT/ TRANSPORT/ABORTED), 替代 instanceof APIError。AbortError 优先且兼容 DOMException 与任意 .name="AbortError" 的 Error。 - src/services/llm/httpClient.ts: postMessages — 复用现有 client.ts 的 getUserAgent/getProxyFetchOptions/getAnthropicApiKey/computeCch/ CLIENT_REQUEST_ID_HEADER, firstParty 走 cch 签名, 非 2xx/fetch 失败 抛 LlmRequestError (含分类后的 LlmFailure)。 - 单测: sseStream 10 例 (含跨 chunk/心跳/中断), errors 20 例 (status/ message/Abort 优先/重试判定)。types 4 例 (Phase 1) 共 34 例全绿。 checkpoint: typecheck ✓ / 72 tests pass ✓ / build ✓ Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ag 守护)
接缝层适配器实现 (纯新增, LLM_ADAPTER_SEAM feature 守护, 默认关闭走 SDK):
- src/services/llm/adapter.ts: AnthropicWireAdapter
- buildRequestBody: GenerateOptions -> /v1/messages JSON (model/messages/
system[字符串|块数组]/tools->input_schema/max_tokens/stream/thinking/
temperature/stop_sequences + extraBody 透传 betas/metadata 等扩展字段)
依据 claude.ts:1539 paramsFromContext 实际形状。
- sseToChunk: Anthropic SSE 事件 -> StreamChunk 中立词表
(message_start->message-start, content_block_start->block-start,
text_delta/thinking_delta/signature_delta/input_json_delta/
connector_text_delta -> 对应 delta chunk, content_block_stop->block-end,
message_delta->usage(记 stop_reason), message_stop->finish)。
依据 claude.ts:1975 switch。signature_delta 归 thinking 块 (零宽+signature)。
- AnthropicWireAdapter.stream: postMessages + parseSseStream + sseToChunk。
- src/services/llm/mlxAdapter.ts: createMlxAdapter
- 复用 createFusionMlxFetch (fetch override, 内部 Anthropic<->OpenAI 转译,
响应已 encodeStreamToAnthropicSSE), 故 MLX 路径直接复用 AnthropicWireAdapter
的 SSE 解析。baseUrl 占位 (override 按 url.includes("/v1/messages") 拦截)。
- src/services/llm/registry.ts: getLlmAdapter(provider, model)
- 按 APIProvider 静态分发。seam 关闭 (feature 宏 false) 返回 null -> 调用方
回退 SDK, 实现 instant rollback。seam 期 firstParty+fusionMlx 走新适配器,
bedrock/vertex/foundry/openai 暂返回 null (Phase 4/5 迁移)。
- feature() 直接用在 if 里 (Bun DCE 宏约束, 不可包在返回它的函数中)。
- src/services/llm/httpClient.ts: PostMessagesOptions 增 fetchFn 可选注入
(MLX 路径用 createFusionMlxFetch 作为 fetch)。
- scripts/build.ts: fullExperimentalFeatures 注册 LLM_ADAPTER_SEAM。
- 单测: adapter 29 例 (buildRequestBody 10 + sseToChunk 19), registry 4 例
(seam 关闭可空契约; on-path 由带 flag 构建的集成测试覆盖)。共 63 llm 例全绿。
checkpoint: typecheck ✓ / 101 tests pass ✓ / build ✓ (含 --feature=LLM_ADAPTER_SEAM 构建 ✓)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
claude.ts 在 SDK messages.create 前注入 LLM_ADAPTER_SEAM 接缝分支:
flag 开 + provider∈{firstParty,fusionMlx} 时, 调 streamViaSeam 直接
POST /v1/messages 并把 SSE→StreamChunk→SdkPart, 喂给下方既有 switch,
零 switch 改动。flag 关走 SDK 原路径, 回滚=关 flag。
- seam.ts: isSeamActive (ternary, DCE 兼容) + streamViaSeam 核心流式
- chunkToPart.ts: StreamChunk→SdkPart 翻译器 (sseToChunk 的逆映射),
usage chunk 携带 stopReason 于 message_delta 时 emit
- types.ts: usage chunk 扩展 stopReason 字段
- adapter.ts: sseToChunk message_delta emit stopReason
- claude.ts: import seam + 接缝分支 (queryCheckpoint + return)
- 测试: chunkToPart 12 项, adapter message_delta stopReason 断言更新
验证: typecheck ✓ / 113 tests ✓ / build(默认+flag) ✓ /
本地 MLX 真实模型 smoke (Llama-3.1-8B, seam 激活, status 200, 正确流式应答) ✓
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
withRetry.ts 不再 import @anthropic-ai/sdk 运行时类: - instanceof APIError → isApiErrorLike (接纳 SDK APIError 与 seam LlmRequestError) - instanceof APIConnectionError → isConnectionErrorLike - new APIUserAbortError() → abortError() (name="AbortError") - APIError 形参 → ApiErrorLike 形态类型 前置 (step 1): - LlmFailure/LlmRequestError 暴露 SDK 兼容 .status/.headers/.requestID - classifyError 接收 headers; httpClient 捕获响应头到 LlmRequestError - 新增 isApiErrorLike/isConnectionErrorLike/isTimeoutErrorLike/isAbortErrorLike 修复 seam 路径正确性缺口: 之前 seam 抛的 LlmRequestError 无 .status/.headers, 会穿透 withRetry 所有 instanceof APIError 分支 → seam 错误不重试/不分类。 现形态桥同时覆盖 SDK 路径 (flag 关) 与 seam 路径 (flag 开)。 验证: typecheck 0 错; bun test src/__tests__/llm 75 pass; tests/services 146 pass; build:dev --feature=LLM_ADAPTER_SEAM 绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
errors.ts 不再 runtime import @anthropic-ai/sdk: - instanceof APIError (×22) → isApiErrorLike - instanceof APIConnectionError/TimeoutError → isConnectionErrorLike/isTimeoutErrorLike - APIError 保留为 type-only (anthropic-protocol.ts, 构建期擦除), 用于 categorizeRetryableAPIError 形参与 formatAPIError 调用点 cast isConnectionErrorLike/isTimeoutErrorLike 升级为类型守卫 (error is Error), 使 errors.ts 在守卫后可读 error.message (原 instanceof 自动收窄的等价能力)。 验证: typecheck 0 错; bun test 259 pass (含 llm 75 + services 146); build:dev --feature=LLM_ADAPTER_SEAM 绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
utils/errors.ts: isAbortError 改用 .name/.message 鸭子判断, 不再 instanceof SDK
bashPermissions / permissions / useCanUseTool / GenerateStep / awaySummary:
instanceof APIUserAbortError → isAbortError()
compact.ts: abortError 工厂改为 () => Error{name:'AbortError'}, 不再 new APIUserAbortError
checkpoint: typecheck 0 / 113 tests pass / build green
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
logging.ts / claudeAiLimits.ts / validateModel.ts / claude.ts / rateLimitMocking.ts: instanceof APIError → isApiErrorLike (按形状: status/headers/requestID) instanceof NotFoundError → isApiErrorLike && status===404 instanceof AuthenticationError → status===401 instanceof APIConnectionError → isConnectionErrorLike new APIUserAbortError() / new APIConnectionTimeoutError() → 具名 Error 工厂 rateLimitMocking: 本地 MockAPIError extends Error 替代 SDK APIError 类 (ant-only) checkpoint: typecheck 0 / 113 tests pass / build green Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
beforeEach 未清理 FUSION_BASE_URL/FUSION_GATEWAY_ENABLED/FUSION_API_KEY, shell 中 FUSION_BASE_URL=http://127.0.0.1 使 shouldAutoUseFusionMlx() 误判为 true, 导致 "有 FUSION_API_KEY 时不应自动启用" 用例失败。补齐清理使用例 hermetic。 (报错用例定位修复 — 与 div-anthropic 无关, 但按规则一并修复) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
model-providers.md: 新增 "LLM Adapter 接缝" 节 — 架构/错误层鸭子桥/范围与遗留(#63-65) README.md: Build 节新增 LLM_ADAPTER_SEAM flag 说明 + 链接 docs CLAUDE.md: 本地 (gitignored) 新增 seam 子系统指引 关联 issue: #63 (client.ts 云 provider) #64 (package.json) #65 (sdk/runtime/mcpb 评估) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述
去除 fusion-code 对
@anthropic-ai/sdk的运行时耦合 — Phase 1-5 完成(错误层解耦),Phase 6 文档。完整方案见~/fusion/architecture/fusion-code-div-anthropic.md。本 PR 引入 provider 中立的 LLM 接缝 (seam),并用鸭子类型桥替代所有
instanceofSDK 错误类判定。编译后二进制 0 处@anthropic-ai/sdk错误类引用。变更
Phase 1-4: 接缝基础设施 + 主循环接入
src/services/llm/types.ts— provider 中立类型 (LlmAdapter/StreamChunk/GenerateOptions/LlmFailure)src/services/llm/{sseStream,errors,httpClient}.ts— SSE 解析 / 错误分类 / 裸 HTTP 客户端src/services/llm/{adapter,mlxAdapter,registry}.ts— AnthropicWireAdapter + MLX 适配器 + 注册表src/services/llm/seam.ts—isSeamActive()+streamViaSeam(): flagLLM_ADAPTER_SEAM开 + provider∈{firstParty,fusionMlx} 时,POST/v1/messages直连,SSE→StreamChunk→SDK parts 喂下方既有 switch(零 switch 改动)。回滚=关 flag。Phase 5: 错误层脱离 SDK
鸭子类型桥
src/services/llm/errors.ts(isApiErrorLike/isConnectionErrorLike/isTimeoutErrorLike/isAbortError),按形状判定 (status/headers/requestID/name/message),同时接受 SDKAPIError(flag-off)与接缝LlmRequestError(flag-on)。修复了接缝错误缺.status/.headers落穿所有instanceof APIError分支的接缝正确性 bug。12 个文件脱 SDK 运行时:
withRetry.ts/api/errors.ts—instanceof APIError→isApiErrorLike,APIConnectionError→isConnectionErrorLike,throw APIUserAbortError→abortError()utils/errors.ts—isAbortError用.name/.message(minified build SDK 类名混淆为nJT)APIUserAbortError: bashPermissions / permissions / useCanUseTool / GenerateStep / awaySummary / compact / claude →isAbortError()APIError: logging / claudeAiLimits / validateModel (NotFoundError→404, Auth→401) / claude / rateLimitMocking (本地MockAPIError extends Error)Phase 6: 文档 + 测试修复
docs/model-providers.md/README.md— 接缝与错误层文档tests/cli/startup.test.ts— 修复 provider 检测用例环境变量泄漏 (FUSION_BASE_URL未清理)验证
--dev --feature=LLM_ADAPTER_SEAM): green@anthropic-ai/sdk引用: 0mlx-community--Meta-Llama-3.1-8B-Instruct-4bit): 200 OK,全链路 httpClient→MLX→SSE→StreamChunk→渲染遗留(已开 issue,不阻塞本 PR)
client.ts云 provider (bedrock/vertex/foundry 签名) 解耦决策package.jsonSDK 依赖移除(受 div-anthropic D1: client.ts 云 provider 解耦 — 决定 seam 是否覆盖 bedrock/vertex/foundry 签名 #63 阻塞)claude-agent-sdk/sandbox-runtime/mcpb解耦评估接缝当前只覆盖 firstParty+fusionMlx;云 provider 仍走 SDK
new Anthropic(...),故@anthropic-ai/sdk仍是运行时必需依赖(package.json 未动)。回滚
关闭
LLM_ADAPTER_SEAMflag(默认即关)→streamViaSeam经 Bun DCE 消除,回退 SDK 路径。错误层鸭子桥对 flag-off 的 SDK 错误同样生效,无功能损失。🤖 Generated with Claude Code