@goodandready/dsh-clinebot 0.3.7 → 0.3.9
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 +24 -0
- package/README.md +15 -2
- package/README.ru.md +199 -0
- package/README.zh.md +201 -0
- package/docs/README.ru.md +14 -1
- package/docs/README.zh.md +14 -1
- package/docs/design/DESIGN.md +15 -1
- package/lib/client.js +122 -106
- package/lib/cline-client.js +340 -131
- package/lib/index.js +143 -109
- package/lib/models.js +69 -0
- package/lib/updater.js +245 -0
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import z from '@deepseek-ai/schemastery'
|
|
2
2
|
import { credentialRef } from '@deepseek-ai/dsh-credentials'
|
|
3
3
|
import { writeJson, readBody, isTrustedSettingsRequest } from './http.js'
|
|
4
|
+
import { registerPluginUpdater } from './updater.js'
|
|
4
5
|
import {
|
|
5
6
|
CLINE_MODELS,
|
|
6
7
|
DEFAULT_MODEL_ID,
|
|
@@ -21,6 +22,9 @@ import {
|
|
|
21
22
|
DEFAULT_SMOKE_TIMEOUT_MS,
|
|
22
23
|
normalizeBaseUrl,
|
|
23
24
|
resolveApiKey,
|
|
25
|
+
resolveKeyValue,
|
|
26
|
+
resolveAccountPool,
|
|
27
|
+
rotateToNextAccount,
|
|
24
28
|
saveCredentialKey,
|
|
25
29
|
fetchUsageLimits,
|
|
26
30
|
probeHealth,
|
|
@@ -29,6 +33,7 @@ import {
|
|
|
29
33
|
sessionStats,
|
|
30
34
|
recordSessionRequest,
|
|
31
35
|
resetSessionStats,
|
|
36
|
+
usageCache,
|
|
32
37
|
} from './cline-client.js'
|
|
33
38
|
|
|
34
39
|
export const name = '@goodandready/dsh-clinebot'
|
|
@@ -37,7 +42,7 @@ export const inject = ['settings', 'webServer', 'credentials']
|
|
|
37
42
|
export const NS = 'dsh-clinebot'
|
|
38
43
|
export const LLM_PI_AI_NS = 'llm-pi-ai'
|
|
39
44
|
|
|
40
|
-
export { sessionStats, recordSessionRequest, resetSessionStats }
|
|
45
|
+
export { sessionStats, recordSessionRequest, resetSessionStats, rotateToNextAccount }
|
|
41
46
|
|
|
42
47
|
export const Config = z.object({
|
|
43
48
|
enabled: z.boolean().default(true)
|
|
@@ -120,104 +125,51 @@ function resolvePathWithHome(p) {
|
|
|
120
125
|
return p
|
|
121
126
|
}
|
|
122
127
|
|
|
123
|
-
async function
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
} catch {
|
|
133
|
-
/* credentials service miss, fall through */
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const fromEnv = resolveApiKey(refName)
|
|
138
|
-
if (fromEnv.value) {
|
|
139
|
-
return { ...fromEnv, source: 'env' }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
return { envName: refName, value: '', source: 'none' }
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Resolve all accounts in pool with their status and quota.
|
|
147
|
-
*/
|
|
148
|
-
async function resolveAccountPool(ctx, cfg) {
|
|
149
|
-
const pub = publicConfig(cfg)
|
|
150
|
-
const defaultSlot = {
|
|
151
|
-
id: 'default',
|
|
152
|
-
label: 'Default',
|
|
153
|
-
apiKeyEnv: pub.apiKeyEnv,
|
|
154
|
-
}
|
|
155
|
-
const allSlots = [defaultSlot, ...(Array.isArray(pub.accounts) ? pub.accounts : [])]
|
|
156
|
-
const resolved = []
|
|
157
|
-
|
|
158
|
-
for (let i = 0; i < allSlots.length; i++) {
|
|
159
|
-
const slot = allSlots[i]
|
|
160
|
-
const envName = slot.apiKeyEnv || (i === 0 ? pub.apiKeyEnv : `CLINEBOT_API_KEY_${i + 1}`)
|
|
161
|
-
const keyInfo = await resolveKeyValue(ctx, envName)
|
|
162
|
-
resolved.push({
|
|
163
|
-
id: slot.id || (i === 0 ? 'default' : `account-${i + 1}`),
|
|
164
|
-
label: slot.label || (i === 0 ? 'Default' : `Account ${i + 1}`),
|
|
165
|
-
apiKeyEnv: envName,
|
|
166
|
-
present: Boolean(keyInfo.value),
|
|
167
|
-
source: keyInfo.source,
|
|
168
|
-
value: keyInfo.value,
|
|
169
|
-
isPinned: pub.activeAccount ? pub.activeAccount === envName : i === 0,
|
|
170
|
-
})
|
|
128
|
+
async function checkRegisteredInPiAi(ctx) {
|
|
129
|
+
const settings = ctx?.get?.('settings')
|
|
130
|
+
if (!settings?.get) return false
|
|
131
|
+
try {
|
|
132
|
+
const piAi = settings.get(LLM_PI_AI_NS)
|
|
133
|
+
return !!piAi?.providers?.[PROVIDER_ID]
|
|
134
|
+
} catch {
|
|
135
|
+
return false
|
|
171
136
|
}
|
|
172
|
-
|
|
173
|
-
return resolved
|
|
174
137
|
}
|
|
175
138
|
|
|
176
|
-
/**
|
|
177
|
-
* Resolve active key with failover support.
|
|
178
|
-
*/
|
|
179
139
|
async function resolveActiveAccountKey(ctx, cfg) {
|
|
180
140
|
const pool = await resolveAccountPool(ctx, cfg)
|
|
181
141
|
const configured = pool.filter((acc) => acc.present && acc.value)
|
|
182
142
|
if (!configured.length) {
|
|
183
143
|
return { envName: publicConfig(cfg).apiKeyEnv, value: '', source: 'none', id: 'default' }
|
|
184
144
|
}
|
|
185
|
-
|
|
186
|
-
// If user pinned a specific account and it has a key, prefer it
|
|
187
145
|
const pub = publicConfig(cfg)
|
|
188
146
|
if (pub.activeAccount) {
|
|
189
147
|
const pinned = configured.find((acc) => acc.apiKeyEnv === pub.activeAccount || acc.id === pub.activeAccount)
|
|
190
148
|
if (pinned) return pinned
|
|
191
149
|
}
|
|
192
|
-
|
|
193
|
-
// Default: first available configured account
|
|
194
150
|
return configured[0]
|
|
195
151
|
}
|
|
196
152
|
|
|
197
|
-
async function checkRegisteredInPiAi(ctx) {
|
|
198
|
-
const settings = ctx?.get?.('settings')
|
|
199
|
-
if (!settings?.get) return false
|
|
200
|
-
try {
|
|
201
|
-
const piAi = settings.get(LLM_PI_AI_NS)
|
|
202
|
-
return !!piAi?.providers?.[PROVIDER_ID]
|
|
203
|
-
} catch {
|
|
204
|
-
return false
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
153
|
async function buildStatus(ctx, cfg) {
|
|
209
154
|
const pub = publicConfig(cfg)
|
|
210
|
-
const key = await resolveKeyValue(ctx, pub.apiKeyEnv)
|
|
211
|
-
// Non-blocking quick health probe with low timeout so settings page loads instantly
|
|
212
155
|
const probeTimeout = Math.min(2500, pub.timeoutMs || 2500)
|
|
213
|
-
|
|
214
|
-
|
|
156
|
+
|
|
157
|
+
// Concurrent resolution of keys, health probe (SWR cached), accounts pool and DSH registration
|
|
158
|
+
const [key, pool, isRegistered, health] = await Promise.all([
|
|
159
|
+
resolveKeyValue(ctx, pub.apiKeyEnv),
|
|
160
|
+
resolveAccountPool(ctx, cfg),
|
|
161
|
+
checkRegisteredInPiAi(ctx),
|
|
162
|
+
probeHealth(pub.baseUrl, { timeoutMs: probeTimeout }),
|
|
163
|
+
])
|
|
164
|
+
|
|
165
|
+
const activeAcc = await resolveActiveAccountKey(ctx, cfg)
|
|
215
166
|
const allModels = getAllModels(pub.dynamicModels)
|
|
216
167
|
|
|
217
168
|
let usage = null
|
|
218
|
-
|
|
169
|
+
const keyToUse = activeAcc.value || key.value
|
|
170
|
+
if (keyToUse) {
|
|
219
171
|
// Uses 60s cache; if cache miss, times out quickly
|
|
220
|
-
usage = await fetchUsageLimits(pub.baseUrl,
|
|
172
|
+
usage = await fetchUsageLimits(pub.baseUrl, keyToUse, { timeoutMs: probeTimeout }).catch(() => null)
|
|
221
173
|
}
|
|
222
174
|
|
|
223
175
|
// Evaluate warning state
|
|
@@ -227,22 +179,18 @@ async function buildStatus(ctx, cfg) {
|
|
|
227
179
|
if (pct >= 95) {
|
|
228
180
|
quotaWarning = {
|
|
229
181
|
level: 'exhausted',
|
|
230
|
-
message: `5
|
|
182
|
+
message: `5-hour rolling limit is almost exhausted (${pct}%). New requests may be rejected until quota reset.`,
|
|
231
183
|
resetsAt: usage.windows.fiveHour.resetsAt,
|
|
232
184
|
}
|
|
233
185
|
} else if (pct >= 80) {
|
|
234
186
|
quotaWarning = {
|
|
235
187
|
level: 'warning',
|
|
236
|
-
message:
|
|
188
|
+
message: `Notice: ${pct}% of the 5-hour rolling limit has been consumed.`,
|
|
237
189
|
resetsAt: usage.windows.fiveHour.resetsAt,
|
|
238
190
|
}
|
|
239
191
|
}
|
|
240
192
|
}
|
|
241
193
|
|
|
242
|
-
// Resolve all accounts in pool
|
|
243
|
-
const pool = await resolveAccountPool(ctx, cfg)
|
|
244
|
-
const activeAcc = await resolveActiveAccountKey(ctx, cfg)
|
|
245
|
-
|
|
246
194
|
return {
|
|
247
195
|
ok: true,
|
|
248
196
|
providerId: PROVIDER_ID,
|
|
@@ -427,6 +375,16 @@ export function apply(ctx, config) {
|
|
|
427
375
|
|
|
428
376
|
// Web server HTTP route handlers
|
|
429
377
|
if (ctx.webServer?.register) {
|
|
378
|
+
// 0. GET & POST /dsh-clinebot/update — host one-click updater
|
|
379
|
+
const unregisterUpdater = registerPluginUpdater(ctx, {
|
|
380
|
+
endpoint: '/dsh-clinebot/update',
|
|
381
|
+
packageName: name,
|
|
382
|
+
manifestUrl: new URL('../package.json', import.meta.url),
|
|
383
|
+
})
|
|
384
|
+
if (typeof ctx.effect === 'function') {
|
|
385
|
+
ctx.effect(() => () => unregisterUpdater?.(), 'dsh-clinebot: updater')
|
|
386
|
+
}
|
|
387
|
+
|
|
430
388
|
// 1. GET /dsh-clinebot/status
|
|
431
389
|
ctx.effect(() => ctx.webServer.register({
|
|
432
390
|
kind: 'exact',
|
|
@@ -618,10 +576,19 @@ export function apply(ctx, config) {
|
|
|
618
576
|
latencyMs: outcome.latencyMs,
|
|
619
577
|
ok: outcome.ok,
|
|
620
578
|
error: outcome.error,
|
|
621
|
-
promptTokens: 5,
|
|
622
|
-
completionTokens: 10,
|
|
579
|
+
promptTokens: outcome.promptTokens || 5,
|
|
580
|
+
completionTokens: outcome.completionTokens || 10,
|
|
623
581
|
})
|
|
624
|
-
|
|
582
|
+
|
|
583
|
+
let failover = null
|
|
584
|
+
if (outcome.status === 429) {
|
|
585
|
+
failover = await rotateToNextAccount(ctx, live(), 'smoke_429', settingsApi)
|
|
586
|
+
if (failover.rotated) {
|
|
587
|
+
await syncProviderState(live())
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
writeJson(res, outcome.ok ? 200 : 502, { ...outcome, failover })
|
|
625
592
|
} catch (err) {
|
|
626
593
|
recordSessionRequest({ ok: false, error: String(err?.message || err) })
|
|
627
594
|
writeJson(res, 500, { ok: false, error: String(err?.message || err) })
|
|
@@ -802,7 +769,7 @@ export function apply(ctx, config) {
|
|
|
802
769
|
|
|
803
770
|
const unregister = commands.register({
|
|
804
771
|
name: 'cline',
|
|
805
|
-
description: 'Check ClinePass subscription quota, models, accounts and
|
|
772
|
+
description: 'Check ClinePass subscription quota, models, accounts and test connectivity (/cline [quota|models|accounts|switch <name>|test [model]|ping|rotate])',
|
|
806
773
|
execute: async (rawArgs) => {
|
|
807
774
|
const pub = publicConfig(live())
|
|
808
775
|
const subcmd = String(rawArgs || '').trim().toLowerCase().split(/\s+/)[0] || 'quota'
|
|
@@ -831,36 +798,97 @@ export function apply(ctx, config) {
|
|
|
831
798
|
const pool = await resolveAccountPool(ctx, live())
|
|
832
799
|
const lines = [
|
|
833
800
|
'### 🔑 ClinePass Accounts Pool',
|
|
834
|
-
`*
|
|
801
|
+
`* **Active Account**: \`${pub.activeAccount || pub.apiKeyEnv}\``,
|
|
835
802
|
'',
|
|
836
803
|
]
|
|
837
804
|
for (const acc of pool) {
|
|
838
805
|
const pinBadge = acc.isPinned ? '📌 [Pinned]' : ''
|
|
839
|
-
const statusBadge = acc.present ? '✅ Configured' : '⚠️ Missing'
|
|
840
|
-
|
|
806
|
+
const statusBadge = acc.present ? '✅ Configured' : '⚠️ Missing Key'
|
|
807
|
+
const cacheKey = `cline:usage:${(acc.value || '').slice(-8)}`
|
|
808
|
+
const cached = usageCache.get(cacheKey)?.data
|
|
809
|
+
const usageInfo = cached?.windows?.fiveHour ? ` · ⏱ ${cached.windows.fiveHour.percentUsed}% used` : ''
|
|
810
|
+
lines.push(`* **${acc.label}** (\`${acc.apiKeyEnv}\`): ${statusBadge} ${pinBadge}${usageInfo}`)
|
|
841
811
|
}
|
|
842
|
-
lines.push('', '
|
|
812
|
+
lines.push('', 'Switch active account: `/cline switch <env_variable_name>`')
|
|
843
813
|
return lines.join('\n')
|
|
844
814
|
}
|
|
845
815
|
|
|
846
816
|
// 3. Subcommand /cline switch <account>
|
|
847
817
|
if (subcmd === 'switch') {
|
|
848
818
|
if (!param) {
|
|
849
|
-
return '⚠️
|
|
819
|
+
return '⚠️ Please specify account: `/cline switch <CLINEBOT_API_KEY_2>`'
|
|
850
820
|
}
|
|
851
821
|
if (settingsApi?.replace) {
|
|
852
822
|
const next = Config({ ...live(), activeAccount: param })
|
|
853
823
|
await settingsApi.replace(next)
|
|
854
824
|
await syncProviderState(next)
|
|
855
|
-
return `✅
|
|
825
|
+
return `✅ Active account switched to \`${param}\``
|
|
856
826
|
}
|
|
857
|
-
return `⚠️
|
|
827
|
+
return `⚠️ Could not apply setting (settings service unavailable).`
|
|
858
828
|
}
|
|
859
829
|
|
|
860
|
-
// 4.
|
|
830
|
+
// 4. Subcommand /cline rotate (smart failover next)
|
|
831
|
+
if (subcmd === 'rotate') {
|
|
832
|
+
const res = await rotateToNextAccount(ctx, live(), 'slash_command', settingsApi)
|
|
833
|
+
if (res.rotated) {
|
|
834
|
+
await syncProviderState(live())
|
|
835
|
+
return `🔄 **Account Rotated**: switched from \`${res.previousAccount}\` to \`${res.activeAccount}\`. DSH provider updated!`
|
|
836
|
+
}
|
|
837
|
+
return `⚠️ Rotation skipped: ${res.message || 'no alternative configured accounts in pool'}.`
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// 5. Subcommand /cline ping (fresh host reachability probe)
|
|
841
|
+
if (subcmd === 'ping') {
|
|
842
|
+
const health = await probeHealth(pub.baseUrl, { bypassCache: true, timeoutMs: 5000 })
|
|
843
|
+
if (health.ok) {
|
|
844
|
+
return `🏓 **Cline API Pong**: \`${pub.baseUrl}\` is reachable (latency: **${health.latencyMs} ms**, HTTP ${health.status})`
|
|
845
|
+
}
|
|
846
|
+
return `❌ **Cline API Ping Failed**: ${health.error || 'Host unreachable'}`
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// 6. Subcommand /cline test [model] / smoke
|
|
850
|
+
if (subcmd === 'test' || subcmd === 'smoke') {
|
|
851
|
+
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
852
|
+
if (!activeKey.value) {
|
|
853
|
+
return '⚠️ **ClineBot**: API key is not configured. Open **Settings → Plugins → ClineBot**.'
|
|
854
|
+
}
|
|
855
|
+
const modelToTest = param || pub.defaultModel || DEFAULT_MODEL_ID
|
|
856
|
+
const outcome = await smokeChat(pub.baseUrl, activeKey.value, {
|
|
857
|
+
model: modelToTest,
|
|
858
|
+
timeoutMs: pub.smokeTimeoutMs,
|
|
859
|
+
})
|
|
860
|
+
recordSessionRequest({
|
|
861
|
+
latencyMs: outcome.latencyMs,
|
|
862
|
+
ok: outcome.ok,
|
|
863
|
+
error: outcome.error,
|
|
864
|
+
promptTokens: outcome.promptTokens || 5,
|
|
865
|
+
completionTokens: outcome.completionTokens || 10,
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
let failoverNotice = ''
|
|
869
|
+
if (outcome.status === 429) {
|
|
870
|
+
const failover = await rotateToNextAccount(ctx, live(), 'smoke_429', settingsApi)
|
|
871
|
+
if (failover.rotated) {
|
|
872
|
+
await syncProviderState(live())
|
|
873
|
+
failoverNotice = `\n🔄 **Auto-failover**: HTTP 429 detected! Active account automatically rotated to \`${failover.activeAccount}\`.`
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
if (outcome.ok) {
|
|
878
|
+
return [
|
|
879
|
+
`### 🟢 Smoke Test Passed: \`${outcome.model}\``,
|
|
880
|
+
`* **Latency**: ${outcome.latencyMs} ms`,
|
|
881
|
+
`* **Tokens**: prompt: ${outcome.promptTokens}, completion: ${outcome.completionTokens} (total: ${outcome.totalTokens})`,
|
|
882
|
+
`* **Preview**: _"${outcome.preview}"_`,
|
|
883
|
+
].join('\n')
|
|
884
|
+
}
|
|
885
|
+
return `❌ **Smoke Test Failed**: ${outcome.error} (HTTP ${outcome.status || 'timeout'})${failoverNotice}`
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
// 7. Subcommand /cline quota or /cline balance
|
|
861
889
|
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
862
890
|
if (!activeKey.value) {
|
|
863
|
-
return '⚠️ **ClineBot**: API
|
|
891
|
+
return '⚠️ **ClineBot**: API key is not configured. Open **Settings → Plugins → ClineBot**.'
|
|
864
892
|
}
|
|
865
893
|
|
|
866
894
|
const [health, usage] = await Promise.all([
|
|
@@ -870,31 +898,31 @@ export function apply(ctx, config) {
|
|
|
870
898
|
|
|
871
899
|
const fiveHour = usage?.windows?.fiveHour
|
|
872
900
|
const weekly = usage?.windows?.weekly
|
|
873
|
-
const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString() : '
|
|
874
|
-
const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString() : '
|
|
901
|
+
const reset5h = fiveHour?.resetsAt ? new Date(fiveHour.resetsAt).toLocaleTimeString() : 'N/A'
|
|
902
|
+
const resetWk = weekly?.resetsAt ? new Date(weekly.resetsAt).toLocaleDateString() : 'N/A'
|
|
875
903
|
|
|
876
904
|
const lines = [
|
|
877
905
|
`### 🤖 ClinePass Status (${usage?.plan || 'ClinePass'})`,
|
|
878
|
-
`*
|
|
879
|
-
`*
|
|
880
|
-
`*
|
|
906
|
+
`* **Host Ping**: ${health.ok ? `✅ ${health.latencyMs} ms` : '❌ Unreachable'}`,
|
|
907
|
+
`* **Active Key**: \`${activeKey.envName}\` (${activeKey.source})`,
|
|
908
|
+
`* **Default Model**: \`${pub.defaultModel}\``,
|
|
881
909
|
'',
|
|
882
|
-
`**⏱ 5
|
|
883
|
-
`**📅
|
|
910
|
+
`**⏱ 5-Hour Rolling Limit**: ${formatProgressBar(fiveHour?.percentUsed)} (reset: ${reset5h})`,
|
|
911
|
+
`**📅 Weekly Window**: ${formatProgressBar(weekly?.percentUsed)} (reset: ${resetWk})`,
|
|
884
912
|
]
|
|
885
913
|
|
|
886
914
|
if (fiveHour?.percentUsed >= 95) {
|
|
887
|
-
lines.push('', '🚨
|
|
915
|
+
lines.push('', '🚨 **CRITICAL LIMIT**: 5-hour quota is 95%+ consumed! New requests may be throttled until reset.')
|
|
888
916
|
} else if (fiveHour?.percentUsed >= 80) {
|
|
889
|
-
lines.push('',
|
|
917
|
+
lines.push('', `⚠️ **Warning**: 5-hour quota is ${fiveHour.percentUsed}% consumed.`)
|
|
890
918
|
}
|
|
891
919
|
|
|
892
920
|
if (sessionStats.totalRequests > 0) {
|
|
893
|
-
lines.push('', `**📊
|
|
921
|
+
lines.push('', `**📊 Session Telemetry**: ${sessionStats.successfulRequests}/${sessionStats.totalRequests} successful requests, ~${sessionStats.totalTokensEst} total tokens`)
|
|
894
922
|
}
|
|
895
923
|
|
|
896
924
|
if (usage?.user?.email) {
|
|
897
|
-
lines.push(`*
|
|
925
|
+
lines.push(`* **Account**: \`${usage.user.email}\``)
|
|
898
926
|
}
|
|
899
927
|
|
|
900
928
|
return lines.join('\n')
|
|
@@ -917,15 +945,21 @@ export function apply(ctx, config) {
|
|
|
917
945
|
},
|
|
918
946
|
runSmokeTest: async (model) => {
|
|
919
947
|
const pub = publicConfig(live())
|
|
920
|
-
const
|
|
921
|
-
const res = await smokeChat(pub.baseUrl,
|
|
948
|
+
const activeKey = await resolveActiveAccountKey(ctx, live())
|
|
949
|
+
const res = await smokeChat(pub.baseUrl, activeKey.value, { model: model || pub.defaultModel })
|
|
922
950
|
recordSessionRequest({
|
|
923
951
|
latencyMs: res.latencyMs,
|
|
924
952
|
ok: res.ok,
|
|
925
953
|
error: res.error,
|
|
926
|
-
promptTokens: 5,
|
|
927
|
-
completionTokens: 10,
|
|
954
|
+
promptTokens: res.promptTokens || 5,
|
|
955
|
+
completionTokens: res.completionTokens || 10,
|
|
928
956
|
})
|
|
957
|
+
if (res.status === 429) {
|
|
958
|
+
const failover = await rotateToNextAccount(ctx, live(), 'service_429', settingsApi)
|
|
959
|
+
if (failover.rotated) {
|
|
960
|
+
await syncProviderState(live())
|
|
961
|
+
}
|
|
962
|
+
}
|
|
929
963
|
return res
|
|
930
964
|
},
|
|
931
965
|
}
|
package/lib/models.js
CHANGED
|
@@ -136,6 +136,75 @@ export const CLINE_MODELS = Object.freeze([
|
|
|
136
136
|
recommended: false,
|
|
137
137
|
isCustom: false,
|
|
138
138
|
},
|
|
139
|
+
{
|
|
140
|
+
id: 'cline-pass/claude-3-7-sonnet',
|
|
141
|
+
name: 'Claude 3.7 Sonnet',
|
|
142
|
+
description: 'Hybrid reasoning and standard generation model with high coding proficiency.',
|
|
143
|
+
contextLength: 200000,
|
|
144
|
+
maxTokens: 8192,
|
|
145
|
+
input: ['text', 'image'],
|
|
146
|
+
category: 'coding',
|
|
147
|
+
recommended: true,
|
|
148
|
+
isCustom: false,
|
|
149
|
+
reasoningEfforts: ['low', 'medium', 'high'],
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 'cline-pass/gpt-4.5-preview',
|
|
153
|
+
name: 'GPT-4.5 Preview',
|
|
154
|
+
description: 'Advanced flagship frontier model with deep world knowledge and intuition.',
|
|
155
|
+
contextLength: 128000,
|
|
156
|
+
maxTokens: 16384,
|
|
157
|
+
input: ['text', 'image'],
|
|
158
|
+
category: 'general',
|
|
159
|
+
recommended: true,
|
|
160
|
+
isCustom: false,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
id: 'cline-pass/o3-mini',
|
|
164
|
+
name: 'o3-mini',
|
|
165
|
+
description: 'Fast, cost-effective reasoning model specialized for STEM and coding.',
|
|
166
|
+
contextLength: 200000,
|
|
167
|
+
maxTokens: 65536,
|
|
168
|
+
input: ['text'],
|
|
169
|
+
category: 'reasoning',
|
|
170
|
+
recommended: true,
|
|
171
|
+
isCustom: false,
|
|
172
|
+
reasoningEfforts: ['low', 'medium', 'high'],
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'cline-pass/gemini-2.5-pro',
|
|
176
|
+
name: 'Gemini 2.5 Pro',
|
|
177
|
+
description: 'State-of-the-art multimodal reasoning model with extended context.',
|
|
178
|
+
contextLength: 1000000,
|
|
179
|
+
maxTokens: 8192,
|
|
180
|
+
input: ['text', 'image'],
|
|
181
|
+
category: 'multimodal',
|
|
182
|
+
recommended: true,
|
|
183
|
+
isCustom: false,
|
|
184
|
+
reasoningEfforts: ['low', 'medium', 'high'],
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'cline-pass/gemini-2.5-flash',
|
|
188
|
+
name: 'Gemini 2.5 Flash',
|
|
189
|
+
description: 'Ultra-fast multimodal model optimized for real-time agent workflows.',
|
|
190
|
+
contextLength: 1000000,
|
|
191
|
+
maxTokens: 8192,
|
|
192
|
+
input: ['text', 'image'],
|
|
193
|
+
category: 'general',
|
|
194
|
+
recommended: false,
|
|
195
|
+
isCustom: false,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: 'cline-pass/qwen-2.5-coder-32b',
|
|
199
|
+
name: 'Qwen 2.5 Coder 32B',
|
|
200
|
+
description: 'Open-weights powerhouse for code generation, refactoring and bug fixing.',
|
|
201
|
+
contextLength: 131072,
|
|
202
|
+
maxTokens: 8192,
|
|
203
|
+
input: ['text'],
|
|
204
|
+
category: 'coding',
|
|
205
|
+
recommended: false,
|
|
206
|
+
isCustom: false,
|
|
207
|
+
},
|
|
139
208
|
])
|
|
140
209
|
|
|
141
210
|
/**
|