@omdp/dsh-key-fallback 1.0.0 → 1.0.2

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/cordis.patch.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  - insert:
2
2
  - id: key-fallback
3
- name: dsh-key-fallback
3
+ name: '@omdp/dsh-key-fallback'
4
4
  inject: [llm, settings, webServer]
5
5
  config: {}
package/lib/client.js CHANGED
@@ -1,65 +1,140 @@
1
- 'use strict'
2
1
  // @omdp/dsh-key-fallback — Client half
3
2
  // Settings → 插件 里的只读状态卡:显示每个 provider 的 key 数 / 游标 / 冷却。
4
- function apply(ctx) {
5
- const slots = ctx.get('slots')
6
- if (slots === undefined) return
3
+ //
4
+ // Hand-written in the lazy-CJS bundle protocol (window.__ModuleLoader__.load
5
+ // with a factory receiving `require`) — the same zero-dependency stance as the
6
+ // host half. The registered id MUST be the package name (@omdp/dsh-key-fallback):
7
+ // dsh-client-modules rejects any bundle whose __ModuleLoader__.load id does not
8
+ // match the package it was loaded for.
9
+ window.__ModuleLoader__.load({
10
+ id: '@omdp/dsh-key-fallback',
11
+ factory: function (require) {
12
+ var module = { exports: {} }
13
+ var exports = module.exports
14
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' })
15
+ var React = require('react')
7
16
 
8
- async function fetchJson(url, opts) {
9
- const r = await fetch(url, opts)
10
- return r.json()
11
- }
17
+ function fetchJson(url, signal) {
18
+ return fetch(url, signal ? { signal: signal } : undefined).then(function (r) { return r.json() })
19
+ }
12
20
 
13
- function KeyFallbackCard() {
14
- const [state, setState] = React.useState({ loading: true, data: null })
15
- const refresh = React.useCallback(function () {
16
- fetchJson('/dsh-key-fallback/pools')
17
- .then(function (data) { setState({ loading: false, data: (data && data.pools) || {} }) })
18
- .catch(function () { setState({ loading: false, data: null }) })
19
- }, [])
20
- React.useEffect(function () {
21
- refresh()
22
- const t = setInterval(refresh, 2000)
23
- return function () { clearInterval(t) }
24
- }, [refresh])
21
+ // 卡片可见性驱动的"仅在用户观看时轮询":
22
+ // 不看(页面隐藏 / 卡片滚出视口 / 组件卸载)→ 停止轮询,零网络;
23
+ // 重新可见 → 立即刷新并恢复每 2s 轮询,数据最多晚 2s,正常及时。
24
+ function KeyFallbackCard() {
25
+ var s = React.useState({ loading: true, data: null })
26
+ var state = s[0]
27
+ var setState = s[1]
28
+ var rootRef = React.useRef(null)
29
+ var ioRef = React.useRef(null)
25
30
 
26
- if (state.loading) {
27
- return React.createElement('div', { style: { fontSize: '12px', opacity: 0.6 } }, '加载中…')
28
- }
29
- const data = state.data || {}
30
- const providers = Object.keys(data)
31
- if (providers.length === 0) {
32
- return React.createElement('p', { style: { fontSize: '12px', opacity: 0.6, margin: 0 } },
33
- '未在 settings.yaml 配置 keyFallback.providers')
31
+ // 回调 ref:卡片根元素变化(loading ↔ 列表)时先解除旧元素再观察新元素,
32
+ // 避免旧元素因被移除触发 isIntersecting:false 而错误停止轮询。
33
+ var attachRoot = React.useCallback(function (el) {
34
+ var prev = rootRef.current
35
+ var io = ioRef.current
36
+ if (io && prev && prev !== el) io.unobserve(prev)
37
+ rootRef.current = el
38
+ if (io && el) io.observe(el)
39
+ }, [])
40
+
41
+ var refresh = React.useCallback(function () {
42
+ fetchJson('/dsh-key-fallback/pools')
43
+ .then(function (data) { setState({ loading: false, data: (data && data.pools) || {} }) })
44
+ .catch(function () { setState({ loading: false, data: null }) })
45
+ }, [])
46
+
47
+ React.useEffect(function () {
48
+ var alive = true
49
+ var timer = null
50
+ var pageVisible = !document.hidden
51
+ var cardVisible = true
52
+
53
+ function stop() {
54
+ if (timer !== null) { clearInterval(timer); timer = null }
55
+ }
56
+ function start() {
57
+ if (!alive || timer !== null || !pageVisible || !cardVisible) return
58
+ refresh()
59
+ timer = setInterval(refresh, 2000)
60
+ }
61
+ function sync() {
62
+ if (!alive) return
63
+ if (pageVisible && cardVisible) start()
64
+ else stop()
65
+ }
66
+ function onVisibility() {
67
+ pageVisible = !document.hidden
68
+ sync()
69
+ }
70
+
71
+ document.addEventListener('visibilitychange', onVisibility)
72
+
73
+ if (typeof IntersectionObserver === 'function') {
74
+ var io = new IntersectionObserver(function (entries) {
75
+ if (!alive) return
76
+ for (var i = 0; i < entries.length; i++) {
77
+ cardVisible = entries[i].isIntersecting
78
+ }
79
+ sync()
80
+ }, { threshold: 0.01 })
81
+ ioRef.current = io
82
+ var el = rootRef.current
83
+ if (el) io.observe(el)
84
+ }
85
+
86
+ refresh()
87
+ start()
88
+
89
+ return function () {
90
+ alive = false
91
+ stop()
92
+ document.removeEventListener('visibilitychange', onVisibility)
93
+ if (ioRef.current) { ioRef.current.disconnect(); ioRef.current = null }
94
+ ioRef.current = null
95
+ }
96
+ }, [refresh])
97
+
98
+ if (state.loading) {
99
+ return React.createElement('div', { ref: attachRoot, style: { fontSize: '12px', opacity: 0.6 } }, '加载中…')
100
+ }
101
+ var data = state.data || {}
102
+ var providers = Object.keys(data)
103
+ if (providers.length === 0) {
104
+ return React.createElement('p', { ref: attachRoot, style: { fontSize: '12px', opacity: 0.6, margin: 0 } },
105
+ '未在 settings.yaml 配置 keyFallback.providers')
106
+ }
107
+ return React.createElement('div', { ref: attachRoot, style: { fontSize: '12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
108
+ providers.map(function (p) {
109
+ var pd = data[p]
110
+ return React.createElement('div', { key: p, style: { padding: '6px 0', borderTop: '1px solid var(--dsw-alias-border-l2, #eee)' } },
111
+ React.createElement('div', { style: { fontWeight: 600 } }, p + ' (' + pd.env + ')'),
112
+ React.createElement('div', { style: { opacity: 0.8 } }, 'key 数: ' + pd.keyCount + ',当前游标: ' + pd.currentIndex),
113
+ pd.cooling.length > 0
114
+ ? React.createElement('div', { style: { color: '#c62828' } }, '冷却中: ' + pd.cooling.join(', '))
115
+ : null,
116
+ pd.healthy.length > 0
117
+ ? React.createElement('div', { style: { color: '#2e7d32' } }, '健康: ' + pd.healthy.join(', '))
118
+ : null,
119
+ )
120
+ }),
121
+ )
34
122
  }
35
- return React.createElement('div', { style: { fontSize: '12px', display: 'flex', flexDirection: 'column', gap: '8px' } },
36
- providers.map(function (p) {
37
- const pd = data[p]
38
- return React.createElement('div', { key: p, style: { padding: '6px 0', borderTop: '1px solid var(--dsw-alias-border-l2, #eee)' } },
39
- React.createElement('div', { style: { fontWeight: 600 } }, p + ' (' + pd.env + ')'),
40
- React.createElement('div', { style: { opacity: 0.8 } }, 'key 数: ' + pd.keyCount + ',当前游标: ' + pd.currentIndex),
41
- pd.cooling.length > 0
42
- ? React.createElement('div', { style: { color: '#c62828' } }, '冷却中: ' + pd.cooling.join(', '))
43
- : null,
44
- pd.healthy.length > 0
45
- ? React.createElement('div', { style: { color: '#2e7d32' } }, '健康: ' + pd.healthy.join(', '))
46
- : null,
47
- )
48
- }),
49
- )
50
- }
51
123
 
52
- ctx.slots.inject('settings.plugin.item', function* () {
53
- yield ctx.slots.register({
54
- name: 'settings.plugin.item',
55
- key: 'key-fallback',
56
- id: 'key-fallback',
57
- order: 50,
58
- label: () => 'API Key 回退',
59
- inject: () => ({}),
60
- }, KeyFallbackCard)
61
- })
62
- }
124
+ function apply(ctx) {
125
+ var slots = ctx.get('slots')
126
+ if (slots === undefined) return
127
+ // settings.plugin.item 是 root-scope 的 keyed slot,按 settings 命名空间
128
+ // 分发:host 端 settings.register('key-fallback', …) 让该 key 进入
129
+ // served 集合后,本卡片才会被渲染(参考 auto-continue 的注册姿势)。
130
+ slots.inject('settings.plugin.item', () => slots.register({
131
+ name: 'settings.plugin.item',
132
+ key: 'key-fallback',
133
+ }, KeyFallbackCard))
134
+ }
63
135
 
64
- exports.apply = apply
65
- exports.inject = ['slots']
136
+ exports.apply = apply
137
+ exports.inject = ['slots']
138
+ return module.exports
139
+ },
140
+ })
package/lib/index.js CHANGED
@@ -8,6 +8,11 @@ const { load: loadYaml } = (() => {
8
8
  try { return require('js-yaml') }
9
9
  catch { return { load: (s) => { try { return JSON.parse(s) } catch { return {} } } } }
10
10
  })()
11
+ // schemastery 用于注册 settings 命名空间占位(served 集合来源)。
12
+ // bundle 环境解析失败时降级为 null,此时仅卡片显示受影响、轮换逻辑照常。
13
+ const zs = (() => {
14
+ try { return require('@deepseek-ai/schemastery') } catch { return null }
15
+ })()
11
16
 
12
17
  const name = 'key-fallback'
13
18
  const inject = ['llm', 'settings', 'webServer']
@@ -41,12 +46,26 @@ function readConfig() {
41
46
  }
42
47
 
43
48
  function apply(ctx) {
44
- // 占位命名空间:让只读状态卡出现在 Settings → 插件(keyed slot 要求 key ∈ served)
45
- ctx.inject(['settings'], (sctx) => {
49
+ // 注册 settings 命名空间(含真实字段 schema):让客户端 Settings → 插件
50
+ // 的 keyed slot(settings.plugin.item, key='key-fallback')被 serve 并渲染状态卡。
51
+ // 状态卡只读;轮换逻辑仍直接读 settings.yaml(v1.0.0 原设计)。
52
+ // 注意:1.0.0 版写的是 `if (typeof zs !== 'undefined')` 死代码(zs 从未导入),
53
+ // 注册实际从未发生;这里用真实 require 修复。
54
+ if (zs) {
46
55
  try {
47
- if (typeof zs !== 'undefined') sctx.settings.register('key-fallback', zs.object({}), { base: {} })
48
- } catch (e) { /* 命名空间注册失败不影响核心轮换逻辑 */ }
49
- })
56
+ ctx.inject(['settings'], (sctx) => {
57
+ sctx.settings.register('key-fallback', zs.object({
58
+ providers: zs.dict(zs.object({
59
+ env: zs.string(),
60
+ keys: zs.array(zs.string()),
61
+ cooldownMs: zs.natural(),
62
+ })),
63
+ }), { base: {} })
64
+ })
65
+ } catch (e) {
66
+ // 命名空间注册失败不影响核心轮换逻辑
67
+ }
68
+ }
50
69
 
51
70
  const pools = new Map()
52
71
  const attempts = new Map() // `${turn}:${step}` -> 已尝试次数(防止死循环)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@omdp/dsh-key-fallback",
3
3
  "description": "Minimal API key fallback for DeepSeek Harness: on a retryable request error, silently retry the same call with the next key in the pool.",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "type": "commonjs",
6
6
  "main": "lib/index.js",
7
7
  "exports": {