深入 Cline SDK 的 @cline/shared 共享原语包:路径解析、会话配置、日志契约与跨客户端 DTO
本文基于 Cline 仓库中 @cline/shared 包的官方说明文档,系统讲解这个实验性(experimental)共享包的核心职责:它如何以 ./storage 等子路径导出 Node 端文件系统路径解析器、如何定义跨客户端统一的 BasicLogger 日志契约、如何集中 AgentMode/SessionPromptConfig 等会话配置原语、以及它是如何承载 @cline/cli 与宿主应用之间共享的聊天/Provider RPC 载荷 DTO 的。读完本文,你可以在基于 Cline SDK 构建宿主、运行时或 IDE 扩展时,正确复用这些跨包契约,避免重复定义类型与重复推导数据目录。
包定位:跨包原语的统一出口
@cline/shared 在 sdk/packages/shared/README.md 中被标注为 experimental 包,其定位是"own shared cross-package primitives",即持有跨包共享的原语(session common types/utilities)。它不是某个具体功能的实现包,而是 Cline 各包(core、agents、CLI、宿主应用)之间的契约层与工具层:日志类型、会话配置形状、hook 会话上下文、运行时载荷 DTO 都在这里集中定义,避免每个宿主重复定义相似字段。
从 sdk/packages/shared/package.json 可以看到该包的工程形态:
- 多子路径导出(exports 字段):
@cline/shared按子路径拆分为多个独立入口,宿主可以只引入自己需要的部分:.(根入口,同时区分browser条件导出./dist/index.browser.js与 Node 入口./dist/index.js)./browser、./types、./storage、./db、./node、./automation、./remote-config
- 运行环境:
engines.node >= 22,构建脚本为BUILD_MODE=package bun bun.mts,单元测试走vitest run --config vitest.config.ts。 - 依赖极窄:仅
aws4fetch、jsonrepair、zod、zod-to-json-schema四个运行时依赖,符合"共享原语包"不引入重依赖的设计取向。
README 中的两条中央文档入口也值得保留:包级总览见 sdk/packages/README.md,架构与交互见 sdk/ARCHITECTURE.md。
storage 子路径导出:Node-only 文件系统路径解析器
README 明确指出:Node-only 的文件系统路径解析器位于 @cline/shared/storage 子路径导出下,代表性函数为 resolveClineDataDir、resolveDbDataDir、resolveSessionDataDir、resolveTeamDataDir。之所以单独走 ./storage 子路径而不是根入口,正是因为这些函数依赖 node:fs/node:path 等 Node 运行时能力,不能进入浏览器构建(对应 package.json 中 . 入口的 browser 条件导出走的是 index.browser.js,而 ./storage 只声明了 types + import 两个条件)。
实现位于 sdk/packages/shared/src/storage/paths.ts,其核心模式高度一致:优先读取显式环境变量,否则从 Cline 数据根目录派生。以四个代表性函数为例(paths.ts L179-L245):
export function resolveClineDataDir(): string {
const explicitDir = process.env.CLINE_DATA_DIR?.trim();
if (explicitDir) {
return explicitDir;
}
return join(resolveClineDir(), "data");
}
export function resolveSessionDataDir(): string {
const explicitDir = process.env.CLINE_SESSION_DATA_DIR?.trim();
if (explicitDir) {
return explicitDir;
}
return join(resolveClineDataDir(), "sessions");
}
// resolveTeamDataDir -> CLINE_TEAM_DATA_DIR,默认 <data>/teams
// resolveDbDataDir -> CLINE_DB_DATA_DIR,默认 <data>/db
整理成一张可操作的参数表:
| 函数 | 环境变量覆盖 | 默认值 |
|---|---|---|
resolveClineDataDir() |
CLINE_DATA_DIR |
<ClineDir>/data |
resolveSessionDataDir() |
CLINE_SESSION_DATA_DIR |
<data>/sessions |
resolveTeamDataDir() |
CLINE_TEAM_DATA_DIR |
<data>/teams |
resolveDbDataDir() |
CLINE_DB_DATA_DIR |
<data>/db |
resolveConnectorDataDir() |
CLINE_CONNECTOR_DATA_DIR |
<data>/connectors |
这套"环境变量可覆盖 + 目录层级派生"的设计有一个直接收益:CLI、hub 守护进程、IDE 宿主等所有客户端只要遵守同一组环境变量约定,就能落到同一份数据目录上,无需各自实现路径推导。sdk/packages/shared/src/storage/paths.test.ts 中的用例印证了这一点,例如设置 CLINE_DATA_DIR=/tmp/cline-data 后断言 resolveClineDataDir() 为 /tmp/cline-data、resolveSessionDataDir() 为 /tmp/cline-data/sessions、resolveTeamDataDir() 为 /tmp/cline-data/teams、resolveDbDataDir() 为 /tmp/cline-data/db。
paths.ts 中还有一组围绕 connector 的路径解析,例如 resolveConnectorLogPath(channel, instanceKey) 会把非法字符替换为 _ 后落到 <data>/logs/connectors/<channel>/<key>.log——源码注释解释了它放在共享包的原因:CLI(直接 spawn 分离 connector)与 hub supervisor(spawn 并回收 connector)必须对该日志路径达成一致,所以它"住在两边都不属于的公共位置"。
附赠能力:容错 Unicode 文件名解析
同目录下的 sdk/packages/shared/src/storage/path-resolution.ts 提供了一个值得注意的健壮性工具 resolveExistingFilePath:macOS(Sonoma 及以后)在截屏文件名 AM/PM 前插入 U+202F 窄不换行空格,当路径经过剪贴板、终端、粘贴解码层后被归一化成普通空格时,字面 fs.stat 会抛 ENOENT。该函数按顺序尝试 macOS AM/PM 变体、NFD 归一化变体、弯引号(U+2019)变体,最后回退到父目录枚举做"规范空白折叠"匹配,从而把被"打伤"的路径恢复到真实磁盘条目。对需要在多种客户端间传递文件路径的 Cline 宿主来说,这是一个典型的"共享包收编边角坑位"的例子。
BasicLogger:跨客户端日志契约
README 的第二块内容是跨客户端日志契约:@cline/shared 导出 BasicLogger,让 runtime、SDK 与宿主应用共享同一个 logger 类型。实现只有 45 行,位于 sdk/packages/shared/src/logging/logger.ts:
export interface BasicLogger {
/** 冗长诊断;宿主应在非调试模式下 no-op 或过滤 */
debug: (message: string, metadata?: BasicLogMetadata) => void;
/** 运维消息(取代旧版 info / 非 error warn 的拆分) */
log: (message: string, metadata?: BasicLogMetadata) => void;
error?: (
message: string,
metadata?: BasicLogMetadata & { error?: unknown },
) => void;
}
设计要点有三:
- 刻意保持"最小接口"。
debug/log必填,error可选——源码注释说明,宿主如果没有独立 error 通道,可以走log并在BasicLogMetadata.severity中标"error"。这解释了为什么 metadata 里有一个severity?: "info" | "warn" | "error"字段:它是给那些把单一log方法映射到多级输出(如 Pino 的 info vs warn)的后端做消歧用的。 BasicLogMetadata约定了一批跨组件查询键:sessionId、runId、providerId、toolName、durationMs,同时继承Record<string, unknown>允许宿主自由扩展。noopBasicLogger作为全 no-op 安全默认值,注入缺失时可直接兜底。
这种"接口 + 约定字段 + no-op 兜底"的三件套,使得宿主可以把自己的 logger(pino、VS Code 的日志 API 等)适配进来,而 SDK 内部代码永远只面向 BasicLogger 编程。
会话配置原语:AgentMode 与三个 Session 配置接口
README 强调:会话配置原语被集中在此,"so hosts/runtimes can compose one base shape instead of redefining similar fields repeatedly"(宿主/运行时组合出一个基础形状,而不是反复重定义相似字段)。实现位于 sdk/packages/shared/src/session/runtime-config.ts,四个原语在 sdk/packages/shared/src/index.ts 的 L568-L581 统一从根入口导出。
export type AgentMode = "act" | "plan" | "yolo" | "zen";
export interface SessionPromptConfig {
mode?: AgentMode;
systemPrompt?: string;
rules?: string;
maxIterations?: number;
}
export interface SessionWorkspaceConfig {
cwd: string;
workspaceRoot?: string;
}
export interface SessionExecutionConfig {
enableTools: boolean;
teamName?: number | undefined extends undefined ? string : never; // 实际为 teamName?: string
missionLogIntervalSteps?: number;
missionLogIntervalMs?: number;
maxConsecutiveMistakes?: number;
toolPolicies?: Record<string, ToolPolicy>;
}
(上面 SessionExecutionConfig 按源码原样为 teamName?: string,toolPolicies 即 README 所说的"canonical ToolPolicy map shape",ToolPolicy 类型来自 sdk/packages/shared/src/llms/tools.ts。)
三个接口分工清晰:
SessionPromptConfig:提示词维度——模式、系统提示词、rules、最大迭代数;SessionWorkspaceConfig:工作区维度——cwd必填、workspaceRoot可选;SessionExecutionConfig:执行维度——工具开关(唯一必填的enableTools)、团队名、任务日志节奏、连续错误上限、按工具名的策略表。
同一文件还定义了运行期"配置扩展"原语,供宿主裁剪默认加载的扩展类型:
export type RuntimeConfigExtensionKind =
| "rules" | "skills" | "workflows" | "plugins" | "hooks";
export const DEFAULT_RUNTIME_CONFIG_EXTENSIONS =
RUNTIME_CONFIG_EXTENSION_KINDS; // 默认全部启用
并配套 isRuntimeConfigExtensionKind(类型守卫)、parseRuntimeConfigExtensions(从未知输入过滤去重出合法 kind 数组)、hasRuntimeConfigExtension(未显式指定时按"默认全开"判断)三个辅助函数。
会话标识方面,sdk/packages/shared/src/session/index.ts 提供 createSessionId(prefix, suffix):用 nanoid 的小写字母数字字母表生成 5 位随机串,拼上毫秒时间戳,形如 <prefix><ts>_<nanoid5><suffix>,时间戳在前保证可排序,随机后缀避免并发碰撞。
Hook 会话上下文原语
README 声明该包还导出 hook 会话上下文原语,"used across agents/core/CLI":HookSessionContext、resolveHookSessionContext(...)、resolveRootSessionId(...)、resolveHookLogPath(...)。
当前源码中前两者的实现在 sdk/packages/shared/src/session/hook-context.ts,并由 sdk/packages/shared/src/index.ts L552-L560 导出:
export interface HookSessionContext {
rootSessionId?: string;
}
export type HookSessionContextProvider =
| HookSessionContext
| ((input?: HookSessionContextLookup) => HookSessionContext | undefined);
export function resolveHookSessionContext(
provider?: HookSessionContextProvider,
input?: HookSessionContextLookup,
): HookSessionContext | undefined
resolveHookSessionContext 的设计点在于:provider 既可以是静态上下文对象,也可以是一个函数(由调用方在 hook 触发时惰性求值,HookSessionContextLookup 提供 hookName/conversationId/agentId/parentAgentId 供求值使用);解析结果会对 rootSessionId 做 trim 归一化,空值统一收敛为 undefined。resolveRootSessionId 则是从上下文中取根会话 ID 的轻量辅助。
需要如实说明的是:在检索当前仓库源码时,resolveHookLogPath 仅在 sdk/packages/shared/README.md 中出现,当前 session/hook-context.ts 与根入口导出表中未见同名函数——从源码结构看,该函数可能已被移除或改名,而 hook 日志路径目前以事件载荷字段的形式存在:sdk/packages/shared/src/hooks/events.ts L277-L282 的 HookEventPayloadSchema 中,sessionContext 对象包含可选的 rootSessionId 与 hookLogPath 两个字符串字段。也就是说,"hook 写日志的位置"这一信息通过 hook 事件 schema 的 sessionContext.hookLogPath 在 hook 执行侧传递。
跨客户端运行时载荷 DTO:rpc/runtime 契约
README 后半部分说明:@cline/shared 还导出跨客户端运行时载荷 DTO,供多个宿主(@cline/cli、@cline/code)复用,"so request/response contracts are not duplicated outside transport wiring"(请求/响应契约不在传输接线之外重复定义)。实现集中在 sdk/packages/shared/src/rpc/runtime.ts,共 468 行,根入口 L412-L456 将其类型全部转出。
聊天运行时载荷
export interface ChatRuntimeConfig extends SessionPromptConfig {
cwd?: string;
apiKey?: string;
logger?: RuntimeLoggerConfig;
enableTools: boolean;
enableSpawn?: boolean;
enableTeams?: boolean;
disableMcpSettingsTools?: boolean;
autoApproveTools?: boolean;
missionStepInterval?: number;
missionTimeIntervalMs?: number;
timeoutSeconds?: number;
toolPolicies?: SessionExecutionConfig["toolPolicies"];
toolExecutors?: HubToolExecutorName[];
configExtensions?: RuntimeConfigExtensionKind[];
}
export interface ChatStartSessionRequest extends ChatRuntimeConfig {
sessionId?: string;
workspaceRoot: string;
provider: string;
model: string;
source?: string;
interactive?: boolean;
}
可以看到 RPC 层与上文会话原语是组合关系:ChatRuntimeConfig extends SessionPromptConfig(因此继承 mode/systemPrompt/rules/maxIterations),toolPolicies 直接复用 SessionExecutionConfig["toolPolicies"] 的类型,configExtensions 复用 RuntimeConfigExtensionKind[]。这正是 README 所说"compose one base shape"的落地:会话配置原语在 RPC 契约中被二次组合,而非复制。
README 提到的 initialMessages、可选 toolPolicies、用于默认系统提示词组装的可选 rules,以及让宿主把序列化日志配置跨传输边界传过去的可选 logger(RuntimeLoggerConfig),在源码中均可对应到字段:
export interface RuntimeLoggerConfig {
enabled?: boolean;
level?: "trace" | "debug" | "info" | "warn" | "error" | "fatal" | "silent";
destination?: string;
name?: string;
bindings?: Record<string, string | number | boolean>;
}
其中 bindings 是 README 特别点名的能力:宿主可以为所有运行时日志记录附加稳定的上下文字段(例如 clientId、clientType、clientApp),从而让远端 runtime 产出的日志在宿主侧可按客户端维度检索。
回合与结果侧同样有完整契约:ChatRunTurnRequest 携带 config(一个完整的 ChatStartSessionRequest)、prompt、可选 attachments(userImages: string[] 与 userFiles: ChatAttachmentFile[])以及 delivery?: "queue" | "steer";ChatTurnResult 返回 text、带缓存 token 与成本信息的 usage(inputTokens/outputTokens/cacheReadTokens?/cacheWriteTokens?/totalCost?)、iterations、finishReason 以及每次工具调用的明细数组 toolCalls: ChatToolCallResult[](含 name/input/output/error/durationMs)。同一文件还定义了 ChatStartSessionArtifacts(sessionId/manifestPath/messagesPath),说明启动会话会落盘 manifest 与消息文件并返回其路径。
Provider 运行时载荷
Provider 侧契约覆盖目录查询、模型操作与增改 Provider:ProviderActionRequest、ProviderCatalogResponse、ProviderOAuthLoginResponse,以及 README 点名的细粒度复用契约 AddProviderActionRequest、SaveProviderSettingsActionRequest、ProviderCapability(由 ProviderCapabilitySchema 推导)与 OAuth 相关的 Provider ID 类型。它们集中在 runtime.ts L271-L414 附近,ProviderSettingsActionRequest 等联合类型把目录/模型操作与 settings 宿主的 add/save 操作统一在一棵类型树下——这意味着一个实现 ProviderClient 的宿主可以处理整个 Provider 操作面,而无需为每个 action 单独定义 DTO。
使用建议:把 @cline/shared 当作契约层消费
基于以上源码事实,对要在 Cline 生态里构建宿主或 SDK 集成的开发者,有三条可直接落地的实践:
- 数据目录不要自己拼路径。凡是涉及会话、团队、数据库、connector 落盘,一律调用
@cline/shared/storage的resolve*函数并遵守CLINE_DATA_DIR等环境变量约定,保证多客户端数据一致(实现见 sdk/packages/shared/src/storage/paths.ts,回归见 paths.test.ts)。 - 日志注入统一实现
BasicLogger。宿主把 pino/VS Code logger 适配成{ debug, log, error? },缺省用noopBasicLogger;跨传输边界传日志配置时用RuntimeLoggerConfig,并善用bindings打上clientId等稳定维度。 - RPC 契约不要本地复刻。跨客户端的请求/响应一律引用
ChatStartSessionRequest、ChatRunTurnRequest、ChatTurnResult、ProviderActionRequest等导出类型;需要裁剪默认加载的扩展时用parseRuntimeConfigExtensions/hasRuntimeConfigExtension,保持与 core 相同的"默认全开"语义。
需要说明的适用前提:该包在 README 中自标 experimental,当前版本为 package.json 中的 0.0.82,要求 Node 22+;对 resolveHookLogPath 等 README 与源码存在出入的导出,以仓库当前 src/ 与根入口的实际导出为准。包级全景(core/llms/agents 各包职责与交互)可继续阅读 sdk/packages/README.md 与 sdk/ARCHITECTURE.md。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00