@tokenbuddy/tokenbuddy 1.0.28 → 1.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/daemon.d.ts +11 -4
- package/dist/src/daemon.d.ts.map +1 -1
- package/dist/src/daemon.js +130 -42
- package/dist/src/daemon.js.map +1 -1
- package/dist/src/doctor-diagnostics.d.ts.map +1 -1
- package/dist/src/doctor-diagnostics.js +7 -1
- package/dist/src/doctor-diagnostics.js.map +1 -1
- package/dist/src/prewarm-cache.d.ts +4 -0
- package/dist/src/prewarm-cache.d.ts.map +1 -1
- package/dist/src/prewarm-cache.js +1 -0
- package/dist/src/prewarm-cache.js.map +1 -1
- package/dist/src/prewarm-scheduler.d.ts +2 -0
- package/dist/src/prewarm-scheduler.d.ts.map +1 -1
- package/dist/src/prewarm-scheduler.js +4 -1
- package/dist/src/prewarm-scheduler.js.map +1 -1
- package/dist/src/provider-install.d.ts.map +1 -1
- package/dist/src/provider-install.js +196 -18
- package/dist/src/provider-install.js.map +1 -1
- package/dist/src/seller-catalog.d.ts +4 -0
- package/dist/src/seller-catalog.d.ts.map +1 -1
- package/dist/src/seller-catalog.js.map +1 -1
- package/dist/src/seller-pool.d.ts +13 -0
- package/dist/src/seller-pool.d.ts.map +1 -1
- package/dist/src/seller-pool.js +43 -2
- package/dist/src/seller-pool.js.map +1 -1
- package/dist/src/seller-route-planner.d.ts +9 -0
- package/dist/src/seller-route-planner.d.ts.map +1 -1
- package/dist/src/seller-route-planner.js +39 -15
- package/dist/src/seller-route-planner.js.map +1 -1
- package/dist/src/seller-routing-strategy.d.ts +6 -4
- package/dist/src/seller-routing-strategy.d.ts.map +1 -1
- package/dist/src/seller-routing-strategy.js +15 -12
- package/dist/src/seller-routing-strategy.js.map +1 -1
- package/dist/src/terminal-detect.d.ts +5 -5
- package/dist/src/terminal-detect.d.ts.map +1 -1
- package/dist/src/terminal-detect.js +79 -26
- package/dist/src/terminal-detect.js.map +1 -1
- package/package.json +1 -1
- package/src/daemon.ts +168 -46
- package/src/doctor-diagnostics.ts +5 -1
- package/src/prewarm-cache.ts +5 -0
- package/src/prewarm-scheduler.ts +6 -1
- package/src/provider-install.ts +203 -18
- package/src/seller-catalog.ts +4 -0
- package/src/seller-pool.ts +68 -2
- package/src/seller-route-planner.ts +61 -15
- package/src/seller-routing-strategy.ts +21 -16
- package/src/terminal-detect.ts +81 -24
- package/static/ui/assets/index-DEDEl8o2.js +236 -0
- package/static/ui/assets/{index-UAfOhbwC.js.map → index-DEDEl8o2.js.map} +1 -1
- package/static/ui/index.html +1 -1
- package/tests/control-plane-ui-endpoints.test.ts +73 -0
- package/tests/seller-pool.test.ts +55 -0
- package/tests/seller-route-planner.test.ts +45 -1
- package/tests/seller-routing-strategy.test.ts +6 -5
- package/tests/tokenbuddy.test.ts +346 -38
- package/static/ui/assets/index-UAfOhbwC.js +0 -236
package/src/terminal-detect.ts
CHANGED
|
@@ -35,7 +35,7 @@ export function getHomeDir(): string {
|
|
|
35
35
|
* Detect which coding terminals are installed on the local system.
|
|
36
36
|
*
|
|
37
37
|
* 通过 `~/.claude/settings.json`、`Library/Application Support/Claude/...`、
|
|
38
|
-
* `~/.openclaw/
|
|
38
|
+
* `~/.openclaw/openclaw.json`、`~/.hermes/config.yaml` 等约定路径判断。
|
|
39
39
|
* Claude Desktop 在非 macOS 平台上 `configPath` 可能为空字符串,
|
|
40
40
|
* 调用方需以 `detected` 字段为准。
|
|
41
41
|
*
|
|
@@ -74,25 +74,25 @@ export function detectTerminals(): TerminalCandidate[] {
|
|
|
74
74
|
});
|
|
75
75
|
|
|
76
76
|
// 3. Openclaw
|
|
77
|
-
const openclawPath = path.join(home, ".openclaw", "
|
|
77
|
+
const openclawPath = path.join(home, ".openclaw", "openclaw.json");
|
|
78
78
|
const openclawDetected = fs.existsSync(openclawPath);
|
|
79
79
|
candidates.push({
|
|
80
80
|
id: "openclaw",
|
|
81
81
|
name: "Openclaw Agent",
|
|
82
82
|
detected: openclawDetected,
|
|
83
83
|
configPath: openclawPath,
|
|
84
|
-
reason: openclawDetected ? "Found `~/.openclaw/
|
|
84
|
+
reason: openclawDetected ? "Found `~/.openclaw/openclaw.json`" : "Missing `~/.openclaw/openclaw.json`"
|
|
85
85
|
});
|
|
86
86
|
|
|
87
87
|
// 4. Hermes
|
|
88
|
-
const hermesPath = path.join(home, ".hermes", "
|
|
88
|
+
const hermesPath = path.join(home, ".hermes", "config.yaml");
|
|
89
89
|
const hermesDetected = fs.existsSync(hermesPath);
|
|
90
90
|
candidates.push({
|
|
91
91
|
id: "hermes",
|
|
92
92
|
name: "Hermes Terminal",
|
|
93
93
|
detected: hermesDetected,
|
|
94
94
|
configPath: hermesPath,
|
|
95
|
-
reason: hermesDetected ? "Found `~/.hermes/
|
|
95
|
+
reason: hermesDetected ? "Found `~/.hermes/config.yaml`" : "Missing `~/.hermes/config.yaml`"
|
|
96
96
|
});
|
|
97
97
|
|
|
98
98
|
return candidates;
|
|
@@ -213,10 +213,10 @@ export function rewriteClaudeDesktop(configPath: string, proxyUrl: string, model
|
|
|
213
213
|
/**
|
|
214
214
|
* Rewrite Openclaw settings.
|
|
215
215
|
*
|
|
216
|
-
* 写入 `
|
|
216
|
+
* 写入 `models.providers.tokenbuddy`,并把 `agents.defaults.model` 指向 TokenBuddy。
|
|
217
217
|
* 失败仅打印日志,不抛出。
|
|
218
218
|
*
|
|
219
|
-
* @param configPath `~/.openclaw/
|
|
219
|
+
* @param configPath `~/.openclaw/openclaw.json` 的绝对路径
|
|
220
220
|
* @param proxyUrl TokenBuddy proxy 的 base URL
|
|
221
221
|
* @param model Openclaw 默认模型 ID
|
|
222
222
|
*/
|
|
@@ -230,9 +230,40 @@ export function rewriteOpenclaw(configPath: string, proxyUrl: string, model: str
|
|
|
230
230
|
try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
config.
|
|
234
|
-
|
|
235
|
-
|
|
233
|
+
const providers = config.models && typeof config.models === "object" && !Array.isArray(config.models)
|
|
234
|
+
? config.models.providers && typeof config.models.providers === "object" && !Array.isArray(config.models.providers)
|
|
235
|
+
? config.models.providers
|
|
236
|
+
: {}
|
|
237
|
+
: {};
|
|
238
|
+
const tokenbuddy = providers.tokenbuddy && typeof providers.tokenbuddy === "object" && !Array.isArray(providers.tokenbuddy)
|
|
239
|
+
? providers.tokenbuddy
|
|
240
|
+
: {};
|
|
241
|
+
const existingModels = Array.isArray(tokenbuddy.models) ? tokenbuddy.models : [];
|
|
242
|
+
providers.tokenbuddy = {
|
|
243
|
+
...tokenbuddy,
|
|
244
|
+
baseUrl: openAiBaseUrl(proxyUrl),
|
|
245
|
+
apiKey: PLACEHOLDER_API_KEY,
|
|
246
|
+
auth: "api-key",
|
|
247
|
+
api: "openai-completions",
|
|
248
|
+
models: [
|
|
249
|
+
...existingModels.filter((entry: any) => !(entry && typeof entry === "object" && entry.id === model)),
|
|
250
|
+
{
|
|
251
|
+
id: model,
|
|
252
|
+
name: model,
|
|
253
|
+
api: "openai-completions",
|
|
254
|
+
input: ["text", "image"],
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
config.models = {
|
|
259
|
+
...(config.models && typeof config.models === "object" && !Array.isArray(config.models) ? config.models : {}),
|
|
260
|
+
providers,
|
|
261
|
+
};
|
|
262
|
+
const agents = config.agents && typeof config.agents === "object" && !Array.isArray(config.agents) ? config.agents : {};
|
|
263
|
+
const defaults = agents.defaults && typeof agents.defaults === "object" && !Array.isArray(agents.defaults) ? agents.defaults : {};
|
|
264
|
+
defaults.model = `tokenbuddy/${model}`;
|
|
265
|
+
agents.defaults = defaults;
|
|
266
|
+
config.agents = agents;
|
|
236
267
|
|
|
237
268
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
|
|
238
269
|
console.log(`[terminal-detect] Openclaw successfully routed to ${proxyUrl}`);
|
|
@@ -244,10 +275,10 @@ export function rewriteOpenclaw(configPath: string, proxyUrl: string, model: str
|
|
|
244
275
|
/**
|
|
245
276
|
* Rewrite Hermes settings.
|
|
246
277
|
*
|
|
247
|
-
* 在 `
|
|
278
|
+
* 在 `model` 块下写入 TokenBuddy 的默认模型、custom provider、base_url、api_key 和 api_mode。
|
|
248
279
|
* 失败仅打印日志,不抛出。
|
|
249
280
|
*
|
|
250
|
-
* @param configPath `~/.hermes/
|
|
281
|
+
* @param configPath `~/.hermes/config.yaml` 的绝对路径
|
|
251
282
|
* @param proxyUrl TokenBuddy proxy 的 base URL
|
|
252
283
|
* @param model Hermes 默认模型 ID
|
|
253
284
|
*/
|
|
@@ -256,21 +287,47 @@ export function rewriteHermes(configPath: string, proxyUrl: string, model: strin
|
|
|
256
287
|
const parent = path.dirname(configPath);
|
|
257
288
|
if (!fs.existsSync(parent)) fs.mkdirSync(parent, { recursive: true });
|
|
258
289
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
config.openai.api_key = PLACEHOLDER_API_KEY;
|
|
269
|
-
config.openai.model = model;
|
|
290
|
+
const existing = fs.existsSync(configPath) ? fs.readFileSync(configPath, "utf8") : "";
|
|
291
|
+
const modelSection = [
|
|
292
|
+
"model:",
|
|
293
|
+
` default: ${model}`,
|
|
294
|
+
" provider: custom",
|
|
295
|
+
` base_url: ${JSON.stringify(openAiBaseUrl(proxyUrl))}`,
|
|
296
|
+
` api_key: ${PLACEHOLDER_API_KEY}`,
|
|
297
|
+
" api_mode: chat_completions",
|
|
298
|
+
].join("\n");
|
|
270
299
|
|
|
271
|
-
fs.writeFileSync(configPath,
|
|
300
|
+
fs.writeFileSync(configPath, replaceTopLevelYamlSection(existing, "model", modelSection), "utf8");
|
|
272
301
|
console.log(`[terminal-detect] Hermes successfully routed to ${proxyUrl}`);
|
|
273
302
|
} catch (err: any) {
|
|
274
303
|
console.error("[terminal-detect] Failed to rewrite Hermes config:", err.message);
|
|
275
304
|
}
|
|
276
305
|
}
|
|
306
|
+
|
|
307
|
+
function openAiBaseUrl(proxyUrl: string): string {
|
|
308
|
+
const normalized = proxyUrl.replace(/\/+$/, "");
|
|
309
|
+
return normalized.endsWith("/v1") ? normalized : `${normalized}/v1`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function replaceTopLevelYamlSection(existing: string, sectionName: string, sectionBody: string): string {
|
|
313
|
+
const lines = existing.split(/\r?\n/);
|
|
314
|
+
const sectionStart = lines.findIndex((line) => line === `${sectionName}:` || line.startsWith(`${sectionName}: `));
|
|
315
|
+
const bodyLines = sectionBody.trimEnd().split(/\r?\n/);
|
|
316
|
+
if (sectionStart < 0) {
|
|
317
|
+
const prefix = existing.trimEnd();
|
|
318
|
+
return `${prefix}${prefix ? "\n" : ""}${bodyLines.join("\n")}\n`;
|
|
319
|
+
}
|
|
320
|
+
let sectionEnd = sectionStart + 1;
|
|
321
|
+
while (sectionEnd < lines.length) {
|
|
322
|
+
const line = lines[sectionEnd];
|
|
323
|
+
if (line.trim() && !line.startsWith(" ") && !line.startsWith("\t")) {
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
sectionEnd += 1;
|
|
327
|
+
}
|
|
328
|
+
return `${[
|
|
329
|
+
...lines.slice(0, sectionStart),
|
|
330
|
+
...bodyLines,
|
|
331
|
+
...lines.slice(sectionEnd),
|
|
332
|
+
].join("\n").replace(/\n*$/, "")}\n`;
|
|
333
|
+
}
|