@goodandready/dsh-subscriptions 0.6.2 → 0.6.4
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/accounts.js +7 -1
- package/lib/http.js +1 -1
- package/lib/import-auth.js +20 -6
- package/lib/index.js +1 -1
- package/lib/routes/accounts.js +0 -1
- package/lib/routes/oauth.js +0 -1
- package/lib/routes/status.js +0 -1
- package/lib/routes.js +1 -9
- package/lib/ui-styles.js +13 -13
- package/lib/vendors/claude-cli.js +6 -2
- package/lib/vendors/cody.js +12 -1
- package/package.json +5 -5
- package/lib/atomic-lock.js +0 -35
- package/lib/preflight-tokens.js +0 -23
- package/lib/revocation.js +0 -25
package/lib/accounts.js
CHANGED
|
@@ -12,6 +12,8 @@ export function normalizeSlots(slots) {
|
|
|
12
12
|
const out = []
|
|
13
13
|
const seen = new Set()
|
|
14
14
|
for (const slot of Array.isArray(slots) ? slots : []) {
|
|
15
|
+
// User-editable config: a null or non-object entry must be skipped, not crash the plugin.
|
|
16
|
+
if (!slot || typeof slot !== 'object') continue
|
|
15
17
|
if (!isProvider(slot.provider)) continue
|
|
16
18
|
const index = Number(slot.index)
|
|
17
19
|
if (!Number.isInteger(index) || index < 1) continue
|
|
@@ -105,10 +107,14 @@ export function createAccountStore({ credentials, getConfig, fetchImpl, fetchFor
|
|
|
105
107
|
quarantines.delete(ref)
|
|
106
108
|
writeQueues.delete(ref)
|
|
107
109
|
for (const k of [...notifyThresholds.keys()]) { if (k.startsWith(ref + ':')) notifyThresholds.delete(k) }
|
|
108
|
-
for (const k of [...notifyThresholds.keys()]) { if (k.startsWith(ref + ':')) notifyThresholds.delete(k) }
|
|
109
110
|
refreshFailures.delete(ref)
|
|
110
111
|
usage.delete(ref)
|
|
111
112
|
usageFetched.delete(ref)
|
|
113
|
+
// Counters and health must not outlive the account, otherwise a
|
|
114
|
+
// re-added slot inherits stale rotation statistics.
|
|
115
|
+
requestCounts.delete(ref)
|
|
116
|
+
health.delete(ref)
|
|
117
|
+
windows.delete(ref)
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
async function describeRef(ref) {
|
package/lib/http.js
CHANGED
|
@@ -12,7 +12,7 @@ export function writeHtml(res, code, html) {
|
|
|
12
12
|
} catch { /* socket closed */ }
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
export function readBody(req, maxBytes) {
|
|
15
|
+
export function readBody(req, maxBytes = 64 * 1024) {
|
|
16
16
|
return new Promise((resolve, reject) => {
|
|
17
17
|
const chunks = []
|
|
18
18
|
let size = 0
|
package/lib/import-auth.js
CHANGED
|
@@ -4,8 +4,12 @@ import { join } from 'node:path'
|
|
|
4
4
|
import { readClaudeCredentials } from './vendors/claude-cli.js'
|
|
5
5
|
import { discoverCodingAgentConfigs } from './importers/coding-agents.js'
|
|
6
6
|
|
|
7
|
+
// Test seam: a fixture root overrides the real home for the duration of a
|
|
8
|
+
// public call. Undefined (the default) keeps production behaviour.
|
|
9
|
+
let activeHome = null
|
|
10
|
+
|
|
7
11
|
function homeFile(...parts) {
|
|
8
|
-
return join(homedir(), ...parts)
|
|
12
|
+
return join(activeHome || homedir(), ...parts)
|
|
9
13
|
}
|
|
10
14
|
|
|
11
15
|
async function readJson(path) {
|
|
@@ -17,7 +21,17 @@ async function readJson(path) {
|
|
|
17
21
|
}
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
export async function discoverLocalCliSessions() {
|
|
24
|
+
export async function discoverLocalCliSessions(options = {}) {
|
|
25
|
+
const prevHome = activeHome
|
|
26
|
+
activeHome = options && options.home ? String(options.home) : null
|
|
27
|
+
try {
|
|
28
|
+
return await discoverLocalCliSessionsInner()
|
|
29
|
+
} finally {
|
|
30
|
+
activeHome = prevHome
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function discoverLocalCliSessionsInner() {
|
|
21
35
|
const detected = {}
|
|
22
36
|
|
|
23
37
|
// 1. Codex CLI
|
|
@@ -144,7 +158,7 @@ export async function discoverLocalCliSessions() {
|
|
|
144
158
|
}
|
|
145
159
|
|
|
146
160
|
// 8. Claude CLI Direct Import
|
|
147
|
-
const claudeCreds = await readClaudeCredentials()
|
|
161
|
+
const claudeCreds = await readClaudeCredentials(activeHome)
|
|
148
162
|
if (claudeCreds) {
|
|
149
163
|
detected['claude-cli'] = {
|
|
150
164
|
provider: 'claude',
|
|
@@ -186,13 +200,13 @@ export async function discoverLocalCliSessions() {
|
|
|
186
200
|
return detected
|
|
187
201
|
}
|
|
188
202
|
|
|
189
|
-
export async function loadLocalCliBlob(provider) {
|
|
190
|
-
const discovered = await discoverLocalCliSessions()
|
|
203
|
+
export async function loadLocalCliBlob(provider, options = {}) {
|
|
204
|
+
const discovered = await discoverLocalCliSessions(options)
|
|
191
205
|
const info = discovered[provider]
|
|
192
206
|
if (!info) throw new Error(`no local CLI session found for ${provider}`)
|
|
193
207
|
|
|
194
208
|
if (provider === 'claude-cli' || (provider === 'claude' && info.provider === 'claude')) {
|
|
195
|
-
const creds = await readClaudeCredentials()
|
|
209
|
+
const creds = await readClaudeCredentials(options && options.home)
|
|
196
210
|
if (!creds) throw new Error('Claude CLI credentials not found')
|
|
197
211
|
return {
|
|
198
212
|
accessToken: creds.accessToken,
|
package/lib/index.js
CHANGED
|
@@ -141,7 +141,7 @@ export function apply(ctx, config) {
|
|
|
141
141
|
if (!cfg.ollamaFallback || !models.length) throw err
|
|
142
142
|
const model = cfg.ollamaFallbackModel || models[0].id
|
|
143
143
|
try { ctx.emit && ctx.emit('subscriptions.ollama-fallback', { provider, model, reason: err && err.code || 'EXHAUSTED' }) } catch {}
|
|
144
|
-
try { ctx.log && ctx.log.warn && ctx.log.warn(`[dsh-subscriptions] ${provider}: all accounts exhausted (${err && err.code || 'EXHAUSTED'}),
|
|
144
|
+
try { ctx.log && ctx.log.warn && ctx.log.warn(`[dsh-subscriptions] ${provider}: all accounts exhausted (${err && err.code || 'EXHAUSTED'}), falling back to ollama/${model}`) } catch {}
|
|
145
145
|
try {
|
|
146
146
|
recordHistory({
|
|
147
147
|
provider: 'ollama',
|
package/lib/routes/accounts.js
CHANGED
package/lib/routes/oauth.js
CHANGED
package/lib/routes/status.js
CHANGED
package/lib/routes.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { publicConfig } from './config-schema.js'
|
|
2
2
|
import { PROVIDERS, displayName } from './refs.js'
|
|
3
|
-
import { writeHtml, writeJson } from './http.js'
|
|
3
|
+
import { escapeHtml, writeHtml, writeJson } from './http.js'
|
|
4
4
|
import { registerStatusRoutes } from './routes/status.js'
|
|
5
5
|
import { registerOauthRoutes } from './routes/oauth.js'
|
|
6
6
|
import { registerAccountsRoutes } from './routes/accounts.js'
|
|
@@ -11,7 +11,6 @@ export function registerRoutes(ctx, state) {
|
|
|
11
11
|
accountsView,
|
|
12
12
|
history,
|
|
13
13
|
live,
|
|
14
|
-
subscriptions,
|
|
15
14
|
} = state
|
|
16
15
|
|
|
17
16
|
async function configResponse() {
|
|
@@ -101,10 +100,3 @@ function esc(x){return String(x==null?'':x).replace(/&/g,'&').replace(/</g,'
|
|
|
101
100
|
registerAccountsRoutes(ctx, state)
|
|
102
101
|
registerProxyRoutes(ctx, state)
|
|
103
102
|
}
|
|
104
|
-
|
|
105
|
-
export function escapeHtml(text) {
|
|
106
|
-
return String(text)
|
|
107
|
-
.replace(/&/g, '&')
|
|
108
|
-
.replace(/</g, '<')
|
|
109
|
-
.replace(/>/g, '>')
|
|
110
|
-
}
|
package/lib/ui-styles.js
CHANGED
|
@@ -2,14 +2,14 @@ export const CSS =
|
|
|
2
2
|
'.dsub-card{list-style:none;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-3);border-radius:12px;transition:border-color .16s,background .16s}' +
|
|
3
3
|
'.dsub-card:hover{border-color:var(--dsw-alias-label-dimmed)}' +
|
|
4
4
|
'.dsub-cardOpen{background:var(--dsw-alias-bg-layer-2);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
5
|
-
'.dsub-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:
|
|
5
|
+
'.dsub-header{appearance:none;width:100%;font:inherit;color:inherit;text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;align-items:center;gap:12px;padding:18px 20px;display:flex}' +
|
|
6
6
|
'.dsub-header:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:-2px}' +
|
|
7
7
|
'.dsub-headText{flex-direction:column;flex:1;gap:4px;min-width:0;display:flex}' +
|
|
8
|
-
'.dsub-name{color:var(--dsw-alias-label-primary);font-size:
|
|
8
|
+
'.dsub-name{color:var(--dsw-alias-label-primary);font-size:16px;font-weight:600;line-height:1.4}' +
|
|
9
9
|
'.dsub-description{color:var(--dsw-alias-label-secondary);font-size:13px}' +
|
|
10
10
|
'.dsub-chev{color:var(--dsw-alias-label-tertiary);flex:none;transition:transform .16s}' +
|
|
11
11
|
'.dsub-chevOpen{transform:rotate(180deg)}' +
|
|
12
|
-
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0
|
|
12
|
+
'.dsub-body{border-top:1px solid var(--dsw-alias-border-l2);margin:0 20px;padding-bottom:12px}' +
|
|
13
13
|
'.dsub-block{margin-top:10px}' +
|
|
14
14
|
'.dsub-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-top:8px}' +
|
|
15
15
|
'.dsub-grow{flex:1 1 180px;min-width:140px}' +
|
|
@@ -25,13 +25,13 @@ export const CSS =
|
|
|
25
25
|
'.dsub-ok{font-size:12px;color:var(--dsw-alias-state-success-primary)}' +
|
|
26
26
|
'.dsub-bad{font-size:12px;color:var(--dsw-alias-state-error-primary);overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
|
|
27
27
|
'.dsub-hint{overflow-wrap:anywhere;word-break:break-word;max-width:100%}' +
|
|
28
|
-
'.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:
|
|
28
|
+
'.dsub-btn{appearance:none;font:inherit;cursor:pointer;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:7px 14px;font-size:13px;font-weight:500;background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);transition:all .15s}' +
|
|
29
29
|
'.dsub-btn:hover{background:var(--dsw-alias-bg-layer-1);border-color:var(--dsw-alias-label-dimmed)}' +
|
|
30
|
-
'.dsub-inp{height:
|
|
30
|
+
'.dsub-inp{height:36px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);color:var(--dsw-alias-label-primary);border-radius:8px;padding:0 12px;font-size:13px;min-width:180px}' +
|
|
31
31
|
'.dsub-inp:focus{outline:none;border-color:var(--dsw-alias-brand-primary)}' +
|
|
32
|
-
'.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:
|
|
32
|
+
'.dsub-box{border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:12px 14px;margin-top:8px;background:var(--dsw-alias-bg-layer-2)}' +
|
|
33
33
|
'.dsub-cooldown{display:inline-block;padding:2px 6px;border-radius:4px;background:var(--dsw-alias-state-warning-primary);color:#000;font-size:11px;font-weight:600}' +
|
|
34
|
-
'.dsub-bar{position:relative;height:
|
|
34
|
+
'.dsub-bar{position:relative;height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden;flex:1;min-width:80px}' +
|
|
35
35
|
'.dsub-barFill{height:100%;border-radius:3px;background:var(--dsw-alias-state-success-primary);transition:width .2s}' +
|
|
36
36
|
'.dsub-barWarn .dsub-barFill{background:var(--dsw-alias-state-warning-primary)}' +
|
|
37
37
|
'.dsub-barFull .dsub-barFill{background:var(--dsw-alias-state-error-primary)}' +
|
|
@@ -62,11 +62,11 @@ export const CSS =
|
|
|
62
62
|
'.dsub-heroModel{font-size:11.5px;color:var(--dsw-alias-label-secondary);font-family:ui-monospace,monospace}' +
|
|
63
63
|
'.dsub-heroBars{display:flex;flex-direction:column;gap:8px;margin-top:10px}' +
|
|
64
64
|
'.dsub-barLabelRow{display:flex;justify-content:space-between;font-size:11.5px;color:var(--dsw-alias-label-secondary);margin-bottom:3px}' +
|
|
65
|
-
'.dsub-barTrack{height:
|
|
65
|
+
'.dsub-barTrack{height:10px;border-radius:999px;background:var(--dsw-alias-bg-layer-1);overflow:hidden}' +
|
|
66
66
|
'.dsub-barFillGrad{height:100%;border-radius:3px;transition:width .3s ease}' +
|
|
67
|
-
'.dsub-panel-box{margin-top:10px;padding:
|
|
67
|
+
'.dsub-panel-box{margin-top:10px;padding:14px 16px;border-radius:10px;border:1px solid var(--dsw-alias-border-l2);background:var(--dsw-alias-bg-layer-2);display:flex;flex-direction:column;gap:8px}' +
|
|
68
68
|
'.dsub-panel-warn{border-color:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 40%,transparent);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 5%,transparent)}' +
|
|
69
|
-
'.dsub-panel-title{font-size:
|
|
69
|
+
'.dsub-panel-title{font-size:16px;font-weight:600;color:var(--dsw-alias-label-primary);display:flex;align-items:center;justify-content:space-between}' +
|
|
70
70
|
'.dsub-panel-desc{font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.4}' +
|
|
71
71
|
'.dsub-usage-grid{display:flex;flex-direction:column;gap:8px;margin-top:4px}' +
|
|
72
72
|
'.dsub-usage-card{display:flex;flex-direction:column;gap:5px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2);border-radius:8px;background:var(--dsw-alias-bg-layer-1)}' +
|
|
@@ -74,9 +74,9 @@ export const CSS =
|
|
|
74
74
|
'.dsub-usage-track{width:100%;height:8px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);border:1px solid var(--dsw-alias-border-l2);overflow:hidden}' +
|
|
75
75
|
'.dsub-usage-fill{height:100%;border-radius:999px;transition:width .3s ease}' +
|
|
76
76
|
'.dsub-usage-meta{display:flex;justify-content:space-between;align-items:center;font-size:11px;color:var(--dsw-alias-label-secondary)}' +
|
|
77
|
-
'.dsub-tag-ok{border-color:
|
|
78
|
-
'.dsub-tag-warn{border-color:
|
|
79
|
-
'.dsub-tag-bad{border-color:
|
|
77
|
+
'.dsub-tag-ok{border-color:var(--dsw-alias-state-success-primary);color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
78
|
+
'.dsub-tag-warn{border-color:var(--dsw-alias-state-warning-primary);color:var(--dsw-alias-state-warning-primary);background:color-mix(in srgb,var(--dsw-alias-state-warning-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
79
|
+
'.dsub-tag-bad{border-color:var(--dsw-alias-state-error-primary);color:var(--dsw-alias-state-error-primary);background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 8%,transparent);padding:3px 10px;border-radius:999px;font-size:12px;font-weight:600;border-style:solid;border-width:1px}' +
|
|
80
80
|
'.dsub-slot-meta-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px;padding-top:10px;border-top:1px solid var(--dsw-alias-border-l2);font-size:12px}' +
|
|
81
81
|
'.dsub-ref-tag{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--dsw-alias-label-secondary)}' +
|
|
82
82
|
'.dsub-ref-tag code{padding:2px 6px;border-radius:4px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);font-family:ui-monospace,monospace;color:var(--dsw-alias-label-primary);font-size:11px}' +
|
|
@@ -2,8 +2,12 @@ import { readFile } from 'node:fs/promises'
|
|
|
2
2
|
import { homedir } from 'node:os'
|
|
3
3
|
import { join } from 'node:path'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Read Claude Code CLI credentials. `home` is a test seam: when omitted the
|
|
7
|
+
* real user home is used, so production behaviour is unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export async function readClaudeCredentials(home) {
|
|
10
|
+
const p = join(home || homedir(), '.claude', 'credentials.json')
|
|
7
11
|
try {
|
|
8
12
|
const text = await readFile(p, 'utf8')
|
|
9
13
|
const json = JSON.parse(text)
|
package/lib/vendors/cody.js
CHANGED
|
@@ -16,6 +16,11 @@ export const CODY_MODELS = [
|
|
|
16
16
|
}
|
|
17
17
|
]
|
|
18
18
|
|
|
19
|
+
/** Catalog id -> Sourcegraph model name. */
|
|
20
|
+
export const CODY_MODEL_ALIASES = {
|
|
21
|
+
'cody-claude-3.5-sonnet': 'claude-3-5-sonnet',
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
export function providerInfo() {
|
|
20
25
|
return { id, name: 'Sourcegraph Cody' }
|
|
21
26
|
}
|
|
@@ -50,8 +55,14 @@ export async function* streamOnce({ blob, options, fetchImpl, headers, config, s
|
|
|
50
55
|
if (!token) throw new LlmError('Sourcegraph Cody not authenticated', 'AUTH')
|
|
51
56
|
|
|
52
57
|
const url = `${(config && config.apiBase) || CODY_API_BASE}/chat/completions`
|
|
58
|
+
// Catalog ids carry a cody- prefix and dotted versions; the Sourcegraph
|
|
59
|
+
// API expects the bare dashed name (cody-claude-3.5-sonnet -> claude-3-5-sonnet).
|
|
60
|
+
const requested = String(options.model || '').trim()
|
|
61
|
+
const mapped = CODY_MODEL_ALIASES[requested]
|
|
62
|
+
|| requested.replace(/^cody-/, '').replace(/\./g, '-')
|
|
63
|
+
|| 'claude-3-5-sonnet'
|
|
53
64
|
const body = {
|
|
54
|
-
model:
|
|
65
|
+
model: mapped,
|
|
55
66
|
messages: openaiMessages(options),
|
|
56
67
|
stream: true
|
|
57
68
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-subscriptions",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.4",
|
|
4
4
|
"description": "Use ChatGPT Codex, Claude, Grok, Antigravity, Kimi, GLM, Cursor, Kiro, Copilot, Qwen, ERNIE, Spark, JetBrains, Perplexity, Replit, and Cody subscriptions as DeepSeek Harness LLM providers via OAuth.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -34,6 +34,9 @@
|
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/GooDAnDReaDY/dsh-subscriptions/issues"
|
|
36
36
|
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "eslint lib/ && node --test test/*.test.mjs"
|
|
39
|
+
},
|
|
37
40
|
"dsh": {
|
|
38
41
|
"bundle": {
|
|
39
42
|
"patch": "./cordis.patch.yml"
|
|
@@ -60,8 +63,5 @@
|
|
|
60
63
|
},
|
|
61
64
|
"devDependencies": {
|
|
62
65
|
"@deepseek-ai/dsh-llm": "0.1.0-rc.8"
|
|
63
|
-
},
|
|
64
|
-
"scripts": {
|
|
65
|
-
"test": "eslint lib/ && node --test test/*.test.mjs"
|
|
66
66
|
}
|
|
67
|
-
}
|
|
67
|
+
}
|
package/lib/atomic-lock.js
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { open, unlink } from 'node:fs/promises'
|
|
2
|
-
|
|
3
|
-
export async function withFileLock(filePath, fn, timeoutMs = 5000) {
|
|
4
|
-
const lockPath = `${filePath}.lock`
|
|
5
|
-
const start = Date.now()
|
|
6
|
-
let handle = null
|
|
7
|
-
|
|
8
|
-
while (Date.now() - start < timeoutMs) {
|
|
9
|
-
try {
|
|
10
|
-
handle = await open(lockPath, 'wx')
|
|
11
|
-
break
|
|
12
|
-
} catch (err) {
|
|
13
|
-
if (err.code === 'EEXIST') {
|
|
14
|
-
await new Promise((r) => setTimeout(r, 40))
|
|
15
|
-
continue
|
|
16
|
-
}
|
|
17
|
-
throw err
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
if (!handle) {
|
|
22
|
-
throw new Error(`Failed to acquire lock on ${lockPath} after ${timeoutMs}ms`)
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
return await fn()
|
|
27
|
-
} finally {
|
|
28
|
-
try {
|
|
29
|
-
await handle.close()
|
|
30
|
-
await unlink(lockPath)
|
|
31
|
-
} catch {
|
|
32
|
-
// ignore unlock errors
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
package/lib/preflight-tokens.js
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export function estimateTokens(text) {
|
|
2
|
-
if (!text || typeof text !== 'string') return 0
|
|
3
|
-
// Average English: ~4 chars per token; CJK/Code: ~1.5 - 2 chars per token
|
|
4
|
-
const hasCjk = /[\u4e00-\u9fa5]/.test(text)
|
|
5
|
-
const ratio = hasCjk ? 2 : 3.8
|
|
6
|
-
return Math.max(1, Math.ceil(text.length / ratio))
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function validateContextLimit(messages, contextWindowLimit = 128000) {
|
|
10
|
-
let totalChars = 0
|
|
11
|
-
for (const m of messages || []) {
|
|
12
|
-
const content = typeof m.content === 'string' ? m.content : JSON.stringify(m.content || '')
|
|
13
|
-
totalChars += content.length
|
|
14
|
-
}
|
|
15
|
-
const estimated = estimateTokens(totalChars > 0 ? 'x'.repeat(totalChars) : '')
|
|
16
|
-
const safe = estimated <= contextWindowLimit * 0.95
|
|
17
|
-
return {
|
|
18
|
-
estimatedTokens: estimated,
|
|
19
|
-
limit: contextWindowLimit,
|
|
20
|
-
isSafe: safe,
|
|
21
|
-
marginTokens: Math.max(0, Math.floor(contextWindowLimit * 0.95 - estimated))
|
|
22
|
-
}
|
|
23
|
-
}
|
package/lib/revocation.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
const REVOKED_ERRORS = new Set([
|
|
2
|
-
'invalid_grant',
|
|
3
|
-
'token_revoked',
|
|
4
|
-
'account_deactivated',
|
|
5
|
-
'session_expired'
|
|
6
|
-
])
|
|
7
|
-
|
|
8
|
-
export function isTokenRevokedError(err) {
|
|
9
|
-
if (!err) return false
|
|
10
|
-
const msg = String(err.message || err.error || err.code || '').toLowerCase()
|
|
11
|
-
for (const pattern of REVOKED_ERRORS) {
|
|
12
|
-
if (msg.includes(pattern)) return true
|
|
13
|
-
}
|
|
14
|
-
return false
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function handleAccountRevocation(account) {
|
|
18
|
-
return {
|
|
19
|
-
...account,
|
|
20
|
-
hasToken: false,
|
|
21
|
-
status: 'revoked',
|
|
22
|
-
needsReauth: true,
|
|
23
|
-
revokedAt: Date.now()
|
|
24
|
-
}
|
|
25
|
-
}
|