dsh-livebench-panel 0.1.8 → 0.1.10

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,40 @@ 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 if (protocol === "openai_responses") {
385
+ // OpenAI Responses API proxy: provider name matches
386
+ // get_api_function('openai_responses') → chat_completion_openai_responses,
387
+ // which converts reasoning_effort to reasoning:{effort} itself.
388
+ lines.push(` openai_responses: ${modelId}`);
389
+ if (reasoningEffort) {
390
+ lines.push("api_kwargs:", " default:", ` reasoning_effort: ${reasoningEffort}`);
391
+ }
392
+ } else {
393
+ lines.push(` local: ${modelId}`);
394
+ if (reasoningEffort) {
395
+ lines.push("api_kwargs:", " default:", ` reasoning_effort: ${reasoningEffort}`);
396
+ }
397
+ }
398
+ lines.push("");
399
+ const doc = lines.join("\n");
373
400
  try {
374
401
  if (!existsSync(configDir)) return `model_configs directory not found: ${configDir}`;
375
402
  writeFileSync(target, doc, "utf8");
@@ -532,8 +559,26 @@ function apply(ctx) {
532
559
  const hasEffortSuffix = reasoningEffort !== null && reasoningEffort !== "off";
533
560
  const displayName = displayModelName(providerId || "direct", modelId) + (hasEffortSuffix ? "@" + reasoningEffort : "");
534
561
  let cliModel = modelId;
535
- if (hasEffortSuffix) {
536
- const writeError = writeGeneratedModelConfig(layout, loadYaml(profileDir), { displayName, modelId, reasoningEffort });
562
+ let writeError = null;
563
+ // Anthropic-protocol proxies cannot go through --api-base (that path
564
+ // speaks OpenAI Chat Completions). Instead the generated model config
565
+ // selects LiveBench's native anthropic client and the spawn env points
566
+ // the SDK at the proxy (ANTHROPIC_BASE_URL / ANTHROPIC_API_KEY).
567
+ const isAnthropicRoute = provider && provider.api === "anthropic-messages" && provider.baseURL;
568
+ // OpenAI Responses-API proxies (api: openai-responses) select LiveBench's
569
+ // openai_responses client; --api-base + LIVEBENCH_API_KEY route them the
570
+ // usual way, and reasoning_effort converts to reasoning:{effort} inside.
571
+ const isOpenAIResponsesRoute = provider
572
+ && (provider.api === "openai-responses" || provider.api === "openai_responses")
573
+ && provider.baseURL;
574
+ const needConfig = isAnthropicRoute || isOpenAIResponsesRoute || hasEffortSuffix;
575
+ if (needConfig) {
576
+ writeError = writeGeneratedModelConfig(layout, loadYaml(profileDir), {
577
+ displayName,
578
+ modelId,
579
+ reasoningEffort: isAnthropicRoute ? null : reasoningEffort,
580
+ protocol: isAnthropicRoute ? "anthropic" : isOpenAIResponsesRoute ? "openai_responses" : "openai",
581
+ });
537
582
  if (writeError) return { status: 500, payload: { ok: false, error: writeError } };
538
583
  cliModel = displayName;
539
584
  }
@@ -567,15 +612,13 @@ function apply(ctx) {
567
612
  // livebench import (shortuuid etc.).
568
613
  PATH: `${join(layout.root, ".venv", "Scripts")}${delimiter}${process.env.PATH ?? ""}`,
569
614
  };
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);
615
+ // Provider routing: keys are resolved through the harness credential seam
616
+ // when available (values may live encrypted in .credentials.yaml rather
617
+ // than in the process env), falling back to the plain environment
618
+ // variable. Secrets travel via env only never the command line.
619
+ if (provider && provider.baseURL) {
620
+ let key;
577
621
  if (provider.keyEnv) {
578
- let key;
579
622
  try {
580
623
  const credentials = ctx.get ? ctx.get("credentials") : undefined;
581
624
  if (credentials && typeof credentials.resolve === "function") {
@@ -584,7 +627,15 @@ function apply(ctx) {
584
627
  }
585
628
  } catch { /* credential seam unavailable — fall through to env */ }
586
629
  if (!key && process.env[provider.keyEnv]) key = process.env[provider.keyEnv];
630
+ }
631
+ if (provider.api === "openai-completions") {
632
+ args.push("--api-base", provider.baseURL);
587
633
  if (key) env.LIVEBENCH_API_KEY = key;
634
+ } else if (provider.api === "anthropic-messages") {
635
+ // The generated model config selects the native anthropic client;
636
+ // the Anthropic SDK picks endpoint+key up from these env vars.
637
+ if (key) env.ANTHROPIC_API_KEY = key;
638
+ env.ANTHROPIC_BASE_URL = provider.baseURL;
588
639
  }
589
640
  }
590
641
 
@@ -657,7 +708,8 @@ function apply(ctx) {
657
708
  providers: providers.map(({ id, name: pname, models, api, baseURL }) => ({
658
709
  id,
659
710
  name: pname,
660
- routable: api === "openai-completions" && typeof baseURL === "string" && baseURL.length > 0,
711
+ routable: ["openai-completions", "openai-responses", "openai_responses", "anthropic-messages"].includes(api)
712
+ && typeof baseURL === "string" && baseURL.length > 0,
661
713
  baseURL: baseURL ?? null,
662
714
  models,
663
715
  })),
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.10",
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",