@maudecode/talaria-web 1.18.0 → 1.20.0
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 +8 -8
- package/dist/api/auth-raw.js +16 -16
- package/dist/api/auth-raw.js.map +1 -1
- package/dist/api/automation-raw.d.ts +2 -2
- package/dist/api/automation-raw.js +93 -38
- package/dist/api/automation-raw.js.map +1 -1
- package/dist/api/automation-router.d.ts +99 -17
- package/dist/api/automation-router.js +22 -14
- package/dist/api/automation-router.js.map +1 -1
- package/dist/api/chat-router.d.ts +1433 -37
- package/dist/api/chat-router.js +205 -90
- package/dist/api/chat-router.js.map +1 -1
- package/dist/api/git-router.d.ts +6 -2
- package/dist/api/git-router.js +30 -30
- package/dist/api/git-router.js.map +1 -1
- package/dist/api/raw-routes.d.ts +3 -3
- package/dist/api/raw-routes.js +76 -62
- package/dist/api/raw-routes.js.map +1 -1
- package/dist/api/router.d.ts +5 -5
- package/dist/api/router.js +22 -22
- package/dist/api/router.js.map +1 -1
- package/dist/api/session-visibility.d.ts +4 -4
- package/dist/api/session-visibility.js +26 -16
- package/dist/api/session-visibility.js.map +1 -1
- package/dist/api/sessions-router.d.ts +2635 -151
- package/dist/api/sessions-router.js +193 -177
- package/dist/api/sessions-router.js.map +1 -1
- package/dist/api/settings-router.d.ts +450 -11
- package/dist/api/settings-router.js +102 -49
- package/dist/api/settings-router.js.map +1 -1
- package/dist/api/sse-routes.d.ts +2 -2
- package/dist/api/sse-routes.js +102 -80
- package/dist/api/sse-routes.js.map +1 -1
- package/dist/api/tools-raw.d.ts +5 -0
- package/dist/api/tools-raw.js +58 -33
- package/dist/api/tools-raw.js.map +1 -1
- package/dist/api/tools-router.d.ts +963 -109
- package/dist/api/tools-router.js +426 -72
- package/dist/api/tools-router.js.map +1 -1
- package/dist/api/transcribe-stream.d.ts +16 -0
- package/dist/api/transcribe-stream.js +172 -0
- package/dist/api/transcribe-stream.js.map +1 -0
- package/dist/app.d.ts +4 -1
- package/dist/app.js +80 -57
- package/dist/app.js.map +1 -1
- package/dist/auth/crypto.d.ts +3 -0
- package/dist/auth/crypto.js +18 -0
- package/dist/auth/crypto.js.map +1 -1
- package/dist/auth/gate.js +14 -9
- package/dist/auth/gate.js.map +1 -1
- package/dist/auth/passkeys.d.ts +15 -14
- package/dist/auth/passkeys.js +89 -91
- package/dist/auth/passkeys.js.map +1 -1
- package/dist/auth/store.d.ts +17 -7
- package/dist/auth/store.js +93 -52
- package/dist/auth/store.js.map +1 -1
- package/dist/bin/talaria-web.js +12 -81
- package/dist/bin/talaria-web.js.map +1 -1
- package/dist/cli/ctl.js +8 -2
- package/dist/cli/ctl.js.map +1 -1
- package/dist/cli/dotenv.d.ts +5 -0
- package/dist/cli/dotenv.js +14 -2
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/cli/launcher.js +5 -5
- package/dist/cli/launcher.js.map +1 -1
- package/dist/cli/main.d.ts +1 -0
- package/dist/cli/main.js +90 -0
- package/dist/cli/main.js.map +1 -0
- package/dist/cli/supervise.d.ts +24 -0
- package/dist/cli/supervise.js +147 -6
- package/dist/cli/supervise.js.map +1 -1
- package/dist/config/agent-config.d.ts +37 -11
- package/dist/config/agent-config.js +146 -64
- package/dist/config/agent-config.js.map +1 -1
- package/dist/config.d.ts +2 -0
- package/dist/config.js +14 -5
- package/dist/config.js.map +1 -1
- package/dist/fs/atomic.d.ts +4 -0
- package/dist/fs/atomic.js +19 -2
- package/dist/fs/atomic.js.map +1 -1
- package/dist/fs/stat.d.ts +7 -0
- package/dist/fs/stat.js +27 -0
- package/dist/fs/stat.js.map +1 -0
- package/dist/http/context.d.ts +28 -9
- package/dist/http/context.js +77 -25
- package/dist/http/context.js.map +1 -1
- package/dist/onboarding.d.ts +2 -2
- package/dist/onboarding.js +10 -8
- package/dist/onboarding.js.map +1 -1
- package/dist/profiles/profiles.d.ts +12 -4
- package/dist/profiles/profiles.js +78 -30
- package/dist/profiles/profiles.js.map +1 -1
- package/dist/projects.d.ts +15 -6
- package/dist/projects.js +55 -47
- package/dist/projects.js.map +1 -1
- package/dist/providers/catalog.d.ts +85 -14
- package/dist/providers/catalog.js +502 -146
- package/dist/providers/catalog.js.map +1 -1
- package/dist/providers/env-file.d.ts +10 -3
- package/dist/providers/env-file.js +24 -75
- package/dist/providers/env-file.js.map +1 -1
- package/dist/providers/runtime-env.d.ts +4 -2
- package/dist/providers/runtime-env.js +9 -6
- package/dist/providers/runtime-env.js.map +1 -1
- package/dist/providers/tables.d.ts +3 -1
- package/dist/providers/tables.js +4 -2
- package/dist/providers/tables.js.map +1 -1
- package/dist/redact.js +56 -23
- package/dist/redact.js.map +1 -1
- package/dist/release.d.ts +7 -0
- package/dist/release.js +19 -3
- package/dist/release.js.map +1 -1
- package/dist/runtime.d.ts +5 -1
- package/dist/runtime.js +303 -96
- package/dist/runtime.js.map +1 -1
- package/dist/server.js +31 -18
- package/dist/server.js.map +1 -1
- package/dist/sessions/anchor.d.ts +11 -4
- package/dist/sessions/anchor.js +33 -37
- package/dist/sessions/anchor.js.map +1 -1
- package/dist/sessions/background-events.d.ts +2 -0
- package/dist/sessions/background-events.js +10 -0
- package/dist/sessions/background-events.js.map +1 -0
- package/dist/sessions/background-tasks.d.ts +49 -23
- package/dist/sessions/background-tasks.js +161 -89
- package/dist/sessions/background-tasks.js.map +1 -1
- package/dist/sessions/background-updates.d.ts +15 -8
- package/dist/sessions/background-updates.js +101 -25
- package/dist/sessions/background-updates.js.map +1 -1
- package/dist/sessions/cli-sessions.d.ts +23 -6
- package/dist/sessions/cli-sessions.js +84 -84
- package/dist/sessions/cli-sessions.js.map +1 -1
- package/dist/sessions/completions.d.ts +38 -2
- package/dist/sessions/completions.js +348 -77
- package/dist/sessions/completions.js.map +1 -1
- package/dist/sessions/compress.d.ts +8 -0
- package/dist/sessions/compress.js +2 -2
- package/dist/sessions/compress.js.map +1 -1
- package/dist/sessions/drafts.d.ts +14 -10
- package/dist/sessions/drafts.js +38 -43
- package/dist/sessions/drafts.js.map +1 -1
- package/dist/sessions/export.d.ts +2 -0
- package/dist/sessions/export.js +6 -2
- package/dist/sessions/export.js.map +1 -1
- package/dist/sessions/gateway-sessions.d.ts +86 -0
- package/dist/sessions/gateway-sessions.js +914 -0
- package/dist/sessions/gateway-sessions.js.map +1 -0
- package/dist/sessions/gateway-watcher.d.ts +10 -11
- package/dist/sessions/gateway-watcher.js +25 -21
- package/dist/sessions/gateway-watcher.js.map +1 -1
- package/dist/sessions/goals.d.ts +31 -0
- package/dist/sessions/goals.js +75 -0
- package/dist/sessions/goals.js.map +1 -0
- package/dist/sessions/journal.d.ts +5 -79
- package/dist/sessions/journal.js +15 -606
- package/dist/sessions/journal.js.map +1 -1
- package/dist/sessions/list.d.ts +34 -18
- package/dist/sessions/list.js +161 -147
- package/dist/sessions/list.js.map +1 -1
- package/dist/sessions/merge.d.ts +77 -13
- package/dist/sessions/merge.js +315 -65
- package/dist/sessions/merge.js.map +1 -1
- package/dist/sessions/notices.d.ts +22 -0
- package/dist/sessions/notices.js +72 -0
- package/dist/sessions/notices.js.map +1 -0
- package/dist/sessions/pending.d.ts +10 -4
- package/dist/sessions/pending.js +83 -7
- package/dist/sessions/pending.js.map +1 -1
- package/dist/sessions/prefix-index.d.ts +85 -0
- package/dist/sessions/prefix-index.js +243 -0
- package/dist/sessions/prefix-index.js.map +1 -0
- package/dist/sessions/recovery.d.ts +4 -7
- package/dist/sessions/recovery.js +100 -283
- package/dist/sessions/recovery.js.map +1 -1
- package/dist/sessions/relay.d.ts +20 -20
- package/dist/sessions/relay.js +205 -127
- package/dist/sessions/relay.js.map +1 -1
- package/dist/sessions/service.d.ts +251 -78
- package/dist/sessions/service.js +1510 -564
- package/dist/sessions/service.js.map +1 -1
- package/dist/sessions/session.d.ts +20 -2
- package/dist/sessions/session.js +33 -5
- package/dist/sessions/session.js.map +1 -1
- package/dist/sessions/shares.d.ts +7 -6
- package/dist/sessions/shares.js +22 -45
- package/dist/sessions/shares.js.map +1 -1
- package/dist/sessions/source-kind.js +1 -0
- package/dist/sessions/source-kind.js.map +1 -1
- package/dist/sessions/state-db-identity.d.ts +18 -0
- package/dist/sessions/state-db-identity.js +67 -0
- package/dist/sessions/state-db-identity.js.map +1 -0
- package/dist/sessions/state-db-worker.js +24 -5
- package/dist/sessions/state-db-worker.js.map +1 -1
- package/dist/sessions/state-db.d.ts +157 -21
- package/dist/sessions/state-db.js +1145 -204
- package/dist/sessions/state-db.js.map +1 -1
- package/dist/sessions/state-saved.d.ts +1 -1
- package/dist/sessions/state-saved.js +35 -11
- package/dist/sessions/state-saved.js.map +1 -1
- package/dist/sessions/storage/backend.d.ts +171 -0
- package/dist/sessions/storage/backend.js +19 -0
- package/dist/sessions/storage/backend.js.map +1 -0
- package/dist/sessions/storage/conformance.d.ts +20 -0
- package/dist/sessions/storage/conformance.js +237 -0
- package/dist/sessions/storage/conformance.js.map +1 -0
- package/dist/sessions/storage/document.d.ts +16 -0
- package/dist/sessions/storage/document.js +44 -0
- package/dist/sessions/storage/document.js.map +1 -0
- package/dist/sessions/storage/json-documents.d.ts +25 -0
- package/dist/sessions/storage/json-documents.js +277 -0
- package/dist/sessions/storage/json-documents.js.map +1 -0
- package/dist/sessions/storage/json-files.d.ts +23 -0
- package/dist/sessions/storage/json-files.js +244 -0
- package/dist/sessions/storage/json-files.js.map +1 -0
- package/dist/sessions/storage/json-installation.d.ts +3 -0
- package/dist/sessions/storage/json-installation.js +31 -0
- package/dist/sessions/storage/json-installation.js.map +1 -0
- package/dist/sessions/storage/json-journal.d.ts +113 -0
- package/dist/sessions/storage/json-journal.js +756 -0
- package/dist/sessions/storage/json-journal.js.map +1 -0
- package/dist/sessions/storage/json-recovery.d.ts +4 -0
- package/dist/sessions/storage/json-recovery.js +195 -0
- package/dist/sessions/storage/json-recovery.js.map +1 -0
- package/dist/sessions/storage/json.d.ts +82 -0
- package/dist/sessions/storage/json.js +350 -0
- package/dist/sessions/storage/json.js.map +1 -0
- package/dist/sessions/storage/row.d.ts +6 -0
- package/dist/sessions/storage/row.js +49 -0
- package/dist/sessions/storage/row.js.map +1 -0
- package/dist/sessions/storage/search-text.d.ts +4 -0
- package/dist/sessions/storage/search-text.js +16 -0
- package/dist/sessions/storage/search-text.js.map +1 -0
- package/dist/sessions/store.d.ts +67 -73
- package/dist/sessions/store.js +341 -607
- package/dist/sessions/store.js.map +1 -1
- package/dist/sessions/streams.d.ts +12 -7
- package/dist/sessions/streams.js +58 -15
- package/dist/sessions/streams.js.map +1 -1
- package/dist/sessions/titles.js +1 -1
- package/dist/sessions/titles.js.map +1 -1
- package/dist/sessions/todo.d.ts +5 -0
- package/dist/sessions/todo.js +28 -21
- package/dist/sessions/todo.js.map +1 -1
- package/dist/sessions/tool-display.d.ts +8 -5
- package/dist/sessions/tool-display.js +28 -16
- package/dist/sessions/tool-display.js.map +1 -1
- package/dist/sessions/turn-context.d.ts +10 -1
- package/dist/sessions/turn-context.js +13 -6
- package/dist/sessions/turn-context.js.map +1 -1
- package/dist/sessions/turn.d.ts +110 -22
- package/dist/sessions/turn.js +918 -380
- package/dist/sessions/turn.js.map +1 -1
- package/dist/sessions/wakeup-pause.d.ts +8 -13
- package/dist/sessions/wakeup-pause.js +31 -49
- package/dist/sessions/wakeup-pause.js.map +1 -1
- package/dist/sessions/window.js +13 -12
- package/dist/sessions/window.js.map +1 -1
- package/dist/settings.d.ts +19 -7
- package/dist/settings.js +179 -183
- package/dist/settings.js.map +1 -1
- package/dist/sidecar/client.d.ts +41 -4
- package/dist/sidecar/client.js +192 -42
- package/dist/sidecar/client.js.map +1 -1
- package/dist/sidecar/discover.d.ts +2 -1
- package/dist/sidecar/discover.js +93 -10
- package/dist/sidecar/discover.js.map +1 -1
- package/dist/sidecar/env.d.ts +4 -0
- package/dist/sidecar/env.js +3 -0
- package/dist/sidecar/env.js.map +1 -0
- package/dist/sidecar/fake.d.ts +6 -2
- package/dist/sidecar/fake.js +34 -4
- package/dist/sidecar/fake.js.map +1 -1
- package/dist/spa.d.ts +9 -4
- package/dist/spa.js +36 -12
- package/dist/spa.js.map +1 -1
- package/dist/startup-timing.d.ts +66 -0
- package/dist/startup-timing.js +129 -0
- package/dist/startup-timing.js.map +1 -0
- package/dist/test/env-file.d.ts +5 -0
- package/dist/test/env-file.js +88 -0
- package/dist/test/env-file.js.map +1 -0
- package/dist/test/fs-handles.d.ts +11 -0
- package/dist/test/fs-handles.js +31 -0
- package/dist/test/fs-handles.js.map +1 -0
- package/dist/test/harness.d.ts +1 -0
- package/dist/test/harness.js +1 -1
- package/dist/test/harness.js.map +1 -1
- package/dist/test/passkeys.d.ts +22 -0
- package/dist/test/passkeys.js +62 -0
- package/dist/test/passkeys.js.map +1 -0
- package/dist/test/storage.d.ts +15 -0
- package/dist/test/storage.js +26 -0
- package/dist/test/storage.js.map +1 -0
- package/dist/tools/commands.d.ts +6 -1
- package/dist/tools/commands.js +8 -2
- package/dist/tools/commands.js.map +1 -1
- package/dist/tools/connectors.d.ts +33 -0
- package/dist/tools/connectors.js +121 -0
- package/dist/tools/connectors.js.map +1 -0
- package/dist/tools/crons.d.ts +46 -5
- package/dist/tools/crons.js +100 -26
- package/dist/tools/crons.js.map +1 -1
- package/dist/tools/dashboard-plugins.d.ts +34 -5
- package/dist/tools/dashboard-plugins.js +146 -14
- package/dist/tools/dashboard-plugins.js.map +1 -1
- package/dist/tools/extensions.d.ts +5 -4
- package/dist/tools/extensions.js +213 -207
- package/dist/tools/extensions.js.map +1 -1
- package/dist/tools/gateway-restart.d.ts +82 -0
- package/dist/tools/gateway-restart.js +222 -0
- package/dist/tools/gateway-restart.js.map +1 -0
- package/dist/tools/health.d.ts +28 -4
- package/dist/tools/health.js +171 -50
- package/dist/tools/health.js.map +1 -1
- package/dist/tools/hygiene.d.ts +4 -3
- package/dist/tools/hygiene.js +23 -9
- package/dist/tools/hygiene.js.map +1 -1
- package/dist/tools/insights.d.ts +1 -1
- package/dist/tools/insights.js +2 -2
- package/dist/tools/insights.js.map +1 -1
- package/dist/tools/kanban.d.ts +4 -2
- package/dist/tools/kanban.js +28 -19
- package/dist/tools/kanban.js.map +1 -1
- package/dist/tools/mcp-health.js +74 -24
- package/dist/tools/mcp-health.js.map +1 -1
- package/dist/tools/mcp.js +19 -1
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/memory-masks.d.ts +5 -0
- package/dist/tools/memory-masks.js +256 -0
- package/dist/tools/memory-masks.js.map +1 -0
- package/dist/tools/memory.d.ts +29 -3
- package/dist/tools/memory.js +69 -27
- package/dist/tools/memory.js.map +1 -1
- package/dist/tools/plugin-oauth.d.ts +14 -0
- package/dist/tools/plugin-oauth.js +53 -0
- package/dist/tools/plugin-oauth.js.map +1 -0
- package/dist/tools/plugin-settings.d.ts +15 -0
- package/dist/tools/plugin-settings.js +90 -0
- package/dist/tools/plugin-settings.js.map +1 -0
- package/dist/tools/prune-advice.d.ts +50 -0
- package/dist/tools/prune-advice.js +114 -0
- package/dist/tools/prune-advice.js.map +1 -0
- package/dist/tools/skills.d.ts +13 -5
- package/dist/tools/skills.js +53 -19
- package/dist/tools/skills.js.map +1 -1
- package/dist/tools/update-notifications.d.ts +102 -17
- package/dist/tools/update-notifications.js +400 -82
- package/dist/tools/update-notifications.js.map +1 -1
- package/dist/tools/update-rollback.d.ts +11 -0
- package/dist/tools/update-rollback.js +28 -0
- package/dist/tools/update-rollback.js.map +1 -0
- package/dist/tools/updates.d.ts +100 -33
- package/dist/tools/updates.js +1085 -345
- package/dist/tools/updates.js.map +1 -1
- package/dist/util.d.ts +2 -0
- package/dist/util.js +5 -0
- package/dist/util.js.map +1 -1
- package/dist/workspace/git.d.ts +16 -1
- package/dist/workspace/git.js +291 -11
- package/dist/workspace/git.js.map +1 -1
- package/dist/workspace/media.d.ts +12 -9
- package/dist/workspace/media.js +98 -38
- package/dist/workspace/media.js.map +1 -1
- package/dist/workspace/preview.js +4 -14
- package/dist/workspace/preview.js.map +1 -1
- package/dist/workspace/rollback.d.ts +8 -4
- package/dist/workspace/rollback.js +22 -12
- package/dist/workspace/rollback.js.map +1 -1
- package/dist/workspace/workspaces.d.ts +15 -19
- package/dist/workspace/workspaces.js +55 -127
- package/dist/workspace/workspaces.js.map +1 -1
- package/dist/workspace/worktrees.js +170 -32
- package/dist/workspace/worktrees.js.map +1 -1
- package/package.json +6 -4
- package/sidecar/agent_dependency.json +4 -4
- package/sidecar/agent_support.json +29 -0
- package/sidecar/scripts/record-fixtures.py +12 -3
- package/sidecar/scripts/replay_sidecar.py +8 -0
- package/sidecar/scripts/suite_runner.py +458 -0
- package/sidecar/scripts/test.sh +11 -5
- package/sidecar/talaria_sidecar/__init__.py +6 -1
- package/sidecar/talaria_sidecar/__main__.py +13 -4
- package/sidecar/talaria_sidecar/agent_mutation.py +1111 -0
- package/sidecar/talaria_sidecar/agent_services.py +1116 -0
- package/sidecar/talaria_sidecar/context_detail.py +59 -0
- package/sidecar/talaria_sidecar/dependency_sync.py +85 -0
- package/sidecar/talaria_sidecar/home.py +133 -14
- package/sidecar/talaria_sidecar/methods/__init__.py +2 -2
- package/sidecar/talaria_sidecar/methods/aux.py +211 -41
- package/sidecar/talaria_sidecar/methods/chat.py +1378 -200
- package/sidecar/talaria_sidecar/methods/checkpoints.py +29 -4
- package/sidecar/talaria_sidecar/methods/commands.py +483 -13
- package/sidecar/talaria_sidecar/methods/config.py +195 -6
- package/sidecar/talaria_sidecar/methods/connectors.py +375 -0
- package/sidecar/talaria_sidecar/methods/cron.py +374 -36
- package/sidecar/talaria_sidecar/methods/cron_execution.py +284 -0
- package/sidecar/talaria_sidecar/methods/dashboard_plugins.py +301 -0
- package/sidecar/talaria_sidecar/methods/gateway.py +215 -55
- package/sidecar/talaria_sidecar/methods/gateway_sessions.py +418 -0
- package/sidecar/talaria_sidecar/methods/goals.py +81 -41
- package/sidecar/talaria_sidecar/methods/kanban.py +82 -30
- package/sidecar/talaria_sidecar/methods/memory.py +84 -0
- package/sidecar/talaria_sidecar/methods/oauth.py +144 -13
- package/sidecar/talaria_sidecar/methods/plugin_catalog.py +198 -0
- package/sidecar/talaria_sidecar/methods/plugin_oauth.py +234 -0
- package/sidecar/talaria_sidecar/methods/process.py +229 -16
- package/sidecar/talaria_sidecar/methods/profiles.py +48 -9
- package/sidecar/talaria_sidecar/methods/providers.py +196 -26
- package/sidecar/talaria_sidecar/methods/runtime.py +71 -1
- package/sidecar/talaria_sidecar/methods/skills.py +83 -5
- package/sidecar/talaria_sidecar/methods/state_db.py +405 -112
- package/sidecar/talaria_sidecar/methods/stt.py +159 -78
- package/sidecar/talaria_sidecar/methods/text.py +14 -1
- package/sidecar/talaria_sidecar/methods/toolsets.py +51 -0
- package/sidecar/talaria_sidecar/methods/usage.py +113 -14
- package/sidecar/talaria_sidecar/methods/worktree.py +145 -19
- package/sidecar/talaria_sidecar/methods/write_approval.py +181 -0
- package/sidecar/talaria_sidecar/rpc.py +70 -7
- package/sidecar/talaria_sidecar/runtime.py +122 -14
- package/sidecar/talaria_sidecar/speed.py +47 -0
- package/sidecar/talaria_sidecar/startup_timing.py +119 -0
- package/static/dist/FILES.txt +242 -215
- package/static/dist/THIRD_PARTY_LICENSES.md +6 -22
- package/static/dist/assets/AppShell-3isgvoro.js +8 -0
- package/static/dist/assets/Button-f4n6dapf.js +1 -0
- package/static/dist/assets/ChatPage-k7b6xfax.js +4 -0
- package/static/dist/assets/ConnectTargets-juqvt5lm.js +1 -0
- package/static/dist/assets/Dialog-hta5utr3.js +1 -0
- package/static/dist/assets/ExtensionRoute-zrhjn0wm.js +1 -0
- package/static/dist/assets/Field-b6ez48oz.js +1 -0
- package/static/dist/assets/HubRoute-iwvso2tz.js +2 -0
- package/static/dist/assets/LinkCheckContext-ix6zqqkk.js +1 -0
- package/static/dist/assets/Markdown-jdeth7zq.js +2 -0
- package/static/dist/assets/MarkdownRenderer-fbswsgld.js +302 -0
- package/static/dist/assets/MarkdownRenderer-i9o6nr38.css +1 -0
- package/static/dist/assets/OAuthSignIn-bpd8hom3.js +1 -0
- package/static/dist/assets/ResourceSwitch-odi6dy2o.js +1 -0
- package/static/dist/assets/RightPanel-eu6enlo6.js +1 -0
- package/static/dist/assets/Select-kr07928w.js +1 -0
- package/static/dist/assets/SessionListPanel-etb2i54l.js +1 -0
- package/static/dist/assets/SettingsLayout-eu3xxp23.js +1 -0
- package/static/dist/assets/States-icab19yg.js +1 -0
- package/static/dist/assets/TerminalPanel-hngmsr8b.js +16 -0
- package/static/dist/assets/Toaster-nb2uzjw1.js +1 -0
- package/static/dist/assets/_app-ga1moszl.js +1 -0
- package/static/dist/assets/_app.ext._extensionId-na62087t.js +1 -0
- package/static/dist/assets/_app.index-jypgdqoi.js +1 -0
- package/static/dist/assets/_app.insights-kabejdtm.js +1 -0
- package/static/dist/assets/_app.kanban-m19kfcmx.js +1 -0
- package/static/dist/assets/_app.logs-iw4m96sz.js +1 -0
- package/static/dist/assets/_app.memory-jq5frwcg.js +1 -0
- package/static/dist/assets/_app.profiles-kwa3m7rp.js +1 -0
- package/static/dist/assets/_app.session._sessionId-o5lyeipj.js +1 -0
- package/static/dist/assets/_app.settings-ckl8hdll.js +1 -0
- package/static/dist/assets/_app.settings._section-dtdjk98a.js +3 -0
- package/static/dist/assets/_app.skills-n016rihd.js +1 -0
- package/static/dist/assets/_app.tasks-n8zuqsqe.js +1 -0
- package/static/dist/assets/_app.todos-btew5v2s.js +1 -0
- package/static/dist/assets/_app.workspaces-lnfk95ch.js +1 -0
- package/static/dist/assets/abnfDiagram-VCTEODGH-dttheegb.js +1 -0
- package/static/dist/assets/{angular-html-nu6l31w3.js → angular-html-l28bd5fo.js} +1 -1
- package/static/dist/assets/{angular-ts-fbx4f8mm.js → angular-ts-ffzx1vj0.js} +1 -1
- package/static/dist/assets/{apl-bx2xr7dh.js → apl-b7h7mmy4.js} +1 -1
- package/static/dist/assets/{arc-7popfhbk.js → arc-fk1b21lm.js} +1 -1
- package/static/dist/assets/architecture-7GRP2DOG-ic435919.js +1 -0
- package/static/dist/assets/architectureDiagram-5GKGNRK7-8r3glgiq.js +36 -0
- package/static/dist/assets/archive-il7c6053.js +1 -0
- package/static/dist/assets/{astro-g77i97cb.js → astro-k117fy0t.js} +1 -1
- package/static/dist/assets/back-gu3nu279.js +1 -0
- package/static/dist/assets/{blade-boo0u92y.js → blade-npefsmn8.js} +1 -1
- package/static/dist/assets/{blockDiagram-I7D4REHJ-jzthp1wa.js → blockDiagram-I7D4REHJ-jenyos6c.js} +4 -4
- package/static/dist/assets/{c-cat0w3sc.js → c-2umg6kbn.js} +1 -1
- package/static/dist/assets/{c4Diagram-7LVT6UL2-mwr67mt8.js → c4Diagram-7LVT6UL2-nzlx7yc4.js} +1 -1
- package/static/dist/assets/cancel-bsnfo36u.js +1 -0
- package/static/dist/assets/channel-dsbtk01y.js +1 -0
- package/static/dist/assets/{chunk-4HAMMTFA-gpq1y228.js → chunk-4HAMMTFA-g0cru63b.js} +20 -20
- package/static/dist/assets/{chunk-75Z2AOVW-ktje58c4.js → chunk-75Z2AOVW-7rkswkjw.js} +1 -1
- package/static/dist/assets/chunk-DU6HZSFF-fn73jzdo.js +128 -0
- package/static/dist/assets/{chunk-F27PBJKO-cmev85ti.js → chunk-F27PBJKO-oey8e72m.js} +1 -1
- package/static/dist/assets/{chunk-FOHPRMQF-opuflp73.js → chunk-FOHPRMQF-ipewusgj.js} +8 -8
- package/static/dist/assets/{chunk-GMAD6QVW-htjgt22y.js → chunk-GMAD6QVW-l2a32wiy.js} +6 -6
- package/static/dist/assets/{chunk-GVQU2GXP-qndvoxl7.js → chunk-GVQU2GXP-o9dwzh8h.js} +1 -1
- package/static/dist/assets/{chunk-IMKFNOWR-e5bkd5q6.js → chunk-IMKFNOWR-eblwj20b.js} +5 -5
- package/static/dist/assets/{chunk-L3NEJ4N5-mj1155r6.js → chunk-L3NEJ4N5-o2mufspb.js} +1 -1
- package/static/dist/assets/chunk-OSK3NFVY-ijp9pw29.js +10 -0
- package/static/dist/assets/{chunk-P2QGCYS3-f7t54xfm.js → chunk-P2QGCYS3-hs9hqwl4.js} +1 -1
- package/static/dist/assets/{chunk-POPQ4Y6H-i927v9oa.js → chunk-POPQ4Y6H-bp9rsqez.js} +1 -1
- package/static/dist/assets/{chunk-PWAF6VOD-m84lmnyt.js → chunk-PWAF6VOD-brkprov6.js} +1 -1
- package/static/dist/assets/{chunk-SHT3W25Y-mi0tlcu9.js → chunk-SHT3W25Y-gmpv0pxz.js} +13 -13
- package/static/dist/assets/{chunk-SVP7TREG-dnau702j.js → chunk-SVP7TREG-oldm08fu.js} +4 -4
- package/static/dist/assets/{chunk-TICWLB2K-edem1gkg.js → chunk-TICWLB2K-ooqilfv7.js} +6 -6
- package/static/dist/assets/{chunk-XXDRQBXY-n40pf9vy.js → chunk-XXDRQBXY-jkmnqu65.js} +1 -1
- package/static/dist/assets/classDiagram-ZZMXUADV-fvgcv24g.js +1 -0
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-fvgcv24g.js +1 -0
- package/static/dist/assets/{cobol-go622aep.js → cobol-ssf9hkh7.js} +1 -1
- package/static/dist/assets/{coffee-jwo1xjhq.js → coffee-lsi9fwae.js} +1 -1
- package/static/dist/assets/copied-bz3gkazh.js +1 -0
- package/static/dist/assets/copy-jo006coq.js +1 -0
- package/static/dist/assets/cose-bilkent-JH36ORCC-layy2r27.js +1 -0
- package/static/dist/assets/{cpp-nre7j0kq.js → cpp-lfrh4y55.js} +1 -1
- package/static/dist/assets/{crystal-lviso2je.js → crystal-efh4pte7.js} +1 -1
- package/static/dist/assets/{css-ywz9j4hb.js → css-i82qdo89.js} +1 -1
- package/static/dist/assets/cynefin-OW5HDTMX-d47op7qs.js +1 -0
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-doa7594y.js +62 -0
- package/static/dist/assets/cytoscape.esm-fjn2algp.js +321 -0
- package/static/dist/assets/dagre-GXQ25YYZ-os768dp4.js +4 -0
- package/static/dist/assets/dagre-o5jmjz46.js +1 -0
- package/static/dist/assets/{diagram-S7CK7UJ4-l8abxaes.js → diagram-S7CK7UJ4-fyi81nxd.js} +4 -4
- package/static/dist/assets/diagram-UQ7AKVKN-j3exggaq.js +41 -0
- package/static/dist/assets/{diagram-VSXAHHWV-fnky6lvc.js → diagram-VSXAHHWV-omo1en2l.js} +1 -1
- package/static/dist/assets/diagram-VX7I27RA-oro2cyu0.js +24 -0
- package/static/dist/assets/diagram-Z3DM3KII-byt9vdb7.js +24 -0
- package/static/dist/assets/dist-crc46di9.js +7 -0
- package/static/dist/assets/dist-etpoyscq.js +44 -0
- package/static/dist/assets/{dist-c4c2m3kv.js → dist-nb670eu6.js} +1 -1
- package/static/dist/assets/ebnfDiagram-PWID7BFC-ee3haugh.js +1 -0
- package/static/dist/assets/{edge-dvv35byi.js → edge-lrob635n.js} +1 -1
- package/static/dist/assets/{elixir-i2f1ki1g.js → elixir-kdpxeq93.js} +1 -1
- package/static/dist/assets/{elm-cpepyjm4.js → elm-c8irj9uh.js} +1 -1
- package/static/dist/assets/erDiagram-RLTQ6QDP-d1e93yff.js +99 -0
- package/static/dist/assets/{erb-idlyfheg.js → erb-flwsv146.js} +1 -1
- package/static/dist/assets/eventmodeling-NTZA5JFV-i1ihb40c.js +1 -0
- package/static/dist/assets/flowDiagram-HODETNUW-jahfpiod.js +1 -0
- package/static/dist/assets/folder-plus-ivoglq98.js +1 -0
- package/static/dist/assets/{ganttDiagram-EL5Y4UJY-ngbsyp37.js → ganttDiagram-EL5Y4UJY-m7g0jud4.js} +1 -1
- package/static/dist/assets/{git-rebase-d9lpfpbn.js → git-rebase-bli19aoq.js} +1 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-hzdj07yy.js +1 -0
- package/static/dist/assets/{gitGraphDiagram-WWUBYQGX-eravdti4.js → gitGraphDiagram-WWUBYQGX-dm9agbna.js} +1 -1
- package/static/dist/assets/{glimmer-js-39i0urfb.js → glimmer-js-d4icg38p.js} +1 -1
- package/static/dist/assets/{glimmer-ts-gdtd6n73.js → glimmer-ts-bk75772u.js} +1 -1
- package/static/dist/assets/{glsl-o1djm5pp.js → glsl-lqormp2b.js} +1 -1
- package/static/dist/assets/{graphql-kj6rz55t.js → graphql-hw6s5qzd.js} +1 -1
- package/static/dist/assets/{hack-pe1qqr2i.js → hack-ozrv6l4d.js} +1 -1
- package/static/dist/assets/{haml-cknnbcqs.js → haml-gytfosdt.js} +1 -1
- package/static/dist/assets/{handlebars-hwq64orh.js → handlebars-b6g7jffd.js} +1 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-mmgz2czu.js +1 -0
- package/static/dist/assets/host-fr6lni1p.js +1 -0
- package/static/dist/assets/{html-derivative-h2i2pyjc.js → html-derivative-f1tqcroo.js} +1 -1
- package/static/dist/assets/{html-ha05xtvt.js → html-on6oj6yj.js} +1 -1
- package/static/dist/assets/{http-cf94zu27.js → http-odhufsl7.js} +1 -1
- package/static/dist/assets/{hurl-lslax568.js → hurl-mu680336.js} +1 -1
- package/static/dist/assets/index-73649w7o.js +10 -0
- package/static/dist/assets/index-lhf1fsdf.css +2 -0
- package/static/dist/assets/info-A6RAGUB7-lll1dqmm.js +1 -0
- package/static/dist/assets/infoDiagram-27XIBGKW-k7esc639.js +2 -0
- package/static/dist/assets/{ishikawaDiagram-5VMMS53U-i6ypq351.js → ishikawaDiagram-5VMMS53U-lgr6k9wo.js} +6 -6
- package/static/dist/assets/{java-ndjf2o89.js → java-nxbe8uju.js} +1 -1
- package/static/dist/assets/{javascript-coftlwy3.js → javascript-lz3kxcyz.js} +1 -1
- package/static/dist/assets/{jinja-etei9wy7.js → jinja-l5pk84gt.js} +1 -1
- package/static/dist/assets/{jison-hyekgft1.js → jison-jdutdwyy.js} +1 -1
- package/static/dist/assets/{journeyDiagram-3NMN7TZE-lduf1z95.js → journeyDiagram-3NMN7TZE-ndmbxuqk.js} +1 -1
- package/static/dist/assets/{json-lok0yaho.js → json-dnge4ssf.js} +1 -1
- package/static/dist/assets/{jsx-b64w0nbx.js → jsx-c3lwhkep.js} +1 -1
- package/static/dist/assets/{jsx-runtime-eu92a5pu.js → jsx-runtime-o40ukmjl.js} +1 -1
- package/static/dist/assets/{julia-jxxzz2yn.js → julia-hwd4rrcb.js} +1 -1
- package/static/dist/assets/{just-mc29iw9t.js → just-nka3oxdx.js} +1 -1
- package/static/dist/assets/{kanban-definition-UXKFOSKX-e18bu4pg.js → kanban-definition-UXKFOSKX-mjxym0jp.js} +28 -28
- package/static/dist/assets/katex-cbdd1ohw.css +1 -0
- package/static/dist/assets/katex-pbij2oo6.js +257 -0
- package/static/dist/assets/{latex-pafa03ft.js → latex-iqmr7e40.js} +1 -1
- package/static/dist/assets/lazyRouteComponent-cwb6l7xh.js +1 -0
- package/static/dist/assets/{line-meizqsvn.js → line-mtr5k6il.js} +1 -1
- package/static/dist/assets/linear-jlwrvvy6.js +1 -0
- package/static/dist/assets/link-iadw4ghw.js +2 -0
- package/static/dist/assets/{liquid-hz5c2pov.js → liquid-okmk9tbp.js} +1 -1
- package/static/dist/assets/locale-cs-n41hptse.js +5 -0
- package/static/dist/assets/locale-de-k7ht47ew.js +5 -0
- package/static/dist/assets/locale-es-ih078lco.js +5 -0
- package/static/dist/assets/locale-fr-ojabnf41.js +5 -0
- package/static/dist/assets/locale-it-kb96jufy.js +5 -0
- package/static/dist/assets/locale-ja-jpsp3xs1.js +7 -0
- package/static/dist/assets/locale-ko-cpzuy0n7.js +5 -0
- package/static/dist/assets/locale-pl-dbap4leu.js +5 -0
- package/static/dist/assets/locale-pt-nci7w72x.js +5 -0
- package/static/dist/assets/locale-ru-soghe4cs.js +7 -0
- package/static/dist/assets/locale-tr-ks00mu68.js +5 -0
- package/static/dist/assets/locale-vi-ij7twfm6.js +5 -0
- package/static/dist/assets/locale-zh-Hant-feakwr7a.js +5 -0
- package/static/dist/assets/locale-zh-ren0vjyv.js +7 -0
- package/static/dist/assets/login-lkhtzfcl.js +1 -0
- package/static/dist/assets/{lua-leyf8mlb.js → lua-krx4xj5h.js} +1 -1
- package/static/dist/assets/{marko-cn1fzpdv.js → marko-gbvpfehz.js} +1 -1
- package/static/dist/assets/{mdc-huhy88df.js → mdc-c21zlyi5.js} +1 -1
- package/static/dist/assets/mermaid-HWGCJPDP-jx0q5dm2.js +1 -0
- package/static/dist/assets/mermaid-parser.core-k91vdnvi.js +7 -0
- package/static/dist/assets/micromark-factory-space-fnmnpi6s.js +1 -0
- package/static/dist/assets/{mindmap-definition-YA3MSWOX-27b26eok.js → mindmap-definition-YA3MSWOX-ehztfea4.js} +28 -28
- package/static/dist/assets/{nginx-e7lvhsmn.js → nginx-f3d68ed4.js} +1 -1
- package/static/dist/assets/{nim-dun5d8mk.js → nim-dw0jcy70.js} +1 -1
- package/static/dist/assets/onboarding-f67z0a2r.js +1 -0
- package/static/dist/assets/packet-AYTQ26CC-ok2xrblz.js +1 -0
- package/static/dist/assets/pcmTap.worklet-74EKjWAZ.js +1 -0
- package/static/dist/assets/pegDiagram-XKGWAZYB-ec3rpj0m.js +1 -0
- package/static/dist/assets/{perl-bj3oqtet.js → perl-3c0r45c8.js} +1 -1
- package/static/dist/assets/{php-buzp12r0.js → php-rznhidvp.js} +1 -1
- package/static/dist/assets/pie-WAS4IAKB-iwkir23u.js +1 -0
- package/static/dist/assets/pieDiagram-E7YTZNPT-fcpqpekh.js +39 -0
- package/static/dist/assets/{pug-mjad7he9.js → pug-f2le4f3y.js} +1 -1
- package/static/dist/assets/{qml-d1gdp10s.js → qml-e2wbq9cr.js} +1 -1
- package/static/dist/assets/{quadrantDiagram-AXDQQJYC-hhue7d8o.js → quadrantDiagram-AXDQQJYC-lqsync43.js} +1 -1
- package/static/dist/assets/queryKeys-ed4n2egc.js +9 -0
- package/static/dist/assets/{r-o3r9c7ao.js → r-epfzuw6q.js} +1 -1
- package/static/dist/assets/radar-RG4KPBEZ-hje4jbk6.js +1 -0
- package/static/dist/assets/railroad-74A4TZTK-gll9n6qi.js +1 -0
- package/static/dist/assets/railroad-abnf-HS5TGJTU-o6h6lpn4.js +1 -0
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-juaefj6o.js +1 -0
- package/static/dist/assets/railroad-peg-WCYAUIDC-cgfhvpah.js +1 -0
- package/static/dist/assets/railroadDiagram-O6MQD6OU-lp3w7awf.js +1 -0
- package/static/dist/assets/{razor-ilc585yr.js → razor-pea1u1d0.js} +1 -1
- package/static/dist/assets/refresh-cw-iccm7a4e.js +1 -0
- package/static/dist/assets/refresh-i9ya9dvl.js +1 -0
- package/static/dist/assets/{regexp-mpjtcvwm.js → regexp-hzte0a5x.js} +1 -1
- package/static/dist/assets/registry-g7w3353u.js +1 -0
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ha8eibcf.js +84 -0
- package/static/dist/assets/rolldown-runtime-bnnw9q4a.js +1 -0
- package/static/dist/assets/{rst-jrkwjdyi.js → rst-m6tugj5o.js} +1 -1
- package/static/dist/assets/{ruby-n4tjcbpz.js → ruby-l19vq3w1.js} +1 -1
- package/static/dist/assets/runtime-nlxpnegr.js +1 -0
- package/static/dist/assets/{sankeyDiagram-P5KCCOFB-fqb8xcjf.js → sankeyDiagram-P5KCCOFB-oxf31pr9.js} +1 -1
- package/static/dist/assets/{sas-hjo6y93k.js → sas-ktdxo397.js} +1 -1
- package/static/dist/assets/{scss-ncidjv48.js → scss-kn3oxfwf.js} +1 -1
- package/static/dist/assets/{sequenceDiagram-WJ2MYXX4-dlcz6qjz.js → sequenceDiagram-WJ2MYXX4-jiq8lhyo.js} +11 -11
- package/static/dist/assets/settings_section_extensions_title-fadc7o65.js +1 -0
- package/static/dist/assets/share._token-o76ki0ly.js +2 -0
- package/static/dist/assets/{shellscript-m8whjpqd.js → shellscript-l100jtrg.js} +1 -1
- package/static/dist/assets/{shellsession-i5bj6syz.js → shellsession-m5qukwow.js} +1 -1
- package/static/dist/assets/sizeCapture-INFHLROL-dk1io13w.js +1 -0
- package/static/dist/assets/sliders-horizontal-ddcs88av.js +1 -0
- package/static/dist/assets/{soy-hhh36ysq.js → soy-mexlscur.js} +1 -1
- package/static/dist/assets/{sql-j21bzcl6.js → sql-o0ho5suh.js} +1 -1
- package/static/dist/assets/src-h89s0hs4.js +1 -0
- package/static/dist/assets/{stata-bbu2onh1.js → stata-be9ck1q3.js} +1 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-f3fslnon.js +1 -0
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-hlt6yphw.js +1 -0
- package/static/dist/assets/{surrealql-l5vczuco.js → surrealql-gk1xkkyy.js} +1 -1
- package/static/dist/assets/{svelte-idn422g6.js → svelte-k6ecjilf.js} +1 -1
- package/static/dist/assets/swimlanes-42K2YHIH-k1hmk7jf.js +1 -0
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-hd5t9tnt.js +8 -0
- package/static/dist/assets/{templ-e22qou3c.js → templ-gf3bftk6.js} +1 -1
- package/static/dist/assets/{tex-deqpc5ee.js → tex-d21fqv1z.js} +1 -1
- package/static/dist/assets/{timeline-definition-24CTP7MA-b1vl2zil.js → timeline-definition-24CTP7MA-n3ncfy9r.js} +8 -8
- package/static/dist/assets/toast-liparz2j.js +56 -0
- package/static/dist/assets/treeView-Q6P3EWNA-l3y2pejt.js +1 -0
- package/static/dist/assets/treemap-WGGIJYW6-jown9axh.js +1 -0
- package/static/dist/assets/{ts-tags-ctnibc27.js → ts-tags-n0g61gr6.js} +1 -1
- package/static/dist/assets/{tsx-i163n32g.js → tsx-cvv7wv6q.js} +1 -1
- package/static/dist/assets/{twig-x0fi7wm8.js → twig-n1f67204.js} +1 -1
- package/static/dist/assets/{typescript-g18c34xz.js → typescript-og7o55cx.js} +1 -1
- package/static/dist/assets/untitled-mj86r4gz.js +1 -0
- package/static/dist/assets/useDebounced-g8r0ggve.js +1 -0
- package/static/dist/assets/useForm-o4uu74mx.js +1 -0
- package/static/dist/assets/{useParams-b4dq6n4q.js → useParams-ftsveu84.js} +1 -1
- package/static/dist/assets/{useSearch-ipfmv3he.js → useSearch-crqueqga.js} +1 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-dh59uq1c.js +34 -0
- package/static/dist/assets/{vue-mwu5l1bb.js → vue-ej4xpwqw.js} +1 -1
- package/static/dist/assets/{vue-html-nskj3du0.js → vue-html-d48xsjuo.js} +1 -1
- package/static/dist/assets/{vue-vine-cqe77smq.js → vue-vine-pev88ip6.js} +1 -1
- package/static/dist/assets/wardley-WFR3VGLG-e6d74b0j.js +1 -0
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hnofh78f.js +78 -0
- package/static/dist/assets/with-selector-nu1b5z0v.js +1 -0
- package/static/dist/assets/{xml-gau7gvrv.js → xml-ifxp6gqb.js} +1 -1
- package/static/dist/assets/{xsl-ew09zq8b.js → xsl-fug2cckc.js} +1 -1
- package/static/dist/assets/{xychartDiagram-S5SC5T6Z-e49523ab.js → xychartDiagram-S5SC5T6Z-bpymjelb.js} +6 -6
- package/static/dist/assets/{yaml-ofnrxtvf.js → yaml-fjmafnlo.js} +1 -1
- package/static/dist/extension-sdk.js +1 -1
- package/static/dist/index.html +1 -1
- package/static/dist/sw.js +180 -136
- package/static/dist/assets/AppShell-g3bkgnvt.js +0 -1
- package/static/dist/assets/ChatPage-oo7bdan6.js +0 -25
- package/static/dist/assets/Dialog-g355qxhp.js +0 -1
- package/static/dist/assets/ExtensionRoute-i2y9fvjm.js +0 -1
- package/static/dist/assets/HubRoute-lmkrbw47.js +0 -2
- package/static/dist/assets/Markdown-j4kut4j2.js +0 -598
- package/static/dist/assets/Markdown-lwviulmg.css +0 -1
- package/static/dist/assets/RightPanel-ohlsdzfl.js +0 -1
- package/static/dist/assets/Select-kddq75qc.js +0 -1
- package/static/dist/assets/SessionListPanel-lu3oy0hw.js +0 -1
- package/static/dist/assets/SettingsLayout-o9bv4mgb.js +0 -1
- package/static/dist/assets/States-eq6fe29m.js +0 -1
- package/static/dist/assets/Toaster-pc247u9f.js +0 -1
- package/static/dist/assets/_app-f8n1i0m1.js +0 -1
- package/static/dist/assets/_app.ext._extensionId-gfbzpoou.js +0 -1
- package/static/dist/assets/_app.index-bzzv80hk.js +0 -1
- package/static/dist/assets/_app.insights-dzixbryi.js +0 -1
- package/static/dist/assets/_app.kanban-c6hep2us.js +0 -1
- package/static/dist/assets/_app.logs-i69qds98.js +0 -1
- package/static/dist/assets/_app.memory-kl4vf0gd.js +0 -1
- package/static/dist/assets/_app.profiles-b8zinmyp.js +0 -1
- package/static/dist/assets/_app.session._sessionId-fn1bhde6.js +0 -1
- package/static/dist/assets/_app.settings-cg8plwmf.js +0 -1
- package/static/dist/assets/_app.settings._section-bkwbi9zn.js +0 -3
- package/static/dist/assets/_app.skills-cs47sbvs.js +0 -1
- package/static/dist/assets/_app.tasks-dwvk64tc.js +0 -1
- package/static/dist/assets/_app.todos-c8xjm8p3.js +0 -1
- package/static/dist/assets/_app.workspaces-ehvd1gds.js +0 -1
- package/static/dist/assets/abnfDiagram-VCTEODGH-c8kdixke.js +0 -1
- package/static/dist/assets/architecture-7GRP2DOG-6crvnylg.js +0 -1
- package/static/dist/assets/architectureDiagram-5GKGNRK7-cku06x1p.js +0 -36
- package/static/dist/assets/back-h7aun7ew.js +0 -1
- package/static/dist/assets/cancel-fhi0vcnu.js +0 -1
- package/static/dist/assets/channel-b2zo73z1.js +0 -1
- package/static/dist/assets/chunk-DU6HZSFF-ofajdixh.js +0 -128
- package/static/dist/assets/chunk-OSK3NFVY-e7gkikcw.js +0 -10
- package/static/dist/assets/classDiagram-ZZMXUADV-oe99st8x.js +0 -1
- package/static/dist/assets/classDiagram-v2-VYDZK3BY-oe99st8x.js +0 -1
- package/static/dist/assets/copied-hcplv46p.js +0 -1
- package/static/dist/assets/copy-magtofyp.js +0 -1
- package/static/dist/assets/cose-bilkent-JH36ORCC-eclcy6ja.js +0 -1
- package/static/dist/assets/cynefin-OW5HDTMX-siqlulkl.js +0 -1
- package/static/dist/assets/cynefinDiagram-5FMLGOSQ-i648tah9.js +0 -62
- package/static/dist/assets/cytoscape.esm-bqu53u12.js +0 -321
- package/static/dist/assets/dagre-GXQ25YYZ-mwn6uc17.js +0 -4
- package/static/dist/assets/dagre-lkoknbjn.js +0 -1
- package/static/dist/assets/diagram-UQ7AKVKN-mekq7zgq.js +0 -41
- package/static/dist/assets/diagram-VX7I27RA-xe6r5qfw.js +0 -24
- package/static/dist/assets/diagram-Z3DM3KII-nq07dg7k.js +0 -24
- package/static/dist/assets/ebnfDiagram-PWID7BFC-c62ipm3g.js +0 -1
- package/static/dist/assets/endpoints-klbxod4t.js +0 -64
- package/static/dist/assets/erDiagram-RLTQ6QDP-h39kmnzy.js +0 -99
- package/static/dist/assets/eventmodeling-NTZA5JFV-obfede4u.js +0 -1
- package/static/dist/assets/flowDiagram-HODETNUW-kdpsp1je.js +0 -1
- package/static/dist/assets/gitGraph-4MIJSDKK-fr2i7caa.js +0 -1
- package/static/dist/assets/highlighted-body-KPVGNVTW-jwpop9p8.js +0 -1
- package/static/dist/assets/host-i828xuo7.js +0 -1
- package/static/dist/assets/index-eh8zilnv.css +0 -2
- package/static/dist/assets/index-r648f4o1.js +0 -11
- package/static/dist/assets/info-A6RAGUB7-h11x0dxo.js +0 -1
- package/static/dist/assets/infoDiagram-27XIBGKW-dfh47dw9.js +0 -2
- package/static/dist/assets/lazyRouteComponent-e1y4t2i9.js +0 -1
- package/static/dist/assets/linear-becwlnhk.js +0 -1
- package/static/dist/assets/link-oot2yn73.js +0 -1
- package/static/dist/assets/login-gu28l9ou.js +0 -1
- package/static/dist/assets/mermaid-HWGCJPDP-mtwrj8fn.js +0 -1
- package/static/dist/assets/mermaid-parser.core-ntne45i9.js +0 -7
- package/static/dist/assets/not-found-ccnnac39.js +0 -1
- package/static/dist/assets/onboarding-lkphzu1s.js +0 -1
- package/static/dist/assets/onboarding_model_label-ftpjdwqk.js +0 -1
- package/static/dist/assets/packet-AYTQ26CC-l6jdka8p.js +0 -1
- package/static/dist/assets/pegDiagram-XKGWAZYB-g1er3bxj.js +0 -1
- package/static/dist/assets/pie-WAS4IAKB-i0ylcrt9.js +0 -1
- package/static/dist/assets/pieDiagram-E7YTZNPT-fbz1e55z.js +0 -39
- package/static/dist/assets/radar-RG4KPBEZ-f9b70aa4.js +0 -1
- package/static/dist/assets/railroad-74A4TZTK-lgsuoeda.js +0 -1
- package/static/dist/assets/railroad-abnf-HS5TGJTU-nf9d6pdx.js +0 -1
- package/static/dist/assets/railroad-ebnf-LZEXJU2U-f7ummkt5.js +0 -1
- package/static/dist/assets/railroad-peg-WCYAUIDC-oczq5tqs.js +0 -1
- package/static/dist/assets/railroadDiagram-O6MQD6OU-eqpy9fnz.js +0 -1
- package/static/dist/assets/refresh-abtu93ke.js +0 -1
- package/static/dist/assets/refresh-cw-hlwb02qv.js +0 -1
- package/static/dist/assets/requirementDiagram-BXWQKSXE-ogf6puhi.js +0 -84
- package/static/dist/assets/rolldown-runtime-b9miacsj.js +0 -1
- package/static/dist/assets/root-mbklpuv4.js +0 -1
- package/static/dist/assets/saved-girnabkf.js +0 -1
- package/static/dist/assets/settings_section_extensions_title-h14cwk6f.js +0 -1
- package/static/dist/assets/share._token-kl0r7ido.js +0 -2
- package/static/dist/assets/sizeCapture-INFHLROL-g612l48j.js +0 -1
- package/static/dist/assets/sliders-horizontal-n1zm34qq.js +0 -1
- package/static/dist/assets/src-hjyib4mu.js +0 -1
- package/static/dist/assets/sse-iwku0sjo.js +0 -1
- package/static/dist/assets/stateDiagram-D77RDMKH-fyoznpcs.js +0 -1
- package/static/dist/assets/stateDiagram-v2-MP3YSRHH-ixhx8r46.js +0 -1
- package/static/dist/assets/swimlanes-42K2YHIH-oefldhfy.js +0 -1
- package/static/dist/assets/swimlanesDiagram-VR7AAH4N-h9ersk2e.js +0 -8
- package/static/dist/assets/todoStore-jjr5yr78.js +0 -1
- package/static/dist/assets/treeView-Q6P3EWNA-f7hcci5o.js +0 -1
- package/static/dist/assets/treemap-WGGIJYW6-nl85071a.js +0 -1
- package/static/dist/assets/units-kgxzxfhi.js +0 -1
- package/static/dist/assets/untitled-cikfjntn.js +0 -1
- package/static/dist/assets/useForm-kqoqg0k9.js +0 -1
- package/static/dist/assets/useLocale-o59tbiea.js +0 -1
- package/static/dist/assets/vennDiagram-4TSXK5OY-h5hzbk0n.js +0 -34
- package/static/dist/assets/wardley-WFR3VGLG-jftvpm3e.js +0 -1
- package/static/dist/assets/wardleyDiagram-VM6X3IG4-hedh85ge.js +0 -78
- /package/static/dist/assets/{ChatPage-gmu5c7lf.css → TerminalPanel-gmu5c7lf.css} +0 -0
|
@@ -1,15 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
* Provider cards, the model picker catalog, live model lists, quotas and cost
|
|
3
|
-
* history (Python `api/providers.py`, the `/api/models` parts of
|
|
4
|
-
* `api/config.py`, `_handle_live_models`).
|
|
5
|
-
*
|
|
6
|
-
* The Agent's own primitives (registry, auth status, live model ids, account
|
|
7
|
-
* usage) come through the sidecar; the static tables and key detection live
|
|
8
|
-
* here. ponytail: one live-id cache with a 24h TTL replaces the Python
|
|
9
|
-
* publisher/provenance machinery; `refresh()` evicts it.
|
|
10
|
-
*/
|
|
1
|
+
import { updateDocument } from '../sessions/storage/document.js';
|
|
11
2
|
import { homeDotenvKeys } from '../cli/dotenv.js';
|
|
12
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
13
4
|
import { createHash, randomUUID } from 'node:crypto';
|
|
14
5
|
import { join } from 'node:path';
|
|
15
6
|
import { atomicWriteText } from '../fs/atomic.js';
|
|
@@ -17,7 +8,7 @@ import { str } from '../util.js';
|
|
|
17
8
|
import { QUOTA_THRESHOLD_DEFAULTS } from '../settings.js';
|
|
18
9
|
import { loadEnvFile } from './env-file.js';
|
|
19
10
|
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
|
-
import { activeProviderFromConfig, canonicaliseProviderId, configuredModelIds, configuredModelOptions, customProviderEntries, customProviderSlug, dict, effectiveDefaultModel, isDict,
|
|
11
|
+
import { activeProviderFromConfig, canonicaliseProviderId, configuredModelIds, configuredModelOptions, customProviderEntries, customProviderSlug, dict, effectiveDefaultModel, isDict, modelSection, parseProviderQualifiedModel, providerIdentity, resolveProviderAlias, } from '../config/agent-config.js';
|
|
21
12
|
/** Python `_model_matches_picker_selection`: same bare id, and the routing hints agree when the selection names one. */
|
|
22
13
|
function modelMatchesPickerSelection(modelId, selected, providerId) {
|
|
23
14
|
const candidate = modelId.trim();
|
|
@@ -133,16 +124,19 @@ export function stampAuxiliarySelections(aux, catalog) {
|
|
|
133
124
|
const match = provider === 'auto' ? undefined : options.find((o) => o.provider === key && o.bare === model);
|
|
134
125
|
if (match)
|
|
135
126
|
return { value_label: match.label || match.bare, provider_label: match.group, option_id: match.id };
|
|
136
|
-
|
|
137
|
-
return { value_label: model || null, provider_label: provider === 'auto' ? null : group?.provider ?? displayName(provider), option_id: null };
|
|
127
|
+
return { value_label: model || null, provider_label: provider === 'auto' ? null : providerLabel(catalog, provider), option_id: null };
|
|
138
128
|
};
|
|
139
129
|
// Auto falls back to the main chat model; name the effective one the catalog resolved (a legacy string `model` or a
|
|
140
130
|
// `HERMES_MODEL`-style override never reaches `aux.main`).
|
|
141
131
|
const mainParsed = parseProviderQualifiedModel(catalog.default_model);
|
|
142
132
|
const main = describe(mainParsed?.[1] ?? catalog.active_provider ?? aux.main.provider, mainParsed?.[0] ?? (catalog.default_model || aux.main.model));
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
133
|
+
const isAuto = (route) => (str(route.provider).trim() || 'auto') === 'auto' && !str(route.model).trim();
|
|
134
|
+
const tasks = aux.tasks.map(({ effective, ...t }) => {
|
|
135
|
+
if (isAuto(t)) {
|
|
136
|
+
// TAL-800: an Auto task that inherits another slot runs on that slot's route, the main model only when it is Auto too.
|
|
137
|
+
const base = isDict(effective) && !isAuto(effective) ? describe(effective.provider, effective.model) : main;
|
|
138
|
+
return { ...t, is_auto: true, value_label: base.value_label, provider_label: base.provider_label, selected_option_id: null, in_catalog: true };
|
|
139
|
+
}
|
|
146
140
|
const pinned = describe(t.provider, t.model);
|
|
147
141
|
return { ...t, is_auto: false, value_label: pinned.value_label, provider_label: pinned.provider_label, selected_option_id: pinned.option_id, in_catalog: pinned.option_id !== null };
|
|
148
142
|
});
|
|
@@ -167,7 +161,13 @@ const unqualifiedModelId = (id) => parseProviderQualifiedModel(id)?.[0] ?? id.tr
|
|
|
167
161
|
const MODEL_PICKER_OVERFLOW_THRESHOLD = 25;
|
|
168
162
|
const MODEL_PICKER_VISIBLE_TARGET = 15;
|
|
169
163
|
const ACCOUNT_USAGE_CACHE_TTL_S = 45;
|
|
164
|
+
/** TAL-807: the oldest account-usage read the picker still shows a quota level from. */
|
|
165
|
+
const PICKER_USAGE_MAX_AGE_S = 600;
|
|
166
|
+
/** TAL-807: how long a picker read waits for a provider's first account-usage read. */
|
|
167
|
+
const PICKER_USAGE_WAIT_MS = 2_000;
|
|
170
168
|
const LIVE_TTL_S = 86_400;
|
|
169
|
+
/** TAL-799: the Agent's `_PROVIDER_MODELS_FALLBACK_TTL`; its placeholder for an unreachable catalog is re-probed soon. */
|
|
170
|
+
const FALLBACK_TTL_S = 60;
|
|
171
171
|
const PROVIDERS_TTL_S = 30;
|
|
172
172
|
/** The sidecar reads a balance endpoint with a 15 s timeout of its own. */
|
|
173
173
|
const BALANCE_TIMEOUT_MS = 20_000;
|
|
@@ -343,6 +343,11 @@ export function classifyQuotaSource(source, t) {
|
|
|
343
343
|
urgency: { remaining: urgency(source.windows[0]).remaining, pace: urgency(source.windows[source.pace_window_index ?? 0]).pace },
|
|
344
344
|
};
|
|
345
345
|
}
|
|
346
|
+
/** A provider's name as the catalog lists it (a named custom endpoint's configured name, a plugin's display name), else its static display name. */
|
|
347
|
+
export function providerLabel(catalog, provider) {
|
|
348
|
+
const key = canonicaliseProviderId(provider);
|
|
349
|
+
return catalog?.groups.find((g) => canonicaliseProviderId(g.provider_id) === key)?.provider ?? displayName(provider);
|
|
350
|
+
}
|
|
346
351
|
export function displayName(pid) {
|
|
347
352
|
return PROVIDER_DISPLAY[pid] ?? pid.split('-').map((w) => (w ? w[0].toUpperCase() + w.slice(1) : w)).join(' ');
|
|
348
353
|
}
|
|
@@ -391,6 +396,10 @@ function looksLikeCodexOauthToken(value) {
|
|
|
391
396
|
return false;
|
|
392
397
|
}
|
|
393
398
|
}
|
|
399
|
+
const BALANCE_PROVIDERS = ['openrouter', 'deepseek', 'opencode-go'];
|
|
400
|
+
function isBalanceProvider(pid) {
|
|
401
|
+
return BALANCE_PROVIDERS.some((provider) => provider === pid);
|
|
402
|
+
}
|
|
394
403
|
function valueCountsAsApiKey(pid, value) {
|
|
395
404
|
const text = str(value).trim();
|
|
396
405
|
if (!text)
|
|
@@ -458,9 +467,14 @@ export function uniqueQuotaSources(sources) {
|
|
|
458
467
|
}
|
|
459
468
|
export class ProviderCatalog {
|
|
460
469
|
deps;
|
|
470
|
+
scopedKeyRevisions = new Map();
|
|
471
|
+
scopedKeyAuthority = new Map();
|
|
472
|
+
modelGenerations = new Map();
|
|
473
|
+
modelReads = new Map();
|
|
474
|
+
credentialReads = new Map();
|
|
461
475
|
/** Failed live lookups by home, provider, and the `sourceFingerprint` they ran under (TAL-542). */
|
|
462
476
|
liveFailed = new Set();
|
|
463
|
-
/** `source`: the `sourceFingerprint` the ids were fetched under; another one makes them stale (TAL-542). */
|
|
477
|
+
/** `source`: the `sourceFingerprint` the ids were fetched under; another one makes them stale (TAL-542). `ttl`: seconds they stay fresh. */
|
|
464
478
|
liveIds = new Map();
|
|
465
479
|
liveInflight = new Map();
|
|
466
480
|
/** `pooled`: the fill's `poolProviders` answer, null when unknown (TAL-638). */
|
|
@@ -482,15 +496,32 @@ export class ProviderCatalog {
|
|
|
482
496
|
}
|
|
483
497
|
return { groups: this.liveIds.size, models, age_seconds: oldest === null ? null : Math.max(0, Math.round(this.deps.now() - oldest)) };
|
|
484
498
|
}
|
|
485
|
-
invalidate(profileHome, provider) {
|
|
499
|
+
invalidate(profileHome, provider, opts = {}) {
|
|
500
|
+
// Explicit refresh retires builds without changing the credential identity of confirmed live ids.
|
|
501
|
+
if (profileHome)
|
|
502
|
+
this.modelGenerations.set(profileHome, (this.modelGenerations.get(profileHome) ?? 0) + 1);
|
|
503
|
+
else
|
|
504
|
+
for (const [home, generation] of this.modelGenerations)
|
|
505
|
+
this.modelGenerations.set(home, generation + 1);
|
|
486
506
|
for (const key of [...this.liveIds.keys()]) {
|
|
487
507
|
if (profileHome && !key.startsWith(`${profileHome}\0`))
|
|
488
508
|
continue;
|
|
489
509
|
if (provider && !key.endsWith(`\0${provider}`))
|
|
490
510
|
continue;
|
|
491
|
-
|
|
511
|
+
if (opts.preserveConfirmed)
|
|
512
|
+
this.liveIds.get(key).ttl = 0;
|
|
513
|
+
else
|
|
514
|
+
this.liveIds.delete(key);
|
|
492
515
|
}
|
|
493
516
|
this.providersCache.clear();
|
|
517
|
+
for (const key of this.pickerLimitsCache.keys())
|
|
518
|
+
if (!profileHome || key === profileHome)
|
|
519
|
+
this.pickerLimitsCache.delete(key);
|
|
520
|
+
if (opts.preserveConfirmed)
|
|
521
|
+
return;
|
|
522
|
+
for (const key of [...this.lastQuotaSources.keys()])
|
|
523
|
+
if (!profileHome || key.startsWith(`${profileHome}\0`))
|
|
524
|
+
this.lastQuotaSources.delete(key);
|
|
494
525
|
// Credentials changed: the last pool answers no longer stand in for a failed lookup.
|
|
495
526
|
for (const key of [...this.lastPoolAnswers.keys()])
|
|
496
527
|
if (!profileHome || key.startsWith(`${profileHome}\0`))
|
|
@@ -514,27 +545,44 @@ export class ProviderCatalog {
|
|
|
514
545
|
}
|
|
515
546
|
return catalogOptionId(catalog, model, provider);
|
|
516
547
|
}
|
|
548
|
+
/** TAL-821: `providerLabel` against the last catalog built for this home; a cold home starts building one. */
|
|
549
|
+
providerLabel(profileHome, provider) {
|
|
550
|
+
const catalog = this.lastModels.get(profileHome);
|
|
551
|
+
if (!catalog)
|
|
552
|
+
void this.warmModelOptions(profileHome);
|
|
553
|
+
return providerLabel(catalog, provider);
|
|
554
|
+
}
|
|
517
555
|
/**
|
|
518
556
|
* TAL-542: the identity of the files a catalog and its live ids derive from: config.yaml, `.env`, the Agent's sign-in
|
|
519
557
|
* store (`auth.json`, rewritten by an account switch), and the installed plugins directory, so a change outside Web
|
|
520
|
-
* makes both stale.
|
|
558
|
+
* makes both stale. Opaque scoped-key revisions cover external secret sources without exposing their keys.
|
|
521
559
|
*/
|
|
522
|
-
sourceFingerprint(profileHome) {
|
|
560
|
+
sourceFingerprint(profileHome, includeScoped = true) {
|
|
523
561
|
const stamp = (name) => {
|
|
524
562
|
try {
|
|
525
563
|
const st = statSync(join(profileHome, name), { bigint: true });
|
|
526
|
-
return `${String(st.mtimeNs)}:${String(st.size)}:${String(st.ino)}`;
|
|
564
|
+
return `${String(st.mtimeNs)}:${String(st.ctimeNs)}:${String(st.size)}:${String(st.ino)}`;
|
|
527
565
|
}
|
|
528
566
|
catch {
|
|
529
567
|
return 'missing';
|
|
530
568
|
}
|
|
531
569
|
};
|
|
532
|
-
|
|
570
|
+
const names = new Set([...Object.values(PROVIDER_ENV_VAR), ...Object.values(PROVIDER_ENV_VAR_ALIASES).flat()]);
|
|
571
|
+
const config = this.deps.config.peek(profileHome, { refresh: false }) ?? {};
|
|
572
|
+
for (const cp of [modelSection(config), ...Object.values(dict(config.providers)).filter(isDict), ...customProviderEntries(config)]) {
|
|
573
|
+
for (const name of [str(cp.key_env).trim(), /^\$\{([^}]+)\}$/.exec(str(cp.api_key).trim())?.[1]])
|
|
574
|
+
if (name)
|
|
575
|
+
names.add(name);
|
|
576
|
+
}
|
|
577
|
+
const credentials = [...names].sort().map((name) => [name, this.processEnv(name, profileHome)]);
|
|
578
|
+
const models = ['HERMES_MODEL', 'OPENAI_MODEL', 'LLM_MODEL'].map((name) => [name, this.deps.env[name]]);
|
|
579
|
+
const environment = createHash('sha256').update(JSON.stringify([credentials, models, includeScoped ? this.scopedKeyRevisions.get(profileHome) ?? '[]' : '[]'])).digest('hex');
|
|
580
|
+
return [profileHome, environment, this.deps.config.fingerprint(profileHome), ...['.env', 'auth.json', 'plugins'].map(stamp)].join('|');
|
|
533
581
|
}
|
|
534
582
|
/** The last catalog built for this home, younger than `maxAgeS`, while its source files are unchanged since (TAL-542). */
|
|
535
583
|
currentModels(profileHome, maxAgeS = Infinity) {
|
|
536
584
|
const meta = this.lastModelsMeta.get(profileHome);
|
|
537
|
-
return meta?.source === this.sourceFingerprint(profileHome) && this.deps.now() - meta.at < maxAgeS ? this.lastModels.get(profileHome) : undefined;
|
|
585
|
+
return this.scopedKeyAuthority.get(profileHome) !== false && meta?.source === this.sourceFingerprint(profileHome) && this.deps.now() - meta.at < maxAgeS ? this.lastModels.get(profileHome) : undefined;
|
|
538
586
|
}
|
|
539
587
|
/**
|
|
540
588
|
* TAL-542: `repairSessionModel` against this home's catalog. Agent-owned sign-in and plugin state has no file to
|
|
@@ -543,17 +591,19 @@ export class ProviderCatalog {
|
|
|
543
591
|
*/
|
|
544
592
|
sessionModelRepair(profileHome, model, provider) {
|
|
545
593
|
const catalog = this.currentModels(profileHome, PROVIDERS_TTL_S);
|
|
546
|
-
|
|
547
|
-
|
|
594
|
+
const pid = canonicaliseProviderId(provider) || canonicaliseProviderId(catalog?.active_provider);
|
|
595
|
+
if (!catalog || this.lastModelsMeta.get(profileHome)?.unconfirmed.has(pid))
|
|
596
|
+
void this.models(profileHome).catch(() => undefined);
|
|
597
|
+
if (!catalog)
|
|
548
598
|
return null;
|
|
549
|
-
}
|
|
550
599
|
return repairSessionModel(catalog, model, provider, this.lastModelsMeta.get(profileHome)?.unconfirmed);
|
|
551
600
|
}
|
|
552
601
|
/**
|
|
553
|
-
*
|
|
554
|
-
*
|
|
602
|
+
* Explicitly await discovery, or start it in the background from `sessionModelRepair`. Admission never awaits it.
|
|
603
|
+
* A source that changed while the build awaited makes that build stale, so it is rebuilt once.
|
|
555
604
|
*/
|
|
556
605
|
async warmSessionModelRepair(profileHome) {
|
|
606
|
+
await this.poolProviders(profileHome);
|
|
557
607
|
for (let attempt = 0; attempt < 2 && !this.currentModels(profileHome, PROVIDERS_TTL_S); attempt += 1) {
|
|
558
608
|
try {
|
|
559
609
|
await this.models(profileHome);
|
|
@@ -563,8 +613,14 @@ export class ProviderCatalog {
|
|
|
563
613
|
}
|
|
564
614
|
}
|
|
565
615
|
}
|
|
616
|
+
/** TAL-1381: warms this home's catalog once, then answers `modelOptionFor` synchronously for every row of a batch. */
|
|
617
|
+
async modelOptionResolver(profileHome) {
|
|
618
|
+
await this.warmModelOptions(profileHome);
|
|
619
|
+
return (model, provider) => this.modelOptionFor(profileHome, model, provider);
|
|
620
|
+
}
|
|
566
621
|
/** Builds the catalog `modelOptionFor` reads once per home, again after config.yaml or `.env` changes; an unavailable catalog leaves every option id null. */
|
|
567
622
|
async warmModelOptions(profileHome) {
|
|
623
|
+
await this.poolProviders(profileHome);
|
|
568
624
|
if (this.currentModels(profileHome))
|
|
569
625
|
return;
|
|
570
626
|
try {
|
|
@@ -613,18 +669,14 @@ export class ProviderCatalog {
|
|
|
613
669
|
return false;
|
|
614
670
|
}
|
|
615
671
|
async authStatus(profileHome, pid) {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
return
|
|
619
|
-
|
|
620
|
-
return (await sidecar.call('providers.auth_status', { profile_home: profileHome, provider: pid })).status;
|
|
621
|
-
}
|
|
622
|
-
catch {
|
|
623
|
-
return null;
|
|
624
|
-
}
|
|
672
|
+
return this.poolLookup(profileHome, `auth\0${pid}`, async (sidecar) => {
|
|
673
|
+
const { status } = await sidecar.call('providers.auth_status', { profile_home: profileHome, provider: pid });
|
|
674
|
+
return { logged_in: status.logged_in, key_source: str(status.key_source), error: str(status.error) };
|
|
675
|
+
});
|
|
625
676
|
}
|
|
626
677
|
/** `pooled`: the providers whose persisted credential pool holds an added account (TAL-638). */
|
|
627
|
-
async probeKey(profileHome, pid, config, envValues, pooled) {
|
|
678
|
+
async probeKey(profileHome, pid, config, envValues, pooled, scopedKeys) {
|
|
679
|
+
let authoritative = true;
|
|
628
680
|
let hasKey = this.providerHasKey(pid, config, envValues, profileHome);
|
|
629
681
|
let isOauth = OAUTH_PROVIDERS.has(pid);
|
|
630
682
|
let keySource = 'none';
|
|
@@ -632,7 +684,9 @@ export class ProviderCatalog {
|
|
|
632
684
|
let removable = false;
|
|
633
685
|
if (isOauth) {
|
|
634
686
|
keySource = 'oauth';
|
|
635
|
-
const
|
|
687
|
+
const lookup = await this.authStatus(profileHome, pid);
|
|
688
|
+
authoritative = lookup.authoritative;
|
|
689
|
+
const status = lookup.value;
|
|
636
690
|
if (status?.logged_in) {
|
|
637
691
|
hasKey = true;
|
|
638
692
|
keySource = str(status.key_source) || 'oauth';
|
|
@@ -677,8 +731,16 @@ export class ProviderCatalog {
|
|
|
677
731
|
hasKey = true;
|
|
678
732
|
keySource = 'credential_pool';
|
|
679
733
|
}
|
|
734
|
+
else if (isBalanceProvider(pid)) {
|
|
735
|
+
if (scopedKeys[pid]?.present) {
|
|
736
|
+
hasKey = true;
|
|
737
|
+
keySource = 'env';
|
|
738
|
+
}
|
|
739
|
+
}
|
|
680
740
|
else if (!providerEnvVar(pid) && /^[a-z][a-z0-9_-]{0,63}$/.test(pid)) {
|
|
681
|
-
const
|
|
741
|
+
const lookup = await this.authStatus(profileHome, pid);
|
|
742
|
+
authoritative = lookup.authoritative;
|
|
743
|
+
const status = lookup.value;
|
|
682
744
|
if (status?.logged_in) {
|
|
683
745
|
hasKey = true;
|
|
684
746
|
const raw = str(status.key_source);
|
|
@@ -686,14 +748,21 @@ export class ProviderCatalog {
|
|
|
686
748
|
isOauth = true;
|
|
687
749
|
}
|
|
688
750
|
}
|
|
689
|
-
return { hasKey, keySource, authError, isOauth, removable };
|
|
751
|
+
return { authoritative, hasKey, keySource, authError, isOauth, removable };
|
|
690
752
|
}
|
|
691
753
|
/** Live model ids from the Agent for one provider, cached per profile home. */
|
|
692
754
|
async liveModelIds(profileHome, pid, opts = {}) {
|
|
755
|
+
const inputSource = this.sourceFingerprint(profileHome, false);
|
|
756
|
+
if (!opts.source || opts.source !== this.sourceFingerprint(profileHome))
|
|
757
|
+
await this.poolProviders(profileHome);
|
|
693
758
|
const key = `${profileHome}\0${pid}`;
|
|
694
|
-
const source = this.sourceFingerprint(profileHome);
|
|
759
|
+
const source = inputSource === this.sourceFingerprint(profileHome, false) ? this.sourceFingerprint(profileHome) : inputSource;
|
|
695
760
|
const hit = this.liveIds.get(key);
|
|
696
|
-
if (
|
|
761
|
+
if (this.scopedKeyAuthority.get(profileHome) === false) {
|
|
762
|
+
this.liveFailed.add(`${key}\0${source}`);
|
|
763
|
+
return hit?.source === source ? hit.ids : [];
|
|
764
|
+
}
|
|
765
|
+
if (hit?.source === source && !opts.force && this.deps.now() - hit.at < hit.ttl)
|
|
697
766
|
return hit.ids;
|
|
698
767
|
// A lookup started under another source answers for the old endpoint or credential; this one starts its own.
|
|
699
768
|
const flightKey = `${key}\0${source}`;
|
|
@@ -704,51 +773,75 @@ export class ProviderCatalog {
|
|
|
704
773
|
// No sidecar is a failed lookup for this source: the ids it keeps showing never confirm a stale-model repair (TAL-542).
|
|
705
774
|
if (!sidecar) {
|
|
706
775
|
this.liveFailed.add(flightKey);
|
|
707
|
-
return hit?.ids
|
|
776
|
+
return hit?.source === source && source === this.sourceFingerprint(profileHome) ? hit.ids : [];
|
|
708
777
|
}
|
|
709
778
|
const run = sidecar.call('providers.model_ids', { profile_home: profileHome, provider: pid, ...(opts.force ? { force_refresh: true } : {}) }, { timeoutMs: 30_000 })
|
|
710
|
-
.then((r) => {
|
|
711
|
-
|
|
779
|
+
.then((r) => {
|
|
780
|
+
if (source !== this.sourceFingerprint(profileHome)) {
|
|
781
|
+
this.liveFailed.add(flightKey);
|
|
782
|
+
return [];
|
|
783
|
+
}
|
|
784
|
+
if (r.fallback && hit?.source === source && hit.confirmed) {
|
|
785
|
+
this.liveFailed.add(flightKey);
|
|
786
|
+
return hit.ids;
|
|
787
|
+
}
|
|
788
|
+
this.liveIds.set(key, { at: this.deps.now(), ids: r.model_ids, source, ttl: r.fallback ? FALLBACK_TTL_S : LIVE_TTL_S, confirmed: !r.fallback });
|
|
789
|
+
// TAL-799: the Agent's curated placeholder for an unreachable catalog is a failed lookup that still lists models.
|
|
790
|
+
if (r.fallback)
|
|
791
|
+
this.liveFailed.add(flightKey);
|
|
792
|
+
else
|
|
793
|
+
this.liveFailed.delete(flightKey);
|
|
794
|
+
return r.model_ids;
|
|
795
|
+
})
|
|
796
|
+
.catch((error) => { this.deps.log(`[catalog] live model ids for ${pid} failed: ${str(error.message)}`); this.liveFailed.add(flightKey); return hit?.source === source && source === this.sourceFingerprint(profileHome) ? hit.ids : []; })
|
|
712
797
|
.finally(() => { this.liveInflight.delete(flightKey); });
|
|
713
798
|
this.liveInflight.set(flightKey, run);
|
|
714
799
|
return run;
|
|
715
800
|
}
|
|
801
|
+
/** Whether the last live lookup for this provider under `source` failed or answered with the Agent's placeholder list. */
|
|
802
|
+
liveLookupFailed(profileHome, pid, source = this.sourceFingerprint(profileHome)) {
|
|
803
|
+
return this.liveFailed.has(`${profileHome}\0${pid}\0${source}`);
|
|
804
|
+
}
|
|
716
805
|
/**
|
|
717
806
|
* TAL-288: the model-provider plugins installed and enabled in this profile, as the sidecar reads them from the Agent.
|
|
718
807
|
* A failed lookup lists none, so every other provider stays usable; an id that cannot be a provider routing hint is dropped.
|
|
719
808
|
*/
|
|
720
809
|
async pluginProviders(profileHome) {
|
|
721
|
-
|
|
722
|
-
if (!sidecar)
|
|
723
|
-
return [];
|
|
724
|
-
try {
|
|
810
|
+
return this.poolLookup(profileHome, 'plugins', async (sidecar) => {
|
|
725
811
|
const { providers } = await sidecar.call('plugins.providers', { profile_home: profileHome }, { timeoutMs: PLUGIN_LIST_TIMEOUT_MS });
|
|
726
812
|
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);
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* TAL-798: the GGUFs staged for the Agent's managed llama.cpp provider, selectable before its server runs (a turn boots
|
|
817
|
+
* it). Null when none are staged or the listing failed, so every other provider stays usable.
|
|
818
|
+
*/
|
|
819
|
+
async localModels(profileHome) {
|
|
820
|
+
return this.poolLookup(profileHome, 'local-models', async (sidecar) => sidecar.call('providers.local_models', { profile_home: profileHome }, { timeoutMs: PLUGIN_LIST_TIMEOUT_MS }));
|
|
732
821
|
}
|
|
733
822
|
/** Python `get_providers`. */
|
|
734
823
|
async providers(profileHome) {
|
|
735
824
|
return structuredClone((await this.providerRows(profileHome)).payload);
|
|
736
825
|
}
|
|
737
826
|
/** The cached `providers` answer, shared with `quotas`; callers never mutate it. */
|
|
738
|
-
async providerRows(profileHome) {
|
|
827
|
+
async providerRows(profileHome, pool) {
|
|
739
828
|
const config = await this.deps.config.read(profileHome);
|
|
740
829
|
const envValues = loadEnvFile(join(profileHome, '.env'));
|
|
741
|
-
|
|
830
|
+
pool ??= await this.poolProviders(profileHome);
|
|
831
|
+
const source = this.sourceFingerprint(profileHome);
|
|
832
|
+
const cacheKey = source;
|
|
742
833
|
const hit = this.providersCache.get(profileHome);
|
|
743
|
-
if (hit?.key === cacheKey && this.deps.now() - hit.at < PROVIDERS_TTL_S)
|
|
834
|
+
if (pool.authoritative && hit?.key === cacheKey && this.deps.now() - hit.at < PROVIDERS_TTL_S)
|
|
744
835
|
return hit;
|
|
745
|
-
const pooledIds =
|
|
836
|
+
const pooledIds = pool.value?.providers ?? null;
|
|
837
|
+
const pluginLookup = await this.pluginProviders(profileHome);
|
|
838
|
+
let authoritative = pool.authoritative && pluginLookup.authoritative;
|
|
746
839
|
const pooled = new Set(pooledIds ?? []);
|
|
747
840
|
const active = activeProviderFromConfig(config);
|
|
748
841
|
const known = new Set([...Object.keys(PROVIDER_DISPLAY), ...Object.keys(PROVIDER_MODELS), ...OAUTH_PROVIDERS]);
|
|
749
842
|
// A plugin never stands in for a built-in provider, by id or alias (that would route to the built-in's billing); it
|
|
750
843
|
// does own a `providers.<id>` config entry.
|
|
751
|
-
const plugins = (
|
|
844
|
+
const plugins = (pluginLookup.value ?? []).filter((p) => !known.has(p.name) && !known.has(providerIdentity(p.name)));
|
|
752
845
|
const pluginIds = new Set(plugins.map((p) => p.name));
|
|
753
846
|
const providersCfg = dict(config.providers);
|
|
754
847
|
for (const key of Object.keys(providersCfg)) {
|
|
@@ -759,7 +852,8 @@ export class ProviderCatalog {
|
|
|
759
852
|
}
|
|
760
853
|
const rows = [];
|
|
761
854
|
for (const pid of [...known].sort()) {
|
|
762
|
-
const probe = await this.probeKey(profileHome, pid, config, envValues, pooled);
|
|
855
|
+
const probe = await this.probeKey(profileHome, pid, config, envValues, pooled, pool.value?.keys ?? {});
|
|
856
|
+
authoritative &&= probe.authoritative;
|
|
763
857
|
if (pid === 'openai' && !probe.hasKey && looksLikeCodexOauthToken(str(envValues[providerEnvVar('openai') ?? ''] ?? this.deps.env.OPENAI_API_KEY)))
|
|
764
858
|
continue;
|
|
765
859
|
const configured = probe.hasKey || selfHostedEndpoint(pid, config);
|
|
@@ -768,7 +862,7 @@ export class ProviderCatalog {
|
|
|
768
862
|
// Python: the card prefers the live catalog for every keyed provider (exactly like the picker), keeping the
|
|
769
863
|
// static list as the cold/failed-probe fallback; Nous renders a featured subset with the full count.
|
|
770
864
|
if (configured && (probe.isOauth || pid === 'lmstudio' || pid === 'nous' || !BESPOKE_CATALOG_PROVIDERS.has(pid))) {
|
|
771
|
-
const live = await this.liveModelIds(profileHome, pid);
|
|
865
|
+
const live = await this.liveModelIds(profileHome, pid, { source });
|
|
772
866
|
if (live.length) {
|
|
773
867
|
models = pid === 'nous' ? live.slice(0, 25).map((id) => ({ id: `@nous:${id}`, label: `${formatOllamaLabel(id.includes('/') ? id.slice(id.indexOf('/') + 1) : id)} (via Nous)` })) : live.map((id) => ({ id, label: labelForModel(id, []) }));
|
|
774
868
|
modelsTotal = live.length;
|
|
@@ -843,20 +937,47 @@ export class ProviderCatalog {
|
|
|
843
937
|
row.key_source_kind = keySourceKind(row);
|
|
844
938
|
row.models_total = Math.max(Number(row.models_total), row.models.length);
|
|
845
939
|
}
|
|
846
|
-
|
|
847
|
-
this.
|
|
940
|
+
authoritative &&= source === this.sourceFingerprint(profileHome);
|
|
941
|
+
const availability = this.availability(source, authoritative, Boolean(pool.value));
|
|
942
|
+
const entry = { at: this.deps.now(), key: cacheKey, payload: { providers: rows, active_provider: active, availability }, pooled: pooledIds };
|
|
943
|
+
if (authoritative)
|
|
944
|
+
this.providersCache.set(profileHome, entry);
|
|
848
945
|
return entry;
|
|
849
946
|
}
|
|
850
947
|
/** Python `get_available_models` (static catalog + live ids for keyed providers). */
|
|
851
|
-
async models(profileHome) {
|
|
948
|
+
async models(profileHome, attempt = 0) {
|
|
949
|
+
const generation = this.modelGenerations.get(profileHome) ?? 0;
|
|
950
|
+
this.modelGenerations.set(profileHome, generation);
|
|
951
|
+
const inputSource = this.sourceFingerprint(profileHome, false);
|
|
952
|
+
const pool = await this.poolProviders(profileHome);
|
|
953
|
+
if (generation !== this.modelGenerations.get(profileHome) || inputSource !== this.sourceFingerprint(profileHome, false)) {
|
|
954
|
+
if (attempt === 0)
|
|
955
|
+
return this.models(profileHome, 1);
|
|
956
|
+
throw new Error('Provider configuration changed during catalog lookup. Try again.');
|
|
957
|
+
}
|
|
852
958
|
const source = this.sourceFingerprint(profileHome);
|
|
959
|
+
const key = `${source}\0${generation}`;
|
|
960
|
+
const pending = this.modelReads.get(key);
|
|
961
|
+
if (pending)
|
|
962
|
+
return structuredClone(await pending);
|
|
963
|
+
const read = this.buildModels(profileHome, pool, source, generation, attempt).finally(() => { this.modelReads.delete(key); });
|
|
964
|
+
this.modelReads.set(key, read);
|
|
965
|
+
return structuredClone(await read);
|
|
966
|
+
}
|
|
967
|
+
async buildModels(profileHome, pool, source, generation, attempt) {
|
|
968
|
+
const providerState = (await this.providerRows(profileHome, pool)).payload;
|
|
853
969
|
const unconfirmed = new Set();
|
|
854
970
|
const config = await this.deps.config.read(profileHome);
|
|
855
971
|
const envValues = loadEnvFile(join(profileHome, '.env'));
|
|
856
|
-
const active = activeProviderFromConfig(config);
|
|
857
972
|
const model = modelSection(config);
|
|
858
973
|
const defaultModel = effectiveDefaultModel(config, this.deps.env);
|
|
859
974
|
const cfgBaseUrl = str(model.base_url).trim();
|
|
975
|
+
const localLookup = await this.localModels(profileHome);
|
|
976
|
+
const local = localLookup.value?.models.length ? localLookup.value : null;
|
|
977
|
+
const configured = activeProviderFromConfig(config);
|
|
978
|
+
// A `custom` main model at the running local server's endpoint is that server: its Local group owns the selection.
|
|
979
|
+
const atLocalServer = configured === 'custom' && Boolean(cfgBaseUrl) && cfgBaseUrl.replace(/\/+$/, '') === str(local?.current_endpoint).replace(/\/+$/, '');
|
|
980
|
+
const active = local && atLocalServer ? local.provider_id : configured;
|
|
860
981
|
const detected = new Set();
|
|
861
982
|
const configuredIds = new Map();
|
|
862
983
|
const append = (pid, id) => {
|
|
@@ -870,6 +991,8 @@ export class ProviderCatalog {
|
|
|
870
991
|
};
|
|
871
992
|
if (active)
|
|
872
993
|
detected.add(active);
|
|
994
|
+
if (local)
|
|
995
|
+
detected.add(local.provider_id);
|
|
873
996
|
const providersCfg = dict(config.providers);
|
|
874
997
|
const rawKeyFor = new Map();
|
|
875
998
|
for (const [key, value] of Object.entries(providersCfg)) {
|
|
@@ -897,8 +1020,8 @@ export class ProviderCatalog {
|
|
|
897
1020
|
// ready plugin providers (TAL-288), under their own name, and providers keyed only through the credential pool (TAL-638).
|
|
898
1021
|
const signedIn = new Set();
|
|
899
1022
|
const pluginNames = new Map();
|
|
900
|
-
for (const row of
|
|
901
|
-
if (row.has_key !== true || (row.is_oauth !== true && row.is_plugin_provider !== true && row.key_source !== 'credential_pool'))
|
|
1023
|
+
for (const row of providerState.providers) {
|
|
1024
|
+
if (row.has_key !== true || (row.is_oauth !== true && row.is_plugin_provider !== true && row.key_source !== 'credential_pool' && row.key_source !== 'env'))
|
|
902
1025
|
continue;
|
|
903
1026
|
signedIn.add(str(row.id));
|
|
904
1027
|
if (row.is_plugin_provider === true)
|
|
@@ -936,8 +1059,15 @@ export class ProviderCatalog {
|
|
|
936
1059
|
const named = [...namedCustom.entries()].find(([, v]) => customProviderEntries(config).some((e) => customProviderSlug(e.name) === customProviderSlug(v.name) && str(e.base_url).trim().replace(/\/+$/, '') === cfgBaseUrl.replace(/\/+$/, '')));
|
|
937
1060
|
detected.add(named?.[0] ?? active ?? 'custom');
|
|
938
1061
|
}
|
|
1062
|
+
// TAL-911: Agent pickers drop `model_catalog.excluded_providers` under any of their aliases, the active provider included.
|
|
1063
|
+
const excludedCfg = dict(config.model_catalog).excluded_providers;
|
|
1064
|
+
const excluded = new Set(Array.isArray(excludedCfg) ? excludedCfg.map(providerIdentity).filter(Boolean) : []);
|
|
1065
|
+
const isExcluded = (pid) => excluded.has(providerIdentity(pid));
|
|
1066
|
+
const pickerDefault = isExcluded(active) || isExcluded(parseProviderQualifiedModel(defaultModel)?.[1]) ? '' : defaultModel;
|
|
939
1067
|
const groups = [];
|
|
940
1068
|
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()) {
|
|
1069
|
+
if (isExcluded(pid))
|
|
1070
|
+
continue;
|
|
941
1071
|
if (pid.startsWith('custom:')) {
|
|
942
1072
|
const g = namedCustom.get(pid);
|
|
943
1073
|
const models = [...(g?.models ?? [])];
|
|
@@ -945,6 +1075,11 @@ export class ProviderCatalog {
|
|
|
945
1075
|
groups.push({ provider: g?.name ?? pid.replace('custom:', ''), provider_id: pid, models: applyProviderPrefix(models, pid, active) });
|
|
946
1076
|
continue;
|
|
947
1077
|
}
|
|
1078
|
+
if (pid === local?.provider_id) {
|
|
1079
|
+
// A GGUF's id is its file name, quant tag included (`Q4_K_M`); prettifying would mangle it.
|
|
1080
|
+
groups.push({ provider: 'Local', provider_id: pid, models: applyProviderPrefix(local.models.map((id) => ({ id, label: id })), pid, active) });
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
948
1083
|
if (pid === 'custom') {
|
|
949
1084
|
const models = [...customModels];
|
|
950
1085
|
for (const id of configuredIds.get(pid) ?? [])
|
|
@@ -956,20 +1091,25 @@ export class ProviderCatalog {
|
|
|
956
1091
|
}
|
|
957
1092
|
const providerCfg = dict(providersCfg[rawKeyFor.get(pid) ?? pid]);
|
|
958
1093
|
let raw = [];
|
|
1094
|
+
let allowFallback = false;
|
|
959
1095
|
if ('models' in providerCfg && providerCfg.models_discovered !== true)
|
|
960
1096
|
raw = configuredModelOptions(providerCfg.models);
|
|
961
|
-
if (!raw.length
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1097
|
+
if (!raw.length) {
|
|
1098
|
+
// TAL-799: a provider without credentials lists the Agent's curated catalog too; the static tables only cover a missing sidecar.
|
|
1099
|
+
const keyed = this.providerHasKey(pid, config, envValues, profileHome) || selfHostedEndpoint(pid, config) || signedIn.has(pid);
|
|
1100
|
+
const live = await this.liveModelIds(profileHome, pid, { source });
|
|
1101
|
+
const failed = this.liveLookupFailed(profileHome, pid, source);
|
|
1102
|
+
// TAL-542: a failed lookup, or a curated list no account confirmed, leaves this group unconfirmed, so the stale-model repair never trusts it.
|
|
1103
|
+
allowFallback = failed || !keyed;
|
|
1104
|
+
if (allowFallback)
|
|
965
1105
|
unconfirmed.add(pid);
|
|
966
1106
|
if (live.length)
|
|
967
1107
|
raw = live.map((id) => ({ id, label: pid === 'nous' ? `${formatOllamaLabel(id.includes('/') ? id.slice(id.indexOf('/') + 1) : id)} (via Nous)` : labelForModel(id, []) }));
|
|
968
1108
|
// Python (#1567): an authenticated Nous account with an empty live catalog shows no group; only a failed lookup falls back to the curated list.
|
|
969
|
-
else if (pid === 'nous' && !
|
|
1109
|
+
else if (keyed && pid === 'nous' && !failed)
|
|
970
1110
|
continue;
|
|
971
1111
|
}
|
|
972
|
-
if (!raw.length)
|
|
1112
|
+
if (!raw.length && allowFallback)
|
|
973
1113
|
raw = pid === 'openrouter' ? FALLBACK_MODELS.map((m) => ({ id: m.id, label: m.label })) : [...(PROVIDER_MODELS[pid] ?? [])];
|
|
974
1114
|
for (const id of configuredIds.get(pid) ?? [])
|
|
975
1115
|
if (!raw.some((m) => m.id === id))
|
|
@@ -977,7 +1117,7 @@ export class ProviderCatalog {
|
|
|
977
1117
|
if (raw.length)
|
|
978
1118
|
groups.push({ provider: pluginNames.get(pid) ?? displayName(pid), provider_id: pid, models: applyProviderPrefix(raw, pid, active) });
|
|
979
1119
|
}
|
|
980
|
-
if (
|
|
1120
|
+
if (pickerDefault) {
|
|
981
1121
|
const all = new Set(groups.flatMap((g) => g.models.map((m) => m.id)));
|
|
982
1122
|
if (!all.has(defaultModel) && !all.has(`@${active ?? ''}:${defaultModel}`)) {
|
|
983
1123
|
const label = labelForModel(defaultModel, groups);
|
|
@@ -1010,11 +1150,6 @@ export class ProviderCatalog {
|
|
|
1010
1150
|
}
|
|
1011
1151
|
const rank = (g) => (g.provider_id === active ? 0 : g.provider_id.startsWith('custom:') ? 1 : withKeys.has(g.provider_id) ? 2 : 3);
|
|
1012
1152
|
kept.sort((a, b) => rank(a) - rank(b) || (a.provider_id < b.provider_id ? -1 : a.provider_id > b.provider_id ? 1 : 0));
|
|
1013
|
-
for (const g of kept)
|
|
1014
|
-
if (isOpenAiFamilyProvider(g.provider_id))
|
|
1015
|
-
for (const m of g.models)
|
|
1016
|
-
if (/^(@[^:]+:)?(openai\/)?(gpt-5|o[0-9])/.test(m.id))
|
|
1017
|
-
m.supports_fast_tier = true;
|
|
1018
1153
|
const aliases = {};
|
|
1019
1154
|
const rawAliases = model.aliases;
|
|
1020
1155
|
if (isDict(rawAliases))
|
|
@@ -1022,19 +1157,136 @@ export class ProviderCatalog {
|
|
|
1022
1157
|
if (k && v)
|
|
1023
1158
|
aliases[k.trim()] = str(v).trim();
|
|
1024
1159
|
const [defaultBare, defaultProvider] = parseProviderQualifiedModel(defaultModel) ?? [defaultModel, active];
|
|
1025
|
-
|
|
1026
|
-
|
|
1160
|
+
if (source !== this.sourceFingerprint(profileHome)) {
|
|
1161
|
+
if (attempt === 0)
|
|
1162
|
+
return this.models(profileHome, 1);
|
|
1163
|
+
throw new Error('Provider configuration changed during catalog lookup. Try again.');
|
|
1164
|
+
}
|
|
1165
|
+
if (!providerState.availability.authoritative || !localLookup.authoritative)
|
|
1166
|
+
for (const g of kept)
|
|
1167
|
+
unconfirmed.add(g.provider_id);
|
|
1168
|
+
const previous = this.currentModels(profileHome);
|
|
1169
|
+
const availability = this.availability(source, providerState.availability.authoritative && localLookup.authoritative && !unconfirmed.size && source === this.sourceFingerprint(profileHome), previous?.availability?.status === 'available' || previous?.availability?.status === 'stale');
|
|
1170
|
+
for (const g of kept)
|
|
1171
|
+
g.availability = this.availability(source, !unconfirmed.has(g.provider_id) && providerState.availability.authoritative, Boolean(previous?.groups.some((old) => old.provider_id === g.provider_id && old.availability?.status !== 'unavailable')));
|
|
1172
|
+
const defaults = { availability, active_provider: active, default_model: defaultModel, default_provider_id: defaultProvider, default_bare_id: defaultBare };
|
|
1173
|
+
if (!kept.length && pickerDefault) {
|
|
1027
1174
|
const providerId = active ?? 'default';
|
|
1028
|
-
return this.remember(profileHome, { source, unconfirmed }, { ...defaults, groups: [{ provider: 'Default', provider_id: providerId, models: stampModelEntries([{ id: defaultModel, label: labelForModel(defaultModel, []) }], providerId) }], aliases: {}, configured_model_badges: {} });
|
|
1175
|
+
return this.remember(profileHome, { source, generation, unconfirmed, attempt }, { ...defaults, groups: [{ provider: 'Default', provider_id: providerId, models: stampModelEntries([{ id: defaultModel, label: labelForModel(defaultModel, []) }], providerId) }], aliases: {}, configured_model_badges: {} });
|
|
1029
1176
|
}
|
|
1030
1177
|
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) } : {}) }));
|
|
1031
|
-
return this.remember(profileHome, { source, unconfirmed }, { ...defaults, groups: stamped, aliases, configured_model_badges: this.badges(kept, active, defaultModel, fallbackCfg) });
|
|
1178
|
+
return this.remember(profileHome, { source, generation, unconfirmed, attempt }, { ...defaults, groups: stamped, aliases, configured_model_badges: this.badges(kept, active, defaultModel, fallbackCfg) });
|
|
1179
|
+
}
|
|
1180
|
+
pickerLimitsCache = new Map();
|
|
1181
|
+
/** Bounded, shared pool-limit reads; a warm picker serves its snapshot during refresh. */
|
|
1182
|
+
async pickerLimits(profileHome, providers) {
|
|
1183
|
+
const fingerprint = this.sourceFingerprint(profileHome);
|
|
1184
|
+
const source = JSON.stringify([fingerprint, providers]);
|
|
1185
|
+
let cached = this.pickerLimitsCache.get(profileHome);
|
|
1186
|
+
if (cached?.source !== source) {
|
|
1187
|
+
cached = { source, limits: {} };
|
|
1188
|
+
this.pickerLimitsCache.set(profileHome, cached);
|
|
1189
|
+
}
|
|
1190
|
+
const entry = cached;
|
|
1191
|
+
if ((entry.at === undefined || this.deps.now() - entry.at >= ACCOUNT_USAGE_CACHE_TTL_S) && !entry.read) {
|
|
1192
|
+
entry.read = (async () => {
|
|
1193
|
+
try {
|
|
1194
|
+
const sidecar = this.deps.sidecar();
|
|
1195
|
+
entry.limits = sidecar ? (await sidecar.call('usage.limits', { profile_home: profileHome, providers }, { timeoutMs: 3_000 })).limits : {};
|
|
1196
|
+
}
|
|
1197
|
+
catch (error) {
|
|
1198
|
+
entry.limits = {};
|
|
1199
|
+
this.deps.log(`[catalog] pool limits failed: ${str(error.message)}`);
|
|
1200
|
+
}
|
|
1201
|
+
entry.at = this.deps.now();
|
|
1202
|
+
})().finally(() => { delete entry.read; });
|
|
1203
|
+
}
|
|
1204
|
+
if (entry.at === undefined)
|
|
1205
|
+
await entry.read;
|
|
1206
|
+
return fingerprint === this.sourceFingerprint(profileHome) && this.pickerLimitsCache.get(profileHome) === entry ? entry.limits : {};
|
|
1207
|
+
}
|
|
1208
|
+
/** TAL-807: the picker adds pool limits and quota levels without storing them in the model catalog. */
|
|
1209
|
+
async pickerModels(profileHome, profile) {
|
|
1210
|
+
const catalog = await this.models(profileHome);
|
|
1211
|
+
const entries = (g) => [...g.models, ...(g.extra_models ?? [])];
|
|
1212
|
+
const providers = catalog.groups.map((g) => ({ slug: g.provider_id, models: entries(g).map((e) => e.bare_id ?? e.id) }));
|
|
1213
|
+
const limits = await this.pickerLimits(profileHome, providers);
|
|
1214
|
+
const active = (resetsAt) => Date.parse(resetsAt) / 1000 > this.deps.now();
|
|
1215
|
+
for (const g of catalog.groups) {
|
|
1216
|
+
const limit = limits[g.provider_id];
|
|
1217
|
+
if (limit?.scope === 'account' && active(limit.resets_at))
|
|
1218
|
+
g.limit = limit;
|
|
1219
|
+
if (limit?.scope === 'models') {
|
|
1220
|
+
// The sidecar answers by bare id; the picker names entries by id.
|
|
1221
|
+
const cooled = {};
|
|
1222
|
+
for (const e of entries(g)) {
|
|
1223
|
+
const resetsAt = limit.models[e.bare_id ?? e.id];
|
|
1224
|
+
if (resetsAt && active(resetsAt)) {
|
|
1225
|
+
e.limit = { scope: 'model', resets_at: resetsAt };
|
|
1226
|
+
cooled[e.id] = resetsAt;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
if (Object.keys(cooled).length)
|
|
1230
|
+
g.limit = { scope: 'models', models: cooled };
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
const thresholds = (await this.deps.quotaThresholds?.(profile)) ?? QUOTA_THRESHOLD_DEFAULTS;
|
|
1234
|
+
// An account limit already says the login is out; the quota level would only repeat it.
|
|
1235
|
+
const levels = await Promise.all(catalog.groups.map((g) => (g.limit?.scope === 'account' ? Promise.resolve(null) : this.pickerQuotaLevel(profileHome, g.provider_id, thresholds))));
|
|
1236
|
+
catalog.groups.forEach((g, i) => { const level = levels[i]; if (level)
|
|
1237
|
+
g.quota_level = level; });
|
|
1238
|
+
return catalog;
|
|
1239
|
+
}
|
|
1240
|
+
/** TAL-807: account-usage reads the picker started, by `accountUsageCache` key. */
|
|
1241
|
+
pickerUsageReads = new Map();
|
|
1242
|
+
/**
|
|
1243
|
+
* TAL-807: the TAL-411 `remaining` level of the tightest account-scope window in the provider's cached account-usage
|
|
1244
|
+
* read; a model-family window (Opus or Sonnet weekly) never counts. A read past the cache TTL starts a new one, which
|
|
1245
|
+
* the picker waits for only when it has no read at all, and then for at most `PICKER_USAGE_WAIT_MS`; one past
|
|
1246
|
+
* `PICKER_USAGE_MAX_AGE_S` shows nothing.
|
|
1247
|
+
*/
|
|
1248
|
+
async pickerQuotaLevel(profileHome, pid, t) {
|
|
1249
|
+
if (!ACCOUNT_USAGE_PROVIDERS.has(pid))
|
|
1250
|
+
return null;
|
|
1251
|
+
const key = `${profileHome}\0${pid}\0`;
|
|
1252
|
+
let cached = this.accountUsageCache.get(key);
|
|
1253
|
+
if (cached?.source !== this.sourceFingerprint(profileHome))
|
|
1254
|
+
cached = undefined;
|
|
1255
|
+
if (!cached || this.deps.now() - cached.at > ACCOUNT_USAGE_CACHE_TTL_S) {
|
|
1256
|
+
let read = this.pickerUsageReads.get(key);
|
|
1257
|
+
if (!read) {
|
|
1258
|
+
read = this.quota(profileHome, pid).then(() => undefined, () => undefined).finally(() => this.pickerUsageReads.delete(key));
|
|
1259
|
+
this.pickerUsageReads.set(key, read);
|
|
1260
|
+
}
|
|
1261
|
+
if (!cached) {
|
|
1262
|
+
let timer;
|
|
1263
|
+
await Promise.race([read, new Promise((resolve) => { timer = setTimeout(resolve, PICKER_USAGE_WAIT_MS); })]);
|
|
1264
|
+
clearTimeout(timer);
|
|
1265
|
+
cached = this.accountUsageCache.get(key);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
const limits = cached?.limits;
|
|
1269
|
+
if (cached?.source !== this.sourceFingerprint(profileHome) || !limits?.available || this.deps.now() - cached.at > PICKER_USAGE_MAX_AGE_S)
|
|
1270
|
+
return null;
|
|
1271
|
+
const raw = (Array.isArray(limits.windows) ? limits.windows : []).filter((w) => isDict(w) && w.scope !== 'model');
|
|
1272
|
+
const tightest = normalizeQuotaWindows(raw, this.deps.now()).windows
|
|
1273
|
+
.reduce((a, w) => (w.remaining_percent !== null && w.remaining_percent < (a?.remaining_percent ?? Infinity) ? w : a), undefined);
|
|
1274
|
+
return tightest ? quotaLevel(tightest, limits.stale ? 'stale' : 'available', 'remaining', t) : null;
|
|
1032
1275
|
}
|
|
1033
1276
|
/** Stamps the default's entry id and keeps the catalog for `modelOptionFor`. */
|
|
1034
|
-
remember(profileHome, meta, catalog) {
|
|
1277
|
+
async remember(profileHome, meta, catalog) {
|
|
1278
|
+
if (catalog.availability)
|
|
1279
|
+
for (const group of catalog.groups)
|
|
1280
|
+
group.availability ??= catalog.availability;
|
|
1281
|
+
await this.stampSpeedTiers(profileHome, catalog.groups.flatMap((g) => [...g.models, ...(g.extra_models ?? [])]));
|
|
1282
|
+
if (meta.generation !== this.modelGenerations.get(profileHome) || meta.source !== this.sourceFingerprint(profileHome)) {
|
|
1283
|
+
if (meta.attempt === 0)
|
|
1284
|
+
return this.models(profileHome, 1);
|
|
1285
|
+
throw new Error('Provider configuration changed during catalog lookup. Try again.');
|
|
1286
|
+
}
|
|
1035
1287
|
catalog.default_option_id = catalogOptionId(catalog, catalog.default_bare_id ?? null, catalog.default_provider_id ?? null);
|
|
1036
1288
|
this.lastModels.set(profileHome, catalog);
|
|
1037
|
-
this.lastModelsMeta.set(profileHome, {
|
|
1289
|
+
this.lastModelsMeta.set(profileHome, { source: meta.source, unconfirmed: meta.unconfirmed, at: this.deps.now() });
|
|
1038
1290
|
return structuredClone(catalog);
|
|
1039
1291
|
}
|
|
1040
1292
|
/** Python `_configured_model_badges_from_static_catalog`: which picker rows are the main/fallback selections. */
|
|
@@ -1066,14 +1318,17 @@ export class ProviderCatalog {
|
|
|
1066
1318
|
return badges;
|
|
1067
1319
|
}
|
|
1068
1320
|
/** Python `_handle_live_models`. */
|
|
1069
|
-
async liveModels(profileHome, providerRaw) {
|
|
1321
|
+
async liveModels(profileHome, providerRaw, attempt = 0) {
|
|
1322
|
+
const fingerprint = this.sourceFingerprint(profileHome);
|
|
1070
1323
|
const config = await this.deps.config.read(profileHome);
|
|
1071
1324
|
let provider = providerRaw.trim().toLowerCase() || str(modelSection(config).provider).trim().toLowerCase();
|
|
1072
1325
|
if (!provider)
|
|
1073
1326
|
return { error: 'no_provider', models: [] };
|
|
1074
1327
|
provider = resolveProviderAlias(provider);
|
|
1075
1328
|
let ids = await this.liveModelIds(profileHome, provider);
|
|
1076
|
-
|
|
1329
|
+
if (fingerprint !== this.sourceFingerprint(profileHome))
|
|
1330
|
+
return attempt === 0 ? this.liveModels(profileHome, providerRaw, 1) : { availability: this.availability(this.sourceFingerprint(profileHome), false, false), provider, source: 'unavailable', models: [], count: 0 };
|
|
1331
|
+
let source = this.liveLookupFailed(profileHome, provider, fingerprint) ? 'fallback' : 'live';
|
|
1077
1332
|
if (!ids.length) {
|
|
1078
1333
|
const fromConfig = [];
|
|
1079
1334
|
if (provider === 'custom' || provider.startsWith('custom:')) {
|
|
@@ -1083,16 +1338,33 @@ export class ProviderCatalog {
|
|
|
1083
1338
|
fromConfig.push(...[str(cp.model).trim(), ...configuredModelIds(cp.models)].filter(Boolean));
|
|
1084
1339
|
}
|
|
1085
1340
|
}
|
|
1086
|
-
|
|
1087
|
-
|
|
1341
|
+
if (fromConfig.length) {
|
|
1342
|
+
ids = [...new Set(fromConfig)];
|
|
1343
|
+
source = 'config';
|
|
1344
|
+
}
|
|
1345
|
+
else if (source === 'fallback') {
|
|
1346
|
+
ids = (PROVIDER_MODELS[provider] ?? []).map((m) => m.id);
|
|
1347
|
+
source = 'static';
|
|
1348
|
+
}
|
|
1088
1349
|
}
|
|
1089
|
-
// Python: the dropdown-enrichment surface keeps the picker's visibility budget (15 rows past 25) and annotates
|
|
1090
|
-
// OpenAI-family rows with `supports_fast_tier`; the answer carries `count`.
|
|
1091
1350
|
if (provider !== 'nous' && ids.length > MODEL_PICKER_OVERFLOW_THRESHOLD)
|
|
1092
1351
|
ids = ids.slice(0, MODEL_PICKER_VISIBLE_TARGET);
|
|
1093
|
-
const
|
|
1094
|
-
|
|
1095
|
-
|
|
1352
|
+
const models = stampModelEntries(ids.filter(Boolean).map((id) => ({ id, label: labelForModel(id, []) })), provider);
|
|
1353
|
+
await this.stampSpeedTiers(profileHome, models);
|
|
1354
|
+
if (fingerprint !== this.sourceFingerprint(profileHome))
|
|
1355
|
+
return attempt === 0 ? this.liveModels(profileHome, providerRaw, 1) : { availability: this.availability(this.sourceFingerprint(profileHome), false, false), provider, source: 'unavailable', models: [], count: 0 };
|
|
1356
|
+
return { availability: this.availability(fingerprint, source === 'live' || source === 'config', source === 'fallback' && models.length > 0), provider, source, models, count: models.length };
|
|
1357
|
+
}
|
|
1358
|
+
async stampSpeedTiers(profileHome, entries) {
|
|
1359
|
+
if (!entries.length)
|
|
1360
|
+
return;
|
|
1361
|
+
const config = await this.deps.config.read(profileHome);
|
|
1362
|
+
// An unavailable Agent offers no paid tier; writes still require a successful lookup.
|
|
1363
|
+
const tiers = await this.deps.config.speedTiers(profileHome, config, entries.map((e) => ({ model: e.bare_id ?? e.id, provider: e.provider_id ?? '' }))).catch(() => []);
|
|
1364
|
+
entries.forEach((entry, i) => {
|
|
1365
|
+
entry.supported_speed_tiers = tiers[i] ?? [];
|
|
1366
|
+
entry.supports_fast_tier = entry.supported_speed_tiers.some((t) => t.service_tier === 'priority');
|
|
1367
|
+
});
|
|
1096
1368
|
}
|
|
1097
1369
|
/**
|
|
1098
1370
|
* Python `_resolve_model_context_length` inputs: the effective base URL, key, and any configured window for this
|
|
@@ -1155,27 +1427,35 @@ export class ProviderCatalog {
|
|
|
1155
1427
|
if (envVar) {
|
|
1156
1428
|
for (const name of [envVar, ...(PROVIDER_ENV_VAR_ALIASES[pid] ?? [])]) {
|
|
1157
1429
|
const v = str(envValues[name] ?? this.processEnv(name, profileHome)).trim();
|
|
1430
|
+
// A reference owns this slot; only the Agent can resolve it before lower-priority keys.
|
|
1158
1431
|
if (v)
|
|
1159
|
-
return v;
|
|
1432
|
+
return v.startsWith('op://') ? null : v;
|
|
1160
1433
|
}
|
|
1161
1434
|
}
|
|
1162
1435
|
// The same config sources `providerHasKey` counts, so a provider shown as keyed resolves its key.
|
|
1163
1436
|
const model = modelSection(config);
|
|
1164
1437
|
if (canonicaliseProviderId(model.provider) === canonicaliseProviderId(pid) && valueCountsAsApiKey(pid, model.api_key))
|
|
1165
|
-
return str(model.api_key).trim();
|
|
1438
|
+
return str(model.api_key).trim().startsWith('op://') ? null : str(model.api_key).trim();
|
|
1166
1439
|
for (const [key, value] of Object.entries(dict(config.providers))) {
|
|
1167
1440
|
if (providerIdentity(key) === providerIdentity(pid) && isDict(value) && valueCountsAsApiKey(pid, value.api_key))
|
|
1168
|
-
return str(value.api_key).trim();
|
|
1441
|
+
return str(value.api_key).trim().startsWith('op://') ? null : str(value.api_key).trim();
|
|
1169
1442
|
}
|
|
1170
1443
|
return null;
|
|
1171
1444
|
}
|
|
1445
|
+
/** A raw key or the names the Agent resolves inside the profile's secret scope. */
|
|
1446
|
+
usageKey(pid, apiKey) {
|
|
1447
|
+
const envVar = providerEnvVar(pid);
|
|
1448
|
+
return apiKey ? { api_key: apiKey } : { env_vars: envVar ? [envVar, ...(PROVIDER_ENV_VAR_ALIASES[pid] ?? [])] : [] };
|
|
1449
|
+
}
|
|
1172
1450
|
/** Account-usage snapshots by profile home, provider, and pool account. */
|
|
1173
1451
|
accountUsageCache = new Map();
|
|
1452
|
+
quotaReadSequence = 0;
|
|
1174
1453
|
/**
|
|
1175
1454
|
* Python `get_provider_quota`. `credential` scopes the read to one credential-pool account (TAL-548): a benched account
|
|
1176
1455
|
* answers with its local pool state, and every other read uses that account's key.
|
|
1177
1456
|
*/
|
|
1178
1457
|
async quota(profileHome, providerRaw, opts = {}) {
|
|
1458
|
+
const sequence = opts.sequence ?? ++this.quotaReadSequence;
|
|
1179
1459
|
const at = opts.at ?? this.deps.now();
|
|
1180
1460
|
const computed_at = isoAt(at);
|
|
1181
1461
|
const config = await this.deps.config.read(profileHome);
|
|
@@ -1193,7 +1473,9 @@ export class ProviderCatalog {
|
|
|
1193
1473
|
let limits = null;
|
|
1194
1474
|
// Python `_ACCOUNT_USAGE_CACHE_TTL_SECONDS`: a snapshot answers repeat polls for 45 s unless `refresh` is set.
|
|
1195
1475
|
const cacheKey = `${profileHome}\0${provider}\0${credential?.credential_id ?? ''}`;
|
|
1196
|
-
const
|
|
1476
|
+
const hit = this.accountUsageCache.get(cacheKey);
|
|
1477
|
+
const source = this.sourceFingerprint(profileHome);
|
|
1478
|
+
const cached = hit?.source === source ? hit : undefined;
|
|
1197
1479
|
if (cached && !opts.refresh && this.deps.now() - cached.at <= ACCOUNT_USAGE_CACHE_TTL_S)
|
|
1198
1480
|
limits = cached.limits;
|
|
1199
1481
|
else if (sidecar) {
|
|
@@ -1206,7 +1488,12 @@ export class ProviderCatalog {
|
|
|
1206
1488
|
catch (error) {
|
|
1207
1489
|
limits = { available: false, unavailable_reason: str(error.message), windows: [], details: [] };
|
|
1208
1490
|
}
|
|
1209
|
-
|
|
1491
|
+
if (source === this.sourceFingerprint(profileHome)) {
|
|
1492
|
+
if ((this.accountUsageCache.get(cacheKey)?.sequence ?? -1) <= sequence)
|
|
1493
|
+
this.accountUsageCache.set(cacheKey, { at: this.deps.now(), limits, source, sequence });
|
|
1494
|
+
}
|
|
1495
|
+
else
|
|
1496
|
+
limits = null;
|
|
1210
1497
|
}
|
|
1211
1498
|
// The cache keeps the Agent's raw windows; pace is recomputed as of every response.
|
|
1212
1499
|
if (limits)
|
|
@@ -1216,11 +1503,11 @@ export class ProviderCatalog {
|
|
|
1216
1503
|
const reason = str(limits?.unavailable_reason).trim();
|
|
1217
1504
|
return answer(false, 'unavailable', reason ? `${name} account limits are unavailable. ${reason}` : `${name} account limits are unavailable. Confirm provider authentication and try again.`, { account_limits: limits });
|
|
1218
1505
|
}
|
|
1219
|
-
if (provider
|
|
1506
|
+
if (isBalanceProvider(provider)) {
|
|
1220
1507
|
const apiKey = this.apiKeyFor(provider, profileHome, config);
|
|
1221
|
-
const key = credential ? { credential_id: credential.credential_id } :
|
|
1222
|
-
const read =
|
|
1223
|
-
const status =
|
|
1508
|
+
const key = credential ? { credential_id: credential.credential_id } : this.usageKey(provider, apiKey);
|
|
1509
|
+
const read = await this.balance(profileHome, provider, key);
|
|
1510
|
+
const status = read?.status === 'no_key' ? 'no_key' : read?.status === 'ok' ? 'ok' : read?.status === 'http_error' ? read.http_status : null;
|
|
1224
1511
|
if (provider === 'openrouter') {
|
|
1225
1512
|
if (status === 'no_key')
|
|
1226
1513
|
return answer(false, 'no_key', 'OpenRouter quota status needs an OPENROUTER_API_KEY configured on the server.');
|
|
@@ -1254,7 +1541,7 @@ export class ProviderCatalog {
|
|
|
1254
1541
|
return answer(false, 'unavailable', status === 403 ? 'OpenCode Go account limits require an active subscription.' : 'OpenCode Go account limits are temporarily unavailable.', { account_limits: null });
|
|
1255
1542
|
}
|
|
1256
1543
|
// Python `_local_pool_snapshot`: any other provider with a credential pool shows its accounts' local state.
|
|
1257
|
-
const entries = credential ? [credential] : (await this.poolEntries(profileHome, provider, null)) ?? [];
|
|
1544
|
+
const entries = credential ? [credential] : (await this.poolEntries(profileHome, provider, null)).value?.entries ?? [];
|
|
1258
1545
|
if (entries.length) {
|
|
1259
1546
|
const limits = credential ? poolEntryLimits(credential, at) : poolLimits(entries, at);
|
|
1260
1547
|
return limits.available ? answer(true, 'available', `${name} credential pool status loaded.`, { label: 'Credential pool', account_limits: limits }) : answer(false, 'unavailable', `${name} credential pool: all credentials are unavailable.`, { account_limits: limits });
|
|
@@ -1266,7 +1553,7 @@ export class ProviderCatalog {
|
|
|
1266
1553
|
* `apiKey`, the provider's configured key. Null when the pool is unknown (see `poolLookup`).
|
|
1267
1554
|
*/
|
|
1268
1555
|
poolEntries(profileHome, pid, apiKey) {
|
|
1269
|
-
return this.poolLookup(profileHome, `pool\0${pid}`,
|
|
1556
|
+
return this.poolLookup(profileHome, `pool\0${pid}`, (sidecar) => sidecar.call('usage.pool', { profile_home: profileHome, provider: pid, ...this.usageKey(pid, apiKey) }));
|
|
1270
1557
|
}
|
|
1271
1558
|
/** The last answer of each credential-pool lookup, by profile home and lookup. */
|
|
1272
1559
|
lastPoolAnswers = new Map();
|
|
@@ -1275,23 +1562,48 @@ export class ProviderCatalog {
|
|
|
1275
1562
|
* lookup, else null: an unknown pool, never an empty one, so no pool account is ever reported as removed.
|
|
1276
1563
|
*/
|
|
1277
1564
|
async poolLookup(profileHome, lookup, call) {
|
|
1565
|
+
const source = this.sourceFingerprint(profileHome);
|
|
1278
1566
|
const key = `${profileHome}\0${lookup}`;
|
|
1279
1567
|
try {
|
|
1280
1568
|
const sidecar = this.deps.sidecar();
|
|
1281
1569
|
if (!sidecar)
|
|
1282
1570
|
throw new Error('the sidecar is not running');
|
|
1283
1571
|
const answer = await call(sidecar);
|
|
1284
|
-
this.
|
|
1285
|
-
|
|
1572
|
+
if (source !== this.sourceFingerprint(profileHome))
|
|
1573
|
+
return { value: null, authoritative: false };
|
|
1574
|
+
this.lastPoolAnswers.set(key, { source, value: answer });
|
|
1575
|
+
return { value: answer, authoritative: true };
|
|
1286
1576
|
}
|
|
1287
1577
|
catch (error) {
|
|
1288
1578
|
this.deps.log(`[catalog] credential pool lookup ${lookup.replace('\0', ' ')} failed: ${str(error.message)}`);
|
|
1289
|
-
|
|
1579
|
+
const hit = this.lastPoolAnswers.get(key);
|
|
1580
|
+
return { value: hit?.source === source && source === this.sourceFingerprint(profileHome) ? hit.value : null, authoritative: false };
|
|
1290
1581
|
}
|
|
1291
1582
|
}
|
|
1292
|
-
/**
|
|
1583
|
+
/** One shared read of pool membership and scoped key revisions before reusing catalog data. */
|
|
1293
1584
|
poolProviders(profileHome) {
|
|
1294
|
-
|
|
1585
|
+
const inputSource = this.sourceFingerprint(profileHome, false);
|
|
1586
|
+
const readKey = this.sourceFingerprint(profileHome);
|
|
1587
|
+
const pending = this.credentialReads.get(readKey);
|
|
1588
|
+
if (pending)
|
|
1589
|
+
return pending;
|
|
1590
|
+
const read = (async () => {
|
|
1591
|
+
const config = await this.deps.config.read(profileHome);
|
|
1592
|
+
const key_envs = Object.fromEntries(BALANCE_PROVIDERS.filter((pid) => this.apiKeyFor(pid, profileHome, config) === null).map((pid) => [pid, this.usageKey(pid, null).env_vars ?? []]));
|
|
1593
|
+
const result = await this.poolLookup(profileHome, 'providers', (sidecar) => sidecar.call('usage.pool_providers', { profile_home: profileHome, key_envs }));
|
|
1594
|
+
if (inputSource !== this.sourceFingerprint(profileHome, false))
|
|
1595
|
+
return { value: null, authoritative: false };
|
|
1596
|
+
this.scopedKeyAuthority.set(profileHome, result.authoritative);
|
|
1597
|
+
if (result.authoritative)
|
|
1598
|
+
this.scopedKeyRevisions.set(profileHome, JSON.stringify(Object.entries(result.value?.keys ?? {}).sort(([a], [b]) => a.localeCompare(b)).map(([pid, state]) => [pid, state.revision])));
|
|
1599
|
+
return result;
|
|
1600
|
+
})().catch(() => {
|
|
1601
|
+
if (inputSource === this.sourceFingerprint(profileHome, false))
|
|
1602
|
+
this.scopedKeyAuthority.set(profileHome, false);
|
|
1603
|
+
return { value: null, authoritative: false };
|
|
1604
|
+
}).finally(() => { this.credentialReads.delete(readKey); });
|
|
1605
|
+
this.credentialReads.set(readKey, read);
|
|
1606
|
+
return read;
|
|
1295
1607
|
}
|
|
1296
1608
|
/** TAL-548: one key-based balance read through the sidecar, which resolves a pool account's key itself; null when it fails. */
|
|
1297
1609
|
async balance(profileHome, provider, key) {
|
|
@@ -1308,52 +1620,46 @@ export class ProviderCatalog {
|
|
|
1308
1620
|
}
|
|
1309
1621
|
quotaScopeCache = null;
|
|
1310
1622
|
/** Python `_quota_server_scope_id`: a 32-hex id persisted at `<state>/.quota_scope_id` (created atomically once). */
|
|
1311
|
-
quotaServerScopeId() {
|
|
1623
|
+
async quotaServerScopeId() {
|
|
1312
1624
|
if (this.quotaScopeCache)
|
|
1313
1625
|
return this.quotaScopeCache;
|
|
1314
|
-
const
|
|
1315
|
-
if (!
|
|
1626
|
+
const documents = this.deps.documents;
|
|
1627
|
+
if (!documents) {
|
|
1316
1628
|
this.quotaScopeCache = randomUUID().replaceAll('-', '');
|
|
1317
1629
|
return this.quotaScopeCache;
|
|
1318
1630
|
}
|
|
1319
|
-
const
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
}
|
|
1327
|
-
if (!/^[0-9a-f]{32}$/.test(stored)) {
|
|
1328
|
-
stored = randomUUID().replaceAll('-', '');
|
|
1329
|
-
mkdirSync(dir, { recursive: true });
|
|
1330
|
-
const temporary = `${path}.tmp.${String(process.pid)}`;
|
|
1331
|
-
writeFileSync(temporary, `${stored}\n`, 'utf8');
|
|
1332
|
-
renameSync(temporary, path);
|
|
1333
|
-
}
|
|
1631
|
+
const stored = await updateDocument(documents, 'catalog', 'quota-scope', (value) => {
|
|
1632
|
+
const previous = typeof value === 'string' ? value.trim().toLowerCase() : '';
|
|
1633
|
+
if (/^[0-9a-f]{32}$/.test(previous))
|
|
1634
|
+
return { value, result: previous };
|
|
1635
|
+
const id = randomUUID().replaceAll('-', '');
|
|
1636
|
+
return { value: `${id}\n`, result: id };
|
|
1637
|
+
});
|
|
1334
1638
|
this.quotaScopeCache = stored;
|
|
1335
1639
|
return stored;
|
|
1336
1640
|
}
|
|
1337
1641
|
/** Python `_quota_profile_scope_id`. */
|
|
1338
|
-
quotaProfileScopeId(profile) {
|
|
1339
|
-
return `qscope_${createHash('sha256').update(`${this.quotaServerScopeId()}\0${profile}`).digest('hex').slice(0, 32)}`;
|
|
1642
|
+
async quotaProfileScopeId(profile) {
|
|
1643
|
+
return `qscope_${createHash('sha256').update(`${(await this.quotaServerScopeId())}\0${profile}`).digest('hex').slice(0, 32)}`;
|
|
1340
1644
|
}
|
|
1341
1645
|
/**
|
|
1342
1646
|
* Python `get_provider_quotas`: the stable-identity envelope the iOS widget persists. A keyed provider is one source per
|
|
1343
1647
|
* credential-pool account, else one for its own key (Python `_quota_source_descriptors`, TAL-548). Each source id appears
|
|
1344
1648
|
* once (two custom providers can share a slug), ordered by provider, account label, then source id.
|
|
1345
1649
|
*/
|
|
1346
|
-
async quotas(profileHome, profile, opts = {}) {
|
|
1650
|
+
async quotas(profileHome, profile, opts = {}, attempt = 0) {
|
|
1651
|
+
const sequence = ++this.quotaReadSequence;
|
|
1652
|
+
const source = this.sourceFingerprint(profileHome);
|
|
1347
1653
|
const at = this.deps.now();
|
|
1348
1654
|
const { payload: status, pooled: pooledIds } = await this.providerRows(profileHome);
|
|
1349
1655
|
const active = status.active_provider;
|
|
1350
|
-
const scopeId = this.quotaProfileScopeId(profile);
|
|
1656
|
+
const scopeId = (await this.quotaProfileScopeId(profile));
|
|
1351
1657
|
// Python `_quota_source_id(profile, provider, credential_id)`: a pool account's id, or "provider" for the provider's own key.
|
|
1352
1658
|
const sourceId = (pid, credential) => `qsrc_${createHash('sha256').update(`${scopeId}\0${pid}\0${credential}`).digest('hex').slice(0, 32)}`;
|
|
1353
1659
|
// A provider configured only through `hermes auth add` has no key in .env or config.yaml, yet its pool accounts are
|
|
1354
1660
|
// sources, including a signed-out OAuth provider's. An unknown pool (a failed lookup with no earlier answer) never
|
|
1355
1661
|
// reports a requested source as missing.
|
|
1356
|
-
let poolUnknown =
|
|
1662
|
+
let poolUnknown = !status.availability.authoritative;
|
|
1357
1663
|
const pooled = new Set(pooledIds ?? []);
|
|
1358
1664
|
const config = await this.deps.config.read(profileHome);
|
|
1359
1665
|
const perProvider = await Promise.all(status.providers.filter((p) => p.has_key || p.is_custom || pooled.has(str(p.id))).map(async (p) => {
|
|
@@ -1361,30 +1667,80 @@ export class ProviderCatalog {
|
|
|
1361
1667
|
const label = str(p.display_name) || pid;
|
|
1362
1668
|
const apiKey = this.apiKeyFor(pid, profileHome, config);
|
|
1363
1669
|
const found = await this.poolEntries(profileHome, pid, apiKey);
|
|
1364
|
-
if (!found)
|
|
1670
|
+
if (!found.authoritative)
|
|
1365
1671
|
poolUnknown = true;
|
|
1366
|
-
const entries = found ?? [];
|
|
1672
|
+
const entries = found.value?.entries ?? [];
|
|
1367
1673
|
const accounts = entries.map((e) => ({ source_id: sourceId(pid, e.credential_id), provider_id: pid, provider_label: label, account_label: e.label, credential: e }));
|
|
1368
1674
|
// The provider's own key is a source unless a pool account already holds it.
|
|
1369
|
-
const ownKey = !entries.length || (apiKey !== null && !entries.some((e) => e.matches_api_key));
|
|
1675
|
+
const ownKey = found.authoritative && ((!entries.length && (p.has_key === true || p.is_custom === true) && !pooled.has(pid)) || ((apiKey !== null || found.value?.has_api_key === true) && !entries.some((e) => e.matches_api_key)));
|
|
1370
1676
|
return ownKey ? [{ source_id: sourceId(pid, 'provider'), provider_id: pid, provider_label: label, account_label: label, credential: null }, ...accounts] : accounts;
|
|
1371
1677
|
}));
|
|
1372
1678
|
let descriptors = uniqueQuotaSources(perProvider.flat());
|
|
1679
|
+
const membership = new Set(descriptors.map((d) => d.source_id));
|
|
1373
1680
|
const requested = str(opts.sourceId).trim() || null;
|
|
1374
1681
|
if (requested)
|
|
1375
1682
|
descriptors = descriptors.filter((d) => d.source_id === requested);
|
|
1376
|
-
const thresholds = this.deps.quotaThresholds?.(profile) ?? QUOTA_THRESHOLD_DEFAULTS;
|
|
1377
|
-
|
|
1378
|
-
const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false, at, credential: d.credential });
|
|
1683
|
+
const thresholds = (await this.deps.quotaThresholds?.(profile)) ?? QUOTA_THRESHOLD_DEFAULTS;
|
|
1684
|
+
let sources = await Promise.all(descriptors.map(async (d) => {
|
|
1685
|
+
const q = await this.quota(profileHome, d.provider_id, { refresh: opts.refresh ?? false, at, credential: d.credential, sequence });
|
|
1379
1686
|
const limits = dict(q.account_limits);
|
|
1380
1687
|
return classifyQuotaSource({
|
|
1381
|
-
source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.account_label,
|
|
1688
|
+
computed_at: isoAt(at), source_id: d.source_id, provider_id: d.provider_id, provider_label: d.provider_label, account_label: d.account_label,
|
|
1382
1689
|
is_active_provider: d.provider_id === active, supported: q.supported === true, status: str(limits.status) || str(q.status) || 'unavailable',
|
|
1383
1690
|
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 ?? [],
|
|
1384
1691
|
unavailable_reason: limits.unavailable_reason ?? null, retry_after: limits.retry_after ?? null, fetched_at: limits.fetched_at ?? null, message: q.message ?? null,
|
|
1385
1692
|
}, thresholds);
|
|
1386
1693
|
}));
|
|
1387
|
-
|
|
1694
|
+
if (source !== this.sourceFingerprint(profileHome) && attempt === 0)
|
|
1695
|
+
return this.quotas(profileHome, profile, opts, 1);
|
|
1696
|
+
poolUnknown ||= source !== this.sourceFingerprint(profileHome);
|
|
1697
|
+
const cacheKey = `${profileHome}\0${scopeId}`;
|
|
1698
|
+
const cached = source === this.sourceFingerprint(profileHome) ? this.lastQuotaSources.get(cacheKey) : undefined;
|
|
1699
|
+
const prior = cached?.source === source ? cached : undefined;
|
|
1700
|
+
const previous = prior?.sources ?? [];
|
|
1701
|
+
if (!poolUnknown) {
|
|
1702
|
+
sources = sources.map((s) => {
|
|
1703
|
+
const old = previous.find((p) => p.source_id === s.source_id);
|
|
1704
|
+
return s.status === 'unavailable' && old ? this.staleQuotaSource(old, str(s.unavailable_reason || s.message)) : s;
|
|
1705
|
+
});
|
|
1706
|
+
const latestMembership = prior && prior.sequence > sequence ? prior.membership : membership;
|
|
1707
|
+
const sourceSequences = new Map(prior?.sourceSequences);
|
|
1708
|
+
const merged = new Map(previous.filter((s) => latestMembership.has(str(s.source_id))).map((s) => [str(s.source_id), s]));
|
|
1709
|
+
for (const value of sources) {
|
|
1710
|
+
const id = str(value.source_id);
|
|
1711
|
+
if (!latestMembership.has(id) || (sourceSequences.get(id) ?? -1) > sequence)
|
|
1712
|
+
continue;
|
|
1713
|
+
merged.set(id, value);
|
|
1714
|
+
sourceSequences.set(id, sequence);
|
|
1715
|
+
}
|
|
1716
|
+
for (const id of sourceSequences.keys())
|
|
1717
|
+
if (!latestMembership.has(id))
|
|
1718
|
+
sourceSequences.delete(id);
|
|
1719
|
+
const snapshot = uniqueQuotaSources([...merged.values()]);
|
|
1720
|
+
this.lastQuotaSources.set(cacheKey, { source, sequence: Math.max(sequence, prior?.sequence ?? -1), membership: latestMembership, sources: snapshot, sourceSequences });
|
|
1721
|
+
// A delayed read answers with the newest confirmed projection, including confirmed removals.
|
|
1722
|
+
sources = snapshot.filter((s) => !requested || s.source_id === requested);
|
|
1723
|
+
}
|
|
1724
|
+
else {
|
|
1725
|
+
sources = previous.filter((s) => !requested || s.source_id === requested).map((s) => this.staleQuotaSource(s));
|
|
1726
|
+
}
|
|
1727
|
+
const currentSource = this.sourceFingerprint(profileHome);
|
|
1728
|
+
if (currentSource !== source) {
|
|
1729
|
+
poolUnknown = true;
|
|
1730
|
+
sources = [];
|
|
1731
|
+
}
|
|
1732
|
+
const latestMembership = this.lastQuotaSources.get(cacheKey)?.membership;
|
|
1733
|
+
const availability = this.availability(currentSource, !poolUnknown, sources.length > 0);
|
|
1734
|
+
return { availability, version: 1, computed_at: isoAt(at), scope_id: scopeId, profile_id: profile, active_provider: active, requested_source_id: requested, missing_source: Boolean(requested && !poolUnknown && !latestMembership?.has(requested)), sources };
|
|
1735
|
+
}
|
|
1736
|
+
lastQuotaSources = new Map();
|
|
1737
|
+
staleQuotaSource(source, reason = 'Provider data is temporarily unavailable. Showing last-confirmed data.') {
|
|
1738
|
+
return { ...source, status: 'stale', unavailable_reason: reason, message: reason, urgency: { remaining: 'stale', pace: 'stale' } };
|
|
1739
|
+
}
|
|
1740
|
+
availability(source, authoritative, cached) {
|
|
1741
|
+
return { authoritative, status: authoritative ? 'available' : cached ? 'stale' : 'unavailable',
|
|
1742
|
+
unavailable_reason: authoritative ? null : cached ? 'Provider data is temporarily unavailable. Showing last-confirmed data.' : 'Provider data is temporarily unavailable. Try again.',
|
|
1743
|
+
generation: createHash('sha256').update(source).digest('hex') };
|
|
1388
1744
|
}
|
|
1389
1745
|
/** Python `get_provider_cost_history` (OpenRouter only; daily snapshots under `<home>/cost-snapshots`). */
|
|
1390
1746
|
async costHistory(profileHome, providerRaw, days) {
|
|
@@ -1397,9 +1753,9 @@ export class ProviderCatalog {
|
|
|
1397
1753
|
const budget = this.deps.costBudget();
|
|
1398
1754
|
const config = await this.deps.config.read(profileHome);
|
|
1399
1755
|
const apiKey = this.apiKeyFor('openrouter', profileHome, config);
|
|
1400
|
-
|
|
1756
|
+
const info = await this.balance(profileHome, 'openrouter', this.usageKey('openrouter', apiKey));
|
|
1757
|
+
if (info?.status === 'no_key')
|
|
1401
1758
|
return { ok: false, provider, display_name: name, supported: true, status: 'no_key', monthly_budget: budget, message: 'OpenRouter cost history needs an OPENROUTER_API_KEY configured on the server.' };
|
|
1402
|
-
const info = await this.balance(profileHome, 'openrouter', { api_key: apiKey });
|
|
1403
1759
|
if (info?.status !== 'ok' || !info.quota) {
|
|
1404
1760
|
return { ok: false, provider, display_name: name, supported: true, status: 'unavailable', window_days: days, ...costHistoryView(this.readCostSnapshots(profileHome), days, budget), limit: null, label: null, monthly_budget: budget, message: 'OpenRouter cost history is temporarily unavailable. Showing last known data.' };
|
|
1405
1761
|
}
|