@maudecode/talaria-web 1.15.0 → 1.16.1
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/_release.json +4 -4
- package/dist/api/automation-router.d.ts +21 -7
- package/dist/api/automation-router.js +3 -0
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +142 -6
- package/dist/api/chat-router.js +11 -5
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.js +44 -32
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.js +5 -58
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +4 -2
- package/dist/api/router.js +8 -8
- package/dist/api/router.js.map +1 -1
- package/dist/api/sessions-router.d.ts +714 -42
- package/dist/api/sessions-router.js +57 -23
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +153 -9
- package/dist/api/settings-router.js +61 -16
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.js +1 -1
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-router.d.ts +13 -1
- package/dist/api/tools-router.js +10 -8
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +1047 -67
- package/dist/auth/gate.js +8 -4
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/oidc.d.ts +6 -2
- package/dist/auth/oidc.js +16 -11
- package/dist/auth/oidc.js.map +1 -1
- package/dist/auth/store.d.ts +14 -2
- package/dist/auth/store.js +26 -1
- package/dist/auth/store.js.map +1 -1
- package/dist/cli/dotenv.d.ts +1 -1
- package/dist/cli/dotenv.js +36 -3
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/http/addresses.d.ts +6 -0
- package/dist/http/addresses.js +26 -0
- package/dist/http/addresses.js.map +1 -1
- package/dist/http/context.d.ts +3 -3
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.js +26 -7
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.js +1 -1
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/providers/catalog.d.ts +74 -1
- package/dist/providers/catalog.js +248 -42
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +9 -4
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.js +493 -13
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +52 -28
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +22 -3
- package/dist/sessions/anchor.js +87 -42
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-tasks.d.ts +4 -1
- package/dist/sessions/background-tasks.js +7 -4
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/completions.js +1 -1
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/list.d.ts +8 -4
- package/dist/sessions/list.js +24 -17
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +70 -7
- package/dist/sessions/merge.js +331 -23
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/pending.d.ts +5 -0
- package/dist/sessions/pending.js +30 -10
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/service.d.ts +57 -3
- package/dist/sessions/service.js +327 -56
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +6 -1
- package/dist/sessions/session.js +6 -0
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.js +11 -2
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/store.js +1 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/turn.d.ts +19 -1
- package/dist/sessions/turn.js +218 -113
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.js +9 -0
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.js +2 -0
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/tools/commands.d.ts +15 -0
- package/dist/tools/commands.js +52 -0
- package/dist/tools/commands.js.map +1 -0
- package/dist/tools/crons.d.ts +4 -1
- package/dist/tools/crons.js +58 -11
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/mcp.js +5 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +1 -1
- package/dist/tools/update-notifications.js +1 -1
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/updates.d.ts +8 -3
- package/dist/tools/updates.js +133 -34
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/git.d.ts +57 -10
- package/dist/workspace/git.js +302 -195
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media-refs.d.ts +13 -0
- package/dist/workspace/media-refs.js +165 -0
- package/dist/workspace/media-refs.js.map +1 -0
- package/dist/workspace/media.d.ts +19 -0
- package/dist/workspace/media.js +61 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +7 -1
- package/dist/workspace/workspaces.js +30 -21
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.d.ts +2 -4
- package/dist/workspace/worktrees.js +28 -33
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +4 -2
- package/sidecar/scripts/replay_sidecar.py +3 -1
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/home.py +59 -2
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +19 -3
- package/sidecar/talaria_sidecar/methods/chat.py +129 -23
- package/sidecar/talaria_sidecar/methods/commands.py +46 -31
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/profiles.py +5 -4
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +2 -0
- package/sidecar/talaria_sidecar/methods/text.py +1 -20
- package/sidecar/talaria_sidecar/methods/usage.py +7 -1
- package/static/dist/FILES.txt +124 -120
- package/static/dist/assets/AppShell-ef3bhu32.js +1 -0
- package/static/dist/assets/Button-nx37f4u1.js +1 -0
- package/static/dist/assets/ChatPage-cdcpykcg.js +25 -0
- package/static/dist/assets/Dialog-mec42hvb.js +1 -0
- package/static/dist/assets/HubRoute-ecb65zpr.js +2 -0
- package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-jqrx5es0.js} +125 -125
- package/static/dist/assets/RightPanel-d1lgvk4s.js +1 -0
- package/static/dist/assets/Select-h4ivl9qf.js +1 -0
- package/static/dist/assets/SessionListPanel-ewib046b.js +1 -0
- package/static/dist/assets/SettingsLayout-lx654cup.js +1 -0
- package/static/dist/assets/States-ehsb6lzo.js +1 -0
- package/static/dist/assets/Toaster-mch1pa3t.js +1 -0
- package/static/dist/assets/_app-m8d961ef.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-mp33hbzi.js +1 -0
- package/static/dist/assets/_app.index-dk6zp7e0.js +1 -0
- package/static/dist/assets/_app.insights-jt2fk4i1.js +1 -0
- package/static/dist/assets/_app.kanban-if4pubeu.js +1 -0
- package/static/dist/assets/_app.logs-mfaw9m6g.js +1 -0
- package/static/dist/assets/_app.memory-h3eaarb6.js +1 -0
- package/static/dist/assets/_app.profiles-ienktovs.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-hh27wwu2.js} +1 -1
- package/static/dist/assets/_app.settings-dpoe0bzf.js +1 -0
- package/static/dist/assets/_app.settings._section-l468ynag.js +3 -0
- package/static/dist/assets/_app.skills-kcbncc8i.js +1 -0
- package/static/dist/assets/_app.tasks-occdrqhw.js +2 -0
- package/static/dist/assets/_app.todos-nnke2rcc.js +1 -0
- package/static/dist/assets/_app.workspaces-cdlatvwj.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-oity9y4o.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hj11333z.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-ceaepnyr.js +36 -0
- package/static/dist/assets/{back-e4m6nsnm.js → back-eu64w89w.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-n79st4pj.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-mi73akzk.js} +1 -1
- package/static/dist/assets/cancel-mzvkdjg8.js +1 -0
- package/static/dist/assets/channel-mbvmf2vv.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-laat0g1q.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-0psy1tr2.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-lw8uox8x.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-erassib3.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-fz7rx619.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gdb0epb2.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-ea8t26qw.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-mb1gu9kz.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-eovmccvy.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-bo55zjo5.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-ja7ohe4h.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-e30g9nus.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-gbb798f7.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-h2iw8urm.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-fd2cottz.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-o8r05k0n.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-o8r05k0n.js +1 -0
- package/static/dist/assets/{copied-kq0u3ht7.js → copied-h65fc3cx.js} +1 -1
- package/static/dist/assets/copy-e6977uwq.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-lbltyl5j.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-lxhsqq4a.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-gn4nnaln.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-pa3ij6q7.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-zbipyz57.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-d1kbpzgf.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-i3ty14xl.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m8xefqa7.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-nz7wj05l.js} +1 -1
- package/static/dist/assets/endpoints-k7yf25m0.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fa41ib83.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-kpbq09bx.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-lcz0kx14.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-n7zq01qu.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-c89emb07.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-cz7cgr6w.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-d9bapmni.js} +1 -1
- package/static/dist/assets/{index-nqed3oum.js → index-h00usru2.js} +3 -3
- package/static/dist/assets/index-n73ijkv2.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-ndddhhhh.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-ccxigcpf.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-le0z9vtc.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-ivsmb26o.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-h6n2awt0.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-m1rtyk4t.js +1 -0
- package/static/dist/assets/{line-friyjof3.js → line-dmzlfe0u.js} +1 -1
- package/static/dist/assets/link-budeg5t9.js +1 -0
- package/static/dist/assets/login-kjhasvp8.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-jb5d211j.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-gwyd5bnq.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-l68gg5h7.js} +1 -1
- package/static/dist/assets/onboarding-cdfrk0pj.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-gpv06065.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-e52vs23u.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-l6m7nz5s.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-b4is36bq.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-m8vt2788.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-nxbrhiov.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-222mu9hi.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-kdvgl6x8.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-itiqslby.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-go7kov78.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-o8lldfzv.js} +1 -1
- package/static/dist/assets/{refresh-cw-k66keho6.js → refresh-cw-kbcje1uy.js} +1 -1
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-ovyq38kv.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-ou7lznor.js} +1 -1
- package/static/dist/assets/{saved-do49si3v.js → saved-bazmcup9.js} +1 -1
- package/static/dist/assets/search-jen74z4u.js +1 -0
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-ovckdzf2.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ertwwhlx.js} +1 -1
- package/static/dist/assets/share._token-dan9hgvj.js +2 -0
- package/static/dist/assets/{sliders-horizontal-c4wya4dy.js → sliders-horizontal-f9cb3lh3.js} +1 -1
- package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-fcrexkkv.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-lcfraua2.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-f7usu8fw.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-ogmanni0.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-gp1hvz9f.js} +1 -1
- package/static/dist/assets/todoStore-eqtid4r4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-gb9gf11u.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-nuvfnvvi.js} +1 -1
- package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-htelhc68.js} +1 -1
- package/static/dist/assets/useForm-h2t0u14e.js +1 -0
- package/static/dist/assets/useLocale-hcsb84pj.js +1 -0
- package/static/dist/assets/useNavigate-nwy70ztj.js +1 -0
- package/static/dist/assets/{useParams-lo9u33d1.js → useParams-dqd64jv7.js} +1 -1
- package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-j30pvsy7.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-fzchbzaj.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-m47mhfbh.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-odg6ct1z.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-mrhms5um.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +1 -1
- package/static/dist/assets/AppShell-orat1s43.js +0 -1
- package/static/dist/assets/ChatPage-ojhka3hy.js +0 -22
- package/static/dist/assets/HubRoute-gxgw1t33.js +0 -2
- package/static/dist/assets/RightPanel-c66ani1s.js +0 -1
- package/static/dist/assets/Select-fswf66up.js +0 -1
- package/static/dist/assets/SessionListPanel-goh5mtt8.js +0 -1
- package/static/dist/assets/SettingsLayout-n5d4wq4m.js +0 -1
- package/static/dist/assets/States-kxyu50at.js +0 -1
- package/static/dist/assets/Toaster-6zmbcqgg.js +0 -1
- package/static/dist/assets/_app-fowyruiu.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-nvjyrwhy.js +0 -1
- package/static/dist/assets/_app.index-c9zv4y7w.js +0 -1
- package/static/dist/assets/_app.insights-jf12nofo.js +0 -1
- package/static/dist/assets/_app.kanban-lri5a4yu.js +0 -1
- package/static/dist/assets/_app.logs-md1p0zqo.js +0 -1
- package/static/dist/assets/_app.memory-bzvf2e28.js +0 -1
- package/static/dist/assets/_app.profiles-jpg7zmbn.js +0 -1
- package/static/dist/assets/_app.settings-mpuwlx4j.js +0 -1
- package/static/dist/assets/_app.settings._section-knz8rwoj.js +0 -1
- package/static/dist/assets/_app.skills-fwu1dkir.js +0 -1
- package/static/dist/assets/_app.tasks-h9gphtl3.js +0 -2
- package/static/dist/assets/_app.todos-hd1dnvrh.js +0 -1
- package/static/dist/assets/_app.workspaces-l8toyn5f.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-cavnusaw.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-k5a7v36n.js +0 -36
- package/static/dist/assets/channel-gozy3dp3.js +0 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-n0vs2f2q.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-n0vs2f2q.js +0 -1
- package/static/dist/assets/endpoints-k8agnro8.js +0 -9
- package/static/dist/assets/eventmodeling-NTZA5JFV-j1y2ekec.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-g2cvcbm5.js +0 -1
- package/static/dist/assets/index-nfy0ezs6.css +0 -2
- package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
- package/static/dist/assets/lazyRouteComponent-b2eg0fws.js +0 -1
- package/static/dist/assets/link-m90ahx3n.js +0 -1
- package/static/dist/assets/login-bw7m2fzo.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-njwcl62n.js +0 -1
- package/static/dist/assets/onboarding-ret37j1e.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-m741owar.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-noa4fbia.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-o4bpw4nb.js +0 -1
- package/static/dist/assets/search-j5ki6a9o.js +0 -1
- package/static/dist/assets/share._token-kg8w647b.js +0 -2
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-k2wg2645.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-n5kvjtgr.js +0 -8
- package/static/dist/assets/tabBuild-bjmyvmn5.js +0 -56
- package/static/dist/assets/todoStore-kjk5l0sy.js +0 -1
- package/static/dist/assets/useForm-ocxu460f.js +0 -1
- package/static/dist/assets/useLocale-fzy5jhg6.js +0 -1
|
@@ -4,6 +4,8 @@ export interface ModelEntry {
|
|
|
4
4
|
id: string;
|
|
5
5
|
label: string;
|
|
6
6
|
supports_fast_tier?: boolean;
|
|
7
|
+
provider_id?: string;
|
|
8
|
+
bare_id?: string;
|
|
7
9
|
}
|
|
8
10
|
export interface ModelGroup {
|
|
9
11
|
provider: string;
|
|
@@ -11,11 +13,16 @@ export interface ModelGroup {
|
|
|
11
13
|
models: ModelEntry[];
|
|
12
14
|
extra_models?: ModelEntry[];
|
|
13
15
|
}
|
|
16
|
+
/** TAL-301: the catalog entry a stored `(model, provider)` pair selects, so clients render the pairing instead of computing it. */
|
|
17
|
+
export declare function catalogOptionId(catalog: ModelsCatalog, model: string | null, provider: string | null): string | null;
|
|
14
18
|
/** Python `_split_picker_overflow_models`: past 25 rows the picker shows 15, keeping the selected model visible. */
|
|
15
19
|
export declare function splitPickerOverflow(models: ModelEntry[], selected: string, providerId: string): [ModelEntry[], ModelEntry[]];
|
|
16
20
|
export interface ModelsCatalog {
|
|
17
21
|
active_provider: string | null;
|
|
18
22
|
default_model: string;
|
|
23
|
+
default_provider_id?: string | null;
|
|
24
|
+
default_bare_id?: string;
|
|
25
|
+
default_option_id?: string | null;
|
|
19
26
|
groups: ModelGroup[];
|
|
20
27
|
aliases: Record<string, string>;
|
|
21
28
|
configured_model_badges: Record<string, {
|
|
@@ -49,6 +56,49 @@ export interface CatalogDeps {
|
|
|
49
56
|
/** WebUI state directory; holds `.quota_scope_id`, the stable public identity namespace for quota sources. */
|
|
50
57
|
stateDir?: string;
|
|
51
58
|
}
|
|
59
|
+
/** ISO-8601 UTC for epoch seconds, without a `.000` fraction. */
|
|
60
|
+
export declare const isoAt: (seconds: number) => string;
|
|
61
|
+
/** ISO-8601 UTC (`…Z`) or null. Accepts the pre-TAL-409 sidecar's `str(datetime)` space form; a zone-less stamp is UTC. */
|
|
62
|
+
export declare function isoUtc(value: unknown): string | null;
|
|
63
|
+
export interface QuotaPace {
|
|
64
|
+
expected_remaining_percent: number;
|
|
65
|
+
pace_delta_percent: number;
|
|
66
|
+
burn_rate: number;
|
|
67
|
+
minutes_to_reset: number;
|
|
68
|
+
projected_minutes_to_empty: number | null;
|
|
69
|
+
elapsed_minutes: number;
|
|
70
|
+
valid_until: string;
|
|
71
|
+
}
|
|
72
|
+
export interface QuotaForecast {
|
|
73
|
+
outcome: 'safe' | 'warning';
|
|
74
|
+
budget_unit: 'hour' | 'day';
|
|
75
|
+
budget_percent: number | null;
|
|
76
|
+
depletion_margin_minutes: number | null;
|
|
77
|
+
}
|
|
78
|
+
export interface QuotaWindow {
|
|
79
|
+
label: string;
|
|
80
|
+
used_percent: number | null;
|
|
81
|
+
remaining_percent: number | null;
|
|
82
|
+
reset_at: string | null;
|
|
83
|
+
detail: string | null;
|
|
84
|
+
window_seconds: number | null;
|
|
85
|
+
pace: QuotaPace | null;
|
|
86
|
+
forecast: QuotaForecast | null;
|
|
87
|
+
}
|
|
88
|
+
export interface QuotaWindows {
|
|
89
|
+
windows: QuotaWindow[];
|
|
90
|
+
pace_window_index: number | null;
|
|
91
|
+
session_window_index: number | null;
|
|
92
|
+
weekly_window_index: number | null;
|
|
93
|
+
}
|
|
94
|
+
/** The forecast a pace implies: per-hour or per-day budget until reset, and whether the projection empties the window first. */
|
|
95
|
+
export declare function quotaForecast(pace: QuotaPace, remaining: number | null): QuotaForecast;
|
|
96
|
+
/**
|
|
97
|
+
* TAL-409: the one normaliser for account-usage windows (Python `_snapshot_windows_payload` plus the pace math the
|
|
98
|
+
* App used to run). Blank labels drop; `reset_at` becomes ISO `Z`; each window gets its duration, pace and forecast
|
|
99
|
+
* as of `nowSeconds`; the indexes name the windows the App shows for pace, session and weekly selections.
|
|
100
|
+
*/
|
|
101
|
+
export declare function normalizeQuotaWindows(raw: unknown, nowSeconds: number): QuotaWindows;
|
|
52
102
|
export declare function displayName(pid: string): string;
|
|
53
103
|
/** Python `_format_ollama_label`. */
|
|
54
104
|
export declare function formatOllamaLabel(mid: string): string;
|
|
@@ -59,12 +109,20 @@ export declare function providerEnvVar(pid: string): string | null;
|
|
|
59
109
|
export declare function applyProviderPrefix(models: ModelEntry[], pid: string, active: string | null): ModelEntry[];
|
|
60
110
|
/** Python `_deduplicate_model_ids`: later duplicate ids (by provider-id order) gain an `@provider:` prefix. */
|
|
61
111
|
export declare function deduplicateModelIds(groups: ModelGroup[]): void;
|
|
112
|
+
/** Quota sources keep the first row per source id, ordered by provider id, account label, then source id (TAL-272). */
|
|
113
|
+
export declare function uniqueQuotaSources<T extends {
|
|
114
|
+
source_id: string;
|
|
115
|
+
provider_id: string;
|
|
116
|
+
account_label: string;
|
|
117
|
+
}>(sources: T[]): T[];
|
|
62
118
|
export declare class ProviderCatalog {
|
|
63
119
|
private readonly deps;
|
|
64
120
|
private readonly liveFailed;
|
|
65
121
|
private readonly liveIds;
|
|
66
122
|
private readonly liveInflight;
|
|
67
123
|
private readonly providersCache;
|
|
124
|
+
/** TAL-301: the last catalog served per profile home, for the sync session payloads' `model_option_id`. */
|
|
125
|
+
private readonly lastModels;
|
|
68
126
|
constructor(deps: CatalogDeps);
|
|
69
127
|
/** Python `models_cache` diagnostics: live-id groups held, ids across them, and the oldest snapshot's age. */
|
|
70
128
|
diagnosticSnapshot(): {
|
|
@@ -73,6 +131,10 @@ export declare class ProviderCatalog {
|
|
|
73
131
|
age_seconds: number | null;
|
|
74
132
|
};
|
|
75
133
|
invalidate(profileHome?: string, provider?: string): void;
|
|
134
|
+
/** TAL-301: the entry id a stored `(model, provider)` pair selects in the last catalog built for this home; a cold home starts building one. */
|
|
135
|
+
modelOptionFor(profileHome: string, model: string | null, provider: string | null): string | null;
|
|
136
|
+
/** Builds the catalog `modelOptionFor` reads once per home; an unavailable catalog leaves every option id null. */
|
|
137
|
+
warmModelOptions(profileHome: string): Promise<void>;
|
|
76
138
|
/**
|
|
77
139
|
* The process-environment value of `name` as a named profile may see it: values `loadStartupEnv` copied from the
|
|
78
140
|
* default profile's `.env` belong to that profile only, so they never count for another profile.
|
|
@@ -86,6 +148,11 @@ export declare class ProviderCatalog {
|
|
|
86
148
|
liveModelIds(profileHome: string, pid: string, opts?: {
|
|
87
149
|
force?: boolean;
|
|
88
150
|
}): Promise<string[]>;
|
|
151
|
+
/**
|
|
152
|
+
* TAL-288: the model-provider plugins installed and enabled in this profile, as the sidecar reads them from the Agent.
|
|
153
|
+
* A failed lookup lists none, so every other provider stays usable; an id that cannot be a provider routing hint is dropped.
|
|
154
|
+
*/
|
|
155
|
+
private pluginProviders;
|
|
89
156
|
/** Python `get_providers`. */
|
|
90
157
|
providers(profileHome: string): Promise<{
|
|
91
158
|
providers: Dict[];
|
|
@@ -93,6 +160,8 @@ export declare class ProviderCatalog {
|
|
|
93
160
|
}>;
|
|
94
161
|
/** Python `get_available_models` (static catalog + live ids for keyed providers). */
|
|
95
162
|
models(profileHome: string): Promise<ModelsCatalog>;
|
|
163
|
+
/** Stamps the default's entry id and keeps the catalog for `modelOptionFor`. */
|
|
164
|
+
private remember;
|
|
96
165
|
/** Python `_configured_model_badges_from_static_catalog`: which picker rows are the main/fallback selections. */
|
|
97
166
|
private badges;
|
|
98
167
|
/** Python `_handle_live_models`. */
|
|
@@ -111,6 +180,7 @@ export declare class ProviderCatalog {
|
|
|
111
180
|
private readonly accountUsageCache;
|
|
112
181
|
quota(profileHome: string, providerRaw: string | null, opts?: {
|
|
113
182
|
refresh?: boolean;
|
|
183
|
+
at?: number;
|
|
114
184
|
}): Promise<Dict>;
|
|
115
185
|
private fetchOpenRouterKey;
|
|
116
186
|
private quotaScopeCache;
|
|
@@ -118,7 +188,10 @@ export declare class ProviderCatalog {
|
|
|
118
188
|
private quotaServerScopeId;
|
|
119
189
|
/** Python `_quota_profile_scope_id`. */
|
|
120
190
|
quotaProfileScopeId(profile: string): string;
|
|
121
|
-
/**
|
|
191
|
+
/**
|
|
192
|
+
* Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists.
|
|
193
|
+
* Each source id appears once (two custom providers can share a slug), ordered by provider, account label, then source id.
|
|
194
|
+
*/
|
|
122
195
|
quotas(profileHome: string, profile: string, opts?: {
|
|
123
196
|
sourceId?: string | null;
|
|
124
197
|
refresh?: boolean;
|
|
@@ -18,7 +18,7 @@ import { str } from '../util.js';
|
|
|
18
18
|
import { loadEnvFile } from './env-file.js';
|
|
19
19
|
import { ACCOUNT_USAGE_PROVIDERS, FALLBACK_MODELS, OAUTH_PROVIDERS, PORTAL_PROVIDERS, PROVIDER_DISPLAY, PROVIDER_ENV_VAR, PROVIDER_ENV_VAR_ALIASES, PROVIDER_MODELS, SELF_HOSTED_PROVIDER_IDS, } from './tables.js';
|
|
20
20
|
import { activeProviderFromConfig, canonicaliseProviderId, configuredModelIds, configuredModelOptions, customProviderEntries, customProviderSlug, dict, effectiveDefaultModel, isDict, isOpenAiFamilyProvider, mainModelSupportsServiceTier, modelSection, parseProviderQualifiedModel, providerIdentity, resolveProviderAlias, } from '../config/agent-config.js';
|
|
21
|
-
/** Python `_model_matches_picker_selection`: same bare id, and the routing hints agree when
|
|
21
|
+
/** Python `_model_matches_picker_selection`: same bare id, and the routing hints agree when the selection names one. */
|
|
22
22
|
function modelMatchesPickerSelection(modelId, selected, providerId) {
|
|
23
23
|
const candidate = modelId.trim();
|
|
24
24
|
const sel = selected.trim();
|
|
@@ -26,12 +26,28 @@ function modelMatchesPickerSelection(modelId, selected, providerId) {
|
|
|
26
26
|
return false;
|
|
27
27
|
if (candidate === sel)
|
|
28
28
|
return true;
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
const [candBare, candProvider] = parseProviderQualifiedModel(candidate) ?? [candidate, providerId.trim()];
|
|
30
|
+
const [selBare, selProvider] = parseProviderQualifiedModel(sel) ?? [sel, ''];
|
|
31
|
+
return selBare === candBare && (!selProvider || selProvider.toLowerCase() === candProvider.toLowerCase());
|
|
32
|
+
}
|
|
33
|
+
/** TAL-301: every entry names its routing provider and bare id, split once by `parseProviderQualifiedModel`. */
|
|
34
|
+
function stampModelEntries(models, providerId) {
|
|
35
|
+
return models.map((m) => {
|
|
36
|
+
const [bare, provider] = parseProviderQualifiedModel(m.id) ?? [m.id, providerId];
|
|
37
|
+
return { ...m, bare_id: bare, provider_id: provider };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** TAL-301: the catalog entry a stored `(model, provider)` pair selects, so clients render the pairing instead of computing it. */
|
|
41
|
+
export function catalogOptionId(catalog, model, provider) {
|
|
42
|
+
const bare = str(model).trim();
|
|
43
|
+
const pid = str(provider).trim().toLowerCase();
|
|
44
|
+
if (!bare || !pid)
|
|
45
|
+
return null;
|
|
46
|
+
for (const g of catalog.groups)
|
|
47
|
+
for (const e of [...g.models, ...(g.extra_models ?? [])])
|
|
48
|
+
if (e.bare_id === bare && str(e.provider_id).toLowerCase() === pid)
|
|
49
|
+
return e.id;
|
|
50
|
+
return null;
|
|
35
51
|
}
|
|
36
52
|
/** Python `_split_picker_overflow_models`: past 25 rows the picker shows 15, keeping the selected model visible. */
|
|
37
53
|
export function splitPickerOverflow(models, selected, providerId) {
|
|
@@ -83,10 +99,7 @@ export function stampAuxiliarySelections(aux, catalog) {
|
|
|
83
99
|
/** Python `_BESPOKE_CATALOG_PROVIDERS`: cards whose catalog is resolved by their own rule, never the generic live probe. */
|
|
84
100
|
const BESPOKE_CATALOG_PROVIDERS = new Set(['openai-codex', 'nous', 'xai-oauth', 'lmstudio', 'opencode-go']);
|
|
85
101
|
/** Python `_unqualified_model_id`: strip a picker routing hint (`@provider:`). */
|
|
86
|
-
|
|
87
|
-
const raw = id.trim();
|
|
88
|
-
return raw.startsWith('@') && raw.includes(':') ? raw.slice(raw.indexOf(':') + 1) : raw;
|
|
89
|
-
}
|
|
102
|
+
const unqualifiedModelId = (id) => parseProviderQualifiedModel(id)?.[0] ?? id.trim();
|
|
90
103
|
/** Python `_MODEL_PICKER_OVERFLOW_THRESHOLD` / `_MODEL_PICKER_VISIBLE_TARGET`. */
|
|
91
104
|
const MODEL_PICKER_OVERFLOW_THRESHOLD = 25;
|
|
92
105
|
const MODEL_PICKER_VISIBLE_TARGET = 15;
|
|
@@ -95,7 +108,110 @@ const LIVE_TTL_S = 86_400;
|
|
|
95
108
|
const PROVIDERS_TTL_S = 30;
|
|
96
109
|
const QUOTA_TIMEOUT_MS = 15_000;
|
|
97
110
|
const OPENROUTER_KEY_URL = 'https://openrouter.ai/api/v1/key';
|
|
111
|
+
const PLUGIN_LIST_TIMEOUT_MS = 15_000;
|
|
112
|
+
/** TAL-288: the card text for a plugin provider the Agent does not report ready. */
|
|
113
|
+
const PLUGIN_SETUP_ERRORS = {
|
|
114
|
+
missing_cli: "This provider's CLI was not found on the server. Install it and sign in, then refresh.",
|
|
115
|
+
needs_setup: "This provider is not set up yet. Finish its setup in Hermes, then refresh.",
|
|
116
|
+
not_loaded: 'This provider is installed but could not be loaded. Check it with hermes plugins doctor, then restart Talaria Web.',
|
|
117
|
+
unavailable: "This provider's setup status is unavailable.",
|
|
118
|
+
};
|
|
119
|
+
const PLUGIN_NO_MODELS = 'This provider listed no models.';
|
|
98
120
|
const COST_SNAPSHOT_MAX_DAYS = 365;
|
|
121
|
+
const FIVE_HOUR_WINDOW_S = 18_000;
|
|
122
|
+
const WEEK_WINDOW_S = 604_800;
|
|
123
|
+
/** Swift `rounded()`: half away from zero. */
|
|
124
|
+
function roundTo(value, digits) {
|
|
125
|
+
const scale = 10 ** digits;
|
|
126
|
+
return (Math.sign(value) * Math.round(Math.abs(value) * scale)) / scale;
|
|
127
|
+
}
|
|
128
|
+
const finite = (v) => (typeof v === 'number' && Number.isFinite(v) ? v : null);
|
|
129
|
+
/** ISO-8601 UTC for epoch seconds, without a `.000` fraction. */
|
|
130
|
+
export const isoAt = (seconds) => new Date(seconds * 1000).toISOString().replace('.000Z', 'Z');
|
|
131
|
+
/** ISO-8601 UTC (`…Z`) or null. Accepts the pre-TAL-409 sidecar's `str(datetime)` space form; a zone-less stamp is UTC. */
|
|
132
|
+
export function isoUtc(value) {
|
|
133
|
+
if (typeof value !== 'string' || !value.trim())
|
|
134
|
+
return null;
|
|
135
|
+
let text = value.trim().replace(' ', 'T');
|
|
136
|
+
if (text.includes('T') && !/(Z|[+-]\d{2}:?\d{2})$/i.test(text))
|
|
137
|
+
text += 'Z';
|
|
138
|
+
const ms = Date.parse(text);
|
|
139
|
+
return Number.isFinite(ms) ? isoAt(ms / 1000) : null;
|
|
140
|
+
}
|
|
141
|
+
/** The forecast a pace implies: per-hour or per-day budget until reset, and whether the projection empties the window first. */
|
|
142
|
+
export function quotaForecast(pace, remaining) {
|
|
143
|
+
const daily = pace.minutes_to_reset >= 24 * 60;
|
|
144
|
+
const margin = pace.projected_minutes_to_empty === null ? null : pace.projected_minutes_to_empty - pace.minutes_to_reset;
|
|
145
|
+
return {
|
|
146
|
+
outcome: margin === null || margin >= 0 ? 'safe' : 'warning',
|
|
147
|
+
budget_unit: daily ? 'day' : 'hour',
|
|
148
|
+
budget_percent: remaining !== null && pace.minutes_to_reset > 0 ? remaining / (pace.minutes_to_reset / (daily ? 24 * 60 : 60)) : null,
|
|
149
|
+
depletion_margin_minutes: margin,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* TAL-409: the one normaliser for account-usage windows (Python `_snapshot_windows_payload` plus the pace math the
|
|
154
|
+
* App used to run). Blank labels drop; `reset_at` becomes ISO `Z`; each window gets its duration, pace and forecast
|
|
155
|
+
* as of `nowSeconds`; the indexes name the windows the App shows for pace, session and weekly selections.
|
|
156
|
+
*/
|
|
157
|
+
export function normalizeQuotaWindows(raw, nowSeconds) {
|
|
158
|
+
const nowMs = nowSeconds * 1000;
|
|
159
|
+
const windows = [];
|
|
160
|
+
for (const entry of Array.isArray(raw) ? raw : []) {
|
|
161
|
+
if (!isDict(entry))
|
|
162
|
+
continue;
|
|
163
|
+
const label = str(entry.label).trim();
|
|
164
|
+
if (!label)
|
|
165
|
+
continue;
|
|
166
|
+
const usedRaw = finite(entry.used_percent);
|
|
167
|
+
const remainingRaw = finite(entry.remaining_percent);
|
|
168
|
+
const clamp = (v) => Math.min(Math.max(v, 0), 100);
|
|
169
|
+
const used = usedRaw !== null ? clamp(usedRaw) : remainingRaw !== null ? clamp(100 - remainingRaw) : null;
|
|
170
|
+
const remaining = used === null ? null : 100 - used;
|
|
171
|
+
const resetAt = isoUtc(entry.reset_at);
|
|
172
|
+
const resetMs = resetAt === null ? null : Date.parse(resetAt);
|
|
173
|
+
const minutesToReset = resetMs === null ? null : Math.max(0, roundTo((resetMs - nowMs) / 60_000, 0));
|
|
174
|
+
const lower = label.toLowerCase();
|
|
175
|
+
const providedSeconds = finite(entry.window_seconds);
|
|
176
|
+
const windowSeconds = providedSeconds
|
|
177
|
+
?? (lower.includes('week') ? WEEK_WINDOW_S
|
|
178
|
+
: lower.includes('5h') ? FIVE_HOUR_WINDOW_S
|
|
179
|
+
// A "Session" window is 5h, or the weekly window a provider collapsed into one session bucket.
|
|
180
|
+
: lower.includes('session') && minutesToReset !== null ? (minutesToReset > 5 * 60 ? WEEK_WINDOW_S : FIVE_HOUR_WINDOW_S)
|
|
181
|
+
: null);
|
|
182
|
+
let pace = null;
|
|
183
|
+
if (resetAt !== null && resetMs !== null && minutesToReset !== null && resetMs > nowMs && used !== null && remaining !== null
|
|
184
|
+
&& (windowSeconds === FIVE_HOUR_WINDOW_S || windowSeconds === WEEK_WINDOW_S)) {
|
|
185
|
+
const windowMinutes = windowSeconds / 60;
|
|
186
|
+
const elapsed = Math.max(0, windowMinutes - minutesToReset);
|
|
187
|
+
const expectedRemaining = roundTo(Math.min(Math.max((minutesToReset / windowMinutes) * 100, 0), 100), 1);
|
|
188
|
+
const expectedUsed = roundTo(100 - expectedRemaining, 1);
|
|
189
|
+
const usagePerMinute = elapsed > 0 ? used / elapsed : 0;
|
|
190
|
+
pace = {
|
|
191
|
+
expected_remaining_percent: expectedRemaining,
|
|
192
|
+
pace_delta_percent: roundTo(remaining - expectedRemaining, 1),
|
|
193
|
+
burn_rate: expectedUsed > 0 ? roundTo(used / expectedUsed, 2) : 0,
|
|
194
|
+
minutes_to_reset: minutesToReset,
|
|
195
|
+
projected_minutes_to_empty: usagePerMinute > 0 ? roundTo(remaining / usagePerMinute, 0) : null,
|
|
196
|
+
elapsed_minutes: elapsed,
|
|
197
|
+
valid_until: resetAt,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
windows.push({
|
|
201
|
+
label, used_percent: usedRaw, remaining_percent: remaining, reset_at: resetAt, detail: typeof entry.detail === 'string' ? entry.detail : null,
|
|
202
|
+
window_seconds: windowSeconds, pace, forecast: pace ? quotaForecast(pace, remaining) : null,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const index = (match) => { const i = windows.findIndex(match); return i < 0 ? null : i; };
|
|
206
|
+
const labelHas = (needle) => (w) => w.label.toLowerCase().includes(needle);
|
|
207
|
+
const knownPace = (w) => w.window_seconds === FIVE_HOUR_WINDOW_S || w.window_seconds === WEEK_WINDOW_S || ['week', 'session', '5h'].some((n) => labelHas(n)(w));
|
|
208
|
+
return {
|
|
209
|
+
windows,
|
|
210
|
+
pace_window_index: index(labelHas('week')) ?? index(knownPace),
|
|
211
|
+
session_window_index: index(labelHas('session')) ?? index(labelHas('5h')),
|
|
212
|
+
weekly_window_index: index(labelHas('week')),
|
|
213
|
+
};
|
|
214
|
+
}
|
|
99
215
|
export function displayName(pid) {
|
|
100
216
|
return PROVIDER_DISPLAY[pid] ?? pid.split('-').map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w)).join(' ');
|
|
101
217
|
}
|
|
@@ -115,9 +231,7 @@ export function formatOllamaLabel(mid) {
|
|
|
115
231
|
}
|
|
116
232
|
/** Python `_get_label_for_model`: reuse a known label, else prettify the bare id. */
|
|
117
233
|
export function labelForModel(modelId, groups) {
|
|
118
|
-
|
|
119
|
-
if (lookup.startsWith('@') && lookup.includes(':'))
|
|
120
|
-
lookup = lookup.slice(lookup.indexOf(':') + 1);
|
|
234
|
+
const lookup = unqualifiedModelId(modelId);
|
|
121
235
|
const hasScheme = (s) => s.includes('://');
|
|
122
236
|
const norm = (s) => (s.includes('/') && !hasScheme(s) ? s.slice(s.indexOf('/') + 1) : s).replaceAll('-', '.').toLowerCase();
|
|
123
237
|
const target = norm(lookup);
|
|
@@ -186,12 +300,20 @@ export function deduplicateModelIds(groups) {
|
|
|
186
300
|
m.id = `@${groups[gi].provider_id}:${m.id}`;
|
|
187
301
|
}
|
|
188
302
|
}
|
|
303
|
+
/** Quota sources keep the first row per source id, ordered by provider id, account label, then source id (TAL-272). */
|
|
304
|
+
export function uniqueQuotaSources(sources) {
|
|
305
|
+
const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
306
|
+
return sources.filter((q, i) => sources.findIndex((o) => o.source_id === q.source_id) === i)
|
|
307
|
+
.sort((a, b) => cmp(a.provider_id, b.provider_id) || cmp(a.account_label, b.account_label) || cmp(a.source_id, b.source_id));
|
|
308
|
+
}
|
|
189
309
|
export class ProviderCatalog {
|
|
190
310
|
deps;
|
|
191
311
|
liveFailed = new Set();
|
|
192
312
|
liveIds = new Map();
|
|
193
313
|
liveInflight = new Map();
|
|
194
314
|
providersCache = new Map();
|
|
315
|
+
/** TAL-301: the last catalog served per profile home, for the sync session payloads' `model_option_id`. */
|
|
316
|
+
lastModels = new Map();
|
|
195
317
|
constructor(deps) {
|
|
196
318
|
this.deps = deps;
|
|
197
319
|
}
|
|
@@ -214,6 +336,29 @@ export class ProviderCatalog {
|
|
|
214
336
|
this.liveIds.delete(key);
|
|
215
337
|
}
|
|
216
338
|
this.providersCache.clear();
|
|
339
|
+
// The picker catalog changed: option ids rebuild on the next read rather than pairing against the old one.
|
|
340
|
+
if (profileHome)
|
|
341
|
+
this.lastModels.delete(profileHome);
|
|
342
|
+
else
|
|
343
|
+
this.lastModels.clear();
|
|
344
|
+
}
|
|
345
|
+
/** TAL-301: the entry id a stored `(model, provider)` pair selects in the last catalog built for this home; a cold home starts building one. */
|
|
346
|
+
modelOptionFor(profileHome, model, provider) {
|
|
347
|
+
const catalog = this.lastModels.get(profileHome);
|
|
348
|
+
if (!catalog) {
|
|
349
|
+
void this.warmModelOptions(profileHome);
|
|
350
|
+
return null;
|
|
351
|
+
}
|
|
352
|
+
return catalogOptionId(catalog, model, provider);
|
|
353
|
+
}
|
|
354
|
+
/** Builds the catalog `modelOptionFor` reads once per home; an unavailable catalog leaves every option id null. */
|
|
355
|
+
async warmModelOptions(profileHome) {
|
|
356
|
+
if (this.lastModels.has(profileHome))
|
|
357
|
+
return;
|
|
358
|
+
try {
|
|
359
|
+
await this.models(profileHome);
|
|
360
|
+
}
|
|
361
|
+
catch { /* fail closed: no option ids */ }
|
|
217
362
|
}
|
|
218
363
|
/**
|
|
219
364
|
* The process-environment value of `name` as a named profile may see it: values `loadStartupEnv` copied from the
|
|
@@ -337,6 +482,23 @@ export class ProviderCatalog {
|
|
|
337
482
|
this.liveInflight.set(key, run);
|
|
338
483
|
return run;
|
|
339
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* TAL-288: the model-provider plugins installed and enabled in this profile, as the sidecar reads them from the Agent.
|
|
487
|
+
* A failed lookup lists none, so every other provider stays usable; an id that cannot be a provider routing hint is dropped.
|
|
488
|
+
*/
|
|
489
|
+
async pluginProviders(profileHome) {
|
|
490
|
+
const sidecar = this.deps.sidecar();
|
|
491
|
+
if (!sidecar)
|
|
492
|
+
return [];
|
|
493
|
+
try {
|
|
494
|
+
const { providers } = await sidecar.call('plugins.providers', { profile_home: profileHome }, { timeoutMs: PLUGIN_LIST_TIMEOUT_MS });
|
|
495
|
+
return providers.filter((p, i, all) => /^[a-z0-9][a-z0-9._-]{0,63}$/.test(p.name) && all.findIndex((q) => q.name === p.name) === i);
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
this.deps.log(`[catalog] plugin providers failed: ${str(error.message)}`);
|
|
499
|
+
return [];
|
|
500
|
+
}
|
|
501
|
+
}
|
|
340
502
|
/** Python `get_providers`. */
|
|
341
503
|
async providers(profileHome) {
|
|
342
504
|
const config = await this.deps.config.read(profileHome);
|
|
@@ -347,10 +509,16 @@ export class ProviderCatalog {
|
|
|
347
509
|
return structuredClone(hit.payload);
|
|
348
510
|
const active = activeProviderFromConfig(config);
|
|
349
511
|
const known = new Set([...Object.keys(PROVIDER_DISPLAY), ...Object.keys(PROVIDER_MODELS), ...OAUTH_PROVIDERS]);
|
|
512
|
+
// A plugin never stands in for a built-in provider, by id or alias (that would route to the built-in's billing); it
|
|
513
|
+
// does own a `providers.<id>` config entry.
|
|
514
|
+
const plugins = (await this.pluginProviders(profileHome)).filter((p) => !known.has(p.name) && !known.has(providerIdentity(p.name)));
|
|
515
|
+
const pluginIds = new Set(plugins.map((p) => p.name));
|
|
350
516
|
const providersCfg = dict(config.providers);
|
|
351
517
|
for (const key of Object.keys(providersCfg)) {
|
|
352
518
|
const identity = canonicaliseProviderId(key);
|
|
353
|
-
known.
|
|
519
|
+
const id = known.has(identity) ? identity : key;
|
|
520
|
+
if (!pluginIds.has(id))
|
|
521
|
+
known.add(id);
|
|
354
522
|
}
|
|
355
523
|
const rows = [];
|
|
356
524
|
for (const pid of [...known].sort()) {
|
|
@@ -395,6 +563,17 @@ export class ProviderCatalog {
|
|
|
395
563
|
models_total: modelsTotal,
|
|
396
564
|
});
|
|
397
565
|
}
|
|
566
|
+
// TAL-288: a plugin is selectable only once the Agent reports its setup ready; it never borrows a built-in's key.
|
|
567
|
+
for (const plugin of plugins) {
|
|
568
|
+
const ready = plugin.setup === 'ready';
|
|
569
|
+
const live = ready ? await this.liveModelIds(profileHome, plugin.name) : [];
|
|
570
|
+
rows.push({
|
|
571
|
+
id: plugin.name, display_name: plugin.display_name || plugin.name, has_key: ready,
|
|
572
|
+
configurable: false, is_oauth: false, is_plugin_provider: true, is_self_hosted: false, is_custom: false, key_source: ready ? 'plugin' : 'none',
|
|
573
|
+
base_url: null, auth_error: plugin.setup === 'ready' ? (live.length ? null : PLUGIN_NO_MODELS) : PLUGIN_SETUP_ERRORS[plugin.setup], env_var: null,
|
|
574
|
+
models: live.map((id) => ({ id, label: labelForModel(id, []) })), models_total: live.length,
|
|
575
|
+
});
|
|
576
|
+
}
|
|
398
577
|
for (const cp of customProviderEntries(config)) {
|
|
399
578
|
const name = str(cp.name).trim();
|
|
400
579
|
if (!name)
|
|
@@ -414,6 +593,8 @@ export class ProviderCatalog {
|
|
|
414
593
|
// Python `_provider_sort_key`: active first, then `custom:*`, then keyed providers, then the rest (alphabetical within).
|
|
415
594
|
const rank = (p) => (str(p.id) === active ? 0 : str(p.id).startsWith('custom:') ? 1 : p.has_key ? 2 : 3);
|
|
416
595
|
rows.sort((a, b) => rank(a) - rank(b) || (str(a.id) < str(b.id) ? -1 : str(a.id) > str(b.id) ? 1 : 0));
|
|
596
|
+
for (const row of rows)
|
|
597
|
+
row.models = stampModelEntries(row.models, str(row.id));
|
|
417
598
|
const payload = { providers: rows, active_provider: active };
|
|
418
599
|
this.providersCache.set(profileHome, { at: this.deps.now(), key: cacheKey, payload });
|
|
419
600
|
return structuredClone(payload);
|
|
@@ -462,12 +643,18 @@ export class ProviderCatalog {
|
|
|
462
643
|
if (canonical && this.providerHasKey(canonical, config, envValues, profileHome))
|
|
463
644
|
detected.add(canonical);
|
|
464
645
|
}
|
|
465
|
-
// Python: OAuth providers the Agent reports as logged in join the picker with their live catalog (#1567, #2545)
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
646
|
+
// Python: OAuth providers the Agent reports as logged in join the picker with their live catalog (#1567, #2545); so do
|
|
647
|
+
// ready plugin providers (TAL-288), under their own name.
|
|
648
|
+
const signedIn = new Set();
|
|
649
|
+
const pluginNames = new Map();
|
|
650
|
+
for (const row of (await this.providers(profileHome)).providers) {
|
|
651
|
+
if (row.has_key !== true || (row.is_oauth !== true && row.is_plugin_provider !== true))
|
|
652
|
+
continue;
|
|
653
|
+
signedIn.add(str(row.id));
|
|
654
|
+
if (row.is_plugin_provider === true)
|
|
655
|
+
pluginNames.set(str(row.id), str(row.display_name) || str(row.id));
|
|
656
|
+
}
|
|
657
|
+
for (const pid of signedIn)
|
|
471
658
|
detected.add(pid);
|
|
472
659
|
const fallbackCfg = Array.isArray(config.fallback_providers) ? config.fallback_providers.filter(isDict) : [];
|
|
473
660
|
for (const entry of fallbackCfg) {
|
|
@@ -500,7 +687,7 @@ export class ProviderCatalog {
|
|
|
500
687
|
detected.add(named?.[0] ?? active ?? 'custom');
|
|
501
688
|
}
|
|
502
689
|
const groups = [];
|
|
503
|
-
for (const pid of [...detected].map((p) => (p.startsWith('custom') ? p : canonicaliseProviderId(p) || p)).filter((v, i, a) => v && a.indexOf(v) === i).sort()) {
|
|
690
|
+
for (const pid of [...detected].map((p) => (p.startsWith('custom') || pluginNames.has(p) ? p : canonicaliseProviderId(p) || p)).filter((v, i, a) => v && a.indexOf(v) === i).sort()) {
|
|
504
691
|
if (pid.startsWith('custom:')) {
|
|
505
692
|
const g = namedCustom.get(pid);
|
|
506
693
|
const models = [...(g?.models ?? [])];
|
|
@@ -521,7 +708,7 @@ export class ProviderCatalog {
|
|
|
521
708
|
let raw = [];
|
|
522
709
|
if ('models' in providerCfg && providerCfg.models_discovered !== true)
|
|
523
710
|
raw = configuredModelOptions(providerCfg.models);
|
|
524
|
-
if (!raw.length && (this.providerHasKey(pid, config, envValues, profileHome) ||
|
|
711
|
+
if (!raw.length && (this.providerHasKey(pid, config, envValues, profileHome) || signedIn.has(pid))) {
|
|
525
712
|
const live = await this.liveModelIds(profileHome, pid);
|
|
526
713
|
if (live.length)
|
|
527
714
|
raw = live.map((id) => ({ id, label: pid === 'nous' ? `${formatOllamaLabel(id.includes('/') ? id.slice(id.indexOf('/') + 1) : id)} (via Nous)` : labelForModel(id, []) }));
|
|
@@ -535,7 +722,7 @@ export class ProviderCatalog {
|
|
|
535
722
|
if (!raw.some((m) => m.id === id))
|
|
536
723
|
raw.push({ id, label: labelForModel(id, groups) });
|
|
537
724
|
if (raw.length)
|
|
538
|
-
groups.push({ provider: displayName(pid), provider_id: pid, models: applyProviderPrefix(raw, pid, active) });
|
|
725
|
+
groups.push({ provider: pluginNames.get(pid) ?? displayName(pid), provider_id: pid, models: applyProviderPrefix(raw, pid, active) });
|
|
539
726
|
}
|
|
540
727
|
if (defaultModel) {
|
|
541
728
|
const all = new Set(groups.flatMap((g) => g.models.map((m) => m.id)));
|
|
@@ -581,10 +768,20 @@ export class ProviderCatalog {
|
|
|
581
768
|
for (const [k, v] of Object.entries(rawAliases))
|
|
582
769
|
if (k && v)
|
|
583
770
|
aliases[k.trim()] = str(v).trim();
|
|
771
|
+
const [defaultBare, defaultProvider] = parseProviderQualifiedModel(defaultModel) ?? [defaultModel, active];
|
|
772
|
+
const defaults = { active_provider: active, default_model: defaultModel, default_provider_id: defaultProvider, default_bare_id: defaultBare };
|
|
584
773
|
if (!kept.length && defaultModel) {
|
|
585
|
-
|
|
774
|
+
const providerId = active ?? 'default';
|
|
775
|
+
return this.remember(profileHome, { ...defaults, groups: [{ provider: 'Default', provider_id: providerId, models: stampModelEntries([{ id: defaultModel, label: labelForModel(defaultModel, []) }], providerId) }], aliases: {}, configured_model_badges: {} });
|
|
586
776
|
}
|
|
587
|
-
|
|
777
|
+
const stamped = kept.map((g) => ({ ...g, models: stampModelEntries(g.models, g.provider_id), ...(g.extra_models ? { extra_models: stampModelEntries(g.extra_models, g.provider_id) } : {}) }));
|
|
778
|
+
return this.remember(profileHome, { ...defaults, groups: stamped, aliases, configured_model_badges: this.badges(kept, active, defaultModel, fallbackCfg) });
|
|
779
|
+
}
|
|
780
|
+
/** Stamps the default's entry id and keeps the catalog for `modelOptionFor`. */
|
|
781
|
+
remember(profileHome, catalog) {
|
|
782
|
+
catalog.default_option_id = catalogOptionId(catalog, catalog.default_bare_id ?? null, catalog.default_provider_id ?? null);
|
|
783
|
+
this.lastModels.set(profileHome, catalog);
|
|
784
|
+
return structuredClone(catalog);
|
|
588
785
|
}
|
|
589
786
|
/** Python `_configured_model_badges_from_static_catalog`: which picker rows are the main/fallback selections. */
|
|
590
787
|
badges(groups, active, defaultModel, fallback) {
|
|
@@ -640,7 +837,7 @@ export class ProviderCatalog {
|
|
|
640
837
|
if (provider !== 'nous' && ids.length > MODEL_PICKER_OVERFLOW_THRESHOLD)
|
|
641
838
|
ids = ids.slice(0, MODEL_PICKER_VISIBLE_TARGET);
|
|
642
839
|
const annotateFastTier = isOpenAiFamilyProvider(provider);
|
|
643
|
-
const models = ids.filter(Boolean).map((id) => ({ id, label: labelForModel(id, []), ...(annotateFastTier ? { supports_fast_tier: mainModelSupportsServiceTier(id, provider) } : {}) }));
|
|
840
|
+
const models = stampModelEntries(ids.filter(Boolean).map((id) => ({ id, label: labelForModel(id, []), ...(annotateFastTier ? { supports_fast_tier: mainModelSupportsServiceTier(id, provider) } : {}) })), provider);
|
|
644
841
|
return { provider, source, models, count: models.length };
|
|
645
842
|
}
|
|
646
843
|
/**
|
|
@@ -652,7 +849,7 @@ export class ProviderCatalog {
|
|
|
652
849
|
const config = await this.deps.config.read(profileHome);
|
|
653
850
|
const section = modelSection(config);
|
|
654
851
|
const pid = canonicaliseProviderId(provider ?? section.provider) || null;
|
|
655
|
-
const bare = model
|
|
852
|
+
const bare = unqualifiedModelId(model);
|
|
656
853
|
const positive = (v) => { const n = Math.trunc(Number(v)); return Number.isFinite(n) && n > 0 ? n : null; };
|
|
657
854
|
const fromModels = (models) => {
|
|
658
855
|
if (isDict(models)) {
|
|
@@ -675,7 +872,7 @@ export class ProviderCatalog {
|
|
|
675
872
|
let baseUrl = '';
|
|
676
873
|
let configContextLength = null;
|
|
677
874
|
const sectionModel = str(section.default || (typeof config.model === 'string' ? config.model : '')).trim();
|
|
678
|
-
if (sectionModel && [model, bare].includes(sectionModel
|
|
875
|
+
if (sectionModel && [model, bare].includes(unqualifiedModelId(sectionModel)))
|
|
679
876
|
configContextLength = positive(section.context_length);
|
|
680
877
|
if (!pid || canonicaliseProviderId(section.provider) === pid)
|
|
681
878
|
baseUrl = str(section.base_url).trim();
|
|
@@ -714,10 +911,12 @@ export class ProviderCatalog {
|
|
|
714
911
|
/** Python `get_provider_quota`. */
|
|
715
912
|
accountUsageCache = new Map();
|
|
716
913
|
async quota(profileHome, providerRaw, opts = {}) {
|
|
914
|
+
const at = opts.at ?? this.deps.now();
|
|
915
|
+
const computed_at = isoAt(at);
|
|
717
916
|
const config = await this.deps.config.read(profileHome);
|
|
718
917
|
const provider = (providerRaw ?? activeProviderFromConfig(config) ?? '').trim().toLowerCase();
|
|
719
918
|
if (!provider)
|
|
720
|
-
return { ok: false, provider: null, display_name: null, supported: false, status: 'unavailable', quota: null, message: 'No active provider is configured.' };
|
|
919
|
+
return { computed_at, ok: false, provider: null, display_name: null, supported: false, status: 'unavailable', quota: null, message: 'No active provider is configured.' };
|
|
721
920
|
const name = displayName(provider);
|
|
722
921
|
if (ACCOUNT_USAGE_PROVIDERS.has(provider)) {
|
|
723
922
|
const sidecar = this.deps.sidecar();
|
|
@@ -738,22 +937,25 @@ export class ProviderCatalog {
|
|
|
738
937
|
}
|
|
739
938
|
this.accountUsageCache.set(cacheKey, { at: this.deps.now(), limits });
|
|
740
939
|
}
|
|
940
|
+
// The cache keeps the Agent's raw windows; pace is recomputed as of every response.
|
|
941
|
+
if (limits)
|
|
942
|
+
limits = { ...limits, ...normalizeQuotaWindows(limits.windows, at), fetched_at: isoUtc(limits.fetched_at) };
|
|
741
943
|
if (limits?.available)
|
|
742
|
-
return { ok: true, provider, display_name: name, supported: true, status: limits.stale ? 'stale' : 'available', label: limits.title, quota: null, account_limits: limits, message: limits.stale ? `${name} refresh failed; showing last-known account limits.` : `${name} account limits loaded.` };
|
|
944
|
+
return { computed_at, ok: true, provider, display_name: name, supported: true, status: limits.stale ? 'stale' : 'available', label: limits.title, quota: null, account_limits: limits, message: limits.stale ? `${name} refresh failed; showing last-known account limits.` : `${name} account limits loaded.` };
|
|
743
945
|
const reason = str(limits?.unavailable_reason).trim();
|
|
744
|
-
return { ok: false, provider, display_name: name, supported: true, status: 'unavailable', quota: null, account_limits: limits, message: reason ? `${name} account limits are unavailable. ${reason}` : `${name} account limits are unavailable. Confirm provider authentication and try again.` };
|
|
946
|
+
return { computed_at, ok: false, provider, display_name: name, supported: true, status: 'unavailable', quota: null, account_limits: limits, message: reason ? `${name} account limits are unavailable. ${reason}` : `${name} account limits are unavailable. Confirm provider authentication and try again.` };
|
|
745
947
|
}
|
|
746
948
|
if (provider === 'openrouter') {
|
|
747
949
|
const apiKey = this.apiKeyFor('openrouter', profileHome, config);
|
|
748
950
|
if (!apiKey)
|
|
749
|
-
return { ok: false, provider, display_name: name, supported: true, status: 'no_key', quota: null, message: 'OpenRouter quota status needs an OPENROUTER_API_KEY configured on the server.' };
|
|
951
|
+
return { computed_at, ok: false, provider, display_name: name, supported: true, status: 'no_key', quota: null, message: 'OpenRouter quota status needs an OPENROUTER_API_KEY configured on the server.' };
|
|
750
952
|
const info = await this.fetchOpenRouterKey(apiKey);
|
|
751
953
|
if (info.kind === 'ok')
|
|
752
|
-
return { ok: true, provider, display_name: name, supported: true, status: 'available', label: 'OpenRouter credits', quota: info.quota, message: 'OpenRouter quota status loaded.' };
|
|
954
|
+
return { computed_at, ok: true, provider, display_name: name, supported: true, status: 'available', label: 'OpenRouter credits', quota: info.quota, message: 'OpenRouter quota status loaded.' };
|
|
753
955
|
const status = info.kind === 'invalid_key' ? 'invalid_key' : 'unavailable';
|
|
754
|
-
return { ok: false, provider, display_name: name, supported: true, status, quota: null, message: status === 'invalid_key' ? 'OpenRouter rejected the configured API key.' : 'OpenRouter quota status is temporarily unavailable.' };
|
|
956
|
+
return { computed_at, ok: false, provider, display_name: name, supported: true, status, quota: null, message: status === 'invalid_key' ? 'OpenRouter rejected the configured API key.' : 'OpenRouter quota status is temporarily unavailable.' };
|
|
755
957
|
}
|
|
756
|
-
return { ok: false, provider, display_name: name, supported: false, status: 'unsupported', quota: null, message: `No verified server-side quota or balance endpoint is available for ${name}.` };
|
|
958
|
+
return { computed_at, ok: false, provider, display_name: name, supported: false, status: 'unsupported', quota: null, message: `No verified server-side quota or balance endpoint is available for ${name}.` };
|
|
757
959
|
}
|
|
758
960
|
async fetchOpenRouterKey(apiKey) {
|
|
759
961
|
const f = this.deps.fetch ?? fetch;
|
|
@@ -807,28 +1009,32 @@ export class ProviderCatalog {
|
|
|
807
1009
|
quotaProfileScopeId(profile) {
|
|
808
1010
|
return `qscope_${createHash('sha256').update(`${this.quotaServerScopeId()}\0${profile}`).digest('hex').slice(0, 32)}`;
|
|
809
1011
|
}
|
|
810
|
-
/**
|
|
1012
|
+
/**
|
|
1013
|
+
* Python `get_provider_quotas`: one source per keyed provider, in the stable-identity envelope the iOS widget persists.
|
|
1014
|
+
* Each source id appears once (two custom providers can share a slug), ordered by provider, account label, then source id.
|
|
1015
|
+
*/
|
|
811
1016
|
async quotas(profileHome, profile, opts = {}) {
|
|
1017
|
+
const at = this.deps.now();
|
|
812
1018
|
const status = await this.providers(profileHome);
|
|
813
1019
|
const active = status.active_provider;
|
|
814
1020
|
const scopeId = this.quotaProfileScopeId(profile);
|
|
815
1021
|
// Python `_quota_source_id(profile, provider, "provider")`: the single-credential descriptor per provider.
|
|
816
1022
|
const sourceId = (pid) => `qsrc_${createHash('sha256').update(`${scopeId}\0${pid}\0provider`).digest('hex').slice(0, 32)}`;
|
|
817
|
-
let descriptors = status.providers.filter((p) => p.has_key || p.is_custom).map((p) => ({ source_id: sourceId(str(p.id)), provider_id: str(p.id), provider_label: str(p.display_name) || str(p.id) }));
|
|
1023
|
+
let descriptors = uniqueQuotaSources(status.providers.filter((p) => p.has_key || p.is_custom).map((p) => ({ source_id: sourceId(str(p.id)), provider_id: str(p.id), provider_label: str(p.display_name) || str(p.id), account_label: str(p.display_name) || str(p.id) })));
|
|
818
1024
|
const requested = str(opts.sourceId).trim() || null;
|
|
819
1025
|
if (requested)
|
|
820
1026
|
descriptors = descriptors.filter((d) => d.source_id === requested);
|
|
821
1027
|
const sources = await Promise.all(descriptors.map(async (d) => {
|
|
822
|
-
const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false });
|
|
1028
|
+
const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false, at });
|
|
823
1029
|
const limits = dict(q.account_limits);
|
|
824
1030
|
return {
|
|
825
|
-
source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.
|
|
1031
|
+
source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.account_label,
|
|
826
1032
|
is_active_provider: d.provider_id === active, supported: q.supported === true, status: str(limits.status) || str(q.status) || 'unavailable',
|
|
827
|
-
plan: limits.plan ?? null, windows: limits.windows ?? [], quota: q.quota ?? null, balances: q.balances ?? [], details: limits.details ?? [],
|
|
1033
|
+
plan: limits.plan ?? null, windows: limits.windows ?? [], pace_window_index: limits.pace_window_index ?? null, session_window_index: limits.session_window_index ?? null, weekly_window_index: limits.weekly_window_index ?? null, quota: q.quota ?? null, balances: q.balances ?? [], details: limits.details ?? [],
|
|
828
1034
|
unavailable_reason: limits.unavailable_reason ?? null, retry_after: limits.retry_after ?? null, fetched_at: limits.fetched_at ?? null, message: q.message ?? null,
|
|
829
1035
|
};
|
|
830
1036
|
}));
|
|
831
|
-
return { version: 1, scope_id: scopeId, profile_id: profile, active_provider: active, requested_source_id: requested, missing_source: Boolean(requested && !descriptors.length), sources };
|
|
1037
|
+
return { version: 1, computed_at: isoAt(at), scope_id: scopeId, profile_id: profile, active_provider: active, requested_source_id: requested, missing_source: Boolean(requested && !descriptors.length), sources };
|
|
832
1038
|
}
|
|
833
1039
|
/** Python `get_provider_cost_history` (OpenRouter only; daily snapshots under `<home>/cost-snapshots`). */
|
|
834
1040
|
async costHistory(profileHome, providerRaw, days) {
|