@goodandready/dsh-key-rotation 0.8.62 → 0.8.63
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/CHANGELOG.md +9 -0
- package/README.md +1 -0
- package/README.ru.md +1 -0
- package/README.zh.md +1 -0
- package/lib/client.js +34 -3
- package/lib/index.js +2 -0
- package/lib/ops-status.js +2 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.63 - 2026-10-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Configurable Switch to Disable Chat Header Status Chip (#493 / GitHub #25)**:
|
|
7
|
+
- Added `showChatHeaderChip` boolean setting (defaults to `true` for 100% backward compatibility) in `SettingsSchema` and runtime configuration.
|
|
8
|
+
- Added user-facing toggle in the Settings card ("Chat Header Status Chip" / "聊天头部状态按钮") under rotation settings.
|
|
9
|
+
- `KeyRotationHeaderChip` (`conversation.session.header.utilities`) dynamically subscribes to settings and checks server health state, cleanly returning `null` when disabled without taking up conversational header toolbar space.
|
|
10
|
+
- Additive field `showChatHeaderChip` exposed in `/dsh-key-rotation/status` and `/dsh-key-rotation/health`.
|
|
11
|
+
|
|
3
12
|
## 0.8.62 - 2026-10-16
|
|
4
13
|
|
|
5
14
|
### Added
|
package/README.md
CHANGED
|
@@ -378,6 +378,7 @@ dsh-key-rotation:
|
|
|
378
378
|
| `circuitBreakerOpenMs` | `number` | `30000` | How long the circuit stays open (ms). |
|
|
379
379
|
| `circuitBreakerHalfOpenProbes` | `number` | `1` | Probe requests allowed in half-open state. |
|
|
380
380
|
| `verboseLogging` | `boolean` | `false` | Per-request rotation logs (noisy; off by default). |
|
|
381
|
+
| `showChatHeaderChip` | `boolean` | `true` | Show or hide the pool status button in conversation header toolbar (#25). |
|
|
381
382
|
| `concurrencyLimit` | `number` | `0` (disabled) | Max concurrent in-flight streams per key (0 = unlimited). |
|
|
382
383
|
| `quotaResetWindow` | `object` | `null` | Calendar reset alignment for both provider-reported quota cooldowns and local per-model token budgets (`midnight_utc`, `midnight_pst`, `rolling_24h`). |
|
|
383
384
|
| `cascade` | `array` | `[]` | Fallback provider chain when primary pool is completely exhausted. |
|
package/README.ru.md
CHANGED
|
@@ -409,6 +409,7 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
409
409
|
| `circuitBreakerOpenMs` | number | `30000` | Длительность open, мс |
|
|
410
410
|
| `circuitBreakerHalfOpenProbes` | number | `1` | Проб в half-open |
|
|
411
411
|
| `verboseLogging` | boolean | `false` | Подробные логи rotation (шумно) |
|
|
412
|
+
| `showChatHeaderChip` | boolean | `true` | Показывать кнопку статуса пула в шапке чата (#25) |
|
|
412
413
|
|
|
413
414
|
Status API (v0.8.0): у каждого провайдера `circuit`, в корне ответа `meta.expectedClones` и `meta.notifyQueue`.
|
|
414
415
|
|
package/README.zh.md
CHANGED
|
@@ -378,3 +378,4 @@ MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
|
|
|
378
378
|
| `circuitBreakerThreshold` | number | `5` | 连续失败阈值 |
|
|
379
379
|
| `circuitBreakerOpenMs` | number | `30000` | 打开时长 ms |
|
|
380
380
|
| `circuitBreakerHalfOpenProbes` | number | `1` | 半开探测次数 |
|
|
381
|
+
| `showChatHeaderChip` | boolean | `true` | 在会话界面顶部工具栏显示密钥池状态按钮 (#25) |
|
package/lib/client.js
CHANGED
|
@@ -224,6 +224,8 @@ window.__ModuleLoader__.load({
|
|
|
224
224
|
routingStrategySticky: 'Sticky (preserves prompt cache)',
|
|
225
225
|
proactiveGuardLabel: 'Proactive Rate-Limit Guard',
|
|
226
226
|
proactiveGuardHint: 'Pre-emptively pause keys when remaining quota or Retry-After header indicates limit before hitting 429',
|
|
227
|
+
showChatHeaderChipLabel: 'Chat Header Status Chip',
|
|
228
|
+
showChatHeaderChipDesc: 'Show pool status chip in conversation header',
|
|
227
229
|
selfHealingLabel: 'Auto-Unbreak Interval (min)',
|
|
228
230
|
selfHealingHint: 'Periodic free model check to revive broken keys (0 to disable)',
|
|
229
231
|
updateCheck: 'Check for updates',
|
|
@@ -422,6 +424,8 @@ window.__ModuleLoader__.load({
|
|
|
422
424
|
routingStrategySticky: '粘性模式 (优先保持提示词缓存)',
|
|
423
425
|
proactiveGuardLabel: '前瞻性限流防护',
|
|
424
426
|
proactiveGuardHint: '当响应中的剩余配额或 Retry-After 头提示即将超限时,预先冷却密钥以防触发 429',
|
|
427
|
+
showChatHeaderChipLabel: '聊天头部状态按钮',
|
|
428
|
+
showChatHeaderChipDesc: '在会话界面顶部工具栏显示密钥池状态按钮',
|
|
425
429
|
selfHealingLabel: '自愈检查间隔 (分钟)',
|
|
426
430
|
selfHealingHint: '定期轻量探测以自动恢复损坏密钥 (0 为禁用)',
|
|
427
431
|
updateCheck: '检查更新',
|
|
@@ -2596,6 +2600,16 @@ window.__ModuleLoader__.load({
|
|
|
2596
2600
|
}),
|
|
2597
2601
|
h('span', { key: 'label', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('proactiveGuardLabel')),
|
|
2598
2602
|
]))
|
|
2603
|
+
),
|
|
2604
|
+
h('div', { className: 'krot-grid-2', style: { marginTop: '10px' } },
|
|
2605
|
+
field(t('showChatHeaderChipLabel'), h('label', { style: { display: 'flex', alignItems: 'center', gap: '8px', height: '36px', cursor: 'pointer' } }, [
|
|
2606
|
+
h('input', {
|
|
2607
|
+
key: 'input', type: 'checkbox',
|
|
2608
|
+
checked: val.showChatHeaderChip ?? true,
|
|
2609
|
+
onChange: (e) => setField((cur) => ({ ...cur, showChatHeaderChip: e.target.checked })),
|
|
2610
|
+
}),
|
|
2611
|
+
h('span', { key: 'label', style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary)' } }, t('showChatHeaderChipDesc')),
|
|
2612
|
+
]))
|
|
2599
2613
|
)
|
|
2600
2614
|
);
|
|
2601
2615
|
|
|
@@ -2886,6 +2900,7 @@ window.__ModuleLoader__.load({
|
|
|
2886
2900
|
const t = resolveT(props);
|
|
2887
2901
|
const [snap, setSnap] = React.useState(null);
|
|
2888
2902
|
const [open, setOpen] = React.useState(false);
|
|
2903
|
+
const [, setTick] = React.useState(0);
|
|
2889
2904
|
const ref = React.useRef(null);
|
|
2890
2905
|
|
|
2891
2906
|
React.useEffect(() => {
|
|
@@ -2895,7 +2910,7 @@ window.__ModuleLoader__.load({
|
|
|
2895
2910
|
fetch('/dsh-key-rotation/health', { headers: { accept: 'application/json' }, credentials: 'same-origin', signal: (typeof AbortSignal !== 'undefined' && typeof AbortSignal.timeout === 'function') ? AbortSignal.timeout(8000) : undefined })
|
|
2896
2911
|
.then((r) => (r.ok ? r.json() : null))
|
|
2897
2912
|
.then((d) => { if (alive) setSnap(d); })
|
|
2898
|
-
.catch(() => {});
|
|
2913
|
+
.catch((e) => { void e; });
|
|
2899
2914
|
};
|
|
2900
2915
|
load();
|
|
2901
2916
|
const id = setInterval(load, 5000);
|
|
@@ -2908,6 +2923,13 @@ window.__ModuleLoader__.load({
|
|
|
2908
2923
|
};
|
|
2909
2924
|
}, []);
|
|
2910
2925
|
|
|
2926
|
+
React.useEffect(() => {
|
|
2927
|
+
if (!props?.settingsSource?.subscribe) return;
|
|
2928
|
+
return props.settingsSource.subscribe(() => {
|
|
2929
|
+
setTick((c) => c + 1);
|
|
2930
|
+
});
|
|
2931
|
+
}, [props?.settingsSource]);
|
|
2932
|
+
|
|
2911
2933
|
React.useEffect(() => {
|
|
2912
2934
|
if (!open) return;
|
|
2913
2935
|
const onDocClick = (e) => {
|
|
@@ -2917,6 +2939,16 @@ window.__ModuleLoader__.load({
|
|
|
2917
2939
|
return () => document.removeEventListener('click', onDocClick);
|
|
2918
2940
|
}, [open]);
|
|
2919
2941
|
|
|
2942
|
+
let settingsVal = null;
|
|
2943
|
+
try {
|
|
2944
|
+
const sSnap = props?.settingsSource?.getSnapshot?.()?.service?.getSnapshot?.();
|
|
2945
|
+
settingsVal = sSnap?.value;
|
|
2946
|
+
} catch (e) {
|
|
2947
|
+
void e;
|
|
2948
|
+
}
|
|
2949
|
+
if (settingsVal && settingsVal.showChatHeaderChip === false) return null;
|
|
2950
|
+
if (snap && snap.showChatHeaderChip === false) return null;
|
|
2951
|
+
|
|
2920
2952
|
const poolsObj = (snap && snap.pools) || {};
|
|
2921
2953
|
const poolEntries = Object.entries(poolsObj);
|
|
2922
2954
|
const total = poolEntries.reduce((a, [, p]) => a + (p.total || 0), 0);
|
|
@@ -2952,7 +2984,6 @@ window.__ModuleLoader__.load({
|
|
|
2952
2984
|
) : null
|
|
2953
2985
|
);
|
|
2954
2986
|
}
|
|
2955
|
-
|
|
2956
2987
|
function apply(ctx) {
|
|
2957
2988
|
const settingsSource = createSettingsSource(ctx);
|
|
2958
2989
|
ctx.effect(() => ctx.locale.register(NS, { en, zh }), 'dsh-key-rotation: dictionaries');
|
|
@@ -2963,7 +2994,7 @@ window.__ModuleLoader__.load({
|
|
|
2963
2994
|
try {
|
|
2964
2995
|
ctx.slots.inject('conversation.session.header.utilities', () =>
|
|
2965
2996
|
ctx.slots.register(
|
|
2966
|
-
{ name: 'conversation.session.header.utilities', id: 'dsh-key-rotation-header-chip', order: 6, locale: NS },
|
|
2997
|
+
{ name: 'conversation.session.header.utilities', id: 'dsh-key-rotation-header-chip', order: 6, locale: NS, inject: () => ({ ctx, settingsSource }) },
|
|
2967
2998
|
KeyRotationHeaderChip,
|
|
2968
2999
|
));
|
|
2969
3000
|
} catch { /* slot not available in this build */ }
|
package/lib/index.js
CHANGED
|
@@ -119,6 +119,7 @@ const SettingsSchema = Schema.object({
|
|
|
119
119
|
earlyWarningKeyThreshold: Schema.number().default(0.25),
|
|
120
120
|
warnBelowHealthy: Schema.number().default(0),
|
|
121
121
|
latencySloMs: Schema.number().default(0),
|
|
122
|
+
showChatHeaderChip: Schema.boolean().default(true),
|
|
122
123
|
providers: Schema.array(Schema.object({
|
|
123
124
|
provider: Schema.string().required(),
|
|
124
125
|
keys: Schema.array(Schema.string()).default([]),
|
|
@@ -350,6 +351,7 @@ export function apply(ctx, config = {}) {
|
|
|
350
351
|
proactiveRateLimitGuard: cfg.proactiveRateLimitGuard ?? true,
|
|
351
352
|
selfHealingIntervalMinutes: cfg.selfHealingIntervalMinutes ?? 30,
|
|
352
353
|
routingStrategy: cfg.routingStrategy ?? 'round-robin',
|
|
354
|
+
showChatHeaderChip: cfg.showChatHeaderChip !== false,
|
|
353
355
|
switchNotifyThrottleMs: cfg.switchNotifyThrottleMs ?? 60000,
|
|
354
356
|
warnBelowHealthy: cfg.warnBelowHealthy ?? 0, latencySloMs: cfg.latencySloMs ?? 0,
|
|
355
357
|
index, pools,
|
package/lib/ops-status.js
CHANGED
|
@@ -178,6 +178,7 @@ ctx.effect(() => ctx.webServer.register({
|
|
|
178
178
|
}
|
|
179
179
|
json(res, 200, sanitizeSnapshot({
|
|
180
180
|
providers,
|
|
181
|
+
showChatHeaderChip: runtime.showChatHeaderChip !== false,
|
|
181
182
|
// #266/#263 operational extras (additive)
|
|
182
183
|
meta: {
|
|
183
184
|
expectedClones: [...(runtime.expectedClones ?? [])],
|
|
@@ -248,7 +249,7 @@ ctx.effect(() => ctx.webServer.register({
|
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
const isDegraded = exhaustedAny || circuitOpenAny;
|
|
251
|
-
json(res, 200, { status: isDegraded ? 'degraded' : 'ok', pools, exhaustedAny, latency: latencyHistogram.snapshotAll(), quota: typeof quotaStore?.snapshot === 'function' ? quotaStore.snapshot() : null });
|
|
252
|
+
json(res, 200, { status: isDegraded ? 'degraded' : 'ok', showChatHeaderChip: runtimeH.showChatHeaderChip !== false, pools, exhaustedAny, latency: latencyHistogram.snapshotAll(), quota: typeof quotaStore?.snapshot === 'function' ? quotaStore.snapshot() : null });
|
|
252
253
|
},
|
|
253
254
|
}), 'dsh-key-rotation: health');
|
|
254
255
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-key-rotation",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.63",
|
|
4
4
|
"packageManager": "pnpm@10.33.2",
|
|
5
5
|
"description": "Per-provider API key rotation for DeepSeek Harness: a key pool per provider, auto-created clone routes, and switching to the next key on quota/rate-limit errors. Includes a Settings section (Key Rotation) to edit the key pools, cooldown and switch codes.",
|
|
6
6
|
"keywords": [
|