
Claudian 中 Claude Provider 的架构设计基于 claude-agent-sdk 的执行、存储与历史管理【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian本文以 src/providers/claude/AGENTS.md 这份 Claude Provider 架构文档为主体系统梳理 Claudian一个将 Claude Code/Codex 嵌入 Obsidian 库的插件中 Claude 提供商层的职责边界、目录所有权、设计规则、存储规则、运行时陷阱与核心不变量并结合仓库源码逐条印证这些约束的实际落点帮助读者理解一个在 Electron/Obsidian 运行时上长期驻留 AI SDK 进程的插件层应当如何设计。读完本文你将掌握为什么原生 SDK 事件必须先归一化才能进入核心层、哪些变更会触发持久查询重启、.claude/settings.json的合并写入策略以及会话失忆amnesia与崩溃恢复的检测机制。1. Provider 层定位provider-neutral 契约上的 Claude 兼容层AGENTS.md 开篇即给出该目录的定位src/providers/claude/在anthropic-ai/claude-agent-sdk之上实现provider-neutral提供商无关的执行契约并在其外层叠加 Claude Code CLI 的兼容性处理。当前仓库 package.json 中锁定的 SDK 版本为anthropic-ai/claude-agent-sdk0.3.226这说明整个 Claude 提供商层构建在该 SDK 的原生事件模型之上而非直接调用 HTTP API。同目录下的 CLAUDE.md 只有一行AGENTS.md即把本 AGENTS.md 作为该模块唯一的 agent 指令源——这也是本仓库模块级文档的组织惯例。1.1 依赖边界Dependency Boundary文档明确了两条依赖边界规则归一化先行SDK 的原生事件events、选项options、transcript 记录与 provider 状态在越过边界进入 core 或 feature 契约之前必须被归一化。从源码结构看这一边界由多个专门模块承担src/providers/claude/sdk/types.ts、typeGuards.ts、messages.ts负责原生消息的带类型解释src/providers/claude/normalization/ClaudeTaskToolNormalizer.ts 与 src/providers/claude/stream/transformClaudeMessage.ts 负责消息流转换src/providers/claude/execution/ClaudeExecutionEventNormalizer.ts 在执行会话处完成事件适配。迁移接缝migration seams现有从 provider 兼容层 storage/types 导入到src/app/的引用属于迁移期接缝禁止新增当实质修改这些接缝时应把共享契约下沉到core/。这条规则的意义在于防止 app 层对 Claude 特有的存储结构产生新的耦合保证未来更换或增减 provider 时 app 层不受影响。2. 目录所有权Ownership六个子系统的职责划分AGENTS.md 的核心是一张所有权表把src/providers/claude/下的子目录划分为六个职责域。逐条对照源码目录可以确认其边界是真实落地的区域文档声明的职责源码印证execution/执行会话绑定、快照、事件适配、交互处理、恢复策略src/providers/claude/execution/ 中的ClaudeExecutionSession.ts、ClaudeExecutionBackend.ts、ClaudeInteractionHandler.ts、ClaudeExecutionStrategies.tsruntime/持久 SDK 查询、重启决策、消息通道行为、CLI 派生、原生 prompt 构造src/providers/claude/runtime/ 中的ClaudeSessionManager.ts、ClaudeMessageChannel.ts、customSpawn.ts、claudeColdStartQuery.ts、ClaudeUserMessageFactory.tshistory/只读的原生 transcript 发现、分支投影、历史模型恢复、rewind、subagent 回放src/providers/claude/history/ 中的ClaudeConversationHistoryService.ts、sdkBranchFilter.ts、ClaudeSessionRecovery.ts、ClaudeSubagentHistoryService.tsapp/、commands/、agents/、plugins/工作区作用域发现与 provider 原生目录ClaudeWorkspaceServices.ts、ClaudeCommandCatalog.ts、AgentManager.ts、PluginManager.tsstorage/仅管理文档中列明的 Claudian 托管部分Claude 兼容的 settings、command、skill、agent、plugin 文件src/providers/claude/storage/ 中的CCSettingsStorage.ts、SlashCommandStorage.ts、SkillStorage.ts、AgentVaultStorage.ts等types/Claude 拥有的 provider 状态的带类型解释与消毒src/providers/claude/types/ 中的providerState.ts、settings.ts、models.ts表中还有一条关键的原则性声明执行会话execution session拥有存活的 provider 快照历史服务history services负责重建回放状态但绝不能成为第二个活会话权威second live-session authority。换言之当前对话在 SDK 侧的真实状态只有一个权威来源——执行会话history/下的服务只能读原生 transcript 做投影不能反向改写或替代活会话的视图。这一单一权威约束与文末 Invariants 一节呼应是整个 Claude 层状态一致性的基石。3. 设计规则Design Rules持久查询与重启决策AGENTS.md 的 Design Rules 一节定义了五条运行时行为规则每一条都能在源码中找到对应实现。3.1 保持持久 SDK 查询存活规则原文Keep the persistent SDK query alive across turns when possible. Update model, permission mode, and effort through SDK calls.即跨轮次尽量保持同一个 SDK 查询进程存活模型、权限模式、effort 的变更通过 SDK 的调用而非杀进程重建来生效。这样能避免每轮都付出冷启动代价。从源码结构看claudeColdStartQuery.ts 只承担冷启动查询而日常轮次走持久通道src/providers/claude/execution/ClaudeExecutionSession.ts 则管理会话与查询的绑定。3.2 何时必须重启持久查询规则原文列出了六类必须重启的变更条件生效的系统提示词、被禁用的工具集、插件集、settings 来源集、CLI 路径、Chrome 启用状态、auto-mode 启用状态。可以推断重启判定逻辑位于 src/providers/claude/execution/ClaudeExecutionStrategies.ts 与运行时会话管理中它需要比较当前生效配置指纹与上一轮查询启动时的指纹一旦系统提示词或工具集等影响 SDK 子进程行为的参数漂移就必须终止旧查询、以新参数重新 spawn否则新设置不会真正作用于 SDK 侧的 agent 行为。3.3 fallback 模型是用户偏好而非硬编码规则指出Claude 的 provider fallback 模型是一种用户偏好需对当前动态模型选项包括环境映射选项与自定义选项解析新设置偏好 Opus 档偏好不可用时静默回退且不改变既有会话、也不改变全局的未来标签页种子。仓库源码印证了这一分层src/providers/claude/modelTiers.ts 定义了CLAUDE_MODEL_TIER_DEFINITIONS每个 tierhaiku / sonnet / opus / fable绑定一个环境变量键如ANTHROPIC_DEFAULT_OPUS_MODEL、环境优先级environmentPriority与 1M 上下文/xhigh 的版本门槛modelSelection.ts、modelOptions.ts 则在其上完成偏好与动态选项的解析。偏好不可用时回退而不污染既有会话的语义正是为了避免用户切模型时历史会话的模型归属被篡改。3.4 禁止重复助手文本去重规则Do not duplicate assistant text. The SDK can emit text incrementally and again in the final assistant message; stream handling must preserve the existing dedupe behavior.即 SDK 会先增量流出文本、再在最终 assistant 消息里整体重发一次流处理必须保留去重。从源码结构看去重逻辑落在 src/providers/claude/stream/transformClaudeMessage.ts负责消息形态转换、toolInputStreamState.ts维护工具流状态而 tests/unit/providers/claude/ 下有 41 个测试文件守护整个 provider 层的行为契约其中即包含针对该目录的单测。3.5 Token 用量合并策略规则Token usage is intentionally merged from assistant and result messages. Assistant messages provide accurate input-side counts; result messages provide authoritative context-window data.Token 统计被有意拆成两条来源再合并assistant 消息提供准确的输入侧计数result 消息提供权威的上下文窗口数据。这意味着任何只看单一消息类型的统计实现都会得到不完整数据改动用量展示时必须保持这一合并语义。3.6 自定义 spawn 函数规则createCustomSpawnFunction()handles Obsidian/Electron process quirks. Preserve full-pathnoderesolution and manual abort handling.该规则的实现是 src/providers/claude/runtime/customSpawn.ts其源码注释解释了两处 Electron 特殊处理全路径 node 解析SDK 只对部分脚本扩展名走node因此在 Electron 以shellfalse派生之前cliPathRequiresNode(command)判定的 Node 支撑路径要先归一化——command node时替换为findNodeExecutable(enhancedPath)得到的全路径否则把原命令挪进 args 首位、以解析出的 node 全路径作为新 command。手动 abort 处理源码注释明确写道——不能把signal直接传给spawn()因为 Obsidian 的 Electron 运行时使用不同的 realm 承载AbortSignal会导致 Node 内部的instanceof EventTarget检查失败因此改为监听signal的abort事件并手动child.kill(SIGTERM)。另有installTreeAwareKill包装child.kill在 Windows cmd shim 场景下支持进程树终止当DEBUG_CLAUDE_AGENT_SDK环境变量存在时还会接管 stderr 管道避免 Electron 下 stderr 阻塞。4. 存储规则Storage Rules与 Claude Code 共存的文件边界这是 AGENTS.md 中最具共享文件系统治理色彩的一节Claudian 与 Claude Code 原生 CLI 同时读写用户的~/.claude与 vault 内的.claude/必须严格划分各自的地盘。4.1.claude/settings.json合并写入规则CCSettingsStorage.save()must merge with existing.claude/settings.json; Claudian only owns permissions and plugin enablement.实现见 src/providers/claude/storage/CCSettingsStorage.ts。其save()的行为与文档完全对应先读取既有.claude/settings.json常量CC_SETTINGS_PATH .claude/settings.json解析失败时抛出NotifiedMutationError并弹出 ObsidianNotice——拒绝覆盖非法 JSON防止误伤 Claude Code 写入的字段合并策略为...existing 受管字段保留所有不认识的字段Preserve CC-specific fields we dont manage只覆写$schema、permissions以及可选的enabledPlugins。类还暴露了权限规则的细粒度 APIaddAllowRule/addDenyRule/addAskRule去重追加与removeRule三列表统一过滤以及setPluginEnabled。normalizePermissions会对 allow/deny/ask 列表做字符串过滤消毒——这正对应 Ownership 表中types/的职责带类型解释与消毒。4.2.claude/mcp.jsonClaude Code 独占Claudian 只删不碰规则Claude Code owns MCP configuration, authentication, health checks, and connection lifecycle through its native CLI and settings scopes. At application storage initialization, the composition root invokes the Claude-owned legacy cleanup to delete.claude/mcp.json; no other Claudian code may read, write, inject, or migrate that path.即 MCP 配置、鉴权、健康检查、连接生命周期全部归 Claude Code 原生 CLI 管Claudian 唯一被允许的动作是在存储初始化阶段执行一次遗留清理删除历史版本遗留的.claude/mcp.json其余任何 Claudian 代码不得读写该路径。实现位于 src/providers/claude/storage/LegacyMcpConfigCleanup.ts。这条规则划清了一条硬边界Claudian 不再做 MCP 的影子管理器。4.3 插件启用状态双写规则Plugin enabled state is dual-written to.claude/settings.jsonandPluginManager.plugins[].enabled. Keep both in sync.即插件启用位同时写入.claude/settings.json的enabledPlugins见上文CCSettingsStorage.setPluginEnabled与 src/providers/claude/plugins/PluginManager.ts 维护的plugins[].enabled两处必须同步——前者供 Claude Code CLI 识别后者供 Claudian 内部投影。4.4 原生 transcript 的发现路径规则Native transcripts are read from{CLAUDE_CONFIG_DIR:-~/.claude}/projects/{vault}/; resolve the config dir throughresolveClaudeConfigDir, never hardcode~/.claude.两处源码严格实现了该规则src/providers/claude/config/ClaudeConfigDir.ts 的resolveClaudeConfigDir()优先读环境变量CLAUDE_CONFIG_DIR未设置时回落到按平台解析的主目录Windows 依次取USERPROFILE、HOMEDRIVE HOMEPATH否则$HOME再兜底os.homedir()拼接.claude相对路径则相对 vault 路径解析并做 NFC 归一化。src/providers/claude/history/sdkSessionPaths.ts 的getSDKSessionPath()transcript 位于{configDir}/projects/{编码后的 vault 路径}/{sessionId}.jsonl其中encodeVaultPathForSDK()把 vault 绝对路径中所有非字母数字字符替换为-replace(/[^a-zA-Z0-9]/g, -)与 SDK 的目录命名规则一致isValidSessionId()用isPathSafeId()防御路径穿越长度 ≤128、不含../分隔符、仅允许[a-zA-Z0-9_-]。locateSDKSessions()还能在标准路径缺失时对projects/目录做广度扫描区分available/relocated/missing/unknown四种可用性——这对应文档 Ownership 表中history/的只读原生 transcript 发现。4.5 历史模型恢复与斜杠命令编码历史所选模型恢复Historical selected-model recovery returns a provider-qualified model only from a valid active-branch checkpoint. For multi-segment conversations, the checkpoint-bearing or latest authoritative segment must resolve; do not silently fall back to an older segments model or make the recovery locator resumable.即多段会话恢复时必须解析到带 checkpoint 的分段或最新的权威分段禁止静默回退到更老分段的模型也禁止把恢复定位器做成可续跑的——防止 UI 上显示一个与历史实际不符的模型标签。相关逻辑在 src/providers/claude/history/ClaudeSessionRecovery.ts 与 ClaudeConversationHistoryService.ts。斜杠命令 ID 编码Slash command IDs use reversible encoding: dashes become-_, slashes become--.即把-编码为-_、/编码为--保证命令 ID 在作为标识符如存储键时仍可无损还原实现见 src/providers/claude/storage/SlashCommandStorage.ts。5. 运行时陷阱Runtime Gotchas六条踩坑记录AGENTS.md 把 SDK 集成的六个暗坑写成显式规则每条都对应一个具体的恢复/缓冲机制SDK 失忆检测SDK amnesia is detected when the returned session ID differs from the resume ID. The next turn injects full conversation history unless this is the firstsession_initafter a fork.即当 SDK 返回的 sessionId 与请求恢复resume的 sessionId 不一致时判定 SDK 丢失了会话上下文下一轮将注入完整对话历史——唯一例外是 fork 之后的首个session_init此时换 ID 是预期行为。src/providers/claude/runtime/ClaudeSessionManager.ts 的captureSession()正是该检测的状态机落点hadSession isDifferent时置needsHistoryRebuild true由后续轮次消费consumeInvalidation()、clearHistoryRebuild()提供一次性消费语义并注释说明SDK lost our session context - need to rebuild history on next message。崩溃恢复只重试一次Crash recovery retries once only when the previous send produced no chunks.只有当上一次发送一个 chunk 都没产出时才允许重试且仅一次——已产出部分内容的发送不能盲目重放否则会制造重复输出。自动触发的 SDK 轮次Auto-triggered SDK turns can arrive without a registered handler; they buffer until the result event.SDK 可能自发产生没有已注册处理者的轮次事件须先缓冲、直到 result 事件再统一处理避免无主事件丢失或错乱。MessageChannel 的合流策略MessageChannel coalesces text-only queued messages and keeps only one queued attachment message.src/providers/claude/runtime/ClaudeMessageChannel.ts 对排队的纯文本消息做合并且队列中最多保留一条带附件的消息——防止用户快速连发多条消息时向 SDK 灌入冗余消息。会话文件是树状的Claude session files are tree-structured. Branch filtering must preserve the canonical branch plus relevant sibling tool results.原生.jsonltranscript 实际是树结构fork/rewind 产生分支分支过滤必须保留规范分支加上其相关的兄弟工具结果否则工具调用与结果会被切断。实现见 src/providers/claude/history/sdkBranchFilter.ts 与 sdkMessageParsing.ts。上下文窗口选择的多模型歧义Context-window selection must handle multi-model runs by exact model match first, then family match, and null on ambiguity.一次会话内可能混用多个模型时上下文窗口参数须先按模型全名精确匹配再按模型家族匹配仍歧义则返回 null不猜——而不是取第一个命中。6. 不变量Invariants两条不可妥协的底线文档以两条不变量收束全文重启或恢复查询必须保持预期的会话绑定且不得产生重复的可见输出。这是对 3.2 节重启决策与 5 节崩溃恢复/失忆重建的总括约束任何重建路径amnesia 历史重注、崩溃重试、配置漂移重启都不得让用户看到双份的助手消息也不得把会话绑到错误的对话上。Provider 快照是存活 SDK 状态 → Claudian 持久化 resume 状态的唯一通路。结合 2 节执行会话拥有存活快照的原则可以推断出完整的状态流SDK 实时状态 → 执行会话快照 → 持久化 resume 状态中间不允许旁路直写这正是历史服务不得成为第二权威的持久化侧投影。快照的类型契约位于 src/providers/claude/types/providerState.ts。7. 小结一份约束文档如何约束住一个 SDK 集成层回过头看src/providers/claude/AGENTS.md 的价值不在于描述做什么而在于固化为什么这么做边界层SDK 原生类型归一化后才准进入 core/featuresdk/、normalization/、stream/三个目录就是边界的物化状态层执行会话是活状态的唯一权威history 只读重建快照是唯一持久化通路Ownership Invariants 双锁生命周期层持久查询能活则活六类配置漂移强制重启失忆/崩溃各有检测与恢复策略Design Rules Runtime Gotchas;文件治理层与 Claude Code 共享~/.claude与 vault 内.claude/时Claudian 只拥有 permissions 与 plugin 启用位MCP 配置彻底让渡给原生 CLIStorage Rules。对这些约束感兴趣的读者可以沿本文给出的路径深入execution/与会话恢复策略ClaudeExecutionStrategies.ts、transcript 树解析src/providers/claude/history/、Electron spawn 特殊处理customSpawn.ts以及 tests/unit/providers/claude/ 下的大量单元测试——它们共同构成了这套 provider 层行为的可验证依据。【免费下载链接】claudianAn Obsidian plugin that embeds Claude Code/Codex as an AI collaborator in your vault项目地址: https://gitcode.com/GitHub_Trending/cl/claudian创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考