@bd7pil/reeve 0.1.13 → 0.1.14
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/default-fallback.json +2 -1
- package/package.json +1 -1
- package/provision.json +2 -2
- package/src/gate/config.js +5 -3
- package/src/gate/free.js +62 -5
package/default-fallback.json
CHANGED
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"prefer": ["glm-5.3-flash-free", "mimo-v2.6-flash-free", "deepseek-v4.1-flash-free"],
|
|
31
31
|
"deny": [],
|
|
32
32
|
"require_image": true,
|
|
33
|
-
"
|
|
33
|
+
"modality_url": "https://models.dev/api.json",
|
|
34
|
+
"modality_ttl_seconds": 21600,
|
|
34
35
|
"static": {}
|
|
35
36
|
},
|
|
36
37
|
"chains": {
|
package/package.json
CHANGED
package/provision.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": 1,
|
|
3
3
|
"name": "reeve",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.14",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"//": "REEVE 在 ocrc 实例里的装配说明。**没有二进制工件** —— 代理层是插件内的 GATE(纯 JS),不需要任何本地服务(Bifrost 时代已结束)。",
|
|
7
7
|
"host": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
]
|
|
16
16
|
},
|
|
17
17
|
"enginePlugin": {
|
|
18
|
-
"spec": "@bd7pil/reeve@0.1.
|
|
18
|
+
"spec": "@bd7pil/reeve@0.1.14",
|
|
19
19
|
"kind": "package",
|
|
20
20
|
"entry": "~/.ocrc/components/reeve/node_modules/@bd7pil/reeve",
|
|
21
21
|
"requiresPeer": []
|
package/src/gate/config.js
CHANGED
|
@@ -45,9 +45,10 @@ export const FREE_DEFAULTS = Object.freeze({
|
|
|
45
45
|
failure_cooldown_seconds: 600,
|
|
46
46
|
prefer: Object.freeze([]),
|
|
47
47
|
deny: Object.freeze([]),
|
|
48
|
-
//
|
|
48
|
+
// 免费池只保留 image-capable 模型:模态从 models.dev 动态读取(不再维护静态白名单)
|
|
49
49
|
require_image: false,
|
|
50
|
-
|
|
50
|
+
modality_url: 'https://models.dev/api.json',
|
|
51
|
+
modality_ttl_seconds: 21_600,
|
|
51
52
|
static: Object.freeze({}),
|
|
52
53
|
sources: Object.freeze({
|
|
53
54
|
// 公开模型列表(不需要键;拉不到只影响该 provider,请求照常降级到付费)
|
|
@@ -125,7 +126,8 @@ export function normalizeGateConfig(raw) {
|
|
|
125
126
|
prefer: asStrings(fSrc.prefer),
|
|
126
127
|
deny: asStrings(fSrc.deny),
|
|
127
128
|
require_image: fSrc.require_image === true,
|
|
128
|
-
|
|
129
|
+
modality_url: typeof fSrc.modality_url === 'string' && fSrc.modality_url.length > 0 ? fSrc.modality_url : FREE_DEFAULTS.modality_url,
|
|
130
|
+
modality_ttl_seconds: asInt(fSrc.modality_ttl_seconds, 60, 604_800, FREE_DEFAULTS.modality_ttl_seconds),
|
|
129
131
|
static: {},
|
|
130
132
|
sources: { ...FREE_DEFAULTS.sources },
|
|
131
133
|
}
|
package/src/gate/free.js
CHANGED
|
@@ -37,6 +37,8 @@ export function createFreeLane({ getConfig, getAuth = () => ({}), now = Date.now
|
|
|
37
37
|
/** ref -> { fails: number, until: number }(全局,跨 session:坏模型对所有人都坏) */
|
|
38
38
|
const cooldowns = new Map()
|
|
39
39
|
const inflight = new Map()
|
|
40
|
+
/** models.dev 的 modalities:{ at, map: Map<ref, boolean>|null, error, inflight } */
|
|
41
|
+
const modalityCache = { at: 0, map: null, error: null, inflight: null }
|
|
40
42
|
|
|
41
43
|
const freeCfg = () => {
|
|
42
44
|
const f = getConfig()?.free
|
|
@@ -107,6 +109,52 @@ export function createFreeLane({ getConfig, getAuth = () => ({}), now = Date.now
|
|
|
107
109
|
return inflight.get(id)
|
|
108
110
|
}
|
|
109
111
|
|
|
112
|
+
/**
|
|
113
|
+
* 免费池的模态元数据(models.dev):只拉一次,TTL 6h;失败保留上一份好数据。
|
|
114
|
+
* 不能用 Zen 的 `/models`:它只有 id/object/created/owned_by,没有 modalities。
|
|
115
|
+
*/
|
|
116
|
+
const loadModalities = async () => {
|
|
117
|
+
const free = freeCfg()
|
|
118
|
+
if (free.require_image !== true) return modalityCache
|
|
119
|
+
const ttlMs = Math.max(60, Number(free.modality_ttl_seconds ?? 21_600)) * 1000
|
|
120
|
+
if (modalityCache.map && now() - modalityCache.at < ttlMs) return modalityCache
|
|
121
|
+
if (modalityCache.inflight) return modalityCache.inflight
|
|
122
|
+
const url = typeof free.modality_url === 'string' && free.modality_url.length > 0 ? free.modality_url : 'https://models.dev/api.json'
|
|
123
|
+
modalityCache.inflight = (async () => {
|
|
124
|
+
try {
|
|
125
|
+
const res = await fetchImpl(url, {
|
|
126
|
+
headers: { accept: 'application/json' },
|
|
127
|
+
signal: AbortSignal.timeout(Math.max(timeoutMs, 15_000)),
|
|
128
|
+
})
|
|
129
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
|
130
|
+
const body = await res.json()
|
|
131
|
+
const map = new Map()
|
|
132
|
+
for (const [pid, provider] of Object.entries(body ?? {})) {
|
|
133
|
+
const models = provider?.models
|
|
134
|
+
if (!models || typeof models !== 'object') continue
|
|
135
|
+
for (const [mid, model] of Object.entries(models)) {
|
|
136
|
+
const input = Array.isArray(model?.modalities?.input)
|
|
137
|
+
? model.modalities.input
|
|
138
|
+
: Array.isArray(model?.input)
|
|
139
|
+
? model.input
|
|
140
|
+
: null
|
|
141
|
+
map.set(`${pid}/${mid}`, input ? input.includes('image') : model?.attachment === true)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
modalityCache.map = map
|
|
145
|
+
modalityCache.at = now()
|
|
146
|
+
modalityCache.error = null
|
|
147
|
+
} catch (err) {
|
|
148
|
+
modalityCache.error = String(err?.message ?? err).slice(0, 140)
|
|
149
|
+
logger?.debug?.(`[gate] 免费池模态元数据拉取失败(保留上一份):${modalityCache.error}`)
|
|
150
|
+
} finally {
|
|
151
|
+
modalityCache.inflight = null
|
|
152
|
+
}
|
|
153
|
+
return modalityCache
|
|
154
|
+
})()
|
|
155
|
+
return modalityCache.inflight
|
|
156
|
+
}
|
|
157
|
+
|
|
110
158
|
const isCooling = (ref) => {
|
|
111
159
|
const c = cooldowns.get(ref)
|
|
112
160
|
if (!c) return false
|
|
@@ -127,17 +175,22 @@ export function createFreeLane({ getConfig, getAuth = () => ({}), now = Date.now
|
|
|
127
175
|
const free = freeCfg()
|
|
128
176
|
if (!free.enabled) return []
|
|
129
177
|
const requireImage = free.require_image === true
|
|
130
|
-
|
|
178
|
+
let modalities = null
|
|
179
|
+
if (requireImage) {
|
|
180
|
+
const m = await loadModalities()
|
|
181
|
+
modalities = m.map
|
|
182
|
+
// 元数据从未成功拿到 → 没有可靠证据证明哪个免费模型能吃图;宁可走付费,也不退回纯文本。
|
|
183
|
+
if (!modalities) return []
|
|
184
|
+
}
|
|
131
185
|
const out = []
|
|
132
186
|
for (const id of asList(free.providers)) {
|
|
133
187
|
const provider = providers()[id]
|
|
134
188
|
if (!provider?.base_url || !hasKey(provider)) continue
|
|
135
189
|
const st = await load(id)
|
|
136
190
|
for (const model of st.models ?? []) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
out.push({ provider: id, model, ref: `${id}/${model}` })
|
|
191
|
+
const ref = `${id}/${model}`
|
|
192
|
+
if (requireImage && modalities.get(ref) !== true) continue
|
|
193
|
+
out.push({ provider: id, model, ref })
|
|
141
194
|
}
|
|
142
195
|
}
|
|
143
196
|
return out
|
|
@@ -244,6 +297,9 @@ export function createFreeLane({ getConfig, getAuth = () => ({}), now = Date.now
|
|
|
244
297
|
cooling,
|
|
245
298
|
prefer: asList(free.prefer),
|
|
246
299
|
deny: asList(free.deny),
|
|
300
|
+
require_image: free.require_image === true,
|
|
301
|
+
modality_ready: Boolean(modalityCache.map),
|
|
302
|
+
modality_error: modalityCache.error,
|
|
247
303
|
}
|
|
248
304
|
}
|
|
249
305
|
|
|
@@ -269,6 +325,7 @@ export function createFreeLane({ getConfig, getAuth = () => ({}), now = Date.now
|
|
|
269
325
|
const warm = () => {
|
|
270
326
|
const free = freeCfg()
|
|
271
327
|
if (free.enabled === false) return
|
|
328
|
+
if (free.require_image === true) void loadModalities().catch(() => {})
|
|
272
329
|
for (const id of asList(free.providers)) {
|
|
273
330
|
if (providers()[id]?.base_url && hasKey(providers()[id])) void load(id).catch(() => {})
|
|
274
331
|
}
|