@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.
Files changed (57) hide show
  1. package/dist/src/daemon.d.ts +11 -4
  2. package/dist/src/daemon.d.ts.map +1 -1
  3. package/dist/src/daemon.js +130 -42
  4. package/dist/src/daemon.js.map +1 -1
  5. package/dist/src/doctor-diagnostics.d.ts.map +1 -1
  6. package/dist/src/doctor-diagnostics.js +7 -1
  7. package/dist/src/doctor-diagnostics.js.map +1 -1
  8. package/dist/src/prewarm-cache.d.ts +4 -0
  9. package/dist/src/prewarm-cache.d.ts.map +1 -1
  10. package/dist/src/prewarm-cache.js +1 -0
  11. package/dist/src/prewarm-cache.js.map +1 -1
  12. package/dist/src/prewarm-scheduler.d.ts +2 -0
  13. package/dist/src/prewarm-scheduler.d.ts.map +1 -1
  14. package/dist/src/prewarm-scheduler.js +4 -1
  15. package/dist/src/prewarm-scheduler.js.map +1 -1
  16. package/dist/src/provider-install.d.ts.map +1 -1
  17. package/dist/src/provider-install.js +196 -18
  18. package/dist/src/provider-install.js.map +1 -1
  19. package/dist/src/seller-catalog.d.ts +4 -0
  20. package/dist/src/seller-catalog.d.ts.map +1 -1
  21. package/dist/src/seller-catalog.js.map +1 -1
  22. package/dist/src/seller-pool.d.ts +13 -0
  23. package/dist/src/seller-pool.d.ts.map +1 -1
  24. package/dist/src/seller-pool.js +43 -2
  25. package/dist/src/seller-pool.js.map +1 -1
  26. package/dist/src/seller-route-planner.d.ts +9 -0
  27. package/dist/src/seller-route-planner.d.ts.map +1 -1
  28. package/dist/src/seller-route-planner.js +39 -15
  29. package/dist/src/seller-route-planner.js.map +1 -1
  30. package/dist/src/seller-routing-strategy.d.ts +6 -4
  31. package/dist/src/seller-routing-strategy.d.ts.map +1 -1
  32. package/dist/src/seller-routing-strategy.js +15 -12
  33. package/dist/src/seller-routing-strategy.js.map +1 -1
  34. package/dist/src/terminal-detect.d.ts +5 -5
  35. package/dist/src/terminal-detect.d.ts.map +1 -1
  36. package/dist/src/terminal-detect.js +79 -26
  37. package/dist/src/terminal-detect.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/daemon.ts +168 -46
  40. package/src/doctor-diagnostics.ts +5 -1
  41. package/src/prewarm-cache.ts +5 -0
  42. package/src/prewarm-scheduler.ts +6 -1
  43. package/src/provider-install.ts +203 -18
  44. package/src/seller-catalog.ts +4 -0
  45. package/src/seller-pool.ts +68 -2
  46. package/src/seller-route-planner.ts +61 -15
  47. package/src/seller-routing-strategy.ts +21 -16
  48. package/src/terminal-detect.ts +81 -24
  49. package/static/ui/assets/index-DEDEl8o2.js +236 -0
  50. package/static/ui/assets/{index-UAfOhbwC.js.map → index-DEDEl8o2.js.map} +1 -1
  51. package/static/ui/index.html +1 -1
  52. package/tests/control-plane-ui-endpoints.test.ts +73 -0
  53. package/tests/seller-pool.test.ts +55 -0
  54. package/tests/seller-route-planner.test.ts +45 -1
  55. package/tests/seller-routing-strategy.test.ts +6 -5
  56. package/tests/tokenbuddy.test.ts +346 -38
  57. package/static/ui/assets/index-UAfOhbwC.js +0 -236
@@ -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/config.json`、`~/.hermes/settings.json` 等约定路径判断。
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", "config.json");
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/config.json`" : "Missing `~/.openclaw/config.json`"
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", "settings.json");
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/settings.json`" : "Missing `~/.hermes/settings.json`"
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
- * 写入 `api_url` / `api_key`(占位 `TOKENBUDDY_PROXY`)/`model`。
216
+ * 写入 `models.providers.tokenbuddy`,并把 `agents.defaults.model` 指向 TokenBuddy。
217
217
  * 失败仅打印日志,不抛出。
218
218
  *
219
- * @param configPath `~/.openclaw/config.json` 的绝对路径
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.api_url = proxyUrl;
234
- config.api_key = PLACEHOLDER_API_KEY;
235
- config.model = model;
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
- * 在 `openai` 块下写入 `base_url` / `api_key`(占位 `TOKENBUDDY_PROXY`)/`model`。
278
+ * 在 `model` 块下写入 TokenBuddy 的默认模型、custom provider、base_url、api_key 和 api_mode。
248
279
  * 失败仅打印日志,不抛出。
249
280
  *
250
- * @param configPath `~/.hermes/settings.json` 的绝对路径
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
- let config: any = {};
260
- if (fs.existsSync(configPath)) {
261
- try { config = JSON.parse(fs.readFileSync(configPath, "utf8")); } catch {}
262
- }
263
-
264
- if (!config.openai || typeof config.openai !== "object") {
265
- config.openai = {};
266
- }
267
- config.openai.base_url = proxyUrl;
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, JSON.stringify(config, null, 2), "utf8");
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
+ }