@goodandready/dsh-clinebot 0.3.25 → 0.4.1
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 +32 -0
- package/README.md +21 -5
- package/README.ru.md +21 -5
- package/README.zh.md +21 -5
- package/lib/account-pool.js +59 -4
- package/lib/client.js +596 -200
- package/lib/cline-client.js +45 -45
- package/lib/config.js +25 -4
- package/lib/http.js +15 -2
- package/lib/index.js +117 -50
- package/lib/models.js +57 -25
- package/lib/provider-sync.js +60 -22
- package/lib/routes/accounts.js +178 -10
- package/lib/routes/auth.js +80 -38
- package/lib/routes/models.js +32 -29
- package/lib/routes/settings.js +83 -11
- package/lib/slash-command.js +91 -14
- package/lib/telemetry.js +78 -0
- package/package.json +4 -1
package/lib/routes/settings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { writeJson, readBody } from '../http.js'
|
|
2
2
|
import { isTrustedSettingsRequest, assertTrustedSettingsRequest } from '../access.js'
|
|
3
|
-
import { publicConfig, Config } from '../config.js'
|
|
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
6
|
|
|
@@ -33,22 +33,66 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
33
33
|
if (req.method !== 'PUT') {
|
|
34
34
|
return writeJson(res, 405, { ok: false, error: 'GET or PUT' })
|
|
35
35
|
}
|
|
36
|
-
if (!
|
|
37
|
-
return writeJson(res, 403, { ok: false, error: 'same-origin only' })
|
|
38
|
-
}
|
|
36
|
+
if (!assertTrustedSettingsRequest(req, res)) return
|
|
39
37
|
const settingsApi = getSettingsApi()
|
|
40
38
|
if (!settingsApi) {
|
|
41
39
|
return writeJson(res, 503, { ok: false, error: 'settings not ready' })
|
|
42
40
|
}
|
|
41
|
+
let bodyBuf
|
|
42
|
+
try {
|
|
43
|
+
bodyBuf = await readBody(req)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return writeJson(res, 400, { ok: false, error: String(err?.message || err) })
|
|
46
|
+
}
|
|
43
47
|
let payload
|
|
44
48
|
try {
|
|
45
|
-
payload = JSON.parse(
|
|
49
|
+
payload = JSON.parse(bodyBuf.toString('utf8') || '{}')
|
|
46
50
|
} catch {
|
|
47
51
|
return writeJson(res, 400, { ok: false, error: 'invalid json' })
|
|
48
52
|
}
|
|
49
|
-
|
|
53
|
+
const rawPayload = payload && typeof payload.config === 'object' && payload.config !== null ? payload.config : payload
|
|
54
|
+
if (!rawPayload || typeof rawPayload !== 'object' || Array.isArray(rawPayload)) {
|
|
55
|
+
return writeJson(res, 400, { ok: false, error: 'body must be a JSON object' })
|
|
56
|
+
}
|
|
57
|
+
if ('enabledModels' in rawPayload) {
|
|
58
|
+
return writeJson(res, 400, { ok: false, error: 'enabledModels is deprecated and not allowed; configure disabledModels instead' })
|
|
59
|
+
}
|
|
60
|
+
const allowedKeys = new Set(Object.keys(Config.dict || {}))
|
|
61
|
+
allowedKeys.delete('enabledModels')
|
|
62
|
+
for (const k of Object.keys(rawPayload)) {
|
|
63
|
+
if (!allowedKeys.has(k)) {
|
|
64
|
+
return writeJson(res, 400, { ok: false, error: `unknown config field: ${k}` })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (rawPayload.apiKeyEnv && !/^CLINEBOT_API_KEY(_[A-Z0-9]+)?$/.test(String(rawPayload.apiKeyEnv))) {
|
|
68
|
+
return writeJson(res, 400, { ok: false, error: `Disallowed apiKeyEnv: "${rawPayload.apiKeyEnv}". Must match ^CLINEBOT_API_KEY(_[A-Z0-9]+)?$` })
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(rawPayload.accounts)) {
|
|
71
|
+
for (const acc of rawPayload.accounts) {
|
|
72
|
+
if (!acc || typeof acc !== 'object') {
|
|
73
|
+
return writeJson(res, 400, { ok: false, error: 'Each account must be an object' })
|
|
74
|
+
}
|
|
75
|
+
const env = String(acc.apiKeyEnv || '').trim()
|
|
76
|
+
if (!/^CLINEBOT_API_KEY(_[A-Z0-9]+)?$/.test(env)) {
|
|
77
|
+
return writeJson(res, 400, { ok: false, error: `Disallowed account apiKeyEnv: "${env}". Must match ^CLINEBOT_API_KEY(_[A-Z0-9]+)?$` })
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (rawPayload.baseUrl) {
|
|
82
|
+
try {
|
|
83
|
+
const parsed = new URL(rawPayload.baseUrl)
|
|
84
|
+
if (parsed.protocol !== 'https:' && parsed.hostname !== 'localhost' && parsed.hostname !== '127.0.0.1') {
|
|
85
|
+
return writeJson(res, 400, { ok: false, error: 'Insecure baseUrl: remote endpoint must use https://' })
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
return writeJson(res, 400, { ok: false, error: 'Invalid baseUrl' })
|
|
89
|
+
}
|
|
90
|
+
}
|
|
50
91
|
try {
|
|
51
|
-
const
|
|
92
|
+
const base = plainConfig(live()) || {}
|
|
93
|
+
delete base.enabledModels
|
|
94
|
+
const merged = { ...base, ...rawPayload }
|
|
95
|
+
const parsed = Config(merged)
|
|
52
96
|
await settingsApi.replace(parsed)
|
|
53
97
|
await syncProviderState(parsed)
|
|
54
98
|
writeJson(res, 200, { ok: true, config: publicConfig(live()) })
|
|
@@ -77,15 +121,43 @@ export function registerSettingsRoutes(ctx, { live, getSettingsApi, syncProvider
|
|
|
77
121
|
|
|
78
122
|
const pub = publicConfig(live())
|
|
79
123
|
const targetEnvName = String(body.apiKeyEnv || pub.apiKeyEnv || DEFAULT_API_KEY_ENV).trim()
|
|
124
|
+
|
|
125
|
+
const isDefaultPattern = /^CLINEBOT_API_KEY(_[A-Z0-9]+)?$/.test(targetEnvName)
|
|
126
|
+
if (!isDefaultPattern) {
|
|
127
|
+
return writeJson(res, 400, {
|
|
128
|
+
ok: false,
|
|
129
|
+
error: `Disallowed apiKeyEnv: "${targetEnvName}". Must match ^CLINEBOT_API_KEY(_[A-Z0-9]+)?$.`,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const isConfiguredEnv = targetEnvName === DEFAULT_API_KEY_ENV ||
|
|
134
|
+
targetEnvName === pub.apiKeyEnv ||
|
|
135
|
+
(Array.isArray(pub.accounts) && pub.accounts.some(acc => acc && acc.apiKeyEnv === targetEnvName))
|
|
136
|
+
|
|
137
|
+
if (!isConfiguredEnv) {
|
|
138
|
+
return writeJson(res, 400, {
|
|
139
|
+
ok: false,
|
|
140
|
+
error: `Disallowed apiKeyEnv: "${targetEnvName}". Must be configured in settings accounts pool.`,
|
|
141
|
+
})
|
|
142
|
+
}
|
|
143
|
+
|
|
80
144
|
await saveCredentialKey(ctx, targetEnvName, apiKey)
|
|
81
145
|
|
|
82
146
|
await syncProviderState(live())
|
|
83
147
|
triggerAutoDiscover()
|
|
84
148
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
149
|
+
let validation = { ok: false, error: 'Skipped validation: baseUrl is not https' }
|
|
150
|
+
try {
|
|
151
|
+
const parsedUrl = new URL(pub.baseUrl)
|
|
152
|
+
if (parsedUrl.protocol === 'https:') {
|
|
153
|
+
validation = await smokeChat(pub.baseUrl, apiKey, {
|
|
154
|
+
model: pub.defaultModel,
|
|
155
|
+
timeoutMs: 15000,
|
|
156
|
+
})
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
validation = { ok: false, error: 'Invalid baseUrl' }
|
|
160
|
+
}
|
|
89
161
|
|
|
90
162
|
writeJson(res, 200, {
|
|
91
163
|
ok: true,
|
package/lib/slash-command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { publicConfig, Config } from './config.js'
|
|
1
|
+
import { publicConfig, plainConfig, Config } from './config.js'
|
|
2
2
|
import { resolveActiveAccountKey, formatProgressBar } from './provider-sync.js'
|
|
3
3
|
import {
|
|
4
4
|
getAllModels,
|
|
@@ -13,11 +13,12 @@ import {
|
|
|
13
13
|
probeHealth,
|
|
14
14
|
smokeChat,
|
|
15
15
|
fetchUsageLimits,
|
|
16
|
-
|
|
16
|
+
recordSmokeTest,
|
|
17
17
|
clearUsageCache,
|
|
18
18
|
clearProbeCache,
|
|
19
19
|
usageCache,
|
|
20
20
|
sessionStats,
|
|
21
|
+
getLastRotation,
|
|
21
22
|
} from './cline-client.js'
|
|
22
23
|
|
|
23
24
|
export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderState }) {
|
|
@@ -25,10 +26,8 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
25
26
|
const commands = cmdCtx.commands
|
|
26
27
|
if (typeof commands?.register !== 'function') return
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
description: 'Check ClinePass subscription quota, models, accounts and test connectivity (/cline [quota|models|accounts|switch <name>|test [model]|ping|rotate])',
|
|
31
|
-
execute: async (rawArgs) => {
|
|
29
|
+
const executeCommand = async (rawArgs, invocation = null) => {
|
|
30
|
+
try {
|
|
32
31
|
const pub = publicConfig(live())
|
|
33
32
|
const subcmd = String(rawArgs || '').trim().toLowerCase().split(/\s+/)[0] || 'quota'
|
|
34
33
|
const param = String(rawArgs || '').trim().split(/\s+/)[1] || ''
|
|
@@ -37,11 +36,18 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
37
36
|
if (subcmd === 'models') {
|
|
38
37
|
const allModels = getAllModels(pub.dynamicModels)
|
|
39
38
|
const disabledSet = new Set(pub.disabledModels || [])
|
|
39
|
+
const syncStatus = pub.planSynced
|
|
40
|
+
? `✅ Verified with plan (${pub.planSyncedAt ? new Date(pub.planSyncedAt).toLocaleDateString() : 'synced'})`
|
|
41
|
+
: '⚠️ Not verified with plan (fallback catalog)'
|
|
40
42
|
const lines = [
|
|
41
43
|
'### 🎯 ClinePass Models Catalog',
|
|
44
|
+
`* **Plan Sync**: ${syncStatus}`,
|
|
42
45
|
`* **Total models**: ${allModels.length} (${allModels.filter((m) => !disabledSet.has(m.id)).length} active)`,
|
|
43
|
-
'',
|
|
44
46
|
]
|
|
47
|
+
if (pub.defaultModelWarning) {
|
|
48
|
+
lines.push(`* ⚠️ **Warning**: ${pub.defaultModelWarning}`)
|
|
49
|
+
}
|
|
50
|
+
lines.push('')
|
|
45
51
|
for (const m of allModels) {
|
|
46
52
|
const active = !disabledSet.has(m.id) ? '✅' : '❌'
|
|
47
53
|
const isVis = isVisionModel(m.id, pub.dynamicModels) ? '📷 Vision' : '📝 Text'
|
|
@@ -67,6 +73,10 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
67
73
|
const usageInfo = cached?.windows?.fiveHour ? ` · ⏱ ${cached.windows.fiveHour.percentUsed}% used` : ''
|
|
68
74
|
lines.push(`* **${acc.label}** (\`${acc.apiKeyEnv}\`): ${statusBadge} ${pinBadge}${usageInfo}`)
|
|
69
75
|
}
|
|
76
|
+
const lastRot = getLastRotation()
|
|
77
|
+
if (lastRot) {
|
|
78
|
+
lines.push('', `* **Last Failover**: \`${lastRot.from}\` → \`${lastRot.to}\` (${lastRot.reason})`)
|
|
79
|
+
}
|
|
70
80
|
lines.push('', 'Switch active account: `/cline switch <env_variable_name>`')
|
|
71
81
|
return lines.join('\n')
|
|
72
82
|
}
|
|
@@ -80,7 +90,7 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
80
90
|
clearProbeCache()
|
|
81
91
|
const settingsApi = getSettingsApi()
|
|
82
92
|
if (settingsApi?.replace) {
|
|
83
|
-
const next = Config({ ...live(), activeAccount: param })
|
|
93
|
+
const next = Config({ ...plainConfig(live()), activeAccount: param })
|
|
84
94
|
await settingsApi.replace(next)
|
|
85
95
|
await syncProviderState(next)
|
|
86
96
|
return `✅ Active account switched to \`${param}\``
|
|
@@ -107,7 +117,33 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
107
117
|
return `❌ **Cline API Ping Failed**: ${health.error || 'Host unreachable'}`
|
|
108
118
|
}
|
|
109
119
|
|
|
110
|
-
// 6. Subcommand /cline
|
|
120
|
+
// 6. Subcommand /cline stats (real DSH stream metrics)
|
|
121
|
+
if (subcmd === 'stats' || subcmd === 'telemetry') {
|
|
122
|
+
const lines = [
|
|
123
|
+
'### 📈 DSH ClineBot Stream Telemetry',
|
|
124
|
+
`* **Requests**: ${sessionStats.successfulRequests} successful / ${sessionStats.totalRequests} total${sessionStats.abortedRequests ? ` (${sessionStats.abortedRequests} aborted)` : ''}${sessionStats.failedRequests ? ` (${sessionStats.failedRequests} failed)` : ''}`,
|
|
125
|
+
`* **Estimated Tokens**: ~${sessionStats.totalTokensEst} total (~${sessionStats.promptTokensEst} prompt, ~${sessionStats.completionTokensEst} completion)`,
|
|
126
|
+
`* **Last Stream Latency**: ${sessionStats.lastLatencyMs !== null ? `${sessionStats.lastLatencyMs} ms` : '—'}`,
|
|
127
|
+
`* **Last Request At**: ${sessionStats.lastRequestAt ? new Date(sessionStats.lastRequestAt).toLocaleTimeString() : '—'}`,
|
|
128
|
+
]
|
|
129
|
+
if (sessionStats.lastError) {
|
|
130
|
+
lines.push(`* **Last Stream Error**: ${sessionStats.lastError}`)
|
|
131
|
+
}
|
|
132
|
+
if (sessionStats.byModel && Object.keys(sessionStats.byModel).length) {
|
|
133
|
+
lines.push('', '**By Model:**')
|
|
134
|
+
for (const [mId, mStats] of Object.entries(sessionStats.byModel)) {
|
|
135
|
+
lines.push(`* \`${mId}\`: ${mStats.requests} reqs, ~${mStats.promptTokens + mStats.completionTokens} tokens`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (sessionStats.lastSmoke) {
|
|
139
|
+
const smoke = sessionStats.lastSmoke
|
|
140
|
+
const smokeTime = new Date(smoke.at).toLocaleTimeString()
|
|
141
|
+
lines.push('', `* **Last Smoke Test**: ${smoke.ok ? '✅ Passed' : '❌ Failed'} (${smoke.latencyMs} ms at ${smokeTime}${smoke.model ? `, model: \`${smoke.model}\`` : ''})`)
|
|
142
|
+
}
|
|
143
|
+
return lines.join('\n')
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 7. Subcommand /cline test [model] / smoke
|
|
111
147
|
if (subcmd === 'test' || subcmd === 'smoke') {
|
|
112
148
|
if (param && !isSupportedModel(param, pub.dynamicModels)) {
|
|
113
149
|
return `⚠️ **ClineBot**: Model \`${param}\` is not recognized. Use \`/cline models\` to see available models.`
|
|
@@ -121,12 +157,11 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
121
157
|
model: modelToTest,
|
|
122
158
|
timeoutMs: pub.smokeTimeoutMs,
|
|
123
159
|
})
|
|
124
|
-
|
|
160
|
+
recordSmokeTest({
|
|
125
161
|
latencyMs: outcome.latencyMs,
|
|
126
162
|
ok: outcome.ok,
|
|
127
163
|
error: outcome.error,
|
|
128
|
-
|
|
129
|
-
completionTokens: outcome.completionTokens || 10,
|
|
164
|
+
model: modelToTest,
|
|
130
165
|
})
|
|
131
166
|
|
|
132
167
|
let failoverNotice = ''
|
|
@@ -170,10 +205,15 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
170
205
|
`* **Host Ping**: ${health.ok ? `✅ ${health.latencyMs} ms` : '❌ Unreachable'}`,
|
|
171
206
|
`* **Active Key**: \`${activeKey.envName}\` (${activeKey.source})`,
|
|
172
207
|
`* **Default Model**: \`${pub.defaultModel}\``,
|
|
208
|
+
]
|
|
209
|
+
if (pub.defaultModelWarning) {
|
|
210
|
+
lines.push(`* ⚠️ **Warning**: ${pub.defaultModelWarning}`)
|
|
211
|
+
}
|
|
212
|
+
lines.push(
|
|
173
213
|
'',
|
|
174
214
|
`**⏱ 5-Hour Rolling Limit**: ${formatProgressBar(fiveHour?.percentUsed)} (reset: ${reset5h})`,
|
|
175
215
|
`**📅 Weekly Window**: ${formatProgressBar(weekly?.percentUsed)} (reset: ${resetWk})`,
|
|
176
|
-
|
|
216
|
+
)
|
|
177
217
|
|
|
178
218
|
if (fiveHour?.percentUsed >= 95) {
|
|
179
219
|
lines.push('', '🚨 **CRITICAL LIMIT**: 5-hour quota is 95%+ consumed! New requests may be throttled until reset.')
|
|
@@ -190,7 +230,44 @@ export function registerSlashCommand(ctx, { live, getSettingsApi, syncProviderSt
|
|
|
190
230
|
}
|
|
191
231
|
|
|
192
232
|
return lines.join('\n')
|
|
193
|
-
}
|
|
233
|
+
} catch (err) {
|
|
234
|
+
return `❌ **/cline error**: ${String(err?.message || err)}`
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const commandHandler = async (invocation) => {
|
|
239
|
+
const rawArgs = typeof invocation === 'string'
|
|
240
|
+
? invocation
|
|
241
|
+
: (invocation?.rawInput || invocation?.input || invocation?.text || invocation?.args || '')
|
|
242
|
+
try {
|
|
243
|
+
const output = await executeCommand(rawArgs, invocation)
|
|
244
|
+
return {
|
|
245
|
+
kind: 'success',
|
|
246
|
+
text: output,
|
|
247
|
+
// Support toString() for callers that treat result directly as string
|
|
248
|
+
toString() { return output },
|
|
249
|
+
}
|
|
250
|
+
} catch (err) {
|
|
251
|
+
return {
|
|
252
|
+
kind: 'error',
|
|
253
|
+
text: `❌ /cline error: ${String(err?.message || err)}`,
|
|
254
|
+
toString() { return `❌ /cline error: ${String(err?.message || err)}` },
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const commandExecute = async (rawArgs, invocation = null) => {
|
|
260
|
+
const result = await commandHandler(invocation || rawArgs)
|
|
261
|
+
return result.text
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const unregister = commands.register({
|
|
265
|
+
definitionId: '@goodandready/dsh-clinebot',
|
|
266
|
+
name: 'cline',
|
|
267
|
+
description: 'Check ClinePass subscription quota, models, accounts and test connectivity (/cline [quota|models|accounts|switch <name>|test [model]|ping|rotate])',
|
|
268
|
+
input: { hint: '[quota|models|accounts|switch <name>|test [model]|ping|rotate]' },
|
|
269
|
+
handler: commandHandler,
|
|
270
|
+
execute: commandExecute,
|
|
194
271
|
})
|
|
195
272
|
|
|
196
273
|
ctx.effect(() => () => unregister?.(), 'dsh-clinebot: slash-command')
|
package/lib/telemetry.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory runtime session metrics for ClinePass requests in DeepSeek Harness.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const sessionStats = {
|
|
6
|
+
totalRequests: 0,
|
|
7
|
+
successfulRequests: 0,
|
|
8
|
+
failedRequests: 0,
|
|
9
|
+
abortedRequests: 0,
|
|
10
|
+
promptTokensEst: 0,
|
|
11
|
+
completionTokensEst: 0,
|
|
12
|
+
totalTokensEst: 0,
|
|
13
|
+
lastLatencyMs: null,
|
|
14
|
+
lastRequestAt: null,
|
|
15
|
+
lastError: null,
|
|
16
|
+
byModel: {},
|
|
17
|
+
lastSmoke: null,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function recordSessionRequest({
|
|
21
|
+
latencyMs,
|
|
22
|
+
ok,
|
|
23
|
+
error,
|
|
24
|
+
aborted = false,
|
|
25
|
+
promptTokens = 0,
|
|
26
|
+
completionTokens = 0,
|
|
27
|
+
model,
|
|
28
|
+
}) {
|
|
29
|
+
sessionStats.totalRequests += 1
|
|
30
|
+
if (aborted) {
|
|
31
|
+
sessionStats.abortedRequests = (sessionStats.abortedRequests || 0) + 1
|
|
32
|
+
} else if (ok) {
|
|
33
|
+
sessionStats.successfulRequests += 1
|
|
34
|
+
sessionStats.lastLatencyMs = typeof latencyMs === 'number' ? latencyMs : null
|
|
35
|
+
sessionStats.lastError = null
|
|
36
|
+
} else {
|
|
37
|
+
sessionStats.failedRequests += 1
|
|
38
|
+
sessionStats.lastError = error || 'Request failed'
|
|
39
|
+
}
|
|
40
|
+
sessionStats.lastRequestAt = Date.now()
|
|
41
|
+
sessionStats.promptTokensEst += Number(promptTokens) || 0
|
|
42
|
+
sessionStats.completionTokensEst += Number(completionTokens) || 0
|
|
43
|
+
sessionStats.totalTokensEst += (Number(promptTokens) || 0) + (Number(completionTokens) || 0)
|
|
44
|
+
|
|
45
|
+
if (model) {
|
|
46
|
+
if (!sessionStats.byModel) sessionStats.byModel = {}
|
|
47
|
+
const m = sessionStats.byModel[model] || { requests: 0, promptTokens: 0, completionTokens: 0 }
|
|
48
|
+
m.requests += 1
|
|
49
|
+
m.promptTokens += Number(promptTokens) || 0
|
|
50
|
+
m.completionTokens += Number(completionTokens) || 0
|
|
51
|
+
sessionStats.byModel[model] = m
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function recordSmokeTest({ latencyMs, ok, error, model }) {
|
|
56
|
+
sessionStats.lastSmoke = {
|
|
57
|
+
at: Date.now(),
|
|
58
|
+
latencyMs: typeof latencyMs === 'number' ? latencyMs : null,
|
|
59
|
+
ok: Boolean(ok),
|
|
60
|
+
error: error || null,
|
|
61
|
+
model: model || null,
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function resetSessionStats() {
|
|
66
|
+
sessionStats.totalRequests = 0
|
|
67
|
+
sessionStats.successfulRequests = 0
|
|
68
|
+
sessionStats.failedRequests = 0
|
|
69
|
+
sessionStats.abortedRequests = 0
|
|
70
|
+
sessionStats.promptTokensEst = 0
|
|
71
|
+
sessionStats.completionTokensEst = 0
|
|
72
|
+
sessionStats.totalTokensEst = 0
|
|
73
|
+
sessionStats.lastLatencyMs = null
|
|
74
|
+
sessionStats.lastRequestAt = null
|
|
75
|
+
sessionStats.lastError = null
|
|
76
|
+
sessionStats.byModel = {}
|
|
77
|
+
sessionStats.lastSmoke = null
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-clinebot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "DeepSeek Harness companion for ClineBot / ClinePass: dynamic subscription models sync, quota exhaustion warnings, session metrics, dedicated settings page, live usage limits, and /cline slash-command.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -63,6 +63,9 @@
|
|
|
63
63
|
"@deepseek-ai/dsh-settings": "^0.1.0-rc.6",
|
|
64
64
|
"@deepseek-ai/schemastery": "^3.18.1"
|
|
65
65
|
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@deepseek-ai/schemastery": "^3.18.1"
|
|
68
|
+
},
|
|
66
69
|
"homepage": "https://github.com/GooDAnDReaDY/dsh-clinebot#readme",
|
|
67
70
|
"bugs": {
|
|
68
71
|
"url": "https://github.com/GooDAnDReaDY/dsh-clinebot/issues"
|