@dsh-plus/llm-pi 0.1.13 → 0.1.15
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/lib/client.js +48 -50
- package/lib/index.d.ts +3 -2
- package/lib/index.js +633 -222
- package/package.json +28 -24
- package/src/auth-inline.ts +177 -0
- package/src/catalog/official.ts +0 -2
- package/src/client/api.ts +4 -3
- package/src/client/card.tsx +5 -13
- package/src/client/client.ts +16 -18
- package/src/client/constants.ts +11 -16
- package/src/compat.ts +136 -62
- package/src/config-api.ts +3 -3
- package/src/config.ts +25 -4
- package/src/deepseek-routes.ts +25 -0
- package/src/ns.ts +1 -1
- package/src/profiles-deepseek.ts +12 -6
- package/src/profiles.ts +439 -171
- package/src/resolve-dsh.ts +170 -13
- package/src/service.ts +26 -0
package/src/compat.ts
CHANGED
|
@@ -1,10 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 逐协议 compat
|
|
2
|
+
* 逐协议 compat 门控表与校验(0.1.2-alpha.1 适配版)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* 适配决策:官方 dsh-llm-pi-ai 0.1.2-alpha.1 的 COMPAT_GATES(catalog.ts:292)
|
|
5
|
+
* 是 compat 可配性的唯一事实源——按协议分型(offer/withhold),withhold 字段
|
|
6
|
+
* (catalog 已为对应厂商设置,如 openRouterRouting/zaiToolStream/
|
|
7
|
+
* sendSessionAffinityHeaders/supportsToolSearch 等)写时拒绝并提示以目录
|
|
8
|
+
* provider 名为 route。官方门控表只在 src/catalog.ts 源码子路径内(npm 发布
|
|
9
|
+
* 形态不携带 src/、包根也不导出该符号),插件在 dsh 树(npm 布局)与 vendored
|
|
10
|
+
* 兜底两条路径都无法静态引用,故本文件按官方 catalog.ts 逐字段镜像门控表
|
|
11
|
+
* (字段全集与分型随 pi-ai 升级同步维护,官方以 Record<keyof Compat> 编译期
|
|
12
|
+
* 约束漂移,插件以本表 + 注释人工同步)。
|
|
13
|
+
*
|
|
14
|
+
* 与旧版字段表的差异(0.1.2-alpha.1 官方门控):
|
|
15
|
+
* - completions 新增 offer:supportsFinishReason/chatTemplateArgs/
|
|
16
|
+
* supportsThinkingTokenBudget;thinkingFormat 新增 baseten;
|
|
17
|
+
* - 原表内 openRouterRouting/vercelGatewayRouting/zaiToolStream/
|
|
18
|
+
* sendSessionAffinityHeaders/deferredToolsMode/sessionAffinityFormat/
|
|
19
|
+
* supportsOpenAIGrammarTools(completions/responses)与 supportsToolSearch/
|
|
20
|
+
* supportsExplicitPromptCacheMode(responses)、supportsToolReferences
|
|
21
|
+
* (anthropic)改为 withhold → 写时拒绝;
|
|
22
|
+
* - responses 不再 offer sessionAffinityFormat(withhold)。
|
|
8
23
|
*
|
|
9
24
|
* pi-ai 侧消费语义:getCompat 逐字段 `??` 覆盖 detectCompat 的
|
|
10
25
|
* baseURL/名称猜测;undefined 视为未设置(无法显式清空检测值)。
|
|
@@ -13,23 +28,103 @@
|
|
|
13
28
|
import type { ProtocolId } from './config.ts'
|
|
14
29
|
|
|
15
30
|
type CompatValue = 'boolean' | 'object' | readonly string[]
|
|
31
|
+
type CompatDisposition = 'offer' | 'withhold'
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 官方 COMPLETIONS_COMPAT_GATE(llm-pi-ai/src/catalog.ts)逐字段镜像。
|
|
35
|
+
* offer 17 字段 / withhold 7 字段。
|
|
36
|
+
*/
|
|
37
|
+
const COMPLETIONS_COMPAT_GATE: Readonly<Record<string, CompatDisposition>> = {
|
|
38
|
+
supportsStore: 'offer',
|
|
39
|
+
supportsDeveloperRole: 'offer',
|
|
40
|
+
supportsReasoningEffort: 'offer',
|
|
41
|
+
supportsUsageInStreaming: 'offer',
|
|
42
|
+
supportsFinishReason: 'offer',
|
|
43
|
+
maxTokensField: 'offer',
|
|
44
|
+
requiresToolResultName: 'offer',
|
|
45
|
+
requiresAssistantAfterToolResult: 'offer',
|
|
46
|
+
requiresThinkingAsText: 'offer',
|
|
47
|
+
requiresReasoningContentOnAssistantMessages: 'offer',
|
|
48
|
+
thinkingFormat: 'offer',
|
|
49
|
+
chatTemplateKwargs: 'offer',
|
|
50
|
+
chatTemplateArgs: 'offer',
|
|
51
|
+
supportsThinkingTokenBudget: 'offer',
|
|
52
|
+
supportsStrictMode: 'offer',
|
|
53
|
+
cacheControlFormat: 'offer',
|
|
54
|
+
supportsLongCacheRetention: 'offer',
|
|
55
|
+
openRouterRouting: 'withhold',
|
|
56
|
+
vercelGatewayRouting: 'withhold',
|
|
57
|
+
zaiToolStream: 'withhold',
|
|
58
|
+
supportsOpenAIGrammarTools: 'withhold',
|
|
59
|
+
sendSessionAffinityHeaders: 'withhold',
|
|
60
|
+
deferredToolsMode: 'withhold',
|
|
61
|
+
sessionAffinityFormat: 'withhold',
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 官方 RESPONSES_COMPAT_GATE 逐字段镜像(三协议共享同一 OpenAIResponsesCompat;
|
|
66
|
+
* 本插件只服务 openai-responses)。offer 3 字段 / withhold 5 字段。
|
|
67
|
+
*/
|
|
68
|
+
const RESPONSES_COMPAT_GATE: Readonly<Record<string, CompatDisposition>> = {
|
|
69
|
+
supportsDeveloperRole: 'offer',
|
|
70
|
+
supportsStrictMode: 'offer',
|
|
71
|
+
supportsLongCacheRetention: 'offer',
|
|
72
|
+
sessionAffinityFormat: 'withhold',
|
|
73
|
+
supportsOpenAIGrammarTools: 'withhold',
|
|
74
|
+
supportsAdditionalTools: 'withhold',
|
|
75
|
+
supportsToolSearch: 'withhold',
|
|
76
|
+
supportsExplicitPromptCacheMode: 'withhold',
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 官方 ANTHROPIC_COMPAT_GATE 逐字段镜像。offer 7 字段 / withhold 2 字段。
|
|
81
|
+
*/
|
|
82
|
+
const ANTHROPIC_COMPAT_GATE: Readonly<Record<string, CompatDisposition>> = {
|
|
83
|
+
supportsEagerToolInputStreaming: 'offer',
|
|
84
|
+
supportsLongCacheRetention: 'offer',
|
|
85
|
+
supportsCacheControlOnTools: 'offer',
|
|
86
|
+
supportsTemperature: 'offer',
|
|
87
|
+
forceAdaptiveThinking: 'offer',
|
|
88
|
+
allowEmptySignature: 'offer',
|
|
89
|
+
supportsStrictTools: 'offer',
|
|
90
|
+
sendSessionAffinityHeaders: 'withhold',
|
|
91
|
+
supportsToolReferences: 'withhold',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const GATES_BY_PROTOCOL: Record<ProtocolId, Readonly<Record<string, CompatDisposition>>> = {
|
|
95
|
+
'openai-completions': COMPLETIONS_COMPAT_GATE,
|
|
96
|
+
'openai-responses': RESPONSES_COMPAT_GATE,
|
|
97
|
+
'anthropic-messages': ANTHROPIC_COMPAT_GATE,
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 某协议某字段的可配性('offer'/'withhold';未列出 = 无此字段)。 */
|
|
101
|
+
export function compatDispositionOf(api: ProtocolId, field: string): CompatDisposition | undefined {
|
|
102
|
+
return GATES_BY_PROTOCOL[api][field]
|
|
103
|
+
}
|
|
16
104
|
|
|
17
|
-
/**
|
|
18
|
-
|
|
105
|
+
/**
|
|
106
|
+
* 字段取值约束(对齐官方 config.ts compatProfile schema):
|
|
107
|
+
* boolean 字段 → 'boolean';maxTokensField/thinkingFormat/cacheControlFormat
|
|
108
|
+
* → 枚举;chatTemplateKwargs/chatTemplateArgs → 对象。
|
|
109
|
+
*/
|
|
110
|
+
const VALUE_SPECS: Readonly<Record<string, CompatValue>> = {
|
|
19
111
|
supportsStore: 'boolean',
|
|
20
112
|
supportsDeveloperRole: 'boolean',
|
|
21
113
|
supportsReasoningEffort: 'boolean',
|
|
22
114
|
supportsUsageInStreaming: 'boolean',
|
|
115
|
+
supportsFinishReason: 'boolean',
|
|
23
116
|
maxTokensField: ['max_completion_tokens', 'max_tokens'],
|
|
24
117
|
requiresToolResultName: 'boolean',
|
|
25
118
|
requiresAssistantAfterToolResult: 'boolean',
|
|
26
119
|
requiresThinkingAsText: 'boolean',
|
|
27
120
|
requiresReasoningContentOnAssistantMessages: 'boolean',
|
|
28
121
|
thinkingFormat: [
|
|
122
|
+
// 官方 SUPPORTED_THINKING_FORMATS(含 baseten,随 chatTemplateArgs 使用)
|
|
29
123
|
'openai',
|
|
30
|
-
'openrouter',
|
|
31
124
|
'deepseek',
|
|
125
|
+
'openrouter',
|
|
32
126
|
'together',
|
|
127
|
+
'baseten',
|
|
33
128
|
'zai',
|
|
34
129
|
'qwen',
|
|
35
130
|
'chat-template',
|
|
@@ -38,65 +133,32 @@ const COMPLETIONS_FIELDS: Record<string, CompatValue> = {
|
|
|
38
133
|
'ant-ling',
|
|
39
134
|
],
|
|
40
135
|
chatTemplateKwargs: 'object',
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
zaiToolStream: 'boolean',
|
|
44
|
-
supportsOpenAIGrammarTools: 'boolean',
|
|
136
|
+
chatTemplateArgs: 'object',
|
|
137
|
+
supportsThinkingTokenBudget: 'boolean',
|
|
45
138
|
supportsStrictMode: 'boolean',
|
|
46
139
|
cacheControlFormat: ['anthropic'],
|
|
47
|
-
sendSessionAffinityHeaders: 'boolean',
|
|
48
|
-
deferredToolsMode: ['kimi'],
|
|
49
|
-
sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
|
|
50
140
|
supportsLongCacheRetention: 'boolean',
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** openai-responses 的 7 个字段(pi-ai OpenAIResponsesCompat)。 */
|
|
54
|
-
const RESPONSES_FIELDS: Record<string, CompatValue> = {
|
|
55
|
-
supportsDeveloperRole: 'boolean',
|
|
56
|
-
sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
|
|
57
|
-
supportsLongCacheRetention: 'boolean',
|
|
58
|
-
supportsStrictMode: 'boolean',
|
|
59
|
-
supportsOpenAIGrammarTools: 'boolean',
|
|
60
|
-
supportsToolSearch: 'boolean',
|
|
61
|
-
supportsExplicitPromptCacheMode: 'boolean',
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** anthropic-messages 的 9 个字段(pi-ai AnthropicMessagesCompat)。 */
|
|
65
|
-
const ANTHROPIC_FIELDS: Record<string, CompatValue> = {
|
|
66
141
|
supportsEagerToolInputStreaming: 'boolean',
|
|
67
|
-
supportsLongCacheRetention: 'boolean',
|
|
68
|
-
sendSessionAffinityHeaders: 'boolean',
|
|
69
142
|
supportsCacheControlOnTools: 'boolean',
|
|
70
143
|
supportsTemperature: 'boolean',
|
|
71
144
|
forceAdaptiveThinking: 'boolean',
|
|
72
145
|
allowEmptySignature: 'boolean',
|
|
73
146
|
supportsStrictTools: 'boolean',
|
|
74
|
-
supportsToolReferences: 'boolean',
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const FIELDS_BY_PROTOCOL: Record<ProtocolId, Record<string, CompatValue>> = {
|
|
78
|
-
'openai-completions': COMPLETIONS_FIELDS,
|
|
79
|
-
'openai-responses': RESPONSES_FIELDS,
|
|
80
|
-
'anthropic-messages': ANTHROPIC_FIELDS,
|
|
81
147
|
}
|
|
82
148
|
|
|
83
|
-
/**
|
|
149
|
+
/** 某协议全部可配置(offer)的 compat 键(UI 渲染字段组与校验共用)。 */
|
|
84
150
|
export function compatFieldsOf(api: ProtocolId): readonly string[] {
|
|
85
|
-
return Object.
|
|
151
|
+
return Object.entries(GATES_BY_PROTOCOL[api]).flatMap(([field, disposition]) =>
|
|
152
|
+
disposition === 'offer' ? [field] : [],
|
|
153
|
+
)
|
|
86
154
|
}
|
|
87
155
|
|
|
88
|
-
/**
|
|
156
|
+
/** 某协议某 offer 字段的取值约束(UI 渲染开关/下拉用)。 */
|
|
89
157
|
export function compatFieldSpec(api: ProtocolId, field: string): CompatValue | undefined {
|
|
90
|
-
return
|
|
158
|
+
return GATES_BY_PROTOCOL[api][field] === 'offer' ? VALUE_SPECS[field] : undefined
|
|
91
159
|
}
|
|
92
160
|
|
|
93
|
-
function checkValue(
|
|
94
|
-
_api: ProtocolId,
|
|
95
|
-
field: string,
|
|
96
|
-
spec: CompatValue,
|
|
97
|
-
value: unknown,
|
|
98
|
-
where: string,
|
|
99
|
-
): void {
|
|
161
|
+
function checkValue(field: string, spec: CompatValue, value: unknown, where: string): void {
|
|
100
162
|
if (spec === 'boolean') {
|
|
101
163
|
if (typeof value !== 'boolean') throw new Error(`${where}: compat.${field} 必须是布尔值`)
|
|
102
164
|
return
|
|
@@ -115,8 +177,11 @@ function checkValue(
|
|
|
115
177
|
}
|
|
116
178
|
|
|
117
179
|
/**
|
|
118
|
-
* 校验一份 compat
|
|
119
|
-
*
|
|
180
|
+
* 校验一份 compat 字典对指定协议合法(对齐官方门控语义 + schema 值约束):
|
|
181
|
+
* - 未知键/withhold 字段拒绝(官方 0.1.2-alpha.1 写时拒绝,替代旧版静默丢弃);
|
|
182
|
+
* - 值类型/枚举按官方 schema 校验;
|
|
183
|
+
* - 无值键(null/undefined)拒绝(官方 assertOfferedCompatFields 同款:
|
|
184
|
+
* "写了但没生效" 的表面状态不允许)。
|
|
120
185
|
*/
|
|
121
186
|
export function validateCompat(
|
|
122
187
|
api: string,
|
|
@@ -124,26 +189,35 @@ export function validateCompat(
|
|
|
124
189
|
where: string,
|
|
125
190
|
): void {
|
|
126
191
|
if (compat === undefined) return
|
|
127
|
-
const
|
|
128
|
-
if (
|
|
192
|
+
const gate = GATES_BY_PROTOCOL[api as ProtocolId]
|
|
193
|
+
if (gate === undefined) {
|
|
129
194
|
throw new Error(
|
|
130
|
-
`${where}: 协议 ${JSON.stringify(api)} 无 compat 字段表(支持:${Object.keys(
|
|
195
|
+
`${where}: 协议 ${JSON.stringify(api)} 无 compat 字段表(支持:${Object.keys(GATES_BY_PROTOCOL).join(', ')})`,
|
|
131
196
|
)
|
|
132
197
|
}
|
|
198
|
+
const offered = compatFieldsOf(api as ProtocolId)
|
|
133
199
|
for (const [key, value] of Object.entries(compat)) {
|
|
134
|
-
const
|
|
135
|
-
if (
|
|
200
|
+
const disposition = gate[key]
|
|
201
|
+
if (disposition !== 'offer') {
|
|
202
|
+
if (disposition === 'withhold') {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`${where}: compat.${key} 官方按协议 withhold(内置目录已为对应厂商设置该开关);` +
|
|
205
|
+
'请以目录 provider 名作为 route 名(继承目录值),或移除该字段',
|
|
206
|
+
)
|
|
207
|
+
}
|
|
136
208
|
throw new Error(
|
|
137
|
-
`${where}: compat.${key} 不是 ${api}
|
|
209
|
+
`${where}: compat.${key} 不是 ${api} 协议的合法字段(可配置字段:${offered.join(', ')})`,
|
|
138
210
|
)
|
|
139
211
|
}
|
|
140
|
-
if (value === undefined)
|
|
141
|
-
|
|
212
|
+
if (value === undefined || value === null) {
|
|
213
|
+
throw new Error(`${where}: compat.${key} 未设置值;给出值或移除该键(留空不会生效)`)
|
|
214
|
+
}
|
|
215
|
+
checkValue(key, VALUE_SPECS[key] as CompatValue, value, where)
|
|
142
216
|
}
|
|
143
217
|
}
|
|
144
218
|
|
|
145
219
|
/**
|
|
146
|
-
* 逐字段合并 compat 层(后者覆盖前者),丢弃 undefined 值。
|
|
220
|
+
* 逐字段合并 compat 层(后者覆盖前者),丢弃 undefined/null 值。
|
|
147
221
|
* 层序:继承源(仅同协议)→ route 级 → 模型级。
|
|
148
222
|
*/
|
|
149
223
|
export function mergeCompat(
|
|
@@ -153,7 +227,7 @@ export function mergeCompat(
|
|
|
153
227
|
for (const layer of layers) {
|
|
154
228
|
if (layer === undefined) continue
|
|
155
229
|
for (const [key, value] of Object.entries(layer)) {
|
|
156
|
-
if (value !== undefined) merged[key] = value
|
|
230
|
+
if (value !== undefined && value !== null) merged[key] = value
|
|
157
231
|
}
|
|
158
232
|
}
|
|
159
233
|
return Object.keys(merged).length > 0 ? merged : undefined
|
package/src/config-api.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* 自定义端点 HTTP 通道:配置读写已迁移到官方 settings
|
|
3
|
-
*
|
|
4
|
-
* kitSource / models-dev 运行期诊断,供卡片状态行展示)。
|
|
2
|
+
* 自定义端点 HTTP 通道:配置读写已迁移到官方 remote.settings 直连
|
|
3
|
+
* (0.1.2-alpha.1 起第三方命名空间全量开放),这里只保留官方传输没有的
|
|
4
|
+
* 「模型目录」端点(含 kitSource / models-dev 运行期诊断,供卡片状态行展示)。
|
|
5
5
|
* 仅监听 dsh web 同源(webserver 默认 127.0.0.1),与 GUI 其余面同等暴露面。
|
|
6
6
|
* @module llm-pi/config-api
|
|
7
7
|
*/
|
package/src/config.ts
CHANGED
|
@@ -37,14 +37,19 @@ export type AdapterKind = (typeof ADAPTER_KINDS)[number]
|
|
|
37
37
|
/** deepseek 路由的思考/推理档位(线协议枚举,对齐官方 llm-deepseek)。 */
|
|
38
38
|
export const DEEPSEEK_THINKING = ['enabled', 'disabled'] as const
|
|
39
39
|
export const DEEPSEEK_REASONING_EFFORTS = ['off', 'low', 'high', 'max'] as const
|
|
40
|
+
/** 旧版 imageDetail 枚举(0.1.2-alpha.1 已移除该字段;仅用于 schema 透传陷阱的取值集)。 */
|
|
40
41
|
export const DEEPSEEK_IMAGE_DETAILS = ['auto', 'low'] as const
|
|
41
42
|
|
|
42
43
|
export const DEFAULT_CONTEXT_WINDOW = 262144
|
|
43
44
|
export const DEFAULT_MAX_TOKENS = 32768
|
|
44
45
|
export const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 300000
|
|
45
|
-
/** 单请求 base64
|
|
46
|
+
/** 单请求 base64 图片载荷上限(官方 ResolvedPiAiProviderProfile 必需字段;
|
|
46
47
|
* 缺省取官方默认 20MiB,旧配置无需改动即自动生效)。 */
|
|
47
48
|
export const DEFAULT_MAX_REQUEST_IMAGE_BYTES = 20 * 1024 * 1024
|
|
49
|
+
/** 单请求每个确定性内联版本的像素总预算(官方 DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET 同值)。 */
|
|
50
|
+
export const DEFAULT_REQUEST_IMAGE_PIXEL_BUDGET = 2048 * 2048
|
|
51
|
+
/** 内联版本编码字节目标(官方 DEFAULT_REQUEST_IMAGE_MAX_BYTES 同值)。 */
|
|
52
|
+
export const DEFAULT_REQUEST_IMAGE_MAX_BYTES = 1024 * 1024
|
|
48
53
|
/** dsh-timeout 的定时器上限(与官方 MAX_TIMER_DELAY_MS 对齐)。 */
|
|
49
54
|
export const MAX_TIMER_DELAY_MS = 2 ** 31 - 1
|
|
50
55
|
|
|
@@ -68,7 +73,6 @@ export interface ModelEntryConfig {
|
|
|
68
73
|
/** 以下仅 adapter: deepseek 的 route 有效(官方 llm-deepseek 目录字段)。 */
|
|
69
74
|
imagePixelBudget?: number
|
|
70
75
|
imageMaxBytes?: number
|
|
71
|
-
imageDetail?: 'auto' | 'low'
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
/** 单个 provider route 配置(providers 字典的值)。 */
|
|
@@ -97,6 +101,8 @@ export interface ProviderProfileConfig {
|
|
|
97
101
|
websocketConnectTimeoutMs?: number
|
|
98
102
|
streamIdleTimeoutMs?: number
|
|
99
103
|
maxRequestImageBytes?: number
|
|
104
|
+
requestImagePixelBudget?: number
|
|
105
|
+
requestImageMaxBytes?: number
|
|
100
106
|
retryPolicy?: unknown
|
|
101
107
|
models?: ModelEntryConfig[]
|
|
102
108
|
/** 以下仅 adapter: deepseek 的 route 有效(透传官方 llm-deepseek 同名策略)。 */
|
|
@@ -167,9 +173,12 @@ const modelEntry = z.object({
|
|
|
167
173
|
.step(1)
|
|
168
174
|
.min(1)
|
|
169
175
|
.description('(仅 adapter: deepseek)单张请求图片的编码字节上限;缺省继承官方目录'),
|
|
176
|
+
// 0.1.2-alpha.1 移除字段的 schema 透传陷阱:官方已删除 imageDetail(改用
|
|
177
|
+
// imagePixelBudget/imageMaxBytes),此处保留取值 union 让旧配置通过 schema、
|
|
178
|
+
// 在写时校验(assertServiceable)以中文迁移提示明确拒绝(见 profiles-deepseek.ts)。
|
|
170
179
|
imageDetail: z
|
|
171
180
|
.union(DEEPSEEK_IMAGE_DETAILS)
|
|
172
|
-
.description('
|
|
181
|
+
.description('(已移除)图片细节档位;0.1.2-alpha.1 起请改用 imagePixelBudget/imageMaxBytes'),
|
|
173
182
|
})
|
|
174
183
|
|
|
175
184
|
const providerProfile = z.object({
|
|
@@ -215,7 +224,19 @@ const providerProfile = z.object({
|
|
|
215
224
|
.description('单支流式读取的最大空闲间隔毫秒'),
|
|
216
225
|
maxRequestImageBytes: z
|
|
217
226
|
.natural()
|
|
218
|
-
.description(
|
|
227
|
+
.description(
|
|
228
|
+
'单请求 base64 图片载荷上限字节;缺省 20MiB(0.1.2-alpha.1 起官方必需字段,旧配置免改)',
|
|
229
|
+
),
|
|
230
|
+
requestImagePixelBudget: z
|
|
231
|
+
.number()
|
|
232
|
+
.step(1)
|
|
233
|
+
.min(1)
|
|
234
|
+
.description('单请求每个确定性内联图片版本的像素总预算;缺省 2048x2048(官方同值)'),
|
|
235
|
+
requestImageMaxBytes: z
|
|
236
|
+
.number()
|
|
237
|
+
.step(1)
|
|
238
|
+
.min(1)
|
|
239
|
+
.description('内联图片版本编码字节目标;缺省 1MiB(官方同值)'),
|
|
219
240
|
retryPolicy: z.any().description('provider 重试策略(dsh-llm RetryPolicy 形状,构建期校验)'),
|
|
220
241
|
thinking: z
|
|
221
242
|
.union(DEEPSEEK_THINKING)
|
package/src/deepseek-routes.ts
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
* 热更新:适配器 options thunk 每次操作重读当前物化产物(连接事实变化下一
|
|
11
11
|
* 请求生效);retryPolicy 是注册期捕获事实,变化时 handle.replace 原地重注册
|
|
12
12
|
* (官方同款模式);route 移除即 dispose 释放路由名。
|
|
13
|
+
*
|
|
14
|
+
* 0.1.2-alpha.1:DeepSeekAdapterOptions 新增必需 prepareExtensions(官方接线
|
|
15
|
+
* ctx.get('deepseekLlmApiExtensions'),缺省空实现)与可选 resolveImageAccess。
|
|
13
16
|
* @module llm-pi/deepseek-routes
|
|
14
17
|
*/
|
|
15
18
|
import type { Context } from '@deepseek-ai/cordis'
|
|
@@ -130,6 +133,28 @@ export class DeepseekRouteRegistrar {
|
|
|
130
133
|
resolveApiKey: this.resolveApiKey as never,
|
|
131
134
|
resolveUserId: () => this.resolveUserId() as never,
|
|
132
135
|
resolveAttachments: () => ctx.get('attachments') as never,
|
|
136
|
+
// 附件宿主路径 → 模型工具执行世界只读路径桥(官方接线同款,见
|
|
137
|
+
// llm-deepseek/src/index.ts:447-452)。
|
|
138
|
+
resolveImageAccess: (attachments, ref) =>
|
|
139
|
+
kit.resolveImageAttachmentAccess(
|
|
140
|
+
attachments,
|
|
141
|
+
(hostPath) =>
|
|
142
|
+
(
|
|
143
|
+
ctx.get('fs') as
|
|
144
|
+
| { processPathFromHostPath(hostPath: string): string | undefined }
|
|
145
|
+
| undefined
|
|
146
|
+
)?.processPathFromHostPath(hostPath),
|
|
147
|
+
ref,
|
|
148
|
+
) as never,
|
|
149
|
+
// 0.1.2-alpha.1 必需:官方扩展注册表(deepseekLlmApiExtensions)缺席时
|
|
150
|
+
// 给空实现(官方接线范式,llm-deepseek/src/index.ts:404-470)。
|
|
151
|
+
prepareExtensions: (request) => {
|
|
152
|
+
const extensions = ctx.get('deepseekLlmApiExtensions') as
|
|
153
|
+
| { prepare(request: unknown): Promise<unknown> }
|
|
154
|
+
| undefined
|
|
155
|
+
return (extensions?.prepare(request) ??
|
|
156
|
+
Promise.resolve({ fields: {}, accept: () => Promise.resolve() })) as never
|
|
157
|
+
},
|
|
133
158
|
})
|
|
134
159
|
// 官方 providerInfo 硬编码 name: "DeepSeek"——覆盖为按路由动态读取的
|
|
135
160
|
// displayName,否则自定义 deepseek 路由在模型选择器里与 deepseek-official
|
package/src/ns.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* settings 命名空间字面量(纯常量,零依赖,浏览器半可安全引入)。
|
|
3
3
|
* 服务端经 settingsNamespace() 品牌化后使用(config.ts),浏览器半将其作为
|
|
4
4
|
* settings.plugin.item keyed 槽位的 key —— 同一字面量两处共用,防止漂移。
|
|
5
|
-
* 契约:
|
|
5
|
+
* 契约:0.1.2-alpha.1 起该槽位按「卡片编辑的 settings 命名空间」作 key 分发,
|
|
6
6
|
* 官方配置页只渲染 key 命中 Host 已注册命名空间的卡片。
|
|
7
7
|
* @module llm-pi/ns
|
|
8
8
|
*/
|
package/src/profiles-deepseek.ts
CHANGED
|
@@ -130,19 +130,25 @@ function materializeModel(
|
|
|
130
130
|
if (entry.reasoningEfforts !== undefined || nonEmptyDict(entry.compat)) {
|
|
131
131
|
invalid(route, `model "${entry.id}" 的 reasoningEfforts/compat 仅 adapter: pi 可用`)
|
|
132
132
|
}
|
|
133
|
+
const legacyImageDetail = (entry as ModelEntryConfig & { imageDetail?: unknown }).imageDetail
|
|
134
|
+
if (legacyImageDetail !== undefined) {
|
|
135
|
+
// 0.1.2-alpha.1 移除字段:写时明确拒绝并给出迁移提示(官方 llm-deepseek
|
|
136
|
+
// 对含 imageDetail 的目录模型同样直接抛错,index.ts:212-215)。
|
|
137
|
+
invalid(
|
|
138
|
+
route,
|
|
139
|
+
`model "${entry.id}" 的 imageDetail 已随 0.1.2-alpha.1 移除;` +
|
|
140
|
+
'请改用 imagePixelBudget/imageMaxBytes(低细节可配更小的 imagePixelBudget)并删除 imageDetail',
|
|
141
|
+
)
|
|
142
|
+
}
|
|
133
143
|
const base = resolveOfficialBase(route, entry, deps)
|
|
134
144
|
const input = declaredModalities(entry.input) ?? base.input
|
|
135
145
|
const imageFields = {
|
|
136
146
|
imagePixelBudget: entry.imagePixelBudget ?? base.imagePixelBudget,
|
|
137
147
|
imageMaxBytes: entry.imageMaxBytes ?? base.imageMaxBytes,
|
|
138
|
-
imageDetail: entry.imageDetail ?? base.imageDetail,
|
|
139
148
|
}
|
|
140
149
|
const acceptsImage = input?.includes('image') ?? false
|
|
141
150
|
if (!acceptsImage && Object.values(imageFields).some((value) => value !== undefined)) {
|
|
142
|
-
invalid(
|
|
143
|
-
route,
|
|
144
|
-
`model "${entry.id}" 未声明 image 模态,不可配置 imagePixelBudget/imageMaxBytes/imageDetail`,
|
|
145
|
-
)
|
|
151
|
+
invalid(route, `model "${entry.id}" 未声明 image 模态,不可配置 imagePixelBudget/imageMaxBytes`)
|
|
146
152
|
}
|
|
147
153
|
return {
|
|
148
154
|
id: entry.id,
|
|
@@ -227,7 +233,7 @@ function buildRoute(
|
|
|
227
233
|
if (deps.kit.deepseek === undefined) {
|
|
228
234
|
invalid(
|
|
229
235
|
route,
|
|
230
|
-
'使用 adapter: deepseek,但当前运行时套件不含 dsh-llm-deepseek
|
|
236
|
+
'使用 adapter: deepseek,但当前运行时套件不含 dsh-llm-deepseek(0.1.2-alpha.1 基线)',
|
|
231
237
|
)
|
|
232
238
|
}
|
|
233
239
|
rejectPiOnlyFields(route, profile)
|