feat: 个人定制版 —— API Key 模式显示剩余积分 + 悬浮卡片

基于 @axiaohungry/dsh-llm-workbuddy 1.3.21 (MIT)。
详见 README.CUSTOM.md。
This commit is contained in:
dsh-custom committed 2026-10-10 13:59:09 +08:00
commit a44227b69a
14 files changed
+5517

No files matched your search

+735
View File
@@ -0,0 +1,735 @@
import { randomBytes } from "node:crypto";
import { credentialRef } from "@deepseek-ai/dsh-credentials";
import {
WORKBUDDY_API_KEYS_REF,
WORKBUDDY_SESSION_REF,
WORKBUDDY_SESSIONS_REF,
WORKBUDDY_SESSION_ROUTING_REF,
LEGACY_API_KEYS_REF,
LEGACY_SESSION_REF,
LEGACY_SESSIONS_REF,
activeWorkBuddySession,
workBuddyApiKeyEntries,
workBuddySessionAccounts,
createWorkBuddyApiKeyStore,
createWorkBuddySessionStore,
createWorkBuddySessionRoutingState,
parseWorkBuddyApiKeys,
loginWorkBuddy,
parseWorkBuddySession,
parseWorkBuddySessions,
refreshWorkBuddySession,
serializeWorkBuddyApiKeys,
serializeWorkBuddySession,
serializeWorkBuddySessions,
serializeWorkBuddySessionRouting,
parseWorkBuddySessionRouting,
sessionNeedsRefresh,
upsertWorkBuddyApiKey,
upsertWorkBuddySession,
} from "./workbuddy-auth.js";
import { fetchWorkBuddyCredits } from "./workbuddy-credits.js";
const PROVIDER = "workbuddy-cn";
const LEGACY_PROVIDER = "codebuddy-cn";
const API_KEY_ENV = "WORKBUDDY_API_KEY";
const LEGACY_API_KEY_ENV = "CODEBUDDY_API_KEY";
const ROUTE = "/dsh-llm-workbuddy/auth";
const ENV_SOURCES = new Set(["env", "user-env", "project-env"]);
export function authenticationMode(config) {
const profile = config?.providers?.[PROVIDER] ?? config?.providers?.[LEGACY_PROVIDER];
return profile && profile.apiKeyEnv === undefined ? "token" : "api-key";
}
function json(res, status, body) {
res.writeHead(status, { "content-type": "application/json; charset=utf-8", "cache-control": "no-store" });
res.end(JSON.stringify(body));
}
function localPost(req) {
const address = req.socket.remoteAddress;
const loopback = address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
if (!loopback) return false;
const origin = req.headers.origin;
if (!origin) return req.headers["sec-fetch-site"] === "same-origin"
|| /(?:^|\s)@deepseek-ai\/dsh-desktop\/\d/.test(req.headers["user-agent"] ?? "");
if (origin === "dsh-app://app") return true;
try {
return ["127.0.0.1", "localhost", "[::1]"].includes(new URL(origin).hostname);
} catch {
return false;
}
}
async function requestBody(req) {
let raw = "";
for await (const chunk of req) {
raw += chunk.toString();
if (raw.length > 64 * 1024) throw new Error("请求体过大");
}
if (!raw.trim()) return {};
try {
const value = JSON.parse(raw);
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
} catch {
throw new Error("请求参数格式无效");
}
}
async function setMode(settings, mode, apiKeyRef = API_KEY_ENV) {
const config = settings.get("llm-pi-ai");
const providers = config?.providers ?? {};
const exists = Object.hasOwn(providers, PROVIDER);
const legacy = !exists && Object.hasOwn(providers, LEGACY_PROVIDER) ? providers[LEGACY_PROVIDER] : undefined;
const path = ["providers", PROVIDER];
if (!exists) {
const value = { ...(legacy ?? {}), ...(mode === "token" ? {} : { apiKeyEnv: apiKeyRef }) };
await settings.mutate("llm-pi-ai", [
{ op: "set", path, value },
...(legacy ? [{ op: "unset", path: ["providers", LEGACY_PROVIDER] }] : []),
]);
return;
}
await settings.mutate("llm-pi-ai", [
{
op: mode === "token" ? "unset" : "set",
path: [...path, "apiKeyEnv"],
...(mode === "api-key" ? { value: apiKeyRef } : {}),
},
...(Object.hasOwn(providers, LEGACY_PROVIDER) ? [{ op: "unset", path: ["providers", LEGACY_PROVIDER] }] : []),
]);
}
function configuredApiKeyRef(settings) {
const providers = settings.get("llm-pi-ai")?.providers ?? {};
return providers[PROVIDER]?.apiKeyEnv ?? providers[LEGACY_PROVIDER]?.apiKeyEnv ?? API_KEY_ENV;
}
function settingsAccess(service, settingsNs) {
return {
get: () => typeof service.get === "function"
? service.get(settingsNs)
: service.describe().find((entry) => entry.ns === settingsNs)?.value,
revision: () => typeof service.get !== "function" && typeof service.describe === "function"
? service.describe().find((entry) => entry.ns === settingsNs)?.revision
: undefined,
mutate: (_ns, ops, revision) => service.mutate(settingsNs, ops, revision),
};
}
function validModelOverrides(models) {
if (!Array.isArray(models) || models.length > 100) return false;
const ids = new Set();
for (const model of models) {
if (!model || typeof model !== "object" || Array.isArray(model)
|| typeof model.id !== "string" || !model.id.trim() || model.id.length > 200
|| ids.has(model.id)) return false;
ids.add(model.id);
if (model.name !== undefined && (typeof model.name !== "string" || model.name.length > 200)) return false;
for (const key of ["contextWindow", "maxTokens"]) {
if (model[key] !== undefined && (!Number.isSafeInteger(model[key]) || model[key] <= 0)) return false;
}
if (model.input !== undefined && (!Array.isArray(model.input)
|| model.input.some((value) => value !== "text" && value !== "image"))) return false;
}
return true;
}
export const __testing = Object.freeze({ settingsAccess, setMode, validModelOverrides, localPost });
function maskApiKey(value) {
const text = typeof value === "string" ? value : "";
return text.length > 4 ? `••••${text.slice(-4)}` : text ? "••••" : "";
}
function textLabel(value) {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
function storedApiKeyRef() {
return `WORKBUDDY_API_KEY_DSH_${Date.now().toString(36).toUpperCase()}_${randomBytes(6).toString("hex").toUpperCase()}`;
}
async function readApiKeyStore(credentials) {
for (const ref of [WORKBUDDY_API_KEYS_REF, LEGACY_API_KEYS_REF]) {
const stored = await credentials.resolve(credentialRef(ref));
if (stored?.value) return parseWorkBuddyApiKeys(stored.value);
}
return createWorkBuddyApiKeyStore();
}
async function writeApiKeyStore(credentials, store) {
const normalized = createWorkBuddyApiKeyStore(store?.entries ?? [], store?.activeId);
if (normalized.entries.length === 0) {
await credentials.unset(credentialRef(WORKBUDDY_API_KEYS_REF));
await credentials.unset(credentialRef(LEGACY_API_KEYS_REF));
return;
}
await credentials.set(credentialRef(WORKBUDDY_API_KEYS_REF), serializeWorkBuddyApiKeys(normalized));
await credentials.unset(credentialRef(LEGACY_API_KEYS_REF));
}
async function currentApiKeyState(webCtx, settings) {
const store = await readApiKeyStore(webCtx.credentials);
const apiMode = authenticationMode(settings.get("llm-pi-ai")) === "api-key";
const ref = apiMode ? configuredApiKeyRef(settings) : undefined;
const items = [];
const seenRefs = new Set();
for (const envRef of [API_KEY_ENV, LEGACY_API_KEY_ENV]) {
const environment = await webCtx.credentials.resolve(credentialRef(envRef));
if (environment?.value) {
items.push({
id: `env:${envRef}`,
kind: "environment",
label: `环境变量 ${envRef}`,
ref: envRef,
configured: true,
masked: maskApiKey(environment.value),
source: environment.source,
});
seenRefs.add(envRef);
}
}
for (const entry of workBuddyApiKeyEntries(store)) {
if (seenRefs.has(entry.ref)) continue;
const resolved = await webCtx.credentials.resolve(credentialRef(entry.ref));
items.push({
...entry,
kind: "dsh",
configured: Boolean(resolved?.value),
...(resolved?.value ? { masked: maskApiKey(resolved.value), source: resolved.source } : {}),
});
seenRefs.add(entry.ref);
}
if (ref && !seenRefs.has(ref)) {
const resolved = await webCtx.credentials.resolve(credentialRef(ref));
if (resolved?.value) {
items.push({
id: `dsh:${ref}`,
kind: ENV_SOURCES.has(resolved.source) ? "environment" : "dsh",
label: ENV_SOURCES.has(resolved.source) ? `环境变量 ${ref}` : "DSH 默认 API Key",
ref,
configured: true,
masked: maskApiKey(resolved.value),
source: resolved.source,
});
}
}
const configured = apiMode
? items.find((item) => item.ref === ref)
?? (ref === API_KEY_ENV ? items.find((item) => item.ref === LEGACY_API_KEY_ENV) : undefined)
: undefined;
const active = configured ?? items.find((item) => item.id === store.activeId) ?? items[0];
return {
apiKeys: items,
activeApiKeyId: active?.id ?? null,
apiKeyConfigured: Boolean(configured?.configured),
};
}
async function readSessionStore(credentials) {
for (const ref of [WORKBUDDY_SESSIONS_REF, LEGACY_SESSIONS_REF]) {
const stored = await credentials.resolve(credentialRef(ref));
if (stored?.value) return parseWorkBuddySessions(stored.value);
}
for (const ref of [WORKBUDDY_SESSION_REF, LEGACY_SESSION_REF]) {
const legacy = await credentials.resolve(credentialRef(ref));
if (legacy?.value) return createWorkBuddySessionStore([parseWorkBuddySession(legacy.value)]);
}
return createWorkBuddySessionStore();
}
async function writeSessionStore(credentials, store) {
const active = activeWorkBuddySession(store);
if (!active) {
await credentials.unset(credentialRef(WORKBUDDY_SESSIONS_REF));
await credentials.unset(credentialRef(WORKBUDDY_SESSION_REF));
await credentials.unset(credentialRef(LEGACY_SESSIONS_REF));
await credentials.unset(credentialRef(LEGACY_SESSION_REF));
return;
}
await credentials.set(credentialRef(WORKBUDDY_SESSIONS_REF), serializeWorkBuddySessions(store));
// Keep the old single-session reference as a compatibility pointer for older plugin versions.
await credentials.set(credentialRef(WORKBUDDY_SESSION_REF), serializeWorkBuddySession(active));
await credentials.unset(credentialRef(LEGACY_SESSIONS_REF));
await credentials.unset(credentialRef(LEGACY_SESSION_REF));
}
async function readSessionRouting(credentials) {
const stored = await credentials.resolve(credentialRef(WORKBUDDY_SESSION_ROUTING_REF));
return stored?.value ? parseWorkBuddySessionRouting(stored.value) : createWorkBuddySessionRoutingState();
}
async function writeSessionRouting(credentials, state) {
await credentials.set(credentialRef(WORKBUDDY_SESSION_ROUTING_REF), serializeWorkBuddySessionRouting(state));
}
function sessionIdOf(value) {
return typeof value === "string" && value.trim() ? value.trim() : undefined;
}
async function clearSessionBindings(credentials, predicate) {
const state = await readSessionRouting(credentials);
const bindings = Object.fromEntries(Object.entries(state.bindings).filter(([, binding]) => !predicate(binding)));
const clearRecent = state.lastUsed && predicate(state.lastUsed);
if (clearRecent || Object.keys(bindings).length !== Object.keys(state.bindings).length) {
await writeSessionRouting(credentials, {
...state,
bindings,
...(clearRecent ? { lastUsed: undefined } : {}),
});
}
}
async function resolveSession(webCtx, accountId, sessionId) {
const store = await readSessionStore(webCtx.credentials);
const routing = await readSessionRouting(webCtx.credentials);
const binding = routing.enabled && sessionIdOf(sessionId) ? routing.bindings[sessionIdOf(sessionId)] : undefined;
const boundAccountId = binding?.mode === "token" ? binding.accountId : undefined;
const requestedId = typeof accountId === "string" && accountId
? accountId
: boundAccountId ?? store.activeId;
let session = store.sessions.find((entry) => entry.id === requestedId) ?? activeWorkBuddySession(store);
if (!session) throw new Error("没有找到该 WorkBuddy 登录账号");
if (boundAccountId && session.id !== boundAccountId) throw new Error("当前会话绑定的 WorkBuddy 登录账号不存在");
if (sessionNeedsRefresh(session)) {
session = { ...session, ...(await refreshWorkBuddySession(session)), updatedAt: Date.now() };
const nextStore = {
...store,
sessions: store.sessions.map((entry) => entry.id === session.id ? session : entry),
};
await writeSessionStore(webCtx.credentials, nextStore);
}
return session;
}
export function installWorkBuddyWeb(ctx, settingsNs = "llm-pi-ai") {
ctx.inject(["webServer", "settings", "credentials"], (webCtx) => {
const settings = settingsAccess(webCtx.settings, settingsNs);
let loginPromise;
const currentState = async (requestedSessionId) => {
const sessionId = sessionIdOf(requestedSessionId);
const store = await readSessionStore(webCtx.credentials);
const active = activeWorkBuddySession(store);
const routing = await readSessionRouting(webCtx.credentials);
const apiKeys = await currentApiKeyState(webCtx, settings);
const globalMode = authenticationMode(settings.get("llm-pi-ai"));
const sessionBinding = routing.enabled && sessionId ? routing.bindings[sessionId] : undefined;
const recentBinding = routing.enabled ? routing.lastUsed : undefined;
const exactAccount = sessionBinding?.mode === "token" ? store.sessions.find((entry) => entry.id === sessionBinding.accountId) : undefined;
const exactApiKey = sessionBinding?.mode === "api-key" ? apiKeys.apiKeys.find((entry) => entry.ref === sessionBinding.apiKeyRef) : undefined;
const recentAccount = recentBinding?.mode === "token" ? store.sessions.find((entry) => entry.id === recentBinding.accountId) : undefined;
const recentApiKey = recentBinding?.mode === "api-key" ? apiKeys.apiKeys.find((entry) => entry.ref === recentBinding.apiKeyRef) : undefined;
const sessionBindingValid = !sessionBinding || (sessionBinding.mode === "token" ? Boolean(exactAccount) : Boolean(exactApiKey?.configured));
const recentBindingValid = !recentBinding || (recentBinding.mode === "token" ? Boolean(recentAccount) : Boolean(recentApiKey?.configured));
const effectiveBinding = sessionBinding;
const suggestedBinding = !sessionBinding && recentBindingValid ? recentBinding : undefined;
// Unbound Sessions display the new-Session default, but runtime auth only
// becomes exact after the first model call persists it for that Session.
const displayedBinding = effectiveBinding ?? suggestedBinding;
const boundAccount = displayedBinding?.mode === "token" ? (effectiveBinding ? exactAccount : recentAccount) : undefined;
const boundApiKey = displayedBinding?.mode === "api-key" ? (effectiveBinding ? exactApiKey : recentApiKey) : undefined;
const mode = displayedBinding?.mode ?? globalMode;
return {
ok: true,
mode,
globalMode,
routingEnabled: routing.enabled,
sessionId: sessionId ?? null,
sessionBinding: sessionBinding ?? null,
sessionBindingValid,
lastUsedBinding: recentBinding ?? null,
lastUsedBindingValid: recentBindingValid,
effectiveBinding: effectiveBinding ?? null,
suggestedBinding: suggestedBinding ?? null,
suggestedAccountId: suggestedBinding?.mode === "token" ? recentAccount?.id ?? null : null,
suggestedApiKeyId: suggestedBinding?.mode === "api-key" ? recentApiKey?.id ?? null : null,
authenticated: displayedBinding
? displayedBinding.mode === "token" ? Boolean(boundAccount) : Boolean(boundApiKey?.configured)
: globalMode === "token" ? active !== undefined : apiKeys.apiKeyConfigured,
activeAccountId: displayedBinding?.mode === "token" ? boundAccount?.id ?? null : active?.id ?? null,
globalActiveAccountId: active?.id ?? null,
accounts: workBuddySessionAccounts(store),
...apiKeys,
activeApiKeyId: displayedBinding?.mode === "api-key" ? boundApiKey?.id ?? null : apiKeys.activeApiKeyId,
globalActiveApiKeyId: apiKeys.activeApiKeyId,
apiKeyConfigured: mode === "api-key" && (displayedBinding?.mode === "api-key" ? Boolean(boundApiKey?.configured) : apiKeys.apiKeyConfigured),
};
};
const status = async (req, res) => {
try {
const sessionId = new URL(req.url, "http://127.0.0.1").searchParams.get("sessionId");
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "读取 WorkBuddy 认证状态失败" });
}
};
const models = async (req, res) => {
try {
const providers = settings.get("llm-pi-ai")?.providers ?? {};
const profile = providers[PROVIDER] ?? providers[LEGACY_PROVIDER];
if (req.method === "GET") return json(res, 200, {
ok: true,
models: profile?.models ?? null,
revision: settings.revision(),
});
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面编辑模型" });
const body = await requestBody(req);
if (body.action === "discover") {
return json(res, 200, { ok: true, models: await webCtx.llm.discoverModels(settingsNs, { provider: PROVIDER }) });
}
if (body.action !== "save" && body.action !== "reset") return json(res, 400, { ok: false, message: "模型操作无效" });
if (body.action === "save" && !validModelOverrides(body.models)) return json(res, 400, { ok: false, message: "模型列表格式无效" });
if (profile === undefined && body.action === "reset") return json(res, 200, { ok: true, models: null, revision: settings.revision() });
const path = ["providers", providers[PROVIDER] === undefined && providers[LEGACY_PROVIDER] !== undefined ? LEGACY_PROVIDER : PROVIDER];
const ops = profile === undefined
? [{ op: "set", path, value: { apiKeyEnv: API_KEY_ENV, ...(body.action === "save" ? { models: body.models } : {}) } }]
: [body.action === "save"
? { op: "set", path: [...path, "models"], value: body.models }
: { op: "unset", path: [...path, "models"] }];
await settings.mutate("llm-pi-ai", ops, body.revision);
return json(res, 200, { ok: true, models: body.action === "save" ? body.models : null, revision: settings.revision() });
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "编辑 WorkBuddy 模型失败" });
}
};
const routing = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面切换会话认证" });
try {
const body = await requestBody(req);
if (typeof body.enabled !== "boolean") return json(res, 400, { ok: false, message: "会话级认证开关参数无效" });
const state = await readSessionRouting(webCtx.credentials);
await writeSessionRouting(webCtx.credentials, { ...state, enabled: body.enabled });
json(res, 200, await currentState(body.sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "切换会话级认证失败" });
}
};
const unbind = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面解除会话认证" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
if (!sessionId) return json(res, 400, { ok: false, message: "缺少当前会话 ID" });
const state = await readSessionRouting(webCtx.credentials);
if (Object.hasOwn(state.bindings, sessionId)) {
const bindings = { ...state.bindings };
delete bindings[sessionId];
await writeSessionRouting(webCtx.credentials, { ...state, bindings });
}
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "解除会话认证失败" });
}
};
const apiKey = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面切换认证方式" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
const routingState = await readSessionRouting(webCtx.credentials);
const sessionRouting = routingState.enabled && sessionId;
let ref = API_KEY_ENV;
if (typeof body.keyId === "string" && body.keyId) {
const state = await currentApiKeyState(webCtx, settings);
const selected = state.apiKeys.find((entry) => entry.id === body.keyId);
if (!selected) return json(res, 404, { ok: false, message: "没有找到该 WorkBuddy API Key" });
if (!selected.configured) return json(res, 409, { ok: false, message: "该 API Key 已不可用,请删除后重新添加" });
ref = selected.ref;
if (!sessionRouting && !routingState.enabled) {
const store = await readApiKeyStore(webCtx.credentials);
await writeApiKeyStore(webCtx.credentials, { ...store, activeId: selected.kind === "dsh" ? selected.id : null });
}
}
credentialRef(ref);
if (routingState.enabled && !body.keyId) {
const state = await currentApiKeyState(webCtx, settings);
const selected = state.apiKeys.find((entry) => entry.ref === ref) ?? state.apiKeys.find((entry) => entry.configured);
if (!selected?.configured) return json(res, 409, { ok: false, message: "未检测到可用 WorkBuddy API Key" });
ref = selected.ref;
}
if (sessionRouting) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
bindings: { ...routingState.bindings, [sessionId]: { mode: "api-key", apiKeyRef: ref } },
lastUsed: { mode: "api-key", apiKeyRef: ref },
});
} else if (routingState.enabled) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
lastUsed: { mode: "api-key", apiKeyRef: ref },
});
} else {
await setMode(settings, "api-key", ref);
}
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "切换 API Key 失败" });
}
};
const addApiKey = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面保存 API Key" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
const value = typeof body.key === "string" ? body.key.trim() : "";
if (!value) return json(res, 400, { ok: false, message: "请输入 API Key" });
if (value.length > 16 * 1024) return json(res, 413, { ok: false, message: "API Key 长度超出限制" });
const store = await readApiKeyStore(webCtx.credentials);
const ref = storedApiKeyRef();
const entry = {
id: `dsh:${ref}`,
ref,
label: textLabel(body.label) ?? `DSH API Key ${store.entries.length + 1}`,
};
await webCtx.credentials.set(credentialRef(ref), value);
try {
const next = upsertWorkBuddyApiKey(store, entry);
await writeApiKeyStore(webCtx.credentials, next);
const routingState = await readSessionRouting(webCtx.credentials);
if (routingState.enabled && sessionId) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
bindings: { ...routingState.bindings, [sessionId]: { mode: "api-key", apiKeyRef: ref } },
lastUsed: { mode: "api-key", apiKeyRef: ref },
});
} else if (routingState.enabled) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
lastUsed: { mode: "api-key", apiKeyRef: ref },
});
} else {
await setMode(settings, "api-key", ref);
}
} catch (error) {
await webCtx.credentials.unset(credentialRef(ref));
throw error;
}
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "保存 API Key 失败" });
}
};
const removeApiKey = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面删除 API Key" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
const store = await readApiKeyStore(webCtx.credentials);
const entry = store.entries.find((item) => item.id === body.keyId);
if (!entry) return json(res, 404, { ok: false, message: "没有找到该 WorkBuddy API Key" });
const activeRef = configuredApiKeyRef(settings);
await webCtx.credentials.unset(credentialRef(entry.ref));
const remaining = store.entries.filter((item) => item.id !== entry.id);
await writeApiKeyStore(webCtx.credentials, { version: 1, activeId: remaining[0]?.id, entries: remaining });
await clearSessionBindings(webCtx.credentials, (binding) => binding.mode === "api-key" && binding.apiKeyRef === entry.ref);
if (authenticationMode(settings.get("llm-pi-ai")) === "api-key" && activeRef === entry.ref) {
const environment = await webCtx.credentials.resolve(credentialRef(API_KEY_ENV));
let fallback = API_KEY_ENV;
if (!environment?.value) {
for (const candidate of remaining) {
if ((await webCtx.credentials.resolve(credentialRef(candidate.ref)))?.value) {
fallback = candidate.ref;
break;
}
}
}
await setMode(settings, "api-key", fallback);
}
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "删除 API Key 失败" });
}
};
const token = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面切换认证方式" });
try {
const body = await requestBody(req);
const store = await readSessionStore(webCtx.credentials);
const sessionId = sessionIdOf(body.sessionId);
const accountId = typeof body.accountId === "string" ? body.accountId : store.activeId;
const active = store.sessions.find((entry) => entry.id === accountId);
if (!active) return json(res, 409, { ok: false, message: "没有找到该 WorkBuddy 登录账号" });
const routingState = await readSessionRouting(webCtx.credentials);
if (routingState.enabled && sessionId) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
bindings: { ...routingState.bindings, [sessionId]: { mode: "token", accountId: active.id } },
lastUsed: { mode: "token", accountId: active.id },
});
} else if (routingState.enabled) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
lastUsed: { mode: "token", accountId: active.id },
});
} else {
await writeSessionStore(webCtx.credentials, { ...store, activeId: active.id });
await setMode(settings, "token");
}
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "切换令牌账号失败" });
}
};
const credits = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面查询 WorkBuddy 积分" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
const selection = await currentState(sessionId);
if (selection.mode !== "token") {
// [自用定制 2026-10-10] 账单接口接受 API Key 作为 Bearer 令牌,故 API Key 模式同样可以查积分。
// 还原:见同目录 workbuddy-web.js.bak-20261010
const keyStore = await readApiKeyStore(webCtx.credentials);
const activeKey = keyStore.entries.find((entry) => entry.id === keyStore.activeId) ?? keyStore.entries[0];
const candidates = [activeKey?.ref, API_KEY_ENV, LEGACY_API_KEY_ENV]
.filter((value) => typeof value === "string" && value);
let apiKey;
for (const ref of candidates) {
const stored = await webCtx.credentials.resolve(credentialRef(ref));
if (stored?.value) { apiKey = stored.value; break; }
}
if (!apiKey) {
return json(res, 200, {
ok: true,
accountId: null,
credits: null,
totalDosage: null,
segments: [],
unlimited: false,
cycleResetTime: null,
creditError: "未找到可用的 API Key",
todayUsage: null,
todayUsageError: "今日请求量查询仅支持 WorkBuddy 令牌登录",
});
}
const keyed = await fetchWorkBuddyCredits({
auth: { accessToken: apiKey, domain: "www.codebuddy.cn" },
account: {},
});
return json(res, 200, {
ok: true,
accountId: null,
credits: keyed.credits,
totalDosage: keyed.totalDosage,
segments: keyed.segments,
unlimited: !!keyed.unlimited,
cycleResetTime: keyed.cycleResetTime ?? null,
planName: keyed.planName ?? null,
planCycleEnd: keyed.planCycleEnd ?? null,
creditError: keyed.creditError ?? null,
todayUsage: null,
todayUsageError: "今日请求量查询仅支持 WorkBuddy 令牌登录",
});
}
const session = await resolveSession(webCtx, selection.activeAccountId ?? body.accountId, sessionId);
const result = await fetchWorkBuddyCredits(session);
json(res, 200, {
ok: true,
accountId: session.id,
credits: result.credits,
totalDosage: result.totalDosage,
segments: result.segments,
unlimited: !!result.unlimited,
cycleResetTime: result.cycleResetTime ?? null,
planName: result.planName ?? null,
planCycleEnd: result.planCycleEnd ?? null,
creditError: result.creditError ?? null,
todayUsage: result.todayUsage ?? null,
todayUsageError: result.todayUsageError ?? null,
});
} catch (error) {
json(res, 200, {
ok: true,
accountId: null,
credits: null,
totalDosage: null,
segments: [],
unlimited: false,
cycleResetTime: null,
creditError: error instanceof Error ? error.message : "查询 WorkBuddy 积分失败",
todayUsage: null,
todayUsageError: error instanceof Error ? error.message : "查询 WorkBuddy 今日请求量失败",
});
}
};
const login = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面登录" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
loginPromise ??= (async () => {
const session = await loginWorkBuddy();
const store = await readSessionStore(webCtx.credentials);
const nextStore = upsertWorkBuddySession(store, session);
await writeSessionStore(webCtx.credentials, nextStore);
const routingState = await readSessionRouting(webCtx.credentials);
if (routingState.enabled && sessionId) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
bindings: { ...routingState.bindings, [sessionId]: { mode: "token", accountId: nextStore.activeId } },
lastUsed: { mode: "token", accountId: nextStore.activeId },
});
} else if (routingState.enabled) {
await writeSessionRouting(webCtx.credentials, {
...routingState,
lastUsed: { mode: "token", accountId: nextStore.activeId },
});
} else {
await setMode(settings, "token");
}
})().finally(() => {
loginPromise = undefined;
});
await loginPromise;
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "WorkBuddy 登录失败" });
}
};
const remove = async (req, res) => {
if (req.method !== "POST") return json(res, 405, { ok: false, message: "Method not allowed" });
if (!localPost(req)) return json(res, 403, { ok: false, message: "只允许从本机 DSH 页面管理登录账号" });
try {
const body = await requestBody(req);
const sessionId = sessionIdOf(body.sessionId);
const store = await readSessionStore(webCtx.credentials);
const accountId = typeof body.accountId === "string" ? body.accountId : store.activeId;
const sessions = store.sessions.filter((entry) => entry.id !== accountId);
if (sessions.length === store.sessions.length) return json(res, 404, { ok: false, message: "没有找到该 WorkBuddy 登录账号" });
const activeId = accountId === store.activeId ? sessions[0]?.id : store.activeId;
await writeSessionStore(webCtx.credentials, { version: 1, activeId, sessions });
await clearSessionBindings(webCtx.credentials, (binding) => binding.mode === "token" && binding.accountId === accountId);
json(res, 200, await currentState(sessionId));
} catch (error) {
json(res, 500, { ok: false, message: error instanceof Error ? error.message : "删除令牌账号失败" });
}
};
webCtx.effect(() => {
const dispose = [
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/status`, handler: status }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/models`, handler: models }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/routing`, handler: routing }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/unbind`, handler: unbind }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/api-key`, handler: apiKey }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/api-key/add`, handler: addApiKey }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/api-key/remove`, handler: removeApiKey }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/token`, handler: token }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/credits`, handler: credits }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/login`, handler: login }),
webCtx.webServer.register({ kind: "exact", path: `${ROUTE}/remove`, handler: remove }),
];
return () => dispose.forEach((fn) => fn());
}, "llm-workbuddy: web login routes");
});
}