@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-plus/llm-pi",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "dsh-plus service+ui plugin: 基于 PiAiAdapter 的自定义 LLM 路由(全量 compat、模型继承、models.dev 目录兜底)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -23,9 +23,8 @@
|
|
|
23
23
|
"dsh": {
|
|
24
24
|
"client": {
|
|
25
25
|
"inject": [
|
|
26
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
27
26
|
"@deepseek-ai/dsh-client-locale",
|
|
28
|
-
"@deepseek-ai/dsh-client-
|
|
27
|
+
"@deepseek-ai/dsh-client-ui-renderer",
|
|
29
28
|
"@deepseek-ai/dsh-api-remotes"
|
|
30
29
|
],
|
|
31
30
|
"platform": "web"
|
|
@@ -33,35 +32,40 @@
|
|
|
33
32
|
},
|
|
34
33
|
"dependencies": {
|
|
35
34
|
"https-proxy-agent": "^7.0.6",
|
|
36
|
-
"@dsh-plus/shared": "0.1.
|
|
35
|
+
"@dsh-plus/shared": "0.1.6"
|
|
37
36
|
},
|
|
38
37
|
"peerDependencies": {
|
|
39
38
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
40
|
-
"@deepseek-ai/dsh-anonymous-user-id": "^0.1.
|
|
41
|
-
"@deepseek-ai/dsh-credentials": "^0.1.
|
|
42
|
-
"@deepseek-ai/dsh-home-paths": "^0.1.
|
|
43
|
-
"@deepseek-ai/dsh-host-webserver": "^0.1.
|
|
44
|
-
"@deepseek-ai/dsh-launch-environment": "^0.1.
|
|
45
|
-
"@deepseek-ai/dsh-llm": "^0.1.
|
|
46
|
-
"@deepseek-ai/dsh-llm-deepseek": "^0.1.
|
|
47
|
-
"@deepseek-ai/dsh-llm-pi-ai": "^0.1.
|
|
48
|
-
"@deepseek-ai/dsh-settings": "^0.1.
|
|
39
|
+
"@deepseek-ai/dsh-anonymous-user-id": "^0.1.2-alpha.1",
|
|
40
|
+
"@deepseek-ai/dsh-credentials": "^0.1.2-alpha.1",
|
|
41
|
+
"@deepseek-ai/dsh-home-paths": "^0.1.2-alpha.1",
|
|
42
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.1",
|
|
43
|
+
"@deepseek-ai/dsh-launch-environment": "^0.1.2-alpha.1",
|
|
44
|
+
"@deepseek-ai/dsh-llm": "^0.1.2-alpha.1",
|
|
45
|
+
"@deepseek-ai/dsh-llm-deepseek": "^0.1.2-alpha.1",
|
|
46
|
+
"@deepseek-ai/dsh-llm-pi-ai": "^0.1.2-alpha.1",
|
|
47
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.1",
|
|
49
48
|
"@deepseek-ai/schemastery": "^3.18.1",
|
|
50
|
-
"@earendil-works/pi-ai": "^0.
|
|
49
|
+
"@earendil-works/pi-ai": "^0.84.2"
|
|
51
50
|
},
|
|
52
51
|
"devDependencies": {
|
|
53
52
|
"@deepseek-ai/cordis": "4.0.1",
|
|
54
|
-
"@deepseek-ai/dsh-anonymous-user-id": "0.1.
|
|
55
|
-
"@deepseek-ai/dsh-
|
|
56
|
-
"@deepseek-ai/dsh-
|
|
57
|
-
"@deepseek-ai/dsh-
|
|
58
|
-
"@deepseek-ai/dsh-
|
|
59
|
-
"@deepseek-ai/dsh-
|
|
60
|
-
"@deepseek-ai/dsh-
|
|
61
|
-
"@deepseek-ai/dsh-llm
|
|
62
|
-
"@deepseek-ai/dsh-
|
|
53
|
+
"@deepseek-ai/dsh-anonymous-user-id": "0.1.2-alpha.1",
|
|
54
|
+
"@deepseek-ai/dsh-atomic-write": "0.1.2-alpha.1",
|
|
55
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-alpha.1",
|
|
56
|
+
"@deepseek-ai/dsh-credentials": "0.1.2-alpha.1",
|
|
57
|
+
"@deepseek-ai/dsh-home-paths": "0.1.2-alpha.1",
|
|
58
|
+
"@deepseek-ai/dsh-host-webserver": "0.1.2-alpha.1",
|
|
59
|
+
"@deepseek-ai/dsh-launch-environment": "0.1.2-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-llm": "0.1.2-alpha.1",
|
|
61
|
+
"@deepseek-ai/dsh-llm-deepseek": "0.1.2-alpha.1",
|
|
62
|
+
"@deepseek-ai/dsh-llm-pi-ai": "0.1.2-alpha.1",
|
|
63
|
+
"@deepseek-ai/dsh-settings": "0.1.2-alpha.1",
|
|
64
|
+
"@deepseek-ai/dsh-timeout": "0.1.2-alpha.1",
|
|
65
|
+
"@deepseek-ai/dsh-typert-protocol": "0.1.2-alpha.1",
|
|
66
|
+
"@deepseek-ai/dsh-util-crypto": "0.1.2-alpha.1",
|
|
63
67
|
"@deepseek-ai/schemastery": "3.18.1",
|
|
64
|
-
"@earendil-works/pi-ai": "0.
|
|
68
|
+
"@earendil-works/pi-ai": "0.84.2",
|
|
65
69
|
"@types/react": "~18.3.1",
|
|
66
70
|
"react": "^18.2.0"
|
|
67
71
|
},
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PiAiAdapter 认证注入的内联实现(官方 llm-pi-ai/src/auth.ts 的等价移植)。
|
|
3
|
+
*
|
|
4
|
+
* 官方 credentialStoreFrom/authContextFrom 仅在 @deepseek-ai/dsh-llm-pi-ai 的
|
|
5
|
+
* src/auth.ts 子路径导出(包根只导出 recordKeyFor),而 npm 发布形态不携带
|
|
6
|
+
* src/(lib/index.js 单 bundle)——dsh 树 dev 布局优先经 src 子路径走官方
|
|
7
|
+
* 实现(见 resolve-dsh.ts 探测),本文件在其余形态(npm 布局的 dsh 树 /
|
|
8
|
+
* vendored 兜底)下按官方语义逐行等价实现,并经 assertKitShape 自检兜底。
|
|
9
|
+
* recordKeyFor 由调用方注入(与 PiAiAdapter 同源模块的包根导出,保证记录
|
|
10
|
+
* 键格式与官方写入方一致)。
|
|
11
|
+
* @module llm-pi/auth-inline
|
|
12
|
+
*/
|
|
13
|
+
import { access } from 'node:fs/promises'
|
|
14
|
+
import { homedir } from 'node:os'
|
|
15
|
+
import { resolve as resolvePath } from 'node:path'
|
|
16
|
+
|
|
17
|
+
import type { Context } from '@deepseek-ai/cordis'
|
|
18
|
+
import type {
|
|
19
|
+
CredentialKey,
|
|
20
|
+
CredentialProvider,
|
|
21
|
+
CredentialRecord,
|
|
22
|
+
} from '@deepseek-ai/dsh-credentials'
|
|
23
|
+
import {
|
|
24
|
+
credentialKeyId,
|
|
25
|
+
credentialKeyScope,
|
|
26
|
+
credentialRef,
|
|
27
|
+
isCredentialKeySegment,
|
|
28
|
+
isCredentialRefName,
|
|
29
|
+
} from '@deepseek-ai/dsh-credentials'
|
|
30
|
+
import { launchEnvironmentOf } from '@deepseek-ai/dsh-launch-environment'
|
|
31
|
+
import { LlmError } from '@deepseek-ai/dsh-llm'
|
|
32
|
+
import type {
|
|
33
|
+
AuthContext,
|
|
34
|
+
Credential,
|
|
35
|
+
CredentialInfo,
|
|
36
|
+
CredentialStore,
|
|
37
|
+
} from '@earendil-works/pi-ai'
|
|
38
|
+
|
|
39
|
+
/** 官方 RECORD_SCOPE 常量(llm-pi-ai/src/auth.ts)——读写作用域必须与官方一致。 */
|
|
40
|
+
export const INLINE_RECORD_SCOPE = 'llm-pi-ai'
|
|
41
|
+
|
|
42
|
+
/** 官方 jsonImage:pi-ai 凭据里显式 undefined 成员 JSON 序列化为缺省/数组 null。 */
|
|
43
|
+
function jsonImage(value: unknown): unknown {
|
|
44
|
+
if (Array.isArray(value))
|
|
45
|
+
return value.map((entry) => (entry === undefined ? null : jsonImage(entry)))
|
|
46
|
+
if (
|
|
47
|
+
typeof value === 'object' &&
|
|
48
|
+
value !== null &&
|
|
49
|
+
Object.getPrototypeOf(value) === Object.prototype
|
|
50
|
+
) {
|
|
51
|
+
const image: Record<string, unknown> = {}
|
|
52
|
+
for (const [key, member] of Object.entries(value)) {
|
|
53
|
+
if (member !== undefined) image[key] = jsonImage(member)
|
|
54
|
+
}
|
|
55
|
+
return image
|
|
56
|
+
}
|
|
57
|
+
return value
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** 官方 toPiCredential:api-key 记录按字段重建;grant 载荷原样透传。 */
|
|
61
|
+
function toPiCredential(record: CredentialRecord | undefined): Credential | undefined {
|
|
62
|
+
if (record === undefined) return undefined
|
|
63
|
+
if (record.kind === 'api-key') {
|
|
64
|
+
return {
|
|
65
|
+
type: 'api_key',
|
|
66
|
+
...(record.key === undefined ? {} : { key: record.key }),
|
|
67
|
+
...(record.env === undefined ? {} : { env: { ...record.env } }),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return record.payload as Credential
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** 官方 toRecord:pi-ai 凭据 → 可存储的记录联合。 */
|
|
74
|
+
function toRecord(credential: Credential): CredentialRecord {
|
|
75
|
+
if (credential.type === 'api_key') {
|
|
76
|
+
return {
|
|
77
|
+
kind: 'api-key',
|
|
78
|
+
...(credential.key === undefined ? {} : { key: credential.key }),
|
|
79
|
+
...(credential.env === undefined ? {} : { env: { ...credential.env } }),
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return { kind: 'grant', payload: jsonImage(credential) }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** 官方 writableStore:写路径需要凭据服务,缺失时抛 NO_CREDENTIAL_STORE。 */
|
|
86
|
+
function writableStore(ctx: Context): CredentialProvider {
|
|
87
|
+
const credentials = ctx.get('credentials')
|
|
88
|
+
if (credentials === undefined) {
|
|
89
|
+
throw new LlmError(
|
|
90
|
+
'llm-pi: 当前组合未挂载凭据服务(dsh-credentials-local),登录产生的凭据无处存储',
|
|
91
|
+
'NO_CREDENTIAL_STORE',
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
return credentials
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* pi-ai `CredentialStore`(官方 credentialStoreFrom 等价实现)。
|
|
99
|
+
* 路由键任意而记录 id 受语法约束:语法外的 id 读答"未存储"、删除无事可做、
|
|
100
|
+
* modify 拒绝(写不落地不能报成功)。
|
|
101
|
+
*/
|
|
102
|
+
export function credentialStoreFrom(
|
|
103
|
+
ctx: Context,
|
|
104
|
+
recordKeyFor: (providerId: string) => CredentialKey,
|
|
105
|
+
): CredentialStore {
|
|
106
|
+
return {
|
|
107
|
+
async read(providerId) {
|
|
108
|
+
const credentials = ctx.get('credentials')
|
|
109
|
+
if (credentials === undefined) return undefined
|
|
110
|
+
if (!isCredentialKeySegment(providerId)) return undefined
|
|
111
|
+
return toPiCredential(await credentials.readRecord(recordKeyFor(providerId)))
|
|
112
|
+
},
|
|
113
|
+
async list(): Promise<readonly CredentialInfo[]> {
|
|
114
|
+
const stored = (await ctx.get('credentials')?.listRecords()) ?? []
|
|
115
|
+
const mine: CredentialInfo[] = []
|
|
116
|
+
for (const entry of stored) {
|
|
117
|
+
if (credentialKeyScope(entry.key) !== INLINE_RECORD_SCOPE) continue
|
|
118
|
+
mine.push({
|
|
119
|
+
providerId: credentialKeyId(entry.key),
|
|
120
|
+
type: entry.kind === 'api-key' ? 'api_key' : 'oauth',
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
return mine
|
|
124
|
+
},
|
|
125
|
+
async modify(providerId, mutate) {
|
|
126
|
+
if (!isCredentialKeySegment(providerId)) {
|
|
127
|
+
throw new LlmError(
|
|
128
|
+
`llm-pi: provider id "${providerId}" 无法寻址存储的凭据记录(记录 id 是小写连字符标识);` +
|
|
129
|
+
'该 route 请改用 apiKeyEnv 认证',
|
|
130
|
+
'UNSTORABLE_PROVIDER_ID',
|
|
131
|
+
)
|
|
132
|
+
}
|
|
133
|
+
const stored = await writableStore(ctx).modifyRecord(
|
|
134
|
+
recordKeyFor(providerId),
|
|
135
|
+
async (current) => {
|
|
136
|
+
const next = await mutate(toPiCredential(current))
|
|
137
|
+
return next === undefined ? undefined : toRecord(next)
|
|
138
|
+
},
|
|
139
|
+
)
|
|
140
|
+
return toPiCredential(stored)
|
|
141
|
+
},
|
|
142
|
+
async delete(providerId) {
|
|
143
|
+
if (!isCredentialKeySegment(providerId)) return
|
|
144
|
+
await writableStore(ctx).deleteRecord(recordKeyFor(providerId))
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* pi-ai `AuthContext`(官方 authContextFrom 等价实现):
|
|
151
|
+
* env() 先查凭据服务(存储的环境值对 provider 自发现可见),再回退启动环境;
|
|
152
|
+
* fileExists() 问宿主进程文件系统(~/.aws/credentials 等是本进程所在机器的事实)。
|
|
153
|
+
*/
|
|
154
|
+
export function authContextFrom(ctx: Context): AuthContext {
|
|
155
|
+
return {
|
|
156
|
+
async env(name) {
|
|
157
|
+
if (isCredentialRefName(name)) {
|
|
158
|
+
const credentials = ctx.get('credentials')
|
|
159
|
+
const hit = await credentials?.resolve(credentialRef(name))
|
|
160
|
+
if (hit !== undefined) return hit.value
|
|
161
|
+
}
|
|
162
|
+
return launchEnvironmentOf(ctx).get(name)?.value
|
|
163
|
+
},
|
|
164
|
+
async fileExists(path) {
|
|
165
|
+
const expanded =
|
|
166
|
+
path.startsWith('~/') || path === '~'
|
|
167
|
+
? resolvePath(homedir(), path.slice(1).replace(/^\//, ''))
|
|
168
|
+
: path
|
|
169
|
+
try {
|
|
170
|
+
await access(expanded)
|
|
171
|
+
return true
|
|
172
|
+
} catch {
|
|
173
|
+
return false
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
}
|
|
177
|
+
}
|
package/src/catalog/official.ts
CHANGED
|
@@ -17,7 +17,6 @@ export interface OfficialModelBase {
|
|
|
17
17
|
input?: ('text' | 'image')[]
|
|
18
18
|
imagePixelBudget?: number
|
|
19
19
|
imageMaxBytes?: number
|
|
20
|
-
imageDetail?: 'auto' | 'low'
|
|
21
20
|
}
|
|
22
21
|
|
|
23
22
|
interface OfficialCatalog {
|
|
@@ -46,7 +45,6 @@ function catalogOf(kit: DshKit): OfficialCatalog {
|
|
|
46
45
|
: { input: [...model.inputModalities] as ('text' | 'image')[] }),
|
|
47
46
|
...(model.imagePixelBudget === undefined ? {} : { imagePixelBudget: model.imagePixelBudget }),
|
|
48
47
|
...(model.imageMaxBytes === undefined ? {} : { imageMaxBytes: model.imageMaxBytes }),
|
|
49
|
-
...(model.imageDetail === undefined ? {} : { imageDetail: model.imageDetail }),
|
|
50
48
|
})
|
|
51
49
|
}
|
|
52
50
|
const catalog: OfficialCatalog = { baseUrl: resolved.baseURL, models }
|
package/src/client/api.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* 自定义端点通道:仅剩「模型目录」(配置读写已迁移到 ctx.remote.settings
|
|
3
|
+
* 直连传输,0.1.2-alpha.1 起 connection.api.settings 已移除;见 scope.ts 与
|
|
4
|
+
* card.tsx)。目录响应附带 kitSource 与 models-dev 状态,供卡片的状态行
|
|
5
|
+
* 展示(运行期诊断,非配置数据)。
|
|
5
6
|
* @module llm-pi/client/api
|
|
6
7
|
*/
|
|
7
8
|
|
package/src/client/card.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 「LLM 路由」配置卡片:注册进 settings.plugin.item 插槽(官方插件配置页)。
|
|
3
3
|
* 顶部:enabled / catalogUrl / catalogRefreshHours / 只读状态行(kitSource、
|
|
4
|
-
* modelsDevStatus,来自模型目录端点)+ 保存(settings.
|
|
4
|
+
* modelsDevStatus,来自模型目录端点)+ 保存(settings.update 全量深合并)与
|
|
5
5
|
* 错误/成功提示;下方为 providers 路由列表(新增/删除/字段编辑/compat/模型
|
|
6
6
|
* 目录,见 views/)。外壳与基础控件走 @dsh-plus/shared/client 套件。
|
|
7
7
|
* @module llm-pi/client/card
|
|
@@ -12,12 +12,11 @@ import {
|
|
|
12
12
|
type CardStatusState,
|
|
13
13
|
CheckRow,
|
|
14
14
|
IDLE_STATUS,
|
|
15
|
+
type NamespaceSettingsApi,
|
|
15
16
|
type Scope,
|
|
16
|
-
type SettingsApi,
|
|
17
17
|
TextField,
|
|
18
18
|
} from '@dsh-plus/shared/client'
|
|
19
19
|
import { type ReactElement, useEffect, useMemo, useState, useSyncExternalStore } from 'react'
|
|
20
|
-
import { SETTINGS_NS } from '../ns.ts'
|
|
21
20
|
import { type ConfigValue, fetchCatalog, refreshCatalog, type WireModelsDevStatus } from './api.ts'
|
|
22
21
|
import {
|
|
23
22
|
type Draft,
|
|
@@ -32,7 +31,7 @@ import { ProvidersSection } from './views/providers.tsx'
|
|
|
32
31
|
export interface CardProps {
|
|
33
32
|
t(key: string): string
|
|
34
33
|
scope: Scope
|
|
35
|
-
api:
|
|
34
|
+
api: NamespaceSettingsApi
|
|
36
35
|
}
|
|
37
36
|
|
|
38
37
|
function modelsDevText(status: WireModelsDevStatus | null, t: (key: string) => string): string {
|
|
@@ -129,15 +128,8 @@ export function LlmPiCard(props: CardProps): ReactElement | null {
|
|
|
129
128
|
setSaving(true)
|
|
130
129
|
const revision = scope.getSnapshot().revision
|
|
131
130
|
api
|
|
132
|
-
.
|
|
133
|
-
|
|
134
|
-
section: toPatch(draft) as unknown as Record<string, unknown>,
|
|
135
|
-
...(revision !== undefined ? { expectedRevision: revision } : {}),
|
|
136
|
-
})
|
|
137
|
-
.then(async (response) => {
|
|
138
|
-
if (!response.result.ok) {
|
|
139
|
-
throw new Error(response.result.error?.message ?? t('saveFailed'))
|
|
140
|
-
}
|
|
131
|
+
.update(toPatch(draft) as unknown as Record<string, unknown>, revision)
|
|
132
|
+
.then(async () => {
|
|
141
133
|
await scope.load()
|
|
142
134
|
const next = scope.getSnapshot().value as ConfigValue | undefined
|
|
143
135
|
if (next !== undefined) setDraft(draftFromValue(next))
|
package/src/client/client.ts
CHANGED
|
@@ -3,21 +3,23 @@
|
|
|
3
3
|
* 构建产物为 window.__ModuleLoader__.load({id, factory}) 形式的 CJS factory
|
|
4
4
|
* (包装见 tsdown.config.ts);样式沿用官方 data-plugin-css 约定,HMR 据此卸载。
|
|
5
5
|
*
|
|
6
|
-
* 类型说明:浏览器半只用到 slots/locale/
|
|
7
|
-
* scope 与基础控件走 @dsh-plus/shared/client
|
|
8
|
-
*
|
|
9
|
-
* settings.plugin.item 插槽与 dsh-client-connection 的 settings RPC 面为准。
|
|
10
|
-
* rc7 起该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
6
|
+
* 类型说明:浏览器半只用到 slots/locale/remote 的很窄一面,
|
|
7
|
+
* scope 与基础控件走 @dsh-plus/shared/client 套件(收编自本插件原实现)。
|
|
8
|
+
* 该插槽为 keyed 槽位:key 必须是卡片编辑的 settings 命名空间
|
|
11
9
|
* (即服务端 settingsNamespace() 注册的同一字面量,见 ../ns.ts),
|
|
12
10
|
* 官方配置页按此 key 与 Host 已注册命名空间配对分发。
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* 运行期诊断)。
|
|
11
|
+
* 配置读写经 ctx.remote.settings 直连(0.1.2-alpha.1 起 connection.api.settings
|
|
12
|
+
* 已移除;不复用 settingsScope 服务——非 loopback 页面下它固定 memory 模式无数据,
|
|
13
|
+
* 见 @dsh-plus/shared/client scope.ts);自定义端点仅剩「模型目录」
|
|
14
|
+
* (api.ts,含 kitSource/models-dev 运行期诊断)。
|
|
17
15
|
* @module @dsh-plus/llm-pi/client
|
|
18
16
|
*/
|
|
19
17
|
import type { Context } from '@deepseek-ai/cordis'
|
|
20
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
createNamespaceApi,
|
|
20
|
+
createSettingsScope,
|
|
21
|
+
type SettingsRemoteFace,
|
|
22
|
+
} from '@dsh-plus/shared/client'
|
|
21
23
|
import { SETTINGS_NS } from '../ns.ts'
|
|
22
24
|
import { LlmPiCard } from './card.tsx'
|
|
23
25
|
import { en, NS, zh } from './i18n.ts'
|
|
@@ -26,7 +28,7 @@ import { injectStyle } from './styles.ts'
|
|
|
26
28
|
export const name = 'dsh-plus-llm-pi'
|
|
27
29
|
|
|
28
30
|
/** 浏览器半需要的 cordis 服务 key(loader 据此注入;package.json 的 dsh.client.inject 管包加载顺序)。 */
|
|
29
|
-
export const inject = ['slots', 'locale', '
|
|
31
|
+
export const inject = ['slots', 'locale', 'remote', 'remote.settings'] as const
|
|
30
32
|
|
|
31
33
|
interface SlotsLike {
|
|
32
34
|
inject(key: string, callback: () => unknown): unknown
|
|
@@ -39,17 +41,13 @@ interface LocaleLike {
|
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
interface RemoteLike {
|
|
44
|
+
settings: SettingsRemoteFace
|
|
42
45
|
$on(event: string, listener: (payload?: unknown) => void): () => void
|
|
43
46
|
}
|
|
44
47
|
|
|
45
|
-
interface ConnectionLike {
|
|
46
|
-
api: { settings: Parameters<typeof createApiScope>[0] }
|
|
47
|
-
}
|
|
48
|
-
|
|
49
48
|
interface ClientContext {
|
|
50
49
|
slots: SlotsLike
|
|
51
50
|
locale: LocaleLike
|
|
52
|
-
get(key: 'connection'): ConnectionLike
|
|
53
51
|
get(key: 'remote'): RemoteLike
|
|
54
52
|
on(event: string, listener: () => void): () => void
|
|
55
53
|
effect(execute: () => () => void, label?: string): unknown
|
|
@@ -65,8 +63,8 @@ export function apply(ctx: Context): void {
|
|
|
65
63
|
'llm-pi: style',
|
|
66
64
|
)
|
|
67
65
|
c.effect(() => c.locale.register(NS, { zh, en }), 'llm-pi: locale')
|
|
68
|
-
const
|
|
69
|
-
const
|
|
66
|
+
const scope = createSettingsScope(c, SETTINGS_NS, 'llm-pi: settings scope')
|
|
67
|
+
const api = createNamespaceApi(c.get('remote').settings, SETTINGS_NS)
|
|
70
68
|
c.slots.inject('settings.plugin.item', () =>
|
|
71
69
|
c.slots.register(
|
|
72
70
|
{
|
package/src/client/constants.ts
CHANGED
|
@@ -29,13 +29,18 @@ export const BUDGET_KEYS = ['minimal', 'low', 'medium', 'high'] as const
|
|
|
29
29
|
|
|
30
30
|
type CompatValue = 'boolean' | 'object' | readonly string[]
|
|
31
31
|
|
|
32
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* 逐协议 compat 字段表(来源:compat.ts,逐字段镜像官方 catalog.ts COMPAT_GATES)。
|
|
34
|
+
* 只列 offer 字段(withhold 字段官方写时拒绝,UI 不提供);取值约束对齐官方
|
|
35
|
+
* config.ts compatProfile schema。
|
|
36
|
+
*/
|
|
33
37
|
const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
|
|
34
38
|
'openai-completions': {
|
|
35
39
|
supportsStore: 'boolean',
|
|
36
40
|
supportsDeveloperRole: 'boolean',
|
|
37
41
|
supportsReasoningEffort: 'boolean',
|
|
38
42
|
supportsUsageInStreaming: 'boolean',
|
|
43
|
+
supportsFinishReason: 'boolean',
|
|
39
44
|
maxTokensField: ['max_completion_tokens', 'max_tokens'],
|
|
40
45
|
requiresToolResultName: 'boolean',
|
|
41
46
|
requiresAssistantAfterToolResult: 'boolean',
|
|
@@ -43,9 +48,10 @@ const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
|
|
|
43
48
|
requiresReasoningContentOnAssistantMessages: 'boolean',
|
|
44
49
|
thinkingFormat: [
|
|
45
50
|
'openai',
|
|
46
|
-
'openrouter',
|
|
47
51
|
'deepseek',
|
|
52
|
+
'openrouter',
|
|
48
53
|
'together',
|
|
54
|
+
'baseten',
|
|
49
55
|
'zai',
|
|
50
56
|
'qwen',
|
|
51
57
|
'chat-template',
|
|
@@ -54,36 +60,25 @@ const COMPAT_FIELDS: Record<string, Record<string, CompatValue>> = {
|
|
|
54
60
|
'ant-ling',
|
|
55
61
|
],
|
|
56
62
|
chatTemplateKwargs: 'object',
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
zaiToolStream: 'boolean',
|
|
60
|
-
supportsOpenAIGrammarTools: 'boolean',
|
|
63
|
+
chatTemplateArgs: 'object',
|
|
64
|
+
supportsThinkingTokenBudget: 'boolean',
|
|
61
65
|
supportsStrictMode: 'boolean',
|
|
62
66
|
cacheControlFormat: ['anthropic'],
|
|
63
|
-
sendSessionAffinityHeaders: 'boolean',
|
|
64
|
-
deferredToolsMode: ['kimi'],
|
|
65
|
-
sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
|
|
66
67
|
supportsLongCacheRetention: 'boolean',
|
|
67
68
|
},
|
|
68
69
|
'openai-responses': {
|
|
69
70
|
supportsDeveloperRole: 'boolean',
|
|
70
|
-
sessionAffinityFormat: ['openai', 'openai-nosession', 'openrouter'],
|
|
71
|
-
supportsLongCacheRetention: 'boolean',
|
|
72
71
|
supportsStrictMode: 'boolean',
|
|
73
|
-
|
|
74
|
-
supportsToolSearch: 'boolean',
|
|
75
|
-
supportsExplicitPromptCacheMode: 'boolean',
|
|
72
|
+
supportsLongCacheRetention: 'boolean',
|
|
76
73
|
},
|
|
77
74
|
'anthropic-messages': {
|
|
78
75
|
supportsEagerToolInputStreaming: 'boolean',
|
|
79
76
|
supportsLongCacheRetention: 'boolean',
|
|
80
|
-
sendSessionAffinityHeaders: 'boolean',
|
|
81
77
|
supportsCacheControlOnTools: 'boolean',
|
|
82
78
|
supportsTemperature: 'boolean',
|
|
83
79
|
forceAdaptiveThinking: 'boolean',
|
|
84
80
|
allowEmptySignature: 'boolean',
|
|
85
81
|
supportsStrictTools: 'boolean',
|
|
86
|
-
supportsToolReferences: 'boolean',
|
|
87
82
|
},
|
|
88
83
|
}
|
|
89
84
|
|