@tzyc/dsh-model-visibility 0.0.0-stage → 1.9.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/LICENSE +21 -0
- package/README.md +367 -2
- package/client.js +2258 -0
- package/cordis.patch.yml +7 -0
- package/docs/capability-tags.png +0 -0
- package/docs/icon-180.png +0 -0
- package/docs/settings-page.png +0 -0
- package/icon.svg +9 -0
- package/index.js +476 -0
- package/lib/capability-catalog.js +137 -0
- package/lib/capability-corrections.js +65 -0
- package/lib/image-input-gate.js +113 -0
- package/lib/model-catalog.js +670 -0
- package/lib/model-info.js +786 -0
- package/lib/model-overrides.js +262 -0
- package/lib/provider-probe.js +596 -0
- package/lib/provider-write.js +175 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +64 -6
- package/screenshots.json +4 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Curated corrections for facts the bundled catalog gets wrong.
|
|
3
|
+
*
|
|
4
|
+
* The pi-ai / models.dev snapshot is a snapshot: vendor model names collide
|
|
5
|
+
* (`glm-5.3` is a text-only flagship while `glm-5.3-flash` is the multimodal
|
|
6
|
+
* one), and an entry can be attributed to the wrong provider family. A wrong
|
|
7
|
+
* row that says "multimodal" is worse than an unknown one: the composer would
|
|
8
|
+
* offer image input for a model that rejects it, and the request would fail
|
|
9
|
+
* upstream.
|
|
10
|
+
*
|
|
11
|
+
* Corrections are deliberately tiny, keyed by normalized model id and labelled
|
|
12
|
+
* `official` in the UI, with the documentation URL kept for the tooltip. They
|
|
13
|
+
* only replace `catalog` / `inferred` / `unknown` evidence — an explicit
|
|
14
|
+
* provider declaration or a relay listing still wins, because that is the
|
|
15
|
+
* user's own routing configuration.
|
|
16
|
+
*
|
|
17
|
+
* Add an entry only when a primary source (the vendor's model documentation)
|
|
18
|
+
* contradicts the snapshot. Never guess from a model name here.
|
|
19
|
+
*
|
|
20
|
+
* @module lib/capability-corrections
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { modelIdVariants } from './capability-catalog.js'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Corrected facts keyed by the normalized catalog id.
|
|
27
|
+
*
|
|
28
|
+
* Shape:
|
|
29
|
+
* `input` — exact input modalities the vendor documents.
|
|
30
|
+
* `code` — dictionary key under `correction.*` for the localized note.
|
|
31
|
+
* `note` — English fallback when the dictionary has no key (diagnostics).
|
|
32
|
+
* `source` — canonical documentation URL, shown in the tooltip.
|
|
33
|
+
*/
|
|
34
|
+
export const CORRECTIONS = Object.freeze({
|
|
35
|
+
'glm-5.3': Object.freeze({
|
|
36
|
+
input: Object.freeze(['text']),
|
|
37
|
+
code: 'glm-5.3-text-only',
|
|
38
|
+
note: 'GLM-5.3 accepts text only; the multimodal variant is GLM-5.3-Flash / FlashX.',
|
|
39
|
+
source: 'https://docs.bigmodel.cn/cn/guide/models/text/glm-5.3',
|
|
40
|
+
}),
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
/** Normalized-id index over {@link CORRECTIONS}. */
|
|
44
|
+
const INDEX = (() => {
|
|
45
|
+
const index = new Map()
|
|
46
|
+
for (const [id, correction] of Object.entries(CORRECTIONS)) {
|
|
47
|
+
for (const variant of modelIdVariants(id)) {
|
|
48
|
+
if (!index.has(variant)) index.set(variant, { id, ...correction })
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return index
|
|
52
|
+
})()
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find the curated correction for one model id.
|
|
56
|
+
* @param value - model id as any provider spells it.
|
|
57
|
+
* @returns `{ id, input, code, note, source }` or undefined.
|
|
58
|
+
*/
|
|
59
|
+
export function lookupCorrection(value) {
|
|
60
|
+
for (const variant of modelIdVariants(value)) {
|
|
61
|
+
const found = INDEX.get(variant)
|
|
62
|
+
if (found !== undefined) return found
|
|
63
|
+
}
|
|
64
|
+
return undefined
|
|
65
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-side image-input gate for relay adapters.
|
|
3
|
+
*
|
|
4
|
+
* The settings page can prove that a relay model accepts images (relay
|
|
5
|
+
* listing, built-in catalog, vendor correction), but the `llm-pi-ai` adapter
|
|
6
|
+
* builds an internal model object from the route configuration. For a relay
|
|
7
|
+
* that leaves `input` empty, that object falls back to the provider's
|
|
8
|
+
* `defaultInput` (`["text"]`), so both the session admission check and the
|
|
9
|
+
* adapter's own serializer reject an image that the relay really accepts.
|
|
10
|
+
*
|
|
11
|
+
* This module patches only the adapter method that materializes that model
|
|
12
|
+
* object (`modelOf`), which is used by `resolveModel`, `stream` and
|
|
13
|
+
* `imageRequestTarget` alike. The patch is read-only: it never writes settings,
|
|
14
|
+
* never changes a route, and returns the original object untouched when the
|
|
15
|
+
* capability merge says the model is text-only. It is scoped to the pi-ai
|
|
16
|
+
* adapter shape, so built-in adapters and the DeepSeek Messages adapter keep
|
|
17
|
+
* their own declarations.
|
|
18
|
+
*
|
|
19
|
+
* @module lib/image-input-gate
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* True when an adapter is the pi-ai route adapter whose models come from
|
|
24
|
+
* `modelOf(snapshot, provider, model)`.
|
|
25
|
+
* @param adapter - registered adapter instance.
|
|
26
|
+
* @returns whether the adapter has the patched surface.
|
|
27
|
+
*/
|
|
28
|
+
function isPiAiAdapter(adapter) {
|
|
29
|
+
return (
|
|
30
|
+
adapter !== null &&
|
|
31
|
+
typeof adapter === 'object' &&
|
|
32
|
+
typeof adapter.modelOf === 'function' &&
|
|
33
|
+
typeof adapter.profileOf === 'function' &&
|
|
34
|
+
typeof adapter.current === 'function'
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Patch every registered pi-ai adapter so image-capable relay models advertise
|
|
40
|
+
* `image` to the admission check and to the request serializer.
|
|
41
|
+
*
|
|
42
|
+
* @param llm - the Host `ctx.llm` service (a traceable service proxy is fine).
|
|
43
|
+
* @param options - `{ allow(provider, model), warn(error) }`.
|
|
44
|
+
* @returns `{ refresh, dispose }`; call `refresh` after adapters change.
|
|
45
|
+
*/
|
|
46
|
+
export function installImageInputGate(llm, options = {}) {
|
|
47
|
+
const allow = typeof options.allow === 'function' ? options.allow : () => false
|
|
48
|
+
const warn = typeof options.warn === 'function' ? options.warn : () => {}
|
|
49
|
+
/** adapter -> { original, wrapper } */
|
|
50
|
+
const patched = new Map()
|
|
51
|
+
|
|
52
|
+
const shouldAddImage = (provider, model) => {
|
|
53
|
+
try {
|
|
54
|
+
return allow(provider, model) === true
|
|
55
|
+
} catch (error) {
|
|
56
|
+
warn(error)
|
|
57
|
+
return false
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const patchAdapter = (adapter) => {
|
|
62
|
+
if (!isPiAiAdapter(adapter) || patched.has(adapter)) return
|
|
63
|
+
const original = adapter.modelOf
|
|
64
|
+
const wrapper = function modelOf(snapshot, provider, model) {
|
|
65
|
+
const resolved = original.call(this, snapshot, provider, model)
|
|
66
|
+
if (resolved === null || typeof resolved !== 'object') return resolved
|
|
67
|
+
if (Array.isArray(resolved.input) && resolved.input.includes('image')) return resolved
|
|
68
|
+
if (!shouldAddImage(provider, model)) return resolved
|
|
69
|
+
const input = Array.isArray(resolved.input) ? resolved.input : ['text']
|
|
70
|
+
return { ...resolved, input: [...input, 'image'] }
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
Object.defineProperty(adapter, 'modelOf', {
|
|
74
|
+
value: wrapper,
|
|
75
|
+
configurable: true,
|
|
76
|
+
writable: true,
|
|
77
|
+
})
|
|
78
|
+
patched.set(adapter, { original, wrapper })
|
|
79
|
+
} catch (error) {
|
|
80
|
+
warn(error)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Re-scan the registry; registrations are replaced on route/config changes. */
|
|
85
|
+
const refresh = () => {
|
|
86
|
+
let adapters
|
|
87
|
+
try {
|
|
88
|
+
adapters = llm?.adapters
|
|
89
|
+
} catch (_error) {
|
|
90
|
+
return
|
|
91
|
+
}
|
|
92
|
+
if (!(adapters instanceof Map)) return
|
|
93
|
+
for (const registration of adapters.values()) patchAdapter(registration?.adapter)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
refresh()
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
refresh,
|
|
100
|
+
dispose() {
|
|
101
|
+
for (const [adapter, record] of patched) {
|
|
102
|
+
try {
|
|
103
|
+
if (Object.getOwnPropertyDescriptor(adapter, 'modelOf')?.value === record.wrapper) {
|
|
104
|
+
delete adapter.modelOf
|
|
105
|
+
}
|
|
106
|
+
} catch (_error) {
|
|
107
|
+
// A disposed adapter simply keeps the wrapper; it no longer serves.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
patched.clear()
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
}
|