titan-agent 6.5.3 → 7.1.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/README.md +200 -47
- package/assets/agent-skills/NOTICE-ECC.md +39 -0
- package/assets/agent-skills/agent-introspection-debugging/SKILL.md +153 -0
- package/assets/agent-skills/continuous-agent-loop/SKILL.md +45 -0
- package/assets/agent-skills/continuous-learning/SKILL.md +131 -0
- package/assets/agent-skills/continuous-learning/config.json +18 -0
- package/assets/agent-skills/continuous-learning/evaluate-session.sh +69 -0
- package/assets/agent-skills/production-audit/SKILL.md +206 -0
- package/assets/agent-skills/recursive-decision-ledger/SKILL.md +79 -0
- package/assets/agent-skills/scientific-thinking-literature-review/SKILL.md +192 -0
- package/assets/agent-skills/scientific-thinking-scholar-evaluation/SKILL.md +160 -0
- package/assets/agent-skills/verification-loop/SKILL.md +126 -0
- package/dist/agent/agent.js +104 -29
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +6 -2
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/autoModeClassifier.js +26 -2
- package/dist/agent/autoModeClassifier.js.map +1 -1
- package/dist/agent/contextManager.js +1 -1
- package/dist/agent/contextManager.js.map +1 -1
- package/dist/agent/goalDriver.js +2 -0
- package/dist/agent/goalDriver.js.map +1 -1
- package/dist/agent/goalSpec.js +229 -0
- package/dist/agent/goalSpec.js.map +1 -0
- package/dist/agent/heartbeat.js +130 -0
- package/dist/agent/heartbeat.js.map +1 -0
- package/dist/agent/missionDecompose.js +38 -0
- package/dist/agent/missionDecompose.js.map +1 -0
- package/dist/agent/muscleMemory.js +291 -0
- package/dist/agent/muscleMemory.js.map +1 -0
- package/dist/agent/observationWriter.js +56 -0
- package/dist/agent/observationWriter.js.map +1 -0
- package/dist/agent/promptSectionCaps.js +6 -0
- package/dist/agent/promptSectionCaps.js.map +1 -1
- package/dist/agent/reminders.js +101 -0
- package/dist/agent/reminders.js.map +1 -0
- package/dist/agent/systemWidgets.js +41 -0
- package/dist/agent/systemWidgets.js.map +1 -0
- package/dist/agent/toolRunner.js +24 -0
- package/dist/agent/toolRunner.js.map +1 -1
- package/dist/agent/toolSearch.js +12 -2
- package/dist/agent/toolSearch.js.map +1 -1
- package/dist/channels/messenger.js +39 -0
- package/dist/channels/messenger.js.map +1 -1
- package/dist/config/config.js +7 -0
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +36 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/eval/harness.js +17 -4
- package/dist/eval/harness.js.map +1 -1
- package/dist/gateway/onboarding.js +91 -0
- package/dist/gateway/onboarding.js.map +1 -0
- package/dist/gateway/routes/skills.js +2 -1
- package/dist/gateway/routes/skills.js.map +1 -1
- package/dist/gateway/routes/studioRouter.js +88 -0
- package/dist/gateway/routes/studioRouter.js.map +1 -0
- package/dist/gateway/routes/watchRouter.js +4 -4
- package/dist/gateway/routes/watchRouter.js.map +1 -1
- package/dist/gateway/server.js +270 -46
- package/dist/gateway/server.js.map +1 -1
- package/dist/gateway/slashCommands.js +31 -0
- package/dist/gateway/slashCommands.js.map +1 -1
- package/dist/mcp/agentTools.js +135 -0
- package/dist/mcp/agentTools.js.map +1 -0
- package/dist/mcp/server.js +19 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/memory/graph.js +2 -0
- package/dist/memory/graph.js.map +1 -1
- package/dist/memory/taxonomy.js +117 -0
- package/dist/memory/taxonomy.js.map +1 -0
- package/dist/providers/errorTaxonomy.js +4 -0
- package/dist/providers/errorTaxonomy.js.map +1 -1
- package/dist/providers/moa.js +151 -0
- package/dist/providers/moa.js.map +1 -0
- package/dist/providers/modelCapabilities.js +78 -2
- package/dist/providers/modelCapabilities.js.map +1 -1
- package/dist/providers/ollama.js +9 -2
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai.js +1 -1
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openai_compat.js +52 -4
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +16 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/recipes/runner.js +8 -1
- package/dist/recipes/runner.js.map +1 -1
- package/dist/recipes/store.js +2 -1
- package/dist/recipes/store.js.map +1 -1
- package/dist/skills/builtin/codebase_explore.js +214 -0
- package/dist/skills/builtin/codebase_explore.js.map +1 -0
- package/dist/skills/builtin/config_audit.js +160 -0
- package/dist/skills/builtin/config_audit.js.map +1 -0
- package/dist/skills/builtin/delegate_agent.js +107 -0
- package/dist/skills/builtin/delegate_agent.js.map +1 -0
- package/dist/skills/builtin/memory_map.js +38 -0
- package/dist/skills/builtin/memory_map.js.map +1 -0
- package/dist/skills/builtin/reminders.js +67 -0
- package/dist/skills/builtin/reminders.js.map +1 -0
- package/dist/skills/builtin/spec.js +139 -0
- package/dist/skills/builtin/spec.js.map +1 -0
- package/dist/skills/builtin/widget_gallery.js +12 -5
- package/dist/skills/builtin/widget_gallery.js.map +1 -1
- package/dist/skills/frontmatterLoader.js +7 -1
- package/dist/skills/frontmatterLoader.js.map +1 -1
- package/dist/skills/registry.js +12 -0
- package/dist/skills/registry.js.map +1 -1
- package/dist/substrate/traceBus.js.map +1 -1
- package/dist/telemetry/traceStore.js +127 -0
- package/dist/telemetry/traceStore.js.map +1 -0
- package/dist/utils/benchMode.js +8 -0
- package/dist/utils/benchMode.js.map +1 -0
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/contrast.js +69 -0
- package/dist/utils/contrast.js.map +1 -0
- package/dist/watch/sessionContext.js +14 -0
- package/dist/watch/sessionContext.js.map +1 -0
- package/dist/watch/sessionTrace.js +51 -0
- package/dist/watch/sessionTrace.js.map +1 -0
- package/dist/watch/studioBuffer.js +53 -0
- package/dist/watch/studioBuffer.js.map +1 -0
- package/package.json +29 -28
- package/ui/dist/assets/AboutYouMemory-57X5SqHQ.js +1 -0
- package/ui/dist/assets/AuditPanel-AyB8ciLZ.js +1 -0
- package/ui/dist/assets/AutonomyPanel-wmVPr-n3.js +1 -0
- package/ui/dist/assets/AutopilotPanel-CbLy4lJP.js +1 -0
- package/ui/dist/assets/AutoresearchPanel-DxRO1rBF.js +1 -0
- package/ui/dist/assets/BackupPanel-BUh9rQ-3.js +1 -0
- package/ui/dist/assets/BrowserPanel-OqKwGrIL.js +1 -0
- package/ui/dist/assets/CPActivity-DGfS7EY3.js +1 -0
- package/ui/dist/assets/CPAgentDetail-p0Kvzhhb.js +1 -0
- package/ui/dist/assets/CPAgents-B6wSSQZM.js +1 -0
- package/ui/dist/assets/CPApprovals-CYv9Do0i.js +1 -0
- package/ui/dist/assets/CPCosts-Dx6Q71rN.js +1 -0
- package/ui/dist/assets/CPDashboard-DA8-tE-l.js +1 -0
- package/ui/dist/assets/CPFiles-BJ49tzai.js +1 -0
- package/ui/dist/assets/CPGoals-h0dKL5V_.js +1 -0
- package/ui/dist/assets/CPInbox-CDvefgNH.js +1 -0
- package/ui/dist/assets/CPIssueDetail-CnmLzzDD.js +1 -0
- package/ui/dist/assets/CPIssues-DknAIMyl.js +1 -0
- package/ui/dist/assets/CPLayout-C2nqgkmY.js +2 -0
- package/ui/dist/assets/CPOrg-tDbHFphX.js +1 -0
- package/ui/dist/assets/CPRuns-UlrIURqG.js +1 -0
- package/ui/dist/assets/CPSocial-B-gHgvEw.js +1 -0
- package/ui/dist/assets/CPVoice-CJy938ZF.js +1 -0
- package/ui/dist/assets/ChannelsPanel-DLbxg1zO.js +1 -0
- package/ui/dist/assets/CheckpointsPanel-BB6TT0aE.js +1 -0
- package/ui/dist/assets/CommandPostHub-fIslwU_t.js +2 -0
- package/ui/dist/assets/ConfirmDialog-6hcNjmAh.js +9 -0
- package/ui/dist/assets/CronPanel-DLpw3Y25.js +1 -0
- package/ui/dist/assets/DataTable-KVqtDzvP.js +1 -0
- package/ui/dist/assets/DreamPanel-lcMXpbEb.js +5 -0
- package/ui/dist/assets/EmptyState-D-Dg8B8r.js +1 -0
- package/ui/dist/assets/EvalHarnessPanel-BKzRiiQw.js +2 -0
- package/ui/dist/assets/EvalPanel-Dtodacmj.js +1 -0
- package/ui/dist/assets/FileViewer-DjcVKkO2.js +60 -0
- package/ui/dist/assets/FilesPanel-BI90Pplh.js +1 -0
- package/ui/dist/assets/FleetPanel-tr0wjAwr.js +1 -0
- package/ui/dist/assets/HomelabPanel-m8vMm5rE.js +1 -0
- package/ui/dist/assets/InfraView-3WriJnyE.js +2 -0
- package/ui/dist/assets/IntegrationsPanel-BmL70fOw.js +1 -0
- package/ui/dist/assets/IntelligenceView-BGVxCKD3.js +2 -0
- package/ui/dist/assets/LearningPanel-CrYLQ6eV.js +1 -0
- package/ui/dist/assets/LiveStudio-CnW3eZYF.js +2 -0
- package/ui/dist/assets/LogsPanel-DbShr4Ic.js +1 -0
- package/ui/dist/assets/McpPanel-uMYEsPI4.js +1 -0
- package/ui/dist/assets/MemoryGraphPanel-C7-Z8f0v.js +1 -0
- package/ui/dist/assets/MemoryTaxonomyPanel-KrxN8U1G.js +1 -0
- package/ui/dist/assets/MemoryWikiPanel-DI3q6IHC.js +1 -0
- package/ui/dist/assets/MeshPanel-D4-g2tlu.js +1 -0
- package/ui/dist/assets/MissionCanvas-D8cjakJe.js +61 -0
- package/ui/dist/assets/MissionChat-BvqCUf1E.js +3 -0
- package/ui/dist/assets/MissionLibrary-CZoJQ9WD.js +1 -0
- package/ui/dist/assets/MissionStart-CXCq-_AD.js +1 -0
- package/ui/dist/assets/NvidiaPanel-BfI1K5Pg.js +1 -0
- package/ui/dist/assets/OrganismPanel-B7Gm9pP-.js +1 -0
- package/ui/dist/assets/OverviewPanel-B9tKWRK2.js +1 -0
- package/ui/dist/assets/PageHeader-CH85uoZG.js +1 -0
- package/ui/dist/assets/PersonaProfilesPanel-BPi8fl-U.js +3 -0
- package/ui/dist/assets/PersonasPanel-Dwr7sycT.js +1 -0
- package/ui/dist/assets/PhoneDeskPanel-D7qc6wUI.js +1 -0
- package/ui/dist/assets/RecipesPanel-DcqPWRSU.js +1 -0
- package/ui/dist/assets/SecurityPanel-Bv_B9Ahf.js +1 -0
- package/ui/dist/assets/SelfImprovePanel-Ca4rGwEG.js +1 -0
- package/ui/dist/assets/SelfProposalsPanel-DteMSASq.js +1 -0
- package/ui/dist/assets/SessionsPanel-BdbbKTbL.js +1 -0
- package/ui/dist/assets/SessionsTab-BJipiVJs.js +1 -0
- package/ui/dist/assets/SettingsPanel-mV7ykOCc.js +1 -0
- package/ui/dist/assets/SettingsView-Bu_wYTT5.js +2 -0
- package/ui/dist/assets/SkeletonLoader-9H7QWa9E.js +1 -0
- package/ui/dist/assets/SkillsPanel-CHwX2kDR.js +1 -0
- package/ui/dist/assets/SomaView-Bpsm4ZW7.js +5 -0
- package/ui/dist/assets/SomaView-Dghh9om4.css +1 -0
- package/ui/dist/assets/StatCard-wmSgMAM3.js +1 -0
- package/ui/dist/assets/TeamsPanel-XJiHxdDX.js +1 -0
- package/ui/dist/assets/TelemetryPanel-_pmGVxpO.js +1 -0
- package/ui/dist/assets/TitanCanvas-Cb7KG80W.css +1 -0
- package/ui/dist/assets/TitanCanvas-CmLy7p2N.js +883 -0
- package/ui/dist/assets/ToolsView-PHizHZBx.js +2 -0
- package/ui/dist/assets/TraceViewer-CYAzIcfB.js +1 -0
- package/ui/dist/assets/TracesPanel-CRSN_dzK.js +1 -0
- package/ui/dist/assets/TrainingPanel-B9rcuMsV.js +1 -0
- package/ui/dist/assets/VoiceOverlay-BL_ns1ZA.js +7 -0
- package/ui/dist/assets/VramPanel-BNPQ06_9.js +1 -0
- package/ui/dist/assets/WatchView-BgAkNnur.js +13 -0
- package/ui/dist/assets/WorkTab-RRpPpEOg.js +1 -0
- package/ui/dist/assets/WorkflowBuilderPanel-BPHIH8pf.js +1 -0
- package/ui/dist/assets/WorkflowsPanel-DqtmT0NB.js +1 -0
- package/ui/dist/assets/approvalHeadline-BRg3ReTi.js +1 -0
- package/ui/dist/assets/archive-DcK9HrbE.js +1 -0
- package/ui/dist/assets/arrow-left-4c2FnES1.js +1 -0
- package/ui/dist/assets/book-open-Ds_yciQm.js +1 -0
- package/ui/dist/assets/bot-CWDCc265.js +1 -0
- package/ui/dist/assets/chart-column-hhZMdCgB.js +1 -0
- package/ui/dist/assets/chevron-right-A_pvnRh_.js +1 -0
- package/ui/dist/assets/circle-check-big-C-JUM7wb.js +1 -0
- package/ui/dist/assets/circle-stop-yZuG9ZAE.js +1 -0
- package/ui/dist/assets/clipboard-list-C2HUEjZc.js +1 -0
- package/ui/dist/assets/clock-DSPgndSD.js +1 -0
- package/ui/dist/assets/clsx-CjueKrWZ.js +1 -0
- package/ui/dist/assets/createLucideIcon-qnlrxww7.js +1 -0
- package/ui/dist/assets/database-C1SgHa8T.js +1 -0
- package/ui/dist/assets/dollar-sign-D9siqK91.js +1 -0
- package/ui/dist/assets/download-CJZWQNfV.js +1 -0
- package/ui/dist/assets/external-link-CpQLWKKE.js +1 -0
- package/ui/dist/assets/file-text-BbGTi-Po.js +1 -0
- package/ui/dist/assets/flask-conical-B1YVft4E.js +1 -0
- package/ui/dist/assets/folder-EbR-w2Mq.js +1 -0
- package/ui/dist/assets/funnel-D2Dkn8Pf.js +1 -0
- package/ui/dist/assets/git-branch-CQ2yNSzU.js +1 -0
- package/ui/dist/assets/git-pull-request-BxL5wlGX.js +1 -0
- package/ui/dist/assets/index-Cz8190xO.css +2 -0
- package/ui/dist/assets/index-WI-E4I3h.js +243 -0
- package/ui/dist/assets/jsx-runtime-B15e6lOy.js +1 -0
- package/ui/dist/assets/layers-BPu5SRjI.js +1 -0
- package/ui/dist/assets/legacy-BIzifT-t.js +3 -0
- package/ui/dist/assets/lightbulb-BY7CpMte.js +1 -0
- package/ui/dist/assets/link-VDQkrGJw.js +1 -0
- package/ui/dist/assets/list-todo-DfnNUV_T.js +1 -0
- package/ui/dist/assets/message-square-BpzrINHz.js +1 -0
- package/ui/dist/assets/mic-De6oZFaV.js +1 -0
- package/ui/dist/assets/missions-y-qg_f9b.js +1 -0
- package/ui/dist/assets/network-CM7y1ats.js +1 -0
- package/ui/dist/assets/pause-C4TDJSfA.js +1 -0
- package/ui/dist/assets/pencil-Dd_vzHE-.js +1 -0
- package/ui/dist/assets/play-9WxZWVHA.js +1 -0
- package/ui/dist/assets/plug-Dm25Wbrc.js +1 -0
- package/ui/dist/assets/plus-CHryCIkk.js +1 -0
- package/ui/dist/assets/radio-D-XnoAVu.js +1 -0
- package/ui/dist/assets/refresh-cw-Disd5wRB.js +1 -0
- package/ui/dist/assets/rotate-ccw-Cw1T2W1T.js +1 -0
- package/ui/dist/assets/save-DP7oNar2.js +1 -0
- package/ui/dist/assets/search-aNVcX-nv.js +1 -0
- package/ui/dist/assets/send-D7iQD57b.js +1 -0
- package/ui/dist/assets/server-DFfeYjoM.js +1 -0
- package/ui/dist/assets/settings-5NWXzgyY.js +1 -0
- package/ui/dist/assets/shared-DLz_Jtg3.js +1 -0
- package/ui/dist/assets/shield-check-Cd50jCAn.js +1 -0
- package/ui/dist/assets/square-DlcoJ1ZM.js +1 -0
- package/ui/dist/assets/terminal-DgFz2Vsf.js +1 -0
- package/ui/dist/assets/test-tube-BimtS92_.js +1 -0
- package/ui/dist/assets/toggle-right-BMaR-sfT.js +1 -0
- package/ui/dist/assets/trash-2-CwUv7ww1.js +1 -0
- package/ui/dist/assets/trending-up-Ba9TwEow.js +1 -0
- package/ui/dist/assets/trophy-Gfv1QC45.js +1 -0
- package/ui/dist/assets/volume-2-BVlfVl2U.js +1 -0
- package/ui/dist/assets/wifi-off-CWLeusSV.js +1 -0
- package/ui/dist/assets/zoom-out-DeggJ7RM.js +1 -0
- package/ui/dist/index.html +4 -2
- package/ui/dist/sw.js +1 -1
- package/ui/dist/assets/AuditPanel-BaftX5MP.js +0 -1
- package/ui/dist/assets/AutonomyPanel-CmaXJV8d.js +0 -11
- package/ui/dist/assets/AutopilotPanel-CbohPtXW.js +0 -1
- package/ui/dist/assets/AutoresearchPanel-DCwiQ2v2.js +0 -6
- package/ui/dist/assets/BackupPanel-DAy9G7Wt.js +0 -1
- package/ui/dist/assets/BrowserPanel-D6UaSxap.js +0 -6
- package/ui/dist/assets/CPActivity-BYDsUoMn.js +0 -1
- package/ui/dist/assets/CPAgentDetail-Bb9-GAJY.js +0 -1
- package/ui/dist/assets/CPAgents-BTZuGnki.js +0 -1
- package/ui/dist/assets/CPApprovals-BME2ARkO.js +0 -1
- package/ui/dist/assets/CPCosts-mOXwWq0i.js +0 -1
- package/ui/dist/assets/CPDashboard-CTlKGXXQ.js +0 -1
- package/ui/dist/assets/CPFiles-BO73EZwJ.js +0 -1
- package/ui/dist/assets/CPGoals-BNWhNP14.js +0 -1
- package/ui/dist/assets/CPInbox-ybe4r8LW.js +0 -16
- package/ui/dist/assets/CPIssueDetail-DaMMGbjw.js +0 -1
- package/ui/dist/assets/CPIssues-BgpQOISw.js +0 -1
- package/ui/dist/assets/CPLayout-bgErauTx.js +0 -2
- package/ui/dist/assets/CPOrg-C8l5SIWJ.js +0 -8
- package/ui/dist/assets/CPRuns-DB8S7loF.js +0 -1
- package/ui/dist/assets/CPSocial-Qw5rKy_4.js +0 -11
- package/ui/dist/assets/CPVoice-DfwHPewA.js +0 -1
- package/ui/dist/assets/ChannelsPanel-DS21E-Iz.js +0 -1
- package/ui/dist/assets/CheckpointsPanel-9-l_8GdG.js +0 -1
- package/ui/dist/assets/CommandPostHub-z55Yrhj1.js +0 -24
- package/ui/dist/assets/CronPanel-kEZK3gpx.js +0 -1
- package/ui/dist/assets/DataTable-PnhPeDBG.js +0 -1
- package/ui/dist/assets/DreamPanel-DIrtlk2K.js +0 -15
- package/ui/dist/assets/EmptyState-CfvU7SdB.js +0 -1
- package/ui/dist/assets/EvalHarnessPanel-uYb_ZEQG.js +0 -2
- package/ui/dist/assets/EvalPanel-CDG-njyj.js +0 -1
- package/ui/dist/assets/FileViewer-CVO3TRs3.js +0 -61
- package/ui/dist/assets/FilesPanel-CaLPog2e.js +0 -16
- package/ui/dist/assets/FleetPanel-BtlaV6pH.js +0 -1
- package/ui/dist/assets/HomelabPanel-BZvN4LPM.js +0 -6
- package/ui/dist/assets/InfraView-C0zEuFrR.js +0 -2
- package/ui/dist/assets/InlineEditableField-mBzpbcLB.js +0 -1
- package/ui/dist/assets/Input-CbrMbLgn.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-B4TyR6jC.js +0 -1
- package/ui/dist/assets/IntelligenceView-DZ4HxYZ3.js +0 -2
- package/ui/dist/assets/LearningPanel-B4RIm_iQ.js +0 -1
- package/ui/dist/assets/LogsPanel-DbP3Mwnr.js +0 -1
- package/ui/dist/assets/McpPanel-BmNUFozc.js +0 -1
- package/ui/dist/assets/MemoryGraphPanel-JPurHV0L.js +0 -16
- package/ui/dist/assets/MemoryWikiPanel-DdvCAZHa.js +0 -6
- package/ui/dist/assets/MeshPanel-Bf-4brXL.js +0 -11
- package/ui/dist/assets/MissionCanvas-CJyhnauG.js +0 -72
- package/ui/dist/assets/MissionChat-C2Nto3zM.js +0 -7
- package/ui/dist/assets/MissionLibrary-RXyBHsLL.js +0 -5
- package/ui/dist/assets/MissionStart-wUkcEP0T.js +0 -1
- package/ui/dist/assets/Modal-BmS7C6FN.js +0 -1
- package/ui/dist/assets/NvidiaPanel-4DlcQlyl.js +0 -1
- package/ui/dist/assets/OrganismPanel-C2LYDPrn.js +0 -1
- package/ui/dist/assets/OverviewPanel-DK8uuq4D.js +0 -1
- package/ui/dist/assets/PageHeader-bnc85wQN.js +0 -1
- package/ui/dist/assets/PersonaProfilesPanel-DGtokXUu.js +0 -8
- package/ui/dist/assets/PersonasPanel-dR8VppcD.js +0 -1
- package/ui/dist/assets/PhoneDeskPanel-CdVOY7H9.js +0 -11
- package/ui/dist/assets/RecipesPanel-D3Zs2wxr.js +0 -1
- package/ui/dist/assets/SecurityPanel-BMERf796.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-BxqWHXCU.js +0 -1
- package/ui/dist/assets/SelfProposalsPanel-CuC94vdx.js +0 -2
- package/ui/dist/assets/SessionsPanel-CQ8qvzp7.js +0 -1
- package/ui/dist/assets/SessionsTab-B3eHV3jf.js +0 -1
- package/ui/dist/assets/SettingsPanel-Cumls7N2.js +0 -1
- package/ui/dist/assets/SettingsView-BtA67pol.js +0 -2
- package/ui/dist/assets/SkeletonLoader-Cax7YTqW.js +0 -1
- package/ui/dist/assets/SkillsPanel-CA415CT8.js +0 -11
- package/ui/dist/assets/SomaView-BtCkeWfZ.css +0 -1
- package/ui/dist/assets/SomaView-eG3wI4S1.js +0 -5
- package/ui/dist/assets/StatCard-Djr487DV.js +0 -1
- package/ui/dist/assets/StatusBadge-JpCoA33_.js +0 -1
- package/ui/dist/assets/Tabs-DJHu-roP.js +0 -1
- package/ui/dist/assets/TeamsPanel-C_Vu6vnY.js +0 -1
- package/ui/dist/assets/TelemetryPanel-D4PXsd-X.js +0 -1
- package/ui/dist/assets/TitanCanvas-BfitWg9R.css +0 -1
- package/ui/dist/assets/TitanCanvas-TuYZSZO7.js +0 -1117
- package/ui/dist/assets/ToolsView-BvTN6vFn.js +0 -2
- package/ui/dist/assets/Tooltip-B1LhTXJ8.js +0 -6
- package/ui/dist/assets/TraceViewer-Bh8Jo3Dr.js +0 -1
- package/ui/dist/assets/TrainingPanel-Dmbd1rJ-.js +0 -1
- package/ui/dist/assets/VoiceOverlay-B_ss7Zg_.js +0 -17
- package/ui/dist/assets/VramPanel-CPICbZ2s.js +0 -1
- package/ui/dist/assets/WatchView-BCgLVfiV.js +0 -13
- package/ui/dist/assets/WorkTab-iptofXO9.js +0 -1
- package/ui/dist/assets/WorkflowsPanel-3RYkFHyf.js +0 -11
- package/ui/dist/assets/approvalHeadline-DB9SgR-9.js +0 -1
- package/ui/dist/assets/archive-gZFnGd5A.js +0 -6
- package/ui/dist/assets/arrow-left-qA0Qer2h.js +0 -6
- package/ui/dist/assets/book-open-DIxdPqmD.js +0 -6
- package/ui/dist/assets/bot-CvIF1uHi.js +0 -6
- package/ui/dist/assets/chart-column-BQiM-Td4.js +0 -6
- package/ui/dist/assets/check-DgyDEFtq.js +0 -6
- package/ui/dist/assets/chevron-right-CQ1NTOtd.js +0 -6
- package/ui/dist/assets/chevron-up-UyfdjRp6.js +0 -6
- package/ui/dist/assets/circle-check-big-CAFcaPPH.js +0 -6
- package/ui/dist/assets/circle-stop-C-8xGv0n.js +0 -6
- package/ui/dist/assets/clipboard-list-9DdqBj1F.js +0 -6
- package/ui/dist/assets/clock-CF9BCIpL.js +0 -6
- package/ui/dist/assets/clsx-B-dksMZM.js +0 -1
- package/ui/dist/assets/dollar-sign-LDRZBae6.js +0 -6
- package/ui/dist/assets/download-BBNXvBO0.js +0 -6
- package/ui/dist/assets/external-link-D_ji4Bec.js +0 -6
- package/ui/dist/assets/file-text-DrdTDEYp.js +0 -6
- package/ui/dist/assets/flask-conical-BGn5PgFX.js +0 -6
- package/ui/dist/assets/folder-BeZZUGnK.js +0 -6
- package/ui/dist/assets/funnel-CAJgGO5M.js +0 -6
- package/ui/dist/assets/git-branch-BcXabeOs.js +0 -6
- package/ui/dist/assets/index-CUgepw5Q.js +0 -337
- package/ui/dist/assets/index-Dm6zcSTS.css +0 -1
- package/ui/dist/assets/layers-DxYDmPA_.js +0 -6
- package/ui/dist/assets/legacy-dHWI7k8P.js +0 -3
- package/ui/dist/assets/lightbulb-B0Du4SM4.js +0 -6
- package/ui/dist/assets/link-CUpaumqr.js +0 -6
- package/ui/dist/assets/list-todo-CDDhmPQB.js +0 -6
- package/ui/dist/assets/message-square-D5VWlv-Q.js +0 -6
- package/ui/dist/assets/mic-Ddcc6Ann.js +0 -6
- package/ui/dist/assets/missions-2zox16Tc.js +0 -1
- package/ui/dist/assets/network-DaJ7ugwX.js +0 -6
- package/ui/dist/assets/pause-BukaK6rs.js +0 -6
- package/ui/dist/assets/pencil-D_YwQBeg.js +0 -6
- package/ui/dist/assets/play-BjdwtYMk.js +0 -6
- package/ui/dist/assets/plug-yISDdeW2.js +0 -6
- package/ui/dist/assets/plus-BSjTdepW.js +0 -6
- package/ui/dist/assets/proxy-CWET5FFX.js +0 -9
- package/ui/dist/assets/radio-B7kX_yWE.js +0 -6
- package/ui/dist/assets/refresh-cw-CwDpzXUn.js +0 -6
- package/ui/dist/assets/rotate-ccw-7SQDTI0_.js +0 -6
- package/ui/dist/assets/save-CjTPAQM0.js +0 -6
- package/ui/dist/assets/search-BP7VlNxX.js +0 -6
- package/ui/dist/assets/send-C2XDuRPf.js +0 -6
- package/ui/dist/assets/server-BE8W-AT4.js +0 -6
- package/ui/dist/assets/shield-check-s7la2J2K.js +0 -6
- package/ui/dist/assets/terminal-B8IK4WsO.js +0 -6
- package/ui/dist/assets/test-tube-Xfn9DJeg.js +0 -11
- package/ui/dist/assets/toggle-right-CHpzOIlj.js +0 -11
- package/ui/dist/assets/trash-2-B1C85ePK.js +0 -6
- package/ui/dist/assets/trending-up-DDGHQZY1.js +0 -6
- package/ui/dist/assets/trophy-D0LhJSPA.js +0 -6
- package/ui/dist/assets/volume-2-DMaE91b5.js +0 -6
- package/ui/dist/assets/wifi-off-DR9h8HGA.js +0 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/errorTaxonomy.ts"],"sourcesContent":["/**\n * TITAN — Centralized Error Taxonomy\n *\n * Replaces scattered error handling across router.ts, toolRunner.ts, and server.ts\n * with a single 7-step classification pipeline. Inspired by Hermes error_classifier.py.\n *\n * Each provider error is classified into a FailoverReason with structured recovery hints\n * (retryable, shouldCompress, shouldRotateCredential, shouldFallback, cooldownMs).\n */\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'ErrorTaxonomy';\n\n// ── Error Categories ──────────────────────────────────────────────\nexport enum FailoverReason {\n RATE_LIMIT = 'rate_limit',\n QUOTA_EXCEEDED = 'quota_exceeded',\n CONTEXT_OVERFLOW = 'context_overflow',\n AUTH_INVALID = 'auth_invalid',\n AUTH_EXPIRED = 'auth_expired',\n MODEL_NOT_FOUND = 'model_not_found',\n CONTENT_FILTERED = 'content_filtered',\n SERVER_ERROR = 'server_error',\n TIMEOUT = 'timeout',\n NETWORK_ERROR = 'network_error',\n OVERLOADED = 'overloaded',\n EMPTY_RESPONSE = 'empty_response',\n FORMAT_ERROR = 'format_error',\n /** Model returned 400 because thinking/extended-thinking is not supported.\n * Recovery: strip thinking flag and retry once on the same provider. */\n THINKING_NOT_SUPPORTED = 'thinking_not_supported',\n UNKNOWN = 'unknown',\n}\n\n// ── Classification Result ─────────────────────────────────────────\nexport interface ClassifiedError {\n /** The category of failure */\n reason: FailoverReason;\n /** HTTP status code if available */\n httpStatus?: number;\n /** Whether this error can be retried with the same provider/key */\n retryable: boolean;\n /** Whether context compression might resolve the issue */\n shouldCompress: boolean;\n /** Whether the API key should be rotated (for credential pools) */\n shouldRotateCredential: boolean;\n /** Whether to try a different provider entirely */\n shouldFallback: boolean;\n /** Suggested delay before retry in ms (0 = no delay) */\n cooldownMs: number;\n /** Human-readable error message */\n message: string;\n}\n\n// ── Default recovery profiles ─────────────────────────────────────\nconst RECOVERY_PROFILES: Record<FailoverReason, Omit<ClassifiedError, 'reason' | 'httpStatus' | 'message'>> = {\n [FailoverReason.RATE_LIMIT]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: false,\n cooldownMs: 5000,\n },\n [FailoverReason.QUOTA_EXCEEDED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: true,\n cooldownMs: 3600000, // 1 hour\n },\n [FailoverReason.CONTEXT_OVERFLOW]: {\n retryable: true,\n shouldCompress: true,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.AUTH_INVALID]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: true,\n cooldownMs: 0,\n },\n [FailoverReason.AUTH_EXPIRED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.MODEL_NOT_FOUND]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 0,\n },\n [FailoverReason.CONTENT_FILTERED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.SERVER_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 2000,\n },\n [FailoverReason.TIMEOUT]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 1000,\n },\n [FailoverReason.NETWORK_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 2000,\n },\n [FailoverReason.OVERLOADED]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 10000,\n },\n [FailoverReason.EMPTY_RESPONSE]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 1000,\n },\n [FailoverReason.FORMAT_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.THINKING_NOT_SUPPORTED]: {\n // Retry once on the same provider after stripping thinking options.\n // The router handles the option mutation; after one retry we fall through.\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.UNKNOWN]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n};\n\n// ── Status code extraction ────────────────────────────────────────\nfunction extractStatus(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const e = error as Record<string, unknown>;\n // Direct status property\n if (typeof e.status === 'number') return e.status;\n if (typeof e.statusCode === 'number') return e.statusCode;\n // Nested in response\n if (e.response && typeof e.response === 'object') {\n const resp = e.response as Record<string, unknown>;\n if (typeof resp.status === 'number') return resp.status;\n if (typeof resp.statusCode === 'number') return resp.statusCode;\n }\n // Check error property (Anthropic SDK)\n if (e.error && typeof e.error === 'object') {\n const nested = e.error as Record<string, unknown>;\n if (typeof nested.status === 'number') return nested.status;\n }\n return undefined;\n}\n\n// ── Error body extraction ─────────────────────────────────────────\nfunction extractBody(error: unknown): string {\n if (!error || typeof error !== 'object') return '';\n const e = error as Record<string, unknown>;\n const parts: string[] = [];\n\n if (typeof e.message === 'string') parts.push(e.message);\n if (e.response && typeof e.response === 'object') {\n const resp = e.response as Record<string, unknown>;\n if (typeof resp.data === 'string') parts.push(resp.data);\n if (resp.data && typeof resp.data === 'object') {\n try { parts.push(JSON.stringify(resp.data)); } catch { /* skip */ }\n }\n }\n if (e.body && typeof e.body === 'string') parts.push(e.body);\n if (e.error && typeof e.error === 'object') {\n try { parts.push(JSON.stringify(e.error)); } catch { /* skip */ }\n }\n return parts.join(' ').toLowerCase();\n}\n\n// ── Build result helper ───────────────────────────────────────────\nfunction buildResult(reason: FailoverReason, status: number | undefined, message: string): ClassifiedError {\n return {\n reason,\n httpStatus: status,\n message,\n ...RECOVERY_PROFILES[reason],\n };\n}\n\n// ── 7-Step Classification Pipeline ────────────────────────────────\n/**\n * Classify a provider error into a structured recovery recommendation.\n *\n * Pipeline steps:\n * 1. Extract HTTP status code\n * 2. Extract error body text\n * 3. Match exact status codes (429, 401, 402, 404, 503, 529)\n * 4. Disambiguate 400 (context overflow vs content filter vs format error)\n * 5. Disambiguate 402 (billing vs quota)\n * 6. Match network/timeout patterns from error message\n * 7. Default to UNKNOWN\n */\nexport function classifyProviderError(error: unknown): ClassifiedError {\n // Step 1: Extract HTTP status\n const status = extractStatus(error);\n const msg = (error as Error)?.message || String(error);\n\n // Step 2: Extract body text for pattern matching\n const body = extractBody(error);\n\n // Step 3: Match exact status codes\n if (status === 429) {\n return buildResult(FailoverReason.RATE_LIMIT, status, msg);\n }\n\n if (status === 401) {\n // Step 3a: Disambiguate expired vs invalid auth\n if (body.includes('expired') || body.includes('token expired') || body.includes('refresh')) {\n return buildResult(FailoverReason.AUTH_EXPIRED, status, msg);\n }\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n if (status === 403) {\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n if (status === 404) {\n if (body.includes('model') || body.includes('not found') || body.includes('does not exist')) {\n return buildResult(FailoverReason.MODEL_NOT_FOUND, status, msg);\n }\n return buildResult(FailoverReason.UNKNOWN, status, msg);\n }\n\n // Step 4: Disambiguate 400 errors\n if (status === 400) {\n if (body.includes('context length') || body.includes('maximum context') ||\n body.includes('max tokens') || body.includes('token limit') ||\n body.includes('too long') || body.includes('too many tokens') ||\n body.includes('context window') || body.includes('input too large')) {\n return buildResult(FailoverReason.CONTEXT_OVERFLOW, status, msg);\n }\n if (body.includes('content_filter') || body.includes('content filter') ||\n body.includes('safety') || body.includes('harmful') ||\n body.includes('flagged') || body.includes('blocked')) {\n return buildResult(FailoverReason.CONTENT_FILTERED, status, msg);\n }\n // Detect thinking-not-supported (e.g. Ollama qwen3.5:4b, some OpenAI-compat endpoints)\n if (body.includes('does not support thinking') || body.includes('thinking is not supported') ||\n body.includes('extended-thinking') || body.includes('think_mode') ||\n body.includes('budget_tokens') || body.includes('thinking_mode')) {\n return buildResult(FailoverReason.THINKING_NOT_SUPPORTED, status, msg);\n }\n if (body.includes('invalid') || body.includes('malformed') ||\n body.includes('parse') || body.includes('json')) {\n return buildResult(FailoverReason.FORMAT_ERROR, status, msg);\n }\n return buildResult(FailoverReason.FORMAT_ERROR, status, msg);\n }\n\n // Step 5: Disambiguate 402 (billing vs transient quota)\n if (status === 402) {\n if (body.includes('billing') || body.includes('payment') ||\n body.includes('subscription') || body.includes('plan')) {\n return buildResult(FailoverReason.QUOTA_EXCEEDED, status, msg);\n }\n // Default 402 to quota exceeded (usually means out of credits)\n return buildResult(FailoverReason.QUOTA_EXCEEDED, status, msg);\n }\n\n // Anthropic overloaded (529)\n if (status === 529) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n\n // 5xx server errors\n if (status && status >= 500 && status < 600) {\n if (body.includes('overloaded') || body.includes('capacity')) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n return buildResult(FailoverReason.SERVER_ERROR, status, msg);\n }\n\n // Step 6: Match patterns from error message (no status code available)\n const lowerMsg = msg.toLowerCase();\n\n // Timeout patterns\n if (lowerMsg.includes('timed out') || lowerMsg.includes('timeout') ||\n lowerMsg.includes('etimedout') || lowerMsg.includes('aborted')) {\n // v4.10.0-local (cost cap): claude-code timeouts are NOT retryable.\n // Each retry burns another ~2 min of MAX plan quota on what is\n // probably a stuck tool loop; better to fail fast and let the\n // fallback-ladder try a cheaper model.\n if (lowerMsg.includes('claude cli') || lowerMsg.includes('claude-code')) {\n return {\n ...buildResult(FailoverReason.TIMEOUT, status, msg),\n retryable: false,\n shouldFallback: true,\n cooldownMs: 0,\n };\n }\n return buildResult(FailoverReason.TIMEOUT, status, msg);\n }\n\n // Rate limit patterns (from message without status code)\n if (lowerMsg.includes('rate limit') || lowerMsg.includes('rate_limit') ||\n lowerMsg.includes('too many requests') || lowerMsg.includes('quota exceeded') ||\n lowerMsg.includes('429')) {\n return buildResult(FailoverReason.RATE_LIMIT, status, msg);\n }\n\n // Network/connection patterns\n if (lowerMsg.includes('econnrefused') || lowerMsg.includes('econnreset') ||\n lowerMsg.includes('econnaborted') || lowerMsg.includes('epipe') ||\n lowerMsg.includes('enotfound') || lowerMsg.includes('fetch failed') ||\n lowerMsg.includes('network error') || lowerMsg.includes('socket hang up') ||\n lowerMsg.includes('connection closed') || lowerMsg.includes('connection refused') ||\n lowerMsg.includes('dns')) {\n return buildResult(FailoverReason.NETWORK_ERROR, status, msg);\n }\n\n // Overloaded patterns\n if (lowerMsg.includes('overloaded') || lowerMsg.includes('service unavailable') ||\n lowerMsg.includes('capacity') || lowerMsg.includes('503') || lowerMsg.includes('502')) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n\n // Server error patterns\n if (lowerMsg.includes('500') || lowerMsg.includes('internal server error') ||\n lowerMsg.includes('server error')) {\n return buildResult(FailoverReason.SERVER_ERROR, status, msg);\n }\n\n // Auth patterns from message\n if (lowerMsg.includes('unauthorized') || lowerMsg.includes('invalid api key') ||\n lowerMsg.includes('invalid_api_key') || lowerMsg.includes('authentication')) {\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n // Context overflow patterns from message\n if (lowerMsg.includes('context length') || lowerMsg.includes('token limit') ||\n lowerMsg.includes('too long') || lowerMsg.includes('context window')) {\n return buildResult(FailoverReason.CONTEXT_OVERFLOW, status, msg);\n }\n\n // Empty response\n if (lowerMsg.includes('empty response') || lowerMsg.includes('no content') ||\n lowerMsg.includes('null response')) {\n return buildResult(FailoverReason.EMPTY_RESPONSE, status, msg);\n }\n\n // Step 7: Default\n logger.debug(COMPONENT, `Unclassified error (status=${status}): ${msg.slice(0, 200)}`);\n return buildResult(FailoverReason.UNKNOWN, status, msg);\n}\n\n/**\n * Check if a classified error should prevent the circuit breaker from opening.\n * Auth errors and model-not-found are config problems, not provider instability.\n */\nexport function shouldAffectCircuitBreaker(classified: ClassifiedError): boolean {\n return classified.reason !== FailoverReason.AUTH_INVALID\n && classified.reason !== FailoverReason.AUTH_EXPIRED\n && classified.reason !== FailoverReason.MODEL_NOT_FOUND\n && classified.reason !== FailoverReason.CONTENT_FILTERED\n && classified.reason !== FailoverReason.FORMAT_ERROR;\n}\n\n/**\n * Hunt Finding #37 (2026-04-14): a shared helper for building errors from\n * a failed fetch Response. Previous code pattern was:\n *\n * throw new Error(`Provider error (${response.status}): ${errorText}`);\n *\n * This dropped the `Retry-After` header on the floor. Downstream code in\n * router.ts tried `(error as Response)?.headers?.get?.('Retry-After')`\n * which always returned undefined at runtime because the error is an\n * Error object, not a Response. Result: Retry-After headers were NEVER\n * respected, even though the code pretended to.\n *\n * New pattern: use this helper to attach status + parsed Retry-After\n * (in ms) + the original body snippet to the error, so router.ts can\n * read them back via typed properties.\n */\nexport interface ProviderHttpError extends Error {\n status?: number;\n retryAfterMs?: number | null;\n provider?: string;\n model?: string;\n}\n\nexport function createProviderError(\n providerDisplayName: string,\n response: { status: number; headers?: { get(name: string): string | null } },\n errorText: string,\n opts?: { provider?: string; model?: string },\n): ProviderHttpError {\n const err: ProviderHttpError = new Error(\n `${providerDisplayName} error (${response.status}): ${errorText}`,\n );\n err.status = response.status;\n err.provider = opts?.provider;\n err.model = opts?.model;\n\n // Parse Retry-After at throw time — router.ts reads err.retryAfterMs later\n try {\n const raw = response.headers?.get('Retry-After');\n if (raw) {\n const sec = parseInt(raw, 10);\n if (Number.isFinite(sec)) {\n err.retryAfterMs = Math.max(0, Math.min(sec * 1000, 300_000));\n } else {\n const date = new Date(raw);\n if (!isNaN(date.getTime())) {\n err.retryAfterMs = Math.max(0, Math.min(date.getTime() - Date.now(), 300_000));\n }\n }\n }\n } catch {\n // Header parsing failures are non-fatal — just fall back to calculated backoff.\n }\n\n return err;\n}\n"],"mappings":"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","names":["FailoverReason"]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/errorTaxonomy.ts"],"sourcesContent":["/**\n * TITAN — Centralized Error Taxonomy\n *\n * Replaces scattered error handling across router.ts, toolRunner.ts, and server.ts\n * with a single 7-step classification pipeline. Inspired by Hermes error_classifier.py.\n *\n * Each provider error is classified into a FailoverReason with structured recovery hints\n * (retryable, shouldCompress, shouldRotateCredential, shouldFallback, cooldownMs).\n */\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'ErrorTaxonomy';\n\n// ── Error Categories ──────────────────────────────────────────────\nexport enum FailoverReason {\n RATE_LIMIT = 'rate_limit',\n QUOTA_EXCEEDED = 'quota_exceeded',\n CONTEXT_OVERFLOW = 'context_overflow',\n AUTH_INVALID = 'auth_invalid',\n AUTH_EXPIRED = 'auth_expired',\n MODEL_NOT_FOUND = 'model_not_found',\n CONTENT_FILTERED = 'content_filtered',\n SERVER_ERROR = 'server_error',\n TIMEOUT = 'timeout',\n NETWORK_ERROR = 'network_error',\n OVERLOADED = 'overloaded',\n EMPTY_RESPONSE = 'empty_response',\n FORMAT_ERROR = 'format_error',\n /** Model returned 400 because thinking/extended-thinking is not supported.\n * Recovery: strip thinking flag and retry once on the same provider. */\n THINKING_NOT_SUPPORTED = 'thinking_not_supported',\n UNKNOWN = 'unknown',\n}\n\n// ── Classification Result ─────────────────────────────────────────\nexport interface ClassifiedError {\n /** The category of failure */\n reason: FailoverReason;\n /** HTTP status code if available */\n httpStatus?: number;\n /** Whether this error can be retried with the same provider/key */\n retryable: boolean;\n /** Whether context compression might resolve the issue */\n shouldCompress: boolean;\n /** Whether the API key should be rotated (for credential pools) */\n shouldRotateCredential: boolean;\n /** Whether to try a different provider entirely */\n shouldFallback: boolean;\n /** Suggested delay before retry in ms (0 = no delay) */\n cooldownMs: number;\n /** Human-readable error message */\n message: string;\n}\n\n// ── Default recovery profiles ─────────────────────────────────────\nconst RECOVERY_PROFILES: Record<FailoverReason, Omit<ClassifiedError, 'reason' | 'httpStatus' | 'message'>> = {\n [FailoverReason.RATE_LIMIT]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: false,\n cooldownMs: 5000,\n },\n [FailoverReason.QUOTA_EXCEEDED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: true,\n cooldownMs: 3600000, // 1 hour\n },\n [FailoverReason.CONTEXT_OVERFLOW]: {\n retryable: true,\n shouldCompress: true,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.AUTH_INVALID]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: true,\n cooldownMs: 0,\n },\n [FailoverReason.AUTH_EXPIRED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: true,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.MODEL_NOT_FOUND]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 0,\n },\n [FailoverReason.CONTENT_FILTERED]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.SERVER_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 2000,\n },\n [FailoverReason.TIMEOUT]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 1000,\n },\n [FailoverReason.NETWORK_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 2000,\n },\n [FailoverReason.OVERLOADED]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: true,\n cooldownMs: 10000,\n },\n [FailoverReason.EMPTY_RESPONSE]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 1000,\n },\n [FailoverReason.FORMAT_ERROR]: {\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.THINKING_NOT_SUPPORTED]: {\n // Retry once on the same provider after stripping thinking options.\n // The router handles the option mutation; after one retry we fall through.\n retryable: true,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n [FailoverReason.UNKNOWN]: {\n retryable: false,\n shouldCompress: false,\n shouldRotateCredential: false,\n shouldFallback: false,\n cooldownMs: 0,\n },\n};\n\n// ── Status code extraction ────────────────────────────────────────\nfunction extractStatus(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const e = error as Record<string, unknown>;\n // Direct status property\n if (typeof e.status === 'number') return e.status;\n if (typeof e.statusCode === 'number') return e.statusCode;\n // Nested in response\n if (e.response && typeof e.response === 'object') {\n const resp = e.response as Record<string, unknown>;\n if (typeof resp.status === 'number') return resp.status;\n if (typeof resp.statusCode === 'number') return resp.statusCode;\n }\n // Check error property (Anthropic SDK)\n if (e.error && typeof e.error === 'object') {\n const nested = e.error as Record<string, unknown>;\n if (typeof nested.status === 'number') return nested.status;\n }\n return undefined;\n}\n\n// ── Error body extraction ─────────────────────────────────────────\nfunction extractBody(error: unknown): string {\n if (!error || typeof error !== 'object') return '';\n const e = error as Record<string, unknown>;\n const parts: string[] = [];\n\n if (typeof e.message === 'string') parts.push(e.message);\n if (e.response && typeof e.response === 'object') {\n const resp = e.response as Record<string, unknown>;\n if (typeof resp.data === 'string') parts.push(resp.data);\n if (resp.data && typeof resp.data === 'object') {\n try { parts.push(JSON.stringify(resp.data)); } catch { /* skip */ }\n }\n }\n if (e.body && typeof e.body === 'string') parts.push(e.body);\n if (e.error && typeof e.error === 'object') {\n try { parts.push(JSON.stringify(e.error)); } catch { /* skip */ }\n }\n return parts.join(' ').toLowerCase();\n}\n\n// ── Build result helper ───────────────────────────────────────────\nfunction buildResult(reason: FailoverReason, status: number | undefined, message: string): ClassifiedError {\n return {\n reason,\n httpStatus: status,\n message,\n ...RECOVERY_PROFILES[reason],\n };\n}\n\n// ── 7-Step Classification Pipeline ────────────────────────────────\n/**\n * Classify a provider error into a structured recovery recommendation.\n *\n * Pipeline steps:\n * 1. Extract HTTP status code\n * 2. Extract error body text\n * 3. Match exact status codes (429, 401, 402, 404, 503, 529)\n * 4. Disambiguate 400 (context overflow vs content filter vs format error)\n * 5. Disambiguate 402 (billing vs quota)\n * 6. Match network/timeout patterns from error message\n * 7. Default to UNKNOWN\n */\nexport function classifyProviderError(error: unknown): ClassifiedError {\n // Step 1: Extract HTTP status\n const status = extractStatus(error);\n const msg = (error as Error)?.message || String(error);\n\n // Step 2: Extract body text for pattern matching\n const body = extractBody(error);\n\n // Step 3: Match exact status codes\n // v7.1: a gateway with no route for this model is MODEL_NOT_FOUND, not a\n // format problem — LiteLLM phrases it \"no healthy deployments\" / \"No\n // fallback model group\". Retrying the same request 5x just burns ~30s per\n // call (the exact failure mode that zeroed the GX10 tier in the July\n // benchmark). Non-retryable; fail fast so the router can fall back.\n const bodyLower = body.toLowerCase();\n if (bodyLower.includes('no healthy deployments') || bodyLower.includes('no fallback model group')) {\n return buildResult(FailoverReason.MODEL_NOT_FOUND, status, msg);\n }\n\n if (status === 429) {\n return buildResult(FailoverReason.RATE_LIMIT, status, msg);\n }\n\n if (status === 401) {\n // Step 3a: Disambiguate expired vs invalid auth\n if (body.includes('expired') || body.includes('token expired') || body.includes('refresh')) {\n return buildResult(FailoverReason.AUTH_EXPIRED, status, msg);\n }\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n if (status === 403) {\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n if (status === 404) {\n if (body.includes('model') || body.includes('not found') || body.includes('does not exist')) {\n return buildResult(FailoverReason.MODEL_NOT_FOUND, status, msg);\n }\n return buildResult(FailoverReason.UNKNOWN, status, msg);\n }\n\n // Step 4: Disambiguate 400 errors\n if (status === 400) {\n if (body.includes('context length') || body.includes('maximum context') ||\n body.includes('max tokens') || body.includes('token limit') ||\n body.includes('too long') || body.includes('too many tokens') ||\n body.includes('context window') || body.includes('input too large')) {\n return buildResult(FailoverReason.CONTEXT_OVERFLOW, status, msg);\n }\n if (body.includes('content_filter') || body.includes('content filter') ||\n body.includes('safety') || body.includes('harmful') ||\n body.includes('flagged') || body.includes('blocked')) {\n return buildResult(FailoverReason.CONTENT_FILTERED, status, msg);\n }\n // Detect thinking-not-supported (e.g. Ollama qwen3.5:4b, some OpenAI-compat endpoints)\n if (body.includes('does not support thinking') || body.includes('thinking is not supported') ||\n body.includes('extended-thinking') || body.includes('think_mode') ||\n body.includes('budget_tokens') || body.includes('thinking_mode')) {\n return buildResult(FailoverReason.THINKING_NOT_SUPPORTED, status, msg);\n }\n if (body.includes('invalid') || body.includes('malformed') ||\n body.includes('parse') || body.includes('json')) {\n return buildResult(FailoverReason.FORMAT_ERROR, status, msg);\n }\n return buildResult(FailoverReason.FORMAT_ERROR, status, msg);\n }\n\n // Step 5: Disambiguate 402 (billing vs transient quota)\n if (status === 402) {\n if (body.includes('billing') || body.includes('payment') ||\n body.includes('subscription') || body.includes('plan')) {\n return buildResult(FailoverReason.QUOTA_EXCEEDED, status, msg);\n }\n // Default 402 to quota exceeded (usually means out of credits)\n return buildResult(FailoverReason.QUOTA_EXCEEDED, status, msg);\n }\n\n // Anthropic overloaded (529)\n if (status === 529) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n\n // 5xx server errors\n if (status && status >= 500 && status < 600) {\n if (body.includes('overloaded') || body.includes('capacity')) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n return buildResult(FailoverReason.SERVER_ERROR, status, msg);\n }\n\n // Step 6: Match patterns from error message (no status code available)\n const lowerMsg = msg.toLowerCase();\n\n // Timeout patterns\n if (lowerMsg.includes('timed out') || lowerMsg.includes('timeout') ||\n lowerMsg.includes('etimedout') || lowerMsg.includes('aborted')) {\n // v4.10.0-local (cost cap): claude-code timeouts are NOT retryable.\n // Each retry burns another ~2 min of MAX plan quota on what is\n // probably a stuck tool loop; better to fail fast and let the\n // fallback-ladder try a cheaper model.\n if (lowerMsg.includes('claude cli') || lowerMsg.includes('claude-code')) {\n return {\n ...buildResult(FailoverReason.TIMEOUT, status, msg),\n retryable: false,\n shouldFallback: true,\n cooldownMs: 0,\n };\n }\n return buildResult(FailoverReason.TIMEOUT, status, msg);\n }\n\n // Rate limit patterns (from message without status code)\n if (lowerMsg.includes('rate limit') || lowerMsg.includes('rate_limit') ||\n lowerMsg.includes('too many requests') || lowerMsg.includes('quota exceeded') ||\n lowerMsg.includes('429')) {\n return buildResult(FailoverReason.RATE_LIMIT, status, msg);\n }\n\n // Network/connection patterns\n if (lowerMsg.includes('econnrefused') || lowerMsg.includes('econnreset') ||\n lowerMsg.includes('econnaborted') || lowerMsg.includes('epipe') ||\n lowerMsg.includes('enotfound') || lowerMsg.includes('fetch failed') ||\n lowerMsg.includes('network error') || lowerMsg.includes('socket hang up') ||\n lowerMsg.includes('connection closed') || lowerMsg.includes('connection refused') ||\n lowerMsg.includes('dns')) {\n return buildResult(FailoverReason.NETWORK_ERROR, status, msg);\n }\n\n // Overloaded patterns\n if (lowerMsg.includes('overloaded') || lowerMsg.includes('service unavailable') ||\n lowerMsg.includes('capacity') || lowerMsg.includes('503') || lowerMsg.includes('502')) {\n return buildResult(FailoverReason.OVERLOADED, status, msg);\n }\n\n // Server error patterns\n if (lowerMsg.includes('500') || lowerMsg.includes('internal server error') ||\n lowerMsg.includes('server error')) {\n return buildResult(FailoverReason.SERVER_ERROR, status, msg);\n }\n\n // Auth patterns from message\n if (lowerMsg.includes('unauthorized') || lowerMsg.includes('invalid api key') ||\n lowerMsg.includes('invalid_api_key') || lowerMsg.includes('authentication')) {\n return buildResult(FailoverReason.AUTH_INVALID, status, msg);\n }\n\n // Context overflow patterns from message\n if (lowerMsg.includes('context length') || lowerMsg.includes('token limit') ||\n lowerMsg.includes('too long') || lowerMsg.includes('context window')) {\n return buildResult(FailoverReason.CONTEXT_OVERFLOW, status, msg);\n }\n\n // Empty response\n if (lowerMsg.includes('empty response') || lowerMsg.includes('no content') ||\n lowerMsg.includes('null response')) {\n return buildResult(FailoverReason.EMPTY_RESPONSE, status, msg);\n }\n\n // Step 7: Default\n logger.debug(COMPONENT, `Unclassified error (status=${status}): ${msg.slice(0, 200)}`);\n return buildResult(FailoverReason.UNKNOWN, status, msg);\n}\n\n/**\n * Check if a classified error should prevent the circuit breaker from opening.\n * Auth errors and model-not-found are config problems, not provider instability.\n */\nexport function shouldAffectCircuitBreaker(classified: ClassifiedError): boolean {\n return classified.reason !== FailoverReason.AUTH_INVALID\n && classified.reason !== FailoverReason.AUTH_EXPIRED\n && classified.reason !== FailoverReason.MODEL_NOT_FOUND\n && classified.reason !== FailoverReason.CONTENT_FILTERED\n && classified.reason !== FailoverReason.FORMAT_ERROR;\n}\n\n/**\n * Hunt Finding #37 (2026-04-14): a shared helper for building errors from\n * a failed fetch Response. Previous code pattern was:\n *\n * throw new Error(`Provider error (${response.status}): ${errorText}`);\n *\n * This dropped the `Retry-After` header on the floor. Downstream code in\n * router.ts tried `(error as Response)?.headers?.get?.('Retry-After')`\n * which always returned undefined at runtime because the error is an\n * Error object, not a Response. Result: Retry-After headers were NEVER\n * respected, even though the code pretended to.\n *\n * New pattern: use this helper to attach status + parsed Retry-After\n * (in ms) + the original body snippet to the error, so router.ts can\n * read them back via typed properties.\n */\nexport interface ProviderHttpError extends Error {\n status?: number;\n retryAfterMs?: number | null;\n provider?: string;\n model?: string;\n}\n\nexport function createProviderError(\n providerDisplayName: string,\n response: { status: number; headers?: { get(name: string): string | null } },\n errorText: string,\n opts?: { provider?: string; model?: string },\n): ProviderHttpError {\n const err: ProviderHttpError = new Error(\n `${providerDisplayName} error (${response.status}): ${errorText}`,\n );\n err.status = response.status;\n err.provider = opts?.provider;\n err.model = opts?.model;\n\n // Parse Retry-After at throw time — router.ts reads err.retryAfterMs later\n try {\n const raw = response.headers?.get('Retry-After');\n if (raw) {\n const sec = parseInt(raw, 10);\n if (Number.isFinite(sec)) {\n err.retryAfterMs = Math.max(0, Math.min(sec * 1000, 300_000));\n } else {\n const date = new Date(raw);\n if (!isNaN(date.getTime())) {\n err.retryAfterMs = Math.max(0, Math.min(date.getTime() - Date.now(), 300_000));\n }\n }\n }\n } catch {\n // Header parsing failures are non-fatal — just fall back to calculated backoff.\n }\n\n return err;\n}\n"],"mappings":"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","names":["FailoverReason"]}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { LLMProvider } from "./base.js";
|
|
3
|
+
import { loadConfig } from "../config/config.js";
|
|
4
|
+
import logger from "../utils/logger.js";
|
|
5
|
+
const COMPONENT = "MoA";
|
|
6
|
+
const DEFAULT_REFERENCE_TIMEOUT_MS = 45e3;
|
|
7
|
+
const DEFAULT_REFERENCE_MAX_TOKENS = 1024;
|
|
8
|
+
const TOOL_RESULT_TRIM = 4e3;
|
|
9
|
+
const ADVISOR_SYSTEM_PROMPT = [
|
|
10
|
+
"You are a reference advisor inside a Mixture-of-Agents process. You are NOT the acting agent:",
|
|
11
|
+
"you cannot call tools, and the user never sees your words. Another model (the aggregator) acts",
|
|
12
|
+
"and answers \u2014 your output is private guidance handed to it. Give concise, concrete advice:",
|
|
13
|
+
"the likely best next steps, tool-use strategy, risks, and anything the aggregator might miss.",
|
|
14
|
+
"If the conversation references tools or systems you cannot access, advise from reasoning alone \u2014",
|
|
15
|
+
"never refuse for lack of access."
|
|
16
|
+
].join(" ");
|
|
17
|
+
function trimForReference(messages) {
|
|
18
|
+
const out = [{ role: "system", content: ADVISOR_SYSTEM_PROMPT }];
|
|
19
|
+
for (const m of messages) {
|
|
20
|
+
if (m.role === "system") continue;
|
|
21
|
+
if (m.role === "tool") {
|
|
22
|
+
const body = (m.content || "").slice(0, TOOL_RESULT_TRIM);
|
|
23
|
+
out.push({ role: "user", content: `[Tool result]
|
|
24
|
+
${body}` });
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (m.role === "assistant" && m.toolCalls?.length) {
|
|
28
|
+
const calls = m.toolCalls.map((tc) => `${tc.function.name}(${(tc.function.arguments || "").slice(0, 300)})`).join("; ");
|
|
29
|
+
out.push({ role: "assistant", content: `${m.content || ""}
|
|
30
|
+
[Called tools: ${calls}]`.trim() });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
out.push({ role: m.role, content: m.content });
|
|
34
|
+
}
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
function buildGuidanceBlock(results) {
|
|
38
|
+
const blocks = results.map((r, i) => {
|
|
39
|
+
if (r.content) return `Reference ${i + 1} \u2014 ${r.model}:
|
|
40
|
+
${r.content}`;
|
|
41
|
+
return `Reference ${i + 1} \u2014 ${r.model}: (unavailable: ${r.error || "no response"})`;
|
|
42
|
+
});
|
|
43
|
+
return [
|
|
44
|
+
"[Mixture of Agents context \u2014 private guidance from reference advisors. The user cannot see this.",
|
|
45
|
+
"Weigh it critically; you are the acting agent and the only voice.]",
|
|
46
|
+
"",
|
|
47
|
+
...blocks
|
|
48
|
+
].join("\n");
|
|
49
|
+
}
|
|
50
|
+
function getMoaPresets() {
|
|
51
|
+
try {
|
|
52
|
+
const cfg = loadConfig();
|
|
53
|
+
return cfg.moa?.presets || {};
|
|
54
|
+
} catch {
|
|
55
|
+
return {};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
class MoaProvider extends LLMProvider {
|
|
59
|
+
name = "moa";
|
|
60
|
+
displayName = "Mixture of Agents";
|
|
61
|
+
async healthCheck() {
|
|
62
|
+
return Object.keys(getMoaPresets()).length > 0;
|
|
63
|
+
}
|
|
64
|
+
async listModels() {
|
|
65
|
+
return Object.keys(getMoaPresets()).map((p) => `moa/${p}`);
|
|
66
|
+
}
|
|
67
|
+
resolvePreset(model) {
|
|
68
|
+
const presets = getMoaPresets();
|
|
69
|
+
const name = (model || "").replace(/^moa\//, "") || "default";
|
|
70
|
+
const preset = presets[name];
|
|
71
|
+
if (!preset) throw new Error(`MoA preset "${name}" not found. Configure moa.presets.${name} in titan.json`);
|
|
72
|
+
if (preset.references.some((r) => r.model.startsWith("moa/"))) {
|
|
73
|
+
throw new Error("MoA presets cannot reference other MoA presets (recursion blocked)");
|
|
74
|
+
}
|
|
75
|
+
if (preset.aggregator.startsWith("moa/")) {
|
|
76
|
+
throw new Error("MoA aggregator cannot itself be a MoA preset (recursion blocked)");
|
|
77
|
+
}
|
|
78
|
+
return { name, preset };
|
|
79
|
+
}
|
|
80
|
+
/** Fan out to advisors; never throws — failures become guidance footnotes. */
|
|
81
|
+
async gatherGuidance(preset, messages, signal) {
|
|
82
|
+
const { chat } = await import("./router.js");
|
|
83
|
+
const trimmed = trimForReference(messages);
|
|
84
|
+
const tasks = preset.references.map(async (ref) => {
|
|
85
|
+
const started = Date.now();
|
|
86
|
+
const timeoutMs = ref.timeoutMs ?? DEFAULT_REFERENCE_TIMEOUT_MS;
|
|
87
|
+
const controller = new AbortController();
|
|
88
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
89
|
+
const onOuter = () => controller.abort();
|
|
90
|
+
signal?.addEventListener("abort", onOuter, { once: true });
|
|
91
|
+
try {
|
|
92
|
+
const res = await chat({
|
|
93
|
+
model: ref.model,
|
|
94
|
+
messages: trimmed,
|
|
95
|
+
maxTokens: ref.maxTokens ?? DEFAULT_REFERENCE_MAX_TOKENS,
|
|
96
|
+
temperature: ref.temperature,
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
noFallback: true
|
|
99
|
+
});
|
|
100
|
+
return { model: ref.model, content: res.content || null, ms: Date.now() - started };
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return { model: ref.model, content: null, error: e.message.slice(0, 160), ms: Date.now() - started };
|
|
103
|
+
} finally {
|
|
104
|
+
clearTimeout(timer);
|
|
105
|
+
signal?.removeEventListener("abort", onOuter);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return Promise.all(tasks);
|
|
109
|
+
}
|
|
110
|
+
async prepare(options) {
|
|
111
|
+
const { name, preset } = this.resolvePreset(options.model);
|
|
112
|
+
if (preset.enabled === false) {
|
|
113
|
+
return { preset, name, aggregatorOptions: { ...options, model: preset.aggregator } };
|
|
114
|
+
}
|
|
115
|
+
const t0 = Date.now();
|
|
116
|
+
const guidance = await this.gatherGuidance(preset, options.messages, options.signal);
|
|
117
|
+
const ok = guidance.filter((g) => g.content).length;
|
|
118
|
+
logger.info(COMPONENT, `[${name}] ${ok}/${preset.references.length} advisors answered in ${Date.now() - t0}ms (${guidance.map((g) => `${g.model.split("/").pop()}:${g.ms}ms`).join(", ")})`);
|
|
119
|
+
try {
|
|
120
|
+
const { titanEvents } = await import("../agent/daemon.js");
|
|
121
|
+
titanEvents.emit("moa:guidance", { preset: name, advisors: guidance.map((g) => ({ model: g.model, ok: !!g.content, ms: g.ms })) });
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
const messages = [
|
|
125
|
+
...options.messages,
|
|
126
|
+
{ role: "user", content: buildGuidanceBlock(guidance) }
|
|
127
|
+
];
|
|
128
|
+
return {
|
|
129
|
+
preset,
|
|
130
|
+
name,
|
|
131
|
+
aggregatorOptions: { ...options, model: preset.aggregator, messages, maxTokens: preset.maxTokens ?? options.maxTokens }
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
async chat(options) {
|
|
135
|
+
const { chat } = await import("./router.js");
|
|
136
|
+
const { aggregatorOptions } = await this.prepare(options);
|
|
137
|
+
return chat(aggregatorOptions);
|
|
138
|
+
}
|
|
139
|
+
async *chatStream(options) {
|
|
140
|
+
const { chatStream } = await import("./router.js");
|
|
141
|
+
const { aggregatorOptions } = await this.prepare(options);
|
|
142
|
+
yield* chatStream(aggregatorOptions);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
export {
|
|
146
|
+
MoaProvider,
|
|
147
|
+
buildGuidanceBlock,
|
|
148
|
+
getMoaPresets,
|
|
149
|
+
trimForReference
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=moa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/providers/moa.ts"],"sourcesContent":["/**\n * MoA — Mixture-of-Agents virtual provider (v7.1).\n *\n * A MoA preset = N reference \"advisors\" + 1 aggregator, exposed as a virtual\n * model (`moa/<preset>`) selectable anywhere a model is. On every chat call:\n *\n * 1. Advisors receive a TRIMMED conversation (no TITAN system prompt, no\n * tool schemas, tool results truncated) plus an advisory system prompt —\n * they advise, they never act.\n * 2. Advisors fan out in parallel with per-reference timeouts; a slow or\n * failed advisor is dropped/noted, never awaited into oblivion.\n * 3. Their labeled guidance is appended at the END of the aggregator's\n * prompt (prompt-cache prefix stays stable).\n * 4. The aggregator — the only voice — answers with the FULL tool schema\n * through the normal loop.\n *\n * TITAN's edge over the same feature elsewhere: advisors default to LOCAL\n * models spread across separate machines (a \"local council\"), so the\n * mixture costs nothing and parallelizes across GPUs; and because whole\n * agents can be advisors via MCP, the mixture isn't limited to bare models.\n */\nimport { LLMProvider, type ChatOptions, type ChatResponse, type ChatStreamChunk, type ChatMessage } from './base.js';\nimport { loadConfig } from '../config/config.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'MoA';\nconst DEFAULT_REFERENCE_TIMEOUT_MS = 45_000;\nconst DEFAULT_REFERENCE_MAX_TOKENS = 1_024;\nconst TOOL_RESULT_TRIM = 4_000;\n\nexport interface MoaReference {\n /** provider/model id, e.g. \"litellm/glm-4.7-flash\" */\n model: string;\n maxTokens?: number;\n temperature?: number;\n timeoutMs?: number;\n}\n\nexport interface MoaPreset {\n references: MoaReference[];\n /** provider/model id of the acting aggregator */\n aggregator: string;\n enabled?: boolean;\n /** Optional cap for the aggregator's output */\n maxTokens?: number;\n}\n\nconst ADVISOR_SYSTEM_PROMPT = [\n 'You are a reference advisor inside a Mixture-of-Agents process. You are NOT the acting agent:',\n 'you cannot call tools, and the user never sees your words. Another model (the aggregator) acts',\n 'and answers — your output is private guidance handed to it. Give concise, concrete advice:',\n 'the likely best next steps, tool-use strategy, risks, and anything the aggregator might miss.',\n 'If the conversation references tools or systems you cannot access, advise from reasoning alone —',\n 'never refuse for lack of access.',\n].join(' ');\n\n/** Trim the conversation for advisors: no system prompt, no tool schemas,\n * tool transcripts flattened + truncated. Exported for tests. */\nexport function trimForReference(messages: ChatMessage[]): ChatMessage[] {\n const out: ChatMessage[] = [{ role: 'system', content: ADVISOR_SYSTEM_PROMPT }];\n for (const m of messages) {\n if (m.role === 'system') continue; // TITAN's own harness prompt stays private\n if (m.role === 'tool') {\n const body = (m.content || '').slice(0, TOOL_RESULT_TRIM);\n out.push({ role: 'user', content: `[Tool result]\\n${body}` } as ChatMessage);\n continue;\n }\n if (m.role === 'assistant' && m.toolCalls?.length) {\n const calls = m.toolCalls.map(tc => `${tc.function.name}(${(tc.function.arguments || '').slice(0, 300)})`).join('; ');\n out.push({ role: 'assistant', content: `${m.content || ''}\\n[Called tools: ${calls}]`.trim() } as ChatMessage);\n continue;\n }\n out.push({ role: m.role, content: m.content } as ChatMessage);\n }\n return out;\n}\n\n/** Build the guidance block appended to the aggregator's prompt. Exported for tests. */\nexport function buildGuidanceBlock(results: Array<{ model: string; content: string | null; error?: string }>): string {\n const blocks = results.map((r, i) => {\n if (r.content) return `Reference ${i + 1} — ${r.model}:\\n${r.content}`;\n return `Reference ${i + 1} — ${r.model}: (unavailable: ${r.error || 'no response'})`;\n });\n return [\n '[Mixture of Agents context — private guidance from reference advisors. The user cannot see this.',\n 'Weigh it critically; you are the acting agent and the only voice.]',\n '',\n ...blocks,\n ].join('\\n');\n}\n\nexport function getMoaPresets(): Record<string, MoaPreset> {\n try {\n const cfg = loadConfig() as { moa?: { presets?: Record<string, MoaPreset> } };\n return cfg.moa?.presets || {};\n } catch {\n return {};\n }\n}\n\nexport class MoaProvider extends LLMProvider {\n readonly name = 'moa';\n readonly displayName = 'Mixture of Agents';\n\n async healthCheck(): Promise<boolean> {\n return Object.keys(getMoaPresets()).length > 0;\n }\n\n async listModels(): Promise<string[]> {\n return Object.keys(getMoaPresets()).map(p => `moa/${p}`);\n }\n\n private resolvePreset(model?: string): { name: string; preset: MoaPreset } {\n const presets = getMoaPresets();\n const name = (model || '').replace(/^moa\\//, '') || 'default';\n const preset = presets[name];\n if (!preset) throw new Error(`MoA preset \"${name}\" not found. Configure moa.presets.${name} in titan.json`);\n if (preset.references.some(r => r.model.startsWith('moa/'))) {\n throw new Error('MoA presets cannot reference other MoA presets (recursion blocked)');\n }\n if (preset.aggregator.startsWith('moa/')) {\n throw new Error('MoA aggregator cannot itself be a MoA preset (recursion blocked)');\n }\n return { name, preset };\n }\n\n /** Fan out to advisors; never throws — failures become guidance footnotes. */\n private async gatherGuidance(preset: MoaPreset, messages: ChatMessage[], signal?: AbortSignal):\n Promise<Array<{ model: string; content: string | null; error?: string; ms: number }>> {\n const { chat } = await import('./router.js');\n const trimmed = trimForReference(messages);\n const tasks = preset.references.map(async ref => {\n const started = Date.now();\n const timeoutMs = ref.timeoutMs ?? DEFAULT_REFERENCE_TIMEOUT_MS;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n const onOuter = () => controller.abort();\n signal?.addEventListener('abort', onOuter, { once: true });\n try {\n const res = await chat({\n model: ref.model,\n messages: trimmed,\n maxTokens: ref.maxTokens ?? DEFAULT_REFERENCE_MAX_TOKENS,\n temperature: ref.temperature,\n signal: controller.signal,\n noFallback: true,\n } as ChatOptions & { noFallback: boolean });\n return { model: ref.model, content: res.content || null, ms: Date.now() - started };\n } catch (e) {\n return { model: ref.model, content: null, error: (e as Error).message.slice(0, 160), ms: Date.now() - started };\n } finally {\n clearTimeout(timer);\n signal?.removeEventListener('abort', onOuter);\n }\n });\n return Promise.all(tasks);\n }\n\n private async prepare(options: ChatOptions): Promise<{ preset: MoaPreset; name: string; aggregatorOptions: ChatOptions }> {\n const { name, preset } = this.resolvePreset(options.model);\n if (preset.enabled === false) {\n // Preset disabled → aggregator acts alone (Hermes-compatible semantics)\n return { preset, name, aggregatorOptions: { ...options, model: preset.aggregator } };\n }\n const t0 = Date.now();\n const guidance = await this.gatherGuidance(preset, options.messages, options.signal);\n const ok = guidance.filter(g => g.content).length;\n logger.info(COMPONENT, `[${name}] ${ok}/${preset.references.length} advisors answered in ${Date.now() - t0}ms (${guidance.map(g => `${g.model.split('/').pop()}:${g.ms}ms`).join(', ')})`);\n\n // Emit an observable event for the UI (mascot/Live Studio can narrate)\n try {\n const { titanEvents } = await import('../agent/daemon.js');\n titanEvents.emit('moa:guidance', { preset: name, advisors: guidance.map(g => ({ model: g.model, ok: !!g.content, ms: g.ms })) });\n } catch { /* events are best-effort */ }\n\n // Append guidance at the END (prompt-cache prefix stays stable)\n const messages: ChatMessage[] = [\n ...options.messages,\n { role: 'user', content: buildGuidanceBlock(guidance) } as ChatMessage,\n ];\n return {\n preset, name,\n aggregatorOptions: { ...options, model: preset.aggregator, messages, maxTokens: preset.maxTokens ?? options.maxTokens },\n };\n }\n\n async chat(options: ChatOptions): Promise<ChatResponse> {\n const { chat } = await import('./router.js');\n const { aggregatorOptions } = await this.prepare(options);\n return chat(aggregatorOptions);\n }\n\n async *chatStream(options: ChatOptions): AsyncGenerator<ChatStreamChunk> {\n const { chatStream } = await import('./router.js');\n const { aggregatorOptions } = await this.prepare(options);\n yield* chatStream(aggregatorOptions);\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { loadConfig } from "../config/config.js";
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { TITAN_HOME } from "../utils/constants.js";
|
|
3
6
|
const STATIC_TABLE = {
|
|
4
7
|
"anthropic/claude-sonnet-4-20250514": { contextWindow: 2e5, maxOutput: 64e3, supportsThinking: true },
|
|
5
8
|
"anthropic/claude-opus-4": { contextWindow: 2e5, maxOutput: 32e3, supportsThinking: true },
|
|
@@ -25,15 +28,80 @@ const STATIC_TABLE = {
|
|
|
25
28
|
"groq/llama-3.3-70b-versatile": { contextWindow: 128e3, maxOutput: 32768 }
|
|
26
29
|
};
|
|
27
30
|
const FAMILY_DEFAULTS = [
|
|
31
|
+
// v7.0 — bare-id families for the 2026 agentic models. These match WITH OR
|
|
32
|
+
// WITHOUT a provider prefix (substring, not anchored), so models routed
|
|
33
|
+
// through the generic openai_compat provider or the local :4000 gateway
|
|
34
|
+
// (e.g. "qwen3.6-35b-a3b", "deepseek-v4-pro", "kimi-k2.6") get their real
|
|
35
|
+
// ceilings instead of the 32K/8K DEFAULT_FALLBACK (which truncates JSON).
|
|
36
|
+
// Placed FIRST so they win over the broad `^ollama/` catch-all below.
|
|
37
|
+
{ pattern: /deepseek-v4/i, caps: { contextWindow: 128e3, maxOutput: 64e3, supportsThinking: true } },
|
|
38
|
+
{ pattern: /qwen3\.(6|7)/i, caps: { contextWindow: 262144, maxOutput: 32768, supportsThinking: true } },
|
|
39
|
+
{ pattern: /minimax-m3/i, caps: { contextWindow: 1e6, maxOutput: 4e4 } },
|
|
40
|
+
{ pattern: /minimax/i, caps: { contextWindow: 2e5, maxOutput: 4e4 } },
|
|
41
|
+
{ pattern: /\bglm-5/i, caps: { contextWindow: 198e3, maxOutput: 128e3 } },
|
|
42
|
+
{ pattern: /glm-4\.7/i, caps: { contextWindow: 198e3, maxOutput: 96e3 } },
|
|
43
|
+
{ pattern: /qwen3-coder/i, caps: { contextWindow: 262144, maxOutput: 65536 } },
|
|
44
|
+
{ pattern: /nemotron-nano/i, caps: { contextWindow: 131072, maxOutput: 32e3 } },
|
|
45
|
+
{ pattern: /gemma-4/i, caps: { contextWindow: 131072, maxOutput: 32768 } },
|
|
46
|
+
{ pattern: /kimi-k2/i, caps: { contextWindow: 262144, maxOutput: 128e3 } },
|
|
47
|
+
{ pattern: /nemotron-3/i, caps: { contextWindow: 1e6, maxOutput: 32e3 } },
|
|
28
48
|
{ pattern: /^anthropic\//, caps: { contextWindow: 2e5, maxOutput: 8192 } },
|
|
29
49
|
{ pattern: /^openai\/o\d/, caps: { contextWindow: 2e5, maxOutput: 1e5, supportsThinking: true } },
|
|
30
50
|
{ pattern: /^openai\//, caps: { contextWindow: 128e3, maxOutput: 16384 } },
|
|
31
51
|
{ pattern: /^kimi\/|^moonshot\//, caps: { contextWindow: 262144, maxOutput: 128e3 } },
|
|
32
52
|
{ pattern: /^zhipu\/|^glm/, caps: { contextWindow: 198e3, maxOutput: 128e3 } },
|
|
33
53
|
{ pattern: /^gemini\//, caps: { contextWindow: 1e6, maxOutput: 8192 } },
|
|
54
|
+
{ pattern: /:cloud\b/, caps: { contextWindow: 262144, maxOutput: 32768 } },
|
|
34
55
|
{ pattern: /^ollama\//, caps: { contextWindow: 32e3, maxOutput: 8192 } }
|
|
35
56
|
];
|
|
36
57
|
const DEFAULT_FALLBACK = { contextWindow: 32e3, maxOutput: 8192 };
|
|
58
|
+
const LEARNED_PATH = join(TITAN_HOME, "learned-model-limits.json");
|
|
59
|
+
let learnedCache = null;
|
|
60
|
+
function readLearned() {
|
|
61
|
+
if (learnedCache) return learnedCache;
|
|
62
|
+
try {
|
|
63
|
+
if (existsSync(LEARNED_PATH)) {
|
|
64
|
+
learnedCache = JSON.parse(readFileSync(LEARNED_PATH, "utf-8"));
|
|
65
|
+
return learnedCache;
|
|
66
|
+
}
|
|
67
|
+
} catch {
|
|
68
|
+
}
|
|
69
|
+
learnedCache = {};
|
|
70
|
+
return learnedCache;
|
|
71
|
+
}
|
|
72
|
+
function recordLearnedContextWindow(model, contextWindow) {
|
|
73
|
+
if (!Number.isFinite(contextWindow) || contextWindow < 1024) return;
|
|
74
|
+
const learned = readLearned();
|
|
75
|
+
const existing = learned[model]?.contextWindow;
|
|
76
|
+
if (existing !== void 0 && existing <= contextWindow) return;
|
|
77
|
+
learned[model] = { contextWindow, learnedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
78
|
+
try {
|
|
79
|
+
mkdirSync(TITAN_HOME, { recursive: true });
|
|
80
|
+
writeFileSync(LEARNED_PATH, JSON.stringify(learned, null, 2));
|
|
81
|
+
} catch {
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resetLearnedLimitsCache() {
|
|
85
|
+
learnedCache = null;
|
|
86
|
+
}
|
|
87
|
+
function getModelCapabilitiesEx(model) {
|
|
88
|
+
if (STATIC_TABLE[model]) return { caps: STATIC_TABLE[model], source: "static" };
|
|
89
|
+
try {
|
|
90
|
+
const cfg = loadConfig();
|
|
91
|
+
const override = cfg.providers?.modelCapabilities;
|
|
92
|
+
if (override?.[model]) return { caps: override[model], source: "override" };
|
|
93
|
+
} catch {
|
|
94
|
+
}
|
|
95
|
+
const learned = readLearned()[model];
|
|
96
|
+
for (const f of FAMILY_DEFAULTS) {
|
|
97
|
+
if (f.pattern.test(model)) {
|
|
98
|
+
if (learned) return { caps: { ...f.caps, contextWindow: Math.min(learned.contextWindow, f.caps.contextWindow) }, source: "learned" };
|
|
99
|
+
return { caps: f.caps, source: "family" };
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (learned) return { caps: { ...DEFAULT_FALLBACK, contextWindow: Math.min(learned.contextWindow, DEFAULT_FALLBACK.contextWindow) }, source: "learned" };
|
|
103
|
+
return { caps: DEFAULT_FALLBACK, source: "fallback" };
|
|
104
|
+
}
|
|
37
105
|
function getModelCapabilities(model) {
|
|
38
106
|
if (STATIC_TABLE[model]) return STATIC_TABLE[model];
|
|
39
107
|
try {
|
|
@@ -42,9 +110,14 @@ function getModelCapabilities(model) {
|
|
|
42
110
|
if (override?.[model]) return override[model];
|
|
43
111
|
} catch {
|
|
44
112
|
}
|
|
113
|
+
const learned = readLearned()[model];
|
|
45
114
|
for (const f of FAMILY_DEFAULTS) {
|
|
46
|
-
if (f.pattern.test(model))
|
|
115
|
+
if (f.pattern.test(model)) {
|
|
116
|
+
if (learned) return { ...f.caps, contextWindow: Math.min(learned.contextWindow, f.caps.contextWindow) };
|
|
117
|
+
return f.caps;
|
|
118
|
+
}
|
|
47
119
|
}
|
|
120
|
+
if (learned) return { ...DEFAULT_FALLBACK, contextWindow: Math.min(learned.contextWindow, DEFAULT_FALLBACK.contextWindow) };
|
|
48
121
|
return DEFAULT_FALLBACK;
|
|
49
122
|
}
|
|
50
123
|
function clampMaxTokens(model, requested) {
|
|
@@ -54,6 +127,9 @@ function clampMaxTokens(model, requested) {
|
|
|
54
127
|
}
|
|
55
128
|
export {
|
|
56
129
|
clampMaxTokens,
|
|
57
|
-
getModelCapabilities
|
|
130
|
+
getModelCapabilities,
|
|
131
|
+
getModelCapabilitiesEx,
|
|
132
|
+
recordLearnedContextWindow,
|
|
133
|
+
resetLearnedLimitsCache
|
|
58
134
|
};
|
|
59
135
|
//# sourceMappingURL=modelCapabilities.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/providers/modelCapabilities.ts"],"sourcesContent":["/**\n * TITAN — Per-Model Capability Registry\n *\n * Single source of truth for output-token caps, context-window sizes,\n * and feature flags per model. Providers call clampMaxTokens() so\n * DEFAULT_MAX_TOKENS can safely be a high user-preference ceiling\n * (200 K) without causing 400s on capped providers.\n */\nimport { loadConfig } from '../config/config.js';\n\nexport interface ModelCapabilities {\n contextWindow: number;\n maxOutput: number;\n supportsThinking?: boolean;\n}\n\n// Exact-match table. Keep in alphabetical order by provider / model.\nconst STATIC_TABLE: Record<string, ModelCapabilities> = {\n 'anthropic/claude-sonnet-4-20250514': { contextWindow: 200_000, maxOutput: 64_000, supportsThinking: true },\n 'anthropic/claude-opus-4': { contextWindow: 200_000, maxOutput: 32_000, supportsThinking: true },\n 'anthropic/claude-3-5-sonnet-20241022': { contextWindow: 200_000, maxOutput: 8_192 },\n 'anthropic/claude-3-5-haiku-20241022': { contextWindow: 200_000, maxOutput: 8_192 },\n 'openai/gpt-4o': { contextWindow: 128_000, maxOutput: 16_384 },\n 'openai/gpt-4o-mini': { contextWindow: 128_000, maxOutput: 16_384 },\n 'openai/gpt-4.1': { contextWindow: 1_000_000, maxOutput: 32_768 },\n 'openai/o1': { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true },\n 'openai/o1-mini': { contextWindow: 128_000, maxOutput: 65_536, supportsThinking: true },\n 'openai/o3-mini': { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true },\n 'kimi/kimi-k2.6': { contextWindow: 262_144, maxOutput: 128_000, supportsThinking: true },\n 'moonshot/kimi-k2-0905-preview': { contextWindow: 262_144, maxOutput: 128_000 },\n 'zhipu/glm-5.1': { contextWindow: 198_000, maxOutput: 128_000 },\n 'ollama/qwen3.5:cloud': { contextWindow: 128_000, maxOutput: 32_000 },\n 'ollama/qwen3:32b': { contextWindow: 32_768, maxOutput: 8_192 },\n 'ollama/llama3.3:70b': { contextWindow: 128_000, maxOutput: 8_192 },\n 'ollama/deepseek-v3': { contextWindow: 128_000, maxOutput: 16_384 },\n 'mistral/mistral-large': { contextWindow: 128_000, maxOutput: 8_192 },\n 'gemini/gemini-2.0-flash': { contextWindow: 1_000_000, maxOutput: 8_192 },\n 'gemini/gemini-2.5-pro': { contextWindow: 2_000_000, maxOutput: 65_536, supportsThinking: true },\n 'cohere/command-r-plus': { contextWindow: 128_000, maxOutput: 4_096 },\n 'groq/llama-3.3-70b-versatile': { contextWindow: 128_000, maxOutput: 32_768 },\n};\n\n// Family heuristic for unknown specific versions.\nconst FAMILY_DEFAULTS: Array<{ pattern: RegExp; caps: ModelCapabilities }> = [\n { pattern: /^anthropic\\//, caps: { contextWindow: 200_000, maxOutput: 8_192 } },\n { pattern: /^openai\\/o\\d/, caps: { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true } },\n { pattern: /^openai\\//, caps: { contextWindow: 128_000, maxOutput: 16_384 } },\n { pattern: /^kimi\\/|^moonshot\\//, caps: { contextWindow: 262_144, maxOutput: 128_000 } },\n { pattern: /^zhipu\\/|^glm/, caps: { contextWindow: 198_000, maxOutput: 128_000 } },\n { pattern: /^gemini\\//, caps: { contextWindow: 1_000_000, maxOutput: 8_192 } },\n { pattern: /^ollama\\//, caps: { contextWindow: 32_000, maxOutput: 8_192 } },\n];\n\nconst DEFAULT_FALLBACK: ModelCapabilities = { contextWindow: 32_000, maxOutput: 8_192 };\n\nexport function getModelCapabilities(model: string): ModelCapabilities {\n if (STATIC_TABLE[model]) return STATIC_TABLE[model];\n\n // Config override path\n try {\n const cfg = loadConfig();\n const override = (cfg.providers as Record<string, unknown> | undefined)?.modelCapabilities as\n Record<string, ModelCapabilities> | undefined;\n if (override?.[model]) return override[model];\n } catch { /* config not loaded yet during early boot */ }\n\n // Family fallback\n for (const f of FAMILY_DEFAULTS) {\n if (f.pattern.test(model)) return f.caps;\n }\n\n return DEFAULT_FALLBACK;\n}\n\n/**\n * Clamp a requested maxTokens to the model's actual ceiling.\n * If requested is undefined, returns the model's own maxOutput.\n * Never returns less than 1.\n */\nexport function clampMaxTokens(model: string, requested?: number): number {\n const caps = getModelCapabilities(model);\n const want = requested ?? caps.maxOutput;\n return Math.max(1, Math.min(want, caps.maxOutput));\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/providers/modelCapabilities.ts"],"sourcesContent":["/**\n * TITAN — Per-Model Capability Registry\n *\n * Single source of truth for output-token caps, context-window sizes,\n * and feature flags per model. Providers call clampMaxTokens() so\n * DEFAULT_MAX_TOKENS can safely be a high user-preference ceiling\n * (200 K) without causing 400s on capped providers.\n */\nimport { loadConfig } from '../config/config.js';\nimport { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';\nimport { join } from 'path';\nimport { TITAN_HOME } from '../utils/constants.js';\n\nexport interface ModelCapabilities {\n contextWindow: number;\n maxOutput: number;\n supportsThinking?: boolean;\n}\n\n// Exact-match table. Keep in alphabetical order by provider / model.\nconst STATIC_TABLE: Record<string, ModelCapabilities> = {\n 'anthropic/claude-sonnet-4-20250514': { contextWindow: 200_000, maxOutput: 64_000, supportsThinking: true },\n 'anthropic/claude-opus-4': { contextWindow: 200_000, maxOutput: 32_000, supportsThinking: true },\n 'anthropic/claude-3-5-sonnet-20241022': { contextWindow: 200_000, maxOutput: 8_192 },\n 'anthropic/claude-3-5-haiku-20241022': { contextWindow: 200_000, maxOutput: 8_192 },\n 'openai/gpt-4o': { contextWindow: 128_000, maxOutput: 16_384 },\n 'openai/gpt-4o-mini': { contextWindow: 128_000, maxOutput: 16_384 },\n 'openai/gpt-4.1': { contextWindow: 1_000_000, maxOutput: 32_768 },\n 'openai/o1': { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true },\n 'openai/o1-mini': { contextWindow: 128_000, maxOutput: 65_536, supportsThinking: true },\n 'openai/o3-mini': { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true },\n 'kimi/kimi-k2.6': { contextWindow: 262_144, maxOutput: 128_000, supportsThinking: true },\n 'moonshot/kimi-k2-0905-preview': { contextWindow: 262_144, maxOutput: 128_000 },\n 'zhipu/glm-5.1': { contextWindow: 198_000, maxOutput: 128_000 },\n 'ollama/qwen3.5:cloud': { contextWindow: 128_000, maxOutput: 32_000 },\n 'ollama/qwen3:32b': { contextWindow: 32_768, maxOutput: 8_192 },\n 'ollama/llama3.3:70b': { contextWindow: 128_000, maxOutput: 8_192 },\n 'ollama/deepseek-v3': { contextWindow: 128_000, maxOutput: 16_384 },\n 'mistral/mistral-large': { contextWindow: 128_000, maxOutput: 8_192 },\n 'gemini/gemini-2.0-flash': { contextWindow: 1_000_000, maxOutput: 8_192 },\n 'gemini/gemini-2.5-pro': { contextWindow: 2_000_000, maxOutput: 65_536, supportsThinking: true },\n 'cohere/command-r-plus': { contextWindow: 128_000, maxOutput: 4_096 },\n 'groq/llama-3.3-70b-versatile': { contextWindow: 128_000, maxOutput: 32_768 },\n};\n\n// Family heuristic for unknown specific versions.\nconst FAMILY_DEFAULTS: Array<{ pattern: RegExp; caps: ModelCapabilities }> = [\n // v7.0 — bare-id families for the 2026 agentic models. These match WITH OR\n // WITHOUT a provider prefix (substring, not anchored), so models routed\n // through the generic openai_compat provider or the local :4000 gateway\n // (e.g. \"qwen3.6-35b-a3b\", \"deepseek-v4-pro\", \"kimi-k2.6\") get their real\n // ceilings instead of the 32K/8K DEFAULT_FALLBACK (which truncates JSON).\n // Placed FIRST so they win over the broad `^ollama/` catch-all below.\n { pattern: /deepseek-v4/i, caps: { contextWindow: 128_000, maxOutput: 64_000, supportsThinking: true } },\n { pattern: /qwen3\\.(6|7)/i, caps: { contextWindow: 262_144, maxOutput: 32_768, supportsThinking: true } },\n { pattern: /minimax-m3/i, caps: { contextWindow: 1_000_000, maxOutput: 40_000 } },\n { pattern: /minimax/i, caps: { contextWindow: 200_000, maxOutput: 40_000 } },\n { pattern: /\\bglm-5/i, caps: { contextWindow: 198_000, maxOutput: 128_000 } },\n { pattern: /glm-4\\.7/i, caps: { contextWindow: 198_000, maxOutput: 96_000 } },\n { pattern: /qwen3-coder/i, caps: { contextWindow: 262_144, maxOutput: 65_536 } },\n { pattern: /nemotron-nano/i, caps: { contextWindow: 131_072, maxOutput: 32_000 } },\n { pattern: /gemma-4/i, caps: { contextWindow: 131_072, maxOutput: 32_768 } },\n { pattern: /kimi-k2/i, caps: { contextWindow: 262_144, maxOutput: 128_000 } },\n { pattern: /nemotron-3/i, caps: { contextWindow: 1_000_000, maxOutput: 32_000 } },\n { pattern: /^anthropic\\//, caps: { contextWindow: 200_000, maxOutput: 8_192 } },\n { pattern: /^openai\\/o\\d/, caps: { contextWindow: 200_000, maxOutput: 100_000, supportsThinking: true } },\n { pattern: /^openai\\//, caps: { contextWindow: 128_000, maxOutput: 16_384 } },\n { pattern: /^kimi\\/|^moonshot\\//, caps: { contextWindow: 262_144, maxOutput: 128_000 } },\n { pattern: /^zhipu\\/|^glm/, caps: { contextWindow: 198_000, maxOutput: 128_000 } },\n { pattern: /^gemini\\//, caps: { contextWindow: 1_000_000, maxOutput: 8_192 } },\n { pattern: /:cloud\\b/, caps: { contextWindow: 262_144, maxOutput: 32_768 } },\n { pattern: /^ollama\\//, caps: { contextWindow: 32_000, maxOutput: 8_192 } },\n];\n\nconst DEFAULT_FALLBACK: ModelCapabilities = { contextWindow: 32_000, maxOutput: 8_192 };\n\n// ── Learned deployment limits (v7.1 \"truly model agnostic\") ──────────────\n// A model FAMILY may support 198K context, but the DEPLOYMENT in front of us\n// may be a vLLM serving 16K. When a backend 400 reveals its real ceiling\n// (parseMaxTokenLimit / context-overflow messages), we persist it here so\n// every later request — including tool-tier selection — fits the deployment\n// automatically. Config overrides still win; learning fills the gap.\nconst LEARNED_PATH = join(TITAN_HOME, 'learned-model-limits.json');\nlet learnedCache: Record<string, { contextWindow: number; learnedAt: string }> | null = null;\n\nfunction readLearned(): Record<string, { contextWindow: number; learnedAt: string }> {\n if (learnedCache) return learnedCache;\n try {\n if (existsSync(LEARNED_PATH)) {\n learnedCache = JSON.parse(readFileSync(LEARNED_PATH, 'utf-8'));\n return learnedCache!;\n }\n } catch { /* corrupt file — relearn */ }\n learnedCache = {};\n return learnedCache;\n}\n\n/** Persist a deployment's real context ceiling discovered from a backend 400. */\nexport function recordLearnedContextWindow(model: string, contextWindow: number): void {\n if (!Number.isFinite(contextWindow) || contextWindow < 1024) return;\n const learned = readLearned();\n const existing = learned[model]?.contextWindow;\n // Keep the smallest observed ceiling — a deployment can't grow mid-flight,\n // and re-serving with a bigger window is picked up by deleting the file\n // or via the config override.\n if (existing !== undefined && existing <= contextWindow) return;\n learned[model] = { contextWindow, learnedAt: new Date().toISOString() };\n try {\n mkdirSync(TITAN_HOME, { recursive: true });\n writeFileSync(LEARNED_PATH, JSON.stringify(learned, null, 2));\n } catch { /* best-effort persistence */ }\n}\n\n/** Test hook: reset the learned-limits cache. */\nexport function resetLearnedLimitsCache(): void {\n learnedCache = null;\n}\n\nexport type CapabilitySource = 'static' | 'override' | 'learned' | 'family' | 'fallback';\n\n/** Like getModelCapabilities, but reports WHERE the answer came from — a\n * 32K fallback for an unknown model is a guess, not a measurement, and\n * context-fit tool tiering must not shrink toolsets on a guess. */\nexport function getModelCapabilitiesEx(model: string): { caps: ModelCapabilities; source: CapabilitySource } {\n if (STATIC_TABLE[model]) return { caps: STATIC_TABLE[model], source: 'static' };\n try {\n const cfg = loadConfig();\n const override = (cfg.providers as Record<string, unknown> | undefined)?.modelCapabilities as\n Record<string, ModelCapabilities> | undefined;\n if (override?.[model]) return { caps: override[model], source: 'override' };\n } catch { /* early boot */ }\n const learned = readLearned()[model];\n for (const f of FAMILY_DEFAULTS) {\n if (f.pattern.test(model)) {\n if (learned) return { caps: { ...f.caps, contextWindow: Math.min(learned.contextWindow, f.caps.contextWindow) }, source: 'learned' };\n return { caps: f.caps, source: 'family' };\n }\n }\n if (learned) return { caps: { ...DEFAULT_FALLBACK, contextWindow: Math.min(learned.contextWindow, DEFAULT_FALLBACK.contextWindow) }, source: 'learned' };\n return { caps: DEFAULT_FALLBACK, source: 'fallback' };\n}\n\nexport function getModelCapabilities(model: string): ModelCapabilities {\n if (STATIC_TABLE[model]) return STATIC_TABLE[model];\n\n // Config override path\n try {\n const cfg = loadConfig();\n const override = (cfg.providers as Record<string, unknown> | undefined)?.modelCapabilities as\n Record<string, ModelCapabilities> | undefined;\n if (override?.[model]) return override[model];\n } catch { /* config not loaded yet during early boot */ }\n\n // Learned deployment ceiling (from real backend 400s) — beats the family\n // guess because it reflects the endpoint actually serving this model.\n const learned = readLearned()[model];\n\n // Family fallback\n for (const f of FAMILY_DEFAULTS) {\n if (f.pattern.test(model)) {\n if (learned) return { ...f.caps, contextWindow: Math.min(learned.contextWindow, f.caps.contextWindow) };\n return f.caps;\n }\n }\n\n if (learned) return { ...DEFAULT_FALLBACK, contextWindow: Math.min(learned.contextWindow, DEFAULT_FALLBACK.contextWindow) };\n return DEFAULT_FALLBACK;\n}\n\n/**\n * Clamp a requested maxTokens to the model's actual ceiling.\n * If requested is undefined, returns the model's own maxOutput.\n * Never returns less than 1.\n */\nexport function clampMaxTokens(model: string, requested?: number): number {\n const caps = getModelCapabilities(model);\n const want = requested ?? caps.maxOutput;\n return Math.max(1, Math.min(want, caps.maxOutput));\n}\n"],"mappings":"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","names":[]}
|
package/dist/providers/ollama.js
CHANGED
|
@@ -35,6 +35,7 @@ const CLOUD_MODEL_CTX = {
|
|
|
35
35
|
"devstral-small-2:latest": 32768,
|
|
36
36
|
// Nemotron 3 Nano — 1M native, 32K practical for local
|
|
37
37
|
"nemotron-3-nano": 32768,
|
|
38
|
+
"nemotron-nano": 32768,
|
|
38
39
|
"nemotron-3-nano:latest": 32768,
|
|
39
40
|
"nemotron-3-nano:4b": 32768,
|
|
40
41
|
"nemotron-3-nano:30b": 32768,
|
|
@@ -82,10 +83,15 @@ function inferCapabilitiesFromName(modelName) {
|
|
|
82
83
|
}
|
|
83
84
|
const MODEL_CAPABILITIES = {
|
|
84
85
|
// ── Qwen family — excellent tool calling, uses thinking ──
|
|
86
|
+
"qwen3.6": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },
|
|
85
87
|
"qwen3.5": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },
|
|
86
88
|
"qwen3": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.7 },
|
|
87
89
|
"qwen3-coder-next": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.5 },
|
|
88
|
-
// ── DeepSeek family — strong reasoning, good tool use
|
|
90
|
+
// ── DeepSeek family — strong reasoning, good tool use. NOTE: the reasoner
|
|
91
|
+
// endpoint rejects tool_choice:required + thinking with HTTP 400 — the
|
|
92
|
+
// openai_compat provider guards that; here selfSelectsTools:true also
|
|
93
|
+
// means we don't force tool_choice for them in the first place. ──
|
|
94
|
+
"deepseek-v4": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },
|
|
89
95
|
"deepseek-v3": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },
|
|
90
96
|
"deepseek-v3.1": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },
|
|
91
97
|
"deepseek-v3.2": { selfSelectsTools: true, thinkingWithTools: true, needsSystemMerge: false, toolTemperature: 0.6 },
|
|
@@ -724,6 +730,7 @@ ${msgs2[firstUserIdx].content}`;
|
|
|
724
730
|
}
|
|
725
731
|
}
|
|
726
732
|
export {
|
|
727
|
-
OllamaProvider
|
|
733
|
+
OllamaProvider,
|
|
734
|
+
getModelCapabilities
|
|
728
735
|
};
|
|
729
736
|
//# sourceMappingURL=ollama.js.map
|