dsh-livebench-panel 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -448,7 +448,7 @@ window.__ModuleLoader__.load({
448
448
  running && h("button", { className: c("btn") + " " + c("btnDanger"), onClick: onStop }, "停止"),
449
449
  h("button", { className: c("btnGhost") + " " + c("btn"), onClick: () => { loadConfig(); loadResults(); } }, "刷新"),
450
450
  selectedProvider && !selectedProvider.routable && h("span", { className: c("hint") },
451
- "该 provider 未配置 openai-completions baseURL,LiveBench 将按模型名原生路由(未注册的模型名会失败)。"),
451
+ "该 provider 的协议或端点未知,无法自动路由:LiveBench 将按模型名原生尝试,未注册的模型名会失败。"),
452
452
  ),
453
453
  startError !== null && h("p", { className: c("error") }, startError),
454
454
  ),
package/lib/index.js CHANGED
@@ -176,10 +176,10 @@ function readProviders(profileDir) {
176
176
  if (piAiData !== null) {
177
177
  for (const provider of providers) {
178
178
  if (provider.baseURL === null) {
179
- const base = builtinBaseUrl(piAiData, provider.id);
180
- if (base !== null) {
181
- provider.baseURL = base;
182
- provider.api = provider.api ?? "openai-completions";
179
+ const info = builtinProtocolInfo(piAiData, provider.id);
180
+ if (info) {
181
+ provider.baseURL = info.baseURL;
182
+ provider.api = info.api;
183
183
  }
184
184
  }
185
185
  }
@@ -215,17 +215,25 @@ function resolvePiAiDataDir(profileDir) {
215
215
  return null;
216
216
  }
217
217
 
218
- /** Read one provider's built-in OpenAI-compatible baseUrl from pi-ai data. */
219
- function builtinBaseUrl(piAiDataDir, providerId) {
218
+ /**
219
+ * Read one provider's built-in protocol + baseUrl from pi-ai data.
220
+ * Prefers OpenAI Chat Completions; Anthropic Messages is also routable
221
+ * (LiveBench talks it natively and honors ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY).
222
+ */
223
+ function builtinProtocolInfo(piAiDataDir, providerId) {
220
224
  if (!/^[a-z0-9-]+$/.test(providerId)) return null;
221
225
  const file = join(piAiDataDir, `${providerId}.json`);
222
226
  if (!existsSync(file)) return null;
223
227
  try {
224
228
  const data = JSON.parse(readFileSync(file, "utf8"));
225
- const openai = data?.["openai-completions"];
226
- if (openai && typeof openai === "object") {
227
- for (const entry of Object.values(openai)) {
228
- if (entry && typeof entry.baseUrl === "string" && entry.baseUrl.length > 0) return entry.baseUrl;
229
+ for (const api of ["openai-completions", "anthropic-messages"]) {
230
+ const group = data?.[api];
231
+ if (group && typeof group === "object") {
232
+ for (const entry of Object.values(group)) {
233
+ if (entry && typeof entry.baseUrl === "string" && entry.baseUrl.length > 0) {
234
+ return { api, baseURL: entry.baseUrl };
235
+ }
236
+ }
229
237
  }
230
238
  }
231
239
  } catch { /* unreadable data file — treat as unknown */ }
@@ -355,21 +363,32 @@ function displayModelName(providerId, modelId) {
355
363
  * The file is regenerated on every start; secrets never go in here.
356
364
  * @returns {string|null} error message, or null on success.
357
365
  */
358
- function writeGeneratedModelConfig(layout, YAML, { displayName, modelId, reasoningEffort }) {
366
+ function writeGeneratedModelConfig(layout, YAML, { displayName, modelId, reasoningEffort, protocol }) {
359
367
  const configDir = join(layout.livebenchDir, "model", "model_configs");
360
368
  const target = join(configDir, "dsh_panel_generated.yaml");
361
369
  void YAML;
362
- const doc = [
370
+ const lines = [
363
371
  "# Generated by dsh-livebench-panel — regenerated on every evaluation start.",
364
372
  "---",
365
373
  `display_name: ${displayName}`,
366
374
  "api_name:",
367
- " local: " + modelId,
368
- "api_kwargs:",
369
- " default:",
370
- ` reasoning_effort: ${reasoningEffort}`,
371
- "",
372
- ].join("\n");
375
+ ];
376
+ if (protocol === "anthropic") {
377
+ // Route through LiveBench's native anthropic client; the endpoint is
378
+ // pointed at the provider proxy via ANTHROPIC_BASE_URL (spawn env).
379
+ lines.push(` anthropic: ${modelId}`, "default_provider: anthropic");
380
+ if (reasoningEffort) {
381
+ // note: reasoning_effort is an OpenAI-style knob and is intentionally
382
+ // not forwarded on the anthropic protocol path.
383
+ }
384
+ } else {
385
+ lines.push(` local: ${modelId}`);
386
+ if (reasoningEffort) {
387
+ lines.push("api_kwargs:", " default:", ` reasoning_effort: ${reasoningEffort}`);
388
+ }
389
+ }
390
+ lines.push("");
391
+ const doc = lines.join("\n");
373
392
  try {
374
393
  if (!existsSync(configDir)) return `model_configs directory not found: ${configDir}`;
375
394
  writeFileSync(target, doc, "utf8");
@@ -532,8 +551,19 @@ function apply(ctx) {
532
551
  const hasEffortSuffix = reasoningEffort !== null && reasoningEffort !== "off";
533
552
  const displayName = displayModelName(providerId || "direct", modelId) + (hasEffortSuffix ? "@" + reasoningEffort : "");
534
553
  let cliModel = modelId;
535
- if (hasEffortSuffix) {
536
- const writeError = writeGeneratedModelConfig(layout, loadYaml(profileDir), { displayName, modelId, reasoningEffort });
554
+ let writeError = null;
555
+ // Anthropic-protocol proxies cannot go through --api-base (that path
556
+ // speaks OpenAI Chat Completions). Instead the generated model config
557
+ // selects LiveBench's native anthropic client and the spawn env points
558
+ // the SDK at the proxy (ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY).
559
+ const isAnthropicRoute = provider && provider.api === "anthropic-messages" && provider.baseURL;
560
+ if (isAnthropicRoute || hasEffortSuffix) {
561
+ writeError = writeGeneratedModelConfig(layout, loadYaml(profileDir), {
562
+ displayName,
563
+ modelId,
564
+ reasoningEffort: isAnthropicRoute ? null : reasoningEffort,
565
+ protocol: isAnthropicRoute ? "anthropic" : "openai",
566
+ });
537
567
  if (writeError) return { status: 500, payload: { ok: false, error: writeError } };
538
568
  cliModel = displayName;
539
569
  }
@@ -567,15 +597,13 @@ function apply(ctx) {
567
597
  // livebench import (shortuuid etc.).
568
598
  PATH: `${join(layout.root, ".venv", "Scripts")}${delimiter}${process.env.PATH ?? ""}`,
569
599
  };
570
- // Only OpenAI-compatible providers can be routed with --api-base; for
571
- // those, hand the key over via env (never the command line). The key is
572
- // resolved through the harness credential seam when available (values may
573
- // live encrypted in .credentials.yaml rather than in the process env),
574
- // falling back to the plain environment variable.
575
- if (provider && provider.api === "openai-completions" && provider.baseURL) {
576
- args.push("--api-base", provider.baseURL);
600
+ // Provider routing: keys are resolved through the harness credential seam
601
+ // when available (values may live encrypted in .credentials.yaml rather
602
+ // than in the process env), falling back to the plain environment
603
+ // variable. Secrets travel via env only never the command line.
604
+ if (provider && provider.baseURL) {
605
+ let key;
577
606
  if (provider.keyEnv) {
578
- let key;
579
607
  try {
580
608
  const credentials = ctx.get ? ctx.get("credentials") : undefined;
581
609
  if (credentials && typeof credentials.resolve === "function") {
@@ -584,7 +612,15 @@ function apply(ctx) {
584
612
  }
585
613
  } catch { /* credential seam unavailable — fall through to env */ }
586
614
  if (!key && process.env[provider.keyEnv]) key = process.env[provider.keyEnv];
615
+ }
616
+ if (provider.api === "openai-completions") {
617
+ args.push("--api-base", provider.baseURL);
587
618
  if (key) env.LIVEBENCH_API_KEY = key;
619
+ } else if (provider.api === "anthropic-messages") {
620
+ // The generated model config selects the native anthropic client;
621
+ // the Anthropic SDK picks endpoint+key up from these env vars.
622
+ if (key) env.ANTHROPIC_API_KEY = key;
623
+ env.ANTHROPIC_BASE_URL = provider.baseURL;
588
624
  }
589
625
  }
590
626
 
@@ -657,7 +693,7 @@ function apply(ctx) {
657
693
  providers: providers.map(({ id, name: pname, models, api, baseURL }) => ({
658
694
  id,
659
695
  name: pname,
660
- routable: api === "openai-completions" && typeof baseURL === "string" && baseURL.length > 0,
696
+ routable: (api === "openai-completions" || api === "anthropic-messages") && typeof baseURL === "string" && baseURL.length > 0,
661
697
  baseURL: baseURL ?? null,
662
698
  models,
663
699
  })),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-livebench-panel",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "DSH web plugin: a LiveBench tab in the Trajectory view (right of 对话/轨迹). Run LiveBench evaluations against every model configured in the DeepSeek Harness — pick provider/model, category, task, release and question range from dropdowns, watch progress, and read scores in place.",
5
5
  "license": "MIT",
6
6
  "type": "module",