@aigentic/ruflo 3.7.0-alpha.69
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 +410 -0
- package/bin/ruflo.js +57 -0
- package/package.json +98 -0
- package/src/chat-ui/Dockerfile +25 -0
- package/src/chat-ui/patch-mcp-url-safety.sh +28 -0
- package/src/chat-ui/static/chatui/icon-144x144.png +0 -0
- package/src/chat-ui/static/chatui/omni-welcome.gif +0 -0
- package/src/config/config.example.json +76 -0
- package/src/mcp-bridge/Dockerfile +45 -0
- package/src/mcp-bridge/index.js +1668 -0
- package/src/mcp-bridge/mcp-stdio-kernel.js +159 -0
- package/src/mcp-bridge/package.json +17 -0
- package/src/mcp-bridge/test-harness.js +470 -0
- package/src/nginx/Dockerfile +10 -0
- package/src/nginx/nginx.conf +67 -0
- package/src/nginx/static/favicon-dark.svg +4 -0
- package/src/nginx/static/favicon.svg +4 -0
- package/src/nginx/static/icon.svg +5 -0
- package/src/nginx/static/logo.svg +9 -0
- package/src/nginx/static/manifest.json +22 -0
- package/src/nginx/static/welcome.js +184 -0
- package/src/ruvocal/.claude/skills/add-model-descriptions/SKILL.md +73 -0
- package/src/ruvocal/.devcontainer/Dockerfile +9 -0
- package/src/ruvocal/.devcontainer/devcontainer.json +36 -0
- package/src/ruvocal/.dockerignore +17 -0
- package/src/ruvocal/.eslintignore +13 -0
- package/src/ruvocal/.eslintrc.cjs +45 -0
- package/src/ruvocal/.gcloudignore +18 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/bug-report--chat-ui-.md +43 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/config-support.md +9 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/feature-request--chat-ui-.md +17 -0
- package/src/ruvocal/.github/ISSUE_TEMPLATE/huggingchat.md +11 -0
- package/src/ruvocal/.github/release.yml +16 -0
- package/src/ruvocal/.github/workflows/build-docs.yml +18 -0
- package/src/ruvocal/.github/workflows/build-image.yml +142 -0
- package/src/ruvocal/.github/workflows/build-pr-docs.yml +20 -0
- package/src/ruvocal/.github/workflows/deploy-dev.yml +63 -0
- package/src/ruvocal/.github/workflows/deploy-prod.yml +78 -0
- package/src/ruvocal/.github/workflows/lint-and-test.yml +84 -0
- package/src/ruvocal/.github/workflows/slugify.yaml +72 -0
- package/src/ruvocal/.github/workflows/trufflehog.yml +17 -0
- package/src/ruvocal/.github/workflows/upload-pr-documentation.yml +16 -0
- package/src/ruvocal/.husky/lint-stage-config.js +4 -0
- package/src/ruvocal/.husky/pre-commit +2 -0
- package/src/ruvocal/.prettierignore +14 -0
- package/src/ruvocal/.prettierrc +7 -0
- package/src/ruvocal/CLAUDE.md +126 -0
- package/src/ruvocal/Dockerfile +96 -0
- package/src/ruvocal/LICENSE +203 -0
- package/src/ruvocal/PRIVACY.md +41 -0
- package/src/ruvocal/README.md +164 -0
- package/src/ruvocal/chart/Chart.yaml +5 -0
- package/src/ruvocal/chart/env/dev.yaml +260 -0
- package/src/ruvocal/chart/env/prod.yaml +273 -0
- package/src/ruvocal/chart/templates/_helpers.tpl +22 -0
- package/src/ruvocal/chart/templates/config.yaml +10 -0
- package/src/ruvocal/chart/templates/deployment.yaml +81 -0
- package/src/ruvocal/chart/templates/hpa.yaml +45 -0
- package/src/ruvocal/chart/templates/infisical.yaml +24 -0
- package/src/ruvocal/chart/templates/ingress-internal.yaml +32 -0
- package/src/ruvocal/chart/templates/ingress.yaml +32 -0
- package/src/ruvocal/chart/templates/network-policy.yaml +36 -0
- package/src/ruvocal/chart/templates/service-account.yaml +13 -0
- package/src/ruvocal/chart/templates/service-monitor.yaml +17 -0
- package/src/ruvocal/chart/templates/service.yaml +21 -0
- package/src/ruvocal/chart/values.yaml +73 -0
- package/src/ruvocal/cloudbuild.yaml +68 -0
- package/src/ruvocal/config/branding.env.example +19 -0
- package/src/ruvocal/docker-compose.yml +21 -0
- package/src/ruvocal/docs/adr/ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md +1236 -0
- package/src/ruvocal/docs/adr/ADR-033-RUVECTOR-RUFLO-MCP-INTEGRATION.md +111 -0
- package/src/ruvocal/docs/adr/ADR-034-OPTIONAL-MCP-BACKENDS.md +117 -0
- package/src/ruvocal/docs/adr/ADR-035-MCP-TOOL-GROUPS.md +186 -0
- package/src/ruvocal/docs/adr/ADR-037-AUTOPILOT-CHAT-MODE.md +1500 -0
- package/src/ruvocal/docs/adr/ADR-038-RUVOCAL-FORK.md +286 -0
- package/src/ruvocal/docs/source/_toctree.yml +30 -0
- package/src/ruvocal/docs/source/configuration/common-issues.md +38 -0
- package/src/ruvocal/docs/source/configuration/llm-router.md +105 -0
- package/src/ruvocal/docs/source/configuration/mcp-tools.md +84 -0
- package/src/ruvocal/docs/source/configuration/metrics.md +9 -0
- package/src/ruvocal/docs/source/configuration/open-id.md +57 -0
- package/src/ruvocal/docs/source/configuration/overview.md +89 -0
- package/src/ruvocal/docs/source/configuration/theming.md +20 -0
- package/src/ruvocal/docs/source/developing/architecture.md +48 -0
- package/src/ruvocal/docs/source/index.md +53 -0
- package/src/ruvocal/docs/source/installation/docker.md +43 -0
- package/src/ruvocal/docs/source/installation/helm.md +43 -0
- package/src/ruvocal/docs/source/installation/local.md +62 -0
- package/src/ruvocal/entrypoint.sh +19 -0
- package/src/ruvocal/mcp-bridge/Dockerfile +45 -0
- package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -0
- package/src/ruvocal/mcp-bridge/index.js +1878 -0
- package/src/ruvocal/mcp-bridge/mcp-stdio-kernel.js +159 -0
- package/src/ruvocal/mcp-bridge/package-lock.json +762 -0
- package/src/ruvocal/mcp-bridge/package.json +17 -0
- package/src/ruvocal/mcp-bridge/test-harness.js +470 -0
- package/src/ruvocal/models/add-your-models-here.txt +1 -0
- package/src/ruvocal/package-lock.json +11741 -0
- package/src/ruvocal/package.json +121 -0
- package/src/ruvocal/postcss.config.js +6 -0
- package/src/ruvocal/rvf.manifest.json +204 -0
- package/src/ruvocal/scripts/config.ts +64 -0
- package/src/ruvocal/scripts/generate-welcome.mjs +181 -0
- package/src/ruvocal/scripts/populate.ts +288 -0
- package/src/ruvocal/scripts/samples.txt +194 -0
- package/src/ruvocal/scripts/setups/vitest-setup-client.ts +0 -0
- package/src/ruvocal/scripts/setups/vitest-setup-server.ts +44 -0
- package/src/ruvocal/scripts/updateLocalEnv.ts +48 -0
- package/src/ruvocal/src/ambient.d.ts +7 -0
- package/src/ruvocal/src/app.d.ts +29 -0
- package/src/ruvocal/src/app.html +53 -0
- package/src/ruvocal/src/hooks.server.ts +32 -0
- package/src/ruvocal/src/hooks.ts +6 -0
- package/src/ruvocal/src/lib/APIClient.ts +148 -0
- package/src/ruvocal/src/lib/actions/clickOutside.ts +18 -0
- package/src/ruvocal/src/lib/actions/snapScrollToBottom.ts +346 -0
- package/src/ruvocal/src/lib/buildPrompt.ts +33 -0
- package/src/ruvocal/src/lib/components/AnnouncementBanner.svelte +20 -0
- package/src/ruvocal/src/lib/components/BackgroundGenerationPoller.svelte +168 -0
- package/src/ruvocal/src/lib/components/CodeBlock.svelte +73 -0
- package/src/ruvocal/src/lib/components/CopyToClipBoardBtn.svelte +92 -0
- package/src/ruvocal/src/lib/components/DeleteConversationModal.svelte +75 -0
- package/src/ruvocal/src/lib/components/EditConversationModal.svelte +100 -0
- package/src/ruvocal/src/lib/components/ExpandNavigation.svelte +22 -0
- package/src/ruvocal/src/lib/components/FoundationBackground.svelte +242 -0
- package/src/ruvocal/src/lib/components/HoverTooltip.svelte +44 -0
- package/src/ruvocal/src/lib/components/HtmlPreviewModal.svelte +143 -0
- package/src/ruvocal/src/lib/components/InfiniteScroll.svelte +50 -0
- package/src/ruvocal/src/lib/components/MobileNav.svelte +300 -0
- package/src/ruvocal/src/lib/components/Modal.svelte +115 -0
- package/src/ruvocal/src/lib/components/ModelCardMetadata.svelte +71 -0
- package/src/ruvocal/src/lib/components/NavConversationItem.svelte +151 -0
- package/src/ruvocal/src/lib/components/NavMenu.svelte +313 -0
- package/src/ruvocal/src/lib/components/Pagination.svelte +97 -0
- package/src/ruvocal/src/lib/components/PaginationArrow.svelte +27 -0
- package/src/ruvocal/src/lib/components/Portal.svelte +24 -0
- package/src/ruvocal/src/lib/components/RetryBtn.svelte +18 -0
- package/src/ruvocal/src/lib/components/RuFloUniverse.svelte +185 -0
- package/src/ruvocal/src/lib/components/RufloHelpModal.svelte +411 -0
- package/src/ruvocal/src/lib/components/ScrollToBottomBtn.svelte +47 -0
- package/src/ruvocal/src/lib/components/ScrollToPreviousBtn.svelte +77 -0
- package/src/ruvocal/src/lib/components/ShareConversationModal.svelte +182 -0
- package/src/ruvocal/src/lib/components/StopGeneratingBtn.svelte +69 -0
- package/src/ruvocal/src/lib/components/SubscribeModal.svelte +87 -0
- package/src/ruvocal/src/lib/components/Switch.svelte +36 -0
- package/src/ruvocal/src/lib/components/SystemPromptModal.svelte +44 -0
- package/src/ruvocal/src/lib/components/Toast.svelte +27 -0
- package/src/ruvocal/src/lib/components/Tooltip.svelte +30 -0
- package/src/ruvocal/src/lib/components/WelcomeModal.svelte +46 -0
- package/src/ruvocal/src/lib/components/chat/Alternatives.svelte +77 -0
- package/src/ruvocal/src/lib/components/chat/BlockWrapper.svelte +72 -0
- package/src/ruvocal/src/lib/components/chat/ChatInput.svelte +490 -0
- package/src/ruvocal/src/lib/components/chat/ChatIntroduction.svelte +123 -0
- package/src/ruvocal/src/lib/components/chat/ChatMessage.svelte +548 -0
- package/src/ruvocal/src/lib/components/chat/ChatWindow.svelte +1057 -0
- package/src/ruvocal/src/lib/components/chat/FileDropzone.svelte +92 -0
- package/src/ruvocal/src/lib/components/chat/ImageLightbox.svelte +66 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownBlock.svelte +23 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte +69 -0
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts +58 -0
- package/src/ruvocal/src/lib/components/chat/MessageAvatar.svelte +103 -0
- package/src/ruvocal/src/lib/components/chat/ModelSwitch.svelte +64 -0
- package/src/ruvocal/src/lib/components/chat/OpenReasoningResults.svelte +81 -0
- package/src/ruvocal/src/lib/components/chat/TaskGroup.svelte +88 -0
- package/src/ruvocal/src/lib/components/chat/ToolUpdate.svelte +273 -0
- package/src/ruvocal/src/lib/components/chat/UploadedFile.svelte +253 -0
- package/src/ruvocal/src/lib/components/chat/UrlFetchModal.svelte +203 -0
- package/src/ruvocal/src/lib/components/chat/VoiceRecorder.svelte +214 -0
- package/src/ruvocal/src/lib/components/icons/IconBurger.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconCheap.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconChevron.svelte +24 -0
- package/src/ruvocal/src/lib/components/icons/IconDazzled.svelte +40 -0
- package/src/ruvocal/src/lib/components/icons/IconFast.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconLoading.svelte +22 -0
- package/src/ruvocal/src/lib/components/icons/IconMCP.svelte +28 -0
- package/src/ruvocal/src/lib/components/icons/IconMoon.svelte +21 -0
- package/src/ruvocal/src/lib/components/icons/IconNew.svelte +20 -0
- package/src/ruvocal/src/lib/components/icons/IconOmni.svelte +90 -0
- package/src/ruvocal/src/lib/components/icons/IconPaperclip.svelte +24 -0
- package/src/ruvocal/src/lib/components/icons/IconPro.svelte +37 -0
- package/src/ruvocal/src/lib/components/icons/IconShare.svelte +21 -0
- package/src/ruvocal/src/lib/components/icons/IconSun.svelte +93 -0
- package/src/ruvocal/src/lib/components/icons/Logo.svelte +68 -0
- package/src/ruvocal/src/lib/components/icons/LogoHuggingFaceBorderless.svelte +54 -0
- package/src/ruvocal/src/lib/components/mcp/AddServerForm.svelte +250 -0
- package/src/ruvocal/src/lib/components/mcp/MCPServerManager.svelte +185 -0
- package/src/ruvocal/src/lib/components/mcp/ServerCard.svelte +203 -0
- package/src/ruvocal/src/lib/components/players/AudioPlayer.svelte +82 -0
- package/src/ruvocal/src/lib/components/voice/AudioWaveform.svelte +96 -0
- package/src/ruvocal/src/lib/components/wasm/GalleryPanel.svelte +357 -0
- package/src/ruvocal/src/lib/constants/mcpExamples.ts +114 -0
- package/src/ruvocal/src/lib/constants/mime.ts +11 -0
- package/src/ruvocal/src/lib/constants/pagination.ts +1 -0
- package/src/ruvocal/src/lib/constants/publicSepToken.ts +1 -0
- package/src/ruvocal/src/lib/constants/routerExamples.ts +133 -0
- package/src/ruvocal/src/lib/constants/rvagentPresets.ts +206 -0
- package/src/ruvocal/src/lib/createShareLink.ts +27 -0
- package/src/ruvocal/src/lib/jobs/refresh-conversation-stats.ts +297 -0
- package/src/ruvocal/src/lib/migrations/lock.ts +56 -0
- package/src/ruvocal/src/lib/migrations/migrations.spec.ts +74 -0
- package/src/ruvocal/src/lib/migrations/migrations.ts +109 -0
- package/src/ruvocal/src/lib/migrations/routines/01-update-search-assistants.ts +50 -0
- package/src/ruvocal/src/lib/migrations/routines/02-update-assistants-models.ts +48 -0
- package/src/ruvocal/src/lib/migrations/routines/04-update-message-updates.ts +151 -0
- package/src/ruvocal/src/lib/migrations/routines/05-update-message-files.ts +56 -0
- package/src/ruvocal/src/lib/migrations/routines/06-trim-message-updates.ts +56 -0
- package/src/ruvocal/src/lib/migrations/routines/08-update-featured-to-review.ts +32 -0
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts +214 -0
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.ts +88 -0
- package/src/ruvocal/src/lib/migrations/routines/10-update-reports-assistantid.ts +29 -0
- package/src/ruvocal/src/lib/migrations/routines/index.ts +15 -0
- package/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts +103 -0
- package/src/ruvocal/src/lib/server/abortRegistry.ts +57 -0
- package/src/ruvocal/src/lib/server/abortedGenerations.ts +43 -0
- package/src/ruvocal/src/lib/server/adminToken.ts +62 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts +296 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts +216 -0
- package/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts +235 -0
- package/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts +72 -0
- package/src/ruvocal/src/lib/server/api/__tests__/testHelpers.ts +86 -0
- package/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts +78 -0
- package/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts +239 -0
- package/src/ruvocal/src/lib/server/api/types.ts +37 -0
- package/src/ruvocal/src/lib/server/api/utils/requireAuth.ts +22 -0
- package/src/ruvocal/src/lib/server/api/utils/resolveConversation.ts +69 -0
- package/src/ruvocal/src/lib/server/api/utils/resolveModel.ts +27 -0
- package/src/ruvocal/src/lib/server/api/utils/superjsonResponse.ts +15 -0
- package/src/ruvocal/src/lib/server/apiToken.ts +11 -0
- package/src/ruvocal/src/lib/server/auth.ts +554 -0
- package/src/ruvocal/src/lib/server/config.ts +187 -0
- package/src/ruvocal/src/lib/server/conversation.ts +83 -0
- package/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts +709 -0
- package/src/ruvocal/src/lib/server/database/postgres.ts +700 -0
- package/src/ruvocal/src/lib/server/database/rvf.ts +1078 -0
- package/src/ruvocal/src/lib/server/database.ts +145 -0
- package/src/ruvocal/src/lib/server/endpoints/document.ts +68 -0
- package/src/ruvocal/src/lib/server/endpoints/endpoints.ts +43 -0
- package/src/ruvocal/src/lib/server/endpoints/images.ts +211 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/endpointOai.ts +266 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +212 -0
- package/src/ruvocal/src/lib/server/endpoints/openai/openAICompletionToTextGenerationStream.ts +32 -0
- package/src/ruvocal/src/lib/server/endpoints/preprocessMessages.ts +61 -0
- package/src/ruvocal/src/lib/server/exitHandler.ts +59 -0
- package/src/ruvocal/src/lib/server/files/downloadFile.ts +34 -0
- package/src/ruvocal/src/lib/server/files/uploadFile.ts +29 -0
- package/src/ruvocal/src/lib/server/findRepoRoot.ts +13 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Black.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Bold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-ExtraBold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-ExtraLight.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Light.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Medium.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Regular.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-SemiBold.ttf +0 -0
- package/src/ruvocal/src/lib/server/fonts/Inter-Thin.ttf +0 -0
- package/src/ruvocal/src/lib/server/generateFromDefaultEndpoint.ts +46 -0
- package/src/ruvocal/src/lib/server/hooks/error.ts +37 -0
- package/src/ruvocal/src/lib/server/hooks/fetch.ts +22 -0
- package/src/ruvocal/src/lib/server/hooks/handle.ts +250 -0
- package/src/ruvocal/src/lib/server/hooks/init.ts +51 -0
- package/src/ruvocal/src/lib/server/isURLLocal.spec.ts +31 -0
- package/src/ruvocal/src/lib/server/isURLLocal.ts +74 -0
- package/src/ruvocal/src/lib/server/logger.ts +42 -0
- package/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts +175 -0
- package/src/ruvocal/src/lib/server/mcp/clientPool.ts +0 -0
- package/src/ruvocal/src/lib/server/mcp/hf.ts +32 -0
- package/src/ruvocal/src/lib/server/mcp/httpClient.ts +122 -0
- package/src/ruvocal/src/lib/server/mcp/registry.ts +76 -0
- package/src/ruvocal/src/lib/server/mcp/tools.ts +196 -0
- package/src/ruvocal/src/lib/server/metrics.ts +255 -0
- package/src/ruvocal/src/lib/server/models.ts +518 -0
- package/src/ruvocal/src/lib/server/requestContext.ts +55 -0
- package/src/ruvocal/src/lib/server/router/arch.ts +230 -0
- package/src/ruvocal/src/lib/server/router/endpoint.ts +316 -0
- package/src/ruvocal/src/lib/server/router/multimodal.ts +28 -0
- package/src/ruvocal/src/lib/server/router/policy.ts +49 -0
- package/src/ruvocal/src/lib/server/router/toolsRoute.ts +51 -0
- package/src/ruvocal/src/lib/server/router/types.ts +21 -0
- package/src/ruvocal/src/lib/server/sendSlack.ts +23 -0
- package/src/ruvocal/src/lib/server/textGeneration/generate.ts +258 -0
- package/src/ruvocal/src/lib/server/textGeneration/index.ts +96 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/fileRefs.ts +155 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/routerResolution.ts +108 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/runMcpFlow.ts +831 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/toolInvocation.ts +349 -0
- package/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts +633 -0
- package/src/ruvocal/src/lib/server/textGeneration/reasoning.ts +23 -0
- package/src/ruvocal/src/lib/server/textGeneration/title.ts +83 -0
- package/src/ruvocal/src/lib/server/textGeneration/types.ts +28 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/prepareFiles.ts +88 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/routing.ts +21 -0
- package/src/ruvocal/src/lib/server/textGeneration/utils/toolPrompt.ts +49 -0
- package/src/ruvocal/src/lib/server/urlSafety.ts +77 -0
- package/src/ruvocal/src/lib/server/usageLimits.ts +30 -0
- package/src/ruvocal/src/lib/stores/autopilotStore.svelte.ts +175 -0
- package/src/ruvocal/src/lib/stores/backgroundGenerations.svelte.ts +32 -0
- package/src/ruvocal/src/lib/stores/backgroundGenerations.ts +1 -0
- package/src/ruvocal/src/lib/stores/errors.ts +9 -0
- package/src/ruvocal/src/lib/stores/isAborted.ts +3 -0
- package/src/ruvocal/src/lib/stores/isPro.ts +4 -0
- package/src/ruvocal/src/lib/stores/loading.ts +3 -0
- package/src/ruvocal/src/lib/stores/mcpServers.ts +534 -0
- package/src/ruvocal/src/lib/stores/pendingChatInput.ts +3 -0
- package/src/ruvocal/src/lib/stores/pendingMessage.ts +9 -0
- package/src/ruvocal/src/lib/stores/settings.ts +182 -0
- package/src/ruvocal/src/lib/stores/shareModal.ts +13 -0
- package/src/ruvocal/src/lib/stores/titleUpdate.ts +8 -0
- package/src/ruvocal/src/lib/stores/wasmMcp.ts +472 -0
- package/src/ruvocal/src/lib/switchTheme.ts +124 -0
- package/src/ruvocal/src/lib/types/AbortedGeneration.ts +8 -0
- package/src/ruvocal/src/lib/types/Assistant.ts +31 -0
- package/src/ruvocal/src/lib/types/AssistantStats.ts +11 -0
- package/src/ruvocal/src/lib/types/ConfigKey.ts +4 -0
- package/src/ruvocal/src/lib/types/ConvSidebar.ts +9 -0
- package/src/ruvocal/src/lib/types/Conversation.ts +27 -0
- package/src/ruvocal/src/lib/types/ConversationStats.ts +13 -0
- package/src/ruvocal/src/lib/types/Message.ts +41 -0
- package/src/ruvocal/src/lib/types/MessageEvent.ts +10 -0
- package/src/ruvocal/src/lib/types/MessageUpdate.ts +139 -0
- package/src/ruvocal/src/lib/types/MigrationResult.ts +7 -0
- package/src/ruvocal/src/lib/types/Model.ts +23 -0
- package/src/ruvocal/src/lib/types/Report.ts +12 -0
- package/src/ruvocal/src/lib/types/Review.ts +6 -0
- package/src/ruvocal/src/lib/types/Semaphore.ts +19 -0
- package/src/ruvocal/src/lib/types/Session.ts +22 -0
- package/src/ruvocal/src/lib/types/Settings.ts +93 -0
- package/src/ruvocal/src/lib/types/SharedConversation.ts +9 -0
- package/src/ruvocal/src/lib/types/Template.ts +6 -0
- package/src/ruvocal/src/lib/types/Timestamps.ts +4 -0
- package/src/ruvocal/src/lib/types/TokenCache.ts +6 -0
- package/src/ruvocal/src/lib/types/Tool.ts +77 -0
- package/src/ruvocal/src/lib/types/UrlDependency.ts +5 -0
- package/src/ruvocal/src/lib/types/User.ts +14 -0
- package/src/ruvocal/src/lib/utils/PublicConfig.svelte.ts +75 -0
- package/src/ruvocal/src/lib/utils/auth.ts +17 -0
- package/src/ruvocal/src/lib/utils/chunk.ts +33 -0
- package/src/ruvocal/src/lib/utils/cookiesAreEnabled.ts +13 -0
- package/src/ruvocal/src/lib/utils/debounce.ts +17 -0
- package/src/ruvocal/src/lib/utils/deepestChild.ts +6 -0
- package/src/ruvocal/src/lib/utils/favicon.ts +21 -0
- package/src/ruvocal/src/lib/utils/fetchJSON.ts +23 -0
- package/src/ruvocal/src/lib/utils/file2base64.ts +14 -0
- package/src/ruvocal/src/lib/utils/formatUserCount.ts +37 -0
- package/src/ruvocal/src/lib/utils/generationState.spec.ts +75 -0
- package/src/ruvocal/src/lib/utils/generationState.ts +26 -0
- package/src/ruvocal/src/lib/utils/getHref.ts +41 -0
- package/src/ruvocal/src/lib/utils/getReturnFromGenerator.ts +7 -0
- package/src/ruvocal/src/lib/utils/haptics.ts +64 -0
- package/src/ruvocal/src/lib/utils/hashConv.ts +12 -0
- package/src/ruvocal/src/lib/utils/hf.ts +17 -0
- package/src/ruvocal/src/lib/utils/isDesktop.ts +7 -0
- package/src/ruvocal/src/lib/utils/isUrl.ts +8 -0
- package/src/ruvocal/src/lib/utils/isVirtualKeyboard.ts +16 -0
- package/src/ruvocal/src/lib/utils/loadAttachmentsFromUrls.ts +115 -0
- package/src/ruvocal/src/lib/utils/marked.spec.ts +96 -0
- package/src/ruvocal/src/lib/utils/marked.ts +531 -0
- package/src/ruvocal/src/lib/utils/mcpValidation.ts +147 -0
- package/src/ruvocal/src/lib/utils/mergeAsyncGenerators.ts +38 -0
- package/src/ruvocal/src/lib/utils/messageUpdates.spec.ts +262 -0
- package/src/ruvocal/src/lib/utils/messageUpdates.ts +324 -0
- package/src/ruvocal/src/lib/utils/mime.ts +56 -0
- package/src/ruvocal/src/lib/utils/models.ts +14 -0
- package/src/ruvocal/src/lib/utils/parseBlocks.ts +120 -0
- package/src/ruvocal/src/lib/utils/parseIncompleteMarkdown.ts +644 -0
- package/src/ruvocal/src/lib/utils/parseStringToList.ts +10 -0
- package/src/ruvocal/src/lib/utils/randomUuid.ts +14 -0
- package/src/ruvocal/src/lib/utils/searchTokens.ts +33 -0
- package/src/ruvocal/src/lib/utils/sha256.ts +7 -0
- package/src/ruvocal/src/lib/utils/stringifyError.ts +12 -0
- package/src/ruvocal/src/lib/utils/sum.ts +3 -0
- package/src/ruvocal/src/lib/utils/template.spec.ts +59 -0
- package/src/ruvocal/src/lib/utils/template.ts +53 -0
- package/src/ruvocal/src/lib/utils/timeout.ts +9 -0
- package/src/ruvocal/src/lib/utils/toolProgress.spec.ts +46 -0
- package/src/ruvocal/src/lib/utils/toolProgress.ts +11 -0
- package/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts +102 -0
- package/src/ruvocal/src/lib/utils/tree/addChildren.ts +48 -0
- package/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts +81 -0
- package/src/ruvocal/src/lib/utils/tree/addSibling.ts +41 -0
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts +110 -0
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.ts +24 -0
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts +31 -0
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.ts +36 -0
- package/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts +15 -0
- package/src/ruvocal/src/lib/utils/tree/isMessageId.ts +5 -0
- package/src/ruvocal/src/lib/utils/tree/tree.d.ts +14 -0
- package/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts +167 -0
- package/src/ruvocal/src/lib/utils/updates.ts +39 -0
- package/src/ruvocal/src/lib/utils/urlParams.ts +13 -0
- package/src/ruvocal/src/lib/wasm/idb.ts +438 -0
- package/src/ruvocal/src/lib/wasm/index.ts +1213 -0
- package/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts +565 -0
- package/src/ruvocal/src/lib/wasm/wasm.worker.ts +332 -0
- package/src/ruvocal/src/lib/wasm/workerClient.ts +166 -0
- package/src/ruvocal/src/lib/workers/autopilotWorker.ts +221 -0
- package/src/ruvocal/src/lib/workers/detailFetchWorker.ts +100 -0
- package/src/ruvocal/src/lib/workers/markdownWorker.ts +61 -0
- package/src/ruvocal/src/routes/+error.svelte +20 -0
- package/src/ruvocal/src/routes/+layout.svelte +324 -0
- package/src/ruvocal/src/routes/+layout.ts +91 -0
- package/src/ruvocal/src/routes/+page.svelte +168 -0
- package/src/ruvocal/src/routes/.well-known/oauth-cimd/+server.ts +37 -0
- package/src/ruvocal/src/routes/__debug/openai/+server.ts +21 -0
- package/src/ruvocal/src/routes/admin/export/+server.ts +159 -0
- package/src/ruvocal/src/routes/admin/stats/compute/+server.ts +16 -0
- package/src/ruvocal/src/routes/api/conversation/[id]/+server.ts +40 -0
- package/src/ruvocal/src/routes/api/conversation/[id]/message/[messageId]/+server.ts +42 -0
- package/src/ruvocal/src/routes/api/conversations/+server.ts +48 -0
- package/src/ruvocal/src/routes/api/fetch-url/+server.ts +147 -0
- package/src/ruvocal/src/routes/api/mcp/health/+server.ts +292 -0
- package/src/ruvocal/src/routes/api/mcp/servers/+server.ts +32 -0
- package/src/ruvocal/src/routes/api/models/+server.ts +25 -0
- package/src/ruvocal/src/routes/api/transcribe/+server.ts +104 -0
- package/src/ruvocal/src/routes/api/user/+server.ts +15 -0
- package/src/ruvocal/src/routes/api/user/validate-token/+server.ts +20 -0
- package/src/ruvocal/src/routes/api/v2/conversations/+server.ts +48 -0
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/+server.ts +94 -0
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/message/[messageId]/+server.ts +43 -0
- package/src/ruvocal/src/routes/api/v2/conversations/import-share/+server.ts +23 -0
- package/src/ruvocal/src/routes/api/v2/debug/config/+server.ts +16 -0
- package/src/ruvocal/src/routes/api/v2/debug/refresh/+server.ts +30 -0
- package/src/ruvocal/src/routes/api/v2/export/+server.ts +196 -0
- package/src/ruvocal/src/routes/api/v2/feature-flags/+server.ts +14 -0
- package/src/ruvocal/src/routes/api/v2/models/+server.ts +38 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/+server.ts +8 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/+server.ts +8 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/subscribe/+server.ts +28 -0
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/subscribe/+server.ts +28 -0
- package/src/ruvocal/src/routes/api/v2/models/old/+server.ts +7 -0
- package/src/ruvocal/src/routes/api/v2/models/refresh/+server.ts +33 -0
- package/src/ruvocal/src/routes/api/v2/public-config/+server.ts +7 -0
- package/src/ruvocal/src/routes/api/v2/user/+server.ts +17 -0
- package/src/ruvocal/src/routes/api/v2/user/billing-orgs/+server.ts +73 -0
- package/src/ruvocal/src/routes/api/v2/user/reports/+server.ts +17 -0
- package/src/ruvocal/src/routes/api/v2/user/settings/+server.ts +110 -0
- package/src/ruvocal/src/routes/conversation/+server.ts +115 -0
- package/src/ruvocal/src/routes/conversation/[id]/+page.svelte +586 -0
- package/src/ruvocal/src/routes/conversation/[id]/+page.ts +60 -0
- package/src/ruvocal/src/routes/conversation/[id]/+server.ts +740 -0
- package/src/ruvocal/src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +66 -0
- package/src/ruvocal/src/routes/conversation/[id]/share/+server.ts +69 -0
- package/src/ruvocal/src/routes/conversation/[id]/stop-generating/+server.ts +35 -0
- package/src/ruvocal/src/routes/healthcheck/+server.ts +3 -0
- package/src/ruvocal/src/routes/login/+server.ts +5 -0
- package/src/ruvocal/src/routes/login/callback/+server.ts +103 -0
- package/src/ruvocal/src/routes/login/callback/updateUser.spec.ts +157 -0
- package/src/ruvocal/src/routes/login/callback/updateUser.ts +215 -0
- package/src/ruvocal/src/routes/logout/+server.ts +18 -0
- package/src/ruvocal/src/routes/metrics/+server.ts +18 -0
- package/src/ruvocal/src/routes/models/+page.svelte +233 -0
- package/src/ruvocal/src/routes/models/[...model]/+page.svelte +161 -0
- package/src/ruvocal/src/routes/models/[...model]/+page.ts +14 -0
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/+server.ts +64 -0
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +28 -0
- package/src/ruvocal/src/routes/privacy/+page.svelte +11 -0
- package/src/ruvocal/src/routes/r/[id]/+page.ts +34 -0
- package/src/ruvocal/src/routes/settings/(nav)/+layout.svelte +282 -0
- package/src/ruvocal/src/routes/settings/(nav)/+layout.ts +1 -0
- package/src/ruvocal/src/routes/settings/(nav)/+page.svelte +0 -0
- package/src/ruvocal/src/routes/settings/(nav)/+server.ts +59 -0
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.svelte +464 -0
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.ts +14 -0
- package/src/ruvocal/src/routes/settings/(nav)/application/+page.svelte +362 -0
- package/src/ruvocal/src/routes/settings/+layout.svelte +40 -0
- package/src/ruvocal/src/styles/highlight-js.css +195 -0
- package/src/ruvocal/src/styles/main.css +144 -0
- package/src/ruvocal/static/chatui/apple-touch-icon.png +0 -0
- package/src/ruvocal/static/chatui/favicon-dark.svg +3 -0
- package/src/ruvocal/static/chatui/favicon-dev.svg +3 -0
- package/src/ruvocal/static/chatui/favicon.ico +0 -0
- package/src/ruvocal/static/chatui/favicon.svg +3 -0
- package/src/ruvocal/static/chatui/icon-128x128.png +0 -0
- package/src/ruvocal/static/chatui/icon-144x144.png +0 -0
- package/src/ruvocal/static/chatui/icon-192x192.png +0 -0
- package/src/ruvocal/static/chatui/icon-256x256.png +0 -0
- package/src/ruvocal/static/chatui/icon-36x36.png +0 -0
- package/src/ruvocal/static/chatui/icon-48x48.png +0 -0
- package/src/ruvocal/static/chatui/icon-512x512.png +0 -0
- package/src/ruvocal/static/chatui/icon-72x72.png +0 -0
- package/src/ruvocal/static/chatui/icon-96x96.png +0 -0
- package/src/ruvocal/static/chatui/icon.svg +3 -0
- package/src/ruvocal/static/chatui/logo.svg +7 -0
- package/src/ruvocal/static/chatui/manifest.json +54 -0
- package/src/ruvocal/static/chatui/omni-welcome.gif +0 -0
- package/src/ruvocal/static/chatui/omni-welcome.png +0 -0
- package/src/ruvocal/static/chatui/welcome.js +184 -0
- package/src/ruvocal/static/chatui/welcome.svg +1 -0
- package/src/ruvocal/static/huggingchat/apple-touch-icon.png +0 -0
- package/src/ruvocal/static/huggingchat/assistants-thumbnail.png +0 -0
- package/src/ruvocal/static/huggingchat/castle-example.jpg +0 -0
- package/src/ruvocal/static/huggingchat/favicon-dark.svg +4 -0
- package/src/ruvocal/static/huggingchat/favicon-dev.svg +4 -0
- package/src/ruvocal/static/huggingchat/favicon.ico +0 -0
- package/src/ruvocal/static/huggingchat/favicon.svg +4 -0
- package/src/ruvocal/static/huggingchat/fulltext-logo.svg +2 -0
- package/src/ruvocal/static/huggingchat/icon-128x128.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-144x144.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-192x192.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-256x256.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-36x36.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-48x48.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-512x512.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-72x72.png +0 -0
- package/src/ruvocal/static/huggingchat/icon-96x96.png +0 -0
- package/src/ruvocal/static/huggingchat/icon.svg +4 -0
- package/src/ruvocal/static/huggingchat/logo.svg +4 -0
- package/src/ruvocal/static/huggingchat/manifest.json +54 -0
- package/src/ruvocal/static/huggingchat/omni-welcome.gif +0 -0
- package/src/ruvocal/static/huggingchat/routes.chat.json +226 -0
- package/src/ruvocal/static/huggingchat/thumbnail.png +0 -0
- package/src/ruvocal/static/huggingchat/tools-thumbnail.png +0 -0
- package/src/ruvocal/static/robots.txt +10 -0
- package/src/ruvocal/static/wasm/rvagent_wasm.js +1539 -0
- package/src/ruvocal/static/wasm/rvagent_wasm_bg.wasm +0 -0
- package/src/ruvocal/stub/@reflink/reflink/index.js +0 -0
- package/src/ruvocal/stub/@reflink/reflink/package.json +5 -0
- package/src/ruvocal/svelte.config.js +53 -0
- package/src/ruvocal/tailwind.config.cjs +30 -0
- package/src/ruvocal/tsconfig.json +19 -0
- package/src/ruvocal/vite.config.ts +87 -0
- package/src/scripts/deploy.sh +116 -0
- package/src/scripts/generate-config.js +245 -0
- package/src/scripts/generate-welcome.js +187 -0
- package/src/scripts/package-rvf.sh +116 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { config } from "$lib/server/config";
|
|
2
|
+
import { logger } from "$lib/server/logger";
|
|
3
|
+
import type { EndpointMessage } from "../endpoints/endpoints";
|
|
4
|
+
import type { Route, RouteConfig, RouteSelection } from "./types";
|
|
5
|
+
import { getRoutes } from "./policy";
|
|
6
|
+
import { getApiToken } from "$lib/server/apiToken";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_LAST_TURNS = 16;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Trim a message by keeping start and end, replacing middle with minimal indicator.
|
|
12
|
+
* Uses simple ellipsis since router only needs context for intent classification, not exact content.
|
|
13
|
+
* @param content - The message content to trim
|
|
14
|
+
* @param maxLength - Maximum total length (including indicator)
|
|
15
|
+
* @returns Trimmed content with start, ellipsis, and end
|
|
16
|
+
*/
|
|
17
|
+
function trimMiddle(content: string, maxLength: number): string {
|
|
18
|
+
if (content.length <= maxLength) return content;
|
|
19
|
+
|
|
20
|
+
const indicator = "…";
|
|
21
|
+
const availableLength = maxLength - indicator.length;
|
|
22
|
+
|
|
23
|
+
if (availableLength <= 0) {
|
|
24
|
+
// If no room even for indicator, just hard truncate
|
|
25
|
+
return content.slice(0, maxLength);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Reserve more space for the start (typically contains context)
|
|
29
|
+
const startLength = Math.ceil(availableLength * 0.6);
|
|
30
|
+
const endLength = availableLength - startLength;
|
|
31
|
+
|
|
32
|
+
// Bug fix: slice(-0) returns entire string, so check for endLength <= 0
|
|
33
|
+
if (endLength <= 0) {
|
|
34
|
+
// Not enough space for end portion, just use start + indicator
|
|
35
|
+
return content.slice(0, availableLength) + indicator;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const start = content.slice(0, startLength);
|
|
39
|
+
const end = content.slice(-endLength);
|
|
40
|
+
|
|
41
|
+
return start + indicator + end;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const PROMPT_TEMPLATE = `
|
|
45
|
+
You are a helpful assistant designed to find the best suited route.
|
|
46
|
+
You are provided with route description within <routes></routes> XML tags:
|
|
47
|
+
|
|
48
|
+
<routes>
|
|
49
|
+
|
|
50
|
+
{routes}
|
|
51
|
+
|
|
52
|
+
</routes>
|
|
53
|
+
|
|
54
|
+
<conversation>
|
|
55
|
+
|
|
56
|
+
{conversation}
|
|
57
|
+
|
|
58
|
+
</conversation>
|
|
59
|
+
|
|
60
|
+
Your task is to decide which route is best suit with user intent on the conversation in <conversation></conversation> XML tags.
|
|
61
|
+
|
|
62
|
+
Follow those instructions:
|
|
63
|
+
1. Use prior turns to choose the best route for the current message if needed.
|
|
64
|
+
2. If no route match the full conversation respond with other route {"route": "other"}.
|
|
65
|
+
3. Analyze the route descriptions and find the best match route for user latest intent.
|
|
66
|
+
4. Respond only with the route name that best matches the user's request, using the exact name in the <routes> block.
|
|
67
|
+
Based on your analysis, provide your response in the following JSON format if you decide to match any route:
|
|
68
|
+
{"route": "route_name"}
|
|
69
|
+
`.trim();
|
|
70
|
+
|
|
71
|
+
function lastNTurns<T>(arr: T[], n = DEFAULT_LAST_TURNS) {
|
|
72
|
+
if (!Array.isArray(arr)) return [] as T[];
|
|
73
|
+
return arr.slice(-n);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function toRouterPrompt(messages: EndpointMessage[], routes: Route[]) {
|
|
77
|
+
const simpleRoutes: RouteConfig[] = routes.map((r) => ({
|
|
78
|
+
name: r.name,
|
|
79
|
+
description: r.description,
|
|
80
|
+
}));
|
|
81
|
+
const maxAssistantLength = parseInt(config.LLM_ROUTER_MAX_ASSISTANT_LENGTH || "1000", 10);
|
|
82
|
+
const maxPrevUserLength = parseInt(config.LLM_ROUTER_MAX_PREV_USER_LENGTH || "1000", 10);
|
|
83
|
+
|
|
84
|
+
const convo = messages
|
|
85
|
+
.map((m) => ({ role: m.from, content: m.content }))
|
|
86
|
+
.filter((m) => typeof m.content === "string" && m.content.trim() !== "");
|
|
87
|
+
|
|
88
|
+
// Find the last user message index to preserve its full content
|
|
89
|
+
const lastUserIndex = convo.findLastIndex((m) => m.role === "user");
|
|
90
|
+
|
|
91
|
+
const trimmedConvo = convo.map((m, idx) => {
|
|
92
|
+
if (typeof m.content !== "string") return m;
|
|
93
|
+
|
|
94
|
+
// Trim assistant messages to reduce routing prompt size and improve latency
|
|
95
|
+
// Keep start and end for better context understanding
|
|
96
|
+
if (m.role === "assistant") {
|
|
97
|
+
return {
|
|
98
|
+
...m,
|
|
99
|
+
content: trimMiddle(m.content, maxAssistantLength),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Trim previous user messages, but keep the latest user message full
|
|
104
|
+
// Keep start and end to preserve both context and question
|
|
105
|
+
if (m.role === "user" && idx !== lastUserIndex) {
|
|
106
|
+
return {
|
|
107
|
+
...m,
|
|
108
|
+
content: trimMiddle(m.content, maxPrevUserLength),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return m;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
return PROMPT_TEMPLATE.replace("{routes}", JSON.stringify(simpleRoutes)).replace(
|
|
116
|
+
"{conversation}",
|
|
117
|
+
JSON.stringify(lastNTurns(trimmedConvo))
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parseRouteName(text: string): string | undefined {
|
|
122
|
+
if (!text) return;
|
|
123
|
+
try {
|
|
124
|
+
const obj = JSON.parse(text);
|
|
125
|
+
if (typeof obj?.route === "string" && obj.route.trim()) return obj.route.trim();
|
|
126
|
+
} catch {}
|
|
127
|
+
const m = text.match(/["']route["']\s*:\s*["']([^"']+)["']/);
|
|
128
|
+
if (m?.[1]) return m[1].trim();
|
|
129
|
+
try {
|
|
130
|
+
const obj = JSON.parse(text.replace(/'/g, '"'));
|
|
131
|
+
if (typeof obj?.route === "string" && obj.route.trim()) return obj.route.trim();
|
|
132
|
+
} catch {}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function archSelectRoute(
|
|
137
|
+
messages: EndpointMessage[],
|
|
138
|
+
traceId: string | undefined,
|
|
139
|
+
locals: App.Locals | undefined
|
|
140
|
+
): Promise<RouteSelection> {
|
|
141
|
+
const routes = await getRoutes();
|
|
142
|
+
const prompt = toRouterPrompt(messages, routes);
|
|
143
|
+
|
|
144
|
+
const baseURL = (config.LLM_ROUTER_ARCH_BASE_URL || "").replace(/\/$/, "");
|
|
145
|
+
const archModel = config.LLM_ROUTER_ARCH_MODEL || "router/omni";
|
|
146
|
+
|
|
147
|
+
if (!baseURL) {
|
|
148
|
+
logger.warn("LLM_ROUTER_ARCH_BASE_URL not set; routing will fail over to fallback.");
|
|
149
|
+
return { routeName: "arch_router_failure" };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const headers: HeadersInit = {
|
|
153
|
+
Authorization: `Bearer ${getApiToken(locals)}`,
|
|
154
|
+
"Content-Type": "application/json",
|
|
155
|
+
// Bill to organization if configured (HuggingChat only)
|
|
156
|
+
...(config.isHuggingChat && locals?.billingOrganization
|
|
157
|
+
? { "X-HF-Bill-To": locals.billingOrganization }
|
|
158
|
+
: {}),
|
|
159
|
+
};
|
|
160
|
+
const body = {
|
|
161
|
+
model: archModel,
|
|
162
|
+
messages: [{ role: "user", content: prompt }],
|
|
163
|
+
temperature: 0,
|
|
164
|
+
max_tokens: 16,
|
|
165
|
+
stream: false,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const ctrl = new AbortController();
|
|
169
|
+
const timeoutMs = Number(config.LLM_ROUTER_ARCH_TIMEOUT_MS || 10000);
|
|
170
|
+
const to = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const resp = await fetch(`${baseURL}/chat/completions`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers,
|
|
176
|
+
body: JSON.stringify(body),
|
|
177
|
+
signal: ctrl.signal,
|
|
178
|
+
});
|
|
179
|
+
clearTimeout(to);
|
|
180
|
+
if (!resp.ok) {
|
|
181
|
+
// Extract error message from response
|
|
182
|
+
let errorMessage = `arch-router ${resp.status}`;
|
|
183
|
+
try {
|
|
184
|
+
const errorData = await resp.json();
|
|
185
|
+
// Try to extract message from OpenAI-style error format
|
|
186
|
+
if (errorData.error?.message) {
|
|
187
|
+
errorMessage = errorData.error.message;
|
|
188
|
+
} else if (errorData.message) {
|
|
189
|
+
errorMessage = errorData.message;
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// If JSON parsing fails, use status text
|
|
193
|
+
errorMessage = resp.statusText || errorMessage;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
logger.warn(
|
|
197
|
+
{ status: resp.status, error: errorMessage, traceId },
|
|
198
|
+
"[arch] router returned error"
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return {
|
|
202
|
+
routeName: "arch_router_failure",
|
|
203
|
+
error: {
|
|
204
|
+
message: errorMessage,
|
|
205
|
+
statusCode: resp.status,
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const data: { choices: { message: { content: string } }[] } = await resp.json();
|
|
210
|
+
const text = (data?.choices?.[0]?.message?.content ?? "").toString().trim();
|
|
211
|
+
const raw = parseRouteName(text);
|
|
212
|
+
|
|
213
|
+
const other = config.LLM_ROUTER_OTHER_ROUTE || "casual_conversation";
|
|
214
|
+
const chosen = raw === "other" ? other : raw || "casual_conversation";
|
|
215
|
+
const exists = routes.some((r) => r.name === chosen);
|
|
216
|
+
return { routeName: exists ? chosen : "casual_conversation" };
|
|
217
|
+
} catch (e) {
|
|
218
|
+
clearTimeout(to);
|
|
219
|
+
const err = e as Error;
|
|
220
|
+
logger.warn({ err: String(e), traceId }, "arch router selection failed");
|
|
221
|
+
|
|
222
|
+
// Return error with context but no status code (network/timeout errors)
|
|
223
|
+
return {
|
|
224
|
+
routeName: "arch_router_failure",
|
|
225
|
+
error: {
|
|
226
|
+
message: err.message || String(e),
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Endpoint,
|
|
3
|
+
EndpointParameters,
|
|
4
|
+
EndpointMessage,
|
|
5
|
+
TextGenerationStreamOutputSimplified,
|
|
6
|
+
} from "../endpoints/endpoints";
|
|
7
|
+
import endpoints from "../endpoints/endpoints";
|
|
8
|
+
import type { ProcessedModel } from "../models";
|
|
9
|
+
import { config } from "$lib/server/config";
|
|
10
|
+
import { logger } from "$lib/server/logger";
|
|
11
|
+
import { archSelectRoute } from "./arch";
|
|
12
|
+
import { getRoutes, resolveRouteModels } from "./policy";
|
|
13
|
+
import { getApiToken } from "$lib/server/apiToken";
|
|
14
|
+
import { ROUTER_FAILURE } from "./types";
|
|
15
|
+
import {
|
|
16
|
+
hasActiveToolsSelection,
|
|
17
|
+
isRouterToolsBypassEnabled,
|
|
18
|
+
pickToolsCapableModel,
|
|
19
|
+
ROUTER_TOOLS_ROUTE,
|
|
20
|
+
} from "./toolsRoute";
|
|
21
|
+
import { getConfiguredMultimodalModelId } from "./multimodal";
|
|
22
|
+
|
|
23
|
+
const REASONING_BLOCK_REGEX = /<think>[\s\S]*?(?:<\/think>|$)/g;
|
|
24
|
+
|
|
25
|
+
const ROUTER_MULTIMODAL_ROUTE = "multimodal";
|
|
26
|
+
|
|
27
|
+
// Cache models at module level to avoid redundant dynamic imports on every request
|
|
28
|
+
let cachedModels: ProcessedModel[] | undefined;
|
|
29
|
+
|
|
30
|
+
async function getModels(): Promise<ProcessedModel[]> {
|
|
31
|
+
if (!cachedModels) {
|
|
32
|
+
const mod = await import("../models");
|
|
33
|
+
cachedModels = (mod as { models: ProcessedModel[] }).models;
|
|
34
|
+
}
|
|
35
|
+
return cachedModels;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Custom error class that preserves HTTP status codes
|
|
40
|
+
*/
|
|
41
|
+
class HTTPError extends Error {
|
|
42
|
+
constructor(
|
|
43
|
+
message: string,
|
|
44
|
+
public statusCode?: number
|
|
45
|
+
) {
|
|
46
|
+
super(message);
|
|
47
|
+
this.name = "HTTPError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract the actual error message and status from OpenAI SDK errors or other upstream errors
|
|
53
|
+
*/
|
|
54
|
+
function extractUpstreamError(error: unknown): { message: string; statusCode?: number } {
|
|
55
|
+
// Check if it's an OpenAI APIError with structured error info
|
|
56
|
+
if (error && typeof error === "object") {
|
|
57
|
+
const err = error as Record<string, unknown>;
|
|
58
|
+
|
|
59
|
+
// OpenAI SDK error with error.error.message and status
|
|
60
|
+
if (
|
|
61
|
+
err.error &&
|
|
62
|
+
typeof err.error === "object" &&
|
|
63
|
+
"message" in err.error &&
|
|
64
|
+
typeof err.error.message === "string"
|
|
65
|
+
) {
|
|
66
|
+
return {
|
|
67
|
+
message: err.error.message,
|
|
68
|
+
statusCode: typeof err.status === "number" ? err.status : undefined,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// HTTPError or error with statusCode
|
|
73
|
+
if (typeof err.statusCode === "number" && typeof err.message === "string") {
|
|
74
|
+
return { message: err.message, statusCode: err.statusCode };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Error with status field
|
|
78
|
+
if (typeof err.status === "number" && typeof err.message === "string") {
|
|
79
|
+
return { message: err.message, statusCode: err.status };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Direct error message
|
|
83
|
+
if (typeof err.message === "string") {
|
|
84
|
+
return { message: err.message };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return { message: String(error) };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Determines if an error is a policy/entitlement error that should be shown to users immediately
|
|
93
|
+
* (vs transient errors that should trigger fallback)
|
|
94
|
+
*/
|
|
95
|
+
function isPolicyError(statusCode?: number): boolean {
|
|
96
|
+
if (!statusCode) return false;
|
|
97
|
+
// 400: Bad Request, 402: Payment Required, 401: Unauthorized, 403: Forbidden
|
|
98
|
+
return statusCode === 400 || statusCode === 401 || statusCode === 402 || statusCode === 403;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function stripReasoningBlocks(text: string): string {
|
|
102
|
+
const stripped = text.replace(REASONING_BLOCK_REGEX, "");
|
|
103
|
+
return stripped === text ? text : stripped.trim();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function stripReasoningFromMessage(message: EndpointMessage): EndpointMessage {
|
|
107
|
+
const content =
|
|
108
|
+
typeof message.content === "string" ? stripReasoningBlocks(message.content) : message.content;
|
|
109
|
+
return {
|
|
110
|
+
...message,
|
|
111
|
+
content,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create an Endpoint that performs route selection via Arch and then forwards
|
|
117
|
+
* to the selected model (with fallbacks) using the OpenAI-compatible endpoint.
|
|
118
|
+
*/
|
|
119
|
+
export async function makeRouterEndpoint(routerModel: ProcessedModel): Promise<Endpoint> {
|
|
120
|
+
return async function routerEndpoint(params: EndpointParameters) {
|
|
121
|
+
const routes = await getRoutes();
|
|
122
|
+
const sanitizedMessages = params.messages.map(stripReasoningFromMessage);
|
|
123
|
+
const routerMultimodalEnabled =
|
|
124
|
+
(config.LLM_ROUTER_ENABLE_MULTIMODAL || "").toLowerCase() === "true";
|
|
125
|
+
const routerToolsEnabled = isRouterToolsBypassEnabled();
|
|
126
|
+
const hasImageInput = sanitizedMessages.some((message) =>
|
|
127
|
+
(message.files ?? []).some(
|
|
128
|
+
(file) => typeof file?.mime === "string" && file.mime.startsWith("image/")
|
|
129
|
+
)
|
|
130
|
+
);
|
|
131
|
+
// Tools are considered "active" if the client indicated any enabled MCP server
|
|
132
|
+
const hasToolsActive = hasActiveToolsSelection(params.locals);
|
|
133
|
+
|
|
134
|
+
// Helper to create an OpenAI endpoint for a specific candidate model id
|
|
135
|
+
async function createCandidateEndpoint(candidateModelId: string): Promise<Endpoint> {
|
|
136
|
+
// Try to use the real candidate model config if present in chat-ui's model list
|
|
137
|
+
let modelForCall: ProcessedModel | undefined;
|
|
138
|
+
try {
|
|
139
|
+
const all = await getModels();
|
|
140
|
+
modelForCall = all?.find((m) => m.id === candidateModelId || m.name === candidateModelId);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
logger.warn({ err: String(e) }, "[router] failed to load models for candidate lookup");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!modelForCall) {
|
|
146
|
+
// Fallback: clone router model with candidate id
|
|
147
|
+
modelForCall = {
|
|
148
|
+
...routerModel,
|
|
149
|
+
id: candidateModelId,
|
|
150
|
+
name: candidateModelId,
|
|
151
|
+
displayName: candidateModelId,
|
|
152
|
+
} as ProcessedModel;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return endpoints.openai({
|
|
156
|
+
type: "openai",
|
|
157
|
+
baseURL: (config.OPENAI_BASE_URL || "https://router.huggingface.co/v1").replace(/\/$/, ""),
|
|
158
|
+
apiKey: getApiToken(params.locals),
|
|
159
|
+
model: modelForCall,
|
|
160
|
+
// Ensure streaming path is used
|
|
161
|
+
streamingSupported: true,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Yield router metadata for immediate UI display, using the actual candidate
|
|
166
|
+
async function* metadataThenStream(
|
|
167
|
+
gen: AsyncGenerator<TextGenerationStreamOutputSimplified>,
|
|
168
|
+
actualModel: string,
|
|
169
|
+
selectedRoute: string
|
|
170
|
+
) {
|
|
171
|
+
yield {
|
|
172
|
+
token: { id: 0, text: "", special: true, logprob: 0 },
|
|
173
|
+
generated_text: null,
|
|
174
|
+
details: null,
|
|
175
|
+
routerMetadata: { route: selectedRoute, model: actualModel },
|
|
176
|
+
};
|
|
177
|
+
for await (const ev of gen) yield ev;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (routerMultimodalEnabled && hasImageInput) {
|
|
181
|
+
let multimodalCandidate: string | undefined;
|
|
182
|
+
try {
|
|
183
|
+
const all = await getModels();
|
|
184
|
+
multimodalCandidate = getConfiguredMultimodalModelId(all);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
logger.warn({ err: String(e) }, "[router] failed to load models for multimodal lookup");
|
|
187
|
+
}
|
|
188
|
+
if (!multimodalCandidate) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
"Router multimodal is enabled but LLM_ROUTER_MULTIMODAL_MODEL is not correctly configured. Remove the image or configure a multimodal model via LLM_ROUTER_MULTIMODAL_MODEL."
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
logger.info(
|
|
196
|
+
{ route: ROUTER_MULTIMODAL_ROUTE, model: multimodalCandidate },
|
|
197
|
+
"[router] multimodal input detected; bypassing Arch selection"
|
|
198
|
+
);
|
|
199
|
+
const ep = await createCandidateEndpoint(multimodalCandidate);
|
|
200
|
+
const gen = await ep({ ...params });
|
|
201
|
+
return metadataThenStream(gen, multimodalCandidate, ROUTER_MULTIMODAL_ROUTE);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
const { message, statusCode } = extractUpstreamError(e);
|
|
204
|
+
logger.error(
|
|
205
|
+
{
|
|
206
|
+
route: ROUTER_MULTIMODAL_ROUTE,
|
|
207
|
+
model: multimodalCandidate,
|
|
208
|
+
err: message,
|
|
209
|
+
...(statusCode && { status: statusCode }),
|
|
210
|
+
},
|
|
211
|
+
"[router] multimodal fallback failed"
|
|
212
|
+
);
|
|
213
|
+
throw statusCode ? new HTTPError(message, statusCode) : new Error(message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function findToolsCandidateModel(): Promise<ProcessedModel | undefined> {
|
|
218
|
+
try {
|
|
219
|
+
const all = await getModels();
|
|
220
|
+
return pickToolsCapableModel(all);
|
|
221
|
+
} catch (e) {
|
|
222
|
+
logger.warn({ err: String(e) }, "[router] failed to load models for tools lookup");
|
|
223
|
+
return undefined;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (routerToolsEnabled && hasToolsActive) {
|
|
228
|
+
const toolsModel = await findToolsCandidateModel();
|
|
229
|
+
const toolsCandidate = toolsModel?.id ?? toolsModel?.name;
|
|
230
|
+
if (!toolsCandidate) {
|
|
231
|
+
// No tool-capable model found — continue with normal routing instead of hard failing
|
|
232
|
+
} else {
|
|
233
|
+
try {
|
|
234
|
+
logger.info(
|
|
235
|
+
{ route: ROUTER_TOOLS_ROUTE, model: toolsCandidate },
|
|
236
|
+
"[router] tools active; bypassing Arch selection"
|
|
237
|
+
);
|
|
238
|
+
const ep = await createCandidateEndpoint(toolsCandidate);
|
|
239
|
+
const gen = await ep({ ...params });
|
|
240
|
+
return metadataThenStream(gen, toolsCandidate, ROUTER_TOOLS_ROUTE);
|
|
241
|
+
} catch (e) {
|
|
242
|
+
const { message, statusCode } = extractUpstreamError(e);
|
|
243
|
+
const logData = {
|
|
244
|
+
route: ROUTER_TOOLS_ROUTE,
|
|
245
|
+
model: toolsCandidate,
|
|
246
|
+
err: message,
|
|
247
|
+
...(statusCode && { status: statusCode }),
|
|
248
|
+
};
|
|
249
|
+
if (statusCode === 402) {
|
|
250
|
+
logger.warn(logData, "[router] tools fallback failed due to payment required");
|
|
251
|
+
} else {
|
|
252
|
+
logger.error(logData, "[router] tools fallback failed");
|
|
253
|
+
}
|
|
254
|
+
throw statusCode ? new HTTPError(message, statusCode) : new Error(message);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const routeSelection = await archSelectRoute(sanitizedMessages, undefined, params.locals);
|
|
260
|
+
|
|
261
|
+
// If arch router failed with an error, only hard-fail for policy errors (402/401/403)
|
|
262
|
+
// For transient errors (5xx, timeouts, network), allow fallback to continue
|
|
263
|
+
if (routeSelection.routeName === ROUTER_FAILURE && routeSelection.error) {
|
|
264
|
+
const { message, statusCode } = routeSelection.error;
|
|
265
|
+
|
|
266
|
+
if (isPolicyError(statusCode)) {
|
|
267
|
+
// Policy errors should be surfaced to the user immediately (e.g., subscription required)
|
|
268
|
+
logger.error(
|
|
269
|
+
{ err: message, ...(statusCode && { status: statusCode }) },
|
|
270
|
+
"[router] arch router failed with policy error, propagating to client"
|
|
271
|
+
);
|
|
272
|
+
throw statusCode ? new HTTPError(message, statusCode) : new Error(message);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Transient errors: log and continue to fallback
|
|
276
|
+
logger.warn(
|
|
277
|
+
{ err: message, ...(statusCode && { status: statusCode }) },
|
|
278
|
+
"[router] arch router failed with transient error, attempting fallback"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const fallbackModel = config.LLM_ROUTER_FALLBACK_MODEL || routerModel.id;
|
|
283
|
+
const { candidates } = resolveRouteModels(routeSelection.routeName, routes, fallbackModel);
|
|
284
|
+
|
|
285
|
+
let lastErr: unknown = undefined;
|
|
286
|
+
for (const candidate of candidates) {
|
|
287
|
+
try {
|
|
288
|
+
logger.info(
|
|
289
|
+
{ route: routeSelection.routeName, model: candidate },
|
|
290
|
+
"[router] trying candidate"
|
|
291
|
+
);
|
|
292
|
+
const ep = await createCandidateEndpoint(candidate);
|
|
293
|
+
const gen = await ep({ ...params });
|
|
294
|
+
return metadataThenStream(gen, candidate, routeSelection.routeName);
|
|
295
|
+
} catch (e) {
|
|
296
|
+
lastErr = e;
|
|
297
|
+
const { message: errMsg, statusCode: errStatus } = extractUpstreamError(e);
|
|
298
|
+
logger.warn(
|
|
299
|
+
{
|
|
300
|
+
route: routeSelection.routeName,
|
|
301
|
+
model: candidate,
|
|
302
|
+
err: errMsg,
|
|
303
|
+
...(errStatus && { status: errStatus }),
|
|
304
|
+
},
|
|
305
|
+
"[router] candidate failed"
|
|
306
|
+
);
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Exhausted all candidates — throw to signal upstream failure
|
|
312
|
+
// Forward the upstream error to the client
|
|
313
|
+
const { message, statusCode } = extractUpstreamError(lastErr);
|
|
314
|
+
throw statusCode ? new HTTPError(message, statusCode) : new Error(message);
|
|
315
|
+
};
|
|
316
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { config } from "$lib/server/config";
|
|
2
|
+
import type { ProcessedModel } from "../models";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the configured multimodal model when it exists and is valid.
|
|
6
|
+
* - Requires LLM_ROUTER_MULTIMODAL_MODEL to be set (id or name).
|
|
7
|
+
* - Ignores router aliases and non-multimodal models.
|
|
8
|
+
*/
|
|
9
|
+
export function findConfiguredMultimodalModel(
|
|
10
|
+
models: ProcessedModel[] | undefined
|
|
11
|
+
): ProcessedModel | undefined {
|
|
12
|
+
const preferredModelId = (config.LLM_ROUTER_MULTIMODAL_MODEL || "").trim();
|
|
13
|
+
if (!preferredModelId || !models?.length) return undefined;
|
|
14
|
+
|
|
15
|
+
return models.find(
|
|
16
|
+
(candidate) =>
|
|
17
|
+
(candidate.id === preferredModelId || candidate.name === preferredModelId) &&
|
|
18
|
+
!candidate.isRouter &&
|
|
19
|
+
candidate.multimodal
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function getConfiguredMultimodalModelId(
|
|
24
|
+
models: ProcessedModel[] | undefined
|
|
25
|
+
): string | undefined {
|
|
26
|
+
const model = findConfiguredMultimodalModel(models);
|
|
27
|
+
return model?.id ?? model?.name;
|
|
28
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { config } from "$lib/server/config";
|
|
3
|
+
import type { Route } from "./types";
|
|
4
|
+
|
|
5
|
+
let ROUTES: Route[] = [];
|
|
6
|
+
let loaded = false;
|
|
7
|
+
|
|
8
|
+
export async function loadPolicy(): Promise<Route[]> {
|
|
9
|
+
const path = config.LLM_ROUTER_ROUTES_PATH;
|
|
10
|
+
const text = await readFile(path, "utf8");
|
|
11
|
+
const arr = JSON.parse(text) as Route[];
|
|
12
|
+
if (!Array.isArray(arr)) {
|
|
13
|
+
throw new Error("Routes config must be a flat array of routes");
|
|
14
|
+
}
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
for (const r of arr) {
|
|
17
|
+
if (!r?.name || !r?.description || !r?.primary_model) {
|
|
18
|
+
throw new Error(`Invalid route entry: ${JSON.stringify(r)}`);
|
|
19
|
+
}
|
|
20
|
+
if (seen.has(r.name)) {
|
|
21
|
+
throw new Error(`Duplicate route name: ${r.name}`);
|
|
22
|
+
}
|
|
23
|
+
seen.add(r.name);
|
|
24
|
+
}
|
|
25
|
+
ROUTES = arr;
|
|
26
|
+
loaded = true;
|
|
27
|
+
return ROUTES;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function getRoutes(): Promise<Route[]> {
|
|
31
|
+
if (!loaded) await loadPolicy();
|
|
32
|
+
return ROUTES;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function resolveRouteModels(
|
|
36
|
+
routeName: string,
|
|
37
|
+
routes: Route[],
|
|
38
|
+
fallbackModel: string
|
|
39
|
+
): { candidates: string[] } {
|
|
40
|
+
if (routeName === "arch_router_failure") {
|
|
41
|
+
return { candidates: [fallbackModel] };
|
|
42
|
+
}
|
|
43
|
+
const sel =
|
|
44
|
+
routes.find((r) => r.name === routeName) ||
|
|
45
|
+
routes.find((r) => r.name === "casual_conversation");
|
|
46
|
+
if (!sel) return { candidates: [fallbackModel] };
|
|
47
|
+
const fallbacks = Array.isArray(sel.fallback_models) ? sel.fallback_models : [];
|
|
48
|
+
return { candidates: [sel.primary_model, ...fallbacks] };
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { config } from "$lib/server/config";
|
|
2
|
+
import { logger } from "$lib/server/logger";
|
|
3
|
+
import type { ProcessedModel } from "../models";
|
|
4
|
+
|
|
5
|
+
export const ROUTER_TOOLS_ROUTE = "agentic";
|
|
6
|
+
|
|
7
|
+
type LocalsWithMcp = App.Locals & {
|
|
8
|
+
mcp?: {
|
|
9
|
+
selectedServers?: unknown[];
|
|
10
|
+
selectedServerNames?: unknown[];
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function isRouterToolsBypassEnabled(): boolean {
|
|
15
|
+
return (config.LLM_ROUTER_ENABLE_TOOLS || "").toLowerCase() === "true";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function hasActiveToolsSelection(locals: App.Locals | undefined): boolean {
|
|
19
|
+
try {
|
|
20
|
+
const reqMcp = (locals as LocalsWithMcp | undefined)?.mcp;
|
|
21
|
+
const byConfig =
|
|
22
|
+
Array.isArray(reqMcp?.selectedServers) && (reqMcp?.selectedServers?.length ?? 0) > 0;
|
|
23
|
+
const byName =
|
|
24
|
+
Array.isArray(reqMcp?.selectedServerNames) && (reqMcp?.selectedServerNames?.length ?? 0) > 0;
|
|
25
|
+
return Boolean(byConfig || byName);
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function pickToolsCapableModel(
|
|
32
|
+
models: ProcessedModel[] | undefined
|
|
33
|
+
): ProcessedModel | undefined {
|
|
34
|
+
const preferredRaw = (config as unknown as Record<string, string>).LLM_ROUTER_TOOLS_MODEL;
|
|
35
|
+
const preferred = preferredRaw?.trim();
|
|
36
|
+
if (!preferred) {
|
|
37
|
+
logger.warn("[router] tools bypass requested but LLM_ROUTER_TOOLS_MODEL is not set");
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
if (!models?.length) return undefined;
|
|
41
|
+
const found = models.find((m) => m.id === preferred || m.name === preferred);
|
|
42
|
+
if (!found) {
|
|
43
|
+
logger.warn(
|
|
44
|
+
{ configuredModel: preferred },
|
|
45
|
+
"[router] configured tools model not found; falling back to Arch routing"
|
|
46
|
+
);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
logger.info({ model: found.id ?? found.name }, "[router] using configured tools model");
|
|
50
|
+
return found;
|
|
51
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export interface Route {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
primary_model: string;
|
|
5
|
+
fallback_models?: string[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface RouteConfig {
|
|
9
|
+
name: string;
|
|
10
|
+
description: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface RouteSelection {
|
|
14
|
+
routeName: string;
|
|
15
|
+
error?: {
|
|
16
|
+
message: string;
|
|
17
|
+
statusCode?: number;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const ROUTER_FAILURE = "arch_router_failure";
|