@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,281 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Placeholder Strategy for Token Cost Reduction
|
|
3
|
+
*
|
|
4
|
+
* Images in tool results accumulate massive token costs (~1,600 tokens per image).
|
|
5
|
+
* This module implements a placeholder strategy:
|
|
6
|
+
*
|
|
7
|
+
* 1. First appearance: Image is shown in full (agent can see/analyze it)
|
|
8
|
+
* 2. Subsequent appearances: Image is replaced with a text placeholder
|
|
9
|
+
* that references fs_read(tool='<eventId>') for retrieval
|
|
10
|
+
*
|
|
11
|
+
* This reduces token costs by 95-99% for image-heavy conversations while
|
|
12
|
+
* maintaining retrieval capability.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { ToolResultPart } from "ai";
|
|
16
|
+
import { isImageUrl } from "./image-url-utils";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prefix used for image placeholders - allows detection of replaced images
|
|
20
|
+
*/
|
|
21
|
+
export const IMAGE_PLACEHOLDER_PREFIX = "[Image:";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Tracker interface for managing which images have been seen in the conversation
|
|
25
|
+
*/
|
|
26
|
+
export interface ImageTracker {
|
|
27
|
+
/**
|
|
28
|
+
* Check if an image URL has already been seen in this conversation
|
|
29
|
+
*/
|
|
30
|
+
hasBeenSeen(imageUrl: string): boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mark an image URL as seen (typically after first display)
|
|
34
|
+
*/
|
|
35
|
+
markAsSeen(imageUrl: string): void;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get all seen image URLs
|
|
39
|
+
*/
|
|
40
|
+
getSeenUrls(): Set<string>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Create a new image tracker for a conversation
|
|
45
|
+
*/
|
|
46
|
+
export function createImageTracker(): ImageTracker {
|
|
47
|
+
const seenUrls = new Set<string>();
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
hasBeenSeen(imageUrl: string): boolean {
|
|
51
|
+
return seenUrls.has(imageUrl);
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
markAsSeen(imageUrl: string): void {
|
|
55
|
+
seenUrls.add(imageUrl);
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
getSeenUrls(): Set<string> {
|
|
59
|
+
return seenUrls;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a placeholder text for an image that has been seen before.
|
|
66
|
+
*
|
|
67
|
+
* @param imageUrlOrName - The image URL or filename
|
|
68
|
+
* @param eventId - The event ID for retrieval via fs_read
|
|
69
|
+
* @returns Placeholder text with retrieval instructions
|
|
70
|
+
*/
|
|
71
|
+
export function createImagePlaceholder(
|
|
72
|
+
imageUrlOrName: string,
|
|
73
|
+
eventId: string | undefined
|
|
74
|
+
): string {
|
|
75
|
+
// Extract filename from URL if it looks like a URL
|
|
76
|
+
let filename = imageUrlOrName;
|
|
77
|
+
try {
|
|
78
|
+
const url = new URL(imageUrlOrName);
|
|
79
|
+
const pathParts = url.pathname.split("/");
|
|
80
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
81
|
+
if (lastPart) {
|
|
82
|
+
filename = lastPart;
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Not a URL, use as-is
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (eventId) {
|
|
89
|
+
return `${IMAGE_PLACEHOLDER_PREFIX} ${filename} - use fs_read(tool="${eventId}") to retrieve]`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Fallback when eventId is not available (shouldn't happen normally)
|
|
93
|
+
return `${IMAGE_PLACEHOLDER_PREFIX} ${filename} - original context lost, cannot retrieve]`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Regex pattern to match HTTP(S) URLs in text.
|
|
98
|
+
* Must match the raw URL as it appears in text, including trailing chars.
|
|
99
|
+
*/
|
|
100
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"]+/gi;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Normalize a raw URL match by removing trailing punctuation and validating.
|
|
104
|
+
* Returns null if the normalized URL is invalid.
|
|
105
|
+
*
|
|
106
|
+
* @param rawUrl - The raw URL as matched from text
|
|
107
|
+
* @returns Object with rawUrl (as matched) and normalizedUrl, or null if invalid
|
|
108
|
+
*/
|
|
109
|
+
function normalizeImageUrl(rawUrl: string): { rawUrl: string; normalizedUrl: string } | null {
|
|
110
|
+
// Remove trailing punctuation that might have been captured (., ), ], etc.)
|
|
111
|
+
let cleanUrl = rawUrl.replace(/[).,\]]+$/, "");
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
// Validate and normalize via URL constructor
|
|
115
|
+
const normalized = new URL(cleanUrl).href;
|
|
116
|
+
if (isImageUrl(normalized)) {
|
|
117
|
+
return { rawUrl: cleanUrl, normalizedUrl: normalized };
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
// Invalid URL after cleanup
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Extract image URLs from tool result parts.
|
|
128
|
+
* Returns normalized, deduplicated URLs in order of first occurrence.
|
|
129
|
+
*
|
|
130
|
+
* @param toolData - Array of tool result parts
|
|
131
|
+
* @returns Array of unique normalized image URL strings
|
|
132
|
+
*/
|
|
133
|
+
export function extractImageUrlsFromToolResult(toolData: ToolResultPart[]): string[] {
|
|
134
|
+
const seen = new Set<string>();
|
|
135
|
+
const imageUrls: string[] = [];
|
|
136
|
+
|
|
137
|
+
for (const part of toolData) {
|
|
138
|
+
const text = extractTextFromOutput(part.output);
|
|
139
|
+
if (!text) continue;
|
|
140
|
+
|
|
141
|
+
const matches = text.matchAll(new RegExp(URL_PATTERN.source, "gi"));
|
|
142
|
+
for (const match of matches) {
|
|
143
|
+
const result = normalizeImageUrl(match[0]);
|
|
144
|
+
if (result && !seen.has(result.normalizedUrl)) {
|
|
145
|
+
seen.add(result.normalizedUrl);
|
|
146
|
+
imageUrls.push(result.normalizedUrl);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return imageUrls;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Count the number of images in a tool result.
|
|
156
|
+
*
|
|
157
|
+
* @param toolData - Array of tool result parts
|
|
158
|
+
* @returns Number of images found
|
|
159
|
+
*/
|
|
160
|
+
export function countImagesInToolResult(toolData: ToolResultPart[]): number {
|
|
161
|
+
return extractImageUrlsFromToolResult(toolData).length;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Result from processing tool results with image tracking
|
|
166
|
+
*/
|
|
167
|
+
export interface ProcessToolResultOutput {
|
|
168
|
+
/** Processed tool result parts with placeholders applied */
|
|
169
|
+
processedParts: ToolResultPart[];
|
|
170
|
+
/** Number of individual URL occurrences replaced with placeholders */
|
|
171
|
+
replacedCount: number;
|
|
172
|
+
/** Number of unique URLs that were replaced (each may appear multiple times) */
|
|
173
|
+
uniqueReplacedCount: number;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Process a tool result with image tracking.
|
|
178
|
+
*
|
|
179
|
+
* - First-seen images: Preserved as-is, URL is marked as seen
|
|
180
|
+
* - Previously-seen images: URL replaced with placeholder text
|
|
181
|
+
*
|
|
182
|
+
* Uses a match-driven approach: finds all URL occurrences in text via regex,
|
|
183
|
+
* normalizes each match, and replaces ALL occurrences of seen URLs.
|
|
184
|
+
*
|
|
185
|
+
* @param toolData - Array of tool result parts
|
|
186
|
+
* @param tracker - Image tracker for the conversation
|
|
187
|
+
* @param eventId - Event ID for retrieval reference
|
|
188
|
+
* @returns Object with processed tool results and replacement statistics
|
|
189
|
+
*/
|
|
190
|
+
export function processToolResultWithImageTracking(
|
|
191
|
+
toolData: ToolResultPart[],
|
|
192
|
+
tracker: ImageTracker,
|
|
193
|
+
eventId: string | undefined
|
|
194
|
+
): ProcessToolResultOutput {
|
|
195
|
+
let totalReplacedCount = 0;
|
|
196
|
+
const uniqueReplacedUrls = new Set<string>();
|
|
197
|
+
|
|
198
|
+
const processedParts = toolData.map((part) => {
|
|
199
|
+
const text = extractTextFromOutput(part.output);
|
|
200
|
+
if (!text) {
|
|
201
|
+
// Non-text output (e.g., JSON), pass through unchanged
|
|
202
|
+
return part;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Use regex replace callback for match-driven processing
|
|
206
|
+
// This ensures we find and process every URL occurrence in the text
|
|
207
|
+
const processedText = text.replace(
|
|
208
|
+
new RegExp(URL_PATTERN.source, "gi"),
|
|
209
|
+
(rawMatch) => {
|
|
210
|
+
const result = normalizeImageUrl(rawMatch);
|
|
211
|
+
if (!result) {
|
|
212
|
+
// Not a valid image URL, keep as-is
|
|
213
|
+
return rawMatch;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const { rawUrl, normalizedUrl } = result;
|
|
217
|
+
|
|
218
|
+
if (tracker.hasBeenSeen(normalizedUrl)) {
|
|
219
|
+
// Image already seen - replace with placeholder
|
|
220
|
+
totalReplacedCount++;
|
|
221
|
+
uniqueReplacedUrls.add(normalizedUrl);
|
|
222
|
+
return createImagePlaceholder(normalizedUrl, eventId);
|
|
223
|
+
} else {
|
|
224
|
+
// First time seeing this image - mark as seen but preserve URL
|
|
225
|
+
tracker.markAsSeen(normalizedUrl);
|
|
226
|
+
return rawUrl;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
// Return modified part with updated text
|
|
232
|
+
return {
|
|
233
|
+
...part,
|
|
234
|
+
output: updateOutputText(part.output, processedText),
|
|
235
|
+
};
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
processedParts: processedParts as ToolResultPart[],
|
|
240
|
+
replacedCount: totalReplacedCount,
|
|
241
|
+
uniqueReplacedCount: uniqueReplacedUrls.size,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Extract text from a tool result output (various formats)
|
|
247
|
+
*/
|
|
248
|
+
function extractTextFromOutput(output: unknown): string | null {
|
|
249
|
+
if (typeof output === "string") {
|
|
250
|
+
return output;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (output && typeof output === "object" && "value" in output) {
|
|
254
|
+
const value = (output as { value: unknown }).value;
|
|
255
|
+
if (typeof value === "string") {
|
|
256
|
+
return value;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Update the text in a tool result output, preserving the output structure
|
|
265
|
+
*/
|
|
266
|
+
function updateOutputText(output: unknown, newText: string): unknown {
|
|
267
|
+
if (typeof output === "string") {
|
|
268
|
+
return newText;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (output && typeof output === "object" && "value" in output) {
|
|
272
|
+
const existingOutput = output as { type?: string; value: unknown };
|
|
273
|
+
return {
|
|
274
|
+
...existingOutput,
|
|
275
|
+
value: newText,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Unknown format - return as-is
|
|
280
|
+
return output;
|
|
281
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for detecting and extracting image URLs from message content.
|
|
3
|
+
*
|
|
4
|
+
* These utilities help convert text messages containing image URLs into
|
|
5
|
+
* multimodal messages compatible with the AI SDK's ImagePart format.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Supported image file extensions (lowercase, with leading dot)
|
|
10
|
+
*/
|
|
11
|
+
export const IMAGE_EXTENSIONS = [
|
|
12
|
+
".jpg",
|
|
13
|
+
".jpeg",
|
|
14
|
+
".png",
|
|
15
|
+
".gif",
|
|
16
|
+
".webp",
|
|
17
|
+
".svg",
|
|
18
|
+
] as const;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Regex pattern to match HTTP(S) URLs
|
|
22
|
+
* Captures URLs that start with http:// or https://
|
|
23
|
+
*/
|
|
24
|
+
const URL_PATTERN = /https?:\/\/[^\s<>"\]()]+/gi;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Domains that should be skipped for image fetching.
|
|
28
|
+
* These are either reserved example domains (RFC 2606), localhost/development domains,
|
|
29
|
+
* or other non-routable domains that will fail to fetch.
|
|
30
|
+
*/
|
|
31
|
+
const SKIP_DOMAINS = new Set([
|
|
32
|
+
// RFC 2606 reserved example domains (only .com, .net, .org are reserved)
|
|
33
|
+
"example.com",
|
|
34
|
+
"example.org",
|
|
35
|
+
"example.net",
|
|
36
|
+
// Localhost variants
|
|
37
|
+
"localhost",
|
|
38
|
+
"0.0.0.0",
|
|
39
|
+
// IPv6 loopback - URL.hostname returns "[::1]" with brackets in Bun/Node
|
|
40
|
+
"[::1]",
|
|
41
|
+
// Common development/test domains
|
|
42
|
+
"test",
|
|
43
|
+
"invalid",
|
|
44
|
+
"local",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Check if a hostname is a loopback address (127.0.0.0/8 range).
|
|
49
|
+
* This covers all 127.x.x.x addresses, not just 127.0.0.1.
|
|
50
|
+
*
|
|
51
|
+
* @param hostname - The hostname to check
|
|
52
|
+
* @returns true if the hostname is a loopback address
|
|
53
|
+
*/
|
|
54
|
+
function isLoopbackAddress(hostname: string): boolean {
|
|
55
|
+
// Match any 127.x.x.x address (the entire 127.0.0.0/8 block is reserved for loopback)
|
|
56
|
+
return /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(hostname);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Check if a URL should be skipped for image fetching.
|
|
61
|
+
* Returns true for URLs on reserved/example/localhost domains that
|
|
62
|
+
* will fail to fetch and could crash the agent.
|
|
63
|
+
*
|
|
64
|
+
* @param url - The URL string to check
|
|
65
|
+
* @returns true if the URL should be skipped, false if it can be fetched
|
|
66
|
+
*/
|
|
67
|
+
export function shouldSkipImageUrl(url: string): boolean {
|
|
68
|
+
if (!url) return true;
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
const parsedUrl = new URL(url);
|
|
72
|
+
const hostname = parsedUrl.hostname.toLowerCase();
|
|
73
|
+
|
|
74
|
+
// Check exact match against skip domains
|
|
75
|
+
if (SKIP_DOMAINS.has(hostname)) {
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check for any 127.x.x.x loopback address (entire /8 block)
|
|
80
|
+
if (isLoopbackAddress(hostname)) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check for subdomains of example domains (e.g., cdn.example.com, www.example.org)
|
|
85
|
+
for (const skipDomain of SKIP_DOMAINS) {
|
|
86
|
+
if (hostname.endsWith(`.${skipDomain}`)) {
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check for .local, .test, .invalid, .localhost TLDs (RFC 2606 / RFC 6761)
|
|
92
|
+
if (
|
|
93
|
+
hostname.endsWith(".local") ||
|
|
94
|
+
hostname.endsWith(".test") ||
|
|
95
|
+
hostname.endsWith(".invalid") ||
|
|
96
|
+
hostname.endsWith(".localhost") ||
|
|
97
|
+
hostname.endsWith(".example")
|
|
98
|
+
) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return false;
|
|
103
|
+
} catch {
|
|
104
|
+
// Invalid URL - skip it
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Check if a URL string points to an image based on its file extension.
|
|
111
|
+
* Handles URLs with query parameters and fragments.
|
|
112
|
+
*
|
|
113
|
+
* @param url - The URL string to check
|
|
114
|
+
* @returns true if the URL appears to be an image, false otherwise
|
|
115
|
+
*/
|
|
116
|
+
export function isImageUrl(url: string): boolean {
|
|
117
|
+
if (!url) return false;
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const parsedUrl = new URL(url);
|
|
121
|
+
// Only accept http/https protocols
|
|
122
|
+
if (parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Get the pathname and check the extension (ignoring query params and fragments)
|
|
127
|
+
const pathname = parsedUrl.pathname.toLowerCase();
|
|
128
|
+
|
|
129
|
+
return IMAGE_EXTENSIONS.some((ext) => pathname.endsWith(ext));
|
|
130
|
+
} catch {
|
|
131
|
+
// Invalid URL
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract all image URLs from a text string.
|
|
138
|
+
* Returns deduplicated URLs in order of first occurrence.
|
|
139
|
+
*
|
|
140
|
+
* @param text - The text content to search for image URLs
|
|
141
|
+
* @returns Array of unique image URL strings
|
|
142
|
+
*/
|
|
143
|
+
export function extractImageUrls(text: string): string[] {
|
|
144
|
+
if (!text) return [];
|
|
145
|
+
|
|
146
|
+
// Find all URLs in the text
|
|
147
|
+
const allUrls = text.match(URL_PATTERN) || [];
|
|
148
|
+
|
|
149
|
+
// Filter to only image URLs and deduplicate
|
|
150
|
+
const seen = new Set<string>();
|
|
151
|
+
const imageUrls: string[] = [];
|
|
152
|
+
|
|
153
|
+
for (const url of allUrls) {
|
|
154
|
+
// Clean up URL (remove trailing punctuation that might have been captured)
|
|
155
|
+
let cleanUrl = url.replace(/[),.]+$/, "");
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
// Validate and normalize
|
|
159
|
+
cleanUrl = new URL(cleanUrl).href;
|
|
160
|
+
} catch {
|
|
161
|
+
continue; // Skip invalid URLs encountered during cleanup
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (isImageUrl(cleanUrl) && !seen.has(cleanUrl)) {
|
|
165
|
+
seen.add(cleanUrl);
|
|
166
|
+
imageUrls.push(cleanUrl);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return imageUrls;
|
|
171
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utilities for converting text content with image URLs to AI SDK multimodal format.
|
|
3
|
+
*
|
|
4
|
+
* The AI SDK supports multimodal user messages with content arrays containing
|
|
5
|
+
* TextPart and ImagePart objects. This module converts plain text messages
|
|
6
|
+
* containing image URLs into the appropriate multimodal format.
|
|
7
|
+
*
|
|
8
|
+
* @see https://ai-sdk.dev/docs/foundations/prompts#multi-modal-messages
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { TextPart, ImagePart } from "ai";
|
|
12
|
+
import { extractImageUrls, shouldSkipImageUrl } from "./image-url-utils";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Type representing multimodal content for user messages.
|
|
16
|
+
* Can be a simple string (no images) or an array of text/image parts.
|
|
17
|
+
*/
|
|
18
|
+
export type MultimodalContent = string | Array<TextPart | ImagePart>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Check if text content contains any image URLs.
|
|
22
|
+
* Use this to determine if multimodal conversion is needed.
|
|
23
|
+
*
|
|
24
|
+
* @param content - The text content to check
|
|
25
|
+
* @returns true if the content contains image URLs
|
|
26
|
+
*/
|
|
27
|
+
export function hasImageUrls(content: string): boolean {
|
|
28
|
+
return extractImageUrls(content).length > 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Convert text content to multimodal content if it contains image URLs.
|
|
33
|
+
*
|
|
34
|
+
* If the content contains image URLs:
|
|
35
|
+
* - Returns an array with:
|
|
36
|
+
* 1. A TextPart containing the full original text (preserves context)
|
|
37
|
+
* 2. ImageParts for each unique image URL (using URL objects for AI SDK to fetch)
|
|
38
|
+
*
|
|
39
|
+
* If no image URLs are found:
|
|
40
|
+
* - Returns the original string unchanged
|
|
41
|
+
*
|
|
42
|
+
* @param content - The text content to convert
|
|
43
|
+
* @returns Either the original string or a multimodal content array
|
|
44
|
+
*/
|
|
45
|
+
export function convertToMultimodalContent(content: string): MultimodalContent {
|
|
46
|
+
if (!content) return content;
|
|
47
|
+
|
|
48
|
+
const imageUrls = extractImageUrls(content);
|
|
49
|
+
|
|
50
|
+
// No images - return as-is
|
|
51
|
+
if (imageUrls.length === 0) {
|
|
52
|
+
return content;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Build multimodal content array
|
|
56
|
+
const parts: Array<TextPart | ImagePart> = [];
|
|
57
|
+
|
|
58
|
+
// Add the full text content first (preserves context and attribution)
|
|
59
|
+
parts.push({
|
|
60
|
+
type: "text",
|
|
61
|
+
text: content,
|
|
62
|
+
} satisfies TextPart);
|
|
63
|
+
|
|
64
|
+
// Add each image as an ImagePart with URL reference
|
|
65
|
+
// The AI SDK will fetch the images automatically
|
|
66
|
+
// Skip URLs on non-routable domains (example.com, localhost, etc.) that would fail
|
|
67
|
+
for (const imageUrl of imageUrls) {
|
|
68
|
+
// Skip URLs that cannot be fetched (example domains, localhost, etc.)
|
|
69
|
+
// These would cause the AI SDK fetch to fail and crash the agent
|
|
70
|
+
if (shouldSkipImageUrl(imageUrl)) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Note: new URL() here should never throw because extractImageUrls
|
|
75
|
+
// already validates and normalizes URLs. The ImagePart uses URL objects
|
|
76
|
+
// as per AI SDK spec, allowing the SDK to fetch the images.
|
|
77
|
+
parts.push({
|
|
78
|
+
type: "image",
|
|
79
|
+
image: new URL(imageUrl),
|
|
80
|
+
} satisfies ImagePart);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// If all image URLs were skipped (non-fetchable domains), return original string
|
|
84
|
+
// parts[0] is always the TextPart, so if length is 1, no images were added
|
|
85
|
+
if (parts.length === 1) {
|
|
86
|
+
return content;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts;
|
|
90
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ToolResultTruncator - Truncates large/old tool results to save context
|
|
3
|
+
*
|
|
4
|
+
* Rules:
|
|
5
|
+
* - Tool results < 1k chars: never truncated
|
|
6
|
+
* - Tool results > 10k chars: include inline for 3 messages, then truncate
|
|
7
|
+
* - Tool results 1k-10k chars: include inline for 6 messages, then truncate
|
|
8
|
+
*
|
|
9
|
+
* Truncated results are replaced with a reference that allows re-reading via
|
|
10
|
+
* the event ID using fs_read(tool=<eventId>).
|
|
11
|
+
*
|
|
12
|
+
* EMERGENCY CONTENT GUARD: If truncation criteria are met but no eventId is
|
|
13
|
+
* available, content is STILL omitted to protect the context window. This
|
|
14
|
+
* prioritizes context window safety over content retrieval.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { ToolResultPart } from "ai";
|
|
18
|
+
|
|
19
|
+
// Thresholds for truncation
|
|
20
|
+
const NEVER_TRUNCATE_THRESHOLD = 1000; // 1k chars - never truncate below this
|
|
21
|
+
const LARGE_RESULT_THRESHOLD = 10000; // 10k chars
|
|
22
|
+
const LARGE_RESULT_BURIAL_LIMIT = 3; // Messages before truncation for large results
|
|
23
|
+
const SMALL_RESULT_BURIAL_LIMIT = 6; // Messages before truncation for small results
|
|
24
|
+
|
|
25
|
+
export interface TruncationContext {
|
|
26
|
+
/** Current message index being processed */
|
|
27
|
+
currentIndex: number;
|
|
28
|
+
/** Total number of messages in the conversation */
|
|
29
|
+
totalMessages: number;
|
|
30
|
+
/** Event ID for referencing the tool result (required for truncation) */
|
|
31
|
+
eventId?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Calculate the original size of a tool result
|
|
36
|
+
*/
|
|
37
|
+
function getToolResultSize(toolData: ToolResultPart[]): number {
|
|
38
|
+
let totalSize = 0;
|
|
39
|
+
for (const part of toolData) {
|
|
40
|
+
const output = part.output as unknown;
|
|
41
|
+
if (output) {
|
|
42
|
+
if (typeof output === "string") {
|
|
43
|
+
totalSize += output.length;
|
|
44
|
+
} else if (typeof output === "object" && output !== null && "value" in output) {
|
|
45
|
+
const value = (output as { value: unknown }).value;
|
|
46
|
+
if (typeof value === "string") {
|
|
47
|
+
totalSize += value.length;
|
|
48
|
+
} else {
|
|
49
|
+
try {
|
|
50
|
+
totalSize += (JSON.stringify(value) ?? "").length;
|
|
51
|
+
} catch {
|
|
52
|
+
// Fallback for non-serializable values (BigInt, circular refs, etc.)
|
|
53
|
+
// Bias toward truncation when we can't measure - safer for context window
|
|
54
|
+
totalSize += LARGE_RESULT_THRESHOLD;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
try {
|
|
59
|
+
totalSize += (JSON.stringify(output) ?? "").length;
|
|
60
|
+
} catch {
|
|
61
|
+
// Fallback for non-serializable values (BigInt, circular refs, etc.)
|
|
62
|
+
// Bias toward truncation when we can't measure - safer for context window
|
|
63
|
+
totalSize += LARGE_RESULT_THRESHOLD;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return totalSize;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Check if a tool result should be truncated based on its size and burial depth.
|
|
73
|
+
* EMERGENCY CONTENT GUARD: Truncation decision is independent of eventId availability.
|
|
74
|
+
* Context window safety takes priority over content retrieval.
|
|
75
|
+
*/
|
|
76
|
+
export function shouldTruncateToolResult(
|
|
77
|
+
toolData: ToolResultPart[],
|
|
78
|
+
context: TruncationContext
|
|
79
|
+
): boolean {
|
|
80
|
+
const size = getToolResultSize(toolData);
|
|
81
|
+
|
|
82
|
+
// Never truncate small results
|
|
83
|
+
if (size < NEVER_TRUNCATE_THRESHOLD) {
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// NOTE: We no longer check for eventId here. Truncation decision is purely
|
|
88
|
+
// based on size and burial depth. The processToolResult function handles
|
|
89
|
+
// the case where eventId is missing by providing a placeholder message.
|
|
90
|
+
|
|
91
|
+
const burialDepth = context.totalMessages - context.currentIndex - 1;
|
|
92
|
+
|
|
93
|
+
if (size > LARGE_RESULT_THRESHOLD) {
|
|
94
|
+
return burialDepth >= LARGE_RESULT_BURIAL_LIMIT;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return burialDepth >= SMALL_RESULT_BURIAL_LIMIT;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Create a truncated tool result that references the original by event ID
|
|
102
|
+
*/
|
|
103
|
+
export function createTruncatedToolResult(
|
|
104
|
+
toolData: ToolResultPart[],
|
|
105
|
+
eventId: string
|
|
106
|
+
): ToolResultPart[] {
|
|
107
|
+
const size = getToolResultSize(toolData);
|
|
108
|
+
|
|
109
|
+
return toolData.map((part) => ({
|
|
110
|
+
type: "tool-result" as const,
|
|
111
|
+
toolCallId: part.toolCallId,
|
|
112
|
+
toolName: part.toolName,
|
|
113
|
+
output: {
|
|
114
|
+
type: "text" as const,
|
|
115
|
+
value: `[Tool executed, ${size} chars output truncated. Use fs_read(tool="${eventId}") to retrieve full output if needed]`,
|
|
116
|
+
},
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create an omitted tool result when no eventId is available for retrieval.
|
|
122
|
+
* EMERGENCY CONTENT GUARD: This protects the context window from overflow
|
|
123
|
+
* when truncation criteria are met but content cannot be retrieved later.
|
|
124
|
+
*/
|
|
125
|
+
export function createOmittedToolResult(toolData: ToolResultPart[]): ToolResultPart[] {
|
|
126
|
+
const size = getToolResultSize(toolData);
|
|
127
|
+
|
|
128
|
+
return toolData.map((part) => ({
|
|
129
|
+
type: "tool-result" as const,
|
|
130
|
+
toolCallId: part.toolCallId,
|
|
131
|
+
toolName: part.toolName,
|
|
132
|
+
output: {
|
|
133
|
+
type: "text" as const,
|
|
134
|
+
value: `[Tool output omitted to save context (${size} chars) - no reference available for retrieval]`,
|
|
135
|
+
},
|
|
136
|
+
}));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Process tool result, potentially truncating if buried too deep.
|
|
141
|
+
* EMERGENCY CONTENT GUARD: If truncation is needed but no eventId is available,
|
|
142
|
+
* content is omitted with a placeholder to protect the context window.
|
|
143
|
+
*/
|
|
144
|
+
export function processToolResult(
|
|
145
|
+
toolData: ToolResultPart[],
|
|
146
|
+
context: TruncationContext
|
|
147
|
+
): ToolResultPart[] {
|
|
148
|
+
if (shouldTruncateToolResult(toolData, context)) {
|
|
149
|
+
if (context.eventId) {
|
|
150
|
+
// Normal case: truncate with retrieval reference
|
|
151
|
+
return createTruncatedToolResult(toolData, context.eventId);
|
|
152
|
+
} else {
|
|
153
|
+
// EMERGENCY CONTENT GUARD: No eventId available, but we still
|
|
154
|
+
// must protect the context window. Omit content with placeholder.
|
|
155
|
+
return createOmittedToolResult(toolData);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return toolData;
|
|
159
|
+
}
|