@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.
@@ -0,0 +1,175 @@
1
+ /**
2
+ * Write manual capabilities into the model provider's own settings.
3
+ *
4
+ * A manual override fixes what the page shows, but the composer, the adapter
5
+ * and every other reader still see the provider config. When the user edits a
6
+ * model here they usually mean "this is what the model really is", so this
7
+ * module mirrors the writable fields into the `llm-pi-ai` provider profile:
8
+ * `input`, `contextWindow` and `reasoningEfforts`.
9
+ *
10
+ * Only provider fields with an exact equivalent are written:
11
+ * - `kind` has no provider field (it is a display grouping);
12
+ * - `defaultEffort` is not per model in the provider schema (the route-level
13
+ * `reasoning` default applies to a whole provider), so it stays local;
14
+ * - an off-only ladder is rejected by the provider schema ("offers no level
15
+ * beyond off"), so it also stays local.
16
+ *
17
+ * The settings service exposes live, volatile provider configs; writing goes
18
+ * through `settings.mutate(ns, ops)` with explicit path ops so the existing
19
+ * `models` array entry is edited by index instead of being replaced wholesale
20
+ * (`modelOverrides` is illegal once a `models` list exists, so appending is not
21
+ * an option either).
22
+ *
23
+ * @module lib/provider-write
24
+ */
25
+
26
+ /** Fields this module knows how to mirror into a provider profile. */
27
+ export const PROVIDER_WRITE_FIELDS = ['input', 'contextWindow', 'reasoningEfforts']
28
+
29
+ /** Plain-object guard. */
30
+ function isRecord(value) {
31
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
32
+ }
33
+
34
+ /** Best-effort message from an unknown thrown value. */
35
+ function messageOf(error) {
36
+ if (error === null || error === undefined) return undefined
37
+ if (typeof error === 'string') return error
38
+ if (typeof error.message === 'string') return error.message
39
+ return String(error)
40
+ }
41
+
42
+ /**
43
+ * Convert a manual ladder into the provider `reasoningEfforts` shape.
44
+ *
45
+ * `off` maps to `null` ("supported, send nothing"), every other level maps to
46
+ * its own id as the wire value. Returns undefined when there is nothing worth
47
+ * writing: an unset ladder, or one that only offers `off` (provider config
48
+ * rejects that as "offers no level beyond off").
49
+ * @param override - sanitized override entry.
50
+ * @returns a provider ladder, `false` for "no reasoning", or undefined.
51
+ */
52
+ export function effortsForProvider(override) {
53
+ if (override?.reasoningSupported === false) return false
54
+ if (!Array.isArray(override?.efforts) || override.efforts.length === 0) return undefined
55
+ const ladder = {}
56
+ let beyondOff = false
57
+ for (const effort of override.efforts) {
58
+ const id = typeof effort === 'string' ? effort : effort?.id
59
+ if (typeof id !== 'string' || id === '') continue
60
+ if (id === 'off') ladder.off = null
61
+ else {
62
+ ladder[id] = id
63
+ beyondOff = true
64
+ }
65
+ }
66
+ return beyondOff ? ladder : undefined
67
+ }
68
+
69
+ /**
70
+ * The provider fields an override implies.
71
+ * @param override - sanitized override entry.
72
+ * @returns `{input?, contextWindow?, reasoningEfforts?}`; empty when nothing is writable.
73
+ */
74
+ export function providerPatch(override) {
75
+ const patch = {}
76
+ if (Array.isArray(override?.input) && override.input.length > 0) patch.input = [...override.input]
77
+ if (Number.isSafeInteger(override?.contextWindow) && override.contextWindow > 0) {
78
+ patch.contextWindow = override.contextWindow
79
+ }
80
+ const efforts = effortsForProvider(override)
81
+ if (efforts !== undefined) patch.reasoningEfforts = efforts
82
+ return patch
83
+ }
84
+
85
+ /**
86
+ * Find the settings entry that owns one provider.
87
+ *
88
+ * Several settings namespaces can carry a `providers` dict (for example a test
89
+ * double or a future second adapter), so prefer the `llm-pi-ai` namespace and
90
+ * otherwise fall back to the first namespace that declares the provider.
91
+ * @param settings - the `settings` service.
92
+ * @param providerId - provider id from the override.
93
+ * @returns `{ns, models}` or undefined.
94
+ */
95
+ export function findProviderEntry(settings, providerId) {
96
+ if (typeof settings?.describe !== 'function' || typeof providerId !== 'string') return undefined
97
+ const descriptors = settings.describe()
98
+ if (!Array.isArray(descriptors)) return undefined
99
+ const matches = []
100
+ for (const descriptor of descriptors) {
101
+ const providers = descriptor?.value?.providers
102
+ if (!isRecord(providers)) continue
103
+ const profile = providers[providerId]
104
+ if (!isRecord(profile)) continue
105
+ matches.push({
106
+ ns: descriptor.ns,
107
+ models: Array.isArray(profile.models) ? profile.models : undefined,
108
+ preferred: String(descriptor.ns ?? '').includes('pi-ai') ? 1 : 0,
109
+ })
110
+ }
111
+ if (matches.length === 0) return undefined
112
+ matches.sort((left, right) => right.preferred - left.preferred)
113
+ const best = matches[0]
114
+ return { ns: best.ns, models: best.models }
115
+ }
116
+
117
+ /**
118
+ * Mirror the writable parts of one override into the provider settings.
119
+ *
120
+ * Never throws: the caller keeps the local override when a write cannot happen,
121
+ * so a read-only profile or a probe-only install still behaves like before.
122
+ * @param settings - the `settings` service (needs `describe` and `mutate`).
123
+ * @param override - sanitized override entry.
124
+ * @returns `{written, fields, ns?, reason?, message?}`.
125
+ */
126
+ export async function writeCapabilities(settings, override) {
127
+ const fields = Object.keys(providerPatch(override))
128
+ if (fields.length === 0) return { written: false, fields, reason: 'nothing' }
129
+ if (typeof settings?.mutate !== 'function' || typeof settings?.describe !== 'function') {
130
+ return { written: false, fields, reason: 'unsupported' }
131
+ }
132
+
133
+ let entry
134
+ try {
135
+ entry = findProviderEntry(settings, override.provider)
136
+ } catch (error) {
137
+ return { written: false, fields, reason: 'describe-failed', message: messageOf(error) }
138
+ }
139
+ if (entry === undefined) return { written: false, fields, reason: 'provider-missing' }
140
+ if (entry.models === undefined) return { written: false, fields, reason: 'models-missing' }
141
+
142
+ const index = entry.models.findIndex((model) => isRecord(model) && model.id === override.model)
143
+ if (index < 0) return { written: false, fields, reason: 'model-missing' }
144
+
145
+ const patch = providerPatch(override)
146
+ const path = ['providers', override.provider, 'models', String(index)]
147
+ const ops = fields.map((field) => ({ op: 'set', path: [...path, field], value: patch[field] }))
148
+ try {
149
+ await settings.mutate(entry.ns, ops)
150
+ } catch (error) {
151
+ return { written: false, fields, reason: 'settings-failed', message: messageOf(error) }
152
+ }
153
+ return { written: true, fields, ns: entry.ns }
154
+ }
155
+
156
+ /**
157
+ * Remove the fields that have just been mirrored into the provider config.
158
+ * @param override - sanitized override entry that was written.
159
+ * @param fields - fields reported as written.
160
+ * @returns the remaining local override (may contain only provider/model).
161
+ */
162
+ export function stripWrittenFields(override, fields) {
163
+ const remaining = { provider: override.provider, model: override.model }
164
+ if (!fields.includes('input') && override.input !== undefined) remaining.input = override.input
165
+ if (!fields.includes('contextWindow') && override.contextWindow !== undefined) {
166
+ remaining.contextWindow = override.contextWindow
167
+ }
168
+ if (!fields.includes('reasoningEfforts')) {
169
+ if (override.efforts !== undefined) remaining.efforts = override.efforts
170
+ if (override.defaultEffort !== undefined) remaining.defaultEffort = override.defaultEffort
171
+ if (override.reasoningSupported === false) remaining.reasoningSupported = false
172
+ }
173
+ if (override.kind !== undefined) remaining.kind = override.kind
174
+ return remaining
175
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Model Visibility",
4
+ "description": "Choose which models appear in the chat composer's model list, toggle or collapse whole provider groups, and read each model's real capabilities (adapter declarations, relay model lists, built-in catalog, vendor corrections) with colour-coded image, video and embedding labels, plus a hard re-fetch of every model's modalities and reasoning efforts, manual per-model overrides for kind, context window, input modalities and effort levels (optionally written back into the model provider's settings), and an optional image-input alignment for relay routes."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "模型显示",
4
+ "description": "在设置里选择哪些模型出现在聊天输入框的模型列表中,按分组一键显示或隐藏,也可以整组折叠;每个模型标注真实能力(适配器声明、中转站模型列表、内置目录补全、厂商文档校正),生图 / 生视频等类型用不同颜色区分,支持强制重新获取全部模型的模态与思考强度,并可为任意模型手动指定类型、上下文、输入模态与思考强度档位(可同时写回模型提供商的设置);还可选让输入框按能力标注实际放行图像。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,64 @@
1
- {
2
- "name": "@tzyc/dsh-model-visibility",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@tzyc/dsh-model-visibility",
3
+ "version": "1.9.1",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "type": "module",
8
+ "description": "Choose which models appear in the chat composer's model list, with per-provider groups, per-model capability tags sourced from the adapter, the relay model list, a built-in catalog, vendor corrections and manual overrides, separate coloured labels for image/video generation models, name search and capability filters, collapsible provider groups, a hard capability re-fetch, per-model overrides that can also be written back into the model provider's own settings, and an image-input alignment (on by default) for relay routes.",
9
+ "exports": {
10
+ ".": "./index.js",
11
+ "./client": "./client.js",
12
+ "./locale/*.json": "./locale/*.json",
13
+ "./icon": "./icon.svg"
14
+ },
15
+ "files": [
16
+ "index.js",
17
+ "client.js",
18
+ "lib",
19
+ "cordis.patch.yml",
20
+ "locale/*.json",
21
+ "icon.svg",
22
+ "screenshots.json",
23
+ "docs"
24
+ ],
25
+ "scripts": {
26
+ "test": "node --test test/*.test.mjs",
27
+ "catalog": "node scripts/build-catalog.mjs"
28
+ },
29
+ "dsh": {
30
+ "bundle": {
31
+ "patch": "./cordis.patch.yml"
32
+ },
33
+ "client": {
34
+ "platform": "web",
35
+ "immediately": true,
36
+ "inject": [
37
+ "@deepseek-ai/dsh-client-ui-settings",
38
+ "@deepseek-ai/dsh-client-ui-model-selection",
39
+ "@deepseek-ai/dsh-client-ui-conversation",
40
+ "@deepseek-ai/dsh-client-locale"
41
+ ]
42
+ }
43
+ },
44
+ "dependencies": {
45
+ "@deepseek-ai/schemastery": "^3.18.5"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/TZYCeng/dsh-model-visibility.git"
50
+ },
51
+ "homepage": "https://github.com/TZYCeng/dsh-model-visibility#readme",
52
+ "bugs": {
53
+ "url": "https://github.com/TZYCeng/dsh-model-visibility/issues"
54
+ },
55
+ "keywords": [
56
+ "dsh-plugin",
57
+ "deepseek-harness",
58
+ "dsh",
59
+ "model-visibility",
60
+ "model-selector"
61
+ ],
62
+ "author": "TZYCeng",
63
+ "license": "MIT"
64
+ }
@@ -0,0 +1,4 @@
1
+ [
2
+ "docs/settings-page.png",
3
+ "docs/capability-tags.png"
4
+ ]