pi-ccswitch-auto-switch 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -29,6 +29,19 @@ The extension observes real Pi requests, records sanitized health signals, and
29
29
  - CC Switch `3.20+` is recommended for its native Pi model configuration support.
30
30
  - Node.js `22.19+` only for local development and tests. Pi supplies the runtime for the extension itself.
31
31
 
32
+ ## Configuration
33
+
34
+ The extension requires **no user configuration**: it observes Pi's real requests and reads only the model metadata Pi already exposes. It never reads CC Switch databases, Pi auth files, API keys, cookies, or `.env` files, and it never writes Pi model settings or CC Switch data.
35
+
36
+ All knobs below are optional and have sensible defaults:
37
+
38
+ | Setting | Scope | Default | Notes |
39
+ | --- | --- | --- | --- |
40
+ | `PI_CODING_AGENT_DIR` | Extension state | `~/.pi/agent` | Where health state, logs, and failure reports are stored. Only relevant if you relocated your Pi agent directory; the extension follows Pi's own convention. |
41
+ | `PI_BIN` | Headless runner only | `pi` on `PATH` | Must point to the real `pi` executable, never to `pi-ccswitch-run`. |
42
+ | Model scope (`/model` etc.) | Failover candidates | Full registry | When Pi has an active model scope, failover only considers models inside that scope; otherwise the full registry is used. The status bar shows which source is active. |
43
+ | `baseUrl` metadata | Endpoint isolation | provider key | Endpoint-level platform isolation groups models by `baseUrl` (provided by CC Switch `3.20+`). Without it, isolation degrades to provider-level grouping, which still works. |
44
+
32
45
  ## Installation
33
46
 
34
47
  ### Pi package install (recommended)
@@ -49,7 +62,32 @@ CC Switch should be configured normally. This extension deliberately does not wr
49
62
 
50
63
  ### Headless / automation use
51
64
 
52
- Install the package as an npm dependency, then run its binary (or invoke the packaged `runner.mjs` with Node):
65
+ `pi-ccswitch-run`(`runner.mjs`)是独立于 Pi 扩展的 headless 调用入口:它启动一个 `pi --mode rpc` 会话,把一次请求自动切换到健康模型后只返回最终成功结果。它**不在** `pi install` 的扩展加载路径里自动暴露给下游项目,需要单独安装。
66
+
67
+ #### 在消费方项目内安装(推荐,保证 `require.resolve` 可解析)
68
+
69
+ `pi install git:...` 只把扩展装进 Pi 全局目录(`~/.pi/agent/...`),**不会**把它放进下游项目的 `node_modules` 解析路径。因此消费方代码里 `require.resolve('pi-ccswitch-auto-switch/runner.mjs')` 需要把本包安装进**自己的项目依赖**:
70
+
71
+ ```bash
72
+ # 在消费方项目目录内执行
73
+ npm i github:JunyWuuuu91/pi-ccswitch-auto-switch
74
+ # 或从 npm registry 安装最新版
75
+ npm i pi-ccswitch-auto-switch@latest
76
+ ```
77
+
78
+ 安装后 `require.resolve('pi-ccswitch-auto-switch/runner.mjs')` 会命中项目自身 `node_modules`,bin `pi-ccswitch-run` 也可直接调用。
79
+
80
+ #### 版本锁定警告(0.x caret 陷阱)
81
+
82
+ npm 对 `^0.1.6` 的 caret 语义**只匹配 `0.1.x`**,不会自动升到含 `runner.mjs` 的 `0.3.x`。如果机器上 `~/.pi/agent/npm/node_modules/pi-ccswitch-auto-switch` 仍停留在 `0.1.6`(该版本无 `runner.mjs`、无 `pi-ccswitch-run` bin),重复执行 `pi install` 也不会升级。请手动升级 npm 侧版本:
83
+
84
+ ```bash
85
+ cd ~/.pi/agent/npm && npm install pi-ccswitch-auto-switch@latest
86
+ ```
87
+
88
+ 升级后确认 `~/.pi/agent/npm/node_modules/pi-ccswitch-auto-switch/runner.mjs` 存在。也可以在扩展内执行 `/ccswitch-doctor` 检查 `runner.mjs` 是否可解析。
89
+
90
+ #### 运行方式
53
91
 
54
92
  ```bash
55
93
  pi-ccswitch-run --no-tools --no-context-files @prompt.md "Summarize the attached text"
@@ -70,6 +108,7 @@ Exit codes are `0` for success, `1` when candidates are exhausted, `2` for invoc
70
108
  | `/ccswitch disable <provider/model>` | Manually exclude a model from failover. |
71
109
  | `/ccswitch reset <provider/model\|all>` | Delete selected health history after confirmation; `all` also clears learned content-policy constraints. |
72
110
  | `/ccswitch-test` | Inspect candidate discovery without changing models. |
111
+ | `/ccswitch-doctor` | Diagnose whether `runner.mjs` is present and resolvable. |
73
112
 
74
113
  Examples:
75
114
 
package/README.zh-CN.md CHANGED
@@ -27,6 +27,19 @@
27
27
  - 建议使用 CC Switch `3.20+`,其已支持原生维护 Pi 模型配置。
28
28
  - 本地开发和测试需要 Node.js `22.19+`;Pi 运行插件本身不需要额外安装 Node。
29
29
 
30
+ ## 配置
31
+
32
+ 插件**不需要任何用户配置**:它只观察 Pi 的真实请求,读取 Pi 已暴露的模型元数据;从不读取 CC Switch 数据库、Pi 认证文件、API Key、Cookie 或 `.env` 文件,也从不写入 Pi 模型设置或 CC Switch 数据。
33
+
34
+ 以下开关全部可选,且有合理默认值:
35
+
36
+ | 设置项 | 作用域 | 默认值 | 说明 |
37
+ | --- | --- | --- | --- |
38
+ | `PI_CODING_AGENT_DIR` | 扩展状态 | `~/.pi/agent` | 健康状态、日志与失败报告的存放目录;仅在你迁移了 Pi agent 目录时需要,插件跟随 Pi 自身约定。 |
39
+ | `PI_BIN` | 仅 headless runner | `PATH` 中的 `pi` | 必须指向真实 `pi` 可执行文件,不能指向 `pi-ccswitch-run`。 |
40
+ | 模型 scope(`/model` 等) | 故障转移候选 | 全量注册表 | Pi 启用了 model scope 时,只在 scope 内模型间切换;否则使用全量注册表。状态栏会显示当前数据源。 |
41
+ | `baseUrl` 元数据 | 端点隔离 | provider key | 端点级平台隔离按 `baseUrl` 分组模型(由 CC Switch `3.20+` 提供);缺失时退化为 provider 级分组,仍然可用。 |
42
+
30
43
  ## 安装
31
44
 
32
45
  ### 通过 Pi Package 安装(推荐)
@@ -45,7 +58,32 @@ pi install npm:pi-ccswitch-auto-switch
45
58
 
46
59
  ### 无头自动化调用
47
60
 
48
- 将本包作为 npm 依赖安装后,可调用:
61
+ `pi-ccswitch-run`(`runner.mjs`)是独立于 Pi 扩展的 headless 调用入口:它启动一个 `pi --mode rpc` 会话,把一次请求自动切换到健康模型后只返回最终成功结果。它**不在** `pi install` 的扩展加载路径里自动暴露给下游项目,需要单独安装。
62
+
63
+ #### 在消费方项目内安装(推荐,保证 `require.resolve` 可解析)
64
+
65
+ `pi install git:...` 只把扩展装进 Pi 全局目录(`~/.pi/agent/...`),**不会**把它放进下游项目的 `node_modules` 解析路径。因此消费方代码里 `require.resolve('pi-ccswitch-auto-switch/runner.mjs')` 需要把本包安装进**自己的项目依赖**:
66
+
67
+ ```bash
68
+ # 在消费方项目目录内执行
69
+ npm i github:JunyWuuuu91/pi-ccswitch-auto-switch
70
+ # 或从 npm registry 安装最新版
71
+ npm i pi-ccswitch-auto-switch@latest
72
+ ```
73
+
74
+ 安装后 `require.resolve('pi-ccswitch-auto-switch/runner.mjs')` 会命中项目自身 `node_modules`,bin `pi-ccswitch-run` 也可直接调用。
75
+
76
+ #### 版本锁定警告(0.x caret 陷阱)
77
+
78
+ npm 对 `^0.1.6` 的 caret 语义**只匹配 `0.1.x`**,不会自动升到含 `runner.mjs` 的 `0.3.x`。如果机器上 `~/.pi/agent/npm/node_modules/pi-ccswitch-auto-switch` 仍停留在 `0.1.6`(该版本无 `runner.mjs`、无 `pi-ccswitch-run` bin),重复执行 `pi install` 也不会升级。请手动升级 npm 侧版本:
79
+
80
+ ```bash
81
+ cd ~/.pi/agent/npm && npm install pi-ccswitch-auto-switch@latest
82
+ ```
83
+
84
+ 升级后确认 `~/.pi/agent/npm/node_modules/pi-ccswitch-auto-switch/runner.mjs` 存在。也可以在扩展内执行 `/ccswitch-doctor` 检查 `runner.mjs` 是否可解析。
85
+
86
+ #### 运行方式
49
87
 
50
88
  ```bash
51
89
  pi-ccswitch-run --no-tools --no-context-files @prompt.md "请总结附件"
@@ -66,6 +104,7 @@ runner 内部只显式加载本扩展并启动一次 Pi RPC;`PI_BIN` 只能指
66
104
  | `/ccswitch disable <provider/model>` | 手动排除模型。 |
67
105
  | `/ccswitch reset <provider/model\|all>` | 确认后删除相应健康历史;`all` 同时清除已学习的审查约束。 |
68
106
  | `/ccswitch-test` | 仅检查候选发现,不切换模型。 |
107
+ | `/ccswitch-doctor` | 诊断 `runner.mjs` 可解析性和安装情况。 |
69
108
 
70
109
  ## 状态栏
71
110
 
package/health.ts CHANGED
@@ -105,6 +105,8 @@ export class HealthStore {
105
105
  private dirty = false
106
106
  private replaceOnFlush = false
107
107
  private resetModels = new Set<string>()
108
+ /** 合并磁盘状态后仍需强制清空的审查约束(session 边界) */
109
+ private clearContentPolicyOnFlush = false
108
110
 
109
111
  constructor(dir = agentDir()) { this.dir = dir }
110
112
  get file(): string { return join(this.dir, STATE_FILE) }
@@ -242,6 +244,7 @@ export class HealthStore {
242
244
  */
243
245
  clearContentPolicyConstraints(): void {
244
246
  this.state.contentPolicyFamilies = {}
247
+ this.clearContentPolicyOnFlush = true
245
248
  this.touch()
246
249
  }
247
250
 
@@ -270,6 +273,8 @@ export class HealthStore {
270
273
  await this.withLock(async () => {
271
274
  if (!this.replaceOnFlush) this.state = mergeState(await this.readDisk(), this.state)
272
275
  for (const key of this.resetModels) delete this.state.models[key]
276
+ // 必须在 merge 之后再清空:磁盘上残留的旧 session 约束不能因合并而重新出现
277
+ if (this.clearContentPolicyOnFlush) this.state.contentPolicyFamilies = {}
273
278
  await this.commit()
274
279
  }).catch(() => {})
275
280
  }
@@ -308,6 +313,7 @@ export class HealthStore {
308
313
  this.dirty = false
309
314
  this.replaceOnFlush = false
310
315
  this.resetModels.clear()
316
+ this.clearContentPolicyOnFlush = false
311
317
  }
312
318
  private bucket(scope: HealthScope): Record<string, HealthRecord> {
313
319
  return scope === 'model' ? this.state.models : scope === 'provider' ? this.state.providers : this.state.endpoints
package/index.ts CHANGED
@@ -1,3 +1,8 @@
1
+ import { existsSync, readFileSync } from 'node:fs'
2
+ import { createRequire } from 'node:module'
3
+ import { homedir } from 'node:os'
4
+ import { dirname, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
1
6
  import type { ExtensionAPI, ExtensionContext, FailureObservation, ModelRef } from './types.ts'
2
7
  import { classifyFailure, parseRetryAfter } from './classify.ts'
3
8
  import { candidateSnapshot, effectiveCandidates, chooseCandidate, modelFamily, summarizeCandidateHealth } from './candidates.ts'
@@ -8,7 +13,7 @@ const STREAM_IDLE_TIMEOUT = 120_000
8
13
  const MAX_ATTEMPTS = 5
9
14
  const ROUND_LIMIT = 8 * 60_000
10
15
  const RPC_PROTOCOL_VERSION = 1
11
- const EXTENSION_VERSION = '0.3.2'
16
+ const EXTENSION_VERSION = '0.3.4'
12
17
  // 同端点(BaseURL 相同)连续失败达到该次数即隔离该端点,避免同一个平台的多个模型逐个试错耗尽本轮切换
13
18
  const ENDPOINT_FAIL_THRESHOLD = 3
14
19
 
@@ -108,6 +113,7 @@ export default function (pi: ExtensionAPI) {
108
113
  '/ccswitch disable <provider/model> — 手动禁用模型',
109
114
  '/ccswitch reset <provider/model|all> — 清除健康历史;all 也会清除已学习的审查约束(需确认)',
110
115
  '/ccswitch-test — 自检候选模型,不实际切换',
116
+ '/ccswitch-doctor — 诊断 runner.mjs 可解析性和安装情况',
111
117
  ].join('\n'), 'info')
112
118
  }
113
119
  const refresh = async (ctx: ExtensionContext) => {
@@ -381,4 +387,61 @@ export default function (pi: ExtensionAPI) {
381
387
  const source = snapshot.source === 'scoped' ? `Pi scope ${snapshot.sourceEntries} 条` : `Pi 注册表 ${snapshot.sourceEntries} 条`
382
388
  notify(ctx, `CCSwitch 自检:${source} · 唯一模型 ${counts.total} · 健康 ${counts.healthy} · 自动冷却 ${counts.cooling} · 手动禁用 ${counts.disabled} · 熔断记录 ${counts.breakerRecords} · 审查约束系列 ${policyFamilies.size} · 本session切换 ${sessionSwitches} · 累计切换 ${state.switches ?? 0}`, counts.total ? 'info' : 'warning')
383
389
  }})
390
+ pi.registerCommand('ccswitch-doctor', { description: '诊断 runner.mjs 可解析性和安装情况', handler: async (_args, ctx) => {
391
+ const __dirname = dirname(fileURLToPath(import.meta.url))
392
+ const localRunner = join(__dirname, 'runner.mjs')
393
+ const hasLocal = existsSync(localRunner)
394
+ let resolvable = false
395
+ let resolvedPath = ''
396
+ try {
397
+ const cRequire = createRequire(import.meta.url)
398
+ resolvedPath = cRequire.resolve('pi-ccswitch-auto-switch/runner.mjs')
399
+ resolvable = true
400
+ } catch { /* not resolvable — expected when extension is in git checkout */ }
401
+
402
+ // 检查 npm 全局安装版本
403
+ let npmVersion = ''
404
+ const npmPkgDir = join(homedir(), '.pi/agent/npm/node_modules/pi-ccswitch-auto-switch')
405
+ const npmPkgJson = join(npmPkgDir, 'package.json')
406
+ if (existsSync(npmPkgJson)) {
407
+ try {
408
+ npmVersion = JSON.parse(readFileSync(npmPkgJson, 'utf-8')).version
409
+ } catch { /* ignore */ }
410
+ }
411
+ const hasNpmRunner = npmVersion && existsSync(join(npmPkgDir, 'runner.mjs'))
412
+
413
+ const lines = [
414
+ `CCSwitch 诊断 v${EXTENSION_VERSION}`,
415
+ '',
416
+ `扩展加载目录:${__dirname}`,
417
+ `本地 runner.mjs:${hasLocal ? '✅ 存在' : '❌ 缺失'}`,
418
+ `resolve('pi-ccswitch-auto-switch/runner.mjs'):${resolvable ? '✅ 可解析' : '❌ 不可解析'}`,
419
+ '',
420
+ ]
421
+ if (npmVersion) {
422
+ lines.push(`npm 全局安装版本:${npmVersion}${hasNpmRunner ? ' ✅ 含 runner.mjs' : ' ❌ 不含 runner.mjs(版本过旧)'}`)
423
+ lines.push('')
424
+ }
425
+ lines.push('--- 下游消费方正确安装方式 ---',
426
+ 'pi-ccswitch-auto-switch 是 Pi 扩展,`pi install` 只装到 Pi 全局目录,',
427
+ '下游项目 require.resolve() 在自己的 node_modules 路径上解析不到。',
428
+ '',
429
+ '正确做法:在消费方项目内安装本包',
430
+ ' npm i github:JunyWuuuu91/pi-ccswitch-auto-switch',
431
+ ' 或 npm i pi-ccswitch-auto-switch@latest',
432
+ '',
433
+ )
434
+ if (npmVersion && !hasNpmRunner) {
435
+ lines.push('--- 版本锁定修复 ---',
436
+ `npm 全局版本 ${npmVersion} 不含 runner.mjs,因为 ^0.1.x 的 caret 语义只匹配 0.1.x。`,
437
+ '需手动升级 npm 侧版本:',
438
+ ' cd ~/.pi/agent/npm && npm install pi-ccswitch-auto-switch@latest',
439
+ '',
440
+ )
441
+ }
442
+ lines.push('也可通过 NODE_PATH 或全局路径引用:',
443
+ ` ${__dirname}/runner.mjs`,
444
+ )
445
+ notify(ctx, lines.join('\n'), 'info')
446
+ }})
384
447
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ccswitch-auto-switch",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Provider-first automatic model failover extension for Pi and CC Switch",
5
5
  "license": "MIT",
6
6
  "keywords": [