@goodandready/dsh-clinebot 0.5.20 → 0.5.22
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 +19 -0
- package/lib/client.js +269 -29
- package/lib/cline-client.js +21 -2
- package/lib/config.js +3 -0
- package/lib/models.js +83 -136
- package/lib/provider-sync.js +21 -0
- package/lib/proxy-token.js +22 -2
- package/lib/routes/models.js +70 -7
- package/lib/routes/proxy.js +15 -8
- package/lib/routes/settings.js +20 -1
- package/lib/session-router.js +49 -2
- package/lib/slash-command.js +19 -4
- package/lib/stats-storage.js +28 -0
- package/lib/telemetry.js +6 -1
- package/package.json +1 -1
package/lib/models.js
CHANGED
|
@@ -52,12 +52,26 @@ export function getOriginalModelContext(nameOrId) {
|
|
|
52
52
|
if (norm.includes('qwen')) return 1000000
|
|
53
53
|
if (norm.includes('minimax')) return 1000000
|
|
54
54
|
if (norm.includes('mimo')) return 1000000
|
|
55
|
+
if (norm.includes('glm-5.3') || norm.includes('glm-53')) return 1000000
|
|
56
|
+
if (norm.includes('deepseek-v4.1') || norm.includes('deepseek-v41')) return 1000000
|
|
55
57
|
if (norm.includes('deepseek')) return 128000
|
|
56
58
|
if (norm.includes('glm')) return 128000
|
|
57
59
|
return 200000
|
|
58
60
|
}
|
|
59
61
|
|
|
60
|
-
function
|
|
62
|
+
export function getOriginalModelMaxTokens(nameOrId) {
|
|
63
|
+
const norm = String(nameOrId || '').toLowerCase()
|
|
64
|
+
if (norm.includes('qwen')) return 131072
|
|
65
|
+
if (norm.includes('glm')) return 128000
|
|
66
|
+
if (norm.includes('minimax')) return 128000
|
|
67
|
+
if (norm.includes('kimi')) return 65536
|
|
68
|
+
if (norm.includes('deepseek-v4.1') || norm.includes('deepseek-v41')) return 65536
|
|
69
|
+
if (norm.includes('deepseek')) return 16384
|
|
70
|
+
if (norm.includes('mimo')) return 16384
|
|
71
|
+
return 16384
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getOriginalModelProvider(nameOrId) {
|
|
61
75
|
const norm = String(nameOrId || '').toLowerCase()
|
|
62
76
|
if (norm.includes('kimi')) return 'Moonshot AI'
|
|
63
77
|
if (norm.includes('qwen')) return 'Alibaba Cloud'
|
|
@@ -68,181 +82,102 @@ function getOriginalModelProvider(nameOrId) {
|
|
|
68
82
|
return 'Original Vendor'
|
|
69
83
|
}
|
|
70
84
|
|
|
85
|
+
function createCuratedModel({
|
|
86
|
+
id, name, description,
|
|
87
|
+
ctx = 128000, maxTokens = 8192,
|
|
88
|
+
origCtx, origMax, origProv,
|
|
89
|
+
input = ['text'], category = 'general',
|
|
90
|
+
recommended = false, reasoningEfforts,
|
|
91
|
+
}) {
|
|
92
|
+
const c = origCtx || getOriginalModelContext(id)
|
|
93
|
+
const p = origProv || getOriginalModelProvider(id)
|
|
94
|
+
const om = origMax || getOriginalModelMaxTokens(id)
|
|
95
|
+
return {
|
|
96
|
+
id, name, description,
|
|
97
|
+
clineContextLength: ctx, defaultContextLength: ctx, contextLength: ctx,
|
|
98
|
+
originalContextLength: c, originalProvider: p, originalMaxTokens: om,
|
|
99
|
+
maxTokens, defaultMaxTokens: maxTokens,
|
|
100
|
+
input, category, recommended, isCustom: false,
|
|
101
|
+
...(reasoningEfforts ? { reasoningEfforts } : {}),
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
71
105
|
export const CLINE_MODELS = Object.freeze([
|
|
72
|
-
{
|
|
106
|
+
createCuratedModel({
|
|
73
107
|
id: 'cline-pass/deepseek-v4-flash',
|
|
74
108
|
name: 'DeepSeek V4 Flash',
|
|
75
109
|
description: 'High-speed reasoning & code completion model optimized for agentic loops.',
|
|
76
|
-
|
|
77
|
-
defaultContextLength: 128000,
|
|
78
|
-
originalContextLength: 128000,
|
|
79
|
-
originalProvider: 'DeepSeek',
|
|
80
|
-
contextLength: 128000,
|
|
81
|
-
maxTokens: 8192,
|
|
82
|
-
input: ['text', 'image'],
|
|
83
|
-
category: 'coding',
|
|
84
|
-
recommended: true,
|
|
85
|
-
isCustom: false,
|
|
110
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'coding', recommended: true,
|
|
86
111
|
reasoningEfforts: ['low', 'medium', 'high'],
|
|
87
|
-
},
|
|
88
|
-
{
|
|
112
|
+
}),
|
|
113
|
+
createCuratedModel({
|
|
89
114
|
id: 'cline-pass/deepseek-v4-pro',
|
|
90
115
|
name: 'DeepSeek V4 Pro',
|
|
91
116
|
description: 'Flagship reasoning and multi-turn architectural coding model.',
|
|
92
|
-
|
|
93
|
-
defaultContextLength: 128000,
|
|
94
|
-
originalContextLength: 128000,
|
|
95
|
-
originalProvider: 'DeepSeek',
|
|
96
|
-
contextLength: 128000,
|
|
97
|
-
maxTokens: 8192,
|
|
98
|
-
input: ['text', 'image'],
|
|
99
|
-
category: 'coding',
|
|
100
|
-
recommended: true,
|
|
101
|
-
isCustom: false,
|
|
117
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'coding', recommended: true,
|
|
102
118
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
103
|
-
},
|
|
104
|
-
{
|
|
119
|
+
}),
|
|
120
|
+
createCuratedModel({
|
|
105
121
|
id: 'cline-pass/glm-5.2',
|
|
106
122
|
name: 'GLM 5.2',
|
|
107
123
|
description: 'Bilingual general & coding model with strong instruction following.',
|
|
108
|
-
|
|
109
|
-
defaultContextLength: 128000,
|
|
110
|
-
originalContextLength: 128000,
|
|
111
|
-
originalProvider: 'Zhipu AI',
|
|
112
|
-
contextLength: 128000,
|
|
113
|
-
maxTokens: 8192,
|
|
114
|
-
input: ['text'],
|
|
115
|
-
category: 'general',
|
|
116
|
-
recommended: false,
|
|
117
|
-
isCustom: false,
|
|
124
|
+
ctx: 128000, maxTokens: 8192, input: ['text'], category: 'general', recommended: false,
|
|
118
125
|
reasoningEfforts: ['low', 'medium', 'high'],
|
|
119
|
-
},
|
|
120
|
-
{
|
|
126
|
+
}),
|
|
127
|
+
createCuratedModel({
|
|
121
128
|
id: 'cline-pass/kimi-k3',
|
|
122
129
|
name: 'Kimi K3',
|
|
123
130
|
description: 'Long-context reasoning & document synthesis model by Moonshot AI.',
|
|
124
|
-
|
|
125
|
-
defaultContextLength: 200000,
|
|
126
|
-
originalContextLength: 2000000,
|
|
127
|
-
originalProvider: 'Moonshot AI',
|
|
128
|
-
contextLength: 200000,
|
|
129
|
-
maxTokens: 8192,
|
|
130
|
-
input: ['text'],
|
|
131
|
-
category: 'reasoning',
|
|
132
|
-
recommended: true,
|
|
133
|
-
isCustom: false,
|
|
131
|
+
ctx: 200000, maxTokens: 8192, input: ['text'], category: 'reasoning', recommended: true,
|
|
134
132
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
135
|
-
},
|
|
136
|
-
{
|
|
133
|
+
}),
|
|
134
|
+
createCuratedModel({
|
|
137
135
|
id: 'cline-pass/kimi-k2.7-code',
|
|
138
136
|
name: 'Kimi K2.7 Code',
|
|
139
137
|
description: 'Specialized coding model with agentic tool calling support.',
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
originalProvider: 'Moonshot AI',
|
|
144
|
-
contextLength: 200000,
|
|
145
|
-
maxTokens: 8192,
|
|
146
|
-
input: ['text'],
|
|
147
|
-
category: 'coding',
|
|
148
|
-
recommended: false,
|
|
149
|
-
isCustom: false,
|
|
150
|
-
},
|
|
151
|
-
{
|
|
138
|
+
ctx: 200000, maxTokens: 8192, input: ['text'], category: 'coding', recommended: false,
|
|
139
|
+
}),
|
|
140
|
+
createCuratedModel({
|
|
152
141
|
id: 'cline-pass/kimi-k2.6',
|
|
153
142
|
name: 'Kimi K2.6',
|
|
154
143
|
description: 'Balanced long-context conversational model.',
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
originalProvider: 'Moonshot AI',
|
|
159
|
-
contextLength: 200000,
|
|
160
|
-
maxTokens: 8192,
|
|
161
|
-
input: ['text'],
|
|
162
|
-
category: 'general',
|
|
163
|
-
recommended: false,
|
|
164
|
-
isCustom: false,
|
|
165
|
-
},
|
|
166
|
-
{
|
|
144
|
+
ctx: 200000, maxTokens: 8192, input: ['text'], category: 'general', recommended: false,
|
|
145
|
+
}),
|
|
146
|
+
createCuratedModel({
|
|
167
147
|
id: 'cline-pass/qwen3.7-max',
|
|
168
148
|
name: 'Qwen 3.7 Max',
|
|
169
149
|
description: 'Large-scale multimodal foundation model from Alibaba Cloud with reasoning support.',
|
|
170
|
-
|
|
171
|
-
defaultContextLength: 128000,
|
|
172
|
-
originalContextLength: 1000000,
|
|
173
|
-
originalProvider: 'Alibaba Cloud',
|
|
174
|
-
contextLength: 128000,
|
|
175
|
-
maxTokens: 8192,
|
|
176
|
-
input: ['text', 'image'],
|
|
177
|
-
category: 'multimodal',
|
|
178
|
-
recommended: true,
|
|
179
|
-
isCustom: false,
|
|
150
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'multimodal', recommended: true,
|
|
180
151
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
181
|
-
},
|
|
182
|
-
{
|
|
152
|
+
}),
|
|
153
|
+
createCuratedModel({
|
|
183
154
|
id: 'cline-pass/qwen3.7-plus',
|
|
184
155
|
name: 'Qwen 3.7 Plus',
|
|
185
156
|
description: 'Fast, capable multimodal model with strong multilingual skills and reasoning.',
|
|
186
|
-
|
|
187
|
-
defaultContextLength: 128000,
|
|
188
|
-
originalContextLength: 1000000,
|
|
189
|
-
originalProvider: 'Alibaba Cloud',
|
|
190
|
-
contextLength: 128000,
|
|
191
|
-
maxTokens: 8192,
|
|
192
|
-
input: ['text', 'image'],
|
|
193
|
-
category: 'multimodal',
|
|
194
|
-
recommended: false,
|
|
195
|
-
isCustom: false,
|
|
157
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'multimodal', recommended: false,
|
|
196
158
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
197
|
-
},
|
|
198
|
-
{
|
|
159
|
+
}),
|
|
160
|
+
createCuratedModel({
|
|
199
161
|
id: 'cline-pass/minimax-m3',
|
|
200
162
|
name: 'MiniMax M3',
|
|
201
163
|
description: 'High throughput reasoning and synthesis model.',
|
|
202
|
-
|
|
203
|
-
defaultContextLength: 200000,
|
|
204
|
-
originalContextLength: 1000000,
|
|
205
|
-
originalProvider: 'MiniMax',
|
|
206
|
-
contextLength: 200000,
|
|
207
|
-
maxTokens: 8192,
|
|
208
|
-
input: ['text'],
|
|
209
|
-
category: 'reasoning',
|
|
210
|
-
recommended: false,
|
|
211
|
-
isCustom: false,
|
|
164
|
+
ctx: 200000, maxTokens: 8192, input: ['text'], category: 'reasoning', recommended: false,
|
|
212
165
|
reasoningEfforts: ['low', 'medium', 'high'],
|
|
213
|
-
},
|
|
214
|
-
{
|
|
166
|
+
}),
|
|
167
|
+
createCuratedModel({
|
|
215
168
|
id: 'cline-pass/mimo-v2.5',
|
|
216
169
|
name: 'MiMo V2.5',
|
|
217
170
|
description: 'Xiaomi MiMo efficient instruction model with reasoning and multimodal support.',
|
|
218
|
-
|
|
219
|
-
defaultContextLength: 128000,
|
|
220
|
-
originalContextLength: 1000000,
|
|
221
|
-
originalProvider: 'Xiaomi',
|
|
222
|
-
contextLength: 128000,
|
|
223
|
-
maxTokens: 8192,
|
|
224
|
-
input: ['text', 'image'],
|
|
225
|
-
category: 'general',
|
|
226
|
-
recommended: false,
|
|
227
|
-
isCustom: false,
|
|
171
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'general', recommended: false,
|
|
228
172
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
229
|
-
},
|
|
230
|
-
{
|
|
173
|
+
}),
|
|
174
|
+
createCuratedModel({
|
|
231
175
|
id: 'cline-pass/mimo-v2.5-pro',
|
|
232
176
|
name: 'MiMo V2.5 Pro',
|
|
233
177
|
description: 'Xiaomi MiMo advanced agentic reasoning model with multimodal support.',
|
|
234
|
-
|
|
235
|
-
defaultContextLength: 128000,
|
|
236
|
-
originalContextLength: 1000000,
|
|
237
|
-
originalProvider: 'Xiaomi',
|
|
238
|
-
contextLength: 128000,
|
|
239
|
-
maxTokens: 8192,
|
|
240
|
-
input: ['text', 'image'],
|
|
241
|
-
category: 'coding',
|
|
242
|
-
recommended: false,
|
|
243
|
-
isCustom: false,
|
|
178
|
+
ctx: 128000, maxTokens: 8192, input: ['text', 'image'], category: 'coding', recommended: false,
|
|
244
179
|
reasoningEfforts: ['low', 'medium', 'high', 'max'],
|
|
245
|
-
},
|
|
180
|
+
}),
|
|
246
181
|
])
|
|
247
182
|
|
|
248
183
|
/**
|
|
@@ -303,6 +238,7 @@ export function parsePlanIncludedModels(includedInput) {
|
|
|
303
238
|
const isVision = /(vision|vl|multimodal|omni|image)/i.test(name)
|
|
304
239
|
const hasReasoning = /(reason|think|r1|pro|flash|max|plus|k3|m3|glm|qwen|mimo)/i.test(name)
|
|
305
240
|
const origCtx = getOriginalModelContext(name)
|
|
241
|
+
const origMax = getOriginalModelMaxTokens(name)
|
|
306
242
|
const origProv = getOriginalModelProvider(name)
|
|
307
243
|
matched.push({
|
|
308
244
|
id: `cline-pass/${idPart}`,
|
|
@@ -311,9 +247,11 @@ export function parsePlanIncludedModels(includedInput) {
|
|
|
311
247
|
clineContextLength: 200000,
|
|
312
248
|
defaultContextLength: 200000,
|
|
313
249
|
originalContextLength: origCtx,
|
|
250
|
+
originalMaxTokens: origMax,
|
|
314
251
|
originalProvider: origProv,
|
|
315
252
|
contextLength: 200000,
|
|
316
253
|
maxTokens: 8192,
|
|
254
|
+
defaultMaxTokens: 8192,
|
|
317
255
|
input: isVision ? ['text', 'image'] : ['text'],
|
|
318
256
|
category: hasReasoning ? 'reasoning' : (isVision ? 'multimodal' : 'general'),
|
|
319
257
|
recommended: false,
|
|
@@ -351,26 +289,31 @@ export function getAllModels(dynamicModels = [], modelContextOverrides = [], cus
|
|
|
351
289
|
}
|
|
352
290
|
}
|
|
353
291
|
|
|
292
|
+
const wildcardOv = overridesMap.get('*')
|
|
354
293
|
function applyOverride(m) {
|
|
355
294
|
const defaultCtx = m.clineContextLength || m.defaultContextLength || m.contextLength || 200000
|
|
295
|
+
const defaultMax = m.defaultMaxTokens || m.maxTokens || 8192
|
|
356
296
|
const origCtx = m.originalContextLength || getOriginalModelContext(m.id)
|
|
297
|
+
const origMax = m.originalMaxTokens || getOriginalModelMaxTokens(m.id)
|
|
357
298
|
const origProv = m.originalProvider || getOriginalModelProvider(m.id)
|
|
358
|
-
const ov = overridesMap.get(m.id)
|
|
299
|
+
const ov = overridesMap.get(m.id) || wildcardOv
|
|
359
300
|
const activeCtx = ov && typeof ov.contextLength === 'number' && ov.contextLength > 0
|
|
360
301
|
? ov.contextLength
|
|
361
302
|
: defaultCtx
|
|
362
303
|
const activeMaxTokens = ov && typeof ov.maxTokens === 'number' && ov.maxTokens > 0
|
|
363
304
|
? ov.maxTokens
|
|
364
|
-
:
|
|
305
|
+
: defaultMax
|
|
365
306
|
return {
|
|
366
307
|
...m,
|
|
367
308
|
contextLength: activeCtx,
|
|
368
309
|
maxTokens: activeMaxTokens,
|
|
369
310
|
defaultContextLength: defaultCtx,
|
|
370
311
|
clineContextLength: defaultCtx,
|
|
312
|
+
defaultMaxTokens: defaultMax,
|
|
371
313
|
originalContextLength: origCtx,
|
|
314
|
+
originalMaxTokens: origMax,
|
|
372
315
|
originalProvider: origProv,
|
|
373
|
-
isContextOverridden: activeCtx !== defaultCtx,
|
|
316
|
+
isContextOverridden: activeCtx !== defaultCtx || activeMaxTokens !== defaultMax,
|
|
374
317
|
}
|
|
375
318
|
}
|
|
376
319
|
|
|
@@ -392,7 +335,9 @@ export function getAllModels(dynamicModels = [], modelContextOverrides = [], cus
|
|
|
392
335
|
clineContextLength: curated.clineContextLength || curated.contextLength,
|
|
393
336
|
defaultContextLength: curated.defaultContextLength || curated.contextLength,
|
|
394
337
|
originalContextLength: curated.originalContextLength || getOriginalModelContext(curated.id),
|
|
338
|
+
originalMaxTokens: curated.originalMaxTokens || getOriginalModelMaxTokens(curated.id),
|
|
395
339
|
originalProvider: curated.originalProvider || getOriginalModelProvider(curated.id),
|
|
340
|
+
defaultMaxTokens: curated.defaultMaxTokens || curated.maxTokens || 8192,
|
|
396
341
|
input: dm.input && dm.input.length ? dm.input : curated.input,
|
|
397
342
|
category: dm.category || curated.category,
|
|
398
343
|
reasoningEfforts: dm.reasoningEfforts !== undefined ? dm.reasoningEfforts : curated.reasoningEfforts,
|
|
@@ -404,7 +349,9 @@ export function getAllModels(dynamicModels = [], modelContextOverrides = [], cus
|
|
|
404
349
|
clineContextLength: dm.contextLength || 200000,
|
|
405
350
|
defaultContextLength: dm.contextLength || 200000,
|
|
406
351
|
originalContextLength: dm.originalContextLength || getOriginalModelContext(dm.id),
|
|
352
|
+
originalMaxTokens: dm.originalMaxTokens || getOriginalModelMaxTokens(dm.id),
|
|
407
353
|
originalProvider: dm.originalProvider || getOriginalModelProvider(dm.id),
|
|
354
|
+
defaultMaxTokens: dm.maxTokens || 8192,
|
|
408
355
|
unverified: false,
|
|
409
356
|
})
|
|
410
357
|
})
|
package/lib/provider-sync.js
CHANGED
|
@@ -239,6 +239,22 @@ export async function upsertPiAiProvider(ctx, cfg, activeModelIds) {
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
|
|
242
|
+
let existingModels = []
|
|
243
|
+
try {
|
|
244
|
+
if (typeof settings.describe === 'function') {
|
|
245
|
+
const descriptors = settings.describe()
|
|
246
|
+
const piAiDesc = Array.isArray(descriptors) ? descriptors.find((d) => d?.ns === LLM_PI_AI_NS) : null
|
|
247
|
+
const existingProv = piAiDesc?.value?.providers?.[PROVIDER_ID]
|
|
248
|
+
if (Array.isArray(existingProv?.models)) existingModels = existingProv.models
|
|
249
|
+
} else if (typeof settings.get === 'function') {
|
|
250
|
+
const piAiVal = settings.get(LLM_PI_AI_NS)
|
|
251
|
+
const existingProv = piAiVal?.providers?.[PROVIDER_ID]
|
|
252
|
+
if (Array.isArray(existingProv?.models)) existingModels = existingProv.models
|
|
253
|
+
}
|
|
254
|
+
} catch {
|
|
255
|
+
/* ignore read error */
|
|
256
|
+
}
|
|
257
|
+
|
|
242
258
|
const providerObj = buildPiAiProvider({
|
|
243
259
|
baseUrl: effectiveBaseUrl,
|
|
244
260
|
apiKeyEnv: isProxy ? LOCAL_PROXY_KEY_ENV : (activeAcc?.apiKeyEnv || pub.apiKeyEnv),
|
|
@@ -246,6 +262,7 @@ export async function upsertPiAiProvider(ctx, cfg, activeModelIds) {
|
|
|
246
262
|
dynamicModels: pub.dynamicModels,
|
|
247
263
|
modelContextOverrides: pub.modelContextOverrides,
|
|
248
264
|
customModels: pub.customModels,
|
|
265
|
+
existingModels,
|
|
249
266
|
displayName: PROVIDER_DISPLAY_NAME,
|
|
250
267
|
})
|
|
251
268
|
|
|
@@ -257,6 +274,10 @@ export async function upsertPiAiProvider(ctx, cfg, activeModelIds) {
|
|
|
257
274
|
},
|
|
258
275
|
])
|
|
259
276
|
|
|
277
|
+
ctx?.logger?.info?.(
|
|
278
|
+
`[dsh-clinebot] Synchronized llm-pi-ai provider "${PROVIDER_ID}" with ${providerObj.models.length} models.`
|
|
279
|
+
)
|
|
280
|
+
|
|
260
281
|
return providerObj
|
|
261
282
|
}
|
|
262
283
|
|
package/lib/proxy-token.js
CHANGED
|
@@ -14,9 +14,9 @@ export const LOCAL_PROXY_KEY_ENV = 'CLINEBOT_LOCAL_PROXY_TOKEN'
|
|
|
14
14
|
let cachedToken = null
|
|
15
15
|
|
|
16
16
|
const require = createRequire(import.meta.url)
|
|
17
|
-
let packageVersion = '0.5.
|
|
17
|
+
let packageVersion = '0.5.22'
|
|
18
18
|
try {
|
|
19
|
-
packageVersion = require('../package.json').version || '0.5.
|
|
19
|
+
packageVersion = require('../package.json').version || '0.5.22'
|
|
20
20
|
} catch (err) {
|
|
21
21
|
/* fallback to default version if manifest is unreadable */
|
|
22
22
|
}
|
|
@@ -118,3 +118,23 @@ export function sendProxyTerminalError(res, { hadTimeout, is429Exhausted, lastEr
|
|
|
118
118
|
},
|
|
119
119
|
})
|
|
120
120
|
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Safely extract standard and reasoning token counts from usage object.
|
|
125
|
+
*/
|
|
126
|
+
export function extractUsageMetrics(usage) {
|
|
127
|
+
if (!usage || typeof usage !== 'object') {
|
|
128
|
+
return { promptTokens: 0, completionTokens: 0, reasoningTokens: 0, totalTokens: 0 }
|
|
129
|
+
}
|
|
130
|
+
const promptTokens = Number(usage.prompt_tokens) || 0
|
|
131
|
+
const completionTokens = Number(usage.completion_tokens) || 0
|
|
132
|
+
const reasoningTokens = Number(
|
|
133
|
+
usage.completion_tokens_details?.reasoning_tokens ||
|
|
134
|
+
usage.reasoning_tokens ||
|
|
135
|
+
usage.prompt_tokens_details?.reasoning_tokens ||
|
|
136
|
+
0
|
|
137
|
+
)
|
|
138
|
+
const totalTokens = Number(usage.total_tokens) || (promptTokens + completionTokens)
|
|
139
|
+
return { promptTokens, completionTokens, reasoningTokens, totalTokens }
|
|
140
|
+
}
|
package/lib/routes/models.js
CHANGED
|
@@ -2,8 +2,8 @@ import { writeJson, readBody } from '../http.js'
|
|
|
2
2
|
import { assertTrustedSettingsRequest } from '../access.js'
|
|
3
3
|
import { publicConfig, plainConfig, Config } from '../config.js'
|
|
4
4
|
import { resolveActiveAccountKey, resolvePathWithHome } from '../provider-sync.js'
|
|
5
|
-
import { getAllModels, saveModelsDiskCache, getOriginalModelContext } from '../models.js'
|
|
6
|
-
import { fetchUsageLimits } from '../cline-client.js'
|
|
5
|
+
import { getAllModels, saveModelsDiskCache, getOriginalModelContext, getOriginalModelMaxTokens } from '../models.js'
|
|
6
|
+
import { fetchUsageLimits, smokeChat } from '../cline-client.js'
|
|
7
7
|
import { publicUsage } from '../http.js'
|
|
8
8
|
|
|
9
9
|
export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderState }) {
|
|
@@ -129,15 +129,36 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
129
129
|
let currentOverrides = Array.isArray(pub.modelContextOverrides) ? [...pub.modelContextOverrides] : []
|
|
130
130
|
|
|
131
131
|
if (body.mode === 'all-original') {
|
|
132
|
-
// Set all models to their authentic original creator context limits
|
|
132
|
+
// Set all models to their authentic original creator context & token limits
|
|
133
133
|
currentOverrides = allModels.map((m) => ({
|
|
134
134
|
modelId: m.id,
|
|
135
135
|
contextLength: m.originalContextLength || getOriginalModelContext(m.id),
|
|
136
|
-
maxTokens: m.
|
|
136
|
+
maxTokens: m.originalMaxTokens || getOriginalModelMaxTokens(m.id),
|
|
137
137
|
}))
|
|
138
138
|
} else if (body.mode === 'all-default' || body.mode === 'reset-all') {
|
|
139
139
|
// Reset all overrides back to provider defaults
|
|
140
140
|
currentOverrides = []
|
|
141
|
+
} else if (body.mode === 'apply-all') {
|
|
142
|
+
// Bulk apply limits across all models and set wildcard default
|
|
143
|
+
const rawCtx = Number(body.contextLength)
|
|
144
|
+
const contextLength = Number.isFinite(rawCtx) && rawCtx > 0 ? Math.max(1000, rawCtx) : undefined
|
|
145
|
+
const rawMax = Number(body.maxTokens)
|
|
146
|
+
const maxTokens = Number.isFinite(rawMax) && rawMax > 0 ? Math.max(256, rawMax) : undefined
|
|
147
|
+
|
|
148
|
+
currentOverrides = allModels.map((m) => {
|
|
149
|
+
const existing = currentOverrides.find((o) => o.modelId === m.id)
|
|
150
|
+
return {
|
|
151
|
+
modelId: m.id,
|
|
152
|
+
contextLength: contextLength !== undefined ? contextLength : (existing?.contextLength || m.contextLength),
|
|
153
|
+
maxTokens: maxTokens !== undefined ? maxTokens : (existing?.maxTokens || m.maxTokens),
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
currentOverrides = currentOverrides.filter((o) => o.modelId !== '*')
|
|
157
|
+
currentOverrides.push({
|
|
158
|
+
modelId: '*',
|
|
159
|
+
contextLength: contextLength || 200000,
|
|
160
|
+
maxTokens: maxTokens || 16384,
|
|
161
|
+
})
|
|
141
162
|
} else if (Array.isArray(body.overrides)) {
|
|
142
163
|
// Bulk array override
|
|
143
164
|
currentOverrides = body.overrides
|
|
@@ -153,11 +174,19 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
153
174
|
currentOverrides = currentOverrides.filter((o) => o.modelId !== modelId)
|
|
154
175
|
} else {
|
|
155
176
|
const rawCtx = Number(body.contextLength)
|
|
156
|
-
const contextLength = Number.isFinite(rawCtx) && rawCtx > 0 ? Math.max(1000, rawCtx) : 200000
|
|
157
177
|
const rawMax = Number(body.maxTokens)
|
|
158
|
-
const maxTokens = Number.isFinite(rawMax) && rawMax > 0 ? Math.max(256, rawMax) : 8192
|
|
159
|
-
|
|
160
178
|
const existingIdx = currentOverrides.findIndex((o) => o.modelId === modelId)
|
|
179
|
+
const existingEntry = existingIdx >= 0 ? currentOverrides[existingIdx] : null
|
|
180
|
+
const targetModel = allModels.find((m) => m.id === modelId)
|
|
181
|
+
|
|
182
|
+
const contextLength = Number.isFinite(rawCtx) && rawCtx > 0
|
|
183
|
+
? Math.max(1000, rawCtx)
|
|
184
|
+
: (existingEntry?.contextLength || targetModel?.contextLength || 200000)
|
|
185
|
+
|
|
186
|
+
const maxTokens = Number.isFinite(rawMax) && rawMax > 0
|
|
187
|
+
? Math.max(256, rawMax)
|
|
188
|
+
: (existingEntry?.maxTokens || targetModel?.maxTokens || 8192)
|
|
189
|
+
|
|
161
190
|
const newEntry = { modelId, contextLength, maxTokens }
|
|
162
191
|
if (existingIdx >= 0) {
|
|
163
192
|
currentOverrides[existingIdx] = newEntry
|
|
@@ -211,4 +240,38 @@ export function registerModelsRoutes(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
211
240
|
}
|
|
212
241
|
},
|
|
213
242
|
}), 'dsh-clinebot: /usage')
|
|
243
|
+
|
|
244
|
+
// POST /dsh-clinebot/models/probe — benchmark TTFT latency for specific model
|
|
245
|
+
ctx.effect(() => ctx.webServer.register({
|
|
246
|
+
kind: 'exact',
|
|
247
|
+
path: '/dsh-clinebot/models/probe',
|
|
248
|
+
handler: async (req, res) => {
|
|
249
|
+
if (req.method !== 'POST') return writeJson(res, 405, { ok: false, error: 'POST only' })
|
|
250
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
251
|
+
try {
|
|
252
|
+
const bodyBuf = await readBody(req)
|
|
253
|
+
let body = {}
|
|
254
|
+
try { body = JSON.parse(bodyBuf.toString('utf8')) } catch { body = {} }
|
|
255
|
+
const pub = publicConfig(live())
|
|
256
|
+
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
257
|
+
if (!activeKey.value) {
|
|
258
|
+
return writeJson(res, 400, { ok: false, error: 'API key not found' })
|
|
259
|
+
}
|
|
260
|
+
const modelId = body.modelId || body.model || pub.defaultModel
|
|
261
|
+
const result = await smokeChat(pub.baseUrl, activeKey.value, {
|
|
262
|
+
model: modelId,
|
|
263
|
+
timeoutMs: Math.min(pub.smokeTimeoutMs || 25000, 15000),
|
|
264
|
+
})
|
|
265
|
+
return writeJson(res, 200, {
|
|
266
|
+
ok: result.ok,
|
|
267
|
+
modelId,
|
|
268
|
+
latencyMs: result.latencyMs,
|
|
269
|
+
status: result.status,
|
|
270
|
+
error: result.error || null,
|
|
271
|
+
})
|
|
272
|
+
} catch (err) {
|
|
273
|
+
return writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
}), 'dsh-clinebot: /models/probe')
|
|
214
277
|
}
|
package/lib/routes/proxy.js
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
isAuthorizedProxyRequest,
|
|
28
28
|
USER_AGENT,
|
|
29
29
|
sendProxyTerminalError,
|
|
30
|
+
extractUsageMetrics,
|
|
30
31
|
} from '../proxy-token.js'
|
|
31
32
|
|
|
32
33
|
export { hasDisallowedBrowserOrigin, isAuthorizedProxyRequest, USER_AGENT }
|
|
@@ -120,7 +121,7 @@ export function createChatCompletionsHandler(ctx, { live, getSettingsApi, syncPr
|
|
|
120
121
|
return
|
|
121
122
|
}
|
|
122
123
|
|
|
123
|
-
const routeResult = resolveSessionAccount(sessionId, pool, pub.activeAccount, pub.accountMode)
|
|
124
|
+
const routeResult = resolveSessionAccount(sessionId, pool, pub.activeAccount, pub.accountMode, body?.model, pub.modelRoutes)
|
|
124
125
|
const targetAccount = routeResult.account
|
|
125
126
|
lastTargetAccount = targetAccount
|
|
126
127
|
|
|
@@ -297,6 +298,7 @@ export function createChatCompletionsHandler(ctx, { live, getSettingsApi, syncPr
|
|
|
297
298
|
|
|
298
299
|
let promptTokens = 0
|
|
299
300
|
let completionTokens = 0
|
|
301
|
+
let reasoningTokens = 0
|
|
300
302
|
let totalTokens = 0
|
|
301
303
|
let buffer = ''
|
|
302
304
|
let streamError = null
|
|
@@ -350,9 +352,11 @@ export function createChatCompletionsHandler(ctx, { live, getSettingsApi, syncPr
|
|
|
350
352
|
try {
|
|
351
353
|
const parsed = JSON.parse(trimmed.slice(5).trim())
|
|
352
354
|
if (parsed.usage) {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
355
|
+
const u = extractUsageMetrics(parsed.usage)
|
|
356
|
+
promptTokens = u.promptTokens || promptTokens
|
|
357
|
+
completionTokens = u.completionTokens || completionTokens
|
|
358
|
+
reasoningTokens = u.reasoningTokens || reasoningTokens
|
|
359
|
+
totalTokens = u.totalTokens || totalTokens
|
|
356
360
|
}
|
|
357
361
|
} catch {}
|
|
358
362
|
}
|
|
@@ -419,6 +423,7 @@ export function createChatCompletionsHandler(ctx, { live, getSettingsApi, syncPr
|
|
|
419
423
|
accountId: targetAccount.id || targetAccount.label,
|
|
420
424
|
promptTokens,
|
|
421
425
|
completionTokens,
|
|
426
|
+
reasoningTokens,
|
|
422
427
|
totalTokens: totalTokens || (promptTokens + completionTokens),
|
|
423
428
|
isError: isStreamFailed,
|
|
424
429
|
statsPath: pub.statsPath
|
|
@@ -495,12 +500,14 @@ export function createChatCompletionsHandler(ctx, { live, getSettingsApi, syncPr
|
|
|
495
500
|
}
|
|
496
501
|
|
|
497
502
|
cleanupClientListeners()
|
|
503
|
+
const nonStreamUsage = extractUsageMetrics(resJson?.usage)
|
|
498
504
|
recordUsage({
|
|
499
505
|
model: body.model,
|
|
500
506
|
accountId: targetAccount.id || targetAccount.label,
|
|
501
|
-
promptTokens:
|
|
502
|
-
completionTokens:
|
|
503
|
-
|
|
507
|
+
promptTokens: nonStreamUsage.promptTokens,
|
|
508
|
+
completionTokens: nonStreamUsage.completionTokens,
|
|
509
|
+
reasoningTokens: nonStreamUsage.reasoningTokens,
|
|
510
|
+
totalTokens: nonStreamUsage.totalTokens,
|
|
504
511
|
isError: false,
|
|
505
512
|
statsPath: pub.statsPath
|
|
506
513
|
})
|
|
@@ -582,4 +589,4 @@ export function registerProxyRoutes(ctx, routeEnv) {
|
|
|
582
589
|
path: '/dsh-clinebot/v1/models',
|
|
583
590
|
handler: modelsHandler,
|
|
584
591
|
}), 'dsh-clinebot: /v1/models')
|
|
585
|
-
}
|
|
592
|
+
}
|
package/lib/routes/settings.js
CHANGED
|
@@ -3,7 +3,7 @@ import { assertTrustedSettingsRequest } from '../access.js'
|
|
|
3
3
|
import { publicConfig, plainConfig, Config } from '../config.js'
|
|
4
4
|
import { buildStatus } from '../provider-sync.js'
|
|
5
5
|
import { saveCredentialKey, smokeChat, DEFAULT_API_KEY_ENV } from '../cline-client.js'
|
|
6
|
-
import { getStatsSummary, resetStats } from '../stats-storage.js'
|
|
6
|
+
import { getStatsSummary, resetStats, exportStatsAsCsv } from '../stats-storage.js'
|
|
7
7
|
|
|
8
8
|
export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProviderState, triggerAutoDiscover }) {
|
|
9
9
|
// 1. GET /dsh-clinebot/status
|
|
@@ -212,4 +212,23 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
212
212
|
}
|
|
213
213
|
},
|
|
214
214
|
}), 'dsh-clinebot: /stats/reset')
|
|
215
|
+
|
|
216
|
+
// 6. GET /dsh-clinebot/stats/export — export token telemetry as CSV
|
|
217
|
+
ctx.effect(() => ctx.webServer.register({
|
|
218
|
+
kind: 'exact',
|
|
219
|
+
path: '/dsh-clinebot/stats/export',
|
|
220
|
+
handler: async (req, res) => {
|
|
221
|
+
if (req.method !== 'GET') return writeJson(res, 405, { ok: false, error: 'GET only' })
|
|
222
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
223
|
+
try {
|
|
224
|
+
const csv = exportStatsAsCsv()
|
|
225
|
+
res.statusCode = 200
|
|
226
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8')
|
|
227
|
+
res.setHeader('Content-Disposition', 'attachment; filename="clinebot-stats.csv"')
|
|
228
|
+
res.end(csv)
|
|
229
|
+
} catch (err) {
|
|
230
|
+
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
}), 'dsh-clinebot: /stats/export')
|
|
215
234
|
}
|