dsh-llm-adapters 1.2.0 → 1.2.2

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/src/config.ts CHANGED
@@ -1,264 +1,258 @@
1
- /**
2
- * The plugin's configuration surface.
3
- *
4
- * DSH renders this schema in the generated part of the settings UI and, more
5
- * importantly, treats every `.volatile()` field as an in-place edit: changing
6
- * it must never restart the plugin. The adapters therefore read values through
7
- * thunks from {@link liveConfig}.
8
- *
9
- * @module dsh-llm-adapters/config
10
- */
11
-
12
- import { isVolatile } from '@deepseek-ai/cosmokit'
13
- import z from '@deepseek-ai/schemastery'
14
- import type { TraeRegion } from './brand.ts'
15
- import { normalizeTraeRegion } from './brand.ts'
16
-
17
- export interface Config {
18
- loomyEnabled?: boolean
19
- raccoonEnabled?: boolean
20
- traeEnabled?: boolean
21
- traeRegion?: string
22
- codeartsEnabled?: boolean
23
- loomyDisplayName?: string
24
- raccoonDisplayName?: string
25
- traeDisplayName?: string
26
- codeartsDisplayName?: string
27
- loomyDisabledModels?: string[]
28
- raccoonDisabledModels?: string[]
29
- traeDisabledModels?: string[]
30
- codeartsDisabledModels?: string[]
31
- loomyModelContextWindows?: Record<string, number>
32
- raccoonModelContextWindows?: Record<string, number>
33
- traeModelContextWindows?: Record<string, number>
34
- codeartsModelContextWindows?: Record<string, number>
35
- loomyModelAliases?: Record<string, string>
36
- raccoonModelAliases?: Record<string, string>
37
- traeModelAliases?: Record<string, string>
38
- codeartsModelAliases?: Record<string, string>
39
- loomyVisionModelIds?: string[]
40
- loomyTextOnlyModelIds?: string[]
41
- raccoonVisionModelIds?: string[]
42
- raccoonTextOnlyModelIds?: string[]
43
- traeVisionModelIds?: string[]
44
- traeTextOnlyModelIds?: string[]
45
- codeartsVisionModelIds?: string[]
46
- codeartsTextOnlyModelIds?: string[]
47
- defaultMaxTokens?: number
48
- modelCacheTtlSeconds?: number
49
- catalogFetchTimeoutSeconds?: number
50
- /** Minutes between automatic OpenRouter syncs; 0 disables the schedule. */
51
- openRouterSyncMinutes?: number
52
- /** Minutes between Raccoon daily-point checks; 0 disables the monitor. */
53
- raccoonCheckinMinutes?: number
54
- }
55
-
56
- export const Config = z.object({
57
- loomyEnabled: z.boolean().default(true).description('启用 Loomy。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
58
- raccoonEnabled: z.boolean().default(true).description('启用商汤小浣熊。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
59
- traeEnabled: z.boolean().default(true).description('启用 Trae。关闭后该设置页、模型列表与 provider 路由都会移除;两个区域的登录态与模型选择会保留。').volatile(),
60
- traeRegion: z.string().default('cn').description('Trae 登录区域:cn(国内版 trae.cn)或 global(国际版 trae.ai)。两个区域各自保存账号,切换后立即恢复对应账号。').volatile(),
61
- codeartsEnabled: z.boolean().default(true).description('启用华为云 CodeArts。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
62
-
63
- loomyDisplayName: z.string().default('Loomy').description('模型选择器里 Loomy 分组的名称。').volatile(),
64
- raccoonDisplayName: z.string().default('商汤小浣熊').description('模型选择器里小浣熊分组的名称。').volatile(),
65
- traeDisplayName: z.string().default('Trae').description('模型选择器里 Trae 分组的名称。').volatile(),
66
- codeartsDisplayName: z.string().default('华为云 CodeArts').description('模型选择器里 CodeArts 分组的名称。').volatile(),
67
-
68
- loomyDisabledModels: z.array(z.string()).default([]).description('Loomy 已关闭的模型 ID 列表。关闭的模型不会出现在模型选择器里。').volatile(),
69
- raccoonDisabledModels: z.array(z.string()).default([]).description('小浣熊已关闭的模型 ID 列表。').volatile(),
70
- traeDisabledModels: z.array(z.string()).default([]).description('Trae 已关闭的模型 ID 列表。').volatile(),
71
- codeartsDisabledModels: z.array(z.string()).default([]).description('CodeArts 已关闭的模型 ID 列表。').volatile(),
72
- loomyModelContextWindows: z.dict(z.number()).default({}).description('Loomy 按模型指定上下文窗口(token 数)。未指定的模型使用上游声明的窗口。').volatile(),
73
- raccoonModelContextWindows: z.dict(z.number()).default({}).description('小浣熊按模型指定上下文窗口(token 数)。').volatile(),
74
- traeModelContextWindows: z.dict(z.number()).default({}).description('Trae 按模型指定上下文窗口(token 数)。').volatile(),
75
- codeartsModelContextWindows: z.dict(z.number()).default({}).description('CodeArts 按模型指定上下文窗口(token 数)。上游不下发窗口,默认使用 IDE 配置里的实测值。').volatile(),
76
- loomyModelAliases: z.dict(z.string()).default({}).description('Loomy 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
77
- raccoonModelAliases: z.dict(z.string()).default({}).description('小浣熊模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
78
- traeModelAliases: z.dict(z.string()).default({}).description('Trae 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
79
- codeartsModelAliases: z.dict(z.string()).default({}).description('CodeArts 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
80
-
81
- loomyVisionModelIds: z.array(z.string()).default([]).description('Loomy 强制按“能读图”声明的模型 ID,优先级高于目录标签。').volatile(),
82
- loomyTextOnlyModelIds: z.array(z.string()).default([]).description('Loomy 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
83
- raccoonVisionModelIds: z.array(z.string()).default([]).description('小浣熊强制按“能读图”声明的模型 ID。用于纠正上游标签错误。').volatile(),
84
- raccoonTextOnlyModelIds: z.array(z.string()).default([]).description('小浣熊强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
85
- traeVisionModelIds: z.array(z.string()).default([]).description('Trae 强制按“能读图”声明的模型 ID。目录标注 multimodal 的模型会自动启用。').volatile(),
86
- traeTextOnlyModelIds: z.array(z.string()).default([]).description('Trae 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
87
- codeartsVisionModelIds: z.array(z.string()).default([]).description('CodeArts 的“强制读图”列表。所有模型都能接收图片(文本模型先由 VL 模型转述,设置页只给原生多模态模型标“读图”);要关掉某个模型的读图能力请用下面的“纯文本”列表。').volatile(),
88
- codeartsTextOnlyModelIds: z.array(z.string()).default([]).description('CodeArts 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
89
-
90
- defaultMaxTokens: z.number().step(1).min(1).default(32_000).description('模型目录未声明输出上限时使用的输出上限。').volatile(),
91
- modelCacheTtlSeconds: z.number().step(1).min(10).max(86_400).default(120).description('模型列表缓存时长(秒)。调小可更快看到账号侧变化。').volatile(),
92
- catalogFetchTimeoutSeconds: z.number().step(1).min(1).max(300).default(20).description('读取模型目录、额度等请求的时限(秒)。').volatile(),
93
- openRouterSyncMinutes: z.number().step(1).min(0).max(43_200).default(60).description('OpenRouter 模型元数据的自动同步间隔(分钟)。0 表示关闭自动同步;缓存保存在本机,连不上时继续使用上次成功同步的数据。').volatile(),
94
- raccoonCheckinMinutes: z.number().step(1).min(0).max(1_440).default(30).description('小浣熊每日积分自动监测间隔(分钟):到点核对积分流水,未到账时按网页方式尝试触发。0 表示关闭自动监测;「打开网页签到」入口始终保留。').volatile(),
95
- })
96
-
97
- export interface ResolvedConfig {
98
- loomyEnabled: boolean
99
- raccoonEnabled: boolean
100
- traeEnabled: boolean
101
- traeRegion: TraeRegion
102
- codeartsEnabled: boolean
103
- loomyDisplayName: string
104
- raccoonDisplayName: string
105
- traeDisplayName: string
106
- codeartsDisplayName: string
107
- loomyDisabledModels: readonly string[]
108
- raccoonDisabledModels: readonly string[]
109
- traeDisabledModels: readonly string[]
110
- codeartsDisabledModels: readonly string[]
111
- loomyModelContextWindows: Readonly<Record<string, number>>
112
- raccoonModelContextWindows: Readonly<Record<string, number>>
113
- traeModelContextWindows: Readonly<Record<string, number>>
114
- codeartsModelContextWindows: Readonly<Record<string, number>>
115
- loomyModelAliases: Readonly<Record<string, string>>
116
- raccoonModelAliases: Readonly<Record<string, string>>
117
- traeModelAliases: Readonly<Record<string, string>>
118
- codeartsModelAliases: Readonly<Record<string, string>>
119
- loomyVisionModelIds: readonly string[]
120
- loomyTextOnlyModelIds: readonly string[]
121
- raccoonVisionModelIds: readonly string[]
122
- raccoonTextOnlyModelIds: readonly string[]
123
- traeVisionModelIds: readonly string[]
124
- traeTextOnlyModelIds: readonly string[]
125
- codeartsVisionModelIds: readonly string[]
126
- codeartsTextOnlyModelIds: readonly string[]
127
- defaultMaxTokens: number
128
- modelCacheTtlMs: number
129
- catalogFetchTimeoutMs: number
130
- openRouterSyncMinutes: number
131
- raccoonCheckinMinutes: number
132
- }
133
-
134
- export type ParsedConfig = { readonly [K in keyof Config]?: unknown }
135
- export type LiveConfig = { readonly [K in keyof ResolvedConfig]: () => ResolvedConfig[K] }
136
-
137
- export function liveConfig(config: ParsedConfig | undefined): LiveConfig {
138
- const source = (config ?? {}) as Record<string, unknown>
139
- const field = (key: keyof Config): unknown => {
140
- const value = source[key]
141
- return isVolatile(value) ? value.get() : value
142
- }
143
-
144
- return {
145
- loomyEnabled: () => bool(field('loomyEnabled'), true),
146
- raccoonEnabled: () => bool(field('raccoonEnabled'), true),
147
- traeEnabled: () => bool(field('traeEnabled'), true),
148
- traeRegion: () => normalizeTraeRegion(field('traeRegion')),
149
- codeartsEnabled: () => bool(field('codeartsEnabled'), true),
150
- loomyDisplayName: () => nonEmpty(field('loomyDisplayName'), 'Loomy'),
151
- raccoonDisplayName: () => nonEmpty(field('raccoonDisplayName'), '商汤小浣熊'),
152
- traeDisplayName: () => nonEmpty(field('traeDisplayName'), 'Trae'),
153
- codeartsDisplayName: () => nonEmpty(field('codeartsDisplayName'), '华为云 CodeArts'),
154
- loomyDisabledModels: () => stringList(field('loomyDisabledModels')),
155
- raccoonDisabledModels: () => stringList(field('raccoonDisabledModels')),
156
- traeDisabledModels: () => stringList(field('traeDisabledModels')),
157
- codeartsDisabledModels: () => stringList(field('codeartsDisabledModels')),
158
- loomyModelContextWindows: () => numberMap(field('loomyModelContextWindows')),
159
- raccoonModelContextWindows: () => numberMap(field('raccoonModelContextWindows')),
160
- traeModelContextWindows: () => numberMap(field('traeModelContextWindows')),
161
- codeartsModelContextWindows: () => numberMap(field('codeartsModelContextWindows')),
162
- loomyModelAliases: () => stringMap(field('loomyModelAliases')),
163
- raccoonModelAliases: () => stringMap(field('raccoonModelAliases')),
164
- traeModelAliases: () => stringMap(field('traeModelAliases')),
165
- codeartsModelAliases: () => stringMap(field('codeartsModelAliases')),
166
- loomyVisionModelIds: () => stringList(field('loomyVisionModelIds')),
167
- loomyTextOnlyModelIds: () => stringList(field('loomyTextOnlyModelIds')),
168
- raccoonVisionModelIds: () => stringList(field('raccoonVisionModelIds')),
169
- raccoonTextOnlyModelIds: () => stringList(field('raccoonTextOnlyModelIds')),
170
- traeVisionModelIds: () => stringList(field('traeVisionModelIds')),
171
- traeTextOnlyModelIds: () => stringList(field('traeTextOnlyModelIds')),
172
- codeartsVisionModelIds: () => stringList(field('codeartsVisionModelIds')),
173
- codeartsTextOnlyModelIds: () => stringList(field('codeartsTextOnlyModelIds')),
174
- defaultMaxTokens: () => positive(field('defaultMaxTokens'), 32_000),
175
- modelCacheTtlMs: () => positive(field('modelCacheTtlSeconds'), 120) * 1_000,
176
- catalogFetchTimeoutMs: () => positive(field('catalogFetchTimeoutSeconds'), 20) * 1_000,
177
- openRouterSyncMinutes: () => nonNegative(field('openRouterSyncMinutes'), 60),
178
- raccoonCheckinMinutes: () => nonNegative(field('raccoonCheckinMinutes'), 30),
179
- }
180
- }
181
-
182
- export function resolveConfig(config: ParsedConfig | undefined): ResolvedConfig {
183
- const live = liveConfig(config)
184
- return {
185
- loomyEnabled: live.loomyEnabled(),
186
- raccoonEnabled: live.raccoonEnabled(),
187
- traeEnabled: live.traeEnabled(),
188
- traeRegion: live.traeRegion(),
189
- codeartsEnabled: live.codeartsEnabled(),
190
- loomyDisplayName: live.loomyDisplayName(),
191
- raccoonDisplayName: live.raccoonDisplayName(),
192
- traeDisplayName: live.traeDisplayName(),
193
- codeartsDisplayName: live.codeartsDisplayName(),
194
- loomyDisabledModels: live.loomyDisabledModels(),
195
- raccoonDisabledModels: live.raccoonDisabledModels(),
196
- traeDisabledModels: live.traeDisabledModels(),
197
- codeartsDisabledModels: live.codeartsDisabledModels(),
198
- loomyModelContextWindows: live.loomyModelContextWindows(),
199
- raccoonModelContextWindows: live.raccoonModelContextWindows(),
200
- traeModelContextWindows: live.traeModelContextWindows(),
201
- codeartsModelContextWindows: live.codeartsModelContextWindows(),
202
- loomyModelAliases: live.loomyModelAliases(),
203
- raccoonModelAliases: live.raccoonModelAliases(),
204
- traeModelAliases: live.traeModelAliases(),
205
- codeartsModelAliases: live.codeartsModelAliases(),
206
- loomyVisionModelIds: live.loomyVisionModelIds(),
207
- loomyTextOnlyModelIds: live.loomyTextOnlyModelIds(),
208
- raccoonVisionModelIds: live.raccoonVisionModelIds(),
209
- raccoonTextOnlyModelIds: live.raccoonTextOnlyModelIds(),
210
- traeVisionModelIds: live.traeVisionModelIds(),
211
- traeTextOnlyModelIds: live.traeTextOnlyModelIds(),
212
- codeartsVisionModelIds: live.codeartsVisionModelIds(),
213
- codeartsTextOnlyModelIds: live.codeartsTextOnlyModelIds(),
214
- defaultMaxTokens: live.defaultMaxTokens(),
215
- modelCacheTtlMs: live.modelCacheTtlMs(),
216
- catalogFetchTimeoutMs: live.catalogFetchTimeoutMs(),
217
- openRouterSyncMinutes: live.openRouterSyncMinutes(),
218
- raccoonCheckinMinutes: live.raccoonCheckinMinutes(),
219
- }
220
- }
221
-
222
- function text(value: unknown): string {
223
- return typeof value === 'string' ? value.trim() : ''
224
- }
225
-
226
- function nonEmpty(value: unknown, fallback: string): string {
227
- const valueText = text(value)
228
- return valueText.length > 0 ? valueText : fallback
229
- }
230
-
231
- function positive(value: unknown, fallback: number): number {
232
- return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback
233
- }
234
-
235
- function nonNegative(value: unknown, fallback: number): number {
236
- return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback
237
- }
238
-
239
- function bool(value: unknown, fallback: boolean): boolean {
240
- return typeof value === 'boolean' ? value : fallback
241
- }
242
-
243
- function stringList(value: unknown): readonly string[] {
244
- if (!Array.isArray(value)) return []
245
- return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0).map(entry => entry.trim())
246
- }
247
-
248
- function numberMap(value: unknown): Readonly<Record<string, number>> {
249
- if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
250
- const out: Record<string, number> = {}
251
- for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
252
- if (typeof entry === 'number' && Number.isFinite(entry) && entry > 0) out[key] = entry
253
- }
254
- return out
255
- }
256
-
257
- function stringMap(value: unknown): Readonly<Record<string, string>> {
258
- if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
259
- const out: Record<string, string> = {}
260
- for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
261
- if (typeof entry === 'string' && entry.trim().length > 0) out[key] = entry.trim()
262
- }
263
- return out
264
- }
1
+ /**
2
+ * The plugin's configuration surface.
3
+ *
4
+ * DSH renders this schema in the generated part of the settings UI and, more
5
+ * importantly, treats every `.volatile()` field as an in-place edit: changing
6
+ * it must never restart the plugin. The adapters therefore read values through
7
+ * thunks from {@link liveConfig}.
8
+ *
9
+ * @module dsh-llm-adapters/config
10
+ */
11
+
12
+ import { isVolatile } from '@deepseek-ai/cosmokit'
13
+ import z from '@deepseek-ai/schemastery'
14
+ import type { TraeRegion } from './brand.ts'
15
+ import { normalizeTraeRegion } from './brand.ts'
16
+
17
+ export interface Config {
18
+ loomyEnabled?: boolean
19
+ raccoonEnabled?: boolean
20
+ traeEnabled?: boolean
21
+ traeRegion?: string
22
+ codeartsEnabled?: boolean
23
+ loomyDisplayName?: string
24
+ raccoonDisplayName?: string
25
+ traeDisplayName?: string
26
+ codeartsDisplayName?: string
27
+ loomyDisabledModels?: string[]
28
+ raccoonDisabledModels?: string[]
29
+ traeDisabledModels?: string[]
30
+ codeartsDisabledModels?: string[]
31
+ loomyModelContextWindows?: Record<string, number>
32
+ raccoonModelContextWindows?: Record<string, number>
33
+ traeModelContextWindows?: Record<string, number>
34
+ codeartsModelContextWindows?: Record<string, number>
35
+ loomyModelAliases?: Record<string, string>
36
+ raccoonModelAliases?: Record<string, string>
37
+ traeModelAliases?: Record<string, string>
38
+ codeartsModelAliases?: Record<string, string>
39
+ loomyVisionModelIds?: string[]
40
+ loomyTextOnlyModelIds?: string[]
41
+ raccoonVisionModelIds?: string[]
42
+ raccoonTextOnlyModelIds?: string[]
43
+ traeVisionModelIds?: string[]
44
+ traeTextOnlyModelIds?: string[]
45
+ codeartsVisionModelIds?: string[]
46
+ codeartsTextOnlyModelIds?: string[]
47
+ defaultMaxTokens?: number
48
+ modelCacheTtlSeconds?: number
49
+ catalogFetchTimeoutSeconds?: number
50
+ /** Minutes between automatic OpenRouter syncs; 0 disables the schedule. */
51
+ openRouterSyncMinutes?: number
52
+ }
53
+
54
+ export const Config = z.object({
55
+ loomyEnabled: z.boolean().default(true).description('启用 Loomy。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
56
+ raccoonEnabled: z.boolean().default(true).description('启用商汤小浣熊。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
57
+ traeEnabled: z.boolean().default(true).description('启用 Trae。关闭后该设置页、模型列表与 provider 路由都会移除;两个区域的登录态与模型选择会保留。').volatile(),
58
+ traeRegion: z.string().default('cn').description('Trae 登录区域:cn(国内版 trae.cn)或 global(国际版 trae.ai)。两个区域各自保存账号,切换后立即恢复对应账号。').volatile(),
59
+ codeartsEnabled: z.boolean().default(true).description('启用华为云 CodeArts。关闭后该设置页、模型列表与 provider 路由都会移除;登录态与模型选择会保留。').volatile(),
60
+
61
+ loomyDisplayName: z.string().default('Loomy').description('模型选择器里 Loomy 分组的名称。').volatile(),
62
+ raccoonDisplayName: z.string().default('商汤小浣熊').description('模型选择器里小浣熊分组的名称。').volatile(),
63
+ traeDisplayName: z.string().default('Trae').description('模型选择器里 Trae 分组的名称。').volatile(),
64
+ codeartsDisplayName: z.string().default('华为云 CodeArts').description('模型选择器里 CodeArts 分组的名称。').volatile(),
65
+
66
+ loomyDisabledModels: z.array(z.string()).default([]).description('Loomy 已关闭的模型 ID 列表。关闭的模型不会出现在模型选择器里。').volatile(),
67
+ raccoonDisabledModels: z.array(z.string()).default([]).description('小浣熊已关闭的模型 ID 列表。').volatile(),
68
+ traeDisabledModels: z.array(z.string()).default([]).description('Trae 已关闭的模型 ID 列表。').volatile(),
69
+ codeartsDisabledModels: z.array(z.string()).default([]).description('CodeArts 已关闭的模型 ID 列表。').volatile(),
70
+ loomyModelContextWindows: z.dict(z.number()).default({}).description('Loomy 按模型指定上下文窗口(token 数)。未指定的模型使用上游声明的窗口。').volatile(),
71
+ raccoonModelContextWindows: z.dict(z.number()).default({}).description('小浣熊按模型指定上下文窗口(token 数)。').volatile(),
72
+ traeModelContextWindows: z.dict(z.number()).default({}).description('Trae 按模型指定上下文窗口(token 数)。').volatile(),
73
+ codeartsModelContextWindows: z.dict(z.number()).default({}).description('CodeArts 按模型指定上下文窗口(token 数)。上游不下发窗口,默认使用 IDE 配置里的实测值。').volatile(),
74
+ loomyModelAliases: z.dict(z.string()).default({}).description('Loomy 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
75
+ raccoonModelAliases: z.dict(z.string()).default({}).description('小浣熊模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
76
+ traeModelAliases: z.dict(z.string()).default({}).description('Trae 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
77
+ codeartsModelAliases: z.dict(z.string()).default({}).description('CodeArts 模型别名映射:DSH 侧名称 → 上游模型 ID。').volatile(),
78
+
79
+ loomyVisionModelIds: z.array(z.string()).default([]).description('Loomy 强制按“能读图”声明的模型 ID,优先级高于目录标签。').volatile(),
80
+ loomyTextOnlyModelIds: z.array(z.string()).default([]).description('Loomy 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
81
+ raccoonVisionModelIds: z.array(z.string()).default([]).description('小浣熊强制按“能读图”声明的模型 ID。用于纠正上游标签错误。').volatile(),
82
+ raccoonTextOnlyModelIds: z.array(z.string()).default([]).description('小浣熊强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
83
+ traeVisionModelIds: z.array(z.string()).default([]).description('Trae 强制按“能读图”声明的模型 ID。目录标注 multimodal 的模型会自动启用。').volatile(),
84
+ traeTextOnlyModelIds: z.array(z.string()).default([]).description('Trae 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
85
+ codeartsVisionModelIds: z.array(z.string()).default([]).description('CodeArts 的“强制读图”列表。所有模型都能接收图片(文本模型先由 VL 模型转述,设置页只给原生多模态模型标“读图”);要关掉某个模型的读图能力请用下面的“纯文本”列表。').volatile(),
86
+ codeartsTextOnlyModelIds: z.array(z.string()).default([]).description('CodeArts 强制按“纯文本”声明的模型 ID,优先级最高。').volatile(),
87
+
88
+ defaultMaxTokens: z.number().step(1).min(1).default(32_000).description('模型目录未声明输出上限时使用的输出上限。').volatile(),
89
+ modelCacheTtlSeconds: z.number().step(1).min(10).max(86_400).default(120).description('模型列表缓存时长(秒)。调小可更快看到账号侧变化。').volatile(),
90
+ catalogFetchTimeoutSeconds: z.number().step(1).min(1).max(300).default(20).description('读取模型目录、额度等请求的时限(秒)。').volatile(),
91
+ openRouterSyncMinutes: z.number().step(1).min(0).max(43_200).default(60).description('OpenRouter 模型元数据的自动同步间隔(分钟)。0 表示关闭自动同步;缓存保存在本机,连不上时继续使用上次成功同步的数据。').volatile(),
92
+ })
93
+
94
+ export interface ResolvedConfig {
95
+ loomyEnabled: boolean
96
+ raccoonEnabled: boolean
97
+ traeEnabled: boolean
98
+ traeRegion: TraeRegion
99
+ codeartsEnabled: boolean
100
+ loomyDisplayName: string
101
+ raccoonDisplayName: string
102
+ traeDisplayName: string
103
+ codeartsDisplayName: string
104
+ loomyDisabledModels: readonly string[]
105
+ raccoonDisabledModels: readonly string[]
106
+ traeDisabledModels: readonly string[]
107
+ codeartsDisabledModels: readonly string[]
108
+ loomyModelContextWindows: Readonly<Record<string, number>>
109
+ raccoonModelContextWindows: Readonly<Record<string, number>>
110
+ traeModelContextWindows: Readonly<Record<string, number>>
111
+ codeartsModelContextWindows: Readonly<Record<string, number>>
112
+ loomyModelAliases: Readonly<Record<string, string>>
113
+ raccoonModelAliases: Readonly<Record<string, string>>
114
+ traeModelAliases: Readonly<Record<string, string>>
115
+ codeartsModelAliases: Readonly<Record<string, string>>
116
+ loomyVisionModelIds: readonly string[]
117
+ loomyTextOnlyModelIds: readonly string[]
118
+ raccoonVisionModelIds: readonly string[]
119
+ raccoonTextOnlyModelIds: readonly string[]
120
+ traeVisionModelIds: readonly string[]
121
+ traeTextOnlyModelIds: readonly string[]
122
+ codeartsVisionModelIds: readonly string[]
123
+ codeartsTextOnlyModelIds: readonly string[]
124
+ defaultMaxTokens: number
125
+ modelCacheTtlMs: number
126
+ catalogFetchTimeoutMs: number
127
+ openRouterSyncMinutes: number
128
+ }
129
+
130
+ export type ParsedConfig = { readonly [K in keyof Config]?: unknown }
131
+ export type LiveConfig = { readonly [K in keyof ResolvedConfig]: () => ResolvedConfig[K] }
132
+
133
+ export function liveConfig(config: ParsedConfig | undefined): LiveConfig {
134
+ const source = (config ?? {}) as Record<string, unknown>
135
+ const field = (key: keyof Config): unknown => {
136
+ const value = source[key]
137
+ return isVolatile(value) ? value.get() : value
138
+ }
139
+
140
+ return {
141
+ loomyEnabled: () => bool(field('loomyEnabled'), true),
142
+ raccoonEnabled: () => bool(field('raccoonEnabled'), true),
143
+ traeEnabled: () => bool(field('traeEnabled'), true),
144
+ traeRegion: () => normalizeTraeRegion(field('traeRegion')),
145
+ codeartsEnabled: () => bool(field('codeartsEnabled'), true),
146
+ loomyDisplayName: () => nonEmpty(field('loomyDisplayName'), 'Loomy'),
147
+ raccoonDisplayName: () => nonEmpty(field('raccoonDisplayName'), '商汤小浣熊'),
148
+ traeDisplayName: () => nonEmpty(field('traeDisplayName'), 'Trae'),
149
+ codeartsDisplayName: () => nonEmpty(field('codeartsDisplayName'), '华为云 CodeArts'),
150
+ loomyDisabledModels: () => stringList(field('loomyDisabledModels')),
151
+ raccoonDisabledModels: () => stringList(field('raccoonDisabledModels')),
152
+ traeDisabledModels: () => stringList(field('traeDisabledModels')),
153
+ codeartsDisabledModels: () => stringList(field('codeartsDisabledModels')),
154
+ loomyModelContextWindows: () => numberMap(field('loomyModelContextWindows')),
155
+ raccoonModelContextWindows: () => numberMap(field('raccoonModelContextWindows')),
156
+ traeModelContextWindows: () => numberMap(field('traeModelContextWindows')),
157
+ codeartsModelContextWindows: () => numberMap(field('codeartsModelContextWindows')),
158
+ loomyModelAliases: () => stringMap(field('loomyModelAliases')),
159
+ raccoonModelAliases: () => stringMap(field('raccoonModelAliases')),
160
+ traeModelAliases: () => stringMap(field('traeModelAliases')),
161
+ codeartsModelAliases: () => stringMap(field('codeartsModelAliases')),
162
+ loomyVisionModelIds: () => stringList(field('loomyVisionModelIds')),
163
+ loomyTextOnlyModelIds: () => stringList(field('loomyTextOnlyModelIds')),
164
+ raccoonVisionModelIds: () => stringList(field('raccoonVisionModelIds')),
165
+ raccoonTextOnlyModelIds: () => stringList(field('raccoonTextOnlyModelIds')),
166
+ traeVisionModelIds: () => stringList(field('traeVisionModelIds')),
167
+ traeTextOnlyModelIds: () => stringList(field('traeTextOnlyModelIds')),
168
+ codeartsVisionModelIds: () => stringList(field('codeartsVisionModelIds')),
169
+ codeartsTextOnlyModelIds: () => stringList(field('codeartsTextOnlyModelIds')),
170
+ defaultMaxTokens: () => positive(field('defaultMaxTokens'), 32_000),
171
+ modelCacheTtlMs: () => positive(field('modelCacheTtlSeconds'), 120) * 1_000,
172
+ catalogFetchTimeoutMs: () => positive(field('catalogFetchTimeoutSeconds'), 20) * 1_000,
173
+ openRouterSyncMinutes: () => nonNegative(field('openRouterSyncMinutes'), 60),
174
+ }
175
+ }
176
+
177
+ export function resolveConfig(config: ParsedConfig | undefined): ResolvedConfig {
178
+ const live = liveConfig(config)
179
+ return {
180
+ loomyEnabled: live.loomyEnabled(),
181
+ raccoonEnabled: live.raccoonEnabled(),
182
+ traeEnabled: live.traeEnabled(),
183
+ traeRegion: live.traeRegion(),
184
+ codeartsEnabled: live.codeartsEnabled(),
185
+ loomyDisplayName: live.loomyDisplayName(),
186
+ raccoonDisplayName: live.raccoonDisplayName(),
187
+ traeDisplayName: live.traeDisplayName(),
188
+ codeartsDisplayName: live.codeartsDisplayName(),
189
+ loomyDisabledModels: live.loomyDisabledModels(),
190
+ raccoonDisabledModels: live.raccoonDisabledModels(),
191
+ traeDisabledModels: live.traeDisabledModels(),
192
+ codeartsDisabledModels: live.codeartsDisabledModels(),
193
+ loomyModelContextWindows: live.loomyModelContextWindows(),
194
+ raccoonModelContextWindows: live.raccoonModelContextWindows(),
195
+ traeModelContextWindows: live.traeModelContextWindows(),
196
+ codeartsModelContextWindows: live.codeartsModelContextWindows(),
197
+ loomyModelAliases: live.loomyModelAliases(),
198
+ raccoonModelAliases: live.raccoonModelAliases(),
199
+ traeModelAliases: live.traeModelAliases(),
200
+ codeartsModelAliases: live.codeartsModelAliases(),
201
+ loomyVisionModelIds: live.loomyVisionModelIds(),
202
+ loomyTextOnlyModelIds: live.loomyTextOnlyModelIds(),
203
+ raccoonVisionModelIds: live.raccoonVisionModelIds(),
204
+ raccoonTextOnlyModelIds: live.raccoonTextOnlyModelIds(),
205
+ traeVisionModelIds: live.traeVisionModelIds(),
206
+ traeTextOnlyModelIds: live.traeTextOnlyModelIds(),
207
+ codeartsVisionModelIds: live.codeartsVisionModelIds(),
208
+ codeartsTextOnlyModelIds: live.codeartsTextOnlyModelIds(),
209
+ defaultMaxTokens: live.defaultMaxTokens(),
210
+ modelCacheTtlMs: live.modelCacheTtlMs(),
211
+ catalogFetchTimeoutMs: live.catalogFetchTimeoutMs(),
212
+ openRouterSyncMinutes: live.openRouterSyncMinutes(),
213
+ }
214
+ }
215
+
216
+ function text(value: unknown): string {
217
+ return typeof value === 'string' ? value.trim() : ''
218
+ }
219
+
220
+ function nonEmpty(value: unknown, fallback: string): string {
221
+ const valueText = text(value)
222
+ return valueText.length > 0 ? valueText : fallback
223
+ }
224
+
225
+ function positive(value: unknown, fallback: number): number {
226
+ return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback
227
+ }
228
+
229
+ function nonNegative(value: unknown, fallback: number): number {
230
+ return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback
231
+ }
232
+
233
+ function bool(value: unknown, fallback: boolean): boolean {
234
+ return typeof value === 'boolean' ? value : fallback
235
+ }
236
+
237
+ function stringList(value: unknown): readonly string[] {
238
+ if (!Array.isArray(value)) return []
239
+ return value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0).map(entry => entry.trim())
240
+ }
241
+
242
+ function numberMap(value: unknown): Readonly<Record<string, number>> {
243
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
244
+ const out: Record<string, number> = {}
245
+ for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
246
+ if (typeof entry === 'number' && Number.isFinite(entry) && entry > 0) out[key] = entry
247
+ }
248
+ return out
249
+ }
250
+
251
+ function stringMap(value: unknown): Readonly<Record<string, string>> {
252
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) return {}
253
+ const out: Record<string, string> = {}
254
+ for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {
255
+ if (typeof entry === 'string' && entry.trim().length > 0) out[key] = entry.trim()
256
+ }
257
+ return out
258
+ }
package/src/index.ts CHANGED
@@ -104,8 +104,8 @@ export {
104
104
  RaccoonProvider, RACCOON_BASE, RACCOON_CLIENT_VERSION, raccoonAccessTokenValid, raccoonRefreshCandidate,
105
105
  } from './raccoon.ts'
106
106
  export {
107
- TraeProvider, TRAE_CHAT_FUNCTIONS, TRAE_DIRECTORY_FUNCTIONS, mergeTraeModels,
108
- normalizeEffort, openTraeStream, parseTraeGlobalUsage, parseTraeRemoteModels,
107
+ TraeProvider, TRAE_CHAT_FUNCTIONS, TRAE_CHECKIN_REQ_SOURCE, TRAE_DIRECTORY_FUNCTIONS, mergeTraeModels,
108
+ normalizeEffort, openTraeStream, parseTraeCheckin, parseTraeGlobalUsage, parseTraeRemoteModels,
109
109
  parseTraeUsage, parseTraeWireModels, traeMessages, traeTools,
110
110
  } from './trae.ts'
111
111
  export type {
@@ -113,6 +113,8 @@ export type {
113
113
  } from './trae.ts'
114
114
  export { TRAE_APP_ID, TRAE_REGION_PROFILES, TRAE_SOLO_CHAT_PATH, TRAE_WEB_USER_AGENT } from './trae-endpoints.ts'
115
115
  export type { TraeRegionProfile } from './trae-endpoints.ts'
116
+ export { parseTraeClientStorage, readTraeClientLogin, traeClientDataDirs } from './trae-client.ts'
117
+ export type { TraeClientDetection, TraeClientLoginState, TraeClientReadOptions } from './trae-client.ts'
116
118
  export { TraeWebLogin, machineIdOf } from './trae-login.ts'
117
119
  export type { TraeLoginAccount, TraeWebLoginOptions } from './trae-login.ts'
118
120
  export {
@@ -130,20 +132,17 @@ export {
130
132
  export { headersForFetch, signRequestHuawei } from './codearts-sign.ts'
131
133
  export {
132
134
  RefreshTokenExpiredError, buildAuthorizeUrl, exchangeAuthorizationCode, exchangeRefreshToken,
133
- generateDpopKeyPair, generatePkcePair, readJwtExpiry, signDpopJws,
135
+ generateDpopKeyPair, generatePkcePair, otSessionIdOf, readJwtExpiry, signDpopJws,
134
136
  } from './codearts-oauth.ts'
135
137
  export type { CodeArtsCredential, DpopKeyPair, DpopPrivateJwk, PkcePair } from './codearts-oauth.ts'
136
138
  export { CodeArtsLogin } from './codearts-login.ts'
137
139
  export type { CodeArtsLoginAccount, CodeArtsLoginOptions } from './codearts-login.ts'
138
- export {
139
- CODEARTS_SESSION_LIMIT, CODEARTS_SESSION_WINDOW_MS, CodeArtsSessionWindow, sharedSessionWindow,
140
- } from './codearts-window.ts'
141
140
  export { ProviderHttpError, buildMessages, buildTools, consumeOpenAiStream, openAiStream, sseEvents } from './wire.ts'
142
141
  export type { WireMessage } from './wire.ts'
143
142
  export { API_PREFIX, buildRows } from './api.ts'
144
143
  export { liveConfig, resolveConfig } from './config.ts'
145
144
  export type { Config as ConfigShape, LiveConfig, ParsedConfig, ResolvedConfig } from './config.ts'
146
- export type { CheckinStatus, ProviderStatus, QuotaPack, QuotaSnapshot } from './types.ts'
145
+ export type { CheckinStatus, ClientLoginStatus, ProviderStatus, QuotaPack, QuotaSnapshot } from './types.ts'
147
146
 
148
147
  export const name = 'llm-adapters'
149
148
  export const inject = ['llm']
@@ -320,28 +319,6 @@ export function apply(ctx: Context, config: ParsedConfig): void {
320
319
  syncOpenRouter()
321
320
  ctx.effect(() => () => clearInterval(syncTimer), 'llm-adapters: openrouter sync')
322
321
 
323
- // Raccoon has no claim endpoint; its daily points appear on the first web
324
- // visit. Poll the bills on a live interval and try the website call only when
325
- // today's grant is missing. 0 turns the monitor off; the manual web link the
326
- // card always shows is unaffected.
327
- let lastRaccoonCheckin = Date.now()
328
- const checkinMonitor = (): void => {
329
- if (!live.raccoonEnabled()) return
330
- const minutes = live.raccoonCheckinMinutes()
331
- if (minutes <= 0) return
332
- if (Date.now() - lastRaccoonCheckin < minutes * 60_000) return
333
- lastRaccoonCheckin = Date.now()
334
- void (async () => {
335
- if (!(await raccoonProvider.signedIn())) return
336
- await raccoonProvider.ensureDailyCheckin()
337
- })().catch(error => {
338
- ctx.logger?.warn(`llm-adapters: 小浣熊签到监测失败:${error instanceof Error ? error.message : String(error)}`)
339
- })
340
- }
341
- const checkinTimer = setInterval(checkinMonitor, 60_000)
342
- checkinTimer.unref?.()
343
- ctx.effect(() => () => clearInterval(checkinTimer), 'llm-adapters: raccoon check-in monitor')
344
-
345
322
  ctx.inject(['attachments'], (scope) => {
346
323
  attachments = scope.attachments
347
324
  scope.effect(() => () => { attachments = undefined }, 'llm-adapters.attachments')