@goodandready/dsh-clinebot 0.5.32 → 0.6.0
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/CHANGELOG.md +31 -0
- package/README.md +63 -36
- package/README.ru.md +3 -1
- package/README.zh.md +3 -1
- package/lib/adapter-messages.js +237 -0
- package/lib/adapter-stream.js +292 -0
- package/lib/adapter.js +616 -0
- package/lib/client.js +2564 -924
- package/lib/cline-client.js +15 -6
- package/lib/config.js +266 -233
- package/lib/credential-refs.js +104 -84
- package/lib/index.js +569 -502
- package/lib/model-prices.js +117 -0
- package/lib/provider-sync.js +404 -387
- package/lib/proxy-token.js +187 -140
- package/lib/routes/accounts.js +523 -403
- package/lib/routes/models.js +2 -1
- package/lib/routes/proxy.js +8 -27
- package/lib/routes/settings.js +63 -2
- package/lib/services.js +55 -0
- package/lib/stats-storage.js +364 -166
- package/lib/transport/cline-transport.js +314 -0
- package/lib/tui-settings.js +158 -0
- package/lib/updater.js +5 -1
- package/package.json +113 -77
package/lib/cline-client.js
CHANGED
|
@@ -225,24 +225,33 @@ export async function fetchUsageLimits(baseUrl, apiKey, {
|
|
|
225
225
|
const limitsData = await limitsRes.json().catch(() => ({}))
|
|
226
226
|
const rawLimits = limitsData?.data?.limits || limitsData?.limits || []
|
|
227
227
|
|
|
228
|
-
const
|
|
229
|
-
|
|
228
|
+
const normalizeLimitType = (value) => String(value || '')
|
|
229
|
+
.trim()
|
|
230
|
+
.toLowerCase()
|
|
231
|
+
.replace(/_/g, '-')
|
|
232
|
+
.replace(/\s+/g, '-')
|
|
233
|
+
|
|
234
|
+
const parseWindow = (types) => {
|
|
235
|
+
const aliases = Array.isArray(types) ? types : [types]
|
|
236
|
+
const found = Array.isArray(rawLimits)
|
|
237
|
+
? rawLimits.find((limit) => aliases.includes(normalizeLimitType(limit?.type || limit?.window || limit?.name)))
|
|
238
|
+
: null
|
|
230
239
|
if (!found) return null
|
|
231
240
|
const percentUsed = typeof found.percentUsed === 'number'
|
|
232
241
|
? Math.max(0, Math.min(100, Math.round(found.percentUsed * 10) / 10))
|
|
233
242
|
: 0
|
|
234
243
|
const remainingPercent = Math.max(0, Math.round((100 - percentUsed) * 10) / 10)
|
|
235
244
|
return {
|
|
236
|
-
type,
|
|
245
|
+
type: normalizeLimitType(found.type || found.window || found.name),
|
|
237
246
|
percentUsed,
|
|
238
247
|
remainingPercent,
|
|
239
248
|
resetsAt: found.resetsAt || null,
|
|
240
249
|
}
|
|
241
250
|
}
|
|
242
251
|
|
|
243
|
-
const fiveHour = parseWindow('5-hour')
|
|
244
|
-
const weekly = parseWindow('weekly')
|
|
245
|
-
const monthly = parseWindow('monthly')
|
|
252
|
+
const fiveHour = parseWindow(['5-hour', '5h', 'five-hour', 'fivehour', 'rolling', 'session'])
|
|
253
|
+
const weekly = parseWindow(['weekly', 'week', '7-day', '7day'])
|
|
254
|
+
const monthly = parseWindow(['monthly', 'month', '30-day', '30day'])
|
|
246
255
|
|
|
247
256
|
let userEmail = null
|
|
248
257
|
let createdAt = null
|
package/lib/config.js
CHANGED
|
@@ -1,233 +1,266 @@
|
|
|
1
|
-
import z from '@deepseek-ai/schemastery'
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_BASE_URL,
|
|
4
|
-
DEFAULT_API_KEY_ENV,
|
|
5
|
-
DEFAULT_TIMEOUT_MS,
|
|
6
|
-
DEFAULT_SMOKE_TIMEOUT_MS,
|
|
7
|
-
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
8
|
-
normalizeBaseUrl,
|
|
9
|
-
} from './cline-client.js'
|
|
10
|
-
import {
|
|
11
|
-
DEFAULT_MODEL_ID,
|
|
12
|
-
getAllModels,
|
|
13
|
-
getActiveModelIds,
|
|
14
|
-
migrateModelId,
|
|
15
|
-
} from './models.js'
|
|
16
|
-
|
|
17
|
-
// DSH settings.describe only publishes fields marked volatile. Without them the
|
|
18
|
-
// namespace is omitted and the configuration page stays unavailable.
|
|
19
|
-
if (typeof z.prototype?.volatile !== 'function') {
|
|
20
|
-
z.prototype.volatile = function volatile() {
|
|
21
|
-
if (this.meta && this.meta.volatile) throw new TypeError('volatile schema is already wrapped')
|
|
22
|
-
return typeof this.extra === 'function' ? this.extra('volatile', true) : this
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export const NS = 'dsh-clinebot'
|
|
27
|
-
export const LLM_PI_AI_NS = 'llm-pi-ai'
|
|
28
|
-
|
|
29
|
-
export const Config = z.object({
|
|
30
|
-
enabled: z.boolean().default(true)
|
|
31
|
-
.description('When true, ClineBot is registered as a model provider in DSH.').volatile(),
|
|
32
|
-
|
|
33
|
-
.description('
|
|
34
|
-
|
|
35
|
-
.description('
|
|
36
|
-
|
|
37
|
-
.description('
|
|
38
|
-
|
|
39
|
-
.description('
|
|
40
|
-
|
|
41
|
-
.description('
|
|
42
|
-
|
|
43
|
-
.description('
|
|
44
|
-
|
|
45
|
-
.description('
|
|
46
|
-
|
|
47
|
-
.description('
|
|
48
|
-
|
|
49
|
-
.description('
|
|
50
|
-
|
|
51
|
-
.description('
|
|
52
|
-
|
|
53
|
-
.description('
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
})).default([])
|
|
86
|
-
.description('
|
|
87
|
-
|
|
88
|
-
.description('
|
|
89
|
-
|
|
90
|
-
.description('
|
|
91
|
-
|
|
92
|
-
.description('
|
|
93
|
-
|
|
94
|
-
.description('
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
})).default([])
|
|
103
|
-
.description('
|
|
104
|
-
|
|
105
|
-
.description('
|
|
106
|
-
|
|
107
|
-
.description('
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
let
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
1
|
+
import z from '@deepseek-ai/schemastery'
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_BASE_URL,
|
|
4
|
+
DEFAULT_API_KEY_ENV,
|
|
5
|
+
DEFAULT_TIMEOUT_MS,
|
|
6
|
+
DEFAULT_SMOKE_TIMEOUT_MS,
|
|
7
|
+
DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
8
|
+
normalizeBaseUrl,
|
|
9
|
+
} from './cline-client.js'
|
|
10
|
+
import {
|
|
11
|
+
DEFAULT_MODEL_ID,
|
|
12
|
+
getAllModels,
|
|
13
|
+
getActiveModelIds,
|
|
14
|
+
migrateModelId,
|
|
15
|
+
} from './models.js'
|
|
16
|
+
|
|
17
|
+
// DSH settings.describe only publishes fields marked volatile. Without them the
|
|
18
|
+
// namespace is omitted and the configuration page stays unavailable.
|
|
19
|
+
if (typeof z.prototype?.volatile !== 'function') {
|
|
20
|
+
z.prototype.volatile = function volatile() {
|
|
21
|
+
if (this.meta && this.meta.volatile) throw new TypeError('volatile schema is already wrapped')
|
|
22
|
+
return typeof this.extra === 'function' ? this.extra('volatile', true) : this
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const NS = 'dsh-clinebot'
|
|
27
|
+
export const LLM_PI_AI_NS = 'llm-pi-ai'
|
|
28
|
+
|
|
29
|
+
export const Config = z.object({
|
|
30
|
+
enabled: z.boolean().default(true)
|
|
31
|
+
.description('When true, ClineBot is registered as a model provider in DSH.').volatile(),
|
|
32
|
+
transport: z.union(['adapter', 'pi-ai']).default('adapter')
|
|
33
|
+
.description('Provider transport: native ctx.llm adapter (recommended) or legacy llm-pi-ai + loopback proxy.').volatile(),
|
|
34
|
+
baseUrl: z.string().default(DEFAULT_BASE_URL)
|
|
35
|
+
.description('Base API URL (default: https://api.cline.bot/api/v1).').volatile(),
|
|
36
|
+
apiKeyEnv: z.string().default(DEFAULT_API_KEY_ENV)
|
|
37
|
+
.description('Credential / env name containing the ClinePass API key (never store key directly here).').volatile(),
|
|
38
|
+
defaultModel: z.string().default(DEFAULT_MODEL_ID)
|
|
39
|
+
.description('Default model ID for chat and smoke tests.').volatile(),
|
|
40
|
+
disabledModels: z.array(z.string()).default([])
|
|
41
|
+
.description('List of model IDs explicitly disabled by the user (new models are enabled automatically).').volatile(),
|
|
42
|
+
filterModelsByPlan: z.boolean().default(true)
|
|
43
|
+
.description('Automatically hide models not included in active subscription plan from chat picker.').volatile(),
|
|
44
|
+
visibleModels: z.array(z.string()).default([])
|
|
45
|
+
.description('User-selected visible models in chat model picker (empty means all supported models).').volatile(),
|
|
46
|
+
showSidebarQuota: z.boolean().default(true)
|
|
47
|
+
.description('Display compact quota meter in DSH sidebar footer.').volatile(),
|
|
48
|
+
showComposerCost: z.boolean().default(true)
|
|
49
|
+
.description('Display the estimated session cost widget in the chat composer.').volatile(),
|
|
50
|
+
webSearch: z.boolean().default(true)
|
|
51
|
+
.description('Enable native DSH web search provider via ClineBot.').volatile(),
|
|
52
|
+
offloadSeenImages: z.boolean().default(true)
|
|
53
|
+
.description('Offload previously seen images in conversation history to optimize prompt cache.').volatile(),
|
|
54
|
+
maxPayloadMb: z.number().default(40)
|
|
55
|
+
.description('Maximum multimodal payload budget in MB before evicting oldest images.').volatile(),
|
|
56
|
+
priceOverrides: z.dict(z.object({
|
|
57
|
+
input: z.number().default(0),
|
|
58
|
+
output: z.number().default(0),
|
|
59
|
+
cacheRead: z.number().default(0),
|
|
60
|
+
cacheWrite: z.number().default(0),
|
|
61
|
+
})).default({})
|
|
62
|
+
.description('User-supplied USD per 1M token rates for local cost estimates.').volatile(),
|
|
63
|
+
enabledModels: z.array(z.string()).default([])
|
|
64
|
+
.description('Deprecated: preserved for backwards compatibility with earlier versions.'),
|
|
65
|
+
dynamicModels: z.array(z.object({
|
|
66
|
+
id: z.string(),
|
|
67
|
+
name: z.string(),
|
|
68
|
+
description: z.string().default(''),
|
|
69
|
+
contextLength: z.number().default(200000),
|
|
70
|
+
maxTokens: z.number().default(8192),
|
|
71
|
+
input: z.array(z.string()).default(['text']),
|
|
72
|
+
category: z.string().default('general'),
|
|
73
|
+
isCustom: z.boolean().default(false),
|
|
74
|
+
reasoningEfforts: z.any().default(undefined),
|
|
75
|
+
})).default([])
|
|
76
|
+
.description('Models automatically discovered from the official ClinePass subscription plan.'),
|
|
77
|
+
planSyncedAt: z.number().default(0)
|
|
78
|
+
.description('Timestamp when models were last synchronized with active subscription plan.'),
|
|
79
|
+
legacyMigratedAt: z.number().default(0)
|
|
80
|
+
.description('Internal timestamp of the one-time llm-pi-ai provider migration.'),
|
|
81
|
+
modelContextOverrides: z.array(z.object({
|
|
82
|
+
modelId: z.string(),
|
|
83
|
+
contextLength: z.number().default(200000),
|
|
84
|
+
maxTokens: z.number().default(8192),
|
|
85
|
+
})).default([])
|
|
86
|
+
.description('Custom user-defined model context length and max token overrides.').volatile(),
|
|
87
|
+
contextWindow: z.number().min(0).max(10000000).default(0)
|
|
88
|
+
.description('Global custom context window in tokens (0 follows the catalog; per-model overrides win).').volatile(),
|
|
89
|
+
maxOutputTokens: z.number().min(-1).max(2000000).default(-1)
|
|
90
|
+
.description('Per-turn output cap: -1 derives from the model, 0 omits max_tokens, a positive value tightens the cap.').volatile(),
|
|
91
|
+
modelReasoningDefaults: z.dict(z.string()).default({})
|
|
92
|
+
.description('Configured default reasoning effort per model (low, medium, high, max).').volatile(),
|
|
93
|
+
modelRoutes: z.dict(z.string()).default({})
|
|
94
|
+
.description('Per-model target account routing map (e.g. { "deepseek-v4-pro": "KEY_2" }).').volatile(),
|
|
95
|
+
customModels: z.array(z.object({
|
|
96
|
+
id: z.string(),
|
|
97
|
+
name: z.string(),
|
|
98
|
+
contextLength: z.number().default(128000),
|
|
99
|
+
maxTokens: z.number().default(8192),
|
|
100
|
+
category: z.string().default('general'),
|
|
101
|
+
isReasoning: z.boolean().default(false),
|
|
102
|
+
})).default([])
|
|
103
|
+
.description('User-defined custom gateway models.').volatile(),
|
|
104
|
+
proxyMode: z.boolean().default(true)
|
|
105
|
+
.description('When true, registers local transparent loopback proxy for failover and token tracking.').volatile(),
|
|
106
|
+
statsPath: z.string().default('~/.dsh/clinebot-stats.json')
|
|
107
|
+
.description('Local on-disk persistent path for token usage analytics.').volatile(),
|
|
108
|
+
timeoutMs: z.number().default(DEFAULT_TIMEOUT_MS)
|
|
109
|
+
.description('HTTP probe timeout in milliseconds.').volatile(),
|
|
110
|
+
smokeTimeoutMs: z.number().default(DEFAULT_SMOKE_TIMEOUT_MS)
|
|
111
|
+
.description('Timeout for smoke chat completions in milliseconds.').volatile(),
|
|
112
|
+
streamIdleTimeoutMs: z.number().default(DEFAULT_STREAM_IDLE_TIMEOUT_MS)
|
|
113
|
+
.description('Idle timeout between SSE stream chunks in milliseconds.').volatile(),
|
|
114
|
+
modelsCachePath: z.string().default('~/.dsh/clinebot-models-cache.json')
|
|
115
|
+
.description('Local on-disk cache path for models snapshot.').volatile(),
|
|
116
|
+
accounts: z.array(z.object({
|
|
117
|
+
label: z.string().default(''),
|
|
118
|
+
apiKeyEnv: z.string(),
|
|
119
|
+
})).default([])
|
|
120
|
+
.description('Additional accounts for multi-account failover and rate limit rotation.').volatile(),
|
|
121
|
+
activeAccount: z.string().default('')
|
|
122
|
+
.description('Manually pinned active account envName or empty for auto/default.').volatile(),
|
|
123
|
+
accountMode: z.union(['least_used', 'round_robin']).default('least_used')
|
|
124
|
+
.description('Account selection strategy: least_used (quota-aware) or round_robin (sequential rotation).').volatile(),
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
export function isVolatileRef(value) {
|
|
128
|
+
return !!value && typeof value === 'object' && !Array.isArray(value) && typeof value.get === 'function'
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// DSH stores each volatile field as { get } or getter functions. structuredClone
|
|
132
|
+
// cannot copy functions or getters, so callers need a plain snapshot before validation.
|
|
133
|
+
export function plainConfig(cfg) {
|
|
134
|
+
while (typeof cfg === 'function') {
|
|
135
|
+
cfg = cfg()
|
|
136
|
+
}
|
|
137
|
+
if (isVolatileRef(cfg)) return plainConfig(cfg.get())
|
|
138
|
+
if (!cfg || typeof cfg !== 'object') return cfg
|
|
139
|
+
if (Array.isArray(cfg)) {
|
|
140
|
+
return cfg.map((item) => plainConfig(item))
|
|
141
|
+
}
|
|
142
|
+
const out = {}
|
|
143
|
+
for (const key of Object.keys(cfg)) {
|
|
144
|
+
out[key] = plainConfig(cfg[key])
|
|
145
|
+
}
|
|
146
|
+
return out
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function volatileConfig(cfg) {
|
|
150
|
+
const plain = plainConfig(cfg)
|
|
151
|
+
if (!plain || typeof plain !== 'object') return plain
|
|
152
|
+
const out = {}
|
|
153
|
+
for (const [key, field] of Object.entries(Config.dict || {})) {
|
|
154
|
+
if (field?.meta?.volatile && Object.hasOwn(plain, key)) {
|
|
155
|
+
out[key] = plain[key]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return out
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function publicConfig(cfg) {
|
|
162
|
+
cfg = plainConfig(cfg)
|
|
163
|
+
const dynamic = Array.isArray(cfg?.dynamicModels) ? cfg.dynamicModels : []
|
|
164
|
+
const custom = Array.isArray(cfg?.customModels) ? cfg.customModels : []
|
|
165
|
+
const modelContextOverrides = Array.isArray(cfg?.modelContextOverrides) ? cfg.modelContextOverrides : []
|
|
166
|
+
const contextWindow = Math.max(0, Number(cfg?.contextWindow) || 0)
|
|
167
|
+
// A global context override is the wildcard entry; explicit per-model rows
|
|
168
|
+
// and an explicitly stored `*` row keep precedence.
|
|
169
|
+
const effectiveOverrides = contextWindow > 0 && !modelContextOverrides.some((entry) => entry?.modelId === '*')
|
|
170
|
+
? [...modelContextOverrides, { modelId: '*', contextLength: contextWindow, maxTokens: 0 }]
|
|
171
|
+
: modelContextOverrides
|
|
172
|
+
const isSynced = dynamic.length > 0 && (Number(cfg?.planSyncedAt) > 0)
|
|
173
|
+
const allModels = getAllModels(dynamic, effectiveOverrides, custom)
|
|
174
|
+
const allDefaultIds = allModels.map((m) => m.id)
|
|
175
|
+
|
|
176
|
+
let disabledList = Array.isArray(cfg?.disabledModels) ? cfg.disabledModels.map(migrateModelId) : []
|
|
177
|
+
if (cfg?.disabledModels === undefined && Array.isArray(cfg?.enabledModels) && cfg.enabledModels.length > 0) {
|
|
178
|
+
const enabledSet = new Set(cfg.enabledModels.map(migrateModelId))
|
|
179
|
+
disabledList = allDefaultIds.filter((id) => !enabledSet.has(id))
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const filterModelsByPlan = cfg?.filterModelsByPlan !== false
|
|
183
|
+
const visibleModels = Array.isArray(cfg?.visibleModels) ? cfg.visibleModels.map(migrateModelId) : []
|
|
184
|
+
|
|
185
|
+
const planModelSet = new Set(dynamic.map((m) => m.id))
|
|
186
|
+
for (const m of allModels) {
|
|
187
|
+
if (m.inPlan === undefined) {
|
|
188
|
+
m.inPlan = isSynced ? (planModelSet.has(m.id) || planModelSet.has(migrateModelId(m.id)) || Boolean(m.isCustom)) : true
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let activeIds = getActiveModelIds(allDefaultIds, disabledList)
|
|
193
|
+
if (filterModelsByPlan && isSynced && planModelSet.size > 0) {
|
|
194
|
+
const inPlanIds = activeIds.filter((id) => {
|
|
195
|
+
const m = allModels.find((mod) => mod.id === id)
|
|
196
|
+
return m ? m.inPlan : true
|
|
197
|
+
})
|
|
198
|
+
if (inPlanIds.length > 0) {
|
|
199
|
+
activeIds = inPlanIds
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (visibleModels.length > 0) {
|
|
203
|
+
const visibleSet = new Set(visibleModels)
|
|
204
|
+
const filteredByVisible = activeIds.filter((id) => visibleSet.has(id))
|
|
205
|
+
if (filteredByVisible.length > 0) {
|
|
206
|
+
activeIds = filteredByVisible
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!activeIds.length && allDefaultIds.length) {
|
|
211
|
+
activeIds = [allDefaultIds[0]]
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
let defaultModel = migrateModelId(cfg?.defaultModel || DEFAULT_MODEL_ID)
|
|
215
|
+
let defaultModelWarning = null
|
|
216
|
+
|
|
217
|
+
if (allDefaultIds.length > 0 && !allDefaultIds.includes(defaultModel)) {
|
|
218
|
+
const fallbackModel = activeIds[0] || allDefaultIds[0]
|
|
219
|
+
defaultModelWarning = `Configured default model "${defaultModel}" is not available in your active plan. Using "${fallbackModel}" instead.`
|
|
220
|
+
defaultModel = fallbackModel
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const planSyncedAt = Number(cfg?.planSyncedAt) || (isSynced ? (dynamic.planSyncedAt || 0) : 0)
|
|
224
|
+
const rawMaxOutput = Number(cfg?.maxOutputTokens)
|
|
225
|
+
const maxOutputTokens = Number.isFinite(rawMaxOutput) ? (rawMaxOutput === -1 ? null : rawMaxOutput) : null
|
|
226
|
+
const priceOverrides = cfg?.priceOverrides && typeof cfg.priceOverrides === 'object' && !Array.isArray(cfg.priceOverrides)
|
|
227
|
+
? cfg.priceOverrides
|
|
228
|
+
: {}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
enabled: !!cfg?.enabled,
|
|
232
|
+
transport: cfg?.transport === 'pi-ai' ? 'pi-ai' : 'adapter',
|
|
233
|
+
baseUrl: normalizeBaseUrl(cfg?.baseUrl),
|
|
234
|
+
apiKeyEnv: cfg?.apiKeyEnv || DEFAULT_API_KEY_ENV,
|
|
235
|
+
defaultModel,
|
|
236
|
+
defaultModelWarning,
|
|
237
|
+
dynamicModels: dynamic,
|
|
238
|
+
planSynced: isSynced,
|
|
239
|
+
planSyncedAt,
|
|
240
|
+
modelContextOverrides: effectiveOverrides,
|
|
241
|
+
contextWindow,
|
|
242
|
+
maxOutputTokens,
|
|
243
|
+
priceOverrides,
|
|
244
|
+
modelReasoningDefaults: cfg?.modelReasoningDefaults && typeof cfg.modelReasoningDefaults === 'object' ? cfg.modelReasoningDefaults : {},
|
|
245
|
+
modelRoutes: cfg?.modelRoutes && typeof cfg.modelRoutes === 'object' ? cfg.modelRoutes : {},
|
|
246
|
+
customModels: custom,
|
|
247
|
+
proxyMode: cfg?.proxyMode !== false,
|
|
248
|
+
statsPath: String(cfg?.statsPath || '~/.dsh/clinebot-stats.json'),
|
|
249
|
+
disabledModels: disabledList,
|
|
250
|
+
filterModelsByPlan,
|
|
251
|
+
visibleModels,
|
|
252
|
+
showSidebarQuota: cfg?.showSidebarQuota !== false,
|
|
253
|
+
showComposerCost: cfg?.showComposerCost !== false,
|
|
254
|
+
webSearch: cfg?.webSearch !== false,
|
|
255
|
+
offloadSeenImages: cfg?.offloadSeenImages !== false,
|
|
256
|
+
maxPayloadMb: Number(cfg?.maxPayloadMb) || 40,
|
|
257
|
+
enabledModels: activeIds,
|
|
258
|
+
timeoutMs: Number(cfg?.timeoutMs) || DEFAULT_TIMEOUT_MS,
|
|
259
|
+
smokeTimeoutMs: Number(cfg?.smokeTimeoutMs) || DEFAULT_SMOKE_TIMEOUT_MS,
|
|
260
|
+
streamIdleTimeoutMs: Number(cfg?.streamIdleTimeoutMs) || DEFAULT_STREAM_IDLE_TIMEOUT_MS,
|
|
261
|
+
modelsCachePath: String(cfg?.modelsCachePath || '~/.dsh/clinebot-models-cache.json'),
|
|
262
|
+
accounts: Array.isArray(cfg?.accounts) ? cfg.accounts : [],
|
|
263
|
+
activeAccount: String(cfg?.activeAccount || ''),
|
|
264
|
+
accountMode: cfg?.accountMode === 'round_robin' ? 'round_robin' : 'least_used',
|
|
265
|
+
}
|
|
266
|
+
}
|