@ebony-vinyl/dsh-our-free-model 2.8.3 → 2.8.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 +12 -5
- package/client.js +355 -22
- package/index.js +81 -1
- package/package.json +1 -1
- package/src/catalog.js +148 -31
- package/src/model-test.js +280 -2
- package/src/o2a.js +46 -9
- package/src/plugins.js +2 -2
- package/src/store.js +12 -0
- package/src/updater.js +21 -6
- package/vendor/channel-pack/pack.js +7 -2
package/index.js
CHANGED
|
@@ -43,7 +43,7 @@ import { clearEacUser, readEacUser, writeEacUser } from './src/eac-user.js'
|
|
|
43
43
|
import { createEacLoginPoller } from './src/eac-login.js'
|
|
44
44
|
import { unlockSealedLane } from './src/vault.js'
|
|
45
45
|
import { DEFAULT_LEVEL, budgetLadder, hasDeclaredEffortMenu } from './src/effort.js'
|
|
46
|
-
import { modelAvailability } from './src/model-test.js'
|
|
46
|
+
import { readPerf, readSeries, siteRootFor, modelAvailability } from './src/model-test.js'
|
|
47
47
|
import { AnnouncementFeed } from './src/feed.js'
|
|
48
48
|
import { createPickStats } from './src/picks.js'
|
|
49
49
|
import { PluginUpdater, installationIsManaged } from './src/updater.js'
|
|
@@ -170,6 +170,19 @@ export function apply(ctx, config) {
|
|
|
170
170
|
if (generation > 1) {
|
|
171
171
|
settings.update({ reloadedAt: Date.now(), reloadCount: generation - 1 })
|
|
172
172
|
settings.flush()
|
|
173
|
+
} else {
|
|
174
|
+
// Before issue #761 the only "already seen" marker lived in the browser's
|
|
175
|
+
// origin-scoped localStorage. A settings file from that build can carry an
|
|
176
|
+
// old reloadedAt with no acknowledgement beside it; adopting it here keeps
|
|
177
|
+
// a plain process restart (a new desktop port is a new origin) from
|
|
178
|
+
// replaying a stale notice. A genuinely new reload writes a new timestamp.
|
|
179
|
+
const stored = settings.get()
|
|
180
|
+
const at = Number(stored.reloadedAt ?? 0)
|
|
181
|
+
const seen = Number(stored.reloadNoticeSeen ?? 0)
|
|
182
|
+
if (Number.isSafeInteger(at) && at > 0 && seen === 0) {
|
|
183
|
+
settings.update({ reloadNoticeSeen: at })
|
|
184
|
+
settings.flush()
|
|
185
|
+
}
|
|
173
186
|
}
|
|
174
187
|
|
|
175
188
|
/** `managed` stands down everything that would rewrite the installed bytes. */
|
|
@@ -1152,6 +1165,10 @@ export function apply(ctx, config) {
|
|
|
1152
1165
|
},
|
|
1153
1166
|
subscriptions,
|
|
1154
1167
|
onSubscriptionsChanged: syncAggregateCatalog,
|
|
1168
|
+
// The sealed credential names the lane's base (`…/eac/v1`); the perf route
|
|
1169
|
+
// derives the site root from it so the metrics endpoint is found without a
|
|
1170
|
+
// second hard-coded address.
|
|
1171
|
+
sealedCredential: sealedCredentialOf,
|
|
1155
1172
|
// ── the aggregate lane's settings surface ────────────────────────────────
|
|
1156
1173
|
// The registry is static data; the configured half is the settings file with
|
|
1157
1174
|
// every key masked. Writing one provider rebuilds the catalog and re-emits
|
|
@@ -1404,6 +1421,7 @@ export function apply(ctx, config) {
|
|
|
1404
1421
|
generation,
|
|
1405
1422
|
reloadedAt: settings.get().reloadedAt ?? 0,
|
|
1406
1423
|
reloadCount: settings.get().reloadCount ?? 0,
|
|
1424
|
+
reloadNoticeSeen: settings.get().reloadNoticeSeen ?? 0,
|
|
1407
1425
|
dataDir,
|
|
1408
1426
|
autoReloadWatch: settings.get().autoReloadWatch === true,
|
|
1409
1427
|
lastReload: globalThis[Symbol.for('our-free-model.last-reload')] ?? undefined,
|
|
@@ -1816,6 +1834,29 @@ function imageResolver(ctx, logger) {
|
|
|
1816
1834
|
/** The fixed failure classes the pool proxy may surface to the client. */
|
|
1817
1835
|
const POOL_REASONS = new Set(['no-lane', 'gateway-status', 'malformed', 'unreachable'])
|
|
1818
1836
|
|
|
1837
|
+
// ── per-model performance figures ─────────────────────────────────────────────
|
|
1838
|
+
// The roster asks for these on every mount, and they are hourly samples: a
|
|
1839
|
+
// minute of staleness costs nothing, while an outbound hop per page load is
|
|
1840
|
+
// pure load on the site. The in-flight promise is shared as well, so a burst of
|
|
1841
|
+
// mounts costs one request rather than one each.
|
|
1842
|
+
const PERF_FRESH_MS = 60_000
|
|
1843
|
+
let perfCache = { at: 0, data: null }
|
|
1844
|
+
let perfPending = null
|
|
1845
|
+
function perfSnapshot(root, fetchImpl) {
|
|
1846
|
+
if (root === null || root === undefined) return Promise.resolve({ models: {} })
|
|
1847
|
+
if (perfCache.data !== null && Date.now() - perfCache.at < PERF_FRESH_MS) return Promise.resolve(perfCache.data)
|
|
1848
|
+
if (perfPending !== null) return perfPending
|
|
1849
|
+
perfPending = readPerf({ root, fetch: fetchImpl })
|
|
1850
|
+
.then(data => {
|
|
1851
|
+
// Only a non-empty answer is worth keeping: caching "the site did not
|
|
1852
|
+
// answer" would hide a recovery for a full minute.
|
|
1853
|
+
if (Object.keys(data.models).length > 0) perfCache = { at: Date.now(), data }
|
|
1854
|
+
return data
|
|
1855
|
+
})
|
|
1856
|
+
.finally(() => { perfPending = null })
|
|
1857
|
+
return perfPending
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1819
1860
|
export function createApiRoutes(deps) {
|
|
1820
1861
|
return async function handler(req, res) {
|
|
1821
1862
|
const url = new URL(req.url ?? '/', 'http://localhost')
|
|
@@ -1876,6 +1917,30 @@ export function createApiRoutes(deps) {
|
|
|
1876
1917
|
if (method === 'GET' && routePath === '/stats') {
|
|
1877
1918
|
return send(200, buildStats(deps.stats.get(), deps.catalog()))
|
|
1878
1919
|
}
|
|
1920
|
+
// Per-model performance figures for the roster's cards. The site's own
|
|
1921
|
+
// metrics endpoint is public but sends no CORS headers, so the page
|
|
1922
|
+
// cannot call it directly — this is the server-side hop. One request
|
|
1923
|
+
// answers every model, and the map holds only models whose figure is
|
|
1924
|
+
// trustworthy (see src/model-test.js); an absent model draws no strip.
|
|
1925
|
+
//
|
|
1926
|
+
// Cached for a minute and de-duplicated while in flight: the roster asks
|
|
1927
|
+
// on every mount, and the figures are hourly samples — a fresh outbound
|
|
1928
|
+
// request per page load buys nothing but load on the site.
|
|
1929
|
+
if (method === 'GET' && routePath === '/perf') {
|
|
1930
|
+
const credential = deps.sealedCredential?.() ?? null
|
|
1931
|
+
const root = siteRootFor(credential?.base)
|
|
1932
|
+
return send(200, await perfSnapshot(root, deps.perfFetch ?? directFetch))
|
|
1933
|
+
}
|
|
1934
|
+
// One model's hourly trend series, for the charts behind 详情. Separate
|
|
1935
|
+
// from `/perf` because it is per model: the roster would otherwise pay
|
|
1936
|
+
// twelve requests to draw twelve strips nobody has expanded yet.
|
|
1937
|
+
if (method === 'GET' && routePath === '/perf/series') {
|
|
1938
|
+
const modelId = url.searchParams.get('model') ?? ''
|
|
1939
|
+
const credential = deps.sealedCredential?.() ?? null
|
|
1940
|
+
const root = siteRootFor(credential?.base)
|
|
1941
|
+
const series = await readSeries({ root, modelId, fetch: deps.perfFetch ?? directFetch })
|
|
1942
|
+
return send(200, series === null ? { series: null } : series)
|
|
1943
|
+
}
|
|
1879
1944
|
if (method === 'GET' && routePath === '/outlet') {
|
|
1880
1945
|
return send(200, await deps.outletStatus())
|
|
1881
1946
|
}
|
|
@@ -1967,6 +2032,21 @@ export function createApiRoutes(deps) {
|
|
|
1967
2032
|
if (method === 'GET' && routePath === '/meta') {
|
|
1968
2033
|
return send(200, { ...deps.meta(), feed: { fetchedAt: deps.announcements.view().fetchedAt, source: deps.announcements.view().source, error: deps.announcements.view().error }, update: deps.update.status() })
|
|
1969
2034
|
}
|
|
2035
|
+
if (method === 'POST' && routePath === '/reload-notice/ack') {
|
|
2036
|
+
const body = await readJson(req)
|
|
2037
|
+
const at = body?.at
|
|
2038
|
+
const current = Number(deps.settings.get().reloadedAt ?? 0)
|
|
2039
|
+
// Only the timestamp this process announced may be acknowledged: an
|
|
2040
|
+
// older page or a hand-written request must not pre-mark a future
|
|
2041
|
+
// reload as seen.
|
|
2042
|
+
if (typeof at !== 'number' || !Number.isSafeInteger(at) || at <= 0 || at !== current) {
|
|
2043
|
+
return send(400, { error: 'at must match the current reloadedAt' })
|
|
2044
|
+
}
|
|
2045
|
+
deps.settings.update({ reloadNoticeSeen: at })
|
|
2046
|
+
deps.settings.flush()
|
|
2047
|
+
if (deps.settings.writeFailed === true) return send(500, { error: 'could not persist settings' })
|
|
2048
|
+
return send(200, { ok: true, reloadNoticeSeen: at })
|
|
2049
|
+
}
|
|
1970
2050
|
if (method === 'POST' && routePath === '/eac/login/start') {
|
|
1971
2051
|
const started = await deps.eacAuth.start()
|
|
1972
2052
|
return send(started.error === undefined ? 200 : 404, started.error === undefined ? started : { error: started.error })
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ebony-vinyl/dsh-our-free-model",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Our Free Model — 在 dsh 里装上这个插件即可,无需登录、注册或填 API Key,就能使用包括 DeepSeek V4.1 Flash、Kimi K3 在内的前沿模型,桌面端另有 EAC 协付渠道;2.0 起接入 LobsterAI、Qoder、TRAE、Cline、Loomy、Raccoon、MiniMax、ZCode、Gemini 十个账号渠道(原有匿名免费通道保留)(登录、账号池、积分领取、本地 OpenAI 网关与局域网转发、数据看板与运行日志),六页毛玻璃界面统一呈现。",
|
|
6
6
|
"repository": {
|
package/src/catalog.js
CHANGED
|
@@ -296,17 +296,33 @@ export const EAC_CHANNEL = 'eac'
|
|
|
296
296
|
export const EAC_TAG = 'EAC'
|
|
297
297
|
|
|
298
298
|
const EAC_DISPLAY_NAMES = {
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
'
|
|
299
|
+
// These two ids are the lane's routing names; the models behind them are
|
|
300
|
+
// Dahl Inference's (`inference.dahl.global`), whose own docs name them
|
|
301
|
+
// "DeepSeek V4 Flash" and "GLM 5.3 Flash". The card shows that name, not the
|
|
302
|
+
// relay's internal spelling — the org prefix and the `-free` suffix are both
|
|
303
|
+
// routing detail.
|
|
304
|
+
'opencode/deepseek-v4.1-flash-free': 'DeepSeek V4 Flash',
|
|
305
|
+
'opencode/glm-5.3-flash-free': 'GLM 5.3 Flash',
|
|
303
306
|
'deepseek-ai/deepseek-v4.1-flash': 'DeepSeek V4.1 Flash',
|
|
304
|
-
|
|
307
|
+
// Kimi K2.6 was removed from the NVIDIA channel on 2026-11-14 (its listing
|
|
308
|
+
// there never served this lane), so it is no longer named here either — an
|
|
309
|
+
// entry the catalog cannot receive is a name the picker never shows.
|
|
305
310
|
'moonshotai/kimi-k3': 'Kimi K3',
|
|
306
311
|
'openai/gpt-oss-20b': 'GPT-OSS 20B',
|
|
307
312
|
'z-ai/glm-5.3': 'GLM 5.3',
|
|
308
313
|
'z-ai/glm-5.3-flash': 'GLM 5.3 Flash',
|
|
309
314
|
'mistralai/mistral-large-4-1t-a52b': 'Mistral Large 4 1T A52B',
|
|
315
|
+
// AMD Radeon (developer.amd.com.cn). The lane namespaces its ids `amd/…`;
|
|
316
|
+
// the card drops the prefix the same way it drops `org/` elsewhere.
|
|
317
|
+
'amd/DeepSeek-V4.1-Flash': 'DeepSeek V4.1 Flash (AMD)',
|
|
318
|
+
'amd/DeepSeek-V4-Flash': 'DeepSeek V4 Flash (AMD)',
|
|
319
|
+
'amd/Gemma-4-31B-IT': 'Gemma 4 31B IT (AMD)',
|
|
320
|
+
'amd/GLM-5.3-Flash': 'GLM 5.3 Flash (AMD)',
|
|
321
|
+
'amd/MiMo-V2.6-Flash': 'MiMo V2.6 Flash (AMD)',
|
|
322
|
+
'amd/MiniCPM5-2B': 'MiniCPM5 2B (AMD)',
|
|
323
|
+
'amd/MiniCPM-V-4.7-1B': 'MiniCPM-V 4.7 1B (AMD)',
|
|
324
|
+
'amd/MiniCPM-V-4.7-35B-A3B': 'MiniCPM-V 4.7 35B A3B (AMD)',
|
|
325
|
+
'amd/Qwen3.8-27B': 'Qwen3.8 27B (AMD)',
|
|
310
326
|
}
|
|
311
327
|
|
|
312
328
|
/**
|
|
@@ -364,24 +380,126 @@ const EAC_CAPABILITIES = [
|
|
|
364
380
|
effortPatch: { reasoning_effort: '$effort' },
|
|
365
381
|
effortOffPatch: { reasoning_effort: 'none' },
|
|
366
382
|
},
|
|
367
|
-
// The OpenCode lane's two ids,
|
|
368
|
-
//
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
//
|
|
373
|
-
//
|
|
374
|
-
//
|
|
383
|
+
// The OpenCode lane's two ids, served by Dahl Inference
|
|
384
|
+
// (`inference.dahl.global`, docs read 2026-11-14). Both are the broker's own
|
|
385
|
+
// ids: DeepSeek-V4-Flash-0731 and GLM-5.3-Flash.
|
|
386
|
+
//
|
|
387
|
+
// - Window: Dahl's `/docs/models/` publishes 400K for both.
|
|
388
|
+
// - Vision: Dahl states plainly that vision "is not offered on current
|
|
389
|
+
// models", and `image_url` content is rejected with a 400 — so `vision` is
|
|
390
|
+
// false, not the NVIDIA Build `true` its namesakes carry.
|
|
391
|
+
// - Menu: Dahl validates `reasoning_effort` against
|
|
392
|
+
// none|minimal|low|medium|high|xhigh|max and rejects `disabled` by name
|
|
393
|
+
// ("unsupported value: got \"disabled\""), so the off rung is `none` — the
|
|
394
|
+
// `effortOffPatch` below, which is what the plugin sends for `disabled`.
|
|
395
|
+
// - Output: DeepSeek's ceiling is measured, not guessed — a 5M `max_tokens`
|
|
396
|
+
// request came back `finish_reason: length` at 8192 completion tokens, so
|
|
397
|
+
// the broker caps output there regardless of what the caller asks for. GLM
|
|
398
|
+
// shares that broker cap; Dahl publishes no per-model figure.
|
|
375
399
|
{
|
|
376
400
|
match: /^opencode\/deepseek-v4/,
|
|
377
|
-
vision:
|
|
401
|
+
vision: false, reasoning: true, contextWindow: 409600, maxOutput: 8192,
|
|
378
402
|
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
379
403
|
effortPatch: { reasoning_effort: '$effort' },
|
|
380
404
|
effortOffPatch: { reasoning_effort: 'none' },
|
|
381
405
|
},
|
|
382
406
|
{
|
|
407
|
+
// GLM answers with reasoning even at `none` (measured: `none` still spent
|
|
408
|
+
// 57 reasoning tokens), so its menu has no off rung and the card shows the
|
|
409
|
+
// shared-budget tag rather than promising a switch it cannot honour.
|
|
383
410
|
match: /^opencode\/glm-5\.3-flash/,
|
|
384
|
-
vision:
|
|
411
|
+
vision: false, reasoning: true, contextWindow: 409600, maxOutput: 8192,
|
|
412
|
+
efforts: ['minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
413
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
414
|
+
},
|
|
415
|
+
// ── AMD Radeon (developer.amd.com.cn) ───────────────────────────────────────
|
|
416
|
+
//
|
|
417
|
+
// Two sources, each used for what it actually publishes:
|
|
418
|
+
//
|
|
419
|
+
// - The platform's own listing (`GET /radeon/api/v1/models`, read 2026-11-14)
|
|
420
|
+
// carries `context_length`, `architecture.input_modalities`, and a
|
|
421
|
+
// per-provider block with `vision`, `reasoning` and `tools`.
|
|
422
|
+
// `supported_parameters` lists `reasoning_effort`, so the level is the real
|
|
423
|
+
// control on the wire.
|
|
424
|
+
// - The output ceiling comes from **each model's own vendor docs**, because
|
|
425
|
+
// the platform publishes none. Where a vendor publishes one it is used
|
|
426
|
+
// verbatim; where a vendor publishes none either, `maxOutput` falls back to
|
|
427
|
+
// the platform's window and the comment says so — a guess is worse than a
|
|
428
|
+
// generous ceiling the vendor may clamp.
|
|
429
|
+
//
|
|
430
|
+
// Per-model vendor sources:
|
|
431
|
+
// - DeepSeek V4.1 Flash / V4 Flash: api-docs.deepseek.com model table —
|
|
432
|
+
// 1M context, 384K max output (`deepseek-flash`, "DeepSeek-V4.1-Flash-1017";
|
|
433
|
+
// the older v4-flash name is served by the same model).
|
|
434
|
+
// - GLM-5.3-Flash: docs.z.ai/guides/llm/glm-5.3-flash — 1M context, 128K max
|
|
435
|
+
// output. (The platform's listing reports 262144 for this id; the vendor's
|
|
436
|
+
// own figure is the larger one, so the window below is the platform's, which
|
|
437
|
+
// is the one requests actually hit.)
|
|
438
|
+
// - Gemma 4 31B IT: ai.google.dev model card — 256K context; Google publishes
|
|
439
|
+
// no output ceiling, so it inherits the window.
|
|
440
|
+
// - MiMo V2.6 Flash / Qwen3.8 27B / MiniCPM-V 4.7 / MiniCPM5 2B: neither the
|
|
441
|
+
// platform nor the vendor publishes an output ceiling (checked 2026-11-14),
|
|
442
|
+
// so each inherits its window and the card shows the window as the ceiling.
|
|
443
|
+
{
|
|
444
|
+
match: /^amd\/DeepSeek-V4\.1-Flash/,
|
|
445
|
+
vision: true, reasoning: true, contextWindow: 1048576, maxOutput: 393216,
|
|
446
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
447
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
448
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
match: /^amd\/DeepSeek-V4-Flash/,
|
|
452
|
+
vision: false, reasoning: true, contextWindow: 1048576, maxOutput: 393216,
|
|
453
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
454
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
455
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
match: /^amd\/GLM-5\.3-Flash/,
|
|
459
|
+
vision: false, reasoning: true, contextWindow: 262144, maxOutput: 131072,
|
|
460
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
461
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
462
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
// Xiaomi publishes no output ceiling for MiMo V2.6 Flash, so it inherits
|
|
466
|
+
// the window rather than a guessed 32K.
|
|
467
|
+
match: /^amd\/MiMo-V2\.6-Flash/,
|
|
468
|
+
vision: true, reasoning: true, contextWindow: 1048576, maxOutput: 1048576,
|
|
469
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
470
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
471
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
// Google's Gemma 4 model card states 256K of context and no output ceiling.
|
|
475
|
+
match: /^amd\/Gemma-4-31B-IT/,
|
|
476
|
+
vision: true, reasoning: true, contextWindow: 262144, maxOutput: 262144,
|
|
477
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
478
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
479
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
// Alibaba publishes no output ceiling for Qwen3.8-27B on the model-studio
|
|
483
|
+
// pages; it inherits the window.
|
|
484
|
+
match: /^amd\/Qwen3\.8-27B/,
|
|
485
|
+
vision: true, reasoning: true, contextWindow: 262144, maxOutput: 262144,
|
|
486
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
487
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
488
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
// OpenBMB publishes no output ceiling for the MiniCPM-V 4.7 pair either.
|
|
492
|
+
match: /^amd\/MiniCPM-V/,
|
|
493
|
+
vision: true, reasoning: true, contextWindow: 262144, maxOutput: 262144,
|
|
494
|
+
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
495
|
+
effortPatch: { reasoning_effort: '$effort' },
|
|
496
|
+
effortOffPatch: { reasoning_effort: 'none' },
|
|
497
|
+
},
|
|
498
|
+
{
|
|
499
|
+
// MiniCPM5-2B: the project README states "Context Length 131,072" and no
|
|
500
|
+
// output ceiling.
|
|
501
|
+
match: /^amd\/MiniCPM5-2B/,
|
|
502
|
+
vision: false, reasoning: true, contextWindow: 131072, maxOutput: 131072,
|
|
385
503
|
efforts: ['disabled', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], effortDefault: 'high',
|
|
386
504
|
effortPatch: { reasoning_effort: '$effort' },
|
|
387
505
|
effortOffPatch: { reasoning_effort: 'none' },
|
|
@@ -431,25 +549,25 @@ const EAC_FEATURED = new Set([
|
|
|
431
549
|
/**
|
|
432
550
|
* Display-only ids for the card's small line.
|
|
433
551
|
*
|
|
434
|
-
* The
|
|
435
|
-
*
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
552
|
+
* The lane serves its ids under its own routing names, with a `-free` suffix
|
|
553
|
+
* that is upstream detail. Behind them are Dahl Inference's models, and the
|
|
554
|
+
* line shows the id Dahl itself publishes (`GET /v1/models`, read 2026-11-14)
|
|
555
|
+
* — that is the string a reader can paste into a Dahl-compatible client. Only
|
|
556
|
+
* the label moves: the catalog `id` is what the adapter matches, and it keeps
|
|
557
|
+
* the lane's spelling.
|
|
440
558
|
*/
|
|
441
559
|
const EAC_LABELS = {
|
|
442
|
-
'opencode/deepseek-v4.1-flash-free': 'deepseek-
|
|
443
|
-
'opencode/glm-5.3-flash-free': '
|
|
560
|
+
'opencode/deepseek-v4.1-flash-free': 'deepseek-ai/DeepSeek-V4-Flash-0731',
|
|
561
|
+
'opencode/glm-5.3-flash-free': 'zai-org/GLM-5.3-Flash',
|
|
444
562
|
}
|
|
445
563
|
|
|
446
564
|
function eacLabel(modelId) {
|
|
447
565
|
const known = EAC_LABELS[modelId]
|
|
448
566
|
if (known !== undefined) return known
|
|
449
567
|
const bare = modelId.includes('/') ? modelId.slice(modelId.indexOf('/') + 1) : modelId
|
|
450
|
-
// Any remaining `-free` suffix is the
|
|
451
|
-
//
|
|
452
|
-
return bare.replace(/(?:^|[-_])free(?:$|[-_.])/,
|
|
568
|
+
// Any remaining `-free` suffix is the lane's own routing detail; the line
|
|
569
|
+
// drops it rather than leaking it into the card.
|
|
570
|
+
return bare.replace(/(?:^|[-_])free(?:$|[-_.])/, '')
|
|
453
571
|
}
|
|
454
572
|
|
|
455
573
|
/** `org/model` → the model's own name under the channel tag. */
|
|
@@ -491,11 +609,10 @@ export function buildEacCatalog(ids) {
|
|
|
491
609
|
// The card's small line under the name. The `id` keeps its `org/` prefix —
|
|
492
610
|
// that is what keeps this row distinct from the free lane's same-named
|
|
493
611
|
// model during the merge — but the prefix is routing detail, so the line
|
|
494
|
-
// shows the
|
|
495
|
-
//
|
|
496
|
-
//
|
|
497
|
-
//
|
|
498
|
-
// must not show. The catalog id is untouched, so routing still matches.
|
|
612
|
+
// shows the id the serving platform itself publishes: for the lane's two
|
|
613
|
+
// models that is Dahl's own `deepseek-ai/DeepSeek-V4-Flash-0731` /
|
|
614
|
+
// `zai-org/GLM-5.3-Flash`, which a reader can paste into any Dahl client.
|
|
615
|
+
// The catalog id is untouched, so routing still matches.
|
|
499
616
|
label: eacLabel(id),
|
|
500
617
|
channel: EAC_CHANNEL,
|
|
501
618
|
wire: 'chat',
|