dsh-multi-router 0.5.9 → 0.6.0

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +26 -1
  3. package/cordis.patch.yml +2 -2
  4. package/lib/accounts.js +4 -4
  5. package/lib/api-service.js +11 -0
  6. package/lib/api-service.js.map +7 -0
  7. package/lib/chunk-4V52V5GO.js +105 -0
  8. package/lib/chunk-4V52V5GO.js.map +7 -0
  9. package/lib/{chunk-S6ZQ45SS.js → chunk-JUOESKAK.js} +3 -3
  10. package/lib/{chunk-P43AWJ4Q.js → chunk-LLDJVBTQ.js} +3 -3
  11. package/lib/{chunk-U2MU2M5C.js → chunk-NSKCFVHP.js} +13 -111
  12. package/lib/chunk-NSKCFVHP.js.map +7 -0
  13. package/lib/{chunk-I4EW5TI7.js → chunk-OCMOMSB4.js} +46 -14
  14. package/lib/chunk-OCMOMSB4.js.map +7 -0
  15. package/lib/{chunk-FTM7RHQV.js → chunk-OK2T2UAZ.js} +4 -4
  16. package/lib/{chunk-J3XO7RTF.js → chunk-ROZINFTV.js} +3 -2
  17. package/lib/chunk-ROZINFTV.js.map +7 -0
  18. package/lib/chunk-SNEZMYNU.js +332 -0
  19. package/lib/chunk-SNEZMYNU.js.map +7 -0
  20. package/lib/{chunk-MCHV2R25.js → chunk-WVNY4LSL.js} +3 -1
  21. package/lib/{chunk-MCHV2R25.js.map → chunk-WVNY4LSL.js.map} +2 -2
  22. package/lib/cli.js +7 -6
  23. package/lib/cli.js.map +1 -1
  24. package/lib/client.js +340 -7
  25. package/lib/client.js.map +2 -2
  26. package/lib/clients.js +6 -5
  27. package/lib/{host-v0.5.9.js → host-v0.6.0.js} +18 -13
  28. package/lib/host.js +18 -13
  29. package/lib/index.js +18 -13
  30. package/lib/index.js.map +2 -2
  31. package/lib/manager.js +11 -7
  32. package/lib/router.js +2 -2
  33. package/lib/shim.js +3 -3
  34. package/package.json +1 -1
  35. package/lib/chunk-I4EW5TI7.js.map +0 -7
  36. package/lib/chunk-J3XO7RTF.js.map +0 -7
  37. package/lib/chunk-U2MU2M5C.js.map +0 -7
  38. /package/lib/{chunk-S6ZQ45SS.js.map → chunk-JUOESKAK.js.map} +0 -0
  39. /package/lib/{chunk-P43AWJ4Q.js.map → chunk-LLDJVBTQ.js.map} +0 -0
  40. /package/lib/{chunk-FTM7RHQV.js.map → chunk-OK2T2UAZ.js.map} +0 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/telemetry.mjs", "../src/router.mjs"],
4
+ "sourcesContent": ["import { createHash, randomUUID } from 'node:crypto';\nimport { assistantStreamFirstTokenTime } from '@deepseek-ai/dsh-llm';\nimport { readFile } from 'node:fs/promises';\nimport { writePrivateJson } from './config.mjs';\nconst empty = () => ({ requests: 0, failures: 0, input: 0, output: 0, tokens: 0, unmetered: 0 });\nconst day = at => new Date(at).toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' });\nconst safe = n => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : 0;\nconst measured = n => typeof n === 'number' && Number.isFinite(n) && n >= 0 ? n : null;\nexport class Telemetry {\n constructor(file, now = Date.now) { this.file = file; this.now = now; this.retentionDays = 30; this.data = { accounts: {}, routes: {}, days: {}, rounds: {}, expiringRounds: {}, events: [] }; this.queue = Promise.resolve(); }\n async load() {\n try { const saved = JSON.parse(await readFile(this.file, 'utf8')); if (saved.version !== 1) throw Error('Unsupported telemetry'); this.data = { ...saved, expiringRounds: saved.expiringRounds ?? {} }; }\n catch (e) { if (e.code !== 'ENOENT') throw e; }\n if (this.prune()) await this.persist();\n }\n configure(retentionDays) { this.retentionDays = retentionDays; }\n prune() {\n const cutoff = this.now() - this.retentionDays * 86400000, before = this.data.events.length;\n this.data.events = this.data.events.filter(e => Number.isFinite(e.at) && e.at >= cutoff);\n return before !== this.data.events.length;\n }\n row(group, key) { if (!Object.hasOwn(this.data[group], key)) Object.defineProperty(this.data[group], key, { value: empty(), enumerable: true, writable: true }); return this.data[group][key]; }\n count(alias, account) { return this.row('routes', JSON.stringify([alias, account])); }\n expiring(alias) {\n if (!Object.hasOwn(this.data.expiringRounds, alias)) Object.defineProperty(this.data.expiringRounds, alias, { value: { accounts: [], completed: [], cooldownUntil: 0, number: 1 }, enumerable: true, writable: true });\n return this.data.expiringRounds[alias];\n }\n restartExpiring(alias) { const row = this.expiring(alias); row.accounts = []; row.completed = []; row.cooldownUntil = 0; row.number++; return this.persist(); }\n round(alias, signature) {\n if (!Object.hasOwn(this.data.rounds, alias) || this.data.rounds[alias].signature !== signature) Object.defineProperty(this.data.rounds, alias, { value: { signature, number: 1, bases: {} }, enumerable: true, writable: true });\n return this.data.rounds[alias];\n }\n advance(alias, candidates, signature) {\n const round = this.round(alias, signature); round.number++;\n for (const c of candidates) Object.defineProperty(round.bases, c.account.id, { value: { tokens: this.count(alias, c.account.id).tokens, credits: this.row('accounts', c.account.id).creditsObserved ?? 0 }, enumerable: true, writable: true, configurable: true });\n return this.persist();\n }\n observe(account, snapshot) {\n const row = this.row('accounts', account);\n if (Number.isFinite(snapshot.remaining) && snapshot.unit === 'credits') {\n const sameCycle = row.balance?.cycle === snapshot.cycle;\n const change = sameCycle && Number.isFinite(row.balance?.remaining) ? Math.max(0, row.balance.remaining - snapshot.remaining) : 0;\n row.creditsObserved = (row.creditsObserved ?? 0) + change;\n }\n row.balance = snapshot;\n return this.persist();\n }\n record(event) {\n const at = this.now();\n const rows = [this.row('accounts', event.account), this.count(event.alias, event.account), this.row('days', day(at))];\n for (const row of rows) {\n row.requests++; if (!event.ok) row.failures++;\n if (event.usage) { const input = safe(event.usage.prompt_tokens), output = safe(event.usage.completion_tokens); row.input += input; row.output += output; row.tokens += safe(event.usage.total_tokens) || input + output; }\n else if (event.status === 200 || event.accepted) row.unmetered++;\n }\n const input = measured(event.usage?.prompt_tokens), output = measured(event.usage?.completion_tokens);\n this.data.events.unshift({ at, account: event.account, provider: event.provider, alias: event.alias, model: event.model ?? null, status: event.status,\n outcome: event.ok ? 'completed' : event.cancelled ? 'cancelled' : 'failed', input, output,\n tokens: measured(event.usage?.total_tokens) ?? (input !== null && output !== null ? input + output : null),\n cachedInput: measured(event.usage?.prompt_tokens_details?.cached_tokens), reasoningOutput: measured(event.usage?.completion_tokens_details?.reasoning_tokens),\n callId: event.callId ?? null, session: event.session ?? null, source: event.source === 'api' ? 'api' : 'dsh',\n timing: event.timing ? Object.fromEntries(['modelMs', 'ttftMs', 'decodeMs', 'decodeTokens'].map(k => [k, measured(event.timing[k])])) : null, timingSource: event.timing ? 'upstream' : null });\n const dates = Object.keys(this.data.days).sort(); for (const key of dates.slice(0, -31)) delete this.data.days[key];\n return this.persist();\n }\n persist() { this.prune(); const saved = structuredClone({ ...this.data, version: 1 }); this.queue = this.queue.catch(() => {}).then(() => writePrivateJson(this.file, saved)); return this.queue; }\n state() { this.prune(); return { ...structuredClone(this.data), retentionDays: this.retentionDays, today: day(this.now()), updatedAt: this.now() }; }\n}\n\n// Pass bytes through unchanged; parse only usage and terminal/error markers, never persist content.\nexport function meterResponse(response, complete, signal, { now = () => performance.now(), startedAt = now() } = {}) {\n if (!response.body) return response;\n const reader = response.body.getReader(), decoder = new TextDecoder();\n let pending = '', usage, ended = false, failed = false, finished = false, firstToken = null, terminalAt = null;\n async function finish(cancelled = false) { if (finished) return; finished = true; const end = terminalAt ?? now(); await complete({ usage, ok: ended && !failed && !cancelled, cancelled, timing: { modelMs: Math.max(0, end - startedAt), ttftMs: firstToken === null ? null : Math.max(0, firstToken - startedAt), decodeMs: firstToken === null ? null : Math.max(0, end - firstToken), decodeTokens: firstToken === null ? null : measured(usage?.completion_tokens) } }); }\n function inspect(bytes) {\n pending += decoder.decode(bytes, { stream: true });\n let split;\n while ((split = /\\r?\\n\\r?\\n/.exec(pending))) {\n const frame = pending.slice(0, split.index); pending = pending.slice(split.index + split[0].length);\n const data = frame.split(/\\r?\\n/).filter(l => l.startsWith('data:')).map(l => l.slice(5).trimStart()).join('\\n');\n if (data === '[DONE]') { ended = true; terminalAt ??= now(); continue; }\n try { const value = JSON.parse(data); if (value.usage && ['prompt_tokens', 'completion_tokens', 'total_tokens'].some(k => Number.isFinite(value.usage[k]) && value.usage[k] >= 0)) usage = value.usage; if (value.error) failed = true; if (firstToken === null && value.choices?.some(c => { const d = c.delta; return [d?.content, d?.reasoning_content, d?.reasoning].some(v => typeof v === 'string' && v.trim()) || d?.tool_calls?.some(t => t.function?.name || t.function?.arguments); })) firstToken = now(); if (value.choices?.some(c => c.finish_reason)) { ended = true; terminalAt ??= now(); } } catch { /* Unknown frame is forwarded. */ }\n }\n if (pending.length > 1024 * 1024) { pending = ''; failed = true; }\n }\n const body = new ReadableStream({\n async pull(controller) {\n try { signal?.throwIfAborted(); const chunk = await reader.read(); if (chunk.done) { await finish(); controller.close(); } else { inspect(chunk.value); controller.enqueue(chunk.value); } }\n catch (e) { await finish(signal?.aborted); controller.error(e); await reader.cancel().catch(() => {}); }\n },\n async cancel(reason) { await reader.cancel(reason).catch(() => {}); await finish(!ended); },\n });\n return new Response(body, { status: response.status, headers: response.headers });\n}\n\n// Link local transport attempts to DSH steps without retaining titles, prompts,\n// tool names/arguments/results, or the original session identity.\nexport class SessionMetrics {\n constructor(telemetry) { this.telemetry = telemetry; this.steps = new Map(); }\n key(id) { return createHash('sha256').update(String(id)).digest('hex').slice(0, 24); }\n beginCall(sessionId, purpose) {\n const callId = randomUUID(), session = sessionId == null ? null : this.key(sessionId);\n const step = session && this.steps.get(session);\n if (step && !purpose) step.callId = callId;\n return { callId, session };\n }\n event(session, event) {\n const key = this.key(session.id), d = event.data;\n if (event.type === 'step/start') { this.steps.set(key, { turn: d.turn, step: d.step, start: event.time, first: null, calls: new Map(), toolMs: 0, toolCount: 0, toolsCompleted: 0 }); return; }\n const step = this.steps.get(key);\n if (!step) return;\n if (d.turn !== undefined && (d.turn !== step.turn || d.step !== undefined && d.step !== step.step)) return;\n if (event.type === 'assistant/attempt') { step.first ??= assistantStreamFirstTokenTime(d.stream) ?? null; return; }\n const row = this.telemetry.data.events.find(e => e.callId === step.callId && e.session === key);\n if (event.type === 'assistant/message') {\n if (!row || d.interrupted) return;\n const first = step.first ?? assistantStreamFirstTokenTime(d.stream) ?? null;\n const output = measured(d.usage?.outputTokens);\n row.timing = { modelMs: Math.max(0, event.time - step.start), ttftMs: first === null ? null : Math.max(0, first - step.start),\n decodeMs: first === null || output === null ? null : Math.max(0, event.time - first), decodeTokens: first === null ? null : output };\n row.timingSource = 'dsh-step';\n } else if (event.type === 'tool/call' && row?.timingSource === 'dsh-step') {\n if (!step.calls.has(d.callId)) { step.calls.set(d.callId, event.time); step.toolCount++; }\n } else if (event.type === 'tool/result' && step.calls.has(d.message?.source?.callId)) {\n const id = d.message.source.callId;\n step.toolMs += Math.max(0, event.time - step.calls.get(id)); step.toolsCompleted++; step.calls.delete(id);\n } else if (event.type === 'step/end' || event.type === 'turn/end') {\n this.steps.delete(key);\n } else return;\n if (row?.timingSource !== 'dsh-step') return;\n Object.assign(row.timing, { toolMs: step.toolMs, toolCount: step.toolCount, toolsCompleted: step.toolsCompleted });\n return this.telemetry.persist();\n }\n dispose(session) { this.steps.delete(this.key(session.id)); }\n}\n", "import { meterResponse } from './telemetry.mjs';\nimport { isFreeModel } from './config.mjs';\nimport { reasoningModes } from './reasoning.mjs';\nexport class RouteError extends Error {\n constructor(status, code, attempts = [], hint) {\n super(`${code}${attempts.length ? ': ' + attempts.map(a => `${a.account} HTTP ${a.status}`).join(', ') : ''}${hint ? `\uFF08${hint}\uFF09` : ''}`);\n this.status = status; this.code = code; this.attempts = attempts; this.hint = hint;\n }\n}\nconst normalize = value => value.toLowerCase().replace(/[^a-z0-9]/g, '').replace(/official$/, '');\nexport function abortable(promise, signal) {\n if (!signal) return promise;\n // Attach handlers even if already aborted, so a late rejection is consumed.\n return new Promise((resolve, reject) => {\n const abort = () => reject(signal.reason ?? new DOMException('Aborted', 'AbortError'));\n signal.addEventListener('abort', abort, { once: true });\n if (signal.aborted) abort();\n Promise.resolve(promise).then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));\n });\n}\nexport function matchModel(alias, target, catalog, effort, provider = target.provider) {\n const allowed = m => m && (effort === undefined || reasoningModes(m, provider).includes(effort));\n if (target.model) return catalog.find(m => m.id === target.model && allowed(m));\n if (target.models) return target.models.map(id => catalog.find(m => m.id === id)).find(allowed);\n if (alias === 'ds4.1f') return catalog.find(m => allowed(m) && [m.id, m.name ?? ''].some(x => normalize(x) === 'deepseekv41flash'));\n}\n\nexport class Router {\n constructor(config, createClient, onAttempt = () => {}, now = Date.now) {\n this.config = config; this.createClient = createClient; this.onAttempt = onAttempt; this.now = now;\n this.telemetry = null; this.scores = new Map(); this.monitor = null; this.clients = new Map(); this.cooldowns = new Map(); this.active = new Map();\n }\n updateConfig(config) {\n for (const old of this.config.accounts) {\n const next = config.accounts.find(a => a.id === old.id);\n if (!next || next.provider !== old.provider || next.credentialFile !== old.credentialFile || next.identityFile !== old.identityFile) this.clients.delete(old.id);\n }\n this.config = config;\n for (const alias of this.active.keys()) if (!Object.hasOwn(config.routes, alias)) this.active.delete(alias);\n }\n async client(account) {\n if (!this.clients.has(account.id)) this.clients.set(account.id, Promise.resolve().then(() => this.createClient(account)));\n try { return await this.clients.get(account.id); }\n catch (error) { this.clients.delete(account.id); throw error; }\n }\n status() {\n return this.config.accounts.map(a => ({ id: a.id, provider: a.provider, enabled: a.enabled !== false,\n cooldowns: [...this.cooldowns].filter(([key, time]) => key.startsWith(`${a.id}:`) && time > this.now())\n .map(([key, time]) => ({ route: key.slice(a.id.length + 1), remainingMs: time - this.now() })),\n activeFor: [...this.active].filter(([, key]) => key.split(':')[0] === a.id).map(([alias]) => alias) }));\n }\n candidates(alias) {\n const route = this.config.routes[alias];\n if (!Object.hasOwn(this.config.routes, alias)) throw new RouteError(404, 'unknown_model', [], '\u63D2\u4EF6\u8DEF\u7531\u8868\u4E2D\u6CA1\u6709\u8BE5\u8C03\u7528\u540D\u79F0\uFF0C\u8BF7\u5728\u914D\u7F6E\u9875\u65B0\u5EFA\u6216\u6062\u590D\u5BF9\u5E94\u8DEF\u7531');\n const candidates = route.targets.flatMap((target, index) => this.config.accounts\n .filter(a => a.enabled !== false && a.provider === target.provider && (!target.account || a.id === target.account))\n .flatMap(account => route.freeOnly ? (this.freeModels?.(account) ?? []).filter(m => (!target.model || m.id === target.model) && (!target.models || target.models.includes(m.id)))\n .map(model => ({ account, target: { ...target, model: model.id, models: undefined }, key: `${account.id}:${index}:${model.id}` }))\n : [{ account, target, key: `${account.id}:${index}` }]));\n const active = candidates.findIndex(c => c.key === this.active.get(alias));\n return active < 0 ? candidates : [...candidates.slice(active), ...candidates.slice(0, active)];\n }\n expiringAt(account, route) {\n const balance = this.telemetry?.row('accounts', account.id).balance;\n if (!balance || balance.error || balance.unit !== 'credits' || this.now() - balance.fetchedAt > 120000) return Infinity;\n const horizon = this.now() + (route.expiringWithinDays ?? 7) * 86400000;\n return Math.min(Infinity, ...(balance.packages ?? []).filter(p => p.remaining > 0 && !p.unlimited && p.expiresAt > this.now() && p.expiresAt <= horizon).map(p => p.expiresAt));\n }\n async syncExpiring(alias, route, candidates, below = () => true) {\n if (!route.preferExpiringCredits || !this.telemetry) return false;\n const round = this.telemetry.expiring(alias);\n if (round.cooldownUntil > this.now()) return false;\n const before = JSON.stringify(round);\n if (round.cooldownUntil) { round.accounts = []; round.completed = []; round.cooldownUntil = 0; round.number++; }\n for (const id of round.accounts) {\n const matches = candidates.filter(c => c.account.id === id), balance = this.telemetry.row('accounts', id).balance;\n const fresh = balance && !balance.error && balance.unit === 'credits' && this.now() - balance.fetchedAt <= 120000;\n if (!matches.length || matches.every(c => !below(c)) || (fresh && !Number.isFinite(this.expiringAt({ id }, route)))) {\n if (!round.completed.includes(id)) round.completed.push(id);\n }\n }\n for (const c of candidates) if (below(c) && Number.isFinite(this.expiringAt(c.account, route)) && !round.accounts.includes(c.account.id)) round.accounts.push(c.account.id);\n // Cooling applies only to expiring-credit priority; ordinary routing keeps running.\n if (round.accounts.length && round.accounts.every(id => round.completed.includes(id))) round.cooldownUntil = this.now() + 3 * 86400000;\n if (JSON.stringify(round) !== before) await this.telemetry.persist();\n return round.cooldownUntil <= this.now();\n }\n remainingCooldown(candidate, alias, route) {\n const id = candidate.account.id;\n const keys = [`${id}:*`, `${id}:${alias}`];\n if (route.freeOnly) keys.push(`${id}:${alias}:${candidate.target.model}`);\n return Math.max(0, ...keys.map(key => (this.cooldowns.get(key) ?? 0) - this.now()));\n }\n exhaustHint({ alias, route, strategy, candidates, expiryCandidates, attempts }) {\n const kinds = new Set(attempts.map(a => a.kind));\n const modelOnly = attempts.length > 0 && [...kinds].every(k => ['model_unavailable', 'reasoning_effort_unavailable', 'model_no_longer_free'].includes(k));\n const cooling = candidates.map(c => this.remainingCooldown(c, alias, route)).filter(ms => ms > 0);\n const soonest = cooling.length ? Math.min(...cooling) : undefined;\n const countdown = soonest === undefined ? undefined : `\u7EA6 ${Math.max(1, Math.ceil(soonest / 1000))} \u79D2\u540E\u53EF\u91CD\u8BD5`;\n if (!expiryCandidates.length) return route.freeOnly\n ? '\u8BE5\u8DEF\u7531\u5F53\u524D\u6CA1\u6709\u53EF\u7528\u7684\u514D\u8D39\u6A21\u578B\uFF1A\u53EF\u80FD\u5DF2\u6062\u590D\u6536\u8D39\u3001\u9650\u514D\u5230\u671F\u6216\u76EE\u5F55\u83B7\u53D6\u5931\u8D25'\n : '\u8BE5\u8DEF\u7531\u6CA1\u6709\u53EF\u7528\u7684\u8D26\u53F7\u76EE\u6807\uFF1A\u8D26\u53F7\u53EF\u80FD\u5DF2\u5168\u90E8\u7981\u7528\uFF0C\u6216\u4E0E\u76EE\u6807\u6E20\u9053\u4E0D\u5339\u914D';\n if (modelOnly) return `\u5DF2\u5C1D\u8BD5\u7684\u8D26\u53F7\u76EE\u5F55\u4E2D\u90FD\u6CA1\u6709\u8BE5\u6A21\u578B\u3001\u6240\u9009\u63A8\u7406\u6863\u4F4D\u6216\u514D\u8D39\u8D44\u683C\u4E0D\u6EE1\u8DB3${countdown ? `\uFF1B\u5176\u4F59\u8D26\u53F7\u51B7\u5374\u4E2D\uFF0C${countdown}` : ''}`;\n if (soonest !== undefined && cooling.length >= candidates.length) {\n return attempts.length ? `\u6240\u6709\u5019\u9009\u8D26\u53F7\u672C\u6B21\u8C03\u7528\u5747\u5931\u8D25\u5E76\u5DF2\u8FDB\u5165\u51B7\u5374\uFF0C${countdown}` : `\u5168\u90E8\u8D26\u53F7\u90FD\u5728\u5931\u8D25\u51B7\u5374\u4E2D\uFF0C${countdown}`;\n }\n if (soonest !== undefined) return `\u90E8\u5206\u8D26\u53F7\u4ECD\u5728\u5931\u8D25\u51B7\u5374\u4E2D\uFF0C${countdown}`;\n if (!attempts.length && ['tokens', 'credits'].includes(strategy)) return `\u6240\u6709\u8D26\u53F7\u7684\u672C\u8F6E${strategy === 'tokens' ? ' Token' : '\u79EF\u5206'}\u989D\u5EA6\u5DF2\u7528\u5B8C\uFF0C\u6216\u56E0\u7F3A\u5C11\u7528\u91CF\u8BA1\u91CF\u800C\u6682\u505C\u53C2\u4E0E\u8F6E\u6362`;\n return undefined;\n }\n async chat(input, signal, metadata = {}) {\n const alias = input.model;\n if (!Array.isArray(input.messages) || !input.messages.length || input.stream !== true) throw new RouteError(400, 'messages_and_stream_true_required');\n const initialRoute = this.config.routes[alias];\n if (initialRoute?.freeOnly) {\n await abortable(this.prepareFree?.(), signal);\n if (!this.config.routes[alias]) throw new RouteError(503, 'free_model_pool_unavailable', [], '\u672C\u8DEF\u7531\u4ECA\u5929\u6CA1\u6709\u53EF\u7528\u7684\u514D\u8D39\u6A21\u578B');\n }\n const effort = Object.hasOwn(input, 'reasoning_effort') ? input.reasoning_effort : this.config.routes[alias]?.reasoningEffort;\n input = { ...input }; delete input.reasoning_effort;\n if (effort !== undefined && effort !== null) input.reasoning_effort = effort;\n let candidates = this.candidates(alias);\n const expiryCandidates = [...candidates];\n const route = this.config.routes[alias], strategy = route.strategy ?? 'failover';\n const available = c => (this.cooldowns.get(`${c.account.id}:*`) ?? 0) <= this.now() && (this.cooldowns.get(`${c.account.id}:${alias}`) ?? 0) <= this.now()\n && (!route.freeOnly || (this.cooldowns.get(`${c.account.id}:${alias}:${c.target.model}`) ?? 0) <= this.now());\n if (route.preferExpiringCredits && this.monitor) {\n await Promise.all([...new Map(candidates.filter(available).map(c => [c.account.id, c.account])).values()].map(a => {\n const snapshot = this.telemetry?.row('accounts', a.id).balance;\n return !snapshot || snapshot.error || this.now() - snapshot.fetchedAt > 60000 ? this.monitor(a) : undefined;\n }));\n signal?.throwIfAborted();\n }\n if (strategy === 'credits') {\n await Promise.all([...new Map(candidates.filter(available).map(c => [c.account.id, c.account])).values()].map(a => this.monitor?.(a)));\n signal?.throwIfAborted();\n candidates = candidates.filter(c => { const balance = this.telemetry?.row('accounts', c.account.id).balance; return balance?.unit === 'credits' && Number.isFinite(balance.remaining) && this.now() - balance.fetchedAt < 120000 && !balance.error; });\n if (!candidates.length) throw new RouteError(503, 'credit_usage_unavailable', [], '\u65E0\u6CD5\u83B7\u53D6\u4EFB\u4F55\u5019\u9009\u8D26\u53F7\u7684\u79EF\u5206\u4F59\u989D\u6570\u636E');\n }\n const signature = JSON.stringify({ strategy, targets: route.targets });\n let below = () => true, preferActive;\n if (this.telemetry && ['tokens', 'credits'].includes(strategy)) {\n const round = this.telemetry.round(alias, signature);\n const counters = c => ({ tokens: this.telemetry.count(alias, c.account.id).tokens, credits: this.telemetry.row('accounts', c.account.id).creditsObserved ?? 0 });\n for (const c of candidates) if (!Object.hasOwn(round.bases, c.account.id)) Object.defineProperty(round.bases, c.account.id, { value: counters(c), enumerable: true, writable: true, configurable: true });\n below = c => !round.bases[c.account.id] || counters(c)[strategy] - round.bases[c.account.id][strategy] < (strategy === 'tokens' ? c.target.tokenBudget : c.target.creditBudget);\n preferActive = await this.syncExpiring(alias, route, expiryCandidates, below);\n // Start another round only when every configured, metered candidate has used its budget.\n if (candidates.length && candidates.every(c => !below(c))) {\n await this.telemetry.advance(alias, candidates, signature);\n this.active.delete(alias);\n const allowed = new Set(candidates.map(c => c.key));\n candidates = this.candidates(alias).filter(c => allowed.has(c.key));\n }\n candidates = candidates.filter(below);\n if (strategy === 'tokens' && candidates.some(c => this.telemetry.count(alias, c.account.id).unmetered > 0)) {\n // A missing upstream usage marker cannot silently count as zero forever.\n candidates = candidates.filter(c => this.telemetry.count(alias, c.account.id).unmetered === 0);\n }\n }\n preferActive ??= await this.syncExpiring(alias, route, expiryCandidates, below);\n const expiry = c => preferActive && !this.telemetry.expiring(alias).completed.includes(c.account.id) ? this.expiringAt(c.account, route) : Infinity;\n if (preferActive) candidates.sort((a, b) => expiry(a) - expiry(b));\n if (strategy === 'weighted') {\n let eligible = candidates.filter(available);\n const nearest = Math.min(Infinity, ...eligible.map(expiry));\n if (Number.isFinite(nearest)) eligible = eligible.filter(c => expiry(c) === nearest);\n if (eligible.length) {\n let scores = this.scores.get(alias);\n if (scores?.signature !== signature) { scores = { signature, values: new Map() }; this.scores.set(alias, scores); }\n let total = 0, winner;\n for (const c of eligible) { const weight = c.target.weight ?? 1; total += weight; scores.values.set(c.key, (scores.values.get(c.key) ?? 0) + weight); if (!winner || scores.values.get(c.key) > scores.values.get(winner.key)) winner = c; }\n scores.values.set(winner.key, scores.values.get(winner.key) - total);\n candidates = [winner, ...candidates.filter(c => c !== winner)];\n }\n }\n const attempts = [];\n // Each account/model target is attempted once, even when configuration repeats it.\n const tried = new Set();\n for (const candidate of candidates) {\n signal?.throwIfAborted();\n const { account, target, key } = candidate;\n if (!available(candidate)) continue;\n let result, actualModel, startedAt;\n try {\n const client = await abortable(this.client(account), signal);\n const catalog = await abortable(client.models(signal), signal);\n const model = matchModel(alias, target, catalog, effort ?? undefined);\n if (!model) { attempts.push({ account: account.id, provider: account.provider, status: 404,\n kind: matchModel(alias, target, catalog) ? 'reasoning_effort_unavailable' : 'model_unavailable' }); continue; }\n if (route.freeOnly && !isFreeModel(model, this.now())) { attempts.push({ account: account.id, provider: account.provider, status: 404, kind: 'model_no_longer_free' }); continue; }\n actualModel = model.id;\n const dedupe = `${account.id}:${model.id}`;\n if (tried.has(dedupe)) continue;\n tried.add(dedupe);\n startedAt = performance.now();\n result = await abortable(client.chat({ ...input, model: model.id }, signal), signal);\n signal?.throwIfAborted();\n if (result.ok) {\n this.active.set(alias, key);\n this.onAttempt({ account: account.id, provider: account.provider, alias, model: model.id, status: 200 });\n const response = this.telemetry ? meterResponse(result.response, async summary => {\n await this.telemetry.record({ ...summary, ...metadata, account: account.id, provider: account.provider, alias, model: model.id, accepted: true, status: summary.ok ? 200 : summary.cancelled ? 499 : 502 });\n await this.syncExpiring(alias, route, expiryCandidates, below);\n if (strategy === 'credits' || route.preferExpiringCredits) void this.monitor?.(account).then(() => this.syncExpiring(alias, route, expiryCandidates, below)).catch(() => {});\n }, signal, { startedAt }) : result.response;\n return { response, account: account.id, provider: account.provider };\n }\n } catch (error) {\n signal?.throwIfAborted();\n const reported = error.status ?? error.failure?.status ?? /\\bhttp\\s+(\\d{3})\\b/i.exec(error.message ?? '')?.[1];\n result = { ok: false, status: Number(reported) || 502, kind: error.code ?? error.failure?.code ?? 'transport' };\n }\n const status = result.status >= 400 && result.status <= 599 ? result.status : 502;\n const attempt = { account: account.id, provider: account.provider, status, kind: result.kind };\n attempts.push(attempt);\n await this.telemetry?.record({ ...attempt, ...metadata, alias, model: actualModel, ok: false, timing: startedAt === undefined ? null : { modelMs: Math.max(0, performance.now() - startedAt) } });\n // Upstream messages can contain credentials or request text: log metadata only.\n this.onAttempt({ ...attempt, alias });\n const retryable = status < 500 || this.config.retryServerErrors;\n if (!retryable) throw new RouteError(status, 'upstream_failed', attempts, '\u4E0A\u6E38\u8FD4\u56DE 5xx \u4E14\u672A\u5F00\u542F\u300C5xx \u548C\u7F51\u7EDC\u9519\u8BEF\u4E5F\u5207\u6362\u8D26\u53F7\u300D\uFF0C\u672C\u6B21\u8BF7\u6C42\u5DF2\u505C\u6B62');\n const scope = [401, 402, 429].includes(status) ? '*' : route.freeOnly && actualModel ? `${alias}:${actualModel}` : alias;\n this.cooldowns.set(`${account.id}:${scope}`, this.now() + this.config.cooldownMs);\n }\n const hint = this.exhaustHint({ alias, route, strategy, candidates, expiryCandidates, attempts });\n throw new RouteError(503, route.freeOnly ? 'free_model_pool_unavailable' : 'account_pool_exhausted', attempts, hint);\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["input", "output"]
7
+ }
@@ -0,0 +1,332 @@
1
+ import {
2
+ SseDecoder
3
+ } from "./chunk-4V52V5GO.js";
4
+ import {
5
+ hostIsLoopback
6
+ } from "./chunk-DNQPZMPF.js";
7
+ import {
8
+ RouteError,
9
+ abortable
10
+ } from "./chunk-ROZINFTV.js";
11
+ import {
12
+ REASONING_EFFORTS,
13
+ beijingDay,
14
+ writePrivateJson
15
+ } from "./chunk-WVNY4LSL.js";
16
+
17
+ // src/api-service.mjs
18
+ import { createServer } from "node:http";
19
+ import { randomBytes, randomUUID, createHash, timingSafeEqual } from "node:crypto";
20
+ import { readFile } from "node:fs/promises";
21
+ import { networkInterfaces } from "node:os";
22
+ import { Readable } from "node:stream";
23
+ import { pipeline } from "node:stream/promises";
24
+ import { withFileLock } from "@deepseek-ai/dsh-atomic-write";
25
+ var defaults = () => ({ version: 1, enabled: false, port: 8787, allowLan: false, modelIds: null, key: null });
26
+ var revision = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
27
+ var fail = (status, code) => {
28
+ throw new RouteError(status, code);
29
+ };
30
+ var json = (res, status, value) => {
31
+ res.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store", "X-Content-Type-Options": "nosniff" });
32
+ res.end(JSON.stringify(value));
33
+ };
34
+ function validate(value) {
35
+ if (!value || value.version !== 1 || typeof value.enabled !== "boolean" || typeof value.allowLan !== "boolean" || !Number.isInteger(value.port) || value.port < 1024 || value.port > 65535 || value.modelIds !== null && (!Array.isArray(value.modelIds) || value.modelIds.length > 1e3 || value.modelIds.some((id) => typeof id !== "string" || !/^[\w.-]{1,128}$/.test(id))) || value.key !== null && !/^sk-dsh-[A-Za-z0-9_-]{43}$/.test(value.key) || value.enabled && !value.key) throw Error("API \u8BBE\u7F6E\u65E0\u6548\uFF1A\u7AEF\u53E3\u9700\u4E3A 1024\u201365535\uFF0C\u542F\u7528\u524D\u8BF7\u751F\u6210 API Key\u3002");
36
+ return value;
37
+ }
38
+ var ApiService = class {
39
+ constructor(file, manager) {
40
+ this.file = file;
41
+ this.manager = manager;
42
+ this.settings = defaults();
43
+ this.phase = "stopped";
44
+ this.controllers = /* @__PURE__ */ new Set();
45
+ this.queue = Promise.resolve();
46
+ }
47
+ async load() {
48
+ this.configError = false;
49
+ try {
50
+ this.settings = validate(JSON.parse(await readFile(this.file, "utf8")));
51
+ } catch (e) {
52
+ if (e.code !== "ENOENT") {
53
+ this.configError = true;
54
+ this.phase = "error";
55
+ this.error = "API \u914D\u7F6E\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u670D\u52A1\u672A\u542F\u52A8\u3002\u8BF7\u91CD\u65B0\u4FDD\u5B58\u8BBE\u7F6E\u3002";
56
+ }
57
+ }
58
+ }
59
+ choices() {
60
+ return this.manager.adapterModels().filter((m) => !m.freeOnly || m.dynamicFree);
61
+ }
62
+ models() {
63
+ return this.choices().filter((m) => this.settings.modelIds === null ? m.visible !== false : this.settings.modelIds.includes(m.id));
64
+ }
65
+ state() {
66
+ const { key, ...settings } = this.settings;
67
+ const port = this.server?.address()?.port ?? settings.port;
68
+ const events = this.manager.telemetry.data.events.filter((e) => e.source === "api" && beijingDay(e.at) === beijingDay(Date.now()));
69
+ const addresses = settings.allowLan ? [...new Set(Object.values(networkInterfaces()).flat().filter((a) => a && a.family === "IPv4" && !a.internal).map((a) => `http://${a.address}:${port}/v1`))] : [];
70
+ return {
71
+ ...settings,
72
+ revision: revision(this.settings),
73
+ phase: this.phase,
74
+ error: this.error,
75
+ hasKey: !!key,
76
+ keyHint: key ? `sk-dsh-\u2022\u2022\u2022\u2022${key.slice(-4)}` : null,
77
+ active: this.controllers.size,
78
+ baseUrl: `http://127.0.0.1:${port}/v1`,
79
+ lanUrls: addresses,
80
+ choices: this.choices().map((m) => ({ id: m.id, name: m.name, visible: m.visible !== false, reasoning: m.reasoning })),
81
+ models: this.models().map((m) => ({ id: m.id, name: m.name })),
82
+ today: { calls: new Set(events.map((e) => e.callId)).size, attempts: events.length, tokens: events.reduce((n, e) => n + (e.tokens ?? 0), 0) }
83
+ };
84
+ }
85
+ action(input) {
86
+ const task = this.queue.catch(() => {
87
+ }).then(() => withFileLock(this.file, async () => {
88
+ if (this.closed) throw Error("API \u670D\u52A1\u5DF2\u5173\u95ED\u3002");
89
+ let current = this.settings;
90
+ try {
91
+ current = validate(JSON.parse(await readFile(this.file, "utf8")));
92
+ } catch (e) {
93
+ if (e.code !== "ENOENT" && this.phase !== "error") throw e;
94
+ }
95
+ if (input.apiRevision !== revision(current)) throw Error("API \u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u9762\u677F\u540E\u91CD\u8BD5\u3002");
96
+ const next = structuredClone(current);
97
+ let apiKey;
98
+ if (input.action === "api.key.reveal") return { apiKey: next.key };
99
+ if (input.action === "api.key.create") {
100
+ next.key = `sk-dsh-${randomBytes(32).toString("base64url")}`;
101
+ apiKey = next.key;
102
+ } else if (input.action === "api.key.revoke") {
103
+ next.key = null;
104
+ next.enabled = false;
105
+ } else if (input.action === "api.settings") {
106
+ for (const k of ["enabled", "port", "allowLan", "modelIds"]) if (input[k] !== void 0) next[k] = input[k];
107
+ if (next.modelIds?.some((id) => !this.choices().some((m) => m.id === id) && !current.modelIds?.includes(id))) throw Error("\u6A21\u578B\u76EE\u5F55\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u9762\u677F\u540E\u91CD\u65B0\u9009\u62E9\u3002");
108
+ } else throw Error("\u672A\u77E5 API \u64CD\u4F5C\u3002");
109
+ validate(next);
110
+ await writePrivateJson(this.file, next);
111
+ this.configError = false;
112
+ const restart = next.enabled !== this.settings.enabled || next.port !== this.settings.port || next.allowLan !== this.settings.allowLan;
113
+ this.settings = next;
114
+ if (restart || this.phase === "error" || !this.server && next.enabled) await this.reconcile();
115
+ return { apiKey };
116
+ }, { waitMs: 1e4 }));
117
+ this.queue = task;
118
+ return task;
119
+ }
120
+ async reconcile() {
121
+ await this.stop();
122
+ if (this.configError) {
123
+ this.phase = "error";
124
+ return;
125
+ }
126
+ this.error = void 0;
127
+ if (this.closed || !this.settings.enabled) {
128
+ this.phase = "stopped";
129
+ return;
130
+ }
131
+ this.phase = "starting";
132
+ const server = createServer((req, res) => {
133
+ void this.handle(req, res);
134
+ });
135
+ server.requestTimeout = 3e4;
136
+ server.headersTimeout = 1e4;
137
+ server.keepAliveTimeout = 5e3;
138
+ this.server = server;
139
+ try {
140
+ await new Promise((resolve, reject) => {
141
+ server.once("error", reject);
142
+ server.listen(this.settings.port, this.settings.allowLan ? "0.0.0.0" : "127.0.0.1", resolve);
143
+ });
144
+ this.phase = "running";
145
+ server.on("error", () => {
146
+ this.phase = "error";
147
+ this.error = "API \u76D1\u542C\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u4FDD\u5B58\u8BBE\u7F6E\u3002";
148
+ });
149
+ } catch (e) {
150
+ this.server = void 0;
151
+ this.phase = "error";
152
+ this.error = e.code === "EADDRINUSE" ? "\u7AEF\u53E3\u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u4FEE\u6539\u7AEF\u53E3\u540E\u4FDD\u5B58\u3002" : e.code === "EACCES" ? "\u6CA1\u6709\u76D1\u542C\u6B64\u7AEF\u53E3\u7684\u6743\u9650\uFF0C\u8BF7\u4FEE\u6539\u7AEF\u53E3\u3002" : "API \u670D\u52A1\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7AEF\u53E3\u8BBE\u7F6E\u3002";
153
+ }
154
+ }
155
+ async stop() {
156
+ const server = this.server;
157
+ this.server = void 0;
158
+ for (const c of this.controllers) c.abort();
159
+ if (server) {
160
+ server.closeAllConnections();
161
+ await new Promise((resolve) => server.close(resolve));
162
+ }
163
+ this.phase = "stopped";
164
+ }
165
+ async close() {
166
+ this.closed = true;
167
+ await this.queue.catch(() => {
168
+ });
169
+ await this.stop();
170
+ }
171
+ async handle(req, res) {
172
+ const controller = new AbortController();
173
+ const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(Math.min(this.manager.config.requestTimeoutMs, 2147483647))]);
174
+ req.on("aborted", () => controller.abort());
175
+ res.on("close", () => {
176
+ if (!res.writableEnded) controller.abort();
177
+ });
178
+ let selected;
179
+ try {
180
+ if (!this.settings.enabled || this.closed) fail(503, "service_disabled");
181
+ if (!this.settings.allowLan && !hostIsLoopback(req.headers.host)) fail(403, "loopback_required");
182
+ if (req.headers.origin || req.headers["sec-fetch-site"] === "cross-site") fail(403, "browser_origin_not_allowed");
183
+ const token = /^Bearer\s+(\S+)$/i.exec(req.headers.authorization ?? "")?.[1] ?? "";
184
+ const actual = Buffer.from(token), expected = Buffer.from(this.settings.key ?? "");
185
+ if (!expected.length || actual.length !== expected.length || !timingSafeEqual(actual, expected)) fail(401, "invalid_api_key");
186
+ if (req.method === "GET" && req.url === "/v1/models") {
187
+ json(res, 200, { object: "list", data: this.models().map((m) => ({ id: m.id, object: "model", created: 0, owned_by: "dsh-multi-router" })) });
188
+ return;
189
+ }
190
+ if (req.method !== "POST" || req.url !== "/v1/chat/completions") fail(404, "not_found");
191
+ if (this.controllers.size >= 8) fail(429, "too_many_requests");
192
+ this.controllers.add(controller);
193
+ if (!/^application\/json(?:\s*;|$)/i.test(req.headers["content-type"] ?? "")) fail(415, "json_required");
194
+ const chunks = [];
195
+ let size = 0;
196
+ for await (const chunk of req) {
197
+ size += chunk.length;
198
+ if (size > 32 * 1024 * 1024) fail(413, "request_too_large");
199
+ signal.throwIfAborted();
200
+ chunks.push(chunk);
201
+ }
202
+ let input;
203
+ try {
204
+ input = JSON.parse(Buffer.concat(chunks).toString("utf8"));
205
+ } catch {
206
+ fail(400, "invalid_json");
207
+ }
208
+ if (!input || typeof input !== "object" || Array.isArray(input) || typeof input.model !== "string" || !Array.isArray(input.messages) || !input.messages.length || input.messages.some((m) => !m || typeof m !== "object" || !["system", "developer", "user", "assistant", "tool", "function"].includes(m.role)) || input.stream !== void 0 && typeof input.stream !== "boolean") fail(400, "invalid_request");
209
+ if (!this.models().some((m) => m.id === input.model)) fail(404, "model_not_exposed");
210
+ if (input.n !== void 0 && input.n !== 1) fail(400, "only_n_1_supported");
211
+ if (input.reasoning_effort != null && !REASONING_EFFORTS.includes(input.reasoning_effort)) fail(400, "invalid_reasoning_effort");
212
+ selected = await abortable(this.manager.router.chat({ ...input, stream: true }, signal, { callId: randomUUID(), source: "api" }), signal);
213
+ signal.throwIfAborted();
214
+ const frames = completionFrames(selected.response, input.model, signal);
215
+ if (input.stream === true) {
216
+ res.writeHead(200, { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache", "X-Accel-Buffering": "no" });
217
+ await pipeline(Readable.from((async function* () {
218
+ for await (const frame of frames) {
219
+ if (!input.stream_options?.include_usage && !frame.choices?.length && frame.usage) continue;
220
+ if (!input.stream_options?.include_usage) delete frame.usage;
221
+ yield `data: ${JSON.stringify(frame)}
222
+
223
+ `;
224
+ }
225
+ yield "data: [DONE]\n\n";
226
+ })()), res, { signal });
227
+ } else json(res, 200, await collectCompletion(frames, input.model));
228
+ } catch (e) {
229
+ if (res.destroyed) return;
230
+ if (res.headersSent) {
231
+ res.destroy();
232
+ return;
233
+ }
234
+ const status = e instanceof RouteError ? e.status : signal.aborted ? 504 : 500;
235
+ const code = e instanceof RouteError ? e.code : signal.aborted ? "request_timeout" : "internal_error";
236
+ json(res, status, { error: { message: code, type: status === 401 ? "authentication_error" : status < 500 ? "invalid_request_error" : "server_error", code, param: null } });
237
+ } finally {
238
+ this.controllers.delete(controller);
239
+ if (selected?.response.body && !selected.response.body.locked) await selected.response.body.cancel().catch(() => {
240
+ });
241
+ }
242
+ }
243
+ };
244
+ async function* completionFrames(response, model, signal) {
245
+ if (!response.body) fail(502, "empty_upstream_response");
246
+ const reader = response.body.getReader(), decoder = new TextDecoder(), sse = new SseDecoder();
247
+ let finished = false, total = 0, frameBytes = 0;
248
+ const abort = () => {
249
+ void reader.cancel().catch(() => {
250
+ });
251
+ };
252
+ signal.addEventListener("abort", abort, { once: true });
253
+ function parse(event) {
254
+ if (event.data === "[DONE]") return null;
255
+ let value;
256
+ try {
257
+ value = JSON.parse(event.data);
258
+ } catch {
259
+ fail(502, "invalid_upstream_stream");
260
+ }
261
+ if (!value || typeof value !== "object" || Array.isArray(value) || value.error) fail(502, "upstream_stream_error");
262
+ if (value.choices !== void 0 && (!Array.isArray(value.choices) || value.choices.length > 32)) fail(502, "invalid_upstream_stream");
263
+ for (const c of value.choices ?? []) {
264
+ if (!c || !Number.isInteger(c.index) || c.index < 0 || c.index > 31) fail(502, "invalid_upstream_stream");
265
+ if (c.delta?.tool_calls && (!Array.isArray(c.delta.tool_calls) || c.delta.tool_calls.some((t) => !t || !Number.isInteger(t.index) || t.index < 0 || t.index > 127))) fail(502, "invalid_upstream_stream");
266
+ }
267
+ if (value.choices?.some((c) => c.finish_reason)) finished = true;
268
+ return { ...value, model };
269
+ }
270
+ try {
271
+ while (true) {
272
+ signal.throwIfAborted();
273
+ const chunk = await reader.read();
274
+ signal.throwIfAborted();
275
+ if (chunk.done) break;
276
+ total += chunk.value.byteLength;
277
+ frameBytes += chunk.value.byteLength;
278
+ if (total > 32 * 1024 * 1024 || frameBytes > 1024 * 1024) fail(502, "upstream_response_too_large");
279
+ const events = sse.push(decoder.decode(chunk.value, { stream: true }));
280
+ if (events.length) frameBytes = 0;
281
+ for (const event of events) {
282
+ const value = parse(event);
283
+ if (value) yield value;
284
+ }
285
+ }
286
+ for (const event of sse.finish()) {
287
+ const value = parse(event);
288
+ if (value) yield value;
289
+ }
290
+ if (!finished) fail(502, "upstream_stream_incomplete");
291
+ } finally {
292
+ signal.removeEventListener("abort", abort);
293
+ await reader.cancel().catch(() => {
294
+ });
295
+ reader.releaseLock();
296
+ }
297
+ }
298
+ async function collectCompletion(frames, model) {
299
+ const result = { id: `chatcmpl-${randomUUID()}`, object: "chat.completion", created: Math.floor(Date.now() / 1e3), model, choices: [] };
300
+ const choices = /* @__PURE__ */ new Map();
301
+ for await (const frame of frames) {
302
+ if (frame.id) result.id = frame.id;
303
+ if (frame.created != null) result.created = frame.created;
304
+ if (frame.usage) result.usage = frame.usage;
305
+ for (const part of frame.choices ?? []) {
306
+ const index = part.index ?? 0;
307
+ if (!choices.has(index)) choices.set(index, { index, message: { role: "assistant", content: null }, finish_reason: null, logprobs: null });
308
+ const choice = choices.get(index), delta = part.delta ?? {};
309
+ for (const k of ["content", "reasoning_content", "reasoning", "refusal"]) if (typeof delta[k] === "string") choice.message[k] = (choice.message[k] ?? "") + delta[k];
310
+ if (delta.function_call) {
311
+ choice.message.function_call ??= { name: "", arguments: "" };
312
+ for (const k of ["name", "arguments"]) if (typeof delta.function_call[k] === "string") choice.message.function_call[k] += delta.function_call[k];
313
+ }
314
+ if (delta.tool_calls) for (const tool of delta.tool_calls) {
315
+ choice.message.tool_calls ??= [];
316
+ const i = tool.index ?? 0;
317
+ const value = choice.message.tool_calls[i] ??= { id: "", type: "function", function: { name: "", arguments: "" } };
318
+ if (tool.id) value.id += tool.id;
319
+ if (tool.type) value.type = tool.type;
320
+ for (const k of ["name", "arguments"]) if (typeof tool.function?.[k] === "string") value.function[k] += tool.function[k];
321
+ }
322
+ if (part.finish_reason) choice.finish_reason = part.finish_reason;
323
+ }
324
+ }
325
+ result.choices = [...choices.values()].sort((a, b) => a.index - b.index);
326
+ return result;
327
+ }
328
+
329
+ export {
330
+ ApiService
331
+ };
332
+ //# sourceMappingURL=chunk-SNEZMYNU.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/api-service.mjs"],
4
+ "sourcesContent": ["import { createServer } from 'node:http';\nimport { randomBytes, randomUUID, createHash, timingSafeEqual } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport { networkInterfaces } from 'node:os';\nimport { Readable } from 'node:stream';\nimport { pipeline } from 'node:stream/promises';\nimport { withFileLock } from '@deepseek-ai/dsh-atomic-write';\nimport { writePrivateJson, beijingDay } from './config.mjs';\nimport { RouteError, abortable } from './router.mjs';\nimport { REASONING_EFFORTS } from './reasoning.mjs';\nimport { hostIsLoopback } from '../upstream/workbuddy/src/loopback.ts';\nimport { SseDecoder } from '../upstream/trae/src/sse.ts';\n\nconst defaults = () => ({ version: 1, enabled: false, port: 8787, allowLan: false, modelIds: null, key: null });\nconst revision = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');\nconst fail = (status, code) => { throw new RouteError(status, code); };\nconst json = (res, status, value) => {\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store', 'X-Content-Type-Options': 'nosniff' });\n res.end(JSON.stringify(value));\n};\nfunction validate(value) {\n if (!value || value.version !== 1 || typeof value.enabled !== 'boolean' || typeof value.allowLan !== 'boolean'\n || !Number.isInteger(value.port) || value.port < 1024 || value.port > 65535\n || value.modelIds !== null && (!Array.isArray(value.modelIds) || value.modelIds.length > 1000 || value.modelIds.some(id => typeof id !== 'string' || !/^[\\w.-]{1,128}$/.test(id)))\n || value.key !== null && !/^sk-dsh-[A-Za-z0-9_-]{43}$/.test(value.key)\n || value.enabled && !value.key) throw Error('API \u8BBE\u7F6E\u65E0\u6548\uFF1A\u7AEF\u53E3\u9700\u4E3A 1024\u201365535\uFF0C\u542F\u7528\u524D\u8BF7\u751F\u6210 API Key\u3002');\n return value;\n}\n\n// The service owns only its listener and private access key. Routing, provider\n// credentials, failover and metering remain with the existing Manager/Router.\nexport class ApiService {\n constructor(file, manager) {\n this.file = file; this.manager = manager; this.settings = defaults();\n this.phase = 'stopped'; this.controllers = new Set(); this.queue = Promise.resolve();\n }\n async load() {\n this.configError = false;\n try { this.settings = validate(JSON.parse(await readFile(this.file, 'utf8'))); }\n catch (e) { if (e.code !== 'ENOENT') { this.configError = true; this.phase = 'error'; this.error = 'API \u914D\u7F6E\u65E0\u6CD5\u8BFB\u53D6\uFF0C\u670D\u52A1\u672A\u542F\u52A8\u3002\u8BF7\u91CD\u65B0\u4FDD\u5B58\u8BBE\u7F6E\u3002'; } }\n }\n choices() { return this.manager.adapterModels().filter(m => !m.freeOnly || m.dynamicFree); }\n models() { return this.choices().filter(m => this.settings.modelIds === null ? m.visible !== false : this.settings.modelIds.includes(m.id)); }\n state() {\n const { key, ...settings } = this.settings;\n const port = this.server?.address()?.port ?? settings.port;\n const events = this.manager.telemetry.data.events.filter(e => e.source === 'api' && beijingDay(e.at) === beijingDay(Date.now()));\n const addresses = settings.allowLan ? [...new Set(Object.values(networkInterfaces()).flat().filter(a => a && a.family === 'IPv4' && !a.internal).map(a => `http://${a.address}:${port}/v1`))] : [];\n return { ...settings, revision: revision(this.settings), phase: this.phase, error: this.error,\n hasKey: !!key, keyHint: key ? `sk-dsh-\u2022\u2022\u2022\u2022${key.slice(-4)}` : null, active: this.controllers.size,\n baseUrl: `http://127.0.0.1:${port}/v1`, lanUrls: addresses,\n choices: this.choices().map(m => ({ id: m.id, name: m.name, visible: m.visible !== false, reasoning: m.reasoning })),\n models: this.models().map(m => ({ id: m.id, name: m.name })),\n today: { calls: new Set(events.map(e => e.callId)).size, attempts: events.length, tokens: events.reduce((n, e) => n + (e.tokens ?? 0), 0) } };\n }\n action(input) {\n const task = this.queue.catch(() => {}).then(() => withFileLock(this.file, async () => {\n if (this.closed) throw Error('API \u670D\u52A1\u5DF2\u5173\u95ED\u3002');\n // Compare the on-disk revision too; two DSH instances must not overwrite a key.\n let current = this.settings;\n try { current = validate(JSON.parse(await readFile(this.file, 'utf8'))); } catch (e) { if (e.code !== 'ENOENT' && this.phase !== 'error') throw e; }\n if (input.apiRevision !== revision(current)) throw Error('API \u914D\u7F6E\u5DF2\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u9762\u677F\u540E\u91CD\u8BD5\u3002');\n const next = structuredClone(current);\n let apiKey;\n if (input.action === 'api.key.reveal') return { apiKey: next.key };\n if (input.action === 'api.key.create') { next.key = `sk-dsh-${randomBytes(32).toString('base64url')}`; apiKey = next.key; }\n else if (input.action === 'api.key.revoke') { next.key = null; next.enabled = false; }\n else if (input.action === 'api.settings') {\n for (const k of ['enabled', 'port', 'allowLan', 'modelIds']) if (input[k] !== undefined) next[k] = input[k];\n if (next.modelIds?.some(id => !this.choices().some(m => m.id === id) && !current.modelIds?.includes(id))) throw Error('\u6A21\u578B\u76EE\u5F55\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u9762\u677F\u540E\u91CD\u65B0\u9009\u62E9\u3002');\n } else throw Error('\u672A\u77E5 API \u64CD\u4F5C\u3002');\n validate(next);\n await writePrivateJson(this.file, next);\n this.configError = false;\n const restart = next.enabled !== this.settings.enabled || next.port !== this.settings.port || next.allowLan !== this.settings.allowLan;\n this.settings = next;\n if (restart || this.phase === 'error' || !this.server && next.enabled) await this.reconcile();\n return { apiKey };\n }, { waitMs: 10000 }));\n this.queue = task; return task;\n }\n async reconcile() {\n await this.stop();\n if (this.configError) { this.phase = 'error'; return; }\n this.error = undefined;\n if (this.closed || !this.settings.enabled) { this.phase = 'stopped'; return; }\n this.phase = 'starting';\n const server = createServer((req, res) => { void this.handle(req, res); });\n server.requestTimeout = 30000; server.headersTimeout = 10000; server.keepAliveTimeout = 5000;\n this.server = server;\n try {\n await new Promise((resolve, reject) => {\n server.once('error', reject);\n server.listen(this.settings.port, this.settings.allowLan ? '0.0.0.0' : '127.0.0.1', resolve);\n });\n this.phase = 'running';\n server.on('error', () => { this.phase = 'error'; this.error = 'API \u76D1\u542C\u5F02\u5E38\uFF0C\u8BF7\u91CD\u65B0\u4FDD\u5B58\u8BBE\u7F6E\u3002'; });\n } catch (e) {\n this.server = undefined; this.phase = 'error';\n this.error = e.code === 'EADDRINUSE' ? '\u7AEF\u53E3\u5DF2\u88AB\u5360\u7528\uFF0C\u8BF7\u4FEE\u6539\u7AEF\u53E3\u540E\u4FDD\u5B58\u3002' : e.code === 'EACCES' ? '\u6CA1\u6709\u76D1\u542C\u6B64\u7AEF\u53E3\u7684\u6743\u9650\uFF0C\u8BF7\u4FEE\u6539\u7AEF\u53E3\u3002' : 'API \u670D\u52A1\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u7AEF\u53E3\u8BBE\u7F6E\u3002';\n }\n }\n async stop() {\n const server = this.server; this.server = undefined;\n for (const c of this.controllers) c.abort();\n if (server) { server.closeAllConnections(); await new Promise(resolve => server.close(resolve)); }\n this.phase = 'stopped';\n }\n async close() { this.closed = true; await this.queue.catch(() => {}); await this.stop(); }\n async handle(req, res) {\n const controller = new AbortController();\n const signal = AbortSignal.any([controller.signal, AbortSignal.timeout(Math.min(this.manager.config.requestTimeoutMs, 2147483647))]);\n req.on('aborted', () => controller.abort());\n res.on('close', () => { if (!res.writableEnded) controller.abort(); });\n let selected;\n try {\n if (!this.settings.enabled || this.closed) fail(503, 'service_disabled');\n if (!this.settings.allowLan && !hostIsLoopback(req.headers.host)) fail(403, 'loopback_required');\n // No browser CORS access: an unrelated page must never drive the local service.\n if (req.headers.origin || req.headers['sec-fetch-site'] === 'cross-site') fail(403, 'browser_origin_not_allowed');\n const token = /^Bearer\\s+(\\S+)$/i.exec(req.headers.authorization ?? '')?.[1] ?? '';\n const actual = Buffer.from(token), expected = Buffer.from(this.settings.key ?? '');\n if (!expected.length || actual.length !== expected.length || !timingSafeEqual(actual, expected)) fail(401, 'invalid_api_key');\n if (req.method === 'GET' && req.url === '/v1/models') {\n json(res, 200, { object: 'list', data: this.models().map(m => ({ id: m.id, object: 'model', created: 0, owned_by: 'dsh-multi-router' })) }); return;\n }\n if (req.method !== 'POST' || req.url !== '/v1/chat/completions') fail(404, 'not_found');\n if (this.controllers.size >= 8) fail(429, 'too_many_requests');\n this.controllers.add(controller);\n if (!/^application\\/json(?:\\s*;|$)/i.test(req.headers['content-type'] ?? '')) fail(415, 'json_required');\n const chunks = []; let size = 0;\n for await (const chunk of req) { size += chunk.length; if (size > 32 * 1024 * 1024) fail(413, 'request_too_large'); signal.throwIfAborted(); chunks.push(chunk); }\n let input;\n try { input = JSON.parse(Buffer.concat(chunks).toString('utf8')); } catch { fail(400, 'invalid_json'); }\n if (!input || typeof input !== 'object' || Array.isArray(input) || typeof input.model !== 'string'\n || !Array.isArray(input.messages) || !input.messages.length || input.messages.some(m => !m || typeof m !== 'object' || !['system', 'developer', 'user', 'assistant', 'tool', 'function'].includes(m.role))\n || input.stream !== undefined && typeof input.stream !== 'boolean') fail(400, 'invalid_request');\n if (!this.models().some(m => m.id === input.model)) fail(404, 'model_not_exposed');\n if (input.n !== undefined && input.n !== 1) fail(400, 'only_n_1_supported');\n if (input.reasoning_effort != null && !REASONING_EFFORTS.includes(input.reasoning_effort)) fail(400, 'invalid_reasoning_effort');\n // Never accept internal account/session headers or persist caller user IDs.\n selected = await abortable(this.manager.router.chat({ ...input, stream: true }, signal, { callId: randomUUID(), source: 'api' }), signal);\n signal.throwIfAborted();\n const frames = completionFrames(selected.response, input.model, signal);\n if (input.stream === true) {\n res.writeHead(200, { 'Content-Type': 'text/event-stream; charset=utf-8', 'Cache-Control': 'no-cache', 'X-Accel-Buffering': 'no' });\n await pipeline(Readable.from((async function* () {\n for await (const frame of frames) {\n if (!input.stream_options?.include_usage && !frame.choices?.length && frame.usage) continue;\n if (!input.stream_options?.include_usage) delete frame.usage;\n yield `data: ${JSON.stringify(frame)}\\n\\n`;\n }\n yield 'data: [DONE]\\n\\n';\n })()), res, { signal });\n } else json(res, 200, await collectCompletion(frames, input.model));\n } catch (e) {\n if (res.destroyed) return;\n if (res.headersSent) { res.destroy(); return; }\n const status = e instanceof RouteError ? e.status : signal.aborted ? 504 : 500;\n const code = e instanceof RouteError ? e.code : signal.aborted ? 'request_timeout' : 'internal_error';\n // RouteError.message includes account IDs; upstream exception text can contain credentials.\n json(res, status, { error: { message: code, type: status === 401 ? 'authentication_error' : status < 500 ? 'invalid_request_error' : 'server_error', code, param: null } });\n } finally {\n this.controllers.delete(controller);\n if (selected?.response.body && !selected.response.body.locked) await selected.response.body.cancel().catch(() => {});\n }\n }\n}\n\nasync function* completionFrames(response, model, signal) {\n if (!response.body) fail(502, 'empty_upstream_response');\n const reader = response.body.getReader(), decoder = new TextDecoder(), sse = new SseDecoder();\n let finished = false, total = 0, frameBytes = 0;\n const abort = () => { void reader.cancel().catch(() => {}); };\n signal.addEventListener('abort', abort, { once: true });\n function parse(event) {\n if (event.data === '[DONE]') return null;\n let value;\n try { value = JSON.parse(event.data); } catch { fail(502, 'invalid_upstream_stream'); }\n if (!value || typeof value !== 'object' || Array.isArray(value) || value.error) fail(502, 'upstream_stream_error');\n if (value.choices !== undefined && (!Array.isArray(value.choices) || value.choices.length > 32)) fail(502, 'invalid_upstream_stream');\n for (const c of value.choices ?? []) {\n if (!c || !Number.isInteger(c.index) || c.index < 0 || c.index > 31) fail(502, 'invalid_upstream_stream');\n if (c.delta?.tool_calls && (!Array.isArray(c.delta.tool_calls) || c.delta.tool_calls.some(t => !t || !Number.isInteger(t.index) || t.index < 0 || t.index > 127))) fail(502, 'invalid_upstream_stream');\n }\n if (value.choices?.some(c => c.finish_reason)) finished = true;\n return { ...value, model };\n }\n try {\n while (true) {\n signal.throwIfAborted(); const chunk = await reader.read(); signal.throwIfAborted();\n if (chunk.done) break;\n total += chunk.value.byteLength; frameBytes += chunk.value.byteLength;\n if (total > 32 * 1024 * 1024 || frameBytes > 1024 * 1024) fail(502, 'upstream_response_too_large');\n const events = sse.push(decoder.decode(chunk.value, { stream: true }));\n if (events.length) frameBytes = 0;\n for (const event of events) { const value = parse(event); if (value) yield value; }\n }\n for (const event of sse.finish()) { const value = parse(event); if (value) yield value; }\n if (!finished) fail(502, 'upstream_stream_incomplete');\n } finally { signal.removeEventListener('abort', abort); await reader.cancel().catch(() => {}); reader.releaseLock(); }\n}\n\nasync function collectCompletion(frames, model) {\n const result = { id: `chatcmpl-${randomUUID()}`, object: 'chat.completion', created: Math.floor(Date.now() / 1000), model, choices: [] };\n const choices = new Map();\n for await (const frame of frames) {\n if (frame.id) result.id = frame.id;\n if (frame.created != null) result.created = frame.created;\n if (frame.usage) result.usage = frame.usage;\n for (const part of frame.choices ?? []) {\n const index = part.index ?? 0;\n if (!choices.has(index)) choices.set(index, { index, message: { role: 'assistant', content: null }, finish_reason: null, logprobs: null });\n const choice = choices.get(index), delta = part.delta ?? {};\n for (const k of ['content', 'reasoning_content', 'reasoning', 'refusal']) if (typeof delta[k] === 'string') choice.message[k] = (choice.message[k] ?? '') + delta[k];\n if (delta.function_call) {\n choice.message.function_call ??= { name: '', arguments: '' };\n for (const k of ['name', 'arguments']) if (typeof delta.function_call[k] === 'string') choice.message.function_call[k] += delta.function_call[k];\n }\n if (delta.tool_calls) for (const tool of delta.tool_calls) {\n choice.message.tool_calls ??= [];\n const i = tool.index ?? 0;\n const value = choice.message.tool_calls[i] ??= { id: '', type: 'function', function: { name: '', arguments: '' } };\n if (tool.id) value.id += tool.id;\n if (tool.type) value.type = tool.type;\n for (const k of ['name', 'arguments']) if (typeof tool.function?.[k] === 'string') value.function[k] += tool.function[k];\n }\n if (part.finish_reason) choice.finish_reason = part.finish_reason;\n }\n }\n result.choices = [...choices.values()].sort((a, b) => a.index - b.index);\n return result;\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -149,6 +149,7 @@ function validateConfig(raw, file = resolve("router.json")) {
149
149
  if (!object(modelVisibility) || Object.entries(modelVisibility).some(([provider, visible]) => !PROVIDERS.includes(provider) || typeof visible !== "boolean")) throw new Error("Invalid modelVisibility");
150
150
  for (const key of ["cooldownMs", "requestTimeoutMs", "catalogTtlMs"]) if (raw[key] !== void 0 && !positive(raw[key])) throw new Error(`Invalid ${key}`);
151
151
  if (raw.retryServerErrors !== void 0 && typeof raw.retryServerErrors !== "boolean") throw new Error("Invalid retryServerErrors");
152
+ if (raw.sidebarUsage !== void 0 && typeof raw.sidebarUsage !== "boolean") throw new Error("\u4FA7\u680F\u7528\u91CF\u5F00\u5173\u5FC5\u987B\u662F\u5E03\u5C14\u503C");
152
153
  if (raw.ignoredLocalAccounts !== void 0 && (!Array.isArray(raw.ignoredLocalAccounts) || raw.ignoredLocalAccounts.some((k) => typeof k !== "string" || k.length > 1024))) throw new Error("Invalid ignoredLocalAccounts");
153
154
  if (raw.usageRetentionDays !== void 0 && (!positive(raw.usageRetentionDays) || raw.usageRetentionDays > 365)) throw new Error("Token \u660E\u7EC6\u4FDD\u7559\u5929\u6570\u5FC5\u987B\u4E3A 1\u2013365 \u7684\u6574\u6570");
154
155
  const checkinSchedule = raw.checkinSchedule ?? { mode: "scheduled", time: "10:05" };
@@ -161,6 +162,7 @@ function validateConfig(raw, file = resolve("router.json")) {
161
162
  modelVisibility,
162
163
  cooldownMs: raw.cooldownMs ?? 6e4,
163
164
  checkinSchedule: { ...checkinSchedule },
165
+ sidebarUsage: raw.sidebarUsage ?? true,
164
166
  requestTimeoutMs: raw.requestTimeoutMs ?? 3e5,
165
167
  catalogTtlMs: raw.catalogTtlMs ?? 3e5,
166
168
  retryServerErrors: raw.retryServerErrors ?? false,
@@ -210,4 +212,4 @@ export {
210
212
  ensureConfig,
211
213
  writePrivateJson
212
214
  };
213
- //# sourceMappingURL=chunk-MCHV2R25.js.map
215
+ //# sourceMappingURL=chunk-WVNY4LSL.js.map