create-workframe 0.1.4 → 0.1.5

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 (86) hide show
  1. package/README.md +1 -1
  2. package/package.json +1 -1
  3. package/workframe-api/package.json +1 -1
  4. package/workframe-api/server.py +112 -20
  5. package/workframe-api/test_billing_provider.py +24 -0
  6. package/workframe-ui/public/assets/{arc-BskqxLmE.js → arc-CnriPN3G.js} +1 -1
  7. package/workframe-ui/public/assets/architecture-7EHR7CIX-CFhr7xAe.js +1 -0
  8. package/workframe-ui/public/assets/{architectureDiagram-3BPJPVTR-B3eGdqvq.js → architectureDiagram-3BPJPVTR-DYYB1Ic2.js} +1 -1
  9. package/workframe-ui/public/assets/{blockDiagram-GPEHLZMM-CzFZG7uX.js → blockDiagram-GPEHLZMM-Bn-fFOyj.js} +1 -1
  10. package/workframe-ui/public/assets/{c4Diagram-AAUBKEIU-BS-gGyCH.js → c4Diagram-AAUBKEIU-BPPDtAnD.js} +1 -1
  11. package/workframe-ui/public/assets/channel-Dok_-T0o.js +1 -0
  12. package/workframe-ui/public/assets/{chunk-2J33WTMH-Cjvqf7c9.js → chunk-2J33WTMH-Bz2CJiC3.js} +1 -1
  13. package/workframe-ui/public/assets/{chunk-3OPIFGDE--eUuL8AK.js → chunk-3OPIFGDE-B8aMeE1B.js} +1 -1
  14. package/workframe-ui/public/assets/{chunk-4BX2VUAB-s9lHUFKL.js → chunk-4BX2VUAB-bLFHQTuT.js} +1 -1
  15. package/workframe-ui/public/assets/{chunk-55IACEB6-D1McA5nz.js → chunk-55IACEB6-BRxhx-lG.js} +1 -1
  16. package/workframe-ui/public/assets/{chunk-5ZQYHXKU-DIstLfU2.js → chunk-5ZQYHXKU-BLyOgEJ8.js} +1 -1
  17. package/workframe-ui/public/assets/{chunk-727SXJPM-Cd0GD2CO.js → chunk-727SXJPM-DiuvBl7G.js} +1 -1
  18. package/workframe-ui/public/assets/{chunk-AQP2D5EJ-gc5Ra6G4.js → chunk-AQP2D5EJ-BE1_aBdC.js} +1 -1
  19. package/workframe-ui/public/assets/{chunk-BSJP7CBP-Cg5QfihJ.js → chunk-BSJP7CBP-BicGsmBs.js} +1 -1
  20. package/workframe-ui/public/assets/{chunk-CSCIHK7Q-D_gobbhY.js → chunk-CSCIHK7Q-C3VTns3d.js} +1 -1
  21. package/workframe-ui/public/assets/{chunk-FMBD7UC4-B6eLY9_d.js → chunk-FMBD7UC4-DSiXY3bY.js} +1 -1
  22. package/workframe-ui/public/assets/{chunk-KSCS5N6A-BGroAeW2.js → chunk-KSCS5N6A-Cvw5s3fR.js} +1 -1
  23. package/workframe-ui/public/assets/{chunk-L5ZTLDWV-34xv7WgT.js → chunk-L5ZTLDWV-jJifGdIQ.js} +1 -1
  24. package/workframe-ui/public/assets/{chunk-LZXEDZCA-XJBctFZw.js → chunk-LZXEDZCA-Maee1aqo.js} +2 -2
  25. package/workframe-ui/public/assets/{chunk-ND2GUHAM-nTOZpln8.js → chunk-ND2GUHAM-C77N3VAM.js} +1 -1
  26. package/workframe-ui/public/assets/{chunk-NZK2D7GU-BBrzcaPH.js → chunk-NZK2D7GU-BY68lrPB.js} +1 -1
  27. package/workframe-ui/public/assets/{chunk-O5CBEL6O-Cntncx6L.js → chunk-O5CBEL6O-D_c4kWzh.js} +1 -1
  28. package/workframe-ui/public/assets/chunk-QZHKN3VN-C3wwLrTa.js +1 -0
  29. package/workframe-ui/public/assets/chunk-WU5MYG2G-GFFSg2kf.js +1 -0
  30. package/workframe-ui/public/assets/{chunk-XPW4576I-DSbtd0cg.js → chunk-XPW4576I-DlQgvqBp.js} +1 -1
  31. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-DKbGfT5Z.js +1 -0
  32. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-DKbGfT5Z.js +1 -0
  33. package/workframe-ui/public/assets/{cose-bilkent-S5V4N54A-lmYstEMQ.js → cose-bilkent-S5V4N54A-CHCOHDSd.js} +1 -1
  34. package/workframe-ui/public/assets/{dagre-BM42HDAG-Bdkbdul2.js → dagre-BM42HDAG-CKPcSYwU.js} +1 -1
  35. package/workframe-ui/public/assets/{diagram-2AECGRRQ-Clb9kSXe.js → diagram-2AECGRRQ-C6qe0jP-.js} +1 -1
  36. package/workframe-ui/public/assets/{diagram-5GNKFQAL-BApg0yec.js → diagram-5GNKFQAL-DhQhrieU.js} +1 -1
  37. package/workframe-ui/public/assets/{diagram-KO2AKTUF-peZjDRV6.js → diagram-KO2AKTUF-Bqf6Iw1r.js} +1 -1
  38. package/workframe-ui/public/assets/{diagram-LMA3HP47-DZm_1pjc.js → diagram-LMA3HP47-Bt1ms2D2.js} +1 -1
  39. package/workframe-ui/public/assets/{diagram-OG6HWLK6-DP_N6fYL.js → diagram-OG6HWLK6-C0Rrxe1i.js} +1 -1
  40. package/workframe-ui/public/assets/{dist-CqP43hXK.js → dist-BLcLhTn6.js} +1 -1
  41. package/workframe-ui/public/assets/{erDiagram-TEJ5UH35-Blfs6kLv.js → erDiagram-TEJ5UH35-Cy1GbZVu.js} +1 -1
  42. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C_PrceHe.js +1 -0
  43. package/workframe-ui/public/assets/{flowDiagram-I6XJVG4X-CcmMOkwH.js → flowDiagram-I6XJVG4X-DL3ALqDG.js} +1 -1
  44. package/workframe-ui/public/assets/{ganttDiagram-6RSMTGT7-ByoO3dF7.js → ganttDiagram-6RSMTGT7-CMpwBhXQ.js} +1 -1
  45. package/workframe-ui/public/assets/{gitGraph-WXDBUCRP-DCyTpmPs.js → gitGraph-WXDBUCRP-C0mwHUXL.js} +1 -1
  46. package/workframe-ui/public/assets/{gitGraphDiagram-PVQCEYII-CvLYB55b.js → gitGraphDiagram-PVQCEYII-6gzhWaF0.js} +1 -1
  47. package/workframe-ui/public/assets/{index-DpoUZAxh.css → index-DyO6K0TD.css} +1 -1
  48. package/workframe-ui/public/assets/index-yvpSUs8T.js +133 -0
  49. package/workframe-ui/public/assets/{info-J43DQDTF-CfSRIfOI.js → info-J43DQDTF-Bb_wZTxU.js} +1 -1
  50. package/workframe-ui/public/assets/{infoDiagram-5YYISTIA-DwS44DS6.js → infoDiagram-5YYISTIA-601evYFV.js} +1 -1
  51. package/workframe-ui/public/assets/{ishikawaDiagram-YF4QCWOH-MRlyodyU.js → ishikawaDiagram-YF4QCWOH-BYtF-wHm.js} +1 -1
  52. package/workframe-ui/public/assets/{journeyDiagram-JHISSGLW-sq7awk87.js → journeyDiagram-JHISSGLW-BMizyHzC.js} +1 -1
  53. package/workframe-ui/public/assets/{kanban-definition-UN3LZRKU-CH6HbcWu.js → kanban-definition-UN3LZRKU-CLLJsdnh.js} +1 -1
  54. package/workframe-ui/public/assets/{line-DRBjFmUW.js → line-BFiSvu-q.js} +1 -1
  55. package/workframe-ui/public/assets/{linear-BR0LunjE.js → linear-D6HYZ18_.js} +1 -1
  56. package/workframe-ui/public/assets/{mermaid-parser.core-Cl1GjHkT.js → mermaid-parser.core-Cid5yDBQ.js} +2 -2
  57. package/workframe-ui/public/assets/{mermaid.core-DsiKK1kE.js → mermaid.core-DqIiju5Z.js} +3 -3
  58. package/workframe-ui/public/assets/{mindmap-definition-RKZ34NQL-DBvi3OpW.js → mindmap-definition-RKZ34NQL-Bg7xJdsz.js} +1 -1
  59. package/workframe-ui/public/assets/{packet-YPE3B663-CPDgtUHg.js → packet-YPE3B663-1ebGt86D.js} +1 -1
  60. package/workframe-ui/public/assets/{pie-LRSECV5Y-BXJIPPDm.js → pie-LRSECV5Y-DJ9wOBC_.js} +1 -1
  61. package/workframe-ui/public/assets/{pieDiagram-4H26LBE5-CZwLEK-o.js → pieDiagram-4H26LBE5-BmK96Aus.js} +1 -1
  62. package/workframe-ui/public/assets/{quadrantDiagram-W4KKPZXB-B65bEUIQ.js → quadrantDiagram-W4KKPZXB-ByUqtWPZ.js} +1 -1
  63. package/workframe-ui/public/assets/{radar-GUYGQ44K-vXHkZzRK.js → radar-GUYGQ44K-DN-Hkt96.js} +1 -1
  64. package/workframe-ui/public/assets/{requirementDiagram-4Y6WPE33-BGWdvDfG.js → requirementDiagram-4Y6WPE33-ByU2r8gb.js} +1 -1
  65. package/workframe-ui/public/assets/{sankeyDiagram-5OEKKPKP-BT2oC8dV.js → sankeyDiagram-5OEKKPKP-elyWa0GK.js} +1 -1
  66. package/workframe-ui/public/assets/{sequenceDiagram-3UESZ5HK-45yj0Wtx.js → sequenceDiagram-3UESZ5HK-CzZwsAiK.js} +1 -1
  67. package/workframe-ui/public/assets/{src-D-P8U2PB.js → src-5vgsM_dl.js} +1 -1
  68. package/workframe-ui/public/assets/{stateDiagram-AJRCARHV-SBrBJBZG.js → stateDiagram-AJRCARHV-B0O5w5lJ.js} +1 -1
  69. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-RXZRaSgT.js +1 -0
  70. package/workframe-ui/public/assets/{timeline-definition-PNZ67QCA-DegmxXXj.js → timeline-definition-PNZ67QCA-oyqa5w5y.js} +1 -1
  71. package/workframe-ui/public/assets/{treeView-BLDUP644-BVy7CsCj.js → treeView-BLDUP644-MCV12-eP.js} +1 -1
  72. package/workframe-ui/public/assets/{treemap-LRROVOQU-BD5Kb74J.js → treemap-LRROVOQU-DiA1P9n5.js} +1 -1
  73. package/workframe-ui/public/assets/{vennDiagram-CIIHVFJN-hwAkNbl3.js → vennDiagram-CIIHVFJN-CO_4x7OR.js} +1 -1
  74. package/workframe-ui/public/assets/{wardley-L42UT6IY-CBMPT8Y1.js → wardley-L42UT6IY-CmQNv6Xv.js} +1 -1
  75. package/workframe-ui/public/assets/{wardleyDiagram-YWT4CUSO-DXqihVlE.js → wardleyDiagram-YWT4CUSO-CJxDkbIj.js} +1 -1
  76. package/workframe-ui/public/assets/{xychartDiagram-2RQKCTM6-Cbb4LBzj.js → xychartDiagram-2RQKCTM6-B02MxgzG.js} +1 -1
  77. package/workframe-ui/public/index.html +7 -10
  78. package/workframe-ui/public/assets/architecture-7EHR7CIX-DOxsUI33.js +0 -1
  79. package/workframe-ui/public/assets/channel-Baa20pIs.js +0 -1
  80. package/workframe-ui/public/assets/chunk-QZHKN3VN-B7tPxs4H.js +0 -1
  81. package/workframe-ui/public/assets/chunk-WU5MYG2G-Dvh2By1o.js +0 -1
  82. package/workframe-ui/public/assets/classDiagram-4FO5ZUOK-BljHyDzF.js +0 -1
  83. package/workframe-ui/public/assets/classDiagram-v2-Q7XG4LA2-BljHyDzF.js +0 -1
  84. package/workframe-ui/public/assets/eventmodeling-FCH6USID-C4xwO6dx.js +0 -1
  85. package/workframe-ui/public/assets/index-BIfRGM8p.js +0 -133
  86. package/workframe-ui/public/assets/stateDiagram-v2-BHNVJYJU-B3im3So2.js +0 -1
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  Published on npm as **create-workframe**.
4
4
 
5
5
  ```bash
6
- npx create-workframe@0.1.4 MyProject
6
+ npx create-workframe@0.1.5 MyProject
7
7
  ```
8
8
 
9
9
  Scaffolds an isolated Workframe + Hermes project on Windows, macOS, and Linux.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-workframe",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Scaffold a Workframe + Hermes workspace with guided onboarding",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@workframe/workframe-api",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -12434,7 +12434,9 @@ def _overlay_chat_llm_env(
12434
12434
  user = str(user_id or "").strip()
12435
12435
  if not user:
12436
12436
  return False
12437
- prov = _llm_billing_provider(hermes_prof, provider)
12437
+ prov = _llm_billing_provider(hermes_prof, provider, user_id=user, workspace_id=workspace_id)
12438
+ if not _profile_llm_proxy_matches_billing(hermes_prof, prov):
12439
+ _ensure_profile_llm_proxy(hermes_prof, prov)
12438
12440
  slug = safe_profile_slug(hermes_prof)
12439
12441
  turn_run = str(uuid.uuid4())
12440
12442
  ready = _try_overlay_turn_provider_env(hermes_prof, user, workspace_id, prov, turn_run)
@@ -12513,21 +12515,91 @@ def _augment_model_suggestions(
12513
12515
  return extra + suggestions
12514
12516
 
12515
12517
 
12516
- def _llm_billing_provider(profile: str, provider_hint: str = "") -> str:
12517
- """Map Hermes routing provider (often custom for proxy) to vault/billing provider id."""
12518
- hint = str(provider_hint or "").strip().lower()
12519
- if hint and hint not in {"custom", "auto"}:
12520
- return hint
12521
- cfg_provider, model = _read_model_from_config(profile)
12522
- cfg_provider = str(cfg_provider or "").strip().lower()
12523
- if cfg_provider and cfg_provider not in {"custom", "auto"}:
12524
- return cfg_provider
12525
- model_id = str(model or "").strip()
12526
- if "/" in model_id:
12527
- return model_id.split("/", 1)[0].strip().lower() or "openrouter"
12518
+ _INTERNAL_LLM_PROXY_RE = re.compile(r"/internal/llm/([a-z0-9_-]+)/v1", re.I)
12519
+
12520
+
12521
+ def _billing_provider_from_block(base_url: str, cfg_provider: str, hint: str) -> str:
12522
+ """Pure resolver: the billing provider is the proxy URL segment (the stored fact).
12523
+
12524
+ Order: explicit hint > proxy URL segment > cfg_provider > "openrouter".
12525
+ The model-id prefix (e.g. ``google/`` in ``google/gemini-2.5-flash``) is an
12526
+ OpenRouter vendor namespace, NOT a billing provider, so it is never used here.
12527
+ ponytail: removing model-prefix inference is the root-cause fix for the
12528
+ "provider not recognized" bug where an OpenRouter-only user was billed to google.
12529
+ """
12530
+ h = str(hint or "").strip().lower()
12531
+ if h and h not in {"custom", "auto"}:
12532
+ return h
12533
+ m = _INTERNAL_LLM_PROXY_RE.search(str(base_url or ""))
12534
+ if m:
12535
+ return m.group(1).lower()
12536
+ cfg = str(cfg_provider or "").strip().lower()
12537
+ if cfg and cfg not in {"custom", "auto"}:
12538
+ return cfg
12528
12539
  return "openrouter"
12529
12540
 
12530
12541
 
12542
+ def _llm_billing_provider(
12543
+ profile: str,
12544
+ provider_hint: str = "",
12545
+ *,
12546
+ user_id: str = "",
12547
+ workspace_id: str = "",
12548
+ ) -> str:
12549
+ """Map Hermes routing provider (often custom for proxy) to vault/billing provider id."""
12550
+ prof = resolve_hermes_profile(profile)
12551
+ block = _read_model_block(prof)
12552
+ cfg_provider = str(block.get("provider") or "").strip().lower()
12553
+ if not cfg_provider:
12554
+ cfg_provider = str(_read_model_from_config(prof)[0] or "").strip().lower()
12555
+ candidate = _billing_provider_from_block(
12556
+ str(block.get("base_url") or ""), cfg_provider, provider_hint,
12557
+ )
12558
+ user = str(user_id or "").strip()
12559
+ ws = str(workspace_id or "").strip()
12560
+ if user and not _user_can_use_llm(user, ws, candidate):
12561
+ connected = sorted(_user_llm_providers_for_picker(user))
12562
+ if connected:
12563
+ return connected[0]
12564
+ return candidate or "openrouter"
12565
+
12566
+
12567
+ def _profile_llm_proxy_matches_billing(profile: str, billing_provider: str) -> bool:
12568
+ prof = resolve_hermes_profile(profile)
12569
+ base = str(_read_model_block(prof).get("base_url") or "").strip()
12570
+ return bool(base) and base == _llm_proxy_base_url(billing_provider)
12571
+
12572
+
12573
+ def _reconcile_profile_llm_for_user(
12574
+ profile: str,
12575
+ user_id: str,
12576
+ workspace_id: str,
12577
+ *,
12578
+ prefer_provider: str = "",
12579
+ ) -> bool:
12580
+ """Point proxy + MVP model at a provider the acting user can bill."""
12581
+ user = str(user_id or "").strip()
12582
+ if not user:
12583
+ return False
12584
+ prof = resolve_hermes_profile(profile)
12585
+ billing = _llm_billing_provider(prof, user_id=user, workspace_id=workspace_id)
12586
+ prefer = str(prefer_provider or "").strip().lower()
12587
+ if prefer and _user_can_use_llm(user, workspace_id, prefer):
12588
+ billing = prefer
12589
+ elif not _user_can_use_llm(user, workspace_id, billing):
12590
+ connected = sorted(_user_llm_providers_for_picker(user))
12591
+ if not connected:
12592
+ return False
12593
+ billing = connected[0]
12594
+ if _user_can_use_llm(user, workspace_id, billing) and _profile_llm_proxy_matches_billing(prof, billing):
12595
+ return False
12596
+ changed = _apply_mvp_model_for_provider(prof, billing)
12597
+ block = _read_model_block(prof)
12598
+ if _is_runtime_profile_slug(prof) or str(block.get("provider") or "").strip().lower() == "custom":
12599
+ _ensure_profile_llm_proxy(prof, billing)
12600
+ return changed
12601
+
12602
+
12531
12603
  def _bootstrap_profile_providers(profile: str, user_id: str = "", workspace_id: str = "") -> bool:
12532
12604
  """Seed profile auth + MVP model config from user, workspace, or primary stack."""
12533
12605
  prof = resolve_hermes_profile(profile)
@@ -12756,7 +12828,9 @@ def profile_chat_session(profile: str, payload: dict[str, Any], user_id: str = "
12756
12828
  profile, user_id, "", workspace_id,
12757
12829
  )
12758
12830
  payer = user_id
12759
- llm_provider = _llm_billing_provider(hermes_prof)
12831
+ if payer:
12832
+ _reconcile_profile_llm_for_user(hermes_prof, payer, workspace_id)
12833
+ llm_provider = _llm_billing_provider(hermes_prof, user_id=payer, workspace_id=workspace_id)
12760
12834
  lifecycle = ensure_profile_api(hermes_prof, user_id, workspace_id)
12761
12835
  llm_ready = _user_can_use_llm(payer, workspace_id, llm_provider) if payer else False
12762
12836
  session_extra = {"llm_ready": llm_ready, "has_llm_provider": llm_ready}
@@ -13064,7 +13138,9 @@ def profile_chat_activate_room_session(
13064
13138
  gateway_sid,
13065
13139
  )
13066
13140
 
13067
- llm_provider = _llm_billing_provider(hermes_prof)
13141
+ if payer:
13142
+ _reconcile_profile_llm_for_user(hermes_prof, payer, workspace_id)
13143
+ llm_provider = _llm_billing_provider(hermes_prof, user_id=payer, workspace_id=workspace_id)
13068
13144
  llm_ready = _overlay_chat_llm_env(hermes_prof, user_id, workspace_id, llm_provider)
13069
13145
 
13070
13146
  history = chat_messages(hermes_prof, session_id)
@@ -13277,7 +13353,9 @@ def profile_chat_message(profile: str, payload: dict[str, Any]) -> dict[str, Any
13277
13353
  hermes_prof, _template_prof = _resolve_bind_profile_arg(
13278
13354
  profile, payer, room_id, workspace_id,
13279
13355
  )
13280
- llm_provider = _llm_billing_provider(hermes_prof)
13356
+ if payer:
13357
+ _reconcile_profile_llm_for_user(hermes_prof, payer, workspace_id)
13358
+ llm_provider = _llm_billing_provider(hermes_prof, user_id=payer, workspace_id=workspace_id)
13281
13359
  lifecycle = ensure_profile_api(
13282
13360
  hermes_prof,
13283
13361
  payer,
@@ -13432,7 +13510,9 @@ def stream_profile_chat(handler: BaseHTTPRequestHandler, profile: str, payload:
13432
13510
  raise ValueError("session_id required")
13433
13511
  if not text:
13434
13512
  raise ValueError("text required")
13435
- llm_provider = _llm_billing_provider(prof)
13513
+ if _triggering_user:
13514
+ _reconcile_profile_llm_for_user(prof, _triggering_user, _workspace_id)
13515
+ llm_provider = _llm_billing_provider(prof, user_id=_triggering_user, workspace_id=_workspace_id)
13436
13516
  llm_ready = bool(
13437
13517
  _triggering_user
13438
13518
  and _user_can_use_llm(_triggering_user, _workspace_id, llm_provider)
@@ -14431,17 +14511,23 @@ def _bootstrap_model_after_llm_connect(
14431
14511
  if not spec or str(spec.get("category") or "") != "llm":
14432
14512
  return
14433
14513
  provider_key = str(provider or "").strip().lower()
14514
+ user = str(user_id or "").strip()
14515
+ ws = str(workspace_id or "").strip()
14434
14516
  primary = _primary_profile()
14435
14517
  if primary:
14436
14518
  _, model = _read_model_from_config(primary)
14437
14519
  if not str(model or "").strip():
14438
14520
  _apply_mvp_model_for_provider(primary, provider_key)
14521
+ elif user:
14522
+ _reconcile_profile_llm_for_user(primary, user, ws, prefer_provider=provider_key)
14439
14523
  template = primary or "workframe-agent"
14440
14524
  runtime = _runtime_profile_slug(user_id, template)
14441
14525
  if _runtime_profile_on_disk(runtime):
14442
14526
  _, model = _read_model_from_config(runtime)
14443
14527
  if not str(model or "").strip():
14444
14528
  _apply_mvp_model_for_provider(runtime, provider_key)
14529
+ elif user:
14530
+ _reconcile_profile_llm_for_user(runtime, user, ws, prefer_provider=provider_key)
14445
14531
 
14446
14532
 
14447
14533
  # Curated snapshot of commonly-used models per provider. Surfaced in the
@@ -14504,9 +14590,6 @@ HERMES_MODEL_CATALOG: list[dict[str, str]] = [
14504
14590
  {"provider": "OpenRouter", "model": "openrouter/nvidia/llama-3.1-nemotron-70b-instruct",
14505
14591
  "label": "Nemotron 70B Instruct",
14506
14592
  "description": "NVIDIA Nemotron via OpenRouter."},
14507
- {"provider": "OpenRouter", "model": "openrouter/anthropic/claude-sonnet-4-5",
14508
- "label": "Claude Sonnet 4.5 (via OR)",
14509
- "description": "Sonnet through OpenRouter."},
14510
14593
  {"provider": "OpenRouter", "model": "openrouter/openai/gpt-4o",
14511
14594
  "label": "GPT-4o (via OR)",
14512
14595
  "description": "GPT-4o through OpenRouter."},
@@ -14889,6 +14972,15 @@ def hermes_models(profile: str = "", user_id: str = "", workspace_id: str = "")
14889
14972
  for row in live
14890
14973
  if str(row.get("model") or "") not in seen
14891
14974
  ] + suggestions
14975
+ seen_models: set[str] = set()
14976
+ deduped: list[dict[str, str]] = []
14977
+ for row in suggestions:
14978
+ mid = str(row.get("model") or "").strip()
14979
+ if not mid or mid in seen_models:
14980
+ continue
14981
+ seen_models.add(mid)
14982
+ deduped.append(row)
14983
+ suggestions = deduped
14892
14984
  return {
14893
14985
  "ok": True,
14894
14986
  "profile": primary_profile,
@@ -0,0 +1,24 @@
1
+ """ponytail self-check: billing provider source of truth = proxy URL, never model-id prefix.
2
+
3
+ Run: python services/workframe-api/test_billing_provider.py
4
+ No framework, no fixtures. Fails fast if the inference regression returns.
5
+ """
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
10
+
11
+ import server # noqa: E402 (module import is side-effect-free; main() is guarded)
12
+
13
+ # The bug case: profile model is google/gemini-2.5-flash but proxy routes via openrouter.
14
+ # The model-id prefix MUST NOT leak as the billing provider.
15
+ assert server._billing_provider_from_block("/internal/llm/openrouter/v1", "custom", "") == "openrouter", \
16
+ "proxy URL segment must win over model-id prefix"
17
+ # Explicit hint honored.
18
+ assert server._billing_provider_from_block("", "", "anthropic") == "anthropic", "hint honored"
19
+ # Empty block defaults to openrouter (no inference).
20
+ assert server._billing_provider_from_block("", "", "") == "openrouter", "empty-block default"
21
+ # cfg_provider honored only when no proxy URL is set.
22
+ assert server._billing_provider_from_block("", "openai", "") == "openai", "cfg_provider honored without proxy"
23
+
24
+ print("billing provider self-check ok")
@@ -1 +1 @@
1
- import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-CqP43hXK.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
1
+ import{n as e,t}from"./path-BWPyau1x.js";import{a as n,c as r,d as i,f as a,i as o,l as s,m as c,n as l,o as u,p as d,r as f,u as p}from"./dist-BLcLhTn6.js";function m(e){return e.innerRadius}function h(e){return e.outerRadius}function g(e){return e.startAngle}function _(e){return e.endAngle}function v(e){return e&&e.padAngle}function y(e,t,n,r,i,a,o,s){var c=n-e,l=r-t,u=o-i,d=s-a,f=d*c-u*l;if(!(f*f<1e-12))return f=(u*(t-a)-d*(e-i))/f,[e+f*c,t+f*l]}function b(e,t,n,r,i,a,o){var c=e-n,l=t-r,u=(o?a:-a)/d(c*c+l*l),f=u*l,p=-u*c,m=e+f,h=t+p,g=n+f,_=r+p,v=(m+g)/2,y=(h+_)/2,b=g-m,x=_-h,S=b*b+x*x,C=i-a,w=m*_-g*h,T=(x<0?-1:1)*d(s(0,C*C*S-w*w)),E=(w*x-b*T)/S,D=(-w*b-x*T)/S,O=(w*x+b*T)/S,k=(-w*b+x*T)/S,A=E-v,j=D-y,M=O-v,N=k-y;return A*A+j*j>M*M+N*N&&(E=O,D=k),{cx:E,cy:D,x01:-f,y01:-p,x11:E*(i/C-1),y11:D*(i/C-1)}}function x(){var s=m,x=h,S=e(0),C=null,w=g,T=_,E=v,D=null,O=t(k);function k(){var e,t,m=+s.apply(this,arguments),h=+x.apply(this,arguments),g=w.apply(this,arguments)-r,_=T.apply(this,arguments)-r,v=l(_-g),k=_>g;if(D||=e=O(),h<m&&(t=h,h=m,m=t),!(h>1e-12))D.moveTo(0,0);else if(v>c-1e-12)D.moveTo(h*u(g),h*a(g)),D.arc(0,0,h,g,_,!k),m>1e-12&&(D.moveTo(m*u(_),m*a(_)),D.arc(0,0,m,_,g,k));else{var A=g,j=_,M=g,N=_,P=v,F=v,I=E.apply(this,arguments)/2,L=I>1e-12&&(C?+C.apply(this,arguments):d(m*m+h*h)),R=p(l(h-m)/2,+S.apply(this,arguments)),z=R,B=R,V,H;if(L>1e-12){var U=o(L/m*a(I)),W=o(L/h*a(I));(P-=U*2)>1e-12?(U*=k?1:-1,M+=U,N-=U):(P=0,M=N=(g+_)/2),(F-=W*2)>1e-12?(W*=k?1:-1,A+=W,j-=W):(F=0,A=j=(g+_)/2)}var G=h*u(A),K=h*a(A),q=m*u(N),J=m*a(N);if(R>1e-12){var Y=h*u(j),X=h*a(j),Z=m*u(M),Q=m*a(M),$;if(v<i)if($=y(G,K,Z,Q,Y,X,q,J)){var ee=G-$[0],te=K-$[1],ne=Y-$[0],re=X-$[1],ie=1/a(f((ee*ne+te*re)/(d(ee*ee+te*te)*d(ne*ne+re*re)))/2),ae=d($[0]*$[0]+$[1]*$[1]);z=p(R,(m-ae)/(ie-1)),B=p(R,(h-ae)/(ie+1))}else z=B=0}F>1e-12?B>1e-12?(V=b(Z,Q,G,K,h,B,k),H=b(Y,X,q,J,h,B,k),D.moveTo(V.cx+V.x01,V.cy+V.y01),B<R?D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,B,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,h,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),!k),D.arc(H.cx,H.cy,B,n(H.y11,H.x11),n(H.y01,H.x01),!k))):(D.moveTo(G,K),D.arc(0,0,h,A,j,!k)):D.moveTo(G,K),!(m>1e-12)||!(P>1e-12)?D.lineTo(q,J):z>1e-12?(V=b(q,J,Y,X,m,-z,k),H=b(G,K,Z,Q,m,-z,k),D.lineTo(V.cx+V.x01,V.cy+V.y01),z<R?D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(H.y01,H.x01),!k):(D.arc(V.cx,V.cy,z,n(V.y01,V.x01),n(V.y11,V.x11),!k),D.arc(0,0,m,n(V.cy+V.y11,V.cx+V.x11),n(H.cy+H.y11,H.cx+H.x11),k),D.arc(H.cx,H.cy,z,n(H.y11,H.x11),n(H.y01,H.x01),!k))):D.arc(0,0,m,N,M,k)}if(D.closePath(),e)return D=null,e+``||null}return k.centroid=function(){var e=(+s.apply(this,arguments)+ +x.apply(this,arguments))/2,t=(+w.apply(this,arguments)+ +T.apply(this,arguments))/2-i/2;return[u(t)*e,a(t)*e]},k.innerRadius=function(t){return arguments.length?(s=typeof t==`function`?t:e(+t),k):s},k.outerRadius=function(t){return arguments.length?(x=typeof t==`function`?t:e(+t),k):x},k.cornerRadius=function(t){return arguments.length?(S=typeof t==`function`?t:e(+t),k):S},k.padRadius=function(t){return arguments.length?(C=t==null?null:typeof t==`function`?t:e(+t),k):C},k.startAngle=function(t){return arguments.length?(w=typeof t==`function`?t:e(+t),k):w},k.endAngle=function(t){return arguments.length?(T=typeof t==`function`?t:e(+t),k):T},k.padAngle=function(t){return arguments.length?(E=typeof t==`function`?t:e(+t),k):E},k.context=function(e){return arguments.length?(D=e??null,k):D},k}export{x as t};
@@ -0,0 +1 @@
1
+ import"./chunk-NNHCCRGN-DlpIbxXb.js";import{x as e}from"./mermaid-parser.core-Cid5yDBQ.js";export{e as createArchitectureServices};