首页
/ CC Switch 的 Pi 原生契约与实现边界:models.json、提示词与会话管理的可验证集成指南

CC Switch 的 Pi 原生契约与实现边界:models.json、提示词与会话管理的可验证集成指南

2026-09-06 18:17:17作者:董宙帆

CC Switch 对 Pi Agent 的集成遵循"最小契约 + 明确边界"原则:只管理 models.json 中显式存在的供应商节点,只读全局默认项,绝不触碰 auth.json 与登录态。本文档即该契约的权威说明——它既不是 Pi 配置格式的完整镜像,也不承诺代理、OAuth 或全部兼容字段,实现与测试只覆盖前端真正提供的能力。

验证原则贯穿全文:开发和验收均使用当时最新发布的 Pi,CC Switch 不绑定任何特定 Pi 版本。供应商字段、继承顺序和配置值解析,以开发时最新 Pi 的源码入口和真实 CLI 行为为准;资源与会话行为则通过 Pi 的 DefaultResourceLoaderloadSkillsloadPromptTemplatesSessionManager 实际运行确认。仓库只保留产品真正消费的最小契约和普通测试,不维护上游源码快照、哈希或版本证据库。

当前消费的契约全景

下表是 CC Switch 与 Pi 原生资源之间的完整契约矩阵,逐条对应产品实际行为与状态来源:

资源 CC Switch 行为 状态来源
models.json 管理 providers 中的全部显式供应商节点;精确新增、替换和移除 文件中的实际条目
全局 settings.json 只读 defaultProviderdefaultModelsessionDir Pi 原生设置
auth.json 不读、不写、不刷新 Pi /login
AGENTS.md 提示库中与文件内容精确匹配的项视为正在使用 文件存在及内容
SYSTEM.mdAPPEND_SYSTEM.md 直接编辑固定原生文件;不存在即未配置 文件存在
prompts/*.md 管理顶层斜杠命令模板;空模板是有效原生文件 文件存在
skills/<目录> 目录存在即被 Pi 发现 原生 Skills 目录
Sessions JSONL 读取 Pi 的会话头、树分支、消息和会话名称 原生会话文件

从后端实现看,这一契约由 src-tauri/src/pi_config/mod.rs 的注释明确声明:"Pi owns account login and the active provider/model in settings.json. CC Switch only manages explicit provider entries in models.json."(Pi 拥有账号登录以及 settings.json 中的活动供应商与模型,CC Switch 只管理 models.json 中的显式供应商条目)。这是理解整个集成设计的纲领性句子。

供应商:只管理显式节点

表单只验证并编辑常用字段

Pi 的结构化表单只验证和编辑以下两类字段,其余原样保留:

  • 供应商级:namebaseUrlapiKeyapiheaders
  • 模型级:idnamereasoninginputcontextWindowmaxTokens

供应商是否"可管理"只取决于一个条件:节点是否显式存在于 models.json.providers。这意味着 anthropicopenaideepseek 等 Pi 内置 ID,乃至携带未知字段的未来节点,都一律按普通显式配置同步。这一点在 pi_config/mod.rs 的测试中得到了明确固化:validate_provider_node("anthropic", &json!({})) 被断言为合法——即使它是 Pi 的内置 key,只要显式出现就可以被管理;而空 key 与非法值类型会被拒绝。

// 摘自 src-tauri/src/pi_config/mod.rs(简化示意)
pub(crate) fn validate_provider_node(provider_key: &str, config: &Value) -> Result<(), AppError> {
    if provider_key.trim().is_empty() {
        return Err(AppError::InvalidInput("Pi provider key cannot be empty".to_string()));
    }
    config.as_object().ok_or_else(|| {
        AppError::InvalidInput("Pi provider configuration must be an object".to_string())
    })?;
    Ok(())
}

同文件的 provider_node_accepts_unknown_native_fields 测试(pi_config/mod.rs)证实:即使节点里带有 sdkOption、模型级 compat 等 CC Switch 不认识的字段,也会被当作"可持久化的合法节点"接受。这正是"已有配置中的其他字段原样保留"的代码级保证。

不做的三件事

  • 不把 Pi 运行时合并出的内置模型复制回配置:Pi 通过继承/合并机制暴露的内置模型不会写回 models.json,CC Switch 只同步显式书写的节点。
  • 不解析或执行 apiKey、Header 中的环境变量与命令表达式:这类字符串原样存储,展开交给 Pi 运行时自行处理,请求也始终由 Pi 自己发出。
  • 不写入默认项:启用供应商只把条目加入 models.json,绝不会触碰 defaultProviderdefaultModel

默认供应商与模型的关系

Pi 在全局设置中保存的当前供应商和模型不进入 CC Switch 的供应商列表状态。移除或删除全局默认供应商时,前端只在原有确认框内给出"非阻塞提醒",后端允许继续操作且不改写默认项;编辑显式供应商同样不修改当前供应商或模型——失效引用和回退完全由 Pi 原生处理。

全局设置 vs 项目级设置

需要特别区分两个层级:

  • 项目级 .pi/settings.json 会在启动 Pi 时按工作目录覆盖全局默认项。由于 CC Switch 的供应商页没有项目上下文,它不扫描项目目录、不猜测活动会话,条件提醒只读取全局默认供应商;项目级默认项继续由对应 Pi 会话和 /model 命令管理。
  • 全局默认项只读。前端状态服务在 src-tauri/src/services/pi_state.rs 中通过 PiStateService::current 聚合两路信息:enabled_provider_ids(来自 read_pi_native_providers(),即 models.json 的全部显式 key)与 default_provider_id(来自 read_pi_native_defaults(),即全局 settings.jsondefaultProvider,用于 UI 条件提醒)。

其中 pi_config/mod.rsread_pi_native_defaults 只提取三个字段:defaultProviderdefaultModelsessionDir,且要求 settings.json 根节点必须是对象、目标字段必须是字符串,否则报配置错误。值得注意的是,pi_state.rs 的测试 invalid_global_settings_do_not_hide_provider_membership 证明:即使全局 settings.json 损坏(例如内容为 []),也不会影响 models.json 供应商成员身份的读取——坏设置只会让默认供应商提醒静默失效,绝不拖垮主流程。

Pi agent 目录的解析顺序

所有原生文件都位于"Pi agent 目录"下。后端按如下优先级解析该目录(pi_config/mod.rs):

  1. CC Switch 自身设置中的 Pi override 目录(settings::get_pi_override_dir(),来源标记为 "Pi settings override");
  2. 环境变量 PI_CODING_AGENT_DIR
  3. 默认路径:~/.pi/agentget_home_dir().join(".pi").join("agent"))。

测试 settings_directory_precedes_the_environmentrelative_agent_directory_is_rejectedpi_config/mod.rs)分别验证了"设置优先于环境变量"和"相对目录必须被拒绝"两条规则。models.jsonsettings.jsonAGENTS.mdSYSTEM.mdAPPEND_SYSTEM.mdprompts/*.md 都基于该根目录定位。

用量查询脚本属于元数据

用量查询脚本属于 CC Switch 自身元数据(对应 src-tauri/src/commands/pi.rs 中的 update_pi_provider_usage_script),只更新本地数据库,不重写 models.json——用量统计与 Pi 原生配置始终解耦。

并发与外部修改:成员身份、revision 与原子写

供应商启用 = 成员身份

供应商标识就是成员身份:models.json.providers 中存在该标识即视为"已启用"。CC Switch 在每次进入列表以及应用启动时同步全部显式节点,不按内置 ID、认证字段或模型完整度过滤。状态测试 pi_state.rs 构造了四种节点——普通显式配置、带 oauth 字段的节点、空对象的内置 ID anthropic、仅含 futureField 的未知节点——并断言它们全部出现在 enabled_provider_ids 中。

同步规则可以概括为三条:

  • 外部修改同步:原生配置被修改后,用原生内容更新同 ID 的已保存档案(覆盖 CC Switch 数据库中的卡片和完整配置);
  • 外部删除降级为停用:原生节点被删除,只改变启用状态(不再视为已启用);
  • 移除前保留档案:移除操作前会保存目标节点的最新内容,因此数据库中的卡片和完整配置继续保留,可随时重新启用。

写路径的并发防线

models.json、系统提示文件与模板文件均采用原子写入,并配有三层保护:

  1. 进程内串行化:所有写操作通过静态 MutexMODELS_FILE_LOCKPROMPT_FILE_LOCK)串行执行,避免同进程并发读改写互相覆盖。
  2. revision 比较:同一"读-改-写"周期内的跨进程变更,通过 SHA-256 revision 识别。写入前会重新计算文件当前 revision,与读取时保存的期望值比对,不一致即返回 AppError::Conflict。相关实现见 pi_config/mod.rspi_prompt_files.rsensure_revision
  3. 精准替换:列表刷新先把外部修改同步到数据库;保存时只替换目标 provider 节点,启用/移除只增删目标节点,其他 provider 与顶层字段保持不变——绝不整文件覆盖写。

stale_models_revision_does_not_overwrite_an_external_edit 测试(pi_config/mod.rs)还原了典型场景:读取时拿到 revision A,随后 Pi 或其他编辑器写入新内容(revision B),此时 CC Switch 携带过期的 A 去写——写入被拒绝,文件保持为外部的 B 内容。这保证了"绝不覆盖他人编辑"。

AGENTS.mdpi_prompt_files.rs 提供了 PiAgentsFileGuard:其守卫跨越"读取 + 数据库更新 + 原子替换"的完整生命周期,调用方可以拿"替换前的文件 revision"与数据库写回做对照,若期间文件被外部改动,就回滚数据库写入。所有原生文件还有统一的 1 MiB 读取上限MAX_PI_FILE_BYTES / MAX_PROMPT_FILE_BYTES),超限文件在加载前即被拒绝。

不再维护的东西也值得明确:编辑快照和额外的 ownership 状态已被移除——所有者身份完全由 models.json 的实际内容推导,不依赖任何旁路记录。

指令文件与提示词库

Pi 的系统提示与斜杠命令通过固定文件组织,CC Switch 对这些文件的处理非常直接:

文件 定位 语义
AGENTS.md agent 目录根 提示库中与该文件内容精确匹配的项视为"正在使用"
SYSTEM.md agent 目录根 固定系统提示覆盖文件,直接编辑;不存在即未配置
APPEND_SYSTEM.md agent 目录根 系统提示追加文件,直接编辑;不存在即未配置
prompts/<slug>.md agent 目录/prompts 顶层斜杠命令模板,空模板是合法原生文件

SYSTEM / APPEND_SYSTEM 的约束

值得注意的细节是:SYSTEM.mdAPPEND_SYSTEM.md 存在与否被当作"是否已配置"的标志,因此删除文件才是停用手段——内容为空的文件并不等于未配置。后端的 validate_instruction_content 明确拒绝空内容("Pi instruction cannot be blank; remove the file to deactivate it",见 pi_prompt_files.rs),但 validate_content_size 却放行空模板("empty templates are native"),二者形成了互补的契约:系统提示文件要求非空,而斜杠命令模板允许为空文件(一个空的 /review 模板同样能作为原生入口存在)。

模板命名的可移植性约束

prompts/*.md 的模板名(slug)必须是一个可移植的单个斜杠命令 tokenpi_prompt_files.rs),校验规则包括:非空、不超过 128 字节、不能以 . 开头或结尾、禁止空白与控制字符、禁止 <>:"/\|?* 等路径保留字符、并排除 Windows 保留名(CONPRNAUXNULCOM1-9LPT1-9)。对应测试覆盖了合法名(review-prrelease.v2、中文 评审)与非法名(with spacea/bCONlpt1.txt 等)。

服务层还提供改名能力:upsert(slug, Some(original_slug), ...) 支持"先改名再写入"的原子组合,重命名后的 fs::rename 失败会回滚(见 pi_prompt_files.rs)。

Sessions:可枚举的边界与宽容的解析

会话目录的三种结果

全局会话页只枚举以下三种来源:

  1. 绝对路径sessionDir
  2. ~ 路径(含 ~/ 前缀)——会展开为主目录;
  3. Pi 默认目录:agent 目录下的 sessions/(采用"项目目录"布局,即 <root>/<project>/<file>.jsonl)。

相对 sessionDir(如 .pi/sessions)依赖启动 Pi 时的项目工作目录,CC Switch 没有可靠的启动上下文,因此明确显示"需要项目上下文"(RequiresProjectContext),绝不会猜测目录或回退到其他根目录。会话根解析的完整实现与 session_discovery() 状态机见 session_manager/providers/pi.rs,测试 relative_root_is_explicitly_non_enumerable 断言 .pi/sessions 只能被归类为"需要项目上下文"。

此外,PI_CODING_AGENT_SESSION_DIR 环境变量优先级最高,其次才是全局 settings.jsonsessionDir,最后回落到默认 agent/sessions。显式配置的目录若不是目录或不可读,会被标记为 Unavailable 并给出具体原因,而不会伪装成"空历史"(对应测试 unavailable_explicit_roots_are_not_reported_as_empty_history)。

JSONL 解析:只消费 UI 所需字段

会话解析只消费 UI 需要的字段,逐行、宽容地读取 Pi 的 JSONL 会话文件,关键规则如下:

  • 会话头:首行必须是 type:"session",携带 id(需通过树 ID 合法性校验)、cwdversion(0 视为不支持版本,>=1 均可按当前语义读取)与 timestamp
  • 树的分支结构:v2+ 条目通过 id/parentId 组织;从最新叶子沿 parentId 链回溯得到活动分支(active entry indexes),被废弃的分支不会出现在消息列表里(测试 latest_leaf_defines_the_active_branch 验证了这一点);缺失父引用与成环的引用会在可读分支处停止遍历,而不是报错拒绝整个会话。
  • 重复 ID:同一会话内重复的条目 ID 以最新条目为准(测试 duplicate_entry_ids_use_the_latest_entry)。
  • 会话标题:优先取全局 session_infoname(即使它所在分支已废弃,测试 global_name_and_malformed_line_follow_pi_semantics 验证该 Pi 语义);没有名称时依次回退到首条用户消息(截断至标题长度上限)或会话目录名。
  • 消息类别映射
    • message:按 role 映射 user/assistanttoolResultbashExecution 归入 tool(后者会拼上 $ 命令 前缀与输出),branchSummary/compactionSummary 归入 system
    • compactionbranch_summary:以摘要内容生成系统消息;
    • custom_message:在 display != false 时生成系统消息;
    • 未知条目一律忽略,格式损坏的行直接跳过,不会中断读取。
  • 时间戳:消息级 message.timestamp 优先于条目级 timestamp(与 Pi 会话选择器行为一致),时间格式兼容字符串与毫秒值两种表示。
  • 安全上限:单个会话文件最大 128 MiB(MAX_SESSION_BYTES),单文件最多解析 500,000 个树条目(MAX_TREE_ENTRIES),树 ID 最长 256 字节且只允许字母数字与 -_.

删除前的双保险校验

删除会话前必须同时通过两道校验(pi.rs):

  1. 目标文件仍在当前已解析的 Pi 会话根目录内(canonicalize 后做 starts_with 前缀检查,并核对活动目录布局:Flat 布局要求相对路径深度为 1,ProjectDirectories 布局要求深度为 2);
  2. 文件内的会话头 id 与传入的会话 ID 逐一核对,不一致直接拒绝。

删除前还会重新解析一次根目录并做 canonical 比较,防止"根目录在删除瞬间已被切换"的竞态("Pi session root changed before deletion")。会话恢复命令则统一生成为 pi --session <文件绝对路径>(见 pi.rs,路径经 shell 转义),与 Pi 按精确文件路径恢复会话的行为一致。

明确不做:边界清单与升级策略

契约的另一半是边界。CC Switch 对 Pi 明确不做以下事情:

  • Pi /loginauth.json 中的 OAuth / API Key 登录、令牌保存与刷新——认证只属于 Pi 自己;
  • 默认供应商或默认模型的写入——defaultProvider/defaultModel 永远只读;
  • 路由、网关、代理、故障转移和请求头合成——这些能力即便在 CC Switch 其他应用中存在,也绝不施加于 Pi 的请求路径,请求始终由 Pi 自己发出;
  • Pi 运行时内置供应商与内置模型目录的复制——不把继承/合并产物回写显式配置;
  • 完整的 compatmodelOverrides 和费用编辑器——思考档位只提供 Pi 原生 thinkingLevelMap 的轻量入口,对应实现见 src/config/piThinkingProfiles.ts(维护 off/minimal/low/medium/high/xhigh/max 七档向各厂商原生取值映射的小型 reviewed 档案,缺失键与 null 有明确差异——空档案被禁止,因为 {} 保留给用户"显式使用 Pi 默认"的选择)与 src/config/piProviderPresets.ts(独立维护的 Pi 供应商目录,含 openai-completionsopenai-responsesanthropic-messagesgoogle-generative-aibedrock-converse-stream 等 API 格式);
  • 相对会话目录的全局猜测——没有项目上下文就如实报告,不臆测。

当 Pi 发布新版本时,通过正常开发和验收流程重新运行供应商、提示词、Skills 和 Sessions 契约测试即可确认兼容性。没有进入产品界面的上游字段,不应为了"覆盖完整"而扩展后端——这正是本契约能长期保持"小、稳、可验证"的原因。

相关文档与深入阅读

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388