pi-ccswitch-auto-switch 0.1.0 → 0.1.1
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 +3 -3
- package/README.zh-CN.md +3 -3
- package/candidates.ts +15 -0
- package/index.ts +17 -8
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -66,15 +66,15 @@ Examples:
|
|
|
66
66
|
## Status bar
|
|
67
67
|
|
|
68
68
|
```text
|
|
69
|
-
CCS ✓
|
|
69
|
+
CCS ✓70/71 · ⏳1 · ⛔0
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
- `✓
|
|
72
|
+
- `✓70/71`: 70 healthy models out of 71 unique `provider/model` combinations in Pi's effective scope. Duplicate scoped entries are counted once.
|
|
73
73
|
- `⏳1`: one model, provider, or endpoint health record is cooling down.
|
|
74
74
|
- `⛔0`: no manually disabled models.
|
|
75
75
|
- During a switch, `CCS ↻2/5 provider/model` means the second of at most five attempts is being made.
|
|
76
76
|
|
|
77
|
-
Use `/ccswitch` to
|
|
77
|
+
When all unique models are healthy, the compact form is `CCS ✓71`. Use `/ccswitch` or `/ccswitch-test` to see Pi's raw scope entry count, the deduplicated model count, the healthy count, and the exact record behind a cooldown.
|
|
78
78
|
|
|
79
79
|
## Failover behavior
|
|
80
80
|
|
package/README.zh-CN.md
CHANGED
|
@@ -58,15 +58,15 @@ pi install npm:pi-ccswitch-auto-switch
|
|
|
58
58
|
## 状态栏
|
|
59
59
|
|
|
60
60
|
```text
|
|
61
|
-
CCS ✓
|
|
61
|
+
CCS ✓70/71 · ⏳1 · ⛔0
|
|
62
62
|
```
|
|
63
63
|
|
|
64
|
-
- `✓
|
|
64
|
+
- `✓70/71`:Pi 有效范围内共有 71 个去重后的 `provider/model` 组合,其中 70 个健康;重复的 scope 条目只计一次。
|
|
65
65
|
- `⏳1`:有一条模型、Provider 或端点健康记录仍在冷却。
|
|
66
66
|
- `⛔0`:没有被手动禁用的模型。
|
|
67
67
|
- 切换中出现 `CCS ↻2/5 provider/model`,表示正在进行最多 5 次尝试中的第 2 次。
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
全部唯一模型都健康时会压缩显示为 `CCS ✓71`。通过 `/ccswitch` 或 `/ccswitch-test` 可同时查看 Pi 原始 scope 条目数、去重模型数、健康数,以及具体哪个记录正在冷却。
|
|
70
70
|
|
|
71
71
|
## 故障转移逻辑
|
|
72
72
|
|
package/candidates.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface CandidateOptions {
|
|
|
8
8
|
health: HealthState
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface CandidateSnapshot {
|
|
12
|
+
models: ModelRef[]
|
|
13
|
+
source: 'scoped' | 'available'
|
|
14
|
+
sourceEntries: number
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
function blocked(model: ModelRef, health: HealthState, now = Date.now()): boolean {
|
|
12
18
|
const records = [health.models[modelKey(model)], health.providers[model.provider], health.endpoints[endpointKey(model)]]
|
|
13
19
|
return records.some(record => Boolean(record?.disabled || (record?.cooldownUntil && record.cooldownUntil > now) || (record?.leaseUntil && record.leaseUntil > now)))
|
|
@@ -24,6 +30,15 @@ export function effectiveCandidates(scoped: readonly ScopedModel[], available: M
|
|
|
24
30
|
})
|
|
25
31
|
}
|
|
26
32
|
|
|
33
|
+
export function candidateSnapshot(scoped: readonly ScopedModel[], available: ModelRef[]): CandidateSnapshot {
|
|
34
|
+
const source = scoped.length > 0 ? 'scoped' : 'available'
|
|
35
|
+
return {
|
|
36
|
+
models: effectiveCandidates(scoped, available),
|
|
37
|
+
source,
|
|
38
|
+
sourceEntries: source === 'scoped' ? scoped.length : available.length,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
export function chooseCandidate(models: ModelRef[], options: CandidateOptions): ModelRef | undefined {
|
|
28
43
|
const current = options.current
|
|
29
44
|
const candidates = models.filter(model => {
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, FailureObservation, ModelRef } from './types.ts'
|
|
2
2
|
import { classifyFailure, parseRetryAfter } from './classify.ts'
|
|
3
|
-
import { effectiveCandidates, chooseCandidate } from './candidates.ts'
|
|
3
|
+
import { candidateSnapshot, effectiveCandidates, chooseCandidate } from './candidates.ts'
|
|
4
4
|
import { HealthStore, endpointKey, modelKey } from './health.ts'
|
|
5
5
|
|
|
6
6
|
const FIRST_RESPONSE_TIMEOUT = 90_000
|
|
@@ -48,7 +48,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
48
48
|
}
|
|
49
49
|
const status = (ctx: ExtensionContext) => {
|
|
50
50
|
const state = health.snapshot
|
|
51
|
-
const models =
|
|
51
|
+
const { models } = candidateSnapshot(ctx.scopedModels, ctx.modelRegistry.getAvailable())
|
|
52
52
|
const now = Date.now()
|
|
53
53
|
const disabled = Object.values(state.models).filter(item => item.disabled).length
|
|
54
54
|
const cooling = [...Object.values(state.models), ...Object.values(state.providers), ...Object.values(state.endpoints)]
|
|
@@ -56,7 +56,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
56
56
|
const unhealthy = models.filter(model => health.isBlocked(model)).length
|
|
57
57
|
const healthy = Math.max(0, models.length - unhealthy)
|
|
58
58
|
const plain = round?.phase === 'switching' ? `CCS ↻${round.attempts}/${MAX_ATTEMPTS} ${round.model ? modelKey(round.model) : ''}` :
|
|
59
|
-
cooling || disabled ? `CCS ✓${healthy} · ⏳${cooling} · ⛔${disabled}` : `CCS ✓${models.length}`
|
|
59
|
+
cooling || disabled ? `CCS ✓${healthy}/${models.length} · ⏳${cooling} · ⛔${disabled}` : `CCS ✓${models.length}`
|
|
60
60
|
const theme = ctx.ui.theme
|
|
61
61
|
ctx.ui.setStatus('ccswitch-ha', theme ? theme.fg(cooling || disabled ? 'warning' : 'success', plain) : plain)
|
|
62
62
|
}
|
|
@@ -81,14 +81,21 @@ export default function (pi: ExtensionAPI) {
|
|
|
81
81
|
}
|
|
82
82
|
const showPanel = async (ctx: ExtensionContext) => {
|
|
83
83
|
const state = health.snapshot
|
|
84
|
-
const
|
|
84
|
+
const snapshot = candidateSnapshot(ctx.scopedModels, ctx.modelRegistry.getAvailable())
|
|
85
|
+
const candidates = snapshot.models
|
|
86
|
+
const healthy = candidates.filter(model => !health.isBlocked(model)).length
|
|
85
87
|
const rows = candidates.slice(0, 10).map(model => {
|
|
86
88
|
const record = state.models[modelKey(model)] ?? state.providers[model.provider]
|
|
87
89
|
const suffix = record?.disabled ? '禁用' : record?.cooldownUntil && record.cooldownUntil > Date.now() ? `冷却 ${Math.ceil((record.cooldownUntil - Date.now()) / 60_000)}m` : '健康'
|
|
88
90
|
return `${modelKey(model)} ${suffix}`
|
|
89
91
|
})
|
|
90
|
-
if (!ctx.ui.select) {
|
|
91
|
-
|
|
92
|
+
if (!ctx.ui.select) {
|
|
93
|
+
const source = snapshot.source === 'scoped' ? `Pi scope ${snapshot.sourceEntries} 条` : `Pi 注册表 ${snapshot.sourceEntries} 条`
|
|
94
|
+
notify(ctx, `CCSwitch:${source} · 唯一模型 ${candidates.length} · 健康 ${healthy}`, 'info')
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
const scopeLabel = snapshot.source === 'scoped' ? `Pi scope:${snapshot.sourceEntries} 条` : `Pi 可用注册表:${snapshot.sourceEntries} 条`
|
|
98
|
+
const action = await ctx.ui.select(`CCSwitch 健康面板\n当前:${key(ctx.model) ?? '无'}\n${scopeLabel} · 唯一模型:${candidates.length} · 健康:${healthy}\n${rows.join('\n') || '没有可用模型'}`, ['刷新', '重新激活当前模型', '禁用当前模型', '重置当前模型历史', '关闭'])
|
|
92
99
|
if (action === '刷新') await refresh(ctx)
|
|
93
100
|
if (action === '重新激活当前模型' && ctx.model) { health.reactivate(ctx.model); await health.flush(); status(ctx); notify(ctx, '已重新激活当前模型') }
|
|
94
101
|
if (action === '禁用当前模型' && ctx.model) { health.disable(modelKey(ctx.model), true); await health.flush(); status(ctx); notify(ctx, '已禁用当前模型', 'warning') }
|
|
@@ -230,7 +237,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
230
237
|
}})
|
|
231
238
|
pi.registerCommand('ccswitch-test', { description: '检查 CCSwitch 候选模型和健康状态(不切换模型)', handler: async (_args, ctx) => {
|
|
232
239
|
await refresh(ctx)
|
|
233
|
-
const
|
|
234
|
-
|
|
240
|
+
const snapshot = candidateSnapshot(ctx.scopedModels, ctx.modelRegistry.getAvailable())
|
|
241
|
+
const healthy = snapshot.models.filter(model => !health.isBlocked(model)).length
|
|
242
|
+
const source = snapshot.source === 'scoped' ? `Pi scope ${snapshot.sourceEntries} 条` : `Pi 注册表 ${snapshot.sourceEntries} 条`
|
|
243
|
+
notify(ctx, `CCSwitch 自检:${source} · 唯一模型 ${snapshot.models.length} · 健康 ${healthy}`, snapshot.models.length ? 'info' : 'warning')
|
|
235
244
|
}})
|
|
236
245
|
}
|