@vellumai/assistant 0.8.7 → 0.8.8
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/Dockerfile +20 -4
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/plugins.md +75 -79
- package/examples/plugins/echo/README.md +6 -12
- package/examples/plugins/echo/register.ts +0 -41
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/openapi.yaml +3381 -348
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.test.ts +1 -1
- package/src/__tests__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
- package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
- package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +2 -8
- package/src/__tests__/injector-chain.test.ts +106 -270
- package/src/__tests__/injector-disk-pressure.test.ts +3 -12
- package/src/__tests__/injector-document-comments.test.ts +2 -2
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pipeline-runner.test.ts +29 -39
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +13 -28
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-types.test.ts +2 -125
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/agent/loop.ts +346 -442
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +368 -0
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
- package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +35 -3
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +122 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +3 -6
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
- package/src/daemon/conversation-agent-loop.ts +281 -760
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-runtime-assembly.ts +130 -347
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation.ts +32 -14
- package/src/daemon/external-plugins-bootstrap.ts +9 -10
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +39 -10
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- package/src/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +4 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +31 -4
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
- package/src/memory/v2/consolidation-job.ts +1 -1
- package/src/memory/v3/__tests__/health.test.ts +16 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
- package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
- package/src/memory/v3/__tests__/router.test.ts +101 -29
- package/src/memory/v3/__tests__/selector.test.ts +93 -27
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/llm-retry.ts +32 -0
- package/src/memory/v3/orchestrate.ts +26 -14
- package/src/memory/v3/provider-blocks.ts +15 -5
- package/src/memory/v3/router.ts +48 -42
- package/src/memory/v3/selector.ts +57 -42
- package/src/memory/v3/shadow-plugin.ts +47 -15
- package/src/memory/v3/types.ts +8 -0
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +8 -1
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +1 -15
- package/src/plugins/defaults/injectors/register.ts +243 -74
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/pipeline.ts +6 -18
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +43 -474
- package/src/proactive-artifact/aux-message-injector.ts +3 -3
- package/src/proactive-artifact/job.test.ts +7 -12
- package/src/prompts/__tests__/system-prompt.test.ts +36 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +5 -1
- package/src/runtime/agent-wake.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-query-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +358 -184
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- package/src/runtime/routes/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +208 -17
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +50 -28
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/executor.ts +1 -53
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +9 -1
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- package/src/__tests__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/package.json +0 -15
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// Single source of truth for classifying `web_search` provider/backend
|
|
2
|
+
// failures and the user-facing copy we surface for them (ATL-727).
|
|
3
|
+
//
|
|
4
|
+
// This is a pure leaf module: it has NO imports from `daemon/`, `agent/`,
|
|
5
|
+
// `apps/`, or any client/UI package. It may only import the logger and pino
|
|
6
|
+
// types (for the telemetry helper). Every web_search code path — native
|
|
7
|
+
// Anthropic handler, app-side providers, and the web client default — funnels
|
|
8
|
+
// failures through `classifyWebSearchFailure` so the same friendly message
|
|
9
|
+
// propagates to every client via `WebSearchMetadata.errorMessage`.
|
|
10
|
+
|
|
11
|
+
import type { Logger } from "pino";
|
|
12
|
+
|
|
13
|
+
import { isAbortReason } from "../../util/abort-reasons.js";
|
|
14
|
+
import { truncateForLog } from "../../util/logger.js";
|
|
15
|
+
import { isRetryableNetworkError } from "../../util/retry.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Canonical user-facing copy for a recoverable web_search backend failure.
|
|
19
|
+
* This is what propagates to every client via `WebSearchMetadata.errorMessage`.
|
|
20
|
+
*
|
|
21
|
+
* It names the search tool as the thing struggling, offers retry /
|
|
22
|
+
* continue-without-search / paste-details, does not blame the user, does not
|
|
23
|
+
* imply we can fix the provider, does not claim the whole internet or all
|
|
24
|
+
* tools are down, and contains no raw provider details, JSON, stack traces,
|
|
25
|
+
* or exception names.
|
|
26
|
+
*/
|
|
27
|
+
export const WEB_SEARCH_BACKEND_FAILURE_MESSAGE =
|
|
28
|
+
"Search is having trouble right now. You can try again in a moment, continue without web search, or paste the relevant details here and I'll use those.";
|
|
29
|
+
|
|
30
|
+
const QUERY_TOO_LONG_MESSAGE =
|
|
31
|
+
"That search query was too long. Try a shorter query.";
|
|
32
|
+
|
|
33
|
+
const MAX_USES_EXCEEDED_MESSAGE =
|
|
34
|
+
"I've hit the web-search limit for this turn. I can continue without more searches, or you can paste the details and I'll use those.";
|
|
35
|
+
|
|
36
|
+
const CONFIG_MESSAGE = "Web search isn't configured.";
|
|
37
|
+
|
|
38
|
+
export type WebSearchFailureCategory =
|
|
39
|
+
| "backend_unavailable"
|
|
40
|
+
| "rate_limited"
|
|
41
|
+
| "query_too_long"
|
|
42
|
+
| "max_uses_exceeded"
|
|
43
|
+
| "config"
|
|
44
|
+
| "no_results"
|
|
45
|
+
| "unknown";
|
|
46
|
+
|
|
47
|
+
export interface WebSearchFailureClassification {
|
|
48
|
+
category: WebSearchFailureCategory;
|
|
49
|
+
isBackendFailure: boolean;
|
|
50
|
+
userMessage: string;
|
|
51
|
+
rawDetail: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface WebSearchFailureInput {
|
|
55
|
+
/** Anthropic `web_search_tool_result_error` code, when present. */
|
|
56
|
+
errorCode?: string;
|
|
57
|
+
/** Thrown error or rejected value from a fetch/provider call. */
|
|
58
|
+
error?: unknown;
|
|
59
|
+
/** HTTP status code from a provider response, when present. */
|
|
60
|
+
statusCode?: number;
|
|
61
|
+
/** Whether the tool result was flagged as an error. */
|
|
62
|
+
isError?: boolean;
|
|
63
|
+
/** Whether a successful call returned any results. */
|
|
64
|
+
hasResults?: boolean;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Categories we consider a transient backend failure worth the friendly copy. */
|
|
68
|
+
function isBackendFailureCategory(category: WebSearchFailureCategory): boolean {
|
|
69
|
+
return category === "backend_unavailable" || category === "rate_limited";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function userMessageFor(category: WebSearchFailureCategory): string {
|
|
73
|
+
switch (category) {
|
|
74
|
+
case "backend_unavailable":
|
|
75
|
+
case "rate_limited":
|
|
76
|
+
return WEB_SEARCH_BACKEND_FAILURE_MESSAGE;
|
|
77
|
+
case "query_too_long":
|
|
78
|
+
return QUERY_TOO_LONG_MESSAGE;
|
|
79
|
+
case "max_uses_exceeded":
|
|
80
|
+
return MAX_USES_EXCEEDED_MESSAGE;
|
|
81
|
+
case "config":
|
|
82
|
+
return CONFIG_MESSAGE;
|
|
83
|
+
case "no_results":
|
|
84
|
+
case "unknown":
|
|
85
|
+
// Neutral passthrough: callers keep their existing behavior.
|
|
86
|
+
return "";
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Map an Anthropic `web_search_tool_result_error` code to a category. */
|
|
91
|
+
function categoryFromErrorCode(
|
|
92
|
+
errorCode: string,
|
|
93
|
+
): WebSearchFailureCategory | undefined {
|
|
94
|
+
switch (errorCode) {
|
|
95
|
+
case "unavailable":
|
|
96
|
+
case "internal_error":
|
|
97
|
+
case "overloaded_error":
|
|
98
|
+
return "backend_unavailable";
|
|
99
|
+
case "too_many_requests":
|
|
100
|
+
return "rate_limited";
|
|
101
|
+
case "query_too_long":
|
|
102
|
+
return "query_too_long";
|
|
103
|
+
case "max_uses_exceeded":
|
|
104
|
+
return "max_uses_exceeded";
|
|
105
|
+
case "invalid_input":
|
|
106
|
+
// Recoverable, but not a backend failure — let callers handle it.
|
|
107
|
+
return "unknown";
|
|
108
|
+
default:
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Map an HTTP status code to a category. */
|
|
114
|
+
function categoryFromStatusCode(
|
|
115
|
+
statusCode: number,
|
|
116
|
+
): WebSearchFailureCategory | undefined {
|
|
117
|
+
if (statusCode === 429) return "rate_limited";
|
|
118
|
+
if (statusCode === 401 || statusCode === 403) return "config";
|
|
119
|
+
if (statusCode >= 500) return "backend_unavailable";
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Classify a thrown error / rejected value. This module is web_search-only, so
|
|
125
|
+
* network-layer failures (fetch failed, connection reset/refused, DNS, and
|
|
126
|
+
* timeouts without an explicit user-abort reason) are treated as backend
|
|
127
|
+
* failures.
|
|
128
|
+
*/
|
|
129
|
+
function categoryFromError(error: unknown): WebSearchFailureCategory | undefined {
|
|
130
|
+
if (error == null) return undefined;
|
|
131
|
+
|
|
132
|
+
// A user-initiated abort (Stop/Esc, preemption, dispose) is not a failure.
|
|
133
|
+
// The tagged `AbortReason` may surface directly (`AbortSignal.throwIfAborted`
|
|
134
|
+
// throws `signal.reason` verbatim), via `error.reason`, or — when a provider
|
|
135
|
+
// wrapper erases the `AbortError` name — on `ProviderError.abortReason`. Check
|
|
136
|
+
// all three FIRST, before the transport-retryability and abort/timeout
|
|
137
|
+
// substring heuristics, so a tagged cancellation that ALSO carries a
|
|
138
|
+
// transport-shaped `cause` (e.g. ECONNRESET) short-circuits to "not a
|
|
139
|
+
// failure" instead of being mislabeled a backend outage. A bare
|
|
140
|
+
// AbortError/timeout with no tagged reason still falls through below.
|
|
141
|
+
if (
|
|
142
|
+
isAbortReason(error) ||
|
|
143
|
+
isAbortReason((error as { reason?: unknown }).reason) ||
|
|
144
|
+
isAbortReason((error as { abortReason?: unknown }).abortReason)
|
|
145
|
+
) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Retryable transport failures (ECONNRESET/ECONNREFUSED/ETIMEDOUT, socket
|
|
150
|
+
// hang-ups, including one level of `cause` chain) are backend failures.
|
|
151
|
+
if (isRetryableNetworkError(error)) return "backend_unavailable";
|
|
152
|
+
|
|
153
|
+
const name = typeof (error as { name?: unknown }).name === "string"
|
|
154
|
+
? (error as { name: string }).name
|
|
155
|
+
: "";
|
|
156
|
+
const haystack = `${name} ${(error as { message?: unknown }).message ?? ""}`
|
|
157
|
+
.toLowerCase();
|
|
158
|
+
|
|
159
|
+
// web_search-only: treat aborts/timeouts/DNS/fetch failures (the cases
|
|
160
|
+
// `isRetryableNetworkError` doesn't cover) as backend failures.
|
|
161
|
+
if (
|
|
162
|
+
name === "AbortError" ||
|
|
163
|
+
haystack.includes("abort") ||
|
|
164
|
+
haystack.includes("timeout") ||
|
|
165
|
+
haystack.includes("timed out") ||
|
|
166
|
+
haystack.includes("fetch failed") ||
|
|
167
|
+
haystack.includes("failed to fetch") ||
|
|
168
|
+
haystack.includes("enotfound")
|
|
169
|
+
) {
|
|
170
|
+
return "backend_unavailable";
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Build the internal-only raw detail string (never embedded in userMessage). */
|
|
176
|
+
function buildRawDetail(input: WebSearchFailureInput): string {
|
|
177
|
+
const parts: string[] = [];
|
|
178
|
+
if (input.errorCode) parts.push(`errorCode=${input.errorCode}`);
|
|
179
|
+
if (typeof input.statusCode === "number") {
|
|
180
|
+
parts.push(`statusCode=${input.statusCode}`);
|
|
181
|
+
}
|
|
182
|
+
if (input.error != null) {
|
|
183
|
+
const err = input.error as { message?: unknown };
|
|
184
|
+
const msg =
|
|
185
|
+
typeof err.message === "string" ? err.message : String(input.error);
|
|
186
|
+
if (msg) parts.push(msg);
|
|
187
|
+
}
|
|
188
|
+
return truncateForLog(parts.join(" "), 500);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Classify a web_search failure into a stable category, a user-facing message,
|
|
193
|
+
* and an internal-only raw detail. Never treats an empty-but-successful result
|
|
194
|
+
* as a failure.
|
|
195
|
+
*/
|
|
196
|
+
export function classifyWebSearchFailure(
|
|
197
|
+
input: WebSearchFailureInput,
|
|
198
|
+
): WebSearchFailureClassification {
|
|
199
|
+
const rawDetail = buildRawDetail(input);
|
|
200
|
+
|
|
201
|
+
// Success-passthrough: nothing went wrong, there were just no results.
|
|
202
|
+
if (!input.isError && input.error == null) {
|
|
203
|
+
return {
|
|
204
|
+
category: "no_results",
|
|
205
|
+
isBackendFailure: false,
|
|
206
|
+
userMessage: userMessageFor("no_results"),
|
|
207
|
+
rawDetail,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Resolution order: Anthropic `errorCode` → explicit HTTP `statusCode` →
|
|
212
|
+
// error-body/network heuristics. An explicit status code is authoritative
|
|
213
|
+
// over substring-sniffing the provider's response body (which can contain
|
|
214
|
+
// misleading keywords like "timeout"/"abort"). Tagged user-aborts carry no
|
|
215
|
+
// status code, so they still flow through `categoryFromError` and
|
|
216
|
+
// short-circuit to a non-failure.
|
|
217
|
+
const category =
|
|
218
|
+
(input.errorCode != null
|
|
219
|
+
? categoryFromErrorCode(input.errorCode)
|
|
220
|
+
: undefined) ??
|
|
221
|
+
(typeof input.statusCode === "number"
|
|
222
|
+
? categoryFromStatusCode(input.statusCode)
|
|
223
|
+
: undefined) ??
|
|
224
|
+
categoryFromError(input.error) ??
|
|
225
|
+
"unknown";
|
|
226
|
+
|
|
227
|
+
return {
|
|
228
|
+
category,
|
|
229
|
+
isBackendFailure: isBackendFailureCategory(category),
|
|
230
|
+
userMessage: userMessageFor(category),
|
|
231
|
+
rawDetail,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export interface WebSearchBackendFailureMeta {
|
|
236
|
+
provider: string;
|
|
237
|
+
requestId?: string;
|
|
238
|
+
errorCategory: WebSearchFailureCategory;
|
|
239
|
+
rawDetail: string;
|
|
240
|
+
fallbackShown: boolean;
|
|
241
|
+
queryLength?: number;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Emit a structured warning for a web_search backend failure (ATL-727).
|
|
246
|
+
*
|
|
247
|
+
* Do NOT log raw query text — only `queryLength`. `rawDetail` is internal-only
|
|
248
|
+
* provider/HTTP context and must never be surfaced to users.
|
|
249
|
+
*/
|
|
250
|
+
export function logWebSearchBackendFailure(
|
|
251
|
+
log: Logger,
|
|
252
|
+
meta: WebSearchBackendFailureMeta,
|
|
253
|
+
): void {
|
|
254
|
+
log.warn(
|
|
255
|
+
{
|
|
256
|
+
event: "web_search_backend_failure",
|
|
257
|
+
tool: "web_search",
|
|
258
|
+
provider: meta.provider,
|
|
259
|
+
requestId: meta.requestId,
|
|
260
|
+
errorCategory: meta.errorCategory,
|
|
261
|
+
rawDetail: meta.rawDetail,
|
|
262
|
+
fallbackShown: meta.fallbackShown,
|
|
263
|
+
queryLength: meta.queryLength,
|
|
264
|
+
},
|
|
265
|
+
"web_search backend failure",
|
|
266
|
+
);
|
|
267
|
+
}
|
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
import { RiskLevel } from "../../permissions/types.js";
|
|
7
7
|
import { getProviderKeyAsync } from "../../security/secure-keys.js";
|
|
8
8
|
import { wrapUntrustedContent } from "../../security/untrusted-content.js";
|
|
9
|
+
import { isAbortReason } from "../../util/abort-reasons.js";
|
|
9
10
|
import { faviconUrlForDomain } from "../../util/favicon.js";
|
|
10
11
|
import { getLogger } from "../../util/logger.js";
|
|
11
12
|
import {
|
|
@@ -22,6 +23,11 @@ import type {
|
|
|
22
23
|
} from "../types.js";
|
|
23
24
|
import { extractDomain } from "./domain-normalize.js";
|
|
24
25
|
import type { ManagedSearchProxyResult } from "./managed-search-proxy.js";
|
|
26
|
+
import {
|
|
27
|
+
classifyWebSearchFailure,
|
|
28
|
+
logWebSearchBackendFailure,
|
|
29
|
+
WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
|
|
30
|
+
} from "./web-search-error.js";
|
|
25
31
|
|
|
26
32
|
const log = getLogger("web-search");
|
|
27
33
|
|
|
@@ -381,6 +387,115 @@ function errorResult(
|
|
|
381
387
|
};
|
|
382
388
|
}
|
|
383
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Wrap an already-read provider response body so {@link backendFailureResult}
|
|
392
|
+
* forwards it into the classifier's internal-only `rawDetail` (telemetry). The
|
|
393
|
+
* classifier reads `error.message`; `buildRawDetail` truncates to ≤500 chars.
|
|
394
|
+
* Returns `undefined` for an empty body so we don't pad `rawDetail` with noise.
|
|
395
|
+
* The body must NEVER reach user-facing `content`/`errorMessage`.
|
|
396
|
+
*/
|
|
397
|
+
function rawBodyDetail(body: unknown): { message: string } | undefined {
|
|
398
|
+
if (body == null) return undefined;
|
|
399
|
+
const text =
|
|
400
|
+
typeof body === "string" ? body : safeStringifyBody(body);
|
|
401
|
+
const trimmed = text.trim();
|
|
402
|
+
return trimmed ? { message: trimmed } : undefined;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function safeStringifyBody(body: unknown): string {
|
|
406
|
+
try {
|
|
407
|
+
return JSON.stringify(body);
|
|
408
|
+
} catch {
|
|
409
|
+
return String(body);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Build a {@link ToolExecutionResult} for a genuine backend/transport failure
|
|
415
|
+
* (5xx, post-retry rate-limit, thrown network/timeout error). Routes the raw
|
|
416
|
+
* detail through {@link classifyWebSearchFailure}: when it is a backend failure
|
|
417
|
+
* we surface the friendly recoverable copy (the bare sentence so the model
|
|
418
|
+
* reads it as guidance — retry / continue-without-search / paste-details —
|
|
419
|
+
* rather than fabricating) in both the model-facing `content` and the client
|
|
420
|
+
* `errorMessage`, and log the raw detail via telemetry. Non-backend categories
|
|
421
|
+
* (e.g. an unexpected 4xx) fall back to {@link errorResult} with `fallback`.
|
|
422
|
+
*
|
|
423
|
+
* Raw provider JSON / status text must never reach `content` or `errorMessage`;
|
|
424
|
+
* only `rawDetail` (internal-only) captures it for the log.
|
|
425
|
+
*/
|
|
426
|
+
function backendFailureResult(
|
|
427
|
+
query: string,
|
|
428
|
+
provider: WebSearchProvider,
|
|
429
|
+
startedAt: number,
|
|
430
|
+
raw: { error?: unknown; statusCode?: number; errorCode?: string },
|
|
431
|
+
fallback: string,
|
|
432
|
+
): ToolExecutionResult {
|
|
433
|
+
const classification = classifyWebSearchFailure({
|
|
434
|
+
isError: true,
|
|
435
|
+
error: raw.error,
|
|
436
|
+
statusCode: raw.statusCode,
|
|
437
|
+
errorCode: raw.errorCode,
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
if (!classification.isBackendFailure) {
|
|
441
|
+
return errorResult(query, provider, startedAt, fallback);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
logWebSearchBackendFailure(log, {
|
|
445
|
+
provider,
|
|
446
|
+
errorCategory: classification.category,
|
|
447
|
+
rawDetail: classification.rawDetail,
|
|
448
|
+
fallbackShown: true,
|
|
449
|
+
queryLength: query.length,
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
content: WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
|
|
454
|
+
isError: true,
|
|
455
|
+
activityMetadata: {
|
|
456
|
+
webSearch: {
|
|
457
|
+
query,
|
|
458
|
+
provider,
|
|
459
|
+
resultCount: 0,
|
|
460
|
+
durationMs: Date.now() - startedAt,
|
|
461
|
+
results: [],
|
|
462
|
+
errorMessage: WEB_SEARCH_BACKEND_FAILURE_MESSAGE,
|
|
463
|
+
},
|
|
464
|
+
},
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Route a thrown fetch error (network/timeout) through {@link backendFailureResult}
|
|
470
|
+
* as a `backend_unavailable` candidate, falling back to a `Web search failed: …`
|
|
471
|
+
* error for non-backend throws (e.g. a JSON parse error).
|
|
472
|
+
*
|
|
473
|
+
* If the caller aborted the request (`signal.aborted` — the user hit Stop/Esc,
|
|
474
|
+
* or an external caller cancelled), the thrown error is re-thrown so the
|
|
475
|
+
* executor's existing cancellation handling takes over. A user-cancel must NOT
|
|
476
|
+
* surface the friendly backend copy or emit `web_search_backend_failure`
|
|
477
|
+
* telemetry. Internal fetch timeouts (where the caller's signal is not aborted)
|
|
478
|
+
* still route to the friendly backend result.
|
|
479
|
+
*/
|
|
480
|
+
function networkFailureResult(
|
|
481
|
+
query: string,
|
|
482
|
+
provider: WebSearchProvider,
|
|
483
|
+
startedAt: number,
|
|
484
|
+
err: unknown,
|
|
485
|
+
signal?: AbortSignal,
|
|
486
|
+
): ToolExecutionResult {
|
|
487
|
+
if (signal?.aborted || isAbortReason((err as { reason?: unknown })?.reason)) {
|
|
488
|
+
throw err;
|
|
489
|
+
}
|
|
490
|
+
return backendFailureResult(
|
|
491
|
+
query,
|
|
492
|
+
provider,
|
|
493
|
+
startedAt,
|
|
494
|
+
{ error: err },
|
|
495
|
+
`Web search failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
|
|
384
499
|
async function executeBraveSearch(
|
|
385
500
|
query: string,
|
|
386
501
|
count: number,
|
|
@@ -396,21 +511,26 @@ async function executeBraveSearch(
|
|
|
396
511
|
const startedAt = Date.now();
|
|
397
512
|
|
|
398
513
|
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
514
|
+
let response: Response;
|
|
515
|
+
try {
|
|
516
|
+
response = await fetch(url, {
|
|
517
|
+
headers: {
|
|
518
|
+
Accept: "application/json",
|
|
519
|
+
"Accept-Encoding": "gzip",
|
|
520
|
+
"X-Subscription-Token": apiKey,
|
|
521
|
+
},
|
|
522
|
+
signal,
|
|
523
|
+
});
|
|
524
|
+
} catch (err) {
|
|
525
|
+
return networkFailureResult(query, "brave", startedAt, err, signal);
|
|
526
|
+
}
|
|
407
527
|
|
|
408
528
|
if (response.ok) {
|
|
409
529
|
const data = (await response.json()) as BraveSearchResponse;
|
|
410
530
|
return successfulBraveResult(data, query, startedAt);
|
|
411
531
|
}
|
|
412
532
|
|
|
413
|
-
await response.text();
|
|
533
|
+
const bodyText = await response.text();
|
|
414
534
|
|
|
415
535
|
if (response.status === 401 || response.status === 403) {
|
|
416
536
|
return errorResult(
|
|
@@ -436,20 +556,22 @@ async function executeBraveSearch(
|
|
|
436
556
|
}
|
|
437
557
|
|
|
438
558
|
log.warn({ status: response.status }, "Brave Search API error");
|
|
439
|
-
return
|
|
559
|
+
return backendFailureResult(
|
|
440
560
|
query,
|
|
441
561
|
"brave",
|
|
442
562
|
startedAt,
|
|
563
|
+
{ statusCode: response.status, error: rawBodyDetail(bodyText) },
|
|
443
564
|
response.status === 429
|
|
444
565
|
? "Brave Search rate limit exceeded after retries. Try again shortly."
|
|
445
566
|
: `Brave Search API returned status ${response.status}`,
|
|
446
567
|
);
|
|
447
568
|
}
|
|
448
569
|
|
|
449
|
-
return
|
|
570
|
+
return backendFailureResult(
|
|
450
571
|
query,
|
|
451
572
|
"brave",
|
|
452
573
|
startedAt,
|
|
574
|
+
{ statusCode: 429 },
|
|
453
575
|
"Brave Search rate limit exceeded after retries. Try again shortly.",
|
|
454
576
|
);
|
|
455
577
|
}
|
|
@@ -478,6 +600,23 @@ async function executeManagedBraveSearch(
|
|
|
478
600
|
);
|
|
479
601
|
|
|
480
602
|
if (!proxyResult.ok) {
|
|
603
|
+
// Keep billing/auth/unavailable mapping as specific copy; route genuine
|
|
604
|
+
// platform 5xx (transport-level failures) to the friendly backend helper.
|
|
605
|
+
if (
|
|
606
|
+
proxyResult.kind === "platform-error" &&
|
|
607
|
+
proxyResult.status >= 500
|
|
608
|
+
) {
|
|
609
|
+
return backendFailureResult(
|
|
610
|
+
query,
|
|
611
|
+
"brave",
|
|
612
|
+
startedAt,
|
|
613
|
+
{
|
|
614
|
+
statusCode: proxyResult.status,
|
|
615
|
+
error: rawBodyDetail(proxyResult.body),
|
|
616
|
+
},
|
|
617
|
+
managedSearchProxyErrorMessage(proxyResult),
|
|
618
|
+
);
|
|
619
|
+
}
|
|
481
620
|
return errorResult(
|
|
482
621
|
query,
|
|
483
622
|
"brave",
|
|
@@ -503,12 +642,18 @@ async function executeManagedBraveSearch(
|
|
|
503
642
|
);
|
|
504
643
|
}
|
|
505
644
|
|
|
506
|
-
if (proxyResult.status === 429) {
|
|
507
|
-
return
|
|
645
|
+
if (proxyResult.status === 429 || proxyResult.status >= 500) {
|
|
646
|
+
return backendFailureResult(
|
|
508
647
|
query,
|
|
509
648
|
"brave",
|
|
510
649
|
startedAt,
|
|
511
|
-
|
|
650
|
+
{
|
|
651
|
+
statusCode: proxyResult.status,
|
|
652
|
+
error: rawBodyDetail(proxyResult.body),
|
|
653
|
+
},
|
|
654
|
+
proxyResult.status === 429
|
|
655
|
+
? "Managed Brave Search rate limit exceeded. Try again shortly."
|
|
656
|
+
: `Managed Brave Search provider returned status ${proxyResult.status}`,
|
|
512
657
|
);
|
|
513
658
|
}
|
|
514
659
|
|
|
@@ -545,18 +690,23 @@ async function executePerplexitySearch(
|
|
|
545
690
|
): Promise<ToolExecutionResult> {
|
|
546
691
|
const startedAt = Date.now();
|
|
547
692
|
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
693
|
+
let response: Response;
|
|
694
|
+
try {
|
|
695
|
+
response = await fetch(PERPLEXITY_API_URL, {
|
|
696
|
+
method: "POST",
|
|
697
|
+
headers: {
|
|
698
|
+
"Content-Type": "application/json",
|
|
699
|
+
Authorization: `Bearer ${apiKey}`,
|
|
700
|
+
},
|
|
701
|
+
body: JSON.stringify({
|
|
702
|
+
model: "sonar",
|
|
703
|
+
messages: [{ role: "user", content: query }],
|
|
704
|
+
}),
|
|
705
|
+
signal,
|
|
706
|
+
});
|
|
707
|
+
} catch (err) {
|
|
708
|
+
return networkFailureResult(query, "perplexity", startedAt, err, signal);
|
|
709
|
+
}
|
|
560
710
|
|
|
561
711
|
if (response.ok) {
|
|
562
712
|
const data = (await response.json()) as PerplexityResponse;
|
|
@@ -574,7 +724,7 @@ async function executePerplexitySearch(
|
|
|
574
724
|
};
|
|
575
725
|
}
|
|
576
726
|
|
|
577
|
-
await response.text();
|
|
727
|
+
const bodyText = await response.text();
|
|
578
728
|
|
|
579
729
|
if (response.status === 401 || response.status === 403) {
|
|
580
730
|
return errorResult(
|
|
@@ -600,20 +750,22 @@ async function executePerplexitySearch(
|
|
|
600
750
|
}
|
|
601
751
|
|
|
602
752
|
log.warn({ status: response.status }, "Perplexity API error");
|
|
603
|
-
return
|
|
753
|
+
return backendFailureResult(
|
|
604
754
|
query,
|
|
605
755
|
"perplexity",
|
|
606
756
|
startedAt,
|
|
757
|
+
{ statusCode: response.status, error: rawBodyDetail(bodyText) },
|
|
607
758
|
response.status === 429
|
|
608
759
|
? "Perplexity rate limit exceeded after retries. Try again shortly."
|
|
609
760
|
: `Perplexity API returned status ${response.status}`,
|
|
610
761
|
);
|
|
611
762
|
}
|
|
612
763
|
|
|
613
|
-
return
|
|
764
|
+
return backendFailureResult(
|
|
614
765
|
query,
|
|
615
766
|
"perplexity",
|
|
616
767
|
startedAt,
|
|
768
|
+
{ statusCode: 429 },
|
|
617
769
|
"Perplexity rate limit exceeded after retries. Try again shortly.",
|
|
618
770
|
);
|
|
619
771
|
}
|
|
@@ -638,16 +790,21 @@ async function executeTavilySearch(
|
|
|
638
790
|
const startedAt = Date.now();
|
|
639
791
|
|
|
640
792
|
for (let attempt = 0; attempt <= DEFAULT_MAX_RETRIES; attempt++) {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
793
|
+
let response: Response;
|
|
794
|
+
try {
|
|
795
|
+
response = await fetch(TAVILY_API_URL, {
|
|
796
|
+
method: "POST",
|
|
797
|
+
headers: {
|
|
798
|
+
"Content-Type": "application/json",
|
|
799
|
+
Authorization: `Bearer ${apiKey}`,
|
|
800
|
+
"X-Client-Source": "vellum-assistant",
|
|
801
|
+
},
|
|
802
|
+
body: JSON.stringify(body),
|
|
803
|
+
signal,
|
|
804
|
+
});
|
|
805
|
+
} catch (err) {
|
|
806
|
+
return networkFailureResult(query, "tavily", startedAt, err, signal);
|
|
807
|
+
}
|
|
651
808
|
|
|
652
809
|
if (response.ok) {
|
|
653
810
|
const data = (await response.json()) as TavilySearchResponse;
|
|
@@ -665,7 +822,7 @@ async function executeTavilySearch(
|
|
|
665
822
|
};
|
|
666
823
|
}
|
|
667
824
|
|
|
668
|
-
await response.text();
|
|
825
|
+
const bodyText = await response.text();
|
|
669
826
|
|
|
670
827
|
if (response.status === 401 || response.status === 403) {
|
|
671
828
|
return errorResult(
|
|
@@ -691,20 +848,22 @@ async function executeTavilySearch(
|
|
|
691
848
|
}
|
|
692
849
|
|
|
693
850
|
log.warn({ status: response.status }, "Tavily Search API error");
|
|
694
|
-
return
|
|
851
|
+
return backendFailureResult(
|
|
695
852
|
query,
|
|
696
853
|
"tavily",
|
|
697
854
|
startedAt,
|
|
855
|
+
{ statusCode: response.status, error: rawBodyDetail(bodyText) },
|
|
698
856
|
response.status === 429
|
|
699
857
|
? "Tavily Search rate limit exceeded after retries. Try again shortly."
|
|
700
858
|
: `Tavily Search API returned status ${response.status}`,
|
|
701
859
|
);
|
|
702
860
|
}
|
|
703
861
|
|
|
704
|
-
return
|
|
862
|
+
return backendFailureResult(
|
|
705
863
|
query,
|
|
706
864
|
"tavily",
|
|
707
865
|
startedAt,
|
|
866
|
+
{ statusCode: 429 },
|
|
708
867
|
"Tavily Search rate limit exceeded after retries. Try again shortly.",
|
|
709
868
|
);
|
|
710
869
|
}
|
|
@@ -844,13 +1003,13 @@ export const webSearchTool = {
|
|
|
844
1003
|
signal: context.signal,
|
|
845
1004
|
});
|
|
846
1005
|
} catch (err) {
|
|
847
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
848
1006
|
log.error({ err }, "Managed web search failed");
|
|
849
|
-
return
|
|
1007
|
+
return networkFailureResult(
|
|
850
1008
|
query,
|
|
851
1009
|
"brave",
|
|
852
1010
|
startedAt,
|
|
853
|
-
|
|
1011
|
+
err,
|
|
1012
|
+
context.signal,
|
|
854
1013
|
);
|
|
855
1014
|
}
|
|
856
1015
|
}
|
|
@@ -897,13 +1056,13 @@ export const webSearchTool = {
|
|
|
897
1056
|
signal: context.signal,
|
|
898
1057
|
});
|
|
899
1058
|
} catch (err) {
|
|
900
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
901
1059
|
log.error({ err }, "Web search failed");
|
|
902
|
-
return
|
|
1060
|
+
return networkFailureResult(
|
|
903
1061
|
query,
|
|
904
1062
|
provider,
|
|
905
1063
|
startedAt,
|
|
906
|
-
|
|
1064
|
+
err,
|
|
1065
|
+
context.signal,
|
|
907
1066
|
);
|
|
908
1067
|
}
|
|
909
1068
|
},
|
|
@@ -144,6 +144,7 @@ export async function executeScheduleCreate(
|
|
|
144
144
|
maxRetries,
|
|
145
145
|
retryBackoffMs,
|
|
146
146
|
timeoutMs,
|
|
147
|
+
createdFromConversationId: context.conversationId,
|
|
147
148
|
});
|
|
148
149
|
|
|
149
150
|
const fireDate = formatLocalDate(job.nextRunAt);
|
|
@@ -225,6 +226,7 @@ export async function executeScheduleCreate(
|
|
|
225
226
|
maxRetries,
|
|
226
227
|
retryBackoffMs,
|
|
227
228
|
timeoutMs,
|
|
229
|
+
createdFromConversationId: context.conversationId,
|
|
228
230
|
});
|
|
229
231
|
|
|
230
232
|
const scheduleDescription =
|