privateboard 0.1.19 → 0.1.21

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/cli.js CHANGED
@@ -955,6 +955,7 @@ import open from "open";
955
955
  init_db();
956
956
  var VALID_CARRIER_PREFS = /* @__PURE__ */ new Set([
957
957
  "openrouter",
958
+ "bai",
958
959
  "anthropic",
959
960
  "openai",
960
961
  "google",
@@ -2268,6 +2269,7 @@ var MODELS = {
2268
2269
  provider: "anthropic",
2269
2270
  directApiId: "claude-sonnet-4-6",
2270
2271
  openrouterId: "anthropic/claude-sonnet-4.6",
2272
+ baiId: "claude-sonnet-4-6",
2271
2273
  displayName: "Sonnet 4.6",
2272
2274
  contextBudget: 2e5,
2273
2275
  deck: "balanced \xB7 default"
@@ -2277,6 +2279,7 @@ var MODELS = {
2277
2279
  provider: "anthropic",
2278
2280
  directApiId: "claude-opus-4-6",
2279
2281
  openrouterId: "anthropic/claude-opus-4.6",
2282
+ baiId: "claude-opus-4-6",
2280
2283
  displayName: "Opus 4.6",
2281
2284
  contextBudget: 1e6,
2282
2285
  deck: "deep reasoning \xB7 1M ctx"
@@ -2286,6 +2289,7 @@ var MODELS = {
2286
2289
  provider: "anthropic",
2287
2290
  directApiId: "claude-opus-4-7",
2288
2291
  openrouterId: "anthropic/claude-opus-4.7",
2292
+ baiId: "claude-opus-4-7",
2289
2293
  displayName: "Opus 4.7",
2290
2294
  contextBudget: 2e5,
2291
2295
  deck: "deep reasoning"
@@ -2295,6 +2299,9 @@ var MODELS = {
2295
2299
  provider: "anthropic",
2296
2300
  directApiId: "claude-opus-4-6-fast",
2297
2301
  openrouterId: "anthropic/claude-opus-4.6-fast",
2302
+ // No baiId · B.AI's catalog doesn't carry Anthropic's "fast"
2303
+ // variants (only the base models). Routing this through B.AI 503s
2304
+ // with "no available channel". Direct Anthropic key or OR carries it.
2298
2305
  displayName: "Opus 4.6 Fast",
2299
2306
  contextBudget: 2e5,
2300
2307
  deck: "faster 4.6 \xB7 same intelligence"
@@ -2306,6 +2313,7 @@ var MODELS = {
2306
2313
  // the unsuffixed `claude-haiku-4-5` 404s on the direct API.
2307
2314
  directApiId: "claude-haiku-4-5-20251001",
2308
2315
  openrouterId: "anthropic/claude-haiku-4.5",
2316
+ baiId: "claude-haiku-4-5",
2309
2317
  displayName: "Haiku 4.5",
2310
2318
  contextBudget: 2e5,
2311
2319
  deck: "fast \xB7 low-cost"
@@ -2320,6 +2328,7 @@ var MODELS = {
2320
2328
  provider: "openai",
2321
2329
  directApiId: "gpt-5.5",
2322
2330
  openrouterId: "openai/gpt-5.5",
2331
+ baiId: "gpt-5-5",
2323
2332
  displayName: "GPT-5.5",
2324
2333
  contextBudget: 1e6,
2325
2334
  deck: "flagship \xB7 1M ctx"
@@ -2329,6 +2338,7 @@ var MODELS = {
2329
2338
  provider: "openai",
2330
2339
  directApiId: "gpt-5.4",
2331
2340
  openrouterId: "openai/gpt-5.4",
2341
+ baiId: "gpt-5-4",
2332
2342
  displayName: "GPT-5.4",
2333
2343
  contextBudget: 1e6,
2334
2344
  deck: "general \xB7 1M ctx"
@@ -2338,6 +2348,10 @@ var MODELS = {
2338
2348
  provider: "openai",
2339
2349
  directApiId: "gpt-5.4-mini",
2340
2350
  openrouterId: "openai/gpt-5.4-mini",
2351
+ // No baiId · B.AI's catalog reports "No available channel for
2352
+ // model gpt-5-4-mini" on the OneAPI distributor — the mini
2353
+ // variant isn't routed there. The full `gpt-5-4` IS available on
2354
+ // B.AI; pick that or use direct / OR for the mini tier.
2341
2355
  displayName: "GPT-5.4 Mini",
2342
2356
  contextBudget: 4e5,
2343
2357
  deck: "fast \xB7 400k ctx"
@@ -2348,20 +2362,23 @@ var MODELS = {
2348
2362
  provider: "openai",
2349
2363
  directApiId: "gpt-5.5-pro",
2350
2364
  openrouterId: "openai/gpt-5.5-pro",
2365
+ baiId: "gpt-5-5-pro",
2351
2366
  displayName: "GPT-5.5 Pro",
2352
2367
  contextBudget: 1e6,
2353
2368
  deck: "deep reasoning \xB7 1M ctx",
2354
- openrouterOnly: true
2369
+ viaUniversalOnly: true
2355
2370
  },
2356
2371
  "codex-5-4": {
2357
2372
  v: "codex-5-4",
2358
2373
  provider: "openai",
2359
2374
  directApiId: "gpt-5.3-codex",
2360
2375
  openrouterId: "openai/gpt-5.3-codex",
2376
+ // No baiId · B.AI's catalog doesn't list a codex variant; the
2377
+ // preview only ships via OR. Routing it through B.AI 503s.
2361
2378
  displayName: "ChatGPT Codex 5.4",
2362
2379
  contextBudget: 4e5,
2363
2380
  deck: "code \xB7 agents",
2364
- openrouterOnly: true
2381
+ viaUniversalOnly: true
2365
2382
  },
2366
2383
  // ── Google · current frontier (3.1 Pro / 3 Flash / 3.1 Flash Lite) ──
2367
2384
  // Replaced the legacy gemini-2.5-pro / gemini-2.5-flash entries — all
@@ -2373,6 +2390,7 @@ var MODELS = {
2373
2390
  provider: "google",
2374
2391
  directApiId: "gemini-3.1-pro-preview",
2375
2392
  openrouterId: "google/gemini-3.1-pro-preview",
2393
+ baiId: "gemini-3-1-pro",
2376
2394
  displayName: "Gemini 3.1 Pro",
2377
2395
  contextBudget: 1e6,
2378
2396
  deck: "flagship \xB7 1M ctx"
@@ -2382,6 +2400,7 @@ var MODELS = {
2382
2400
  provider: "google",
2383
2401
  directApiId: "gemini-3-flash-preview",
2384
2402
  openrouterId: "google/gemini-3-flash-preview",
2403
+ baiId: "gemini-3-flash",
2385
2404
  displayName: "Gemini 3 Flash",
2386
2405
  contextBudget: 1e6,
2387
2406
  deck: "frontier flash \xB7 1M ctx"
@@ -2391,6 +2410,11 @@ var MODELS = {
2391
2410
  provider: "google",
2392
2411
  directApiId: "gemini-3.1-flash-lite-preview",
2393
2412
  openrouterId: "google/gemini-3.1-flash-lite-preview",
2413
+ // No baiId · B.AI's catalog only has `gemini-3-1-pro` and
2414
+ // `gemini-3-flash` for the Gemini family — no 3.1 Flash Lite
2415
+ // channel. Earlier mapping to `gemini-3-1-flash` 503'd with
2416
+ // "no available channel for model gemini-3-1-flash". Direct
2417
+ // Google key or OR carries this preview model.
2394
2418
  displayName: "Gemini 3.1 Flash Lite",
2395
2419
  contextBudget: 1e6,
2396
2420
  deck: "fast \xB7 1M ctx"
@@ -2405,6 +2429,7 @@ var MODELS = {
2405
2429
  provider: "xai",
2406
2430
  directApiId: "grok-4.3",
2407
2431
  openrouterId: "x-ai/grok-4.3",
2432
+ baiId: "grok-4-3",
2408
2433
  displayName: "Grok 4.3",
2409
2434
  contextBudget: 1e6,
2410
2435
  deck: "flagship \xB7 1M ctx"
@@ -2414,6 +2439,7 @@ var MODELS = {
2414
2439
  provider: "xai",
2415
2440
  directApiId: "grok-4.1-fast",
2416
2441
  openrouterId: "x-ai/grok-4.1-fast",
2442
+ baiId: "grok-4-1-fast",
2417
2443
  displayName: "Grok 4.1 Fast",
2418
2444
  contextBudget: 256e3,
2419
2445
  deck: "fast \xB7 256k ctx"
@@ -2423,10 +2449,11 @@ var MODELS = {
2423
2449
  provider: "xai",
2424
2450
  directApiId: "grok-4.20",
2425
2451
  openrouterId: "x-ai/grok-4.20",
2452
+ baiId: "grok-4-20",
2426
2453
  displayName: "Grok 4.20",
2427
2454
  contextBudget: 2e6,
2428
2455
  deck: "2M ctx \xB7 big context",
2429
- openrouterOnly: true
2456
+ viaUniversalOnly: true
2430
2457
  },
2431
2458
  // ── DeepSeek (OR-only · no @ai-sdk/deepseek shipped) ──
2432
2459
  "deepseek-v4-pro": {
@@ -2434,10 +2461,11 @@ var MODELS = {
2434
2461
  provider: "deepseek",
2435
2462
  directApiId: "deepseek-v4-pro",
2436
2463
  openrouterId: "deepseek/deepseek-v4-pro",
2464
+ baiId: "deepseek-v4-pro",
2437
2465
  displayName: "DeepSeek V4 Pro",
2438
2466
  contextBudget: 128e3,
2439
2467
  deck: "reasoning \xB7 open weights",
2440
- openrouterOnly: true
2468
+ viaUniversalOnly: true
2441
2469
  },
2442
2470
  // OpenRouter catalog id · deepseek/deepseek-v4-flash ("V4 Flash" — lite tier).
2443
2471
  "deepseek-v4-flash": {
@@ -2445,10 +2473,41 @@ var MODELS = {
2445
2473
  provider: "deepseek",
2446
2474
  directApiId: "deepseek-v4-flash",
2447
2475
  openrouterId: "deepseek/deepseek-v4-flash",
2476
+ baiId: "deepseek-v4-flash",
2448
2477
  displayName: "DeepSeek Lite",
2449
2478
  contextBudget: 1e6,
2450
2479
  deck: "V4 Flash \xB7 fast \xB7 1M ctx",
2451
- openrouterOnly: true
2480
+ viaUniversalOnly: true
2481
+ },
2482
+ // ── Zhipu (Z.AI) · GLM family · OR + B.AI only ──
2483
+ // OpenRouter catalog convention: `z-ai/glm-X.Y`. B.AI uses
2484
+ // hyphenated lowercase: `glm-5-1`. No direct @ai-sdk client ·
2485
+ // viaUniversalOnly skips the direct path.
2486
+ "glm-5-1": {
2487
+ v: "glm-5-1",
2488
+ provider: "zhipu",
2489
+ directApiId: "glm-5.1",
2490
+ openrouterId: "z-ai/glm-5.1",
2491
+ baiId: "glm-5-1",
2492
+ displayName: "GLM 5.1",
2493
+ contextBudget: 2e5,
2494
+ deck: "Zhipu flagship \xB7 200k ctx",
2495
+ viaUniversalOnly: true
2496
+ },
2497
+ // ── Moonshot · Kimi family · OR + B.AI only ──
2498
+ // OpenRouter catalog convention: `moonshotai/kimi-…`. B.AI uses
2499
+ // hyphenated lowercase: `kimi-2-6`. No direct @ai-sdk client ·
2500
+ // viaUniversalOnly skips the direct path.
2501
+ "kimi-2-6": {
2502
+ v: "kimi-2-6",
2503
+ provider: "moonshot",
2504
+ directApiId: "kimi-2.6",
2505
+ openrouterId: "moonshotai/kimi-2.6",
2506
+ baiId: "kimi-2-6",
2507
+ displayName: "Kimi 2.6",
2508
+ contextBudget: 256e3,
2509
+ deck: "Moonshot \xB7 long-context",
2510
+ viaUniversalOnly: true
2452
2511
  }
2453
2512
  };
2454
2513
  function getModel(v) {
@@ -3486,6 +3545,7 @@ var NoKeyError = class extends Error {
3486
3545
  provider;
3487
3546
  };
3488
3547
  var OPENROUTER_BASE = "https://openrouter.ai/api/v1";
3548
+ var BAI_BASE = "https://api.b.ai/v1";
3489
3549
  var SENSITIVE_HEADER_NAMES = /* @__PURE__ */ new Set([
3490
3550
  "authorization",
3491
3551
  "x-api-key",
@@ -3613,70 +3673,107 @@ function formatStreamError(e) {
3613
3673
  }
3614
3674
  return String(e);
3615
3675
  }
3616
- function resolveModel(modelV, carrier) {
3676
+ function resolveModel(modelV, carrier, excludeCarriers) {
3617
3677
  const meta = getModel(modelV);
3618
- const orKey = getKey("openrouter");
3619
- const directKey = getKey(meta.provider);
3678
+ const skip = (c) => excludeCarriers?.has(c) === true;
3679
+ const orKey = !skip("openrouter") ? getKey("openrouter") : void 0;
3680
+ const baiKey = !skip("bai") ? getKey("bai") : void 0;
3681
+ const directKey = !skip(meta.provider) ? getKey(meta.provider) : void 0;
3620
3682
  if (carrier === "openrouter" && orKey) {
3621
3683
  process.stderr.write(`[adapter] modelV=${modelV} \u2192 openrouter:${meta.openrouterId} (pinned)
3622
3684
  `);
3623
3685
  return openRouterResolved(meta, orKey);
3624
3686
  }
3625
- if (carrier && carrier !== "openrouter" && carrier === meta.provider) {
3626
- const pinnedKey = getKey(carrier);
3687
+ if (carrier === "bai" && baiKey && meta.baiId) {
3688
+ process.stderr.write(`[adapter] modelV=${modelV} \u2192 bai:${meta.baiId} (pinned)
3689
+ `);
3690
+ return baiResolved(meta, baiKey);
3691
+ }
3692
+ if (carrier && carrier !== "openrouter" && carrier !== "bai" && carrier === meta.provider) {
3693
+ const pinnedKey = !skip(carrier) ? getKey(carrier) : void 0;
3627
3694
  if (pinnedKey) {
3628
3695
  process.stderr.write(`[adapter] modelV=${modelV} \u2192 direct:${meta.provider}/${meta.directApiId} (pinned)
3629
3696
  `);
3630
3697
  return directResolved(meta, pinnedKey);
3631
3698
  }
3632
3699
  }
3633
- if (carrier) {
3700
+ if (carrier && !excludeCarriers?.size) {
3634
3701
  process.stderr.write(
3635
3702
  `[adapter] modelV=${modelV} pinned carrier=${carrier} unreachable; falling back to default routing
3636
3703
  `
3637
3704
  );
3638
3705
  }
3639
- if (meta.openrouterOnly && orKey) {
3706
+ if (meta.viaUniversalOnly && orKey) {
3640
3707
  process.stderr.write(`[adapter] modelV=${modelV} \u2192 openrouter:${meta.openrouterId} (preferred)
3641
3708
  `);
3642
3709
  return openRouterResolved(meta, orKey);
3643
3710
  }
3644
- if (directKey && !meta.openrouterOnly) {
3711
+ if (directKey && !meta.viaUniversalOnly) {
3645
3712
  process.stderr.write(`[adapter] modelV=${modelV} \u2192 direct:${meta.provider}/${meta.directApiId}
3646
3713
  `);
3647
3714
  return directResolved(meta, directKey);
3648
3715
  }
3716
+ if (baiKey && meta.baiId) {
3717
+ process.stderr.write(`[adapter] modelV=${modelV} \u2192 bai:${meta.baiId}
3718
+ `);
3719
+ return baiResolved(meta, baiKey);
3720
+ }
3649
3721
  if (orKey) {
3650
3722
  process.stderr.write(`[adapter] modelV=${modelV} \u2192 openrouter:${meta.openrouterId}
3651
3723
  `);
3652
3724
  return openRouterResolved(meta, orKey);
3653
3725
  }
3654
- if (meta.openrouterOnly && directKey) {
3655
- process.stderr.write(`[adapter] modelV=${modelV} \u2192 direct:${meta.provider}/${meta.directApiId} (openrouterOnly fallback \xB7 no OR key)
3726
+ if (meta.viaUniversalOnly && directKey) {
3727
+ process.stderr.write(`[adapter] modelV=${modelV} \u2192 direct:${meta.provider}/${meta.directApiId} (viaUniversalOnly fallback \xB7 no OR / B.AI key)
3656
3728
  `);
3657
3729
  return directResolved(meta, directKey);
3658
3730
  }
3659
3731
  throw new NoKeyError(meta.provider);
3660
3732
  }
3733
+ function isCarrierAccessDenied(message) {
3734
+ if (!message) return false;
3735
+ const m = message.toLowerCase();
3736
+ if (/\b40[23]\b/.test(m) && /(access|deposit|payment|paid|premium|subscri|quota|balance|fund)/.test(m)) return true;
3737
+ if (/access[_\s-]?denied/.test(m)) return true;
3738
+ if (/deposit\s+required/.test(m)) return true;
3739
+ if (/paid[_\s-]?plan[_\s-]?required/.test(m)) return true;
3740
+ if (/premium[_\s-]?model/.test(m)) return true;
3741
+ if (/insufficient[_\s-]?(?:quota|balance|fund)/.test(m)) return true;
3742
+ if (/quota[_\s-]?exceeded/.test(m)) return true;
3743
+ if (/payment[_\s-]?required/.test(m)) return true;
3744
+ if (/billing/.test(m) && /(disabled|inactive|required|invalid|missing)/.test(m)) return true;
3745
+ if (/model[_\s-]?not[_\s-]?found/.test(m)) return true;
3746
+ if (/no\s+available\s+channel/.test(m)) return true;
3747
+ if (/no\s+endpoints?\s+found/.test(m)) return true;
3748
+ if (/invalid\s+model/.test(m)) return true;
3749
+ if (/model.*(unavailable|not\s+(?:supported|available))/.test(m)) return true;
3750
+ if (/prohibited.*(?:terms?\s+of\s+service|provider)/.test(m)) return true;
3751
+ if (/provider.*terms?\s+of\s+service/.test(m)) return true;
3752
+ if (/violates?\s+(?:our|the)?\s*(?:terms|policy|content)/.test(m)) return true;
3753
+ return false;
3754
+ }
3661
3755
  function directResolved(meta, apiKey) {
3662
3756
  switch (meta.provider) {
3663
3757
  case "anthropic":
3664
3758
  return {
3665
- model: createAnthropic({ apiKey, fetch: makeLoggedFetch("anthropic") })(meta.directApiId)
3759
+ model: createAnthropic({ apiKey, fetch: makeLoggedFetch("anthropic") })(meta.directApiId),
3760
+ carrier: "anthropic"
3666
3761
  };
3667
3762
  case "openai":
3668
3763
  return {
3669
3764
  model: createOpenAI({ apiKey, fetch: makeLoggedFetch("openai") }).responses(meta.directApiId),
3670
3765
  providerOptions: {
3671
3766
  openai: { reasoningEffort: "none" }
3672
- }
3767
+ },
3768
+ carrier: "openai"
3673
3769
  };
3674
3770
  case "google":
3675
3771
  return {
3676
3772
  model: createGoogleGenerativeAI({ apiKey, fetch: makeLoggedFetch("google") })(meta.directApiId),
3677
3773
  providerOptions: {
3678
3774
  google: { thinkingConfig: { thinkingBudget: 0 } }
3679
- }
3775
+ },
3776
+ carrier: "google"
3680
3777
  };
3681
3778
  case "xai": {
3682
3779
  return {
@@ -3684,13 +3781,31 @@ function directResolved(meta, apiKey) {
3684
3781
  apiKey,
3685
3782
  baseURL: "https://api.x.ai/v1",
3686
3783
  fetch: makeLoggedFetch("xai")
3687
- }).responses(meta.directApiId)
3784
+ }).responses(meta.directApiId),
3785
+ carrier: "xai"
3688
3786
  };
3689
3787
  }
3690
3788
  default:
3691
3789
  throw new NoKeyError(meta.provider);
3692
3790
  }
3693
3791
  }
3792
+ function baiResolved(meta, apiKey) {
3793
+ const baiId = meta.baiId;
3794
+ if (!baiId) {
3795
+ throw new NoKeyError(meta.provider);
3796
+ }
3797
+ const compat = createOpenAICompatible({
3798
+ name: "bai",
3799
+ apiKey,
3800
+ baseURL: BAI_BASE,
3801
+ fetch: baiLoggedFetch
3802
+ });
3803
+ return {
3804
+ model: compat.chatModel(baiId),
3805
+ carrier: "bai"
3806
+ };
3807
+ }
3808
+ var baiLoggedFetch = makeLoggedFetch("bai");
3694
3809
  function openRouterResolved(meta, apiKey) {
3695
3810
  const compat = createOpenAICompatible({
3696
3811
  name: "openrouter",
@@ -3715,7 +3830,8 @@ function openRouterResolved(meta, apiKey) {
3715
3830
  openrouter: {
3716
3831
  provider: { allow_fallbacks: false }
3717
3832
  }
3718
- }
3833
+ },
3834
+ carrier: "openrouter"
3719
3835
  };
3720
3836
  }
3721
3837
  var RETRY_MAX_ATTEMPTS = 3;
@@ -3766,6 +3882,8 @@ async function* callLLMStream(req) {
3766
3882
  let attempt = 0;
3767
3883
  let lastTransientMessage = "";
3768
3884
  let yieldedText = false;
3885
+ const triedCarriers = /* @__PURE__ */ new Set();
3886
+ triedCarriers.add(resolved.carrier);
3769
3887
  while (attempt < RETRY_MAX_ATTEMPTS) {
3770
3888
  attempt++;
3771
3889
  if (req.signal?.aborted) {
@@ -3807,6 +3925,21 @@ async function* callLLMStream(req) {
3807
3925
  retriableErrorMessage = msg;
3808
3926
  break;
3809
3927
  }
3928
+ if (!yieldedText && isCarrierAccessDenied(msg)) {
3929
+ try {
3930
+ const next = resolveModel(req.modelV, null, triedCarriers);
3931
+ triedCarriers.add(next.carrier);
3932
+ process.stderr.write(
3933
+ `[adapter] modelV=${req.modelV} carrier=${resolved.carrier} rejected (access denied); retrying via ${next.carrier}
3934
+ `
3935
+ );
3936
+ resolved = next;
3937
+ attempt = 0;
3938
+ retriableErrorMessage = msg;
3939
+ break;
3940
+ } catch {
3941
+ }
3942
+ }
3810
3943
  sawError = true;
3811
3944
  yield { type: "error", message: msg };
3812
3945
  }
@@ -3849,6 +3982,20 @@ async function* callLLMStream(req) {
3849
3982
  lastTransientMessage = msg;
3850
3983
  continue;
3851
3984
  }
3985
+ if (!yieldedText && isCarrierAccessDenied(msg)) {
3986
+ try {
3987
+ const next = resolveModel(req.modelV, null, triedCarriers);
3988
+ triedCarriers.add(next.carrier);
3989
+ process.stderr.write(
3990
+ `[adapter] modelV=${req.modelV} carrier=${resolved.carrier} rejected (access denied / threw); retrying via ${next.carrier}
3991
+ `
3992
+ );
3993
+ resolved = next;
3994
+ attempt = 0;
3995
+ continue;
3996
+ } catch {
3997
+ }
3998
+ }
3852
3999
  yield { type: "error", message: msg };
3853
4000
  return;
3854
4001
  }
@@ -3888,25 +4035,31 @@ async function callLLMWithUsage(req) {
3888
4035
  // src/storage/reconcile-models.ts
3889
4036
  var PRIMARY_BY_CARRIER = {
3890
4037
  openrouter: "opus-4-6-fast",
4038
+ bai: "haiku-4-5",
3891
4039
  anthropic: "haiku-4-5",
3892
4040
  openai: "gpt-5-4-mini",
3893
4041
  google: "gemini-3-1-flash",
3894
4042
  xai: "grok-4-1-fast"
3895
4043
  };
3896
- var CARRIER_PRIORITY = ["openrouter", "anthropic", "openai", "google", "xai"];
4044
+ var CARRIER_PRIORITY = ["openrouter", "bai", "anthropic", "openai", "google", "xai"];
3897
4045
  function reachableModelVs() {
3898
4046
  const out = /* @__PURE__ */ new Set();
3899
4047
  const orKey = !!getKey("openrouter");
4048
+ const baiKey = !!getKey("bai");
3900
4049
  for (const [v, meta] of Object.entries(MODELS)) {
3901
4050
  if (orKey) {
3902
4051
  out.add(v);
3903
4052
  continue;
3904
4053
  }
3905
- if (!meta.openrouterOnly && hasDirectKey(meta.provider)) {
4054
+ if (baiKey && meta.baiId) {
3906
4055
  out.add(v);
3907
4056
  continue;
3908
4057
  }
3909
- if (meta.openrouterOnly && hasDirectKey(meta.provider)) {
4058
+ if (!meta.viaUniversalOnly && hasDirectKey(meta.provider)) {
4059
+ out.add(v);
4060
+ continue;
4061
+ }
4062
+ if (meta.viaUniversalOnly && hasDirectKey(meta.provider)) {
3910
4063
  out.add(v);
3911
4064
  }
3912
4065
  }
@@ -3928,14 +4081,16 @@ function activeCarrier() {
3928
4081
  if (prefs.defaultModelV) {
3929
4082
  const meta = MODELS[prefs.defaultModelV];
3930
4083
  if (meta) {
3931
- if (meta.openrouterOnly && getKey("openrouter")) return "openrouter";
4084
+ if (meta.viaUniversalOnly && getKey("openrouter")) return "openrouter";
3932
4085
  if (hasDirectKey(meta.provider)) return meta.provider;
4086
+ if (getKey("bai") && meta.baiId) return "bai";
3933
4087
  if (getKey("openrouter")) return "openrouter";
3934
4088
  }
3935
4089
  }
3936
4090
  for (const c of CARRIER_PRIORITY) {
3937
4091
  if (c === "openrouter" && getKey("openrouter")) return "openrouter";
3938
- if (c !== "openrouter" && hasDirectKey(c)) return c;
4092
+ if (c === "bai" && getKey("bai")) return "bai";
4093
+ if (c !== "openrouter" && c !== "bai" && hasDirectKey(c)) return c;
3939
4094
  }
3940
4095
  return null;
3941
4096
  }
@@ -3946,8 +4101,18 @@ function reconcileAgentModels(opts = {}) {
3946
4101
  const forcePrimary = opts.forcePrimary === true;
3947
4102
  const switched = [];
3948
4103
  const cleared = [];
4104
+ const orReachable = !!getKey("openrouter");
4105
+ const baiReachable = !!getKey("bai");
4106
+ function carrierKeyReachable(c) {
4107
+ if (c === "openrouter") return orReachable;
4108
+ if (c === "bai") return baiReachable;
4109
+ return hasDirectKey(c);
4110
+ }
3949
4111
  for (const agent of listAllAgents()) {
3950
4112
  const v = (agent.modelV || "").trim();
4113
+ if (agent.carrierPref && !carrierKeyReachable(agent.carrierPref)) {
4114
+ updateAgent(agent.id, { carrierPref: null });
4115
+ }
3951
4116
  if (!forcePrimary && v && reachable.has(v)) continue;
3952
4117
  if (primary && carrier) {
3953
4118
  const isChair = agent.roleKind === "moderator";
@@ -3976,26 +4141,29 @@ function reconcileAgentModels(opts = {}) {
3976
4141
  function getProviderKeyState() {
3977
4142
  const directProviders = /* @__PURE__ */ new Set();
3978
4143
  let hasOpenRouter = false;
4144
+ let hasBai = false;
3979
4145
  for (const meta of listKeyMeta()) {
3980
4146
  if (!meta.configured) continue;
3981
4147
  if (meta.provider === "openrouter") hasOpenRouter = true;
4148
+ else if (meta.provider === "bai") hasBai = true;
3982
4149
  else if (meta.provider === "brave" || meta.provider === "tavily" || meta.provider === "minimax" || meta.provider === "elevenlabs") continue;
3983
4150
  else directProviders.add(meta.provider);
3984
4151
  }
3985
- return { hasOpenRouter, directProviders };
4152
+ return { hasOpenRouter, hasBai, directProviders };
3986
4153
  }
3987
4154
  function availabilityFor(meta, keys) {
3988
- const directReachable = !meta.openrouterOnly && keys.directProviders.has(meta.provider);
4155
+ const directReachable = !meta.viaUniversalOnly && keys.directProviders.has(meta.provider);
3989
4156
  const orReachable = keys.hasOpenRouter && !!meta.openrouterId;
3990
- const reachable = directReachable || orReachable;
4157
+ const baiReachable = keys.hasBai && !!meta.baiId;
4158
+ const reachable = directReachable || orReachable || baiReachable;
3991
4159
  return {
3992
4160
  modelV: meta.v,
3993
4161
  displayName: meta.displayName,
3994
4162
  provider: meta.provider,
3995
4163
  deck: meta.deck,
3996
- routes: { direct: directReachable, openrouter: orReachable },
4164
+ routes: { direct: directReachable, openrouter: orReachable, bai: baiReachable },
3997
4165
  reachable,
3998
- preferredRoute: directReachable ? "direct" : orReachable ? "openrouter" : null
4166
+ preferredRoute: directReachable ? "direct" : baiReachable ? "bai" : orReachable ? "openrouter" : null
3999
4167
  };
4000
4168
  }
4001
4169
  function modelAvailability() {
@@ -4007,7 +4175,7 @@ function reachableModels() {
4007
4175
  }
4008
4176
  function hasAnyModelKey() {
4009
4177
  const keys = getProviderKeyState();
4010
- return keys.hasOpenRouter || keys.directProviders.size > 0;
4178
+ return keys.hasOpenRouter || keys.hasBai || keys.directProviders.size > 0;
4011
4179
  }
4012
4180
  var PROVIDER_FLAGSHIP = {
4013
4181
  anthropic: "opus-4-7",
@@ -4015,7 +4183,10 @@ var PROVIDER_FLAGSHIP = {
4015
4183
  google: "gemini-3-flash",
4016
4184
  xai: "grok-4-3",
4017
4185
  deepseek: "deepseek-v4-pro",
4186
+ zhipu: "glm-5-1",
4187
+ moonshot: "kimi-2-6",
4018
4188
  openrouter: "opus-4-7",
4189
+ bai: "opus-4-7",
4019
4190
  brave: null,
4020
4191
  tavily: null,
4021
4192
  minimax: null,
@@ -4027,7 +4198,13 @@ var PROVIDER_FAST = {
4027
4198
  google: "gemini-3-1-flash",
4028
4199
  xai: "grok-4-1-fast",
4029
4200
  deepseek: "deepseek-v4-flash",
4201
+ // GLM / Kimi · no separate fast/flash tier in our registry yet, so
4202
+ // both providers' "fast pick" falls back to the same flagship that
4203
+ // PROVIDER_FLAGSHIP names. Reachability-via-OR/B.AI carries it.
4204
+ zhipu: "glm-5-1",
4205
+ moonshot: "kimi-2-6",
4030
4206
  openrouter: "opus-4-6-fast",
4207
+ bai: "haiku-4-5",
4031
4208
  brave: null,
4032
4209
  tavily: null,
4033
4210
  minimax: null,
@@ -4043,6 +4220,21 @@ var FAST_POOL_BY_CARRIER = {
4043
4220
  "grok-4-1-fast",
4044
4221
  "deepseek-v4-flash"
4045
4222
  ],
4223
+ // B.AI carries the same brand-spanning fast catalog as OpenRouter ·
4224
+ // identical pool gives a B.AI-only user the same visibly-mixed
4225
+ // director cast (different brand badges per seat) that the OpenRouter
4226
+ // path produces. Members are filtered against reachability inside
4227
+ // `pickRandomFastModel`, so models without a baiId fall out naturally
4228
+ // if B.AI ends up not carrying one of them in practice.
4229
+ bai: [
4230
+ "opus-4-6-fast",
4231
+ "haiku-4-5",
4232
+ "gpt-5-4-mini",
4233
+ "gemini-3-flash",
4234
+ "gemini-3-1-flash",
4235
+ "grok-4-1-fast",
4236
+ "deepseek-v4-flash"
4237
+ ],
4046
4238
  anthropic: ["opus-4-6-fast", "haiku-4-5"],
4047
4239
  openai: ["gpt-5-4-mini"],
4048
4240
  google: ["gemini-3-flash", "gemini-3-1-flash"],
@@ -4070,7 +4262,10 @@ var FLAGSHIP_TIER = /* @__PURE__ */ new Set([
4070
4262
  // xAI
4071
4263
  "grok-4-3",
4072
4264
  // DeepSeek
4073
- "deepseek-v4-pro"
4265
+ "deepseek-v4-pro",
4266
+ // Zhipu · Moonshot · single flagship each (both OR + B.AI routed).
4267
+ "glm-5-1",
4268
+ "kimi-2-6"
4074
4269
  ]);
4075
4270
  function effectiveDefaultModel() {
4076
4271
  const prefs = getPrefs();
@@ -4100,6 +4295,12 @@ function defaultModelFor(keys = getProviderKeyState()) {
4100
4295
  const opus = reachable.find((m) => m.modelV === "opus-4-7");
4101
4296
  if (opus) return opus.modelV;
4102
4297
  }
4298
+ if (keys.hasBai) {
4299
+ const fast = reachable.find((m) => m.modelV === "haiku-4-5");
4300
+ if (fast) return fast.modelV;
4301
+ const opus = reachable.find((m) => m.modelV === "opus-4-7");
4302
+ if (opus) return opus.modelV;
4303
+ }
4103
4304
  for (const provider of keys.directProviders) {
4104
4305
  const fast = PROVIDER_FAST[provider];
4105
4306
  if (fast && reachable.find((m) => m.modelV === fast)) return fast;
@@ -4112,6 +4313,8 @@ function defaultModelFor(keys = getProviderKeyState()) {
4112
4313
  }
4113
4314
  var CHEAP_BY_CARRIER = {
4114
4315
  openrouter: "haiku-4-5",
4316
+ bai: "haiku-4-5",
4317
+ // B.AI carries Haiku 4.5 (claude-haiku-4-5)
4115
4318
  anthropic: "sonnet-4-6",
4116
4319
  // only direct-routable Claude
4117
4320
  openai: "gpt-5-4-mini",
@@ -7632,7 +7835,7 @@ function agentsRouter() {
7632
7835
  patch.carrierPref = null;
7633
7836
  } else if (typeof b.carrierPref === "string") {
7634
7837
  const v = b.carrierPref.trim();
7635
- const allowed = /* @__PURE__ */ new Set(["openrouter", "anthropic", "openai", "google", "xai"]);
7838
+ const allowed = /* @__PURE__ */ new Set(["openrouter", "bai", "anthropic", "openai", "google", "xai"]);
7636
7839
  if (!allowed.has(v)) {
7637
7840
  return c.json({ error: `unknown carrier: ${v}` }, 400);
7638
7841
  }
@@ -15200,6 +15403,7 @@ function defaultVoiceForProvider(provider) {
15200
15403
  // src/routes/keys.ts
15201
15404
  var PROVIDERS = /* @__PURE__ */ new Set([
15202
15405
  "openrouter",
15406
+ "bai",
15203
15407
  "anthropic",
15204
15408
  "openai",
15205
15409
  "google",
@@ -15212,6 +15416,7 @@ var PROVIDERS = /* @__PURE__ */ new Set([
15212
15416
  ]);
15213
15417
  var LLM_PROVIDERS = /* @__PURE__ */ new Set([
15214
15418
  "openrouter",
15419
+ "bai",
15215
15420
  "anthropic",
15216
15421
  "openai",
15217
15422
  "google",
@@ -23343,7 +23548,7 @@ function voicesRouter() {
23343
23548
  init_paths();
23344
23549
 
23345
23550
  // src/version.ts
23346
- var VERSION = "0.1.19";
23551
+ var VERSION = "0.1.21";
23347
23552
 
23348
23553
  // src/server.ts
23349
23554
  function createApp() {