Skip to content

🏗️ refactor(shared,sdk): 协议消息类型下沉单源,解除 shared↔sdk 包级循环依赖 - #469

Merged
CavinHuang merged 1 commit into
mainfrom
fix/286-sdk-shared-cycle
Aug 23, 2026
Merged

🏗️ refactor(shared,sdk): 协议消息类型下沉单源,解除 shared↔sdk 包级循环依赖#469
CavinHuang merged 1 commit into
mainfrom
fix/286-sdk-shared-cycle

Conversation

@CavinHuang

@CavinHuang CavinHuang commented Aug 22, 2026

Copy link
Copy Markdown
Owner

Fixes #286

问题

packages/shared 的 manifest 声明依赖 @lume/agent-sdk,代码上又通过 type-only import 从 sdk 取 SDKMessage;而 sdk 运行时反向依赖 shared,形成包级循环,目前仅靠 type 擦除才不炸。

方案

类型下沉(纯移动,字段与注释保持原文):

  • 新增 shared/src/types/sdk-protocol.ts 作为 SDK 消息协议唯一来源,搬入:
    • content block 基础类型:ToolResultContentBlock / ContentBlockParam / ContentBlock
    • 会话消息基础:MessageRole / ConversationMessage
    • SDKMessage 全部 32 个联合成员接口及 SDKAssistantMessageErrorSDKPermissionDenialSDKRateLimitInfo
    • 压缩协议:AgentContextCompactionTrigger/Stage/MetadataCompactionFailureReason(自 sdk/utils/compact.ts 随迁)
    • usage 计量族:ProviderCallKind / UsageIdentity / NormalizedProviderUsage / ContextUsageSnapshot / BillingUsageRecord / BillingUsageSummary / AgentProgressUsage / TokenUsage / ModelUsage / SDKUsageRecord
    • PermissionMode
  • ToolExecutionMetadata 族不重复搬:shared/src/types/runtime-event.ts 已有单源定义,新文件直接引用,sdk 删除本地副本消除双份漂移。

兼容策略(下游零改动):

  • shared/src/types/agent.tsSDKMessage 的 import/re-export 名不变,仅来源改为本地 ./sdk-protocol
  • sdk/src/types.ts 保留全部剩余类型 + 一段全量 export type { ... } from '@lume/shared' re-export 壳,sdk 对外导出面不变,内部所有 from './types' 引用与 src/index.ts 导出零改动。
  • sdk/utils/compact.ts 原地 re-export CompactionFailureReason,既有深引写法不受影响。
  • 删除 shared/package.json 中对 @lume/agent-sdk 的依赖声明,依赖方向收敛为 sdk→shared 单向。

连带修复: desktop tsconfig 补声明 DOM.Iterable。desktop 两处 NodeList 迭代在解环后报 TS2488(必要性已探针实证),按代码实际使用显式声明;至于此前为何能编译通过,未能在 sdk/shared/@types 源中定位到全局增强来源,归因存疑、不强下结论。

验证

  • bun run typecheck 六包(shared/ui/sdk/sidecar/web/desktop)全绿
  • grep -rn "@lume/agent-sdk" packages/shared/src 零命中(含测试)
  • bun test src(shared):178 pass / 0 fail
  • bun run test:unit(sdk):696 pass / 2 skip / 0 fail

🤖 Generated with Claude Code

@CavinHuang

Copy link
Copy Markdown
Owner Author

Review

逐条实证完成:diff 全量通读 + 与 origin/main 原文机械比对 + PR 树上 grep 验证 + TS2488 探针实验。

结论

LGTM,可合。 解环目标达成且实现干净:shared 内 @lume/agent-sdk 引用清零、manifest/lock 同步、依赖方向收敛为 sdk→shared 单向;纯移动声明属实;对外 API 面不变。仅一条 P3 归因表述建议修正,不阻塞。另有一处与 #467 的硬冲突需排合并顺序(见交叉风险节)。

发现

P3-1 DOM.Iterable 连带修复的归因表述存疑(不阻塞)

补 lib 本身必要性已实证:apps/desktop/src/browser-guest-preload.tsx:228apps/desktop/src/browser-overlay/anchor.ts:29 两处 [...doc.querySelectorAll(...)] 展开运算符在 lib: ["ES2022","DOM"] 下报 TS2488(探针复现),且 web/ui tsconfig 本就有 DOM.Iterable,desktop 补齐是一致性对齐,方向正确。

但「此前能力来自 shared→sdk 类型链意外带入的全局类型增强」这一因果链未能独立复原:

  • sdk/shared 源码在本分支与 main 上均无 /// <reference lib>declare global
  • @types/react-dom@18.3.7 无 lib 引用指令;
  • 仅带 --types node,react,react-dom 的最小程序中展开 NodeList 即报错,说明全局增强来自完整程序中某个被 include 的 d.ts,具体来源待定位。

建议:补充「去掉 DOM.Iterable 后 desktop typecheck 报错清单」佐证因果,或把描述弱化为「显式声明既有隐式能力,与 web/ui 对齐」。

交叉风险(vs PR #467

两 PR 均自 base blob c38ac947 修改 packages/sdk/src/types.ts 同一区域,git 必然冲突,且有语义交叠:

#467 改动 #469 对应区域状态
SDKMessage 联合删 SDKSessionStateChangedMessage 成员 + 删接口定义 搬入 shared/sdk-protocol.ts,成员保留
SDKResultMessage.subtype 去掉 | string 兜底、收窄为 | 'error_completion_guard' 搬入后仍保留 | string

其余两个疑似交叉点经查无冲突#467 动的是 shared 根 barrel(src/index.ts 追加 stable-serialize 导出行),#469 动的是 types 子 barrel(src/types/index.ts),不同文件。

建议 #469 先合:纯移动先行,让后续行为 diff 干净落在单源上。#467 后合适配成本低——其 SDKSessionStateChangedMessage 触点共 4 处且全部集中于 types.ts 单文件(无跨文件配套改动),rebase 时把三处语义(删联合成员、删接口、subtype 收窄)原样移植到 packages/shared/src/types/sdk-protocol.ts 并同步清理 re-export 壳名单即可。反序则 #469 需以「删成员+收窄」终态落地,把行为变更混进移动 PR,不建议。

已核实

  1. 纯移动声明:57 个类型定义与 main 删除侧逐字段/逐注释比对一致,SDKMessage 联合 32 成员名称顺序一致、判别字段字面量无一改动。仅有的两处有意变化是 failureReason?/failure_reason?import('./utils/compact.js').CompactionFailureReason 改为直引本地类型——compact.ts 原地 re-export 的即同一符号,语义等价且为解环必需。
  2. FileResultRef 超集论证成立:shared 版较 sdk 原副本仅多可选 fileRef?: FileRef(V1/V2 其余字段逐字一致)。sdk 源码内 FileResultRef 除 re-export 壳外零直接使用点;字段消费方 bash.ts:1032 带运行期守卫 + as ToolExecutionMetadata['resultRef'](索引类型随单源走,无 narrowing 漏洞);process-job-worker.ts:198-199 为子集字面量赋给宽目标(结构兼容)。sidecar/web 的 normalizeFileResultRef 本就以 shared 版为契约。无 exhaustive 处理场景。
  3. re-export 壳完整性:壳 62 名与 main 上 types.ts 可见名一一对应;src/index.ts 零改动,main 上经包级导出的协议类型全部被壳覆盖或留本地(CompactionFailureReason 走 compact.js 路径原地保留);壳内多出的名字(FileResultRef/ToolExecutionMetadata 族等)原本就不在包级导出面,API 面严格不变。shared 双 barrel(types/agent.ts re-export 与 sdk-protocol 直出)指向同一符号,无 ambiguous star-export;62 名与 shared 全仓 1027 个导出名比对零重名
  4. 深引排查:desktop/web/sidecar/sdk 四包 @lume/shared/src/ 形式引用零命中,全部走 barrel。
  5. 值 vs 类型:57 名全为 type/interface,无 const/enum/class(AbortError class 未搬,留在 types.ts);shared 侧无同名值声明,type-only re-export 壳不会运行期炸。
  6. 循环依赖:PR 树上 git grep '@lume/agent-sdk' -- packages/shared 零命中(含测试);shared/package.json dependencies 整块删除,bun.lock 同步;sdk/package.json 声明 @lume/shared": "workspace:*",方向单向收敛。
  7. CI:checks 4/4 passed。

- SDKMessage 全部联合成员及其类型闭包(content block、压缩协议元数据、
  usage 计量族、PermissionMode)从 sdk/types.ts 物理下沉至
  shared/src/types/sdk-protocol.ts;CompactionFailureReason 随迁,
  utils/compact.ts 原地 re-export 保持深引兼容。
- ToolExecutionMetadata 族不再双份:sdk 删本地副本,统一引用
  shared/runtime-event.ts 既有单源定义。
- sdk/types.ts 以全量 type re-export 壳保持对外导出面不变,sdk 内部
  `from './types'` 引用与 src/index.ts 导出零改动。
- shared/types/agent.ts 的 SDKMessage import/re-export 改指本地新文件,
  导出名不变;shared/package.json 删除对 @lume/agent-sdk 的依赖声明,
  包级循环依赖解除(依赖方向收敛为 sdk→shared 单向)。
- desktop tsconfig 补声明 DOM.Iterable:此前 NodeList 迭代能力来自
  shared→sdk 类型链意外带入的全局增强,解环后按真实使用显式声明。

Co-Authored-By: Claude Fable <noreply@anthropic.com>
@CavinHuang
CavinHuang force-pushed the fix/286-sdk-shared-cycle branch from bd0febc to ea8668d Compare August 23, 2026 02:15
@CavinHuang

Copy link
Copy Markdown
Owner Author

Rebase 完成:已基于最新 origin/main(#467 合入点 7704ccc)重放本分支单 commit,远端 tip 现为 ea8668d

#467 三处语义已移植到新单源 packages/shared/src/types/sdk-protocol.ts:

  1. 死事件下线([P3][sdk/shared/natives] 低危积压批次 #413①):删除 SDKSessionStateChangedMessage 接口定义与 SDKMessage 联合中的对应成员;sdk 侧 re-export 壳(packages/sdk/src/types.ts)导出清单同步删除该条目。说明:原计划中 shared 内两处具名导出清单条目(types/agent.ts re-export 与 barrel)在 🏗️ refactor(shared,sdk): 协议消息类型下沉单源,解除 shared↔sdk 包级循环依赖 #469 实际形态中并不存在——agent.ts 仅具名 re-export SDKMessage,barrel 为 export * from "./sdk-protocol",单源删除后自动收敛,无需额外改动。
  2. subtype 收窄([P3][sdk/shared/natives] 低危积压批次 #413②)SDKResultMessage.subtype 联合补入 'error_completion_guard' 字面量并删除 | string,与 main 上 ♻️ fix(sdk): engine 收敛批——惰性 token 估算/stableSerialize 单源/双循环合并/低危积压六项 #467 最终形态一致。
  3. sdk 导出面对齐:packages/sdk/src/index.ts 未被 🏗️ refactor(shared,sdk): 协议消息类型下沉单源,解除 shared↔sdk 包级循环依赖 #469 触碰,rebase 后自动继承 main 版本(无 session_state_changed 导出);engine.ts 四处发射点与 engine.test.ts、sidecar 测试 mock 同理随 main 自动对齐,无需手工移植。

冲突解决:packages/sdk/src/types.ts 以 #469 的 re-export 壳为准(#467 对该文件的全部改动均为删除性移植项,无独有新增)。

验证结论:

  • 全仓 grep SDKSessionStateChangedMessage 零残留;session_state_changed 仅剩 engine.test.ts 一行说明事件已退役的注释
  • bun run typecheck 六包全绿
  • @lume/shared 全套测试 185 pass / 0 fail
  • packages/sdk engine + agent 定向测试 110 pass / 0 fail

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.

🏗️ sdk,shared: @lume/sdk ↔ @lume/shared 包级循环依赖,manifest 双向声明

2 participants