@tenex-chat/backend 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -0
- package/dist/backend-wrapper.cjs +3 -0
- package/dist/src/index.js +331928 -0
- package/package.json +103 -0
- package/src/agents/AgentRegistry.ts +418 -0
- package/src/agents/AgentStorage.ts +1133 -0
- package/src/agents/ConfigResolver.ts +229 -0
- package/src/agents/agent-installer.ts +236 -0
- package/src/agents/agent-loader.ts +241 -0
- package/src/agents/constants.ts +82 -0
- package/src/agents/errors.ts +48 -0
- package/src/agents/execution/AgentExecutor.ts +561 -0
- package/src/agents/execution/ExecutionContextFactory.ts +112 -0
- package/src/agents/execution/MessageCompiler.ts +597 -0
- package/src/agents/execution/MessageSyncer.ts +100 -0
- package/src/agents/execution/PostCompletionChecker.ts +278 -0
- package/src/agents/execution/ProgressMonitor.ts +50 -0
- package/src/agents/execution/RALResolver.ts +177 -0
- package/src/agents/execution/SessionManager.ts +181 -0
- package/src/agents/execution/StreamCallbacks.ts +312 -0
- package/src/agents/execution/StreamExecutionHandler.ts +579 -0
- package/src/agents/execution/StreamSetup.ts +313 -0
- package/src/agents/execution/ToolEventHandlers.ts +239 -0
- package/src/agents/execution/ToolExecutionTracker.ts +498 -0
- package/src/agents/execution/ToolResultUtils.ts +97 -0
- package/src/agents/execution/ToolSupervisionWrapper.ts +174 -0
- package/src/agents/execution/constants.ts +16 -0
- package/src/agents/execution/index.ts +3 -0
- package/src/agents/execution/types.ts +96 -0
- package/src/agents/execution/utils.ts +26 -0
- package/src/agents/index.ts +4 -0
- package/src/agents/script-installer.ts +266 -0
- package/src/agents/supervision/SupervisorLLMService.ts +253 -0
- package/src/agents/supervision/SupervisorOrchestrator.ts +471 -0
- package/src/agents/supervision/heuristics/ConsecutiveToolsWithoutTodoHeuristic.ts +73 -0
- package/src/agents/supervision/heuristics/DelegationClaimHeuristic.ts +80 -0
- package/src/agents/supervision/heuristics/HeuristicRegistry.ts +114 -0
- package/src/agents/supervision/heuristics/PendingTodosHeuristic.ts +93 -0
- package/src/agents/supervision/heuristics/SilentAgentHeuristic.ts +54 -0
- package/src/agents/supervision/heuristics/index.ts +5 -0
- package/src/agents/supervision/index.ts +28 -0
- package/src/agents/supervision/registerHeuristics.ts +110 -0
- package/src/agents/supervision/supervisionHealthCheck.ts +123 -0
- package/src/agents/supervision/types.ts +171 -0
- package/src/agents/tool-names.ts +46 -0
- package/src/agents/tool-normalization.ts +184 -0
- package/src/agents/types/index.ts +2 -0
- package/src/agents/types/runtime.ts +74 -0
- package/src/agents/types/storage.ts +145 -0
- package/src/commands/agent/import/index.ts +6 -0
- package/src/commands/agent/import/openclaw-distiller.ts +57 -0
- package/src/commands/agent/import/openclaw-reader.ts +141 -0
- package/src/commands/agent/import/openclaw.ts +154 -0
- package/src/commands/agent/index.ts +6 -0
- package/src/commands/agent.ts +215 -0
- package/src/commands/daemon.ts +198 -0
- package/src/commands/doctor.ts +134 -0
- package/src/commands/setup/embed.ts +228 -0
- package/src/commands/setup/global-system-prompt.ts +223 -0
- package/src/commands/setup/image.ts +179 -0
- package/src/commands/setup/index.ts +16 -0
- package/src/commands/setup/interactive.ts +95 -0
- package/src/commands/setup/llm.ts +38 -0
- package/src/commands/setup/onboarding.ts +294 -0
- package/src/commands/setup/providers.ts +27 -0
- package/src/constants.ts +34 -0
- package/src/conversations/ConversationDiskReader.ts +148 -0
- package/src/conversations/ConversationRegistry.ts +728 -0
- package/src/conversations/ConversationStore.ts +868 -0
- package/src/conversations/MessageBuilder.ts +866 -0
- package/src/conversations/executionTime.ts +62 -0
- package/src/conversations/formatters/DelegationXmlFormatter.ts +64 -0
- package/src/conversations/formatters/ThreadedConversationFormatter.ts +303 -0
- package/src/conversations/formatters/index.ts +9 -0
- package/src/conversations/formatters/utils/MessageFormatter.ts +46 -0
- package/src/conversations/formatters/utils/TimestampFormatter.ts +56 -0
- package/src/conversations/formatters/utils/TreeBuilder.ts +131 -0
- package/src/conversations/formatters/utils/TreeRenderer.ts +49 -0
- package/src/conversations/index.ts +2 -0
- package/src/conversations/persistence/ToolMessageStorage.ts +143 -0
- package/src/conversations/search/ConversationIndexManager.ts +393 -0
- package/src/conversations/search/QueryParser.ts +114 -0
- package/src/conversations/search/SearchEngine.ts +175 -0
- package/src/conversations/search/SnippetExtractor.ts +345 -0
- package/src/conversations/search/embeddings/ConversationEmbeddingService.ts +484 -0
- package/src/conversations/search/embeddings/ConversationIndexingJob.ts +320 -0
- package/src/conversations/search/embeddings/IndexingStateManager.ts +338 -0
- package/src/conversations/search/embeddings/index.ts +18 -0
- package/src/conversations/search/index.ts +49 -0
- package/src/conversations/search/types.ts +124 -0
- package/src/conversations/services/CategoryManager.ts +160 -0
- package/src/conversations/services/ConversationResolver.ts +296 -0
- package/src/conversations/services/ConversationSummarizer.ts +234 -0
- package/src/conversations/services/MetadataDebounceManager.ts +188 -0
- package/src/conversations/services/index.ts +2 -0
- package/src/conversations/types.ts +148 -0
- package/src/conversations/utils/content-utils.ts +69 -0
- package/src/conversations/utils/image-placeholder.ts +281 -0
- package/src/conversations/utils/image-url-utils.ts +171 -0
- package/src/conversations/utils/multimodal-content.ts +90 -0
- package/src/conversations/utils/tool-result-truncator.ts +159 -0
- package/src/daemon/Daemon.ts +1883 -0
- package/src/daemon/ProjectRuntime.ts +657 -0
- package/src/daemon/RestartState.ts +152 -0
- package/src/daemon/RuntimeLifecycle.ts +268 -0
- package/src/daemon/SubscriptionManager.ts +305 -0
- package/src/daemon/UnixSocketTransport.ts +318 -0
- package/src/daemon/filters/SubscriptionFilterBuilder.ts +119 -0
- package/src/daemon/index.ts +9 -0
- package/src/daemon/routing/DaemonRouter.ts +491 -0
- package/src/daemon/types.ts +150 -0
- package/src/daemon/utils/routing-log.ts +76 -0
- package/src/daemon/utils/telemetry.ts +173 -0
- package/src/event-handler/agentDeletion.ts +383 -0
- package/src/event-handler/index.ts +749 -0
- package/src/event-handler/newConversation.ts +165 -0
- package/src/event-handler/project.ts +166 -0
- package/src/event-handler/reply.ts +18 -0
- package/src/events/NDKAgentDefinition.ts +292 -0
- package/src/events/NDKAgentLesson.ts +106 -0
- package/src/events/NDKEventMetadata.ts +34 -0
- package/src/events/NDKMCPTool.ts +60 -0
- package/src/events/NDKProjectStatus.ts +384 -0
- package/src/events/index.ts +4 -0
- package/src/index.ts +126 -0
- package/src/lib/agent-home.ts +334 -0
- package/src/lib/error-formatter.ts +200 -0
- package/src/lib/fs/filesystem.ts +128 -0
- package/src/lib/fs/index.ts +1 -0
- package/src/lib/json-parser.ts +30 -0
- package/src/lib/string.ts +15 -0
- package/src/lib/time.ts +74 -0
- package/src/llm/ChunkHandler.ts +277 -0
- package/src/llm/FinishHandler.ts +250 -0
- package/src/llm/LLMConfigEditor.ts +154 -0
- package/src/llm/LLMServiceFactory.ts +230 -0
- package/src/llm/MessageProcessor.ts +90 -0
- package/src/llm/RecordingState.ts +37 -0
- package/src/llm/StreamPublisher.ts +40 -0
- package/src/llm/TracingUtils.ts +77 -0
- package/src/llm/chunk-validators.ts +57 -0
- package/src/llm/constants.ts +6 -0
- package/src/llm/index.ts +12 -0
- package/src/llm/meta/MetaModelResolver.ts +352 -0
- package/src/llm/meta/index.ts +11 -0
- package/src/llm/middleware/flight-recorder.ts +188 -0
- package/src/llm/providers/MockProvider.ts +332 -0
- package/src/llm/providers/agent/ClaudeCodeProvider.ts +343 -0
- package/src/llm/providers/agent/ClaudeCodeToolsAdapter.ts +203 -0
- package/src/llm/providers/agent/CodexAppServerProvider.ts +214 -0
- package/src/llm/providers/agent/CodexAppServerToolsAdapter.ts +91 -0
- package/src/llm/providers/agent/index.ts +10 -0
- package/src/llm/providers/base/AgentProvider.ts +107 -0
- package/src/llm/providers/base/BaseProvider.ts +114 -0
- package/src/llm/providers/base/StandardProvider.ts +38 -0
- package/src/llm/providers/base/index.ts +9 -0
- package/src/llm/providers/index.ts +106 -0
- package/src/llm/providers/key-manager.ts +238 -0
- package/src/llm/providers/ollama-models.ts +105 -0
- package/src/llm/providers/openrouter-models.ts +102 -0
- package/src/llm/providers/provider-ids.ts +18 -0
- package/src/llm/providers/registry/ProviderRegistry.ts +414 -0
- package/src/llm/providers/registry/index.ts +7 -0
- package/src/llm/providers/standard/AnthropicProvider.ts +71 -0
- package/src/llm/providers/standard/OllamaProvider.ts +59 -0
- package/src/llm/providers/standard/OpenAIProvider.ts +44 -0
- package/src/llm/providers/standard/OpenRouterProvider.ts +103 -0
- package/src/llm/providers/standard/index.ts +10 -0
- package/src/llm/providers/types.ts +194 -0
- package/src/llm/providers/usage-metadata.ts +78 -0
- package/src/llm/service.ts +713 -0
- package/src/llm/types.ts +167 -0
- package/src/llm/utils/ConfigurationManager.ts +650 -0
- package/src/llm/utils/ConfigurationTester.ts +229 -0
- package/src/llm/utils/ModelSelector.ts +212 -0
- package/src/llm/utils/ProviderConfigUI.ts +177 -0
- package/src/llm/utils/claudeCodePromptCompiler.ts +141 -0
- package/src/llm/utils/codex-models.ts +53 -0
- package/src/llm/utils/context-window-cache.ts +30 -0
- package/src/llm/utils/models-dev-cache.ts +267 -0
- package/src/llm/utils/provider-setup.ts +50 -0
- package/src/llm/utils/tool-errors.ts +78 -0
- package/src/llm/utils/usage.ts +74 -0
- package/src/logging/EventRoutingLogger.ts +205 -0
- package/src/nostr/AgentEventDecoder.ts +357 -0
- package/src/nostr/AgentEventEncoder.ts +677 -0
- package/src/nostr/AgentProfilePublisher.ts +657 -0
- package/src/nostr/AgentPublisher.ts +437 -0
- package/src/nostr/BlossomService.ts +226 -0
- package/src/nostr/InterventionPublisher.ts +132 -0
- package/src/nostr/TagExtractor.ts +228 -0
- package/src/nostr/collectEvents.ts +83 -0
- package/src/nostr/constants.ts +38 -0
- package/src/nostr/encryption.ts +26 -0
- package/src/nostr/index.ts +31 -0
- package/src/nostr/keys.ts +17 -0
- package/src/nostr/kinds.ts +37 -0
- package/src/nostr/ndkClient.ts +72 -0
- package/src/nostr/relays.ts +43 -0
- package/src/nostr/trace-context.ts +39 -0
- package/src/nostr/types.ts +227 -0
- package/src/nostr/utils.ts +84 -0
- package/src/prompts/core/FragmentRegistry.ts +30 -0
- package/src/prompts/core/PromptBuilder.ts +98 -0
- package/src/prompts/core/index.ts +3 -0
- package/src/prompts/core/types.ts +13 -0
- package/src/prompts/fragments/00-global-system-prompt.ts +44 -0
- package/src/prompts/fragments/01-agent-identity.ts +69 -0
- package/src/prompts/fragments/02-agent-home-directory.ts +114 -0
- package/src/prompts/fragments/03-system-reminders-explanation.ts +14 -0
- package/src/prompts/fragments/04-relay-configuration.ts +38 -0
- package/src/prompts/fragments/05-delegation-chain.ts +45 -0
- package/src/prompts/fragments/06-agent-todos.ts +74 -0
- package/src/prompts/fragments/06-todo-usage-guidance.ts +34 -0
- package/src/prompts/fragments/07-meta-project-context.ts +234 -0
- package/src/prompts/fragments/08-active-conversations.ts +382 -0
- package/src/prompts/fragments/09-recent-conversations.ts +153 -0
- package/src/prompts/fragments/10-referenced-article.ts +21 -0
- package/src/prompts/fragments/11-nudges.ts +134 -0
- package/src/prompts/fragments/12-skills.ts +127 -0
- package/src/prompts/fragments/13-available-nudges.ts +122 -0
- package/src/prompts/fragments/15-available-agents.ts +53 -0
- package/src/prompts/fragments/16-stay-in-your-lane.ts +41 -0
- package/src/prompts/fragments/17-todo-before-delegation.ts +39 -0
- package/src/prompts/fragments/20-voice-mode.ts +62 -0
- package/src/prompts/fragments/22-scheduled-tasks.ts +175 -0
- package/src/prompts/fragments/24-retrieved-lessons.ts +26 -0
- package/src/prompts/fragments/25-rag-instructions.ts +333 -0
- package/src/prompts/fragments/26-mcp-resources.ts +237 -0
- package/src/prompts/fragments/27-memorized-reports.ts +77 -0
- package/src/prompts/fragments/28-agent-directed-monitoring.ts +32 -0
- package/src/prompts/fragments/29-rag-collections.ts +50 -0
- package/src/prompts/fragments/30-worktree-context.ts +98 -0
- package/src/prompts/fragments/31-agents-md-guidance.ts +96 -0
- package/src/prompts/fragments/32-process-metrics.ts +72 -0
- package/src/prompts/fragments/debug-mode.ts +48 -0
- package/src/prompts/fragments/delegation-completion.ts +44 -0
- package/src/prompts/fragments/index.ts +91 -0
- package/src/prompts/index.ts +21 -0
- package/src/prompts/utils/systemPromptBuilder.ts +777 -0
- package/src/scripts/migrate-prefix-index.ts +157 -0
- package/src/services/AgentDefinitionMonitor.ts +701 -0
- package/src/services/ConfigService.ts +723 -0
- package/src/services/CooldownRegistry.ts +199 -0
- package/src/services/LLMOperationsRegistry.ts +424 -0
- package/src/services/OwnerAgentListService.ts +354 -0
- package/src/services/PubkeyService.ts +308 -0
- package/src/services/agents/AgentMetadataStore.ts +72 -0
- package/src/services/agents/AgentResolution.ts +59 -0
- package/src/services/agents/EscalationService.ts +281 -0
- package/src/services/agents/NDKAgentDiscovery.ts +95 -0
- package/src/services/agents/index.ts +7 -0
- package/src/services/agents-md/AgentsMdService.ts +184 -0
- package/src/services/agents-md/SystemReminderInjector.ts +238 -0
- package/src/services/agents-md/index.ts +11 -0
- package/src/services/apns/APNsClient.ts +203 -0
- package/src/services/apns/APNsService.ts +358 -0
- package/src/services/apns/index.ts +11 -0
- package/src/services/apns/types.ts +80 -0
- package/src/services/compression/CompressionService.ts +445 -0
- package/src/services/compression/compression-schema.ts +28 -0
- package/src/services/compression/compression-types.ts +74 -0
- package/src/services/compression/compression-utils.ts +587 -0
- package/src/services/config/types.ts +394 -0
- package/src/services/dispatch/AgentDispatchService.ts +937 -0
- package/src/services/dispatch/AgentRouter.ts +181 -0
- package/src/services/dispatch/DelegationCompletionHandler.ts +232 -0
- package/src/services/embedding/EmbeddingProvider.ts +188 -0
- package/src/services/embedding/index.ts +5 -0
- package/src/services/event-context/EventContextService.ts +108 -0
- package/src/services/event-context/index.ts +2 -0
- package/src/services/heuristics/ContextBuilder.ts +106 -0
- package/src/services/heuristics/HeuristicEngine.ts +200 -0
- package/src/services/heuristics/formatters.ts +58 -0
- package/src/services/heuristics/index.ts +12 -0
- package/src/services/heuristics/rules/index.ts +25 -0
- package/src/services/heuristics/rules/todoBeforeDelegation.ts +69 -0
- package/src/services/heuristics/rules/todoReminderOnToolUse.ts +63 -0
- package/src/services/heuristics/types.ts +144 -0
- package/src/services/image/ImageGenerationService.ts +389 -0
- package/src/services/image/index.ts +12 -0
- package/src/services/intervention/InterventionService.ts +1352 -0
- package/src/services/intervention/index.ts +7 -0
- package/src/services/mcp/MCPManager.ts +683 -0
- package/src/services/mcp/McpNotificationDelivery.ts +139 -0
- package/src/services/mcp/McpSubscriptionService.ts +653 -0
- package/src/services/mcp/mcpInstaller.ts +130 -0
- package/src/services/nip46/Nip46SigningLog.ts +81 -0
- package/src/services/nip46/Nip46SigningService.ts +467 -0
- package/src/services/nip46/index.ts +4 -0
- package/src/services/nudge/NudgeService.ts +224 -0
- package/src/services/nudge/NudgeWhitelistService.ts +382 -0
- package/src/services/nudge/index.ts +5 -0
- package/src/services/nudge/types.ts +83 -0
- package/src/services/projects/ProjectContext.ts +672 -0
- package/src/services/projects/ProjectContextStore.ts +102 -0
- package/src/services/projects/index.ts +6 -0
- package/src/services/prompt-compiler/index.ts +15 -0
- package/src/services/prompt-compiler/prompt-compiler-service.ts +1143 -0
- package/src/services/pubkey-gate/PubkeyGateService.ts +93 -0
- package/src/services/pubkey-gate/index.ts +1 -0
- package/src/services/rag/EmbeddingProviderFactory.ts +292 -0
- package/src/services/rag/LanceDBMaintenanceService.ts +211 -0
- package/src/services/rag/RAGDatabaseService.ts +173 -0
- package/src/services/rag/RAGOperations.ts +682 -0
- package/src/services/rag/RAGService.ts +240 -0
- package/src/services/rag/RagSubscriptionService.ts +618 -0
- package/src/services/rag/rag-utils.ts +174 -0
- package/src/services/ral/PendingDelegationsRegistry.ts +168 -0
- package/src/services/ral/RALRegistry.ts +2782 -0
- package/src/services/ral/index.ts +4 -0
- package/src/services/ral/types.ts +292 -0
- package/src/services/reports/LocalReportStore.ts +380 -0
- package/src/services/reports/ReportEmbeddingService.ts +430 -0
- package/src/services/reports/ReportService.ts +440 -0
- package/src/services/reports/articleUtils.ts +52 -0
- package/src/services/reports/index.ts +7 -0
- package/src/services/scheduling/SchedulerService.ts +1057 -0
- package/src/services/scheduling/errors.ts +14 -0
- package/src/services/scheduling/index.ts +7 -0
- package/src/services/scheduling/utils.ts +77 -0
- package/src/services/search/SearchProviderRegistry.ts +78 -0
- package/src/services/search/UnifiedSearchService.ts +218 -0
- package/src/services/search/index.ts +47 -0
- package/src/services/search/projectFilter.ts +22 -0
- package/src/services/search/providers/ConversationSearchProvider.ts +48 -0
- package/src/services/search/providers/LessonSearchProvider.ts +75 -0
- package/src/services/search/providers/ReportSearchProvider.ts +49 -0
- package/src/services/search/types.ts +144 -0
- package/src/services/skill/SkillService.ts +482 -0
- package/src/services/skill/index.ts +2 -0
- package/src/services/skill/types.ts +70 -0
- package/src/services/status/OperationsStatusService.ts +276 -0
- package/src/services/status/ProjectStatusService.ts +522 -0
- package/src/services/status/index.ts +11 -0
- package/src/services/storage/PrefixKVStore.ts +242 -0
- package/src/services/storage/index.ts +1 -0
- package/src/services/system-reminder/SystemReminderUtils.ts +96 -0
- package/src/services/system-reminder/index.ts +7 -0
- package/src/services/trust-pubkeys/TrustPubkeyService.ts +325 -0
- package/src/services/trust-pubkeys/index.ts +2 -0
- package/src/telemetry/ConversationSpanManager.ts +111 -0
- package/src/telemetry/EventLoopMonitor.ts +206 -0
- package/src/telemetry/LLMSpanRegistry.ts +20 -0
- package/src/telemetry/NostrSpanProcessor.ts +89 -0
- package/src/telemetry/ToolCallSpanProcessor.ts +66 -0
- package/src/telemetry/diagnostics.ts +27 -0
- package/src/telemetry/setup.ts +120 -0
- package/src/tools/implementations/agents_discover.ts +121 -0
- package/src/tools/implementations/agents_hire.ts +127 -0
- package/src/tools/implementations/agents_list.ts +96 -0
- package/src/tools/implementations/agents_publish.ts +611 -0
- package/src/tools/implementations/agents_read.ts +173 -0
- package/src/tools/implementations/agents_write.ts +200 -0
- package/src/tools/implementations/ask.ts +411 -0
- package/src/tools/implementations/change_model.ts +141 -0
- package/src/tools/implementations/conversation_get.ts +661 -0
- package/src/tools/implementations/conversation_list.ts +377 -0
- package/src/tools/implementations/conversation_search.ts +370 -0
- package/src/tools/implementations/delegate.ts +327 -0
- package/src/tools/implementations/delegate_crossproject.ts +209 -0
- package/src/tools/implementations/delegate_followup.ts +300 -0
- package/src/tools/implementations/fs_edit.ts +162 -0
- package/src/tools/implementations/fs_glob.ts +182 -0
- package/src/tools/implementations/fs_grep.ts +513 -0
- package/src/tools/implementations/fs_read.ts +332 -0
- package/src/tools/implementations/fs_write.ts +113 -0
- package/src/tools/implementations/generate_image.ts +259 -0
- package/src/tools/implementations/home_fs.ts +515 -0
- package/src/tools/implementations/kill.ts +651 -0
- package/src/tools/implementations/learn.ts +166 -0
- package/src/tools/implementations/lesson-formatter.ts +38 -0
- package/src/tools/implementations/lesson_delete.ts +164 -0
- package/src/tools/implementations/lesson_get.ts +105 -0
- package/src/tools/implementations/lessons_list.ts +153 -0
- package/src/tools/implementations/mcp_resource_read.ts +161 -0
- package/src/tools/implementations/mcp_subscribe.ts +158 -0
- package/src/tools/implementations/mcp_subscription_stop.ts +85 -0
- package/src/tools/implementations/nostr_fetch.ts +149 -0
- package/src/tools/implementations/nostr_publish_as_user.ts +353 -0
- package/src/tools/implementations/project_list.ts +146 -0
- package/src/tools/implementations/rag_add_documents.ts +573 -0
- package/src/tools/implementations/rag_create_collection.ts +65 -0
- package/src/tools/implementations/rag_delete_collection.ts +68 -0
- package/src/tools/implementations/rag_list_collections.ts +77 -0
- package/src/tools/implementations/rag_query.ts +107 -0
- package/src/tools/implementations/rag_subscription_create.ts +105 -0
- package/src/tools/implementations/rag_subscription_delete.ts +80 -0
- package/src/tools/implementations/rag_subscription_get.ts +123 -0
- package/src/tools/implementations/rag_subscription_list.ts +128 -0
- package/src/tools/implementations/report_delete.ts +79 -0
- package/src/tools/implementations/report_read.ts +160 -0
- package/src/tools/implementations/report_write.ts +278 -0
- package/src/tools/implementations/reports_list.ts +77 -0
- package/src/tools/implementations/schedule_task.ts +104 -0
- package/src/tools/implementations/schedule_task_cancel.ts +62 -0
- package/src/tools/implementations/schedule_task_once.ts +128 -0
- package/src/tools/implementations/schedule_tasks_list.ts +79 -0
- package/src/tools/implementations/search.ts +160 -0
- package/src/tools/implementations/shell.ts +553 -0
- package/src/tools/implementations/todo.ts +260 -0
- package/src/tools/implementations/upload_blob.ts +381 -0
- package/src/tools/implementations/web_fetch.ts +153 -0
- package/src/tools/implementations/web_search.ts +250 -0
- package/src/tools/registry.ts +670 -0
- package/src/tools/types.ts +177 -0
- package/src/tools/utils.ts +256 -0
- package/src/types/event-ids.ts +320 -0
- package/src/types/index.ts +46 -0
- package/src/utils/agentFetcher.ts +107 -0
- package/src/utils/cli-error.ts +29 -0
- package/src/utils/conversation-id.ts +27 -0
- package/src/utils/conversation-utils.ts +1 -0
- package/src/utils/delegation-chain.ts +357 -0
- package/src/utils/error-handler.ts +42 -0
- package/src/utils/git/gitignore.ts +69 -0
- package/src/utils/git/index.ts +2 -0
- package/src/utils/git/initializeGitRepo.ts +204 -0
- package/src/utils/git/worktree.ts +260 -0
- package/src/utils/lessonFormatter.ts +70 -0
- package/src/utils/lessonTrust.ts +24 -0
- package/src/utils/lockfile.ts +123 -0
- package/src/utils/logger.ts +149 -0
- package/src/utils/nostr-entity-parser.ts +365 -0
- package/src/utils/process.ts +49 -0
- package/src/wrapper.ts +262 -0
- package/tsconfig.json +41 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { ModelMessage } from "ai";
|
|
2
|
+
import { PROVIDER_IDS } from "./providers/provider-ids";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Add provider-specific cache control to messages.
|
|
6
|
+
* Only Anthropic requires explicit cache control; OpenAI and Gemini cache automatically.
|
|
7
|
+
*/
|
|
8
|
+
export function addCacheControl(messages: ModelMessage[], provider: string): ModelMessage[] {
|
|
9
|
+
// Only add cache control for Anthropic
|
|
10
|
+
if (provider !== PROVIDER_IDS.ANTHROPIC) {
|
|
11
|
+
return messages;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// Rough estimate: 4 characters per token (configurable if needed)
|
|
15
|
+
const CHARS_PER_TOKEN_ESTIMATE = 4;
|
|
16
|
+
const MIN_TOKENS_FOR_CACHE = 1024;
|
|
17
|
+
const minCharsForCache = MIN_TOKENS_FOR_CACHE * CHARS_PER_TOKEN_ESTIMATE;
|
|
18
|
+
|
|
19
|
+
return messages.map((msg) => {
|
|
20
|
+
// Only cache system messages and only if they're large enough
|
|
21
|
+
if (msg.role === "system" && typeof msg.content === "string" && msg.content.length > minCharsForCache) {
|
|
22
|
+
return {
|
|
23
|
+
...msg,
|
|
24
|
+
providerOptions: {
|
|
25
|
+
anthropic: {
|
|
26
|
+
cacheControl: { type: "ephemeral" },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return msg;
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Prepare messages for sending to the LLM.
|
|
37
|
+
* Handles provider-specific transformations.
|
|
38
|
+
*/
|
|
39
|
+
export function prepareMessagesForRequest(messages: ModelMessage[], provider: string): ModelMessage[] {
|
|
40
|
+
let processedMessages = messages;
|
|
41
|
+
|
|
42
|
+
// For Claude Code, filter out system messages since they're passed via systemPrompt
|
|
43
|
+
if (provider === PROVIDER_IDS.CLAUDE_CODE) {
|
|
44
|
+
processedMessages = messages.filter((m) => m.role !== "system");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return addCacheControl(processedMessages, provider);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Extract the last user message text from a message array.
|
|
52
|
+
* Handles both simple string content and complex content arrays.
|
|
53
|
+
*/
|
|
54
|
+
export function extractLastUserMessage(messages: ModelMessage[]): string | undefined {
|
|
55
|
+
// Find the last message with role "user" (iterate from end)
|
|
56
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
57
|
+
const msg = messages[i];
|
|
58
|
+
if (msg.role === "user") {
|
|
59
|
+
// User messages can have string content or content array
|
|
60
|
+
if (typeof msg.content === "string") {
|
|
61
|
+
return msg.content;
|
|
62
|
+
}
|
|
63
|
+
if (Array.isArray(msg.content)) {
|
|
64
|
+
// Extract text from content parts
|
|
65
|
+
const textParts = msg.content
|
|
66
|
+
.filter((part): part is { type: "text"; text: string } =>
|
|
67
|
+
part.type === "text" && typeof part.text === "string"
|
|
68
|
+
)
|
|
69
|
+
.map((part) => part.text);
|
|
70
|
+
if (textParts.length > 0) {
|
|
71
|
+
return textParts.join("\n");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Found user message but couldn't extract text
|
|
75
|
+
return "[User message with non-text content]";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract system messages from a message array and combine them.
|
|
83
|
+
* Used for providers like Claude Code that take system prompt separately.
|
|
84
|
+
*/
|
|
85
|
+
export function extractSystemContent(messages: ModelMessage[]): string {
|
|
86
|
+
return messages
|
|
87
|
+
.filter((m) => m.role === "system")
|
|
88
|
+
.map((m) => (typeof m.content === "string" ? m.content : JSON.stringify(m.content)))
|
|
89
|
+
.join("\n\n");
|
|
90
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { EventEmitter } from "tseep";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global recording state singleton.
|
|
5
|
+
* Controls whether the flight recorder middleware saves LLM interactions.
|
|
6
|
+
*/
|
|
7
|
+
class RecordingStateManager extends EventEmitter<{
|
|
8
|
+
"state-changed": (isRecording: boolean) => void;
|
|
9
|
+
}> {
|
|
10
|
+
private _isRecording = false;
|
|
11
|
+
|
|
12
|
+
get isRecording(): boolean {
|
|
13
|
+
return this._isRecording;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
toggle(): boolean {
|
|
17
|
+
this._isRecording = !this._isRecording;
|
|
18
|
+
this.emit("state-changed", this._isRecording);
|
|
19
|
+
return this._isRecording;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
start(): void {
|
|
23
|
+
if (!this._isRecording) {
|
|
24
|
+
this._isRecording = true;
|
|
25
|
+
this.emit("state-changed", true);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
stop(): void {
|
|
30
|
+
if (this._isRecording) {
|
|
31
|
+
this._isRecording = false;
|
|
32
|
+
this.emit("state-changed", false);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const recordingState = new RecordingStateManager();
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { logger } from "@/utils/logger";
|
|
2
|
+
import type { LocalStreamChunk } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interface for stream transports (Unix socket, future Nostr ephemeral)
|
|
6
|
+
*/
|
|
7
|
+
export interface StreamTransport {
|
|
8
|
+
write(chunk: LocalStreamChunk): void;
|
|
9
|
+
isConnected(): boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Publishes AI SDK chunks to connected transports
|
|
14
|
+
* Fire-and-forget: silently drops if no transport connected
|
|
15
|
+
*/
|
|
16
|
+
export class StreamPublisher {
|
|
17
|
+
private transport: StreamTransport | null = null;
|
|
18
|
+
|
|
19
|
+
setTransport(transport: StreamTransport | null): void {
|
|
20
|
+
this.transport = transport;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
write(chunk: LocalStreamChunk): void {
|
|
24
|
+
logger.debug("[StreamPublisher] write called", {
|
|
25
|
+
hasTransport: !!this.transport,
|
|
26
|
+
isConnected: this.transport?.isConnected() ?? false,
|
|
27
|
+
chunkType: (chunk.data as { type?: string })?.type,
|
|
28
|
+
});
|
|
29
|
+
if (this.transport?.isConnected()) {
|
|
30
|
+
this.transport.write(chunk);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
isConnected(): boolean {
|
|
35
|
+
return this.transport?.isConnected() ?? false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Singleton instance */
|
|
40
|
+
export const streamPublisher = new StreamPublisher();
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { trace } from "@opentelemetry/api";
|
|
2
|
+
import type { TelemetrySettings } from "ai";
|
|
3
|
+
import { shortenConversationId } from "@/utils/conversation-id";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get trace correlation ID for OpenRouter.
|
|
7
|
+
* Returns a string combining trace and span IDs for unique request identification.
|
|
8
|
+
*/
|
|
9
|
+
export function getTraceCorrelationId(): string | undefined {
|
|
10
|
+
const span = trace.getActiveSpan();
|
|
11
|
+
if (!span) return undefined;
|
|
12
|
+
const ctx = span.spanContext();
|
|
13
|
+
return `tenex-${ctx.traceId}-${ctx.spanId}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @deprecated Use shortenConversationId from @/utils/conversation-id instead.
|
|
18
|
+
* This function is kept for backward compatibility but will be removed in a future version.
|
|
19
|
+
*/
|
|
20
|
+
export function shortenConversationIdForSpan(conversationId: string): string {
|
|
21
|
+
return shortenConversationId(conversationId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get OpenRouter metadata for request correlation.
|
|
26
|
+
* Includes OTL trace context plus agent and conversation identifiers.
|
|
27
|
+
*/
|
|
28
|
+
export function getOpenRouterMetadata(
|
|
29
|
+
agentSlug?: string,
|
|
30
|
+
conversationId?: string
|
|
31
|
+
): Record<string, string> {
|
|
32
|
+
const metadata: Record<string, string> = {};
|
|
33
|
+
|
|
34
|
+
const span = trace.getActiveSpan();
|
|
35
|
+
if (span) {
|
|
36
|
+
const ctx = span.spanContext();
|
|
37
|
+
metadata.tenex_trace_id = ctx.traceId;
|
|
38
|
+
metadata.tenex_span_id = ctx.spanId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (agentSlug) metadata.tenex_agent = agentSlug;
|
|
42
|
+
if (conversationId) metadata.tenex_conversation = conversationId;
|
|
43
|
+
|
|
44
|
+
return metadata;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Get full telemetry configuration for AI SDK.
|
|
49
|
+
* Captures EVERYTHING for debugging - no privacy filters.
|
|
50
|
+
*/
|
|
51
|
+
export function getFullTelemetryConfig(config: {
|
|
52
|
+
agentSlug?: string;
|
|
53
|
+
provider: string;
|
|
54
|
+
model: string;
|
|
55
|
+
temperature?: number;
|
|
56
|
+
maxTokens?: number;
|
|
57
|
+
sessionId?: string;
|
|
58
|
+
}): TelemetrySettings {
|
|
59
|
+
return {
|
|
60
|
+
isEnabled: true,
|
|
61
|
+
functionId: `${config.agentSlug || "unknown"}.${config.provider}.${config.model}`,
|
|
62
|
+
|
|
63
|
+
// Metadata for debugging context
|
|
64
|
+
metadata: {
|
|
65
|
+
"agent.slug": config.agentSlug || "unknown",
|
|
66
|
+
"llm.provider": config.provider,
|
|
67
|
+
"llm.model": config.model,
|
|
68
|
+
"llm.temperature": config.temperature ?? 0,
|
|
69
|
+
"llm.max_tokens": config.maxTokens ?? 0,
|
|
70
|
+
"session.id": config.sessionId ?? "unknown",
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
// FULL DATA - no privacy filters for debugging
|
|
74
|
+
recordInputs: true, // Capture full prompts
|
|
75
|
+
recordOutputs: true, // Capture full responses
|
|
76
|
+
};
|
|
77
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { trace } from "@opentelemetry/api";
|
|
2
|
+
import type { TextStreamPart } from "ai";
|
|
3
|
+
import type { AISdkTool } from "@/tools/types";
|
|
4
|
+
|
|
5
|
+
type StreamChunk = TextStreamPart<Record<string, AISdkTool>>;
|
|
6
|
+
|
|
7
|
+
interface ChunkValidator {
|
|
8
|
+
name: string;
|
|
9
|
+
shouldIgnore: (chunk: StreamChunk) => boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates reasoning-delta chunks with [REDACTED] content.
|
|
14
|
+
* Some LLM providers (e.g., OpenRouter with Gemini) send encrypted reasoning
|
|
15
|
+
* data that appears as "[REDACTED]" - these should be ignored.
|
|
16
|
+
*/
|
|
17
|
+
const redactedReasoningValidator: ChunkValidator = {
|
|
18
|
+
name: "redacted-reasoning",
|
|
19
|
+
shouldIgnore: (chunk: StreamChunk): boolean => {
|
|
20
|
+
if (chunk.type !== "reasoning-delta") {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The chunk may have 'text' or 'delta' property depending on how AI SDK processes it
|
|
25
|
+
const content = (chunk as { text?: string; delta?: string }).text
|
|
26
|
+
?? (chunk as { text?: string; delta?: string }).delta;
|
|
27
|
+
|
|
28
|
+
return content === "[REDACTED]";
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* All chunk validators to apply before processing
|
|
34
|
+
*/
|
|
35
|
+
const validators: ChunkValidator[] = [
|
|
36
|
+
redactedReasoningValidator,
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check if a chunk should be ignored based on all validators.
|
|
41
|
+
* Adds an OTL span event when a chunk is ignored for debugging.
|
|
42
|
+
*
|
|
43
|
+
* @returns true if the chunk should be ignored, false otherwise
|
|
44
|
+
*/
|
|
45
|
+
export function shouldIgnoreChunk(chunk: StreamChunk): boolean {
|
|
46
|
+
for (const validator of validators) {
|
|
47
|
+
if (validator.shouldIgnore(chunk)) {
|
|
48
|
+
const activeSpan = trace.getActiveSpan();
|
|
49
|
+
activeSpan?.addEvent("chunk_ignored", {
|
|
50
|
+
"chunk.type": chunk.type,
|
|
51
|
+
"validator.name": validator.name,
|
|
52
|
+
});
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
}
|
package/src/llm/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Export service
|
|
2
|
+
export { LLMService } from "./service";
|
|
3
|
+
|
|
4
|
+
// Export factory
|
|
5
|
+
export { LLMServiceFactory, llmServiceFactory } from "./LLMServiceFactory";
|
|
6
|
+
|
|
7
|
+
// Export stream publisher
|
|
8
|
+
export { StreamPublisher, streamPublisher, type StreamTransport } from "./StreamPublisher";
|
|
9
|
+
export type { LocalStreamChunk } from "./types";
|
|
10
|
+
|
|
11
|
+
// Export types
|
|
12
|
+
export * from "./types";
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MetaModelResolver - Handles dynamic model selection based on keywords
|
|
3
|
+
*
|
|
4
|
+
* Meta models are virtual model configurations that select from underlying
|
|
5
|
+
* real models based on keywords in the user's message. This enables
|
|
6
|
+
* "think harder" type functionality without explicit tool calls.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* User: "ultrathink how do we implement authentication?"
|
|
10
|
+
* → Resolves to high-cost reasoning model, strips "ultrathink" from message
|
|
11
|
+
*
|
|
12
|
+
* User: "quick what's 2+2?"
|
|
13
|
+
* → Resolves to fast model, strips "quick" from message
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { MetaModelConfiguration, MetaModelVariant } from "@/services/config/types";
|
|
17
|
+
import { logger } from "@/utils/logger";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Result of resolving a meta model variant
|
|
21
|
+
*/
|
|
22
|
+
export interface MetaModelResolution {
|
|
23
|
+
/** The resolved variant name */
|
|
24
|
+
variantName: string;
|
|
25
|
+
/** The variant configuration */
|
|
26
|
+
variant: MetaModelVariant;
|
|
27
|
+
/** The underlying LLM configuration name to use */
|
|
28
|
+
configName: string;
|
|
29
|
+
/** The message with keywords stripped (if stripKeywords was true) */
|
|
30
|
+
strippedMessage?: string;
|
|
31
|
+
/** Keywords that were matched */
|
|
32
|
+
matchedKeywords: string[];
|
|
33
|
+
/** Optional additional system prompt from the variant */
|
|
34
|
+
systemPrompt?: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Options for resolving a meta model
|
|
39
|
+
*/
|
|
40
|
+
export interface ResolveOptions {
|
|
41
|
+
/** Whether to strip matched keywords from the message (default: true) */
|
|
42
|
+
stripKeywords?: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* MetaModelResolver handles keyword detection and model resolution
|
|
47
|
+
* for meta model configurations.
|
|
48
|
+
*/
|
|
49
|
+
export class MetaModelResolver {
|
|
50
|
+
/**
|
|
51
|
+
* Build a keyword-to-variant lookup map from a meta model configuration.
|
|
52
|
+
* Keywords are normalized to lowercase for case-insensitive matching.
|
|
53
|
+
*/
|
|
54
|
+
private static buildKeywordMap(
|
|
55
|
+
config: MetaModelConfiguration
|
|
56
|
+
): Map<string, { variantName: string; variant: MetaModelVariant }> {
|
|
57
|
+
const keywordMap = new Map<string, { variantName: string; variant: MetaModelVariant }>();
|
|
58
|
+
|
|
59
|
+
for (const [variantName, variant] of Object.entries(config.variants)) {
|
|
60
|
+
if (variant.keywords) {
|
|
61
|
+
for (const keyword of variant.keywords) {
|
|
62
|
+
const normalizedKeyword = keyword.toLowerCase();
|
|
63
|
+
keywordMap.set(normalizedKeyword, { variantName, variant });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return keywordMap;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Find all matching keywords at the start of a message.
|
|
73
|
+
* Returns keywords in the order they appear, for proper stripping.
|
|
74
|
+
*/
|
|
75
|
+
private static findMatchingKeywords(
|
|
76
|
+
message: string,
|
|
77
|
+
keywordMap: Map<string, { variantName: string; variant: MetaModelVariant }>
|
|
78
|
+
): Array<{ keyword: string; variantName: string; variant: MetaModelVariant; position: number }> {
|
|
79
|
+
const matches: Array<{
|
|
80
|
+
keyword: string;
|
|
81
|
+
variantName: string;
|
|
82
|
+
variant: MetaModelVariant;
|
|
83
|
+
position: number;
|
|
84
|
+
}> = [];
|
|
85
|
+
|
|
86
|
+
// Get all keywords, sorted by length (longest first for proper matching)
|
|
87
|
+
const keywords = Array.from(keywordMap.keys()).sort((a, b) => b.length - a.length);
|
|
88
|
+
|
|
89
|
+
// Check the beginning of the message for keywords
|
|
90
|
+
// Keywords must be at the start or preceded by whitespace
|
|
91
|
+
const messageLower = message.toLowerCase();
|
|
92
|
+
|
|
93
|
+
for (const keyword of keywords) {
|
|
94
|
+
// Check if keyword is at the start
|
|
95
|
+
if (messageLower.startsWith(keyword)) {
|
|
96
|
+
const afterKeyword = message.charAt(keyword.length);
|
|
97
|
+
// Keyword must be followed by whitespace or end of string
|
|
98
|
+
if (!afterKeyword || /\s/.test(afterKeyword)) {
|
|
99
|
+
const data = keywordMap.get(keyword);
|
|
100
|
+
if (data) {
|
|
101
|
+
matches.push({
|
|
102
|
+
keyword,
|
|
103
|
+
variantName: data.variantName,
|
|
104
|
+
variant: data.variant,
|
|
105
|
+
position: 0,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Also check for keywords after initial whitespace (e.g., " ultrathink ...")
|
|
112
|
+
const leadingWhitespaceMatch = messageLower.match(/^\s+/);
|
|
113
|
+
if (leadingWhitespaceMatch) {
|
|
114
|
+
const offset = leadingWhitespaceMatch[0].length;
|
|
115
|
+
const restLower = messageLower.substring(offset);
|
|
116
|
+
if (restLower.startsWith(keyword)) {
|
|
117
|
+
const afterKeyword = message.charAt(offset + keyword.length);
|
|
118
|
+
if (!afterKeyword || /\s/.test(afterKeyword)) {
|
|
119
|
+
const data = keywordMap.get(keyword);
|
|
120
|
+
if (data && !matches.some((m) => m.keyword === keyword)) {
|
|
121
|
+
matches.push({
|
|
122
|
+
keyword,
|
|
123
|
+
variantName: data.variantName,
|
|
124
|
+
variant: data.variant,
|
|
125
|
+
position: offset,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return matches;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Select the winning variant from a set of matches using tier-based resolution.
|
|
138
|
+
* Highest tier wins. If tiers are equal, first match wins.
|
|
139
|
+
*/
|
|
140
|
+
private static selectWinningVariant(
|
|
141
|
+
matches: Array<{ keyword: string; variantName: string; variant: MetaModelVariant; position: number }>
|
|
142
|
+
): { keyword: string; variantName: string; variant: MetaModelVariant; position: number } | null {
|
|
143
|
+
if (matches.length === 0) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Sort by tier (descending), then by position (ascending for first match)
|
|
148
|
+
const sorted = [...matches].sort((a, b) => {
|
|
149
|
+
const tierA = a.variant.tier ?? 0;
|
|
150
|
+
const tierB = b.variant.tier ?? 0;
|
|
151
|
+
if (tierB !== tierA) {
|
|
152
|
+
return tierB - tierA; // Higher tier wins
|
|
153
|
+
}
|
|
154
|
+
return a.position - b.position; // Earlier position wins if tiers equal
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
return sorted[0];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Strip matched keywords from the beginning of a message.
|
|
162
|
+
* Preserves the rest of the message content.
|
|
163
|
+
*/
|
|
164
|
+
private static stripKeywordsFromMessage(
|
|
165
|
+
message: string,
|
|
166
|
+
matchedKeywords: string[]
|
|
167
|
+
): string {
|
|
168
|
+
let result = message;
|
|
169
|
+
|
|
170
|
+
for (const keyword of matchedKeywords) {
|
|
171
|
+
// Match the keyword at the start (case-insensitive) followed by optional whitespace
|
|
172
|
+
const regex = new RegExp(`^\\s*${escapeRegExp(keyword)}\\s*`, "i");
|
|
173
|
+
result = result.replace(regex, "");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return result.trim();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Resolve which variant to use based on the message content.
|
|
181
|
+
*
|
|
182
|
+
* @param config The meta model configuration
|
|
183
|
+
* @param message The user's message to analyze for keywords
|
|
184
|
+
* @param options Resolution options
|
|
185
|
+
* @returns The resolved variant information, or null if using default
|
|
186
|
+
*/
|
|
187
|
+
static resolve(
|
|
188
|
+
config: MetaModelConfiguration,
|
|
189
|
+
message?: string,
|
|
190
|
+
options: ResolveOptions = {}
|
|
191
|
+
): MetaModelResolution {
|
|
192
|
+
const { stripKeywords = true } = options;
|
|
193
|
+
|
|
194
|
+
// Build keyword map
|
|
195
|
+
const keywordMap = this.buildKeywordMap(config);
|
|
196
|
+
|
|
197
|
+
// If no message provided, use default
|
|
198
|
+
if (!message) {
|
|
199
|
+
const defaultVariant = config.variants[config.default];
|
|
200
|
+
if (!defaultVariant) {
|
|
201
|
+
throw new Error(
|
|
202
|
+
`Meta model default variant "${config.default}" not found in variants`
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
variantName: config.default,
|
|
207
|
+
variant: defaultVariant,
|
|
208
|
+
configName: defaultVariant.model,
|
|
209
|
+
matchedKeywords: [],
|
|
210
|
+
systemPrompt: defaultVariant.systemPrompt,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Find matching keywords
|
|
215
|
+
const matches = this.findMatchingKeywords(message, keywordMap);
|
|
216
|
+
|
|
217
|
+
// If no keywords matched, use default
|
|
218
|
+
if (matches.length === 0) {
|
|
219
|
+
const defaultVariant = config.variants[config.default];
|
|
220
|
+
if (!defaultVariant) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`Meta model default variant "${config.default}" not found in variants`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
variantName: config.default,
|
|
227
|
+
variant: defaultVariant,
|
|
228
|
+
configName: defaultVariant.model,
|
|
229
|
+
matchedKeywords: [],
|
|
230
|
+
systemPrompt: defaultVariant.systemPrompt,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Select winning variant based on tier
|
|
235
|
+
const winner = this.selectWinningVariant(matches);
|
|
236
|
+
if (!winner) {
|
|
237
|
+
// Shouldn't happen if matches.length > 0, but handle gracefully
|
|
238
|
+
const defaultVariant = config.variants[config.default];
|
|
239
|
+
return {
|
|
240
|
+
variantName: config.default,
|
|
241
|
+
variant: defaultVariant,
|
|
242
|
+
configName: defaultVariant.model,
|
|
243
|
+
matchedKeywords: [],
|
|
244
|
+
systemPrompt: defaultVariant.systemPrompt,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Collect all matched keywords (for logging/debugging)
|
|
249
|
+
const matchedKeywords = matches.map((m) => m.keyword);
|
|
250
|
+
|
|
251
|
+
// Strip keywords if requested
|
|
252
|
+
const strippedMessage = stripKeywords
|
|
253
|
+
? this.stripKeywordsFromMessage(message, matchedKeywords)
|
|
254
|
+
: message;
|
|
255
|
+
|
|
256
|
+
logger.debug("[MetaModelResolver] Resolved variant", {
|
|
257
|
+
variantName: winner.variantName,
|
|
258
|
+
matchedKeywords,
|
|
259
|
+
tier: winner.variant.tier ?? 0,
|
|
260
|
+
configName: winner.variant.model,
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
variantName: winner.variantName,
|
|
265
|
+
variant: winner.variant,
|
|
266
|
+
configName: winner.variant.model,
|
|
267
|
+
strippedMessage,
|
|
268
|
+
matchedKeywords,
|
|
269
|
+
systemPrompt: winner.variant.systemPrompt,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Resolve directly to a specific variant by name.
|
|
275
|
+
* Used when there's a variant override set (e.g., via change_model tool).
|
|
276
|
+
*
|
|
277
|
+
* @param config The meta model configuration
|
|
278
|
+
* @param variantName The name of the variant to resolve to
|
|
279
|
+
* @returns The resolved variant information
|
|
280
|
+
* @throws Error if the variant doesn't exist
|
|
281
|
+
*/
|
|
282
|
+
static resolveToVariant(
|
|
283
|
+
config: MetaModelConfiguration,
|
|
284
|
+
variantName: string
|
|
285
|
+
): MetaModelResolution {
|
|
286
|
+
const variant = config.variants[variantName];
|
|
287
|
+
if (!variant) {
|
|
288
|
+
throw new Error(
|
|
289
|
+
`Meta model variant "${variantName}" not found. Available variants: ${Object.keys(config.variants).join(", ")}`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
logger.debug("[MetaModelResolver] Resolved to override variant", {
|
|
294
|
+
variantName,
|
|
295
|
+
configName: variant.model,
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
return {
|
|
299
|
+
variantName,
|
|
300
|
+
variant,
|
|
301
|
+
configName: variant.model,
|
|
302
|
+
matchedKeywords: [],
|
|
303
|
+
systemPrompt: variant.systemPrompt,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Generate a system prompt fragment describing available variants.
|
|
309
|
+
* This helps the model understand what models are available and when to use them.
|
|
310
|
+
*
|
|
311
|
+
* @param config The meta model configuration
|
|
312
|
+
* @returns A system prompt fragment describing the variants
|
|
313
|
+
*/
|
|
314
|
+
static generateSystemPromptFragment(config: MetaModelConfiguration): string {
|
|
315
|
+
const lines: string[] = [];
|
|
316
|
+
|
|
317
|
+
if (config.description) {
|
|
318
|
+
lines.push(config.description);
|
|
319
|
+
lines.push("");
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
lines.push("You have access to the following models via change_model() tool:");
|
|
323
|
+
|
|
324
|
+
for (const [variantName, variant] of Object.entries(config.variants)) {
|
|
325
|
+
const description = variant.description || `Model variant "${variantName}"`;
|
|
326
|
+
const keywords = variant.keywords?.length
|
|
327
|
+
? ` (trigger: ${variant.keywords.join(", ")})`
|
|
328
|
+
: "";
|
|
329
|
+
lines.push(`* ${variantName}${keywords} → ${description}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
return lines.join("\n");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Check if a configuration is a meta model configuration.
|
|
337
|
+
*/
|
|
338
|
+
static isMetaModel(config: unknown): config is MetaModelConfiguration {
|
|
339
|
+
if (!config || typeof config !== "object") {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
const c = config as Record<string, unknown>;
|
|
343
|
+
return c.provider === "meta" && typeof c.variants === "object" && typeof c.default === "string";
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Escape special regex characters in a string
|
|
349
|
+
*/
|
|
350
|
+
function escapeRegExp(string: string): string {
|
|
351
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
352
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta Model Module
|
|
3
|
+
*
|
|
4
|
+
* Provides dynamic model selection capabilities through "meta models".
|
|
5
|
+
* Meta models are virtual configurations that resolve to real models
|
|
6
|
+
* based on keywords in user messages.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { MetaModelResolver, type MetaModelResolution, type ResolveOptions } from "./MetaModelResolver";
|