@maudecode/talaria-web 1.15.0 → 1.17.2
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-raw.js +6 -3
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +158 -16
- package/dist/api/automation-router.js +12 -6
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +193 -6
- package/dist/api/chat-router.js +39 -16
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +12 -1
- package/dist/api/git-router.js +60 -38
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +6 -0
- package/dist/api/raw-routes.js +35 -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 +974 -42
- package/dist/api/sessions-router.js +87 -25
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +254 -11
- package/dist/api/settings-router.js +68 -18
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +11 -3
- package/dist/api/sse-routes.js +145 -65
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.js +6 -2
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +86 -9
- package/dist/api/tools-router.js +19 -15
- package/dist/api/tools-router.js.map +1 -1
- package/dist/app.d.ts +0 -8397
- package/dist/app.js +7 -2
- package/dist/app.js.map +1 -1
- package/dist/auth/gate.js +11 -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/config/agent-config.d.ts +2 -0
- package/dist/config/agent-config.js +9 -0
- package/dist/config/agent-config.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 +6 -3
- package/dist/http/context.js +10 -4
- package/dist/http/context.js.map +1 -1
- package/dist/links.d.ts +26 -0
- package/dist/links.js +60 -0
- package/dist/links.js.map +1 -0
- package/dist/onboarding.js +30 -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 +167 -5
- package/dist/providers/catalog.js +662 -135
- 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.d.ts +1 -0
- package/dist/redact.js +518 -15
- package/dist/redact.js.map +1 -1
- package/dist/runtime.js +79 -37
- package/dist/runtime.js.map +1 -1
- package/dist/sessions/anchor.d.ts +24 -3
- package/dist/sessions/anchor.js +94 -44
- 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/claude-code.d.ts +19 -0
- package/dist/sessions/claude-code.js +250 -0
- package/dist/sessions/claude-code.js.map +1 -0
- package/dist/sessions/completions.d.ts +13 -1
- package/dist/sessions/completions.js +64 -12
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +14 -1
- package/dist/sessions/compress.js +52 -11
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/journal.d.ts +8 -0
- package/dist/sessions/journal.js +44 -10
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +13 -5
- package/dist/sessions/list.js +56 -26
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +122 -9
- package/dist/sessions/merge.js +544 -33
- 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 +100 -4
- package/dist/sessions/service.js +567 -102
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +16 -1
- package/dist/sessions/session.js +29 -2
- 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/state-db.d.ts +26 -4
- package/dist/sessions/state-db.js +126 -8
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/store.js +6 -1
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +10 -0
- package/dist/sessions/streams.js +14 -3
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/todo.d.ts +6 -1
- package/dist/sessions/todo.js +11 -4
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +12 -1
- package/dist/sessions/tool-display.js +76 -1
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn.d.ts +49 -10
- package/dist/sessions/turn.js +459 -185
- package/dist/sessions/turn.js.map +1 -1
- package/dist/settings.d.ts +20 -2
- package/dist/settings.js +49 -2
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/fake.d.ts +2 -2
- package/dist/sidecar/fake.js +7 -2
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.js +2 -1
- package/dist/spa.js.map +1 -1
- package/dist/test/harness.js +2 -0
- package/dist/test/harness.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/insights.d.ts +8 -2
- package/dist/tools/insights.js +18 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +41 -1
- package/dist/tools/kanban.js +113 -2
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp.d.ts +2 -1
- package/dist/tools/mcp.js +16 -6
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/terminal.d.ts +1 -0
- package/dist/tools/terminal.js +1 -1
- package/dist/tools/terminal.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 +15 -3
- package/dist/tools/updates.js +207 -41
- package/dist/tools/updates.js.map +1 -1
- package/dist/workspace/fs.d.ts +7 -3
- package/dist/workspace/fs.js +5 -1
- package/dist/workspace/fs.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 +63 -2
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.d.ts +28 -0
- package/dist/workspace/preview.js +110 -0
- package/dist/workspace/preview.js.map +1 -0
- package/dist/workspace/rollback.d.ts +5 -4
- package/dist/workspace/rollback.js +12 -9
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/upload.d.ts +2 -0
- package/dist/workspace/upload.js +4 -1
- package/dist/workspace/upload.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 +4 -2
- package/sidecar/talaria_sidecar/__init__.py +1 -1
- package/sidecar/talaria_sidecar/__main__.py +7 -1
- package/sidecar/talaria_sidecar/home.py +98 -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 +551 -61
- package/sidecar/talaria_sidecar/methods/commands.py +61 -33
- package/sidecar/talaria_sidecar/methods/config.py +14 -10
- package/sidecar/talaria_sidecar/methods/cron.py +10 -6
- package/sidecar/talaria_sidecar/methods/kanban.py +115 -15
- package/sidecar/talaria_sidecar/methods/oauth.py +439 -0
- package/sidecar/talaria_sidecar/methods/process.py +60 -1
- package/sidecar/talaria_sidecar/methods/profiles.py +12 -5
- package/sidecar/talaria_sidecar/methods/providers.py +161 -1
- package/sidecar/talaria_sidecar/methods/runtime.py +5 -0
- package/sidecar/talaria_sidecar/methods/text.py +18 -21
- package/sidecar/talaria_sidecar/methods/usage.py +264 -5
- package/static/dist/FILES.txt +126 -121
- package/static/dist/assets/AppShell-fb2td9zi.js +1 -0
- package/static/dist/assets/ChatPage-cj96kiy9.js +25 -0
- package/static/dist/assets/Dialog-kxcg7ard.js +1 -0
- package/static/dist/assets/ExtensionRoute-jf7h0754.js +1 -0
- package/static/dist/assets/HubRoute-gmanpd5y.js +2 -0
- package/static/dist/assets/{Markdown-dy8t4dy7.js → Markdown-mdwlvj3w.js} +125 -125
- package/static/dist/assets/RightPanel-ftvrjlcp.js +1 -0
- package/static/dist/assets/Select-bilaypej.js +1 -0
- package/static/dist/assets/SessionListPanel-og8n36qs.js +1 -0
- package/static/dist/assets/SettingsLayout-jonjefmj.js +1 -0
- package/static/dist/assets/States-nmejrwrm.js +1 -0
- package/static/dist/assets/Toaster-nnl8zkt9.js +1 -0
- package/static/dist/assets/_app-gdidzd94.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-opyh9y0v.js +1 -0
- package/static/dist/assets/_app.index-hbarzbjx.js +1 -0
- package/static/dist/assets/_app.insights-luywlarz.js +1 -0
- package/static/dist/assets/_app.kanban-kincnbr8.js +1 -0
- package/static/dist/assets/_app.logs-oi2gr3gs.js +1 -0
- package/static/dist/assets/_app.memory-b6jfivt6.js +1 -0
- package/static/dist/assets/_app.profiles-lpvixmqp.js +1 -0
- package/static/dist/assets/{_app.session._sessionId-gyoh5ylx.js → _app.session._sessionId-bfd4h3id.js} +1 -1
- package/static/dist/assets/_app.settings-vowx3iq0.js +1 -0
- package/static/dist/assets/_app.settings._section-cnntg1tu.js +3 -0
- package/static/dist/assets/_app.skills-fb57lpau.js +1 -0
- package/static/dist/assets/_app.tasks-eszpx1q7.js +2 -0
- package/static/dist/assets/_app.todos-nr05ac6w.js +1 -0
- package/static/dist/assets/_app.workspaces-d2luomu6.js +1 -0
- package/static/dist/assets/{abnfDiagram-VCTEODGH-cjqletra.js → abnfDiagram-VCTEODGH-b2x1lqot.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-hd0o7bde.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-h38o5l7b.js +36 -0
- package/static/dist/assets/{back-e4m6nsnm.js → back-bemaer09.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-b195z021.js → blockDiagram-I7D4REHJ-bqquxv2u.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-olu212k1.js → c4Diagram-7LVT6UL2-f3fb8w6z.js} +1 -1
- package/static/dist/assets/cancel-drd0g6d9.js +1 -0
- package/static/dist/assets/channel-cpdgf867.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-b8o1nufs.js → chunk-4HAMMTFA-bzfyyzp3.js} +1 -1
- package/static/dist/assets/{chunk-75Z2AOVW-l1seddto.js → chunk-75Z2AOVW-ly99rfx5.js} +1 -1
- package/static/dist/assets/{chunk-DU6HZSFF-lo5zg15v.js → chunk-DU6HZSFF-bg6ecfqf.js} +3 -3
- package/static/dist/assets/{chunk-F27PBJKO-dsdzrp16.js → chunk-F27PBJKO-e8k7mmqw.js} +1 -1
- package/static/dist/assets/{chunk-GMAD6QVW-f3fxqq8b.js → chunk-GMAD6QVW-cmalnoaj.js} +1 -1
- package/static/dist/assets/{chunk-GVQU2GXP-h8egxtf6.js → chunk-GVQU2GXP-gmif5o01.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-iolwd8s0.js → chunk-IMKFNOWR-f1nwwh6u.js} +1 -1
- package/static/dist/assets/{chunk-L3NEJ4N5-b88bbtwi.js → chunk-L3NEJ4N5-oktfh5v7.js} +1 -1
- package/static/dist/assets/{chunk-OSK3NFVY-e3e7nr6s.js → chunk-OSK3NFVY-b1sdx96z.js} +1 -1
- package/static/dist/assets/{chunk-P2QGCYS3-mdc46rt1.js → chunk-P2QGCYS3-fhu4nzxt.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-kqlllrrm.js → chunk-POPQ4Y6H-em31rnef.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-he585k2x.js → chunk-PWAF6VOD-bxhhyftw.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-gh94y9a4.js → chunk-SHT3W25Y-fmrvwcav.js} +3 -3
- package/static/dist/assets/{chunk-SVP7TREG-euxp9y7e.js → chunk-SVP7TREG-lymukl2j.js} +1 -1
- package/static/dist/assets/{chunk-TICWLB2K-c8cxes1q.js → chunk-TICWLB2K-lmza6k23.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-ko09sguk.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-ko09sguk.js +1 -0
- package/static/dist/assets/{copied-kq0u3ht7.js → copied-ima3pysb.js} +1 -1
- package/static/dist/assets/copy-fw5b1pc6.js +1 -0
- package/static/dist/assets/{cynefin-OW5HDTMX-lxd44ux4.js → cynefin-OW5HDTMX-czlhbyhs.js} +1 -1
- package/static/dist/assets/{cynefinDiagram-5FMLGOSQ-njpiwat8.js → cynefinDiagram-5FMLGOSQ-gq7mvswb.js} +1 -1
- package/static/dist/assets/{dagre-GXQ25YYZ-nvgy6yju.js → dagre-GXQ25YYZ-cqdbf7dr.js} +4 -4
- package/static/dist/assets/{diagram-S7CK7UJ4-g98nyv72.js → diagram-S7CK7UJ4-o6arxa58.js} +3 -3
- package/static/dist/assets/{diagram-UQ7AKVKN-ovgm6lvv.js → diagram-UQ7AKVKN-nhnd68tz.js} +1 -1
- package/static/dist/assets/{diagram-VSXAHHWV-pb2bn2e4.js → diagram-VSXAHHWV-dk2sclzn.js} +1 -1
- package/static/dist/assets/{diagram-VX7I27RA-6j4mvfqo.js → diagram-VX7I27RA-n3k5nyx3.js} +1 -1
- package/static/dist/assets/{diagram-Z3DM3KII-dqmb982r.js → diagram-Z3DM3KII-m6q9cwdh.js} +1 -1
- package/static/dist/assets/{ebnfDiagram-PWID7BFC-ozbpzb6d.js → ebnfDiagram-PWID7BFC-lsa5641p.js} +1 -1
- package/static/dist/assets/endpoints-d33siokx.js +64 -0
- package/static/dist/assets/{erDiagram-RLTQ6QDP-hl7m8vua.js → erDiagram-RLTQ6QDP-fo2ckbe6.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-emctlo64.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-bfi3moqa.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ompixqmh.js → ganttDiagram-EL5Y4UJY-nzqz6x9g.js} +1 -1
- package/static/dist/assets/{gitGraph-4MIJSDKK-b6vxwp9q.js → gitGraph-4MIJSDKK-gukvcxe6.js} +1 -1
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-k98f57on.js → gitGraphDiagram-WWUBYQGX-lqhdp8f0.js} +1 -1
- package/static/dist/assets/{highlighted-body-KPVGNVTW-cd3ff6ge.js → highlighted-body-KPVGNVTW-igw6cp39.js} +1 -1
- package/static/dist/assets/index-cxq58xb1.js +11 -0
- package/static/dist/assets/index-hgtx08n5.css +2 -0
- package/static/dist/assets/{info-A6RAGUB7-hjcwjami.js → info-A6RAGUB7-gvu7a93l.js} +1 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-m8t8g6w1.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-bxak4mwu.js → ishikawaDiagram-5VMMS53U-l07vkbgj.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-khmihclr.js → journeyDiagram-3NMN7TZE-hybkgj8l.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-h1r32wdx.js → kanban-definition-UXKFOSKX-by13uvtn.js} +1 -1
- package/static/dist/assets/{lazyRouteComponent-b2eg0fws.js → lazyRouteComponent-af0lvgjx.js} +1 -1
- package/static/dist/assets/{line-friyjof3.js → line-osxq6fq7.js} +1 -1
- package/static/dist/assets/link-jyxicdhg.js +1 -0
- package/static/dist/assets/login-ec21t39x.js +1 -0
- package/static/dist/assets/mermaid-HWGCJPDP-gfl8z3ep.js +1 -0
- package/static/dist/assets/{mermaid-parser.core-f1gvpwrt.js → mermaid-parser.core-i91oxmou.js} +3 -3
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-mt2oxwue.js → mindmap-definition-YA3MSWOX-m82mx8ww.js} +1 -1
- package/static/dist/assets/onboarding-btnltvyz.js +1 -0
- package/static/dist/assets/onboarding_model_label-dp7xffoh.js +1 -0
- package/static/dist/assets/{packet-AYTQ26CC-jik4ixlf.js → packet-AYTQ26CC-nbtgge2o.js} +1 -1
- package/static/dist/assets/{pegDiagram-XKGWAZYB-erxlv6cc.js → pegDiagram-XKGWAZYB-f1zxzga1.js} +1 -1
- package/static/dist/assets/{pie-WAS4IAKB-mnkoldfl.js → pie-WAS4IAKB-of8vuhxs.js} +1 -1
- package/static/dist/assets/{pieDiagram-E7YTZNPT-i7lwrada.js → pieDiagram-E7YTZNPT-o7y8ftio.js} +3 -3
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-fr8ejdoy.js → quadrantDiagram-AXDQQJYC-iqiwia1t.js} +1 -1
- package/static/dist/assets/{radar-RG4KPBEZ-otarjte2.js → radar-RG4KPBEZ-delnsy2v.js} +1 -1
- package/static/dist/assets/{railroad-74A4TZTK-lw362oto.js → railroad-74A4TZTK-nspnyq9u.js} +1 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nst1qxyn.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-ci37x8qy.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-aimkk07z.js +1 -0
- package/static/dist/assets/{railroadDiagram-O6MQD6OU-esyn75pw.js → railroadDiagram-O6MQD6OU-z41tqx5z.js} +1 -1
- package/static/dist/assets/refresh-b4uams1i.js +1 -0
- package/static/dist/assets/refresh-cw-nt1dtsx4.js +1 -0
- package/static/dist/assets/{requirementDiagram-BXWQKSXE-cyhi5u29.js → requirementDiagram-BXWQKSXE-bdvif194.js} +1 -1
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-jljpbo0h.js → sankeyDiagram-P5KCCOFB-h73e0rol.js} +1 -1
- package/static/dist/assets/{saved-do49si3v.js → saved-kg52csy7.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-fz0dhlu7.js → sequenceDiagram-WJ2MYXX4-iv95mb1z.js} +1 -1
- package/static/dist/assets/{settings_section_extensions_title-b7lojn0o.js → settings_section_extensions_title-ej0iapxe.js} +1 -1
- package/static/dist/assets/share._token-f97e94r0.js +2 -0
- package/static/dist/assets/sliders-horizontal-fj18it47.js +1 -0
- package/static/dist/assets/sse-fwpqtzbg.js +1 -0
- package/static/dist/assets/{stateDiagram-D77RDMKH-kdnithdx.js → stateDiagram-D77RDMKH-k417ixmf.js} +1 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hwn4apct.js +1 -0
- package/static/dist/assets/{swimlanes-42K2YHIH-esd6gmz3.js → swimlanes-42K2YHIH-esola70w.js} +1 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-d1h97oyg.js +8 -0
- package/static/dist/assets/{timeline-definition-24CTP7MA-g0qygccv.js → timeline-definition-24CTP7MA-getrz2y8.js} +1 -1
- package/static/dist/assets/todoStore-eginf9h4.js +1 -0
- package/static/dist/assets/{treeView-Q6P3EWNA-ndchzkuu.js → treeView-Q6P3EWNA-hc7ap1q7.js} +1 -1
- package/static/dist/assets/{treemap-WGGIJYW6-hwjxj0zz.js → treemap-WGGIJYW6-b7k1syy5.js} +1 -1
- package/static/dist/assets/tts-lxdxnmjd.js +1 -0
- package/static/dist/assets/{untitled-mbr6tuhu.js → untitled-ovpfthg4.js} +1 -1
- package/static/dist/assets/useForm-bcblv1rx.js +1 -0
- package/static/dist/assets/useLocale-fi5jb8ma.js +1 -0
- package/static/dist/assets/{useParams-lo9u33d1.js → useParams-lhg566ew.js} +1 -1
- package/static/dist/assets/{useSearch-lq1mr090.js → useSearch-lbrh6ejn.js} +1 -1
- package/static/dist/assets/{vennDiagram-4TSXK5OY-gubovd1r.js → vennDiagram-4TSXK5OY-n6pcy5qb.js} +1 -1
- package/static/dist/assets/{wardley-WFR3VGLG-ir3om65j.js → wardley-WFR3VGLG-lpa18vz1.js} +1 -1
- package/static/dist/assets/{wardleyDiagram-VM6X3IG4-ce0or1ie.js → wardleyDiagram-VM6X3IG4-kpclwwg7.js} +2 -2
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-dexylcjg.js → xychartDiagram-S5SC5T6Z-paa2n8y0.js} +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +2 -2
- 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/index-nqed3oum.js +0 -11
- package/static/dist/assets/infoDiagram-27XIBGKW-fm06rj36.js +0 -2
- package/static/dist/assets/link-m90ahx3n.js +0 -1
- package/static/dist/assets/login-bw7m2fzo.js +0 -1
- package/static/dist/assets/matchContext-f8oy3lo7.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/refresh-cw-k66keho6.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/sliders-horizontal-c4wya4dy.js +0 -1
- 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
|
@@ -11,6 +11,9 @@ from __future__ import annotations
|
|
|
11
11
|
|
|
12
12
|
import dataclasses
|
|
13
13
|
import logging
|
|
14
|
+
import sys
|
|
15
|
+
import threading
|
|
16
|
+
from pathlib import Path
|
|
14
17
|
from typing import Any
|
|
15
18
|
|
|
16
19
|
from ..errors import InvalidParams, RpcError
|
|
@@ -61,19 +64,176 @@ def auth_status(provider_id: str | None) -> dict:
|
|
|
61
64
|
return _plain(status) if isinstance(status, dict) else {"logged_in": bool(status)}
|
|
62
65
|
|
|
63
66
|
|
|
67
|
+
def _module_dir(module: Any) -> Path | None:
|
|
68
|
+
try:
|
|
69
|
+
return Path(module.__file__).resolve().parent
|
|
70
|
+
except Exception: # noqa: BLE001 - namespace/builtin modules have no file
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# The Agent's module-name prefix for provider plugins it imports from a ``$HERMES_HOME`` (``providers._import_plugin_dir``).
|
|
75
|
+
_USER_PLUGIN_MODULE = "_hermes_user_provider_"
|
|
76
|
+
_LOAD_LOCK = threading.Lock()
|
|
77
|
+
_LOAD_ATTEMPTED: set[Path] = set()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _user_plugin_profiles() -> dict[int, tuple[Any, Path]]:
|
|
81
|
+
"""Each registered profile a ``$HERMES_HOME`` provider plugin module holds, with that module's directory."""
|
|
82
|
+
from providers import list_providers
|
|
83
|
+
from providers.base import ProviderProfile
|
|
84
|
+
|
|
85
|
+
registered = {id(p) for p in list_providers()}
|
|
86
|
+
out: dict[int, tuple[Any, Path]] = {}
|
|
87
|
+
for name, module in list(sys.modules.items()):
|
|
88
|
+
directory = _module_dir(module) if module is not None and name.startswith(_USER_PLUGIN_MODULE) else None
|
|
89
|
+
if directory is None:
|
|
90
|
+
continue
|
|
91
|
+
for value in list(vars(module).values()):
|
|
92
|
+
if isinstance(value, ProviderProfile) and id(value) in registered:
|
|
93
|
+
out.setdefault(id(value), (value, directory))
|
|
94
|
+
return out
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _load_plugin(root: Path) -> None:
|
|
98
|
+
"""Load the scoped profile's installed plugin that the process-wide discovery (run under the launch profile) skipped.
|
|
99
|
+
|
|
100
|
+
Uses the Agent's own loader, as ``hermes plugins dev`` does, once per directory. A plugin never displaces a provider
|
|
101
|
+
that is already registered, so one profile's plugin cannot change what another profile's provider id runs.
|
|
102
|
+
"""
|
|
103
|
+
import providers
|
|
104
|
+
|
|
105
|
+
with _LOAD_LOCK:
|
|
106
|
+
if root in _LOAD_ATTEMPTED:
|
|
107
|
+
return
|
|
108
|
+
_LOAD_ATTEMPTED.add(root)
|
|
109
|
+
before = {p.name: p for p in providers.list_providers()}
|
|
110
|
+
try:
|
|
111
|
+
providers._import_plugin_dir(root, "user")
|
|
112
|
+
except Exception: # noqa: BLE001 - a broken plugin reads as not loaded
|
|
113
|
+
log.debug("loading provider plugin %s failed", root, exc_info=True)
|
|
114
|
+
return
|
|
115
|
+
after = {p.name: p for p in providers.list_providers()}
|
|
116
|
+
for name, previous in before.items():
|
|
117
|
+
if after.get(name) is not previous:
|
|
118
|
+
providers.register_provider(previous)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def installed_plugin_profiles() -> list[tuple[str, Any]]:
|
|
122
|
+
"""``(manifest name, profile | None)`` for each enabled model-provider plugin installed in the scoped home.
|
|
123
|
+
|
|
124
|
+
Bundled providers are built-ins, not plugins. The Agent keeps one provider registry per process, so a profile
|
|
125
|
+
belongs to this home only when the plugin module that registered it lives in one of this home's plugin
|
|
126
|
+
directories; a plugin the launch discovery skipped is loaded here. ``None`` means the plugin could not be loaded:
|
|
127
|
+
it failed to import, its provider id is taken by another plugin, or another profile's plugin has the same directory
|
|
128
|
+
name. ponytail: ownership is read from the plugin module's globals, so a plugin that registers an unbound inline
|
|
129
|
+
profile also reads as not loaded; track registrations in the Agent if one ever does.
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
from hermes_cli.plugins_discovery import _get_disabled_plugins, _get_enabled_plugins, collect_directory_manifests, gate_manifest
|
|
133
|
+
except Exception as exc: # noqa: BLE001
|
|
134
|
+
raise RpcError(f"plugin providers unavailable: {exc}", condition="plugins_unavailable") from exc
|
|
135
|
+
disabled, enabled = _get_disabled_plugins(), _get_enabled_plugins()
|
|
136
|
+
roots = {
|
|
137
|
+
Path(m.path).resolve(): str(m.name)
|
|
138
|
+
for m in collect_directory_manifests()
|
|
139
|
+
if m.kind == "model-provider" and m.source == "user" and m.path and gate_manifest(m, disabled, enabled).enabled
|
|
140
|
+
}
|
|
141
|
+
if not roots:
|
|
142
|
+
return []
|
|
143
|
+
|
|
144
|
+
def owned() -> dict[Path, list]:
|
|
145
|
+
found: dict[Path, list] = {root: [] for root in roots}
|
|
146
|
+
for profile, directory in _user_plugin_profiles().values():
|
|
147
|
+
root = next((r for r in roots if directory == r or r in directory.parents), None)
|
|
148
|
+
if root is not None:
|
|
149
|
+
found[root].append(profile)
|
|
150
|
+
return found
|
|
151
|
+
|
|
152
|
+
found = owned()
|
|
153
|
+
missing = [root for root, profiles in found.items() if not profiles]
|
|
154
|
+
if missing:
|
|
155
|
+
for root in missing:
|
|
156
|
+
_load_plugin(root)
|
|
157
|
+
found = owned()
|
|
158
|
+
return [(name, p) for root, name in roots.items() for p in (found[root] or [None])]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _other_profile_plugin(provider_id: str) -> bool:
|
|
162
|
+
"""Whether ``provider_id`` is a plugin provider that another profile installed (the registry is process-wide)."""
|
|
163
|
+
from providers import get_provider_profile
|
|
164
|
+
|
|
165
|
+
profile = get_provider_profile(provider_id)
|
|
166
|
+
if profile is None or id(profile) not in _user_plugin_profiles():
|
|
167
|
+
return False
|
|
168
|
+
return not any(p is profile for _name, p in installed_plugin_profiles())
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def _setup_state(profile: Any) -> str:
|
|
172
|
+
"""The Agent's own setup verdict for a plugin provider, without spawning its CLI or reading its credentials."""
|
|
173
|
+
try:
|
|
174
|
+
status = auth_status(profile.name)
|
|
175
|
+
except RpcError:
|
|
176
|
+
return "unavailable"
|
|
177
|
+
if status.get("error"):
|
|
178
|
+
return "unavailable"
|
|
179
|
+
if profile.auth_type == "external_process":
|
|
180
|
+
return "ready" if status.get("configured") else "missing_cli"
|
|
181
|
+
return "ready" if status.get("logged_in") or status.get("configured") else "needs_setup"
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def plugin_providers() -> list[dict]:
|
|
185
|
+
"""Sanitized rows for this profile's enabled model-provider plugins: identity and setup state, no paths or secrets."""
|
|
186
|
+
rows = []
|
|
187
|
+
for manifest_name, profile in installed_plugin_profiles():
|
|
188
|
+
if profile is None:
|
|
189
|
+
rows.append({"name": manifest_name, "display_name": manifest_name, "auth_type": "", "setup": "not_loaded"})
|
|
190
|
+
continue
|
|
191
|
+
name = str(profile.name).strip().lower()
|
|
192
|
+
rows.append({"name": name, "display_name": str(profile.display_name or name).strip(), "auth_type": str(profile.auth_type), "setup": _setup_state(profile)})
|
|
193
|
+
return rows
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _plugin_catalog(provider_id: str) -> list[str]:
|
|
197
|
+
"""Discovery for an installed plugin the Agent's catalog has no fetcher for: the profile's own listing, then its fallback.
|
|
198
|
+
|
|
199
|
+
API-key plugins already get both from ``provider_model_ids`` with their key, so only keyless auth types list here.
|
|
200
|
+
"""
|
|
201
|
+
for _name, profile in installed_plugin_profiles():
|
|
202
|
+
if profile is None or str(profile.name).strip().lower() != provider_id.strip().lower():
|
|
203
|
+
continue
|
|
204
|
+
try:
|
|
205
|
+
live = profile.fetch_models(timeout=8.0) if profile.supports_model_listing and profile.auth_type != "api_key" else None
|
|
206
|
+
except Exception: # noqa: BLE001 - a broken plugin answers with its fallback catalog
|
|
207
|
+
log.debug("fetch_models(%r) failed", provider_id, exc_info=True)
|
|
208
|
+
live = None
|
|
209
|
+
return [str(m) for m in (live or profile.fallback_models or ()) if m]
|
|
210
|
+
return []
|
|
211
|
+
|
|
212
|
+
|
|
64
213
|
def model_ids(provider_id: str, *, force_refresh: bool) -> list[str]:
|
|
65
214
|
try:
|
|
66
215
|
from hermes_cli.models import provider_model_ids
|
|
67
216
|
except Exception as exc: # noqa: BLE001
|
|
68
217
|
raise RpcError(f"model catalog unavailable: {exc}", condition="providers_unavailable") from exc
|
|
218
|
+
try:
|
|
219
|
+
if _other_profile_plugin(provider_id):
|
|
220
|
+
return []
|
|
221
|
+
except Exception: # noqa: BLE001 - no readable provider registry means no plugin to keep apart
|
|
222
|
+
log.debug("plugin ownership check for %r failed", provider_id, exc_info=True)
|
|
69
223
|
try:
|
|
70
224
|
ids = provider_model_ids(provider_id, force_refresh=force_refresh)
|
|
71
225
|
except TypeError:
|
|
72
226
|
ids = provider_model_ids(provider_id)
|
|
73
227
|
except Exception: # noqa: BLE001
|
|
74
228
|
log.debug("provider_model_ids(%r) failed", provider_id, exc_info=True)
|
|
229
|
+
ids = []
|
|
230
|
+
ids = [str(m) for m in (ids or []) if m]
|
|
231
|
+
if ids:
|
|
232
|
+
return ids
|
|
233
|
+
try:
|
|
234
|
+
return _plugin_catalog(provider_id)
|
|
235
|
+
except RpcError:
|
|
75
236
|
return []
|
|
76
|
-
return [str(m) for m in (ids or []) if m]
|
|
77
237
|
|
|
78
238
|
|
|
79
239
|
def resolve_runtime(*, requested: str | None, api_key: str | None, base_url: str | None, target_model: str | None) -> dict:
|
|
@@ -7,6 +7,7 @@ import re
|
|
|
7
7
|
|
|
8
8
|
from .. import SIDECAR_RPC_VERSION
|
|
9
9
|
from ..errors import InvalidParams, RpcError
|
|
10
|
+
from ..home import edit_launch_env
|
|
10
11
|
from ..rpc import CallContext
|
|
11
12
|
|
|
12
13
|
|
|
@@ -53,6 +54,7 @@ def register(registry) -> None:
|
|
|
53
54
|
os.environ.pop(name, None)
|
|
54
55
|
for name, value in to_set.items():
|
|
55
56
|
os.environ[name] = value
|
|
57
|
+
edit_launch_env(to_set, to_unset)
|
|
56
58
|
# Cached agents bound the credentials they were built with; none of them may outlive a credential change.
|
|
57
59
|
from .chat import evict_all_agents
|
|
58
60
|
|
|
@@ -64,5 +66,8 @@ def register(registry) -> None:
|
|
|
64
66
|
code = params.get("exit_code", 0)
|
|
65
67
|
if not isinstance(code, int):
|
|
66
68
|
raise InvalidParams("exit_code must be an integer")
|
|
69
|
+
from .chat import drain_agents
|
|
70
|
+
|
|
71
|
+
drain_agents()
|
|
67
72
|
ctx.server.request_shutdown(exit_code=code)
|
|
68
73
|
return {"ok": True}
|
|
@@ -1,34 +1,22 @@
|
|
|
1
1
|
"""``text.*``: pure helpers from the Agent that the server must match exactly
|
|
2
|
-
(
|
|
2
|
+
(image input routing, portal tags)."""
|
|
3
3
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
7
|
|
|
8
|
-
from ..errors import InvalidParams
|
|
9
8
|
from ..home import profile_home_param, scoped_home
|
|
10
9
|
from ..rpc import CallContext
|
|
11
10
|
|
|
12
11
|
log = logging.getLogger("talaria_sidecar.text")
|
|
13
12
|
|
|
14
13
|
|
|
15
|
-
def redact(text: str, *, force: bool = True) -> str:
|
|
16
|
-
try:
|
|
17
|
-
from agent.redact import redact_sensitive_text
|
|
18
|
-
except Exception: # noqa: BLE001
|
|
19
|
-
return text
|
|
20
|
-
try:
|
|
21
|
-
return redact_sensitive_text(text, force=force)
|
|
22
|
-
except TypeError:
|
|
23
|
-
return redact_sensitive_text(text)
|
|
24
|
-
|
|
25
|
-
|
|
26
14
|
def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provider: str) -> dict:
|
|
27
15
|
"""``native`` or ``text`` for current-turn image uploads, as the Agent decides it."""
|
|
28
16
|
try:
|
|
29
17
|
from agent.image_routing import decide_image_input_mode
|
|
30
18
|
except Exception: # noqa: BLE001
|
|
31
|
-
return {"mode": "text", "reason": "image routing unavailable"}
|
|
19
|
+
return {"mode": "text", "reason": "image routing unavailable", "supports_vision": None}
|
|
32
20
|
try:
|
|
33
21
|
decision = decide_image_input_mode(provider, model, cfg, requested_provider=requested_provider)
|
|
34
22
|
except TypeError:
|
|
@@ -46,6 +34,18 @@ def image_mode(*, provider: str, model: str, cfg: dict | None, requested_provide
|
|
|
46
34
|
return {"mode": str(decision or "text"), "reason": "", "supports_vision": supports_vision}
|
|
47
35
|
|
|
48
36
|
|
|
37
|
+
def _profile_config() -> dict | None:
|
|
38
|
+
"""The scoped profile's ``config.yaml``; None when the Agent cannot load it."""
|
|
39
|
+
try:
|
|
40
|
+
from hermes_cli.config import load_config
|
|
41
|
+
|
|
42
|
+
cfg = load_config()
|
|
43
|
+
except Exception: # noqa: BLE001
|
|
44
|
+
log.debug("image mode config load failed", exc_info=True)
|
|
45
|
+
return None
|
|
46
|
+
return cfg if isinstance(cfg, dict) else None
|
|
47
|
+
|
|
48
|
+
|
|
49
49
|
def portal_tags() -> dict:
|
|
50
50
|
try:
|
|
51
51
|
from agent.portal_tags import conversation_tag, hermes_client_tag, nous_portal_tags
|
|
@@ -66,17 +66,14 @@ def portal_tags() -> dict:
|
|
|
66
66
|
|
|
67
67
|
|
|
68
68
|
def register(registry) -> None:
|
|
69
|
-
@registry.method("text.redact")
|
|
70
|
-
def redact_(ctx: CallContext, params: dict) -> dict:
|
|
71
|
-
text = params.get("text")
|
|
72
|
-
if not isinstance(text, str):
|
|
73
|
-
raise InvalidParams("text must be a string")
|
|
74
|
-
return {"text": redact(text, force=bool(params.get("force", True)))}
|
|
75
|
-
|
|
76
69
|
@registry.method("text.image_mode")
|
|
77
70
|
def image_mode_(ctx: CallContext, params: dict) -> dict:
|
|
78
71
|
cfg = params.get("cfg") if isinstance(params.get("cfg"), dict) else None
|
|
79
72
|
with scoped_home(profile_home_param(params)):
|
|
73
|
+
# TAL-545: without an explicit cfg the profile's config.yaml decides, so its vision overrides
|
|
74
|
+
# (``agent.image_input_mode``, ``supports_vision``) apply as they do for the Agent's own turn.
|
|
75
|
+
if cfg is None:
|
|
76
|
+
cfg = _profile_config()
|
|
80
77
|
return image_mode(provider=str(params.get("provider") or ""), model=str(params.get("model") or ""), cfg=cfg, requested_provider=str(params.get("requested_provider") or ""))
|
|
81
78
|
|
|
82
79
|
@registry.method("text.portal_tags")
|
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
"""``usage.*``: provider account usage through ``agent.account_usage
|
|
1
|
+
"""``usage.*``: provider account usage through ``agent.account_usage``, the credential pool's local per-entry
|
|
2
|
+
state, and the key-based balance endpoints (TAL-548). Pool keys never leave the sidecar."""
|
|
2
3
|
|
|
3
4
|
from __future__ import annotations
|
|
4
5
|
|
|
5
6
|
import dataclasses
|
|
7
|
+
import json
|
|
6
8
|
import logging
|
|
9
|
+
import math
|
|
10
|
+
import time
|
|
11
|
+
import urllib.error
|
|
12
|
+
import urllib.request
|
|
13
|
+
from datetime import date, datetime, timezone
|
|
7
14
|
from typing import Any
|
|
8
15
|
|
|
9
16
|
from ..errors import InvalidParams, RpcError
|
|
@@ -22,18 +29,246 @@ def _plain(value: Any) -> Any:
|
|
|
22
29
|
return [_plain(v) for v in value]
|
|
23
30
|
if isinstance(value, (str, int, float, bool)) or value is None:
|
|
24
31
|
return value
|
|
32
|
+
if isinstance(value, datetime) and value.tzinfo is not None:
|
|
33
|
+
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
34
|
+
if isinstance(value, date):
|
|
35
|
+
return value.isoformat()
|
|
25
36
|
if hasattr(value, "__dict__"):
|
|
26
37
|
return {k: _plain(v) for k, v in vars(value).items() if not k.startswith("_")}
|
|
27
38
|
return str(value)
|
|
28
39
|
|
|
29
40
|
|
|
30
|
-
def
|
|
41
|
+
def _iso(epoch: float | None) -> str | None:
|
|
42
|
+
return None if epoch is None else datetime.fromtimestamp(epoch, tz=timezone.utc).isoformat().replace("+00:00", "Z")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Python `_is_ambient_gh_cli_entry`: a seeded `gh` CLI or GitHub env token is not an account the user added.
|
|
46
|
+
_AMBIENT_GH_SOURCES = frozenset({"gh_cli", "gh auth token", "env:github_token", "env:gh_token"})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _is_ambient(entry) -> bool:
|
|
50
|
+
return any(str((entry.get(k) if isinstance(entry, dict) else getattr(entry, k, "")) or "").strip().lower() in _AMBIENT_GH_SOURCES for k in ("source", "label", "key_source"))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def pool_providers() -> list[str]:
|
|
54
|
+
"""Providers whose persisted credential pool holds an account the user added; a read of ``auth.json`` with no seeding."""
|
|
55
|
+
try:
|
|
56
|
+
from hermes_cli.auth import read_credential_pool
|
|
57
|
+
except Exception as exc: # noqa: BLE001
|
|
58
|
+
raise RpcError(f"credential pool unavailable: {exc}", condition="usage_unavailable") from exc
|
|
59
|
+
try:
|
|
60
|
+
pools = read_credential_pool()
|
|
61
|
+
except Exception: # noqa: BLE001
|
|
62
|
+
log.debug("read_credential_pool() failed", exc_info=True)
|
|
63
|
+
return []
|
|
64
|
+
return sorted(str(pid) for pid, entries in (pools or {}).items()
|
|
65
|
+
if isinstance(entries, list) and any(isinstance(e, dict) and str(e.get("id") or "").strip() and not _is_ambient(e) for e in entries))
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _pool(provider: str) -> tuple[Any, list]:
|
|
69
|
+
"""The Agent's credential pool for the scoped profile and its non-ambient entries; ``(None, [])`` when unreadable."""
|
|
70
|
+
try:
|
|
71
|
+
from agent.credential_pool import load_pool
|
|
72
|
+
except Exception as exc: # noqa: BLE001
|
|
73
|
+
raise RpcError(f"credential pool unavailable: {exc}", condition="usage_unavailable") from exc
|
|
74
|
+
try:
|
|
75
|
+
pool = load_pool(provider)
|
|
76
|
+
entries = list(pool.entries()) if pool is not None else []
|
|
77
|
+
except Exception: # noqa: BLE001
|
|
78
|
+
log.debug("load_pool(%r) failed", provider, exc_info=True)
|
|
79
|
+
return None, []
|
|
80
|
+
return pool, [e for e in entries if not _is_ambient(e)]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _entry(provider: str, credential_id: str):
|
|
84
|
+
return next((e for e in _pool(provider)[1] if str(getattr(e, "id", "") or "").strip() == credential_id), None)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _entry_key(entry) -> str:
|
|
88
|
+
"""Python `_get_provider_api_key(provider, credential_id)`: the entry's runtime key."""
|
|
89
|
+
for name in ("runtime_api_key", "agent_key", "access_token"):
|
|
90
|
+
try:
|
|
91
|
+
value = str(getattr(entry, name, "") or "").strip()
|
|
92
|
+
except Exception: # noqa: BLE001 - a runtime key property that cannot resolve
|
|
93
|
+
value = ""
|
|
94
|
+
if value:
|
|
95
|
+
return value
|
|
96
|
+
return ""
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _label(entry, index: int) -> str:
|
|
100
|
+
"""Python `_safe_entry_label`."""
|
|
101
|
+
label = " ".join(str(getattr(entry, "label", "") or getattr(entry, "source", "") or f"Credential {index}").split())
|
|
102
|
+
return label[:61].rstrip() + "..." if len(label) > 64 else label
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def pool(provider: str, *, api_key: str | None = None) -> list[dict]:
|
|
106
|
+
"""Python `_local_pool_snapshot` rows: one per pool entry with an id, its local status, whether it holds the
|
|
107
|
+
server-resolved ``api_key``, and no secrets."""
|
|
108
|
+
agent_pool, entries = _pool(provider)
|
|
109
|
+
configured = str(api_key or "").strip()
|
|
110
|
+
try:
|
|
111
|
+
from agent.credential_pool import _exhausted_until
|
|
112
|
+
except Exception: # noqa: BLE001 - an Agent without it cannot date an exhausted mark, so the entry reads as available
|
|
113
|
+
def _exhausted_until(entry, *, sole_credential=False):
|
|
114
|
+
return None
|
|
115
|
+
sole_fn = getattr(agent_pool, "_is_sole_credential", None)
|
|
116
|
+
sole = bool(sole_fn()) if callable(sole_fn) else len(entries) == 1
|
|
117
|
+
rows = []
|
|
118
|
+
for index, entry in enumerate(entries, start=1):
|
|
119
|
+
credential_id = str(getattr(entry, "id", "") or "").strip()
|
|
120
|
+
if not credential_id:
|
|
121
|
+
continue
|
|
122
|
+
row = {"credential_id": credential_id, "label": _label(entry, index), "status": "available", "unavailable_reason": None, "retry_after": None,
|
|
123
|
+
"matches_api_key": bool(configured) and _entry_key(entry) == configured}
|
|
124
|
+
status = str(getattr(entry, "last_status", "") or "").strip().lower()
|
|
125
|
+
until = _exhausted_until(entry, sole_credential=sole) if status == "exhausted" else None
|
|
126
|
+
if status == "dead":
|
|
127
|
+
row.update(status="dead", unavailable_reason="Credential permanently revoked or invalid.")
|
|
128
|
+
elif until is not None and time.time() < until:
|
|
129
|
+
retry_after = _iso(until)
|
|
130
|
+
code = getattr(entry, "last_error_code", None)
|
|
131
|
+
reason = "Credential pool marked this credential exhausted" + (f" after provider status {code}" if code else "") + (f"; retry after {retry_after}" if retry_after else "")
|
|
132
|
+
row.update(status="exhausted", unavailable_reason=reason + ".", retry_after=retry_after)
|
|
133
|
+
rows.append(row)
|
|
134
|
+
return rows
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
_BALANCE_URLS = {
|
|
138
|
+
"openrouter": "https://openrouter.ai/api/v1/key",
|
|
139
|
+
"deepseek": "https://api.deepseek.com/user/balance",
|
|
140
|
+
"opencode-go": "https://opencode.ai/zen/go/v1/usage",
|
|
141
|
+
}
|
|
142
|
+
_BALANCE_TIMEOUT_SECONDS = 15.0
|
|
143
|
+
_BALANCE_MAX_BYTES = 256 * 1024
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _number(value: Any) -> int | float | None:
|
|
147
|
+
"""Python `_quota_number`, finite only: a NaN or infinity is not JSON."""
|
|
148
|
+
if isinstance(value, bool) or value is None:
|
|
149
|
+
return None
|
|
150
|
+
if isinstance(value, int):
|
|
151
|
+
return value
|
|
152
|
+
try:
|
|
153
|
+
number = float(value if isinstance(value, float) else str(value).strip())
|
|
154
|
+
except (TypeError, ValueError):
|
|
155
|
+
return None
|
|
156
|
+
if not math.isfinite(number):
|
|
157
|
+
return None
|
|
158
|
+
return int(number) if number.is_integer() and not isinstance(value, float) else number
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _openrouter(payload: Any) -> dict | None:
|
|
162
|
+
data = payload.get("data") if isinstance(payload, dict) and isinstance(payload.get("data"), dict) else payload
|
|
163
|
+
data = data if isinstance(data, dict) else {}
|
|
164
|
+
return {"quota": {k: _number(data.get(k)) for k in ("limit_remaining", "usage", "limit")}, "label": str(data.get("label") or "").strip() or None}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _deepseek(payload: Any) -> dict | None:
|
|
168
|
+
"""Python `_sanitize_deepseek_balances`: CNY/USD rows with a total, plus the account's `is_available`."""
|
|
169
|
+
if not isinstance(payload, dict) or not isinstance(payload.get("is_available"), bool):
|
|
170
|
+
return None
|
|
171
|
+
balances = []
|
|
172
|
+
for row in payload.get("balance_infos") or []:
|
|
173
|
+
if not isinstance(row, dict):
|
|
174
|
+
continue
|
|
175
|
+
currency = str(row.get("currency") or "").strip().upper()
|
|
176
|
+
total = _number(row.get("total_balance"))
|
|
177
|
+
if currency in {"CNY", "USD"} and total is not None:
|
|
178
|
+
balances.append({"currency": currency, "total": total, "granted": _number(row.get("granted_balance")), "topped_up": _number(row.get("topped_up_balance"))})
|
|
179
|
+
return {"balances": balances, "is_available": payload["is_available"]} if balances else None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _opencode_go(payload: Any) -> dict | None:
|
|
183
|
+
"""Python `_sanitize_opencode_go_account_limits`: all three windows, each well-formed, or nothing."""
|
|
184
|
+
usage = payload.get("usage") if isinstance(payload, dict) else None
|
|
185
|
+
if not isinstance(usage, dict):
|
|
186
|
+
return None
|
|
187
|
+
windows = []
|
|
188
|
+
for key in ("rolling", "weekly", "monthly"):
|
|
189
|
+
window = usage.get(key)
|
|
190
|
+
if not isinstance(window, dict) or window.get("status") not in {"ok", "rate-limited"}:
|
|
191
|
+
return None
|
|
192
|
+
used = _number(window.get("percent"))
|
|
193
|
+
try:
|
|
194
|
+
reset_at = datetime.fromisoformat(str(window.get("resetsAt") or "").strip().replace("Z", "+00:00"))
|
|
195
|
+
except ValueError:
|
|
196
|
+
return None
|
|
197
|
+
if used is None or not 0 <= used <= 100:
|
|
198
|
+
return None
|
|
199
|
+
reset_at = reset_at if reset_at.tzinfo else reset_at.replace(tzinfo=timezone.utc)
|
|
200
|
+
windows.append({"key": key, "used_percent": used, "reset_at": _iso(reset_at.timestamp()), "rate_limited": window["status"] == "rate-limited"})
|
|
201
|
+
return {"windows": windows}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
_BALANCE_PARSERS = {"openrouter": _openrouter, "deepseek": _deepseek, "opencode-go": _opencode_go}
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def balance(provider: str, *, credential_id: str | None, api_key: str | None) -> dict:
|
|
208
|
+
"""One key-based balance read. A pool credential's key resolves here; otherwise the server passes the configured key."""
|
|
209
|
+
result = {"status": "unavailable", "http_status": None, "quota": None, "label": None, "is_available": None, "balances": [], "windows": []}
|
|
210
|
+
key = str(api_key or "").strip()
|
|
211
|
+
if credential_id:
|
|
212
|
+
entry = _entry(provider, credential_id)
|
|
213
|
+
key = _entry_key(entry) if entry is not None else ""
|
|
214
|
+
if not key:
|
|
215
|
+
return {**result, "status": "no_key"}
|
|
216
|
+
request = urllib.request.Request(_BALANCE_URLS[provider], headers={"Authorization": f"Bearer {key}", "Accept": "application/json", "User-Agent": "Talaria-Web/1.0"})
|
|
217
|
+
try:
|
|
218
|
+
with urllib.request.urlopen(request, timeout=_BALANCE_TIMEOUT_SECONDS) as response:
|
|
219
|
+
raw = response.read(_BALANCE_MAX_BYTES + 1)
|
|
220
|
+
if len(raw) > _BALANCE_MAX_BYTES:
|
|
221
|
+
raise ValueError("oversized balance response")
|
|
222
|
+
parsed = _BALANCE_PARSERS[provider](json.loads(raw.decode("utf-8")))
|
|
223
|
+
except urllib.error.HTTPError as exc:
|
|
224
|
+
return {**result, "status": "http_error", "http_status": exc.code}
|
|
225
|
+
except (OSError, ValueError): # URLError and timeouts are OSErrors; malformed JSON or UTF-8 are ValueErrors
|
|
226
|
+
log.debug("%s balance read failed", provider, exc_info=True)
|
|
227
|
+
return result
|
|
228
|
+
return {**result, **parsed, "status": "ok"} if parsed is not None else result
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
_ANTHROPIC_WINDOWS = (("five_hour", "Current session"), ("seven_day", "Current week"), ("seven_day_opus", "Opus week"), ("seven_day_sonnet", "Sonnet week"))
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _anthropic_usage(token: str):
|
|
235
|
+
"""The Agent's Anthropic account-usage read for one given token. The Agent's fetcher reads only the ambient token, so a
|
|
236
|
+
pool account would otherwise report the active account's limits."""
|
|
237
|
+
# ponytail: mirrors agent.account_usage._fetch_anthropic_account_usage on the Agent's own helpers; drop it once that
|
|
238
|
+
# fetcher honours its api_key argument.
|
|
239
|
+
from agent import account_usage as agent_usage
|
|
240
|
+
|
|
241
|
+
if not agent_usage._is_oauth_token(token):
|
|
242
|
+
return agent_usage._snapshot("anthropic", "oauth_usage_api", [], [], unavailable_reason="Anthropic account limits are only available for OAuth-backed Claude accounts.")
|
|
243
|
+
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json",
|
|
244
|
+
"anthropic-beta": "oauth-2025-04-20", "User-Agent": "claude-code/2.1.0"}
|
|
245
|
+
payload = agent_usage._get_json("https://api.anthropic.com/api/oauth/usage", headers, timeout=15.0)
|
|
246
|
+
windows = agent_usage._usage_windows(payload, _ANTHROPIC_WINDOWS, "utilization", "resets_at", fraction=True)
|
|
247
|
+
extra = payload.get("extra_usage") or {}
|
|
248
|
+
used, limit = extra.get("used_credits"), extra.get("monthly_limit")
|
|
249
|
+
details = [f"Extra usage: {used:.2f} / {limit:.2f} {extra.get('currency') or 'USD'}"] if extra.get("is_enabled") and agent_usage._is_num(used) and agent_usage._is_num(limit) else []
|
|
250
|
+
return agent_usage._snapshot("anthropic", "oauth_usage_api", windows, details)
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def account(provider: str, *, base_url: str | None, api_key: str | None, credential_id: str | None = None) -> dict | None:
|
|
254
|
+
if credential_id:
|
|
255
|
+
# One pool account's usage, read with that account's own key.
|
|
256
|
+
entry = _entry(provider, credential_id)
|
|
257
|
+
if entry is None:
|
|
258
|
+
return None
|
|
259
|
+
api_key = _entry_key(entry)
|
|
260
|
+
if not api_key:
|
|
261
|
+
return {"provider": provider, "available": False, "unavailable_reason": "No runtime token available.", "windows": [], "details": []}
|
|
262
|
+
base_url = str(getattr(entry, "runtime_base_url", None) or getattr(entry, "base_url", None) or "").strip() or base_url
|
|
31
263
|
try:
|
|
32
264
|
from agent.account_usage import fetch_account_usage
|
|
33
265
|
except Exception as exc: # noqa: BLE001
|
|
34
266
|
raise RpcError(f"account usage unavailable: {exc}", condition="usage_unavailable") from exc
|
|
35
267
|
try:
|
|
36
|
-
|
|
268
|
+
if credential_id and provider == "anthropic":
|
|
269
|
+
snapshot = _anthropic_usage(api_key)
|
|
270
|
+
else:
|
|
271
|
+
snapshot = fetch_account_usage(provider, base_url=base_url, api_key=api_key)
|
|
37
272
|
except Exception as exc: # noqa: BLE001
|
|
38
273
|
log.debug("fetch_account_usage(%r) failed", provider, exc_info=True)
|
|
39
274
|
return {"provider": provider, "available": False, "unavailable_reason": f"{type(exc).__name__}: {exc}", "windows": [], "details": []}
|
|
@@ -45,7 +280,8 @@ def account(provider: str, *, base_url: str | None, api_key: str | None) -> dict
|
|
|
45
280
|
row.setdefault("provider", provider)
|
|
46
281
|
row["windows"] = [w for w in (row.get("windows") or []) if isinstance(w, dict)]
|
|
47
282
|
row["details"] = list(row.get("details") or [])
|
|
48
|
-
|
|
283
|
+
# The Agent's `available` is a property, which `_plain` (asdict/vars) never sees; read it off the snapshot.
|
|
284
|
+
row["available"] = bool(getattr(snapshot, "available", row.get("available")))
|
|
49
285
|
return row
|
|
50
286
|
|
|
51
287
|
|
|
@@ -55,5 +291,28 @@ def register(registry) -> None:
|
|
|
55
291
|
provider = str(params.get("provider") or "").strip()
|
|
56
292
|
if not provider:
|
|
57
293
|
raise InvalidParams("provider is required")
|
|
294
|
+
credential_id = str(params.get("credential_id") or "").strip() or None
|
|
295
|
+
with scoped_home(profile_home_param(params)):
|
|
296
|
+
return {"snapshot": account(provider, base_url=params.get("base_url"), api_key=params.get("api_key"), credential_id=credential_id)}
|
|
297
|
+
|
|
298
|
+
@registry.method("usage.pool")
|
|
299
|
+
def pool_(ctx: CallContext, params: dict) -> dict:
|
|
300
|
+
provider = str(params.get("provider") or "").strip()
|
|
301
|
+
if not provider:
|
|
302
|
+
raise InvalidParams("provider is required")
|
|
303
|
+
with scoped_home(profile_home_param(params)):
|
|
304
|
+
return {"entries": pool(provider, api_key=params.get("api_key"))}
|
|
305
|
+
|
|
306
|
+
@registry.method("usage.pool_providers")
|
|
307
|
+
def pool_providers_(ctx: CallContext, params: dict) -> dict:
|
|
308
|
+
with scoped_home(profile_home_param(params)):
|
|
309
|
+
return {"providers": pool_providers()}
|
|
310
|
+
|
|
311
|
+
@registry.method("usage.balance")
|
|
312
|
+
def balance_(ctx: CallContext, params: dict) -> dict:
|
|
313
|
+
provider = str(params.get("provider") or "").strip()
|
|
314
|
+
if provider not in _BALANCE_URLS:
|
|
315
|
+
raise InvalidParams(f"provider must be one of {sorted(_BALANCE_URLS)}")
|
|
316
|
+
credential_id = str(params.get("credential_id") or "").strip() or None
|
|
58
317
|
with scoped_home(profile_home_param(params)):
|
|
59
|
-
return
|
|
318
|
+
return balance(provider, credential_id=credential_id, api_key=params.get("api_key"))
|