@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/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 resolveKeyValue(ctx, apiKeyEnv) {
124
- const refName = String(apiKeyEnv || DEFAULT_API_KEY_ENV).trim() || DEFAULT_API_KEY_ENV
125
- const creds = (ctx?.get && ctx.get('credentials')) || ctx?.credentials
126
- if (creds && typeof creds.resolve === 'function') {
127
- try {
128
- const hit = await creds.resolve(credentialRef(refName))
129
- if (hit?.value) {
130
- return { envName: refName, value: hit.value, source: 'credentials' }
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
- const health = await probeHealth(pub.baseUrl, { timeoutMs: probeTimeout })
214
- const isRegistered = await checkRegisteredInPiAi(ctx)
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
- if (key.value) {
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, key.value, { timeoutMs: probeTimeout }).catch(() => null)
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-часовой лимит почти полностью исчерпан (${pct}%). Новые запросы могут отклоняться провайдером до сброса.`,
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: `Внимание: израсходовано ${pct}% 5-часового скользящего лимита.`,
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
- writeJson(res, outcome.ok ? 200 : 502, outcome)
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 session stats (/cline [quota|models|accounts|switch <name>])',
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
- `* **Активный аккаунт**: \`${pub.activeAccount || pub.apiKeyEnv}\``,
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
- lines.push(`* **${acc.label}** (\`${acc.apiKeyEnv}\`): ${statusBadge} ${pinBadge}`)
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('', 'Переключить активный аккаунт: `/cline switch <имя_переменной>`')
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 '⚠️ Укажите аккаунт: `/cline switch <CLINEBOT_API_KEY_2>`'
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 `✅ Активный аккаунт переключен на \`${param}\``
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. Default /cline quota
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-ключ не настроен. Откройте **Настройки → ClineBot** и сохраните ключ.'
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
- `* **Пинг хоста**: ${health.ok ? `✅ ${health.latencyMs} мс` : '❌ Недоступен'}`,
879
- `* **Активный ключ**: \`${activeKey.envName}\` (${activeKey.source})`,
880
- `* **Модель по умолчанию**: \`${pub.defaultModel}\``,
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-часовое окно**: ${formatProgressBar(fiveHour?.percentUsed)} (сброс: ${reset5h})`,
883
- `**📅 Недельное окно**: ${formatProgressBar(weekly?.percentUsed)} (сброс: ${resetWk})`,
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('', '🚨 **КРИТИЧЕСКИЙ ЛИМИТ**: 5-часовая квота израсходована на 95%+. Запросы могут быть заблокированы до сброса!')
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('', '⚠️ **Внимание**: 5-часовая квота израсходована на ' + fiveHour.percentUsed + '%.')
917
+ lines.push('', `⚠️ **Warning**: 5-hour quota is ${fiveHour.percentUsed}% consumed.`)
890
918
  }
891
919
 
892
920
  if (sessionStats.totalRequests > 0) {
893
- lines.push('', `**📊 Сессия DSH**: ${sessionStats.successfulRequests}/${sessionStats.totalRequests} успешных запросов, ~${sessionStats.totalTokensEst} токенов`)
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(`* **Аккаунт**: \`${usage.user.email}\``)
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 key = await resolveKeyValue(ctx, pub.apiKeyEnv)
921
- const res = await smokeChat(pub.baseUrl, key.value, { model: model || pub.defaultModel })
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
  /**