@mycadet/assistant 0.12.7 → 0.12.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/README.md +1 -1
- package/docs/activation-funnel-telemetry.md +6 -6
- package/docs/cadet-doctor.md +7 -7
- package/docs/desktop-browser-cli.md +7 -7
- package/docs/skills.md +2 -2
- package/docs/voice-input-diagnostics.md +1 -1
- package/eslint-rules/__tests__/cli-no-daemon-internals.test.ts +2 -2
- package/node_modules/@mycadet/avatar-manifest/src/__tests__/notification-avatar.test.ts +1 -1
- package/node_modules/@mycadet/avatar-manifest/src/notification-avatar.ts +2 -2
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/ces-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/ces-client/src/rpc-client.ts +1 -1
- package/node_modules/@mycadet/credential-storage/src/oauth-runtime.ts +1 -1
- package/node_modules/@mycadet/credential-storage/src/static-credentials.ts +1 -1
- package/node_modules/@mycadet/environments/src/seeds.ts +6 -6
- package/node_modules/@mycadet/environments/src/shell.test.ts +1 -1
- package/node_modules/@mycadet/environments/src/types.ts +6 -6
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/gateway-client/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/binding-strength-contract.ts +5 -5
- package/node_modules/@mycadet/gateway-client/src/gateway-ipc-contracts.ts +1 -1
- package/node_modules/@mycadet/gateway-client/src/inbound-contract.ts +2 -2
- package/node_modules/@mycadet/gateway-client/src/outbound-contract.ts +1 -1
- package/node_modules/@mycadet/service-contracts/package.json +1 -1
- package/node_modules/@mycadet/service-contracts/src/{platform-credential.ts → cloud-credential.ts} +9 -9
- package/node_modules/@mycadet/service-contracts/src/handles.ts +3 -3
- package/node_modules/@mycadet/service-contracts/src/index.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/ingress.ts +11 -5
- package/node_modules/@mycadet/service-contracts/src/remote-web-pairing.ts +1 -1
- package/node_modules/@mycadet/service-contracts/src/rpc.ts +14 -14
- package/node_modules/@mycadet/service-contracts/src/trust-rules.ts +5 -5
- package/openapi.yaml +6815 -794
- package/package.json +2 -2
- package/scripts/build-test-fixtures.ts +1 -1
- package/scripts/probe-skill-retrieval.ts +1 -1
- package/scripts/sync-llm-catalog.ts +2 -2
- package/scripts/voice-ttft-spike.ts +2 -2
- package/src/__tests__/adaptive-thinking-repair.test.ts +4 -4
- package/src/__tests__/agent-loop-override-profile.test.ts +4 -4
- package/src/__tests__/agent-loop-subagent-billing-headers.test.ts +1 -1
- package/src/__tests__/anthropic-provider.test.ts +2 -2
- package/src/__tests__/available-skills.test.ts +11 -12
- package/src/__tests__/avatar-identity-sync.test.ts +1 -1
- package/src/__tests__/byok-default-profile-ensure.test.ts +7 -7
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-site-routing-connection-auto-resolve.test.ts +2 -2
- package/src/__tests__/catalog-files.test.ts +10 -10
- package/src/__tests__/catalog-install-normalize.test.ts +3 -3
- package/src/__tests__/channel-availability-routes.test.ts +8 -8
- package/src/__tests__/channel-readiness-routes.test.ts +4 -4
- package/src/__tests__/channel-readiness-service.test.ts +14 -14
- package/src/__tests__/channel-readiness-telegram-remote.test.ts +1 -1
- package/src/__tests__/chat-reveal-guard-priming.test.ts +5 -6
- package/src/__tests__/checker.test.ts +6 -6
- package/src/__tests__/clawhub.test.ts +1 -1
- package/src/__tests__/{platform-callback-registration.test.ts → cloud-callback-registration.test.ts} +66 -90
- package/src/__tests__/{platform-client-verify-credential.test.ts → cloud-client-verify-credential.test.ts} +14 -14
- package/src/__tests__/{managed-credential-catalog-cli.test.ts → cloud-credential-catalog-cli.test.ts} +41 -41
- package/src/__tests__/{managed-fallback-dispatch.test.ts → cloud-fallback-dispatch.test.ts} +21 -16
- package/src/__tests__/{managed-profile-guard.test.ts → cloud-profile-guard.test.ts} +60 -60
- package/src/__tests__/{platform-proxy-context.test.ts → cloud-proxy-context.test.ts} +44 -44
- package/src/__tests__/{managed-speech-defaults.test.ts → cloud-speech-defaults.test.ts} +41 -41
- package/src/__tests__/{managed-store.test.ts → cloud-store.test.ts} +99 -99
- package/src/__tests__/compaction-events.test.ts +7 -5
- package/src/__tests__/config-loader-backfill.test.ts +65 -65
- package/src/__tests__/config-loader-cloud-defaults.test.ts +373 -0
- package/src/__tests__/config-loader-corrupt.test.ts +1 -1
- package/src/__tests__/config-loader-validation-reset-notice.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +3 -3
- package/src/__tests__/{config-set-platform-guard.test.ts → config-set-cloud-guard.test.ts} +6 -6
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +0 -8
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/contacts-write.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop.test.ts +2 -2
- package/src/__tests__/conversation-app-control-instantiation.test.ts +18 -18
- package/src/__tests__/conversation-error.test.ts +45 -45
- package/src/__tests__/conversation-inference-profile-route.test.ts +4 -4
- package/src/__tests__/conversation-rendered-delegation-state.test.ts +4 -4
- package/src/__tests__/conversation-skill-tools.test.ts +7 -8
- package/src/__tests__/conversation-slash.test.ts +2 -2
- package/src/__tests__/conversation-speed-override.test.ts +8 -10
- package/src/__tests__/conversation-summarize-up-to.test.ts +4 -4
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -1
- package/src/__tests__/conversation-wait-for-idle.test.ts +7 -5
- package/src/__tests__/credential-execution-api-key-propagation.test.ts +5 -5
- package/src/__tests__/{credential-execution-managed-contract.test.ts → credential-execution-cloud-contract.test.ts} +24 -24
- package/src/__tests__/credential-health-service.test.ts +27 -41
- package/src/__tests__/credential-routes.test.ts +85 -165
- package/src/__tests__/credential-security-invariants.test.ts +6 -7
- package/src/__tests__/custom-profile-ensure.test.ts +1 -1
- package/src/__tests__/date-context.test.ts +1 -1
- package/src/__tests__/default-profile-catalog-fallback.test.ts +15 -15
- package/src/__tests__/delete-managed-skill-tool.test.ts +8 -8
- package/src/__tests__/discord-callback-round-trip.test.ts +1 -1
- package/src/__tests__/disk-usage.test.ts +3 -3
- package/src/__tests__/email-invite-adapter.test.ts +5 -5
- package/src/__tests__/{embedding-managed-proxy-selection.test.ts → embedding-cloud-proxy-selection.test.ts} +44 -43
- package/src/__tests__/emit-signal-routing-intent.test.ts +13 -15
- package/src/__tests__/events-dev-bypass-actor.test.ts +2 -2
- package/src/__tests__/fallback-breaker.test.ts +7 -7
- package/src/__tests__/feature-flag-test-helpers.ts +106 -0
- package/src/__tests__/gateway-flag-listener.test.ts +1 -1
- package/src/__tests__/gemini-image-service.test.ts +2 -2
- package/src/__tests__/gemini-provider.test.ts +5 -5
- package/src/__tests__/get-skill-detail-audit.test.ts +9 -8
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -1
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +5 -5
- package/src/__tests__/host-cu-routes-targeted.test.ts +8 -8
- package/src/__tests__/image-credentials.test.ts +13 -13
- package/src/__tests__/inbound-admitted-introduction-nudge.test.ts +14 -6
- package/src/__tests__/inbound-trust-verdict.test.ts +14 -6
- package/src/__tests__/inference-no-mode-boot-e2e.test.ts +11 -11
- package/src/__tests__/inference-profile-session-handler.test.ts +7 -7
- package/src/__tests__/inference-profile-session-ipc.test.ts +5 -5
- package/src/__tests__/inference-profile-validation-jev.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +40 -24
- package/src/__tests__/input-repairs.test.ts +1 -1
- package/src/__tests__/install-skill-routing.test.ts +10 -8
- package/src/__tests__/integration-status.test.ts +5 -5
- package/src/__tests__/internal-telemetry-routes.test.ts +3 -3
- package/src/__tests__/invite-routes-http.test.ts +1 -1
- package/src/__tests__/llm-catalog-parity.test.ts +5 -7
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -2
- package/src/__tests__/llm-resolver-override-or-default.test.ts +3 -3
- package/src/__tests__/llm-resolver.test.ts +1 -1
- package/src/__tests__/llm-schema-fallback-profile.test.ts +25 -19
- package/src/__tests__/llm-usage-store.test.ts +1 -1
- package/src/__tests__/managed-skill-lifecycle.test.ts +12 -12
- package/src/__tests__/mcp-abort-signal.test.ts +2 -2
- package/src/__tests__/mcp-client-auth.test.ts +2 -2
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +30 -30
- package/src/__tests__/memory-config-file-parity.test.ts +6 -6
- package/src/__tests__/migration-export-to-gcs.test.ts +4 -4
- package/src/__tests__/migration-import-commit-http.test.ts +1 -1
- package/src/__tests__/mtime-cache.test.ts +2 -2
- package/src/__tests__/{notification-platform-adapter.test.ts → notification-cloud-adapter.test.ts} +39 -39
- package/src/__tests__/notification-deep-link.test.ts +40 -40
- package/src/__tests__/oauth-apps-routes.test.ts +3 -3
- package/src/__tests__/oauth-cli.test.ts +9 -9
- package/src/__tests__/oauth-commands-routes.test.ts +16 -16
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -2
- package/src/__tests__/oauth-provider-serializer.test.ts +11 -11
- package/src/__tests__/oauth-providers-routes.test.ts +12 -12
- package/src/__tests__/oauth-store.test.ts +1 -1
- package/src/__tests__/oauth2-gateway-transport.test.ts +3 -3
- package/src/__tests__/onboarding-persona-write.test.ts +2 -2
- package/src/__tests__/openai-image-service.test.ts +2 -2
- package/src/__tests__/openai-responses-cutover-guard.test.ts +2 -2
- package/src/__tests__/openai-responses-provider.test.ts +1 -1
- package/src/__tests__/path-policy.test.ts +4 -4
- package/src/__tests__/personal-memory-auth-bypass.test.ts +1 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +18 -18
- package/src/__tests__/plugin-api-model-profiles.test.ts +1 -1
- package/src/__tests__/plugin-api-provider.test.ts +1 -1
- package/src/__tests__/plugin-api-webhook-url.test.ts +4 -5
- package/src/__tests__/plugin-import-boundary-guard.test.ts +8 -39
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +2 -2
- package/src/__tests__/{provider-platform-proxy-integration.test.ts → provider-cloud-proxy-integration.test.ts} +70 -72
- package/src/__tests__/provider-commit-message-generator.test.ts +1 -1
- package/src/__tests__/provider-connections-backfill.test.ts +9 -9
- package/src/__tests__/provider-error-scenarios.test.ts +8 -8
- package/src/__tests__/provider-registry-ollama.test.ts +1 -1
- package/src/__tests__/provider-send-message-override-profile.test.ts +5 -5
- package/src/__tests__/registry.test.ts +3 -3
- package/src/__tests__/requests-mock.ts +972 -0
- package/src/__tests__/resolve-trust-class.test.ts +1 -1
- package/src/__tests__/resource-pressure-guard.test.ts +6 -6
- package/src/__tests__/resource-pressure-routes.test.ts +5 -5
- package/src/__tests__/retry-fallback-escalation.test.ts +5 -5
- package/src/__tests__/sanitize-config-for-transfer.test.ts +1 -1
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +97 -97
- package/src/__tests__/schedule-store.test.ts +1 -1
- package/src/__tests__/search-skills-unified.test.ts +9 -7
- package/src/__tests__/{secret-routes-platform-proxy.test.ts → secret-routes-cloud-proxy.test.ts} +48 -95
- package/src/__tests__/secret-routes-scrub.test.ts +2 -59
- package/src/__tests__/secure-keys.test.ts +1 -1
- package/src/__tests__/skill-load-plugin-scope.test.ts +2 -2
- package/src/__tests__/skill-load-tool.test.ts +1 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -19
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +1 -1
- package/src/__tests__/skills-file-content-endpoint.test.ts +12 -12
- package/src/__tests__/skills-files-catalog-fallback.test.ts +9 -9
- package/src/__tests__/skills.test.ts +1 -1
- package/src/__tests__/slim-skill-category.test.ts +11 -9
- package/src/__tests__/stt-stream-session.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +1 -1
- package/src/__tests__/subagent-disposal.test.ts +5 -5
- package/src/__tests__/subagent-fork-notifications.test.ts +4 -4
- package/src/__tests__/subagent-fork-spawn.test.ts +5 -5
- package/src/__tests__/subagent-manager-notify.test.ts +5 -5
- package/src/__tests__/subagent-tool-gate-mode.test.ts +2 -2
- package/src/__tests__/system-prompt.test.ts +1 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +34 -52
- package/src/__tests__/telegram-config.test.ts +16 -16
- package/src/__tests__/telephony-tts-guard.test.ts +13 -13
- package/src/__tests__/test-preload.ts +17 -7
- package/src/__tests__/tool-audit.test.ts +1 -1
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +7 -5
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -1
- package/src/__tests__/turn-events-store.test.ts +1 -1
- package/src/__tests__/twilio-routes.test.ts +2 -1
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/unified-turn-context-client-os.test.ts +1 -1
- package/src/__tests__/validate-input.test.ts +5 -5
- package/src/__tests__/verification-outbound-delivery.test.ts +1 -1
- package/src/__tests__/voice-config-update.test.ts +15 -15
- package/src/__tests__/voice-ingress-preflight.test.ts +4 -4
- package/src/__tests__/web-search.test.ts +340 -859
- package/src/__tests__/workspace-git-service.test.ts +1 -1
- package/src/__tests__/workspace-migration-082-backfill-managed-profile-labels.test.ts +12 -12
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +1 -1
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +1 -1
- package/src/__tests__/workspace-migration-100-upgrade-quality-profile-to-fable-5.test.ts +1 -1
- package/src/__tests__/workspace-migration-103-upgrade-quality-profile-to-opus-4-8.test.ts +1 -1
- package/src/__tests__/workspace-migration-110-flip-balanced-profile-to-together.test.ts +1 -1
- package/src/__tests__/workspace-migration-113-swap-balanced-profile-to-glm-5p2.test.ts +1 -1
- package/src/__tests__/workspace-migration-114-swap-quality-profile-to-opus.test.ts +1 -1
- package/src/__tests__/workspace-migration-123-swap-quality-profile-to-fable.test.ts +1 -1
- package/src/__tests__/workspace-migration-125-repoint-managed-connections-to-cadet.test.ts +3 -3
- package/src/__tests__/workspace-migration-126-strip-managed-profile-bodies.test.ts +9 -9
- package/src/__tests__/workspace-migration-127-backfill-default-provider.test.ts +11 -11
- package/src/__tests__/workspace-migration-130-speech-mode-to-provider.test.ts +3 -3
- package/src/__tests__/workspace-migration-132-web-search-mode-to-provider.test.ts +6 -6
- package/src/__tests__/workspace-migration-133-collapse-provider-connections.test.ts +3 -3
- package/src/__tests__/workspace-migration-134-image-generation-mode-to-provider.test.ts +6 -6
- package/src/__tests__/workspace-migration-136-repair-stale-fireworks-kimi-model-id.test.ts +1 -1
- package/src/__tests__/workspace-migration-137-repair-retired-fireworks-minimax-model-id.test.ts +1 -1
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +1 -1
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +1 -1
- package/src/__tests__/workspace-migration-151-repair-renamed-fireworks-deepseek-pro-model-id.test.ts +1 -1
- package/src/__tests__/workspace-migration-152-repair-retired-fireworks-minimax-m2p7-model-id.test.ts +1 -1
- package/src/__tests__/workspace-migration-159-rename-colliding-jev-profile-name.test.ts +1 -1
- package/src/__tests__/workspace-migration-160-repair-retired-fireworks-model-ids.test.ts +1 -1
- package/src/__tests__/workspace-migration-161-repair-retired-fireworks-glm-5p2-model-id.test.ts +1 -1
- package/src/api/events/cloud-disconnected.ts +21 -0
- package/src/api/events/host-bash.ts +1 -1
- package/src/api/events/host-cu.ts +1 -1
- package/src/api/events/host-file.ts +1 -1
- package/src/api/events/notification-intent.ts +1 -1
- package/src/api/events/{show-platform-login.ts → show-cloud-login.ts} +3 -5
- package/src/api/index.ts +11 -11
- package/src/api/responses/home.ts +1 -1
- package/src/api/surfaces.ts +4 -4
- package/src/approvals/guardian-request-resolvers.ts +2 -2
- package/src/avatar/accent-backfill.ts +1 -1
- package/src/avatar/identity-avatar.ts +1 -1
- package/src/avatar/notification-avatar.test.ts +2 -2
- package/src/avatar/notification-avatar.ts +2 -2
- package/src/avatar/resvg-lazy.test.ts +1 -1
- package/src/avatar/resvg-lazy.ts +3 -3
- package/src/avatar/sync-avatar.test.ts +76 -76
- package/src/avatar/sync-avatar.ts +15 -15
- package/src/avatar/traits-png-sync.ts +1 -1
- package/src/background-wake/background-wake-routes.test.ts +39 -39
- package/src/background-wake/{platform-client.test.ts → cloud-client.test.ts} +11 -11
- package/src/background-wake/{platform-client.ts → cloud-client.ts} +14 -14
- package/src/background-wake/publisher.ts +4 -4
- package/src/background-wake/wake-intent-hooks.test.ts +3 -3
- package/src/calls/__tests__/call-controller.test.ts +2 -2
- package/src/calls/__tests__/voice-session-bridge.test.ts +1 -1
- package/src/calls/call-domain.ts +1 -1
- package/src/calls/resolve-call-tts-provider.ts +6 -6
- package/src/calls/telephony-credential-preflight.ts +3 -3
- package/src/calls/telephony-tts-capability.ts +5 -6
- package/src/calls/tts-call-strategy.ts +1 -1
- package/src/calls/voice-escalation-judge.ts +1 -1
- package/src/calls/voice-ingress-preflight.ts +4 -4
- package/src/cli/bundled-modules.ts +1 -1
- package/src/cli/commands/__tests__/apps.test.ts +18 -53
- package/src/cli/commands/__tests__/plugins.test.ts +13 -13
- package/src/cli/commands/__tests__/tts.test.ts +1 -1
- package/src/cli/commands/auth.help.ts +3 -3
- package/src/cli/commands/auth.ts +2 -4
- package/src/cli/commands/browser.help.ts +2 -2
- package/src/cli/commands/channels/index.help.ts +2 -2
- package/src/cli/commands/channels/index.ts +1 -1
- package/src/cli/commands/channels/send.ts +1 -1
- package/src/cli/commands/cloud/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/cloud/__tests__/disconnect.test.ts +3 -4
- package/src/cli/commands/cloud/connect.ts +2 -2
- package/src/cli/commands/cloud/credits-format.ts +1 -1
- package/src/cli/commands/cloud/index.help.ts +35 -35
- package/src/cli/commands/config.ts +1 -1
- package/src/cli/commands/credentials.ts +10 -12
- package/src/cli/commands/domain.ts +1 -1
- package/src/cli/commands/email.help.ts +1 -1
- package/src/cli/commands/email.ts +2 -2
- package/src/cli/commands/gateway.help.ts +1 -1
- package/src/cli/commands/gateway.ts +1 -1
- package/src/cli/commands/image-generation.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/inference.help.ts +2 -2
- package/src/cli/commands/memory/index.help.ts +1 -1
- package/src/cli/commands/oauth/connect.test.ts +2 -2
- package/src/cli/commands/oauth/connect.ts +1 -1
- package/src/cli/commands/oauth/index.help.ts +5 -5
- package/src/cli/commands/oauth/providers.ts +2 -2
- package/src/cli/commands/oauth/shared.ts +3 -3
- package/src/cli/commands/plugins.help.ts +2 -3
- package/src/cli/commands/plugins.ts +5 -5
- package/src/cli/commands/roadmap.help.ts +1 -1
- package/src/cli/commands/telemetry.help.ts +2 -2
- package/src/cli/commands/telemetry.ts +2 -2
- package/src/cli/commands/tts.help.ts +3 -3
- package/src/cli/commands/webhooks.help.ts +9 -9
- package/src/cli/commands/webhooks.ts +1 -1
- package/src/cli/lib/__tests__/inspect-plugin.test.ts +8 -8
- package/src/cli/lib/__tests__/{install-from-platform.test.ts → install-from-cloud.test.ts} +54 -54
- package/src/cli/lib/__tests__/plugin-catalog-cache.test.ts +18 -18
- package/src/cli/lib/__tests__/{plugin-catalog-platform.test.ts → plugin-catalog-cloud.test.ts} +2 -2
- package/src/cli/lib/__tests__/plugin-catalog-local.test.ts +1 -1
- package/src/cli/lib/__tests__/plugin-catalog-resolve.test.ts +9 -9
- package/src/cli/lib/__tests__/plugin-details.test.ts +10 -10
- package/src/cli/lib/__tests__/plugins-install-offline.test.ts +24 -24
- package/src/cli/lib/bundled-marketplace.json +1 -1
- package/src/cli/lib/inspect-plugin.ts +2 -2
- package/src/cli/lib/{install-from-platform.ts → install-from-cloud.ts} +29 -29
- package/src/cli/lib/install-from-github.ts +4 -4
- package/src/cli/lib/plugin-catalog-cache.ts +7 -7
- package/src/cli/lib/{plugin-catalog-platform.ts → plugin-catalog-cloud.ts} +6 -6
- package/src/cli/lib/plugin-catalog-local.ts +2 -2
- package/src/cli/lib/publish-plugin.ts +9 -10
- package/src/cli/lib/search-plugins.ts +2 -2
- package/src/{platform → cloud}/client.test.ts +85 -86
- package/src/{platform → cloud}/client.ts +38 -46
- package/src/{platform/platform-patch-queue.test.ts → cloud/cloud-patch-queue.test.ts} +6 -9
- package/src/{platform/platform-patch-queue.ts → cloud/cloud-patch-queue.ts} +11 -11
- package/src/{platform/managed-speech.test.ts → cloud/cloud-speech.test.ts} +19 -19
- package/src/{platform/managed-speech.ts → cloud/cloud-speech.ts} +41 -41
- package/src/{platform → cloud}/consent-cache.test.ts +5 -5
- package/src/{platform → cloud}/consent-cache.ts +8 -8
- package/src/cloud/feature-gate.ts +8 -0
- package/src/{platform → cloud}/sync-identity.test.ts +4 -4
- package/src/{platform → cloud}/sync-identity.ts +6 -6
- package/src/config/__tests__/{platform-identity.test.ts → cloud-identity.test.ts} +54 -56
- package/src/config/__tests__/{platform-rehydration.test.ts → cloud-rehydration.test.ts} +47 -80
- package/src/config/__tests__/default-profile-catalog.test.ts +12 -12
- package/src/config/__tests__/default-provider.test.ts +4 -4
- package/src/config/__tests__/deployment-context-defaults.test.ts +23 -23
- package/src/config/__tests__/{jev-managed-profile.test.ts → jev-cloud-profile.test.ts} +3 -5
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +2 -2
- package/src/config/__tests__/loader-last-known-good.test.ts +6 -6
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +2 -2
- package/src/config/__tests__/profile-materialization.test.ts +7 -7
- package/src/config/__tests__/seed-default-provider.test.ts +17 -17
- package/src/config/__tests__/sync-gated-profiles.test.ts +20 -20
- package/src/config/__tests__/webhook-routing.test.ts +51 -51
- package/src/config/bundled-skills/computer-use/TOOLS.json +12 -12
- package/src/config/bundled-skills/messaging/SKILL.md +8 -8
- package/src/config/bundled-skills/messaging/TOOLS.json +2 -2
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +1 -1
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -2
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +12 -12
- package/src/config/bundled-skills/skill-management/SKILL.md +3 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +8 -8
- package/src/config/bundled-skills/skill-management/tools/{delete-managed.ts → delete-cloud.ts} +2 -2
- package/src/config/bundled-skills/skill-management/tools/{scaffold-managed.ts → scaffold-cloud.ts} +2 -2
- package/src/config/call-site-defaults.ts +6 -6
- package/src/config/{platform-identity.ts → cloud-identity.ts} +39 -42
- package/src/config/cloud-rehydration.ts +35 -0
- package/src/config/{managed-speech-defaults.ts → cloud-speech-defaults.ts} +13 -13
- package/src/config/default-profile-catalog.ts +36 -38
- package/src/config/default-profile-names.ts +11 -11
- package/src/config/default-provider-resolution.ts +2 -2
- package/src/config/default-provider.ts +1 -1
- package/src/config/env-registry.ts +23 -21
- package/src/config/env.ts +33 -46
- package/src/config/feature-flag-registry.json +5 -5
- package/src/config/inference-profile-validation.ts +1 -1
- package/src/config/llm-resolver.ts +9 -10
- package/src/config/loader.ts +33 -17
- package/src/config/profile-materialization.ts +8 -8
- package/src/config/profile-text-generation.test.ts +1 -1
- package/src/config/sanitize-for-transfer.ts +1 -1
- package/src/config/schema.ts +6 -6
- package/src/config/schemas/__tests__/stt.test.ts +2 -2
- package/src/config/schemas/call-site-catalog.ts +1 -1
- package/src/config/schemas/channels.ts +8 -0
- package/src/config/schemas/{platform.ts → cloud.ts} +7 -6
- package/src/config/schemas/host-browser.ts +1 -0
- package/src/config/schemas/ingress.ts +1 -12
- package/src/config/schemas/llm.ts +45 -36
- package/src/config/schemas/memory-lifecycle.ts +11 -1
- package/src/config/schemas/services.ts +4 -4
- package/src/config/schemas/stt.ts +3 -3
- package/src/config/schemas/tts.ts +10 -5
- package/src/config/schemas/voice.ts +1 -1
- package/src/config/seed-inference-profiles.ts +19 -19
- package/src/config/skill-state.ts +2 -2
- package/src/config/skills.ts +4 -4
- package/src/config/sync-gated-profiles.ts +8 -8
- package/src/config/webhook-routing.ts +19 -19
- package/src/contacts/contacts-write.ts +1 -1
- package/src/credential-execution/ces-runtime.ts +6 -6
- package/src/credential-execution/{managed-catalog.ts → cloud-catalog.ts} +24 -24
- package/src/credential-execution/prompted-credential.ts +6 -18
- package/src/credential-health/credential-health-service.ts +61 -76
- package/src/daemon/__tests__/can-spawn-subagents.test.ts +1 -1
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +4 -4
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/conversation-agent-loop.ts +9 -9
- package/src/daemon/conversation-error.ts +36 -36
- package/src/daemon/conversation-skill-tools.ts +7 -7
- package/src/daemon/conversation-surfaces.ts +1 -1
- package/src/daemon/conversation-tool-setup.ts +2 -2
- package/src/daemon/conversation-turn-finalize.ts +1 -1
- package/src/daemon/conversation-workspace.ts +1 -1
- package/src/daemon/conversation.ts +1 -1
- package/src/daemon/credential-transcript-scrub.ts +21 -35
- package/src/daemon/daemon-readiness.ts +1 -1
- package/src/daemon/event-loop-watchdog.ts +2 -2
- package/src/daemon/handlers/config-ingress.ts +9 -9
- package/src/daemon/handlers/config-telegram.ts +6 -6
- package/src/daemon/handlers/skills.ts +20 -20
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +19 -19
- package/src/daemon/parse-actual-tokens-from-error.ts +3 -3
- package/src/daemon/providers-setup.ts +6 -6
- package/src/daemon/resource-pressure-guard-lifecycle.ts +1 -1
- package/src/daemon/resource-pressure-guard.ts +6 -6
- package/src/daemon/shutdown-handlers.ts +1 -1
- package/src/daemon/sqlite-corruption-watchdog.ts +3 -3
- package/src/daemon/trust-context.ts +1 -1
- package/src/desktop/desktop-automation-lease.ts +1 -1
- package/src/desktop/desktop-browser-endpoint.ts +2 -4
- package/src/desktop/desktop-computer-use-routing.test.ts +6 -6
- package/src/desktop/desktop-stream-bridge.test.ts +4 -4
- package/src/desktop/desktop-stream-bridge.ts +1 -1
- package/src/desktop/{virtual-desktop-platform.test.ts → virtual-desktop-cloud.test.ts} +6 -6
- package/src/desktop/virtual-desktop-feature.ts +7 -7
- package/src/email/byo-email-credential.ts +1 -1
- package/src/email/registered-inbox.test.ts +16 -16
- package/src/email/registered-inbox.ts +20 -20
- package/src/heartbeat/heartbeat-service.ts +1 -1
- package/src/hooks/hook-loader.ts +2 -2
- package/src/inbound/__tests__/oauth-callback-url.test.ts +2 -2
- package/src/inbound/__tests__/tunnel-probe.test.ts +8 -8
- package/src/inbound/{platform-callback-registration.ts → cloud-callback-registration.ts} +49 -60
- package/src/inbound/oauth-callback-url.ts +6 -6
- package/src/inbound/pod-webhook-claim.ts +2 -2
- package/src/inbound/public-ingress-urls.ts +3 -3
- package/src/inbound/tunnel-probe.ts +21 -15
- package/src/inbound/tunnel-webhooks-gate.ts +3 -3
- package/src/ipc/__tests__/db-readiness-ipc-gate.test.ts +1 -1
- package/src/ipc/__tests__/email-ipc.test.ts +8 -8
- package/src/ipc/gateway-client.test.ts +55 -59
- package/src/ipc/gateway-flag-listener.ts +3 -3
- package/src/ipc/routes/db-proxy.ts +1 -1
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +2 -2
- package/src/live-voice/__tests__/{live-voice-managed-speech-fallback.test.ts → live-voice-cloud-speech-fallback.test.ts} +15 -15
- package/src/live-voice/__tests__/live-voice-credential-preflight.test.ts +7 -7
- package/src/live-voice/__tests__/live-voice-stt.test.ts +1 -1
- package/src/live-voice/live-activity-reporter.ts +7 -7
- package/src/live-voice/live-voice-credential-preflight.ts +6 -6
- package/src/live-voice/live-voice-session.ts +3 -3
- package/src/live-voice/live-voice-tts.ts +1 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +4 -4
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +1 -1
- package/src/mcp/mcp-oauth-provider.ts +2 -2
- package/src/media/gemini-image-service.ts +11 -13
- package/src/media/image-credentials.ts +9 -9
- package/src/media/openai-image-service.ts +1 -1
- package/src/media/types.ts +3 -3
- package/src/messaging/providers/channel-transport.ts +1 -1
- package/src/messaging/providers/discord/transport.ts +1 -1
- package/src/messaging/providers/gmail/adapter.ts +1 -1
- package/src/messaging/providers/gmail/client.ts +4 -4
- package/src/messaging/providers/outlook/adapter.ts +1 -1
- package/src/messaging/providers/slack/adapter.ts +1 -1
- package/src/messaging/providers/slack/transport.ts +1 -1
- package/src/messaging/providers/telegram-bot/transport.ts +1 -1
- package/src/monitoring/db-integrity-check.ts +1 -1
- package/src/monitoring/db-integrity-sample.ts +2 -2
- package/src/monitoring/mount-watch.ts +1 -1
- package/src/monitoring/oom-kill-reporter.ts +1 -1
- package/src/monitoring/worker.ts +5 -5
- package/src/notifications/README.md +2 -2
- package/src/notifications/__tests__/broadcaster.test.ts +9 -9
- package/src/notifications/__tests__/completion-policy.test.ts +1 -1
- package/src/notifications/__tests__/emit-signal-pipeline-failure.test.ts +4 -4
- package/src/notifications/activity-failed-dedupe.ts +1 -1
- package/src/notifications/adapters/{platform.ts → cloud.ts} +8 -8
- package/src/notifications/broadcaster.ts +10 -10
- package/src/notifications/decision-engine.ts +1 -1
- package/src/notifications/destination-resolver.ts +1 -1
- package/src/notifications/emit-signal.ts +6 -6
- package/src/notifications/types.ts +2 -2
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -1
- package/src/oauth/__tests__/{seed-providers-managed.test.ts → seed-providers-cloud.test.ts} +13 -13
- package/src/oauth/byo-connection.test.ts +2 -2
- package/src/oauth/{platform-connection.test.ts → cloud-connection.test.ts} +41 -43
- package/src/oauth/{platform-connection.ts → cloud-connection.ts} +19 -19
- package/src/oauth/connection-resolver.test.ts +59 -61
- package/src/oauth/connection-resolver.ts +22 -22
- package/src/oauth/connection.ts +1 -1
- package/src/oauth/oauth-store.ts +8 -8
- package/src/oauth/provider-serializer.ts +4 -4
- package/src/oauth/revoke.ts +1 -1
- package/src/oauth/seed-providers.ts +32 -32
- package/src/permissions/approval-provenance.ts +1 -1
- package/src/permissions/checker.test.ts +18 -16
- package/src/permissions/checker.ts +3 -3
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/embeddings/embedding-backend.test.ts +16 -16
- package/src/persistence/embeddings/embedding-backend.ts +13 -13
- package/src/persistence/embeddings/embedding-gemini.test.ts +9 -12
- package/src/persistence/embeddings/embedding-gemini.ts +11 -11
- package/src/persistence/embeddings/embedding-runtime-manager.ts +2 -2
- package/src/persistence/jobs-store.ts +1 -1
- package/src/persistence/lifecycle-events-store.ts +1 -1
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/migrations/224-oauth-providers-managed-service-is-paid.ts +1 -1
- package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.test.ts +1 -1
- package/src/persistence/migrations/296-rewrite-balanced-economy-profile-pins.ts +2 -2
- package/src/persistence/migrations/306-rewrite-frontier-profile-pins.test.ts +1 -1
- package/src/persistence/migrations/306-rewrite-frontier-profile-pins.ts +1 -1
- package/src/persistence/migrations/351-schedule-skill-script-handoff.ts +1 -1
- package/src/persistence/migrations/361-normalize-managed-connection-rows.test.ts +3 -3
- package/src/persistence/migrations/361-normalize-managed-connection-rows.ts +7 -7
- package/src/persistence/migrations/__tests__/319-remove-legacy-managed-connections.test.ts +2 -2
- package/src/persistence/schema/oauth.ts +1 -1
- package/src/plugin-api/index.ts +4 -4
- package/src/plugin-api/model-profiles.test.ts +1 -1
- package/src/plugin-api/model-profiles.ts +1 -1
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +12 -12
- package/src/plugins/__tests__/source-fingerprint.test.ts +1 -1
- package/src/plugins/defaults/{platform-hosted → cloud-hosted}/__tests__/reengage.test.ts +5 -6
- package/src/plugins/defaults/cloud-hosted/package.json +14 -0
- package/src/plugins/defaults/{platform-hosted → cloud-hosted}/routes/reengage.ts +3 -3
- package/src/plugins/defaults/index.ts +6 -6
- package/src/plugins/defaults/main.ts +5 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +5 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +1 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +1 -1
- package/src/plugins/defaults/memory/config.ts +4 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +2 -2
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +7 -7
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +5 -5
- package/src/plugins/defaults/memory/memory-retrospective-skill-card.ts +2 -2
- package/src/plugins/defaults/memory/src/__tests__/memory-v2-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/src/__tests__/memory-worker-routes.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/boot-maintenance.test.ts +20 -20
- package/src/plugins/defaults/memory/substrate/__tests__/cli-command-store.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-store.test.ts +3 -3
- package/src/plugins/defaults/memory/substrate/boot-maintenance.ts +17 -17
- package/src/plugins/defaults/memory/substrate/cli-command-store.ts +2 -2
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +1 -1
- package/src/plugins/defaults/memory/substrate/skill-store.ts +4 -4
- package/src/plugins/defaults/memory/v3/__tests__/maintain-job.test.ts +3 -3
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +2 -2
- package/src/plugins/defaults/memory/v3/maintain-job.ts +10 -10
- package/src/plugins/defaults/memory/v3/orchestrate.ts +3 -3
- package/src/plugins/defaults/memory/worker.ts +5 -5
- package/src/plugins/defaults/workspace/injectors.ts +2 -2
- package/src/plugins/mtime-cache.ts +1 -1
- package/src/plugins/source-versions.ts +1 -1
- package/src/plugins/types.ts +1 -1
- package/src/prompts/__tests__/system-prompt.test.ts +1 -1
- package/src/prompts/normalize-onboarding.ts +1 -1
- package/src/prompts/persona-resolver.ts +1 -1
- package/src/prompts/templates/system-sections.ts +3 -3
- package/src/providers/__tests__/cadet-connection-routing.test.ts +7 -7
- package/src/providers/__tests__/cadet-mismatch-routing.test.ts +12 -12
- package/src/providers/__tests__/connection-model-compat.test.ts +2 -2
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +10 -10
- package/src/providers/__tests__/inference.test.ts +2 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +16 -16
- package/src/providers/__tests__/retry-callsite.test.ts +1 -1
- package/src/providers/__tests__/satellite-connection-routing.test.ts +4 -4
- package/src/providers/__tests__/search-provider-catalog.test.ts +2 -2
- package/src/providers/__tests__/voice-error-copy.test.ts +3 -3
- package/src/providers/anthropic/client.ts +1 -1
- package/src/providers/cadet/client.ts +1 -1
- package/src/providers/cadet-model-routing.test.ts +19 -19
- package/src/providers/cadet-model-routing.ts +23 -23
- package/src/providers/call-site-routing.ts +10 -10
- package/src/providers/{platform-proxy → cloud-proxy}/constants.ts +11 -11
- package/src/providers/{platform-proxy → cloud-proxy}/context.ts +26 -26
- package/src/providers/connection-resolution.ts +43 -43
- package/src/providers/fallback-breaker.ts +2 -2
- package/src/providers/fetch-provider-catalog.ts +3 -3
- package/src/providers/gemini/client.ts +5 -5
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +3 -3
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +8 -8
- package/src/providers/inference/__tests__/base-url-security.test.ts +4 -4
- package/src/providers/inference/adapter-factory.ts +35 -35
- package/src/providers/inference/auth.ts +10 -10
- package/src/providers/inference/backfill.ts +11 -11
- package/src/providers/inference/connection-availability.ts +24 -24
- package/src/providers/inference/connections.ts +18 -20
- package/src/providers/inference/profile-probe.ts +4 -4
- package/src/providers/inference/resolve-auth.ts +8 -8
- package/src/providers/jev/ask.test.ts +1 -1
- package/src/providers/jev/ask.ts +2 -2
- package/src/providers/model-catalog.ts +8 -9
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +3 -3
- package/src/providers/openai/api-error-normalization.ts +2 -2
- package/src/providers/provider-availability.ts +4 -4
- package/src/providers/provider-env-vars.ts +1 -1
- package/src/providers/provider-secret-catalog.ts +1 -1
- package/src/providers/registry.ts +20 -20
- package/src/providers/retry.ts +26 -26
- package/src/providers/routing-identity.ts +10 -10
- package/src/providers/search-provider-catalog.ts +2 -2
- package/src/providers/speech-to-text/__tests__/{cadet-managed-realtime.test.ts → cadet-cloud-realtime.test.ts} +3 -3
- package/src/providers/speech-to-text/__tests__/{cadet-managed.test.ts → cadet-cloud.test.ts} +35 -35
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +1 -1
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +27 -27
- package/src/providers/speech-to-text/{cadet-managed-flux-realtime.ts → cadet-cloud-flux-realtime.ts} +6 -8
- package/src/providers/speech-to-text/{cadet-managed-realtime.ts → cadet-cloud-realtime.ts} +3 -3
- package/src/providers/speech-to-text/{cadet-managed.ts → cadet-cloud.ts} +18 -20
- package/src/providers/speech-to-text/cadet-speech-relay-connection.ts +7 -7
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +3 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +3 -3
- package/src/providers/speech-to-text/resolve.ts +26 -28
- package/src/providers/together/client.ts +1 -1
- package/src/providers/types.ts +4 -4
- package/src/providers/voice-error-copy.ts +1 -1
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +10 -10
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/auth/__tests__/middleware.test.ts +1 -1
- package/src/runtime/auth/middleware.ts +1 -1
- package/src/runtime/auth/scopes.ts +1 -1
- package/src/runtime/capabilities.ts +1 -1
- package/src/runtime/channel-invite-transports/discord.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +2 -2
- package/src/runtime/channel-readiness-service.ts +15 -13
- package/src/runtime/channel-reply-session.test.ts +1 -1
- package/src/runtime/channel-reply-session.ts +1 -1
- package/src/runtime/http-server.ts +4 -4
- package/src/runtime/introduction-policy.ts +4 -4
- package/src/runtime/migrations/__tests__/origin-mode.test.ts +14 -14
- package/src/runtime/migrations/__tests__/v1-test-helpers.ts +1 -1
- package/src/runtime/migrations/__tests__/vbundle-import-version-compat.test.ts +2 -2
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +3 -4
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +6 -7
- package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +1 -1
- package/src/runtime/migrations/migration-transport.ts +3 -3
- package/src/runtime/migrations/origin-mode.ts +6 -9
- package/src/runtime/migrations/vbundle-import-policy.ts +2 -2
- package/src/runtime/migrations/vbundle-importer.ts +4 -4
- package/src/runtime/migrations/vbundle-streaming-importer.ts +6 -6
- package/src/runtime/migrations/vbundle-validator.ts +1 -1
- package/src/runtime/routes/__tests__/{platform-credits-routes.test.ts → cloud-credits-routes.test.ts} +9 -9
- package/src/runtime/routes/__tests__/{platform-invoice-routes.test.ts → cloud-invoice-routes.test.ts} +13 -13
- package/src/runtime/routes/__tests__/connection-routes-vs-cli-parity.test.ts +1 -1
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +26 -25
- package/src/runtime/routes/__tests__/default-plugin-routes.test.ts +5 -5
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +10 -10
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +18 -16
- package/src/runtime/routes/__tests__/inference-send-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/migration-cadet-metadata-reconcile.test.ts +5 -20
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -22
- package/src/runtime/routes/__tests__/oauth-proxy-routes.test.ts +27 -27
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +5 -5
- package/src/runtime/routes/__tests__/ps-routes.test.ts +5 -3
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +15 -15
- package/src/runtime/routes/__tests__/webhook-routes.test.ts +31 -31
- package/src/runtime/routes/app-management-routes.ts +1 -1
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/auth-routes.ts +14 -14
- package/src/runtime/routes/avatar-routes.ts +0 -14
- package/src/runtime/routes/background-wake-routes.ts +4 -4
- package/src/runtime/routes/channel-availability-routes.ts +4 -4
- package/src/runtime/routes/client-routes.ts +5 -5
- package/src/runtime/routes/cloud-credentials.ts +45 -0
- package/src/runtime/routes/{platform-routes.ts → cloud-routes.ts} +83 -116
- package/src/runtime/routes/conversation-compaction-routes.ts +1 -1
- package/src/runtime/routes/conversation-query-routes.ts +82 -282
- package/src/runtime/routes/credential-routes.ts +28 -30
- package/src/runtime/routes/default-provider-routes.ts +1 -1
- package/src/runtime/routes/desktop-setup-routes.test.ts +5 -5
- package/src/runtime/routes/desktop-setup-routes.ts +1 -1
- package/src/runtime/routes/domain-routes.ts +15 -21
- package/src/runtime/routes/email-routes.ts +13 -13
- package/src/runtime/routes/errors.ts +1 -1
- package/src/runtime/routes/gateway-status-routes.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +3 -3
- package/src/runtime/routes/inbound-message-handler.ts +1 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +1 -1
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +1 -1
- package/src/runtime/routes/index.ts +1 -1
- package/src/runtime/routes/inference-profile-availability-guard.ts +5 -5
- package/src/runtime/routes/inference-profiles-routes.ts +11 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +24 -24
- package/src/runtime/routes/ingress-status-routes.ts +1 -1
- package/src/runtime/routes/integrations/a2a.ts +2 -2
- package/src/runtime/routes/integrations/twilio.ts +21 -35
- package/src/runtime/routes/internal-telemetry-routes.ts +2 -2
- package/src/runtime/routes/live-voice-routes.test.ts +4 -4
- package/src/runtime/routes/live-voice-routes.ts +6 -6
- package/src/runtime/routes/migration-routes.ts +34 -40
- package/src/runtime/routes/oauth-commands-routes.ts +31 -31
- package/src/runtime/routes/oauth-providers.ts +5 -5
- package/src/runtime/routes/oauth-proxy-passthrough.test.ts +1 -1
- package/src/runtime/routes/oauth-proxy-passthrough.ts +8 -8
- package/src/runtime/routes/oauth-proxy-routes.ts +15 -17
- package/src/runtime/routes/oauth-request-hints.test.ts +1 -1
- package/src/runtime/routes/oauth-request-hints.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +9 -9
- package/src/runtime/routes/profiler-routes.ts +1 -1
- package/src/runtime/routes/ps-routes.ts +4 -0
- package/src/runtime/routes/roadmap-routes.ts +18 -20
- package/src/runtime/routes/secret-routes.ts +29 -55
- package/src/runtime/routes/settings-routes.ts +18 -23
- package/src/runtime/routes/telemetry-routes.ts +4 -4
- package/src/runtime/routes/tts-routes.ts +4 -4
- package/src/runtime/routes/user-route-resolution.ts +1 -1
- package/src/runtime/routes/webhook-routes.ts +18 -18
- package/src/runtime/routes/workflow-routes.ts +5 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -2
- package/src/runtime/verification-outbound-actions.ts +4 -4
- package/src/schedule/integration-status.ts +6 -6
- package/src/schedule/run-script.ts +1 -1
- package/src/schedule/schedule-timezone.ts +1 -1
- package/src/schedule/worker.ts +5 -5
- package/src/security/auth-fallback-events-store.ts +1 -1
- package/src/security/oauth2.ts +4 -4
- package/src/skills/available-skills.ts +4 -4
- package/src/skills/catalog-files.ts +8 -8
- package/src/skills/catalog-install.ts +12 -12
- package/src/skills/categories-cache.ts +3 -3
- package/src/skills/clawhub.ts +1 -1
- package/src/skills/{platform-compatibility.test.ts → cloud-compatibility.test.ts} +7 -7
- package/src/skills/{platform-compatibility.ts → cloud-compatibility.ts} +2 -4
- package/src/skills/{managed-store.ts → cloud-store.ts} +21 -23
- package/src/skills/inline-command-runner.ts +1 -1
- package/src/skills/install-meta.ts +1 -1
- package/src/skills/path-classifier.ts +4 -4
- package/src/skills/skill-history.ts +4 -4
- package/src/skills/skillssh-registry.ts +1 -1
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +1 -1
- package/src/stt/daemon-batch-transcriber.ts +7 -7
- package/src/stt/types.ts +3 -3
- package/src/subagent/manager.ts +15 -15
- package/src/subagent/types.ts +1 -1
- package/src/telegram/__tests__/webhook-health.test.ts +16 -16
- package/src/telegram/webhook-health.ts +10 -10
- package/src/telemetry/__tests__/config-setting-events-store.test.ts +1 -1
- package/src/telemetry/__tests__/outbox-test-harness.ts +2 -2
- package/src/telemetry/config-setting-events-store.ts +1 -1
- package/src/telemetry/config-setting-snapshot.ts +1 -1
- package/src/telemetry/memory-corpus-size.ts +1 -1
- package/src/telemetry/memory-tier-reporter.test.ts +2 -2
- package/src/telemetry/memory-tier-reporter.ts +3 -3
- package/src/telemetry/telemetry-event-sources.test.ts +2 -2
- package/src/telemetry/telemetry-event-sources.ts +9 -9
- package/src/telemetry/telemetry-events-outbox.ts +2 -2
- package/src/telemetry/telemetry-wire-validation.test.ts +1 -1
- package/src/telemetry/telemetry-wire-validation.ts +3 -3
- package/src/telemetry/telemetry-wire.generated.ts +1 -1
- package/src/telemetry/tool-audit.ts +1 -1
- package/src/telemetry/tool-executed-events-store.test.ts +1 -1
- package/src/telemetry/trace-collection-policy.ts +4 -4
- package/src/telemetry/turn-events-store.ts +1 -1
- package/src/telemetry/turn-outcome.ts +1 -1
- package/src/telemetry/turn-trace-store.ts +2 -2
- package/src/telemetry/types.test.ts +1 -1
- package/src/telemetry/types.ts +19 -19
- package/src/telemetry/usage-telemetry-reporter.test.ts +42 -42
- package/src/telemetry/usage-telemetry-reporter.ts +17 -17
- package/src/telemetry/watchdog-direct-emit.test.ts +9 -9
- package/src/telemetry/watchdog-direct-emit.ts +10 -10
- package/src/tools/__tests__/side-effect-abort-guard.test.ts +2 -2
- package/src/tools/browser/cdp-client/factory.ts +1 -1
- package/src/tools/computer-use/target.ts +2 -2
- package/src/tools/credentials/store.ts +6 -15
- package/src/tools/network/__tests__/{managed-search-proxy.test.ts → cloud-search-proxy.test.ts} +20 -20
- package/src/tools/network/__tests__/web-search.test.ts +14 -14
- package/src/tools/network/{managed-search-proxy.ts → cloud-search-proxy.ts} +13 -13
- package/src/tools/network/web-search.ts +9 -9
- package/src/tools/permission-checker.ts +2 -2
- package/src/tools/shared/input-misuse.ts +1 -1
- package/src/tools/shared/input-repairs.ts +4 -4
- package/src/tools/skills/{delete-managed.ts → delete-cloud.ts} +4 -4
- package/src/tools/skills/find-similar-skills.test.ts +7 -7
- package/src/tools/skills/find-similar-skills.ts +15 -15
- package/src/tools/skills/load.ts +6 -6
- package/src/tools/skills/{scaffold-managed.ts → scaffold-cloud.ts} +16 -16
- package/src/tools/skills/skill-script-runner.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -3
- package/src/tools/tool-types.ts +2 -2
- package/src/tools/types.ts +2 -2
- package/src/tools/ui-surface/surface-shape-docs.ts +2 -2
- package/src/tools/workflows/manage-workflows.ts +1 -1
- package/src/tools/workspace-tools/loader.ts +1 -1
- package/src/tts/__tests__/cadet-provider.test.ts +5 -5
- package/src/tts/provider-catalog.ts +1 -1
- package/src/tts/providers/cadet-provider.ts +23 -23
- package/src/tts/providers/cadet-tts-socket.ts +2 -2
- package/src/tts/tts-config-resolver.ts +1 -1
- package/src/tts/tts-voice-field.ts +3 -3
- package/src/usage/subagent-attribution.ts +1 -1
- package/src/util/__tests__/process-table.test.ts +47 -0
- package/src/util/canonicalize-identity.ts +1 -1
- package/src/util/cgroup-cpu.ts +5 -5
- package/src/util/cgroup-memory.ts +2 -2
- package/src/util/container-cpu-sampler.ts +4 -4
- package/src/util/disk-usage.ts +4 -4
- package/src/util/errors.ts +3 -3
- package/src/util/oom-priority.ts +1 -1
- package/src/util/platform.ts +5 -5
- package/src/util/process-table.ts +39 -5
- package/src/util/provider-error-patterns.ts +2 -2
- package/src/watch/__tests__/watch-retro.test.ts +2 -2
- package/src/watch/watch-retro.ts +1 -1
- package/src/watcher/__tests__/telemetry.test.ts +2 -2
- package/src/watcher/telemetry.ts +1 -1
- package/src/windows-compiled-cli.ts +1 -1
- package/src/workspace/adaptive-thinking-repair.ts +4 -4
- package/src/workspace/byok-default-profile-ensure.ts +7 -7
- package/src/workspace/git-service.ts +2 -2
- package/src/workspace/migrations/006-services-config.ts +1 -1
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +1 -1
- package/src/workspace/migrations/052-seed-default-inference-profiles.ts +1 -1
- package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +1 -1
- package/src/workspace/migrations/066-seed-heartbeat-callsite-cost-default.ts +1 -1
- package/src/workspace/migrations/082-backfill-managed-profile-labels.ts +4 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +3 -3
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +4 -4
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +5 -5
- package/src/workspace/migrations/100-upgrade-quality-profile-to-fable-5.ts +4 -4
- package/src/workspace/migrations/101-upgrade-balanced-economy-to-minimax-m3.ts +4 -4
- package/src/workspace/migrations/103-upgrade-quality-profile-to-opus-4-8.ts +5 -5
- package/src/workspace/migrations/104-recheck-adaptive-thinking-model-implied-anthropic.ts +1 -1
- package/src/workspace/migrations/106-drop-collect-usage-data.ts +1 -1
- package/src/workspace/migrations/108-drop-balanced-economy-profile.ts +4 -4
- package/src/workspace/migrations/109-swap-quality-profile-to-glm-5p2.ts +11 -11
- package/src/workspace/migrations/110-flip-balanced-profile-to-together.ts +6 -6
- package/src/workspace/migrations/113-swap-balanced-profile-to-glm-5p2.ts +5 -5
- package/src/workspace/migrations/114-swap-quality-profile-to-opus.ts +4 -4
- package/src/workspace/migrations/115-drop-frontier-profile.ts +5 -5
- package/src/workspace/migrations/123-swap-quality-profile-to-fable.ts +5 -5
- package/src/workspace/migrations/125-repoint-managed-connections-to-cadet.ts +3 -3
- package/src/workspace/migrations/126-strip-managed-profile-bodies.ts +2 -2
- package/src/workspace/migrations/127-backfill-default-provider.ts +4 -4
- package/src/workspace/migrations/130-speech-mode-to-provider.ts +4 -4
- package/src/workspace/migrations/131-drop-web-fetch-mode.ts +1 -1
- package/src/workspace/migrations/132-web-search-mode-to-provider.ts +6 -6
- package/src/workspace/migrations/133-collapse-provider-connections.ts +3 -3
- package/src/workspace/migrations/134-image-generation-mode-to-provider.ts +4 -4
- package/src/workspace/migrations/136-repair-stale-fireworks-kimi-model-id.ts +2 -2
- package/src/workspace/migrations/137-repair-retired-fireworks-minimax-model-id.ts +2 -2
- package/src/workspace/migrations/139-clear-renamed-cost-profile-label.ts +2 -2
- package/src/workspace/migrations/141-stt-english-default-to-multilingual.ts +4 -4
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +1 -1
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +2 -2
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +2 -2
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +2 -2
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +1 -1
- package/src/workspace/migrations/151-repair-renamed-fireworks-deepseek-pro-model-id.ts +2 -2
- package/src/workspace/migrations/152-repair-retired-fireworks-minimax-m2p7-model-id.ts +2 -2
- package/src/workspace/migrations/159-rename-colliding-jev-profile-name.ts +2 -2
- package/src/workspace/migrations/160-repair-retired-fireworks-model-ids.ts +2 -2
- package/src/workspace/migrations/161-repair-retired-fireworks-glm-5p2-model-id.ts +2 -2
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +1 -1
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +1 -1
- package/src/workspace/migrations/registry.ts +6 -6
- package/src/workspace/top-level-renderer.ts +1 -1
- package/src/workspace/turn-commit.ts +2 -2
- package/src/__tests__/config-loader-platform-defaults.test.ts +0 -606
- package/src/__tests__/mock-gateway-ipc.ts +0 -260
- package/src/api/events/platform-disconnected.ts +0 -21
- package/src/config/platform-rehydration.ts +0 -60
- package/src/platform/feature-gate.ts +0 -8
- package/src/plugins/defaults/platform-hosted/package.json +0 -14
- package/src/runtime/routes/platform-managed-credentials.ts +0 -50
- /package/src/__tests__/{gateway-client-managed-outbound.test.ts → gateway-client-cloud-outbound.test.ts} +0 -0
- /package/src/__tests__/{secure-keys-managed-failover.test.ts → secure-keys-cloud-failover.test.ts} +0 -0
|
@@ -1,196 +1,186 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Web search tool (Brave / Perplexity / Tavily) driven through the REAL
|
|
3
|
+
* `web-search.ts` execute path: provider keys are seeded into the real
|
|
4
|
+
* secure-keys module (memory backend), provider selection goes through the
|
|
5
|
+
* real workspace config, and HTTP is mocked at the `globalThis.fetch`
|
|
6
|
+
* boundary with `mockHttp()`.
|
|
7
|
+
*
|
|
8
|
+
* This file used to inline-copy the tool's execute logic (three providers'
|
|
9
|
+
* worth of duplicated formatting/retry code) and test the copy; it now tests
|
|
10
|
+
* the production module so drift fails here instead of shipping.
|
|
11
|
+
*/
|
|
12
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
13
|
+
|
|
14
|
+
import { credentialKey } from "../security/credential-key.js";
|
|
15
|
+
import { WEB_SEARCH_BACKEND_FAILURE_MESSAGE } from "../tools/network/web-search-error.js";
|
|
16
|
+
import { useMemorySecureKeys } from "./helpers/secure-keys.js";
|
|
17
|
+
import { setConfig } from "./helpers/set-config.js";
|
|
18
|
+
import { mockHttp } from "./requests-mock.js";
|
|
19
|
+
|
|
20
|
+
const keyStore = useMemorySecureKeys({
|
|
21
|
+
[credentialKey("brave", "api_key")]: "brave-key",
|
|
22
|
+
[credentialKey("perplexity", "api_key")]: "pplx-key",
|
|
23
|
+
[credentialKey("tavily", "api_key")]: "tvly-key",
|
|
24
|
+
});
|
|
2
25
|
|
|
3
|
-
|
|
4
|
-
// helper and does not import any production modules. Stray mock.module calls
|
|
5
|
-
// here leak into the shared Bun test process and break other test files.
|
|
26
|
+
const { webSearchTool } = await import("../tools/network/web-search.js");
|
|
6
27
|
|
|
7
|
-
|
|
8
|
-
const originalEnv = process.env;
|
|
9
|
-
let originalFetch: typeof globalThis.fetch;
|
|
28
|
+
type Provider = "brave" | "perplexity" | "tavily";
|
|
10
29
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
globalThis.fetch = originalFetch;
|
|
19
|
-
});
|
|
30
|
+
async function executeWebSearch(
|
|
31
|
+
input: Record<string, unknown>,
|
|
32
|
+
provider: Provider,
|
|
33
|
+
) {
|
|
34
|
+
setConfig("services", { "web-search": { provider } });
|
|
35
|
+
return webSearchTool.execute(input, {} as never);
|
|
36
|
+
}
|
|
20
37
|
|
|
21
|
-
|
|
22
|
-
//
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
// Fresh keys per test — no-key tests clear the store for their duration
|
|
40
|
+
// and every test starts with all three providers seeded.
|
|
41
|
+
for (const [service, value] of Object.entries({
|
|
42
|
+
brave: "brave-key",
|
|
43
|
+
perplexity: "pplx-key",
|
|
44
|
+
tavily: "tvly-key",
|
|
45
|
+
})) {
|
|
46
|
+
keyStore[credentialKey(service, "api_key")] = value;
|
|
47
|
+
}
|
|
48
|
+
});
|
|
23
49
|
|
|
50
|
+
describe("WebSearchTool", () => {
|
|
24
51
|
describe("API key resolution", () => {
|
|
25
52
|
test("returns error when no API key is configured (brave)", async () => {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
);
|
|
53
|
+
for (const key of Object.keys(keyStore)) {
|
|
54
|
+
delete keyStore[key];
|
|
55
|
+
}
|
|
56
|
+
const http = mockHttp();
|
|
57
|
+
|
|
58
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
32
59
|
expect(result.isError).toBe(true);
|
|
33
60
|
expect(result.content).toContain("No web search API key configured");
|
|
61
|
+
// No key anywhere in the fallback chain — nothing is called.
|
|
62
|
+
expect(http.callCount()).toBe(0);
|
|
34
63
|
});
|
|
35
64
|
|
|
36
65
|
test("returns error when no API key is configured (perplexity)", async () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
);
|
|
66
|
+
for (const key of Object.keys(keyStore)) {
|
|
67
|
+
delete keyStore[key];
|
|
68
|
+
}
|
|
69
|
+
const http = mockHttp();
|
|
70
|
+
|
|
71
|
+
const result = await executeWebSearch({ query: "test" }, "perplexity");
|
|
43
72
|
expect(result.isError).toBe(true);
|
|
44
73
|
expect(result.content).toContain("No web search API key configured");
|
|
74
|
+
expect(http.callCount()).toBe(0);
|
|
45
75
|
});
|
|
46
76
|
|
|
47
77
|
test("returns error when no API key is configured (tavily)", async () => {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
);
|
|
78
|
+
for (const key of Object.keys(keyStore)) {
|
|
79
|
+
delete keyStore[key];
|
|
80
|
+
}
|
|
81
|
+
const http = mockHttp();
|
|
82
|
+
|
|
83
|
+
const result = await executeWebSearch({ query: "test" }, "tavily");
|
|
54
84
|
expect(result.isError).toBe(true);
|
|
55
85
|
expect(result.content).toContain("No web search API key configured");
|
|
86
|
+
expect(http.callCount()).toBe(0);
|
|
56
87
|
});
|
|
57
88
|
});
|
|
58
89
|
|
|
59
90
|
describe("input validation", () => {
|
|
60
91
|
test("rejects missing query", async () => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
fetchCalled = true;
|
|
65
|
-
return new Response("{}", { status: 200 });
|
|
66
|
-
}) as unknown as typeof fetch;
|
|
67
|
-
|
|
68
|
-
process.env.BRAVE_API_KEY = "test-key";
|
|
69
|
-
|
|
70
|
-
// We need to test the tool's execute method directly
|
|
71
|
-
// Since bun:test module mocking is limited for re-imports,
|
|
72
|
-
// we'll create a minimal test using the tool's logic
|
|
73
|
-
const result = await executeWebSearch({}, "test-key", "brave");
|
|
92
|
+
const http = mockHttp();
|
|
93
|
+
|
|
94
|
+
const result = await executeWebSearch({}, "brave");
|
|
74
95
|
expect(result.isError).toBe(true);
|
|
75
96
|
expect(result.content).toContain("query is required");
|
|
76
|
-
expect(
|
|
97
|
+
expect(http.callCount()).toBe(0);
|
|
77
98
|
});
|
|
78
99
|
|
|
79
100
|
test("rejects non-string query", async () => {
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
"brave",
|
|
84
|
-
);
|
|
101
|
+
const http = mockHttp();
|
|
102
|
+
|
|
103
|
+
const result = await executeWebSearch({ query: 123 }, "brave");
|
|
85
104
|
expect(result.isError).toBe(true);
|
|
86
105
|
expect(result.content).toContain("query is required");
|
|
106
|
+
expect(http.callCount()).toBe(0);
|
|
87
107
|
});
|
|
88
108
|
});
|
|
89
109
|
|
|
90
110
|
describe("Brave parameter handling", () => {
|
|
91
111
|
test("clamps count to valid range", async () => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
headers: { "Content-Type": "application/json" },
|
|
98
|
-
});
|
|
99
|
-
}) as unknown as typeof fetch;
|
|
112
|
+
const http = mockHttp();
|
|
113
|
+
http.on("api.search.brave.com", {
|
|
114
|
+
status: 200,
|
|
115
|
+
body: { web: { results: [] } },
|
|
116
|
+
});
|
|
100
117
|
|
|
101
|
-
await executeWebSearch({ query: "test", count: 50 }, "
|
|
102
|
-
expect(
|
|
118
|
+
await executeWebSearch({ query: "test", count: 50 }, "brave");
|
|
119
|
+
expect(http.lastCall()?.url).toContain("count=20");
|
|
103
120
|
|
|
104
|
-
await executeWebSearch({ query: "test", count: -5 }, "
|
|
105
|
-
expect(
|
|
121
|
+
await executeWebSearch({ query: "test", count: -5 }, "brave");
|
|
122
|
+
expect(http.lastCall()?.url).toContain("count=1");
|
|
106
123
|
});
|
|
107
124
|
|
|
108
125
|
test("clamps offset to valid range", async () => {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
headers: { "Content-Type": "application/json" },
|
|
115
|
-
});
|
|
116
|
-
}) as unknown as typeof fetch;
|
|
126
|
+
const http = mockHttp();
|
|
127
|
+
http.on("api.search.brave.com", {
|
|
128
|
+
status: 200,
|
|
129
|
+
body: { web: { results: [] } },
|
|
130
|
+
});
|
|
117
131
|
|
|
118
|
-
await executeWebSearch(
|
|
119
|
-
|
|
120
|
-
"test-key",
|
|
121
|
-
"brave",
|
|
122
|
-
);
|
|
123
|
-
expect(capturedUrl).toContain("offset=9");
|
|
132
|
+
await executeWebSearch({ query: "test", offset: 20 }, "brave");
|
|
133
|
+
expect(http.lastCall()?.url).toContain("offset=9");
|
|
124
134
|
});
|
|
125
135
|
|
|
126
136
|
test("includes freshness when valid", async () => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
headers: { "Content-Type": "application/json" },
|
|
133
|
-
});
|
|
134
|
-
}) as unknown as typeof fetch;
|
|
137
|
+
const http = mockHttp();
|
|
138
|
+
http.on("api.search.brave.com", {
|
|
139
|
+
status: 200,
|
|
140
|
+
body: { web: { results: [] } },
|
|
141
|
+
});
|
|
135
142
|
|
|
136
|
-
await executeWebSearch(
|
|
137
|
-
|
|
138
|
-
"test-key",
|
|
139
|
-
"brave",
|
|
140
|
-
);
|
|
141
|
-
expect(capturedUrl).toContain("freshness=pw");
|
|
143
|
+
await executeWebSearch({ query: "test", freshness: "pw" }, "brave");
|
|
144
|
+
expect(http.lastCall()?.url).toContain("freshness=pw");
|
|
142
145
|
});
|
|
143
146
|
|
|
144
147
|
test("ignores invalid freshness values", async () => {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
headers: { "Content-Type": "application/json" },
|
|
151
|
-
});
|
|
152
|
-
}) as unknown as typeof fetch;
|
|
148
|
+
const http = mockHttp();
|
|
149
|
+
http.on("api.search.brave.com", {
|
|
150
|
+
status: 200,
|
|
151
|
+
body: { web: { results: [] } },
|
|
152
|
+
});
|
|
153
153
|
|
|
154
|
-
await executeWebSearch(
|
|
155
|
-
|
|
156
|
-
"test-key",
|
|
157
|
-
"brave",
|
|
158
|
-
);
|
|
159
|
-
expect(capturedUrl).not.toContain("freshness");
|
|
154
|
+
await executeWebSearch({ query: "test", freshness: "invalid" }, "brave");
|
|
155
|
+
expect(http.lastCall()?.url).not.toContain("freshness");
|
|
160
156
|
});
|
|
161
157
|
});
|
|
162
158
|
|
|
163
159
|
describe("Brave API responses", () => {
|
|
164
160
|
test("formats results correctly", async () => {
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
161
|
+
const http = mockHttp();
|
|
162
|
+
http.on("api.search.brave.com", {
|
|
163
|
+
status: 200,
|
|
164
|
+
body: {
|
|
165
|
+
web: {
|
|
166
|
+
results: [
|
|
167
|
+
{
|
|
168
|
+
title: "Test Result",
|
|
169
|
+
url: "https://example.com",
|
|
170
|
+
description: "A test result",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
title: "Another Result",
|
|
174
|
+
url: "https://other.com",
|
|
175
|
+
description: "Another one",
|
|
176
|
+
age: "2 days ago",
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
180
|
},
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
globalThis.fetch = (async () =>
|
|
184
|
-
new Response(JSON.stringify(mockResults), {
|
|
185
|
-
status: 200,
|
|
186
|
-
headers: { "Content-Type": "application/json" },
|
|
187
|
-
})) as unknown as typeof fetch;
|
|
181
|
+
});
|
|
188
182
|
|
|
189
|
-
const result = await executeWebSearch(
|
|
190
|
-
{ query: "test" },
|
|
191
|
-
"test-key",
|
|
192
|
-
"brave",
|
|
193
|
-
);
|
|
183
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
194
184
|
expect(result.isError).toBe(false);
|
|
195
185
|
expect(result.content).toContain("Test Result");
|
|
196
186
|
expect(result.content).toContain("https://example.com");
|
|
@@ -200,61 +190,51 @@ describe("WebSearchTool", () => {
|
|
|
200
190
|
});
|
|
201
191
|
|
|
202
192
|
test("handles empty results", async () => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
193
|
+
const http = mockHttp();
|
|
194
|
+
http.on("api.search.brave.com", {
|
|
195
|
+
status: 200,
|
|
196
|
+
body: { web: { results: [] } },
|
|
197
|
+
});
|
|
208
198
|
|
|
209
|
-
const result = await executeWebSearch(
|
|
210
|
-
{ query: "noresults" },
|
|
211
|
-
"test-key",
|
|
212
|
-
"brave",
|
|
213
|
-
);
|
|
199
|
+
const result = await executeWebSearch({ query: "noresults" }, "brave");
|
|
214
200
|
expect(result.isError).toBe(false);
|
|
215
201
|
expect(result.content).toContain("No results found");
|
|
216
202
|
});
|
|
217
203
|
|
|
218
204
|
test("handles missing web field", async () => {
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
status: 200,
|
|
222
|
-
headers: { "Content-Type": "application/json" },
|
|
223
|
-
})) as unknown as typeof fetch;
|
|
205
|
+
const http = mockHttp();
|
|
206
|
+
http.on("api.search.brave.com", { status: 200, body: {} });
|
|
224
207
|
|
|
225
|
-
const result = await executeWebSearch(
|
|
226
|
-
{ query: "empty" },
|
|
227
|
-
"test-key",
|
|
228
|
-
"brave",
|
|
229
|
-
);
|
|
208
|
+
const result = await executeWebSearch({ query: "empty" }, "brave");
|
|
230
209
|
expect(result.isError).toBe(false);
|
|
231
210
|
expect(result.content).toContain("No results found");
|
|
232
211
|
});
|
|
233
212
|
|
|
234
213
|
test("handles 401 unauthorized", async () => {
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
status: 401,
|
|
238
|
-
})) as unknown as typeof fetch;
|
|
214
|
+
const http = mockHttp();
|
|
215
|
+
http.on("api.search.brave.com", { status: 401, body: "Unauthorized" });
|
|
239
216
|
|
|
240
|
-
const result = await executeWebSearch(
|
|
241
|
-
{ query: "test" },
|
|
242
|
-
"bad-key",
|
|
243
|
-
"brave",
|
|
244
|
-
);
|
|
217
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
245
218
|
expect(result.isError).toBe(true);
|
|
246
219
|
expect(result.content).toContain("Invalid or expired");
|
|
247
220
|
});
|
|
248
221
|
|
|
249
222
|
test("retries on 429 and succeeds", async () => {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
223
|
+
const http = mockHttp();
|
|
224
|
+
http.on("api.search.brave.com", [
|
|
225
|
+
{
|
|
226
|
+
status: 429,
|
|
227
|
+
body: "Too Many Requests",
|
|
228
|
+
headers: { "Retry-After": "0" },
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
status: 429,
|
|
232
|
+
body: "Too Many Requests",
|
|
233
|
+
headers: { "Retry-After": "0" },
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
status: 200,
|
|
237
|
+
body: {
|
|
258
238
|
web: {
|
|
259
239
|
results: [
|
|
260
240
|
{
|
|
@@ -264,120 +244,89 @@ describe("WebSearchTool", () => {
|
|
|
264
244
|
},
|
|
265
245
|
],
|
|
266
246
|
},
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}) as unknown as typeof fetch;
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
]);
|
|
271
250
|
|
|
272
|
-
const result = await executeWebSearch(
|
|
273
|
-
{ query: "test" },
|
|
274
|
-
"test-key",
|
|
275
|
-
"brave",
|
|
276
|
-
);
|
|
251
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
277
252
|
expect(result.isError).toBe(false);
|
|
278
|
-
expect(result.content).toContain("
|
|
279
|
-
expect(callCount).toBe(3);
|
|
253
|
+
expect(result.content).toContain("Found it");
|
|
254
|
+
expect(http.callCount()).toBe(3);
|
|
280
255
|
});
|
|
281
256
|
|
|
282
|
-
test("returns
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
257
|
+
test("returns the friendly backend copy after exhausting 429 retries", async () => {
|
|
258
|
+
const http = mockHttp();
|
|
259
|
+
http.on("api.search.brave.com", {
|
|
260
|
+
status: 429,
|
|
261
|
+
body: "Too Many Requests",
|
|
262
|
+
headers: { "Retry-After": "0" },
|
|
263
|
+
});
|
|
287
264
|
|
|
288
|
-
const result = await executeWebSearch(
|
|
289
|
-
{ query: "test" },
|
|
290
|
-
"test-key",
|
|
291
|
-
"brave",
|
|
292
|
-
);
|
|
265
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
293
266
|
expect(result.isError).toBe(true);
|
|
294
|
-
expect(result.content).
|
|
295
|
-
|
|
267
|
+
expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
268
|
+
// DEFAULT_MAX_RETRIES = 3 → the initial attempt plus three retries.
|
|
269
|
+
expect(http.callCount()).toBe(4);
|
|
296
270
|
});
|
|
297
271
|
|
|
298
272
|
test("respects Retry-After header on 429", async () => {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
return new Response(JSON.stringify({ web: { results: [] } }), {
|
|
311
|
-
status: 200,
|
|
312
|
-
headers: { "Content-Type": "application/json" },
|
|
313
|
-
});
|
|
314
|
-
}) as unknown as typeof fetch;
|
|
273
|
+
const http = mockHttp();
|
|
274
|
+
http.on("api.search.brave.com", [
|
|
275
|
+
{
|
|
276
|
+
status: 429,
|
|
277
|
+
body: "Too Many Requests",
|
|
278
|
+
headers: { "Retry-After": "0" },
|
|
279
|
+
},
|
|
280
|
+
{ status: 200, body: { web: { results: [] } } },
|
|
281
|
+
]);
|
|
315
282
|
|
|
316
|
-
const result = await executeWebSearch(
|
|
317
|
-
{ query: "test" },
|
|
318
|
-
"test-key",
|
|
319
|
-
"brave",
|
|
320
|
-
);
|
|
283
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
321
284
|
expect(result.isError).toBe(false);
|
|
322
|
-
expect(callCount).toBe(2);
|
|
285
|
+
expect(http.callCount()).toBe(2);
|
|
323
286
|
});
|
|
324
287
|
|
|
325
|
-
test("
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}) as unknown as typeof fetch;
|
|
288
|
+
test("surfaces the friendly backend copy on network errors", async () => {
|
|
289
|
+
const http = mockHttp();
|
|
290
|
+
http.on("api.search.brave.com", new TypeError("fetch failed"));
|
|
329
291
|
|
|
330
|
-
const result = await executeWebSearch(
|
|
331
|
-
{ query: "test" },
|
|
332
|
-
"test-key",
|
|
333
|
-
"brave",
|
|
334
|
-
);
|
|
292
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
335
293
|
expect(result.isError).toBe(true);
|
|
336
|
-
expect(result.content).
|
|
294
|
+
expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
295
|
+
expect(http.callCount()).toBe(1);
|
|
337
296
|
});
|
|
338
297
|
|
|
339
298
|
test("sends correct headers", async () => {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return new Response(JSON.stringify({ web: { results: [] } }), {
|
|
346
|
-
status: 200,
|
|
347
|
-
headers: { "Content-Type": "application/json" },
|
|
348
|
-
});
|
|
349
|
-
}) as unknown as typeof fetch;
|
|
299
|
+
const http = mockHttp();
|
|
300
|
+
http.on("api.search.brave.com", {
|
|
301
|
+
status: 200,
|
|
302
|
+
body: { web: { results: [] } },
|
|
303
|
+
});
|
|
350
304
|
|
|
351
|
-
await executeWebSearch({ query: "test" }, "
|
|
352
|
-
|
|
353
|
-
expect(
|
|
305
|
+
await executeWebSearch({ query: "test" }, "brave");
|
|
306
|
+
const req = http.lastCall();
|
|
307
|
+
expect(req?.headers["X-Subscription-Token"]).toBe("brave-key");
|
|
308
|
+
expect(req?.headers["Accept"]).toBe("application/json");
|
|
354
309
|
});
|
|
355
310
|
|
|
356
311
|
test("includes extra_snippets in output", async () => {
|
|
357
|
-
const
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
312
|
+
const http = mockHttp();
|
|
313
|
+
http.on("api.search.brave.com", {
|
|
314
|
+
status: 200,
|
|
315
|
+
body: {
|
|
316
|
+
web: {
|
|
317
|
+
results: [
|
|
318
|
+
{
|
|
319
|
+
title: "Snippet Test",
|
|
320
|
+
url: "https://example.com",
|
|
321
|
+
description: "Main description",
|
|
322
|
+
extra_snippets: ["Extra snippet 1", "Extra snippet 2"],
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
},
|
|
367
326
|
},
|
|
368
|
-
};
|
|
369
|
-
|
|
370
|
-
globalThis.fetch = (async () =>
|
|
371
|
-
new Response(JSON.stringify(mockResults), {
|
|
372
|
-
status: 200,
|
|
373
|
-
headers: { "Content-Type": "application/json" },
|
|
374
|
-
})) as unknown as typeof fetch;
|
|
327
|
+
});
|
|
375
328
|
|
|
376
|
-
const result = await executeWebSearch(
|
|
377
|
-
{ query: "test" },
|
|
378
|
-
"test-key",
|
|
379
|
-
"brave",
|
|
380
|
-
);
|
|
329
|
+
const result = await executeWebSearch({ query: "test" }, "brave");
|
|
381
330
|
expect(result.isError).toBe(false);
|
|
382
331
|
expect(result.content).toContain("Extra snippet 1");
|
|
383
332
|
expect(result.content).toContain("Extra snippet 2");
|
|
@@ -386,31 +335,25 @@ describe("WebSearchTool", () => {
|
|
|
386
335
|
|
|
387
336
|
describe("Perplexity API responses", () => {
|
|
388
337
|
test("formats results with citations", async () => {
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
338
|
+
const http = mockHttp();
|
|
339
|
+
http.on("api.perplexity.ai", {
|
|
340
|
+
status: 200,
|
|
341
|
+
body: {
|
|
342
|
+
choices: [
|
|
343
|
+
{
|
|
344
|
+
message: {
|
|
345
|
+
content: "Perplexity found this information about the topic.",
|
|
346
|
+
},
|
|
394
347
|
},
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
};
|
|
402
|
-
|
|
403
|
-
globalThis.fetch = (async () =>
|
|
404
|
-
new Response(JSON.stringify(mockResponse), {
|
|
405
|
-
status: 200,
|
|
406
|
-
headers: { "Content-Type": "application/json" },
|
|
407
|
-
})) as unknown as typeof fetch;
|
|
348
|
+
],
|
|
349
|
+
citations: [
|
|
350
|
+
"https://example.com/source1",
|
|
351
|
+
"https://example.com/source2",
|
|
352
|
+
],
|
|
353
|
+
},
|
|
354
|
+
});
|
|
408
355
|
|
|
409
|
-
const result = await executeWebSearch(
|
|
410
|
-
{ query: "test" },
|
|
411
|
-
"pplx-key",
|
|
412
|
-
"perplexity",
|
|
413
|
-
);
|
|
356
|
+
const result = await executeWebSearch({ query: "test" }, "perplexity");
|
|
414
357
|
expect(result.isError).toBe(false);
|
|
415
358
|
expect(result.content).toContain("Perplexity found this information");
|
|
416
359
|
expect(result.content).toContain("https://example.com/source1");
|
|
@@ -418,19 +361,14 @@ describe("WebSearchTool", () => {
|
|
|
418
361
|
});
|
|
419
362
|
|
|
420
363
|
test("handles empty response", async () => {
|
|
421
|
-
const
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
new Response(JSON.stringify(mockResponse), {
|
|
427
|
-
status: 200,
|
|
428
|
-
headers: { "Content-Type": "application/json" },
|
|
429
|
-
})) as unknown as typeof fetch;
|
|
364
|
+
const http = mockHttp();
|
|
365
|
+
http.on("api.perplexity.ai", {
|
|
366
|
+
status: 200,
|
|
367
|
+
body: { choices: [{ message: { content: "" } }] },
|
|
368
|
+
});
|
|
430
369
|
|
|
431
370
|
const result = await executeWebSearch(
|
|
432
371
|
{ query: "noresults" },
|
|
433
|
-
"pplx-key",
|
|
434
372
|
"perplexity",
|
|
435
373
|
);
|
|
436
374
|
expect(result.isError).toBe(false);
|
|
@@ -438,112 +376,87 @@ describe("WebSearchTool", () => {
|
|
|
438
376
|
});
|
|
439
377
|
|
|
440
378
|
test("handles 401 unauthorized", async () => {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
status: 401,
|
|
444
|
-
})) as unknown as typeof fetch;
|
|
379
|
+
const http = mockHttp();
|
|
380
|
+
http.on("api.perplexity.ai", { status: 401, body: "Unauthorized" });
|
|
445
381
|
|
|
446
|
-
const result = await executeWebSearch(
|
|
447
|
-
{ query: "test" },
|
|
448
|
-
"bad-key",
|
|
449
|
-
"perplexity",
|
|
450
|
-
);
|
|
382
|
+
const result = await executeWebSearch({ query: "test" }, "perplexity");
|
|
451
383
|
expect(result.isError).toBe(true);
|
|
452
384
|
expect(result.content).toContain("Invalid or expired");
|
|
453
385
|
});
|
|
454
386
|
|
|
455
|
-
test("sends correct headers", async () => {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
);
|
|
462
|
-
capturedBody = JSON.parse(init.body as string);
|
|
463
|
-
return new Response(
|
|
464
|
-
JSON.stringify({ choices: [{ message: { content: "result" } }] }),
|
|
465
|
-
{
|
|
466
|
-
status: 200,
|
|
467
|
-
headers: { "Content-Type": "application/json" },
|
|
468
|
-
},
|
|
469
|
-
);
|
|
470
|
-
}) as unknown as typeof fetch;
|
|
387
|
+
test("sends correct headers and body", async () => {
|
|
388
|
+
const http = mockHttp();
|
|
389
|
+
http.on("api.perplexity.ai", {
|
|
390
|
+
status: 200,
|
|
391
|
+
body: { choices: [{ message: { content: "result" } }] },
|
|
392
|
+
});
|
|
471
393
|
|
|
472
|
-
await executeWebSearch(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
expect(
|
|
481
|
-
|
|
482
|
-
).toBe("test query");
|
|
394
|
+
await executeWebSearch({ query: "test query" }, "perplexity");
|
|
395
|
+
const req = http.lastCall();
|
|
396
|
+
expect(req?.headers["Authorization"]).toBe("Bearer pplx-key");
|
|
397
|
+
expect(req?.headers["Content-Type"]).toBe("application/json");
|
|
398
|
+
const body = req?.body as {
|
|
399
|
+
model: string;
|
|
400
|
+
messages: Array<{ content: string }>;
|
|
401
|
+
};
|
|
402
|
+
expect(body.model).toBe("sonar");
|
|
403
|
+
expect(body.messages[0]?.content).toBe("test query");
|
|
483
404
|
});
|
|
484
405
|
|
|
485
406
|
test("retries on 429 and succeeds", async () => {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
407
|
+
const http = mockHttp();
|
|
408
|
+
http.on("api.perplexity.ai", [
|
|
409
|
+
{
|
|
410
|
+
status: 429,
|
|
411
|
+
body: "Too Many Requests",
|
|
412
|
+
headers: { "Retry-After": "0" },
|
|
413
|
+
},
|
|
414
|
+
{
|
|
415
|
+
status: 429,
|
|
416
|
+
body: "Too Many Requests",
|
|
417
|
+
headers: { "Retry-After": "0" },
|
|
418
|
+
},
|
|
419
|
+
{
|
|
420
|
+
status: 200,
|
|
421
|
+
body: { choices: [{ message: { content: "Found it" } }] },
|
|
422
|
+
},
|
|
423
|
+
]);
|
|
497
424
|
|
|
498
|
-
const result = await executeWebSearch(
|
|
499
|
-
{ query: "test" },
|
|
500
|
-
"pplx-key",
|
|
501
|
-
"perplexity",
|
|
502
|
-
);
|
|
425
|
+
const result = await executeWebSearch({ query: "test" }, "perplexity");
|
|
503
426
|
expect(result.isError).toBe(false);
|
|
504
427
|
expect(result.content).toContain("Found it");
|
|
505
|
-
expect(callCount).toBe(3);
|
|
428
|
+
expect(http.callCount()).toBe(3);
|
|
506
429
|
});
|
|
507
430
|
|
|
508
|
-
test("
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
}) as unknown as typeof fetch;
|
|
431
|
+
test("surfaces the friendly backend copy on network errors", async () => {
|
|
432
|
+
const http = mockHttp();
|
|
433
|
+
http.on("api.perplexity.ai", new TypeError("fetch failed"));
|
|
512
434
|
|
|
513
|
-
const result = await executeWebSearch(
|
|
514
|
-
{ query: "test" },
|
|
515
|
-
"pplx-key",
|
|
516
|
-
"perplexity",
|
|
517
|
-
);
|
|
435
|
+
const result = await executeWebSearch({ query: "test" }, "perplexity");
|
|
518
436
|
expect(result.isError).toBe(true);
|
|
519
|
-
expect(result.content).
|
|
437
|
+
expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
438
|
+
expect(http.callCount()).toBe(1);
|
|
520
439
|
});
|
|
521
440
|
});
|
|
522
441
|
|
|
523
442
|
describe("Tavily API responses", () => {
|
|
524
443
|
test("formats results", async () => {
|
|
525
|
-
const
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
headers: { "Content-Type": "application/json" },
|
|
540
|
-
})) as unknown as typeof fetch;
|
|
444
|
+
const http = mockHttp();
|
|
445
|
+
http.on("api.tavily.com", {
|
|
446
|
+
status: 200,
|
|
447
|
+
body: {
|
|
448
|
+
results: [
|
|
449
|
+
{
|
|
450
|
+
title: "Tavily Result",
|
|
451
|
+
url: "https://example.com/tavily",
|
|
452
|
+
content: "A Tavily search snippet.",
|
|
453
|
+
score: 0.9234,
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
});
|
|
541
458
|
|
|
542
|
-
const result = await executeWebSearch(
|
|
543
|
-
{ query: "test" },
|
|
544
|
-
"tvly-key",
|
|
545
|
-
"tavily",
|
|
546
|
-
);
|
|
459
|
+
const result = await executeWebSearch({ query: "test" }, "tavily");
|
|
547
460
|
expect(result.isError).toBe(false);
|
|
548
461
|
expect(result.content).toContain("Tavily Result");
|
|
549
462
|
expect(result.content).toContain("https://example.com/tavily");
|
|
@@ -552,90 +465,69 @@ describe("WebSearchTool", () => {
|
|
|
552
465
|
});
|
|
553
466
|
|
|
554
467
|
test("handles empty response", async () => {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
status: 200,
|
|
558
|
-
headers: { "Content-Type": "application/json" },
|
|
559
|
-
})) as unknown as typeof fetch;
|
|
468
|
+
const http = mockHttp();
|
|
469
|
+
http.on("api.tavily.com", { status: 200, body: { results: [] } });
|
|
560
470
|
|
|
561
|
-
const result = await executeWebSearch(
|
|
562
|
-
{ query: "noresults" },
|
|
563
|
-
"tvly-key",
|
|
564
|
-
"tavily",
|
|
565
|
-
);
|
|
471
|
+
const result = await executeWebSearch({ query: "noresults" }, "tavily");
|
|
566
472
|
expect(result.isError).toBe(false);
|
|
567
473
|
expect(result.content).toContain("No results found");
|
|
568
474
|
});
|
|
569
475
|
|
|
570
|
-
test("maps count and freshness into Tavily request body", async () => {
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
globalThis.fetch = (async (_url: string, init: RequestInit) => {
|
|
574
|
-
capturedHeaders = Object.fromEntries(
|
|
575
|
-
Object.entries(init.headers as Record<string, string>),
|
|
576
|
-
);
|
|
577
|
-
capturedBody = JSON.parse(init.body as string);
|
|
578
|
-
return new Response(JSON.stringify({ results: [] }), {
|
|
579
|
-
status: 200,
|
|
580
|
-
headers: { "Content-Type": "application/json" },
|
|
581
|
-
});
|
|
582
|
-
}) as unknown as typeof fetch;
|
|
476
|
+
test("maps count and freshness into the Tavily request body", async () => {
|
|
477
|
+
const http = mockHttp();
|
|
478
|
+
http.on("api.tavily.com", { status: 200, body: { results: [] } });
|
|
583
479
|
|
|
584
480
|
await executeWebSearch(
|
|
585
481
|
{ query: "test query", count: 50, freshness: "pw" },
|
|
586
|
-
"tvly-my-key",
|
|
587
482
|
"tavily",
|
|
588
483
|
);
|
|
589
|
-
|
|
590
|
-
expect(
|
|
591
|
-
expect(
|
|
592
|
-
|
|
593
|
-
expect(
|
|
594
|
-
expect(
|
|
484
|
+
const req = http.lastCall();
|
|
485
|
+
expect(req?.headers.Authorization).toBe("Bearer tvly-key");
|
|
486
|
+
expect(req?.headers["Content-Type"]).toBe("application/json");
|
|
487
|
+
const body = req?.body as Record<string, unknown>;
|
|
488
|
+
expect(body.query).toBe("test query");
|
|
489
|
+
expect(body.search_depth).toBe("advanced");
|
|
490
|
+
expect(body.max_results).toBe(20);
|
|
491
|
+
expect(body.time_range).toBe("week");
|
|
595
492
|
});
|
|
596
493
|
|
|
597
494
|
test("omits invalid freshness values", async () => {
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
capturedBody = JSON.parse(init.body as string);
|
|
601
|
-
return new Response(JSON.stringify({ results: [] }), {
|
|
602
|
-
status: 200,
|
|
603
|
-
headers: { "Content-Type": "application/json" },
|
|
604
|
-
});
|
|
605
|
-
}) as unknown as typeof fetch;
|
|
495
|
+
const http = mockHttp();
|
|
496
|
+
http.on("api.tavily.com", { status: 200, body: { results: [] } });
|
|
606
497
|
|
|
607
498
|
await executeWebSearch(
|
|
608
499
|
{ query: "test query", freshness: "invalid" },
|
|
609
|
-
"tvly-my-key",
|
|
610
500
|
"tavily",
|
|
611
501
|
);
|
|
612
|
-
|
|
502
|
+
const body = http.lastCall()?.body as Record<string, unknown>;
|
|
503
|
+
expect(body.time_range).toBeUndefined();
|
|
613
504
|
});
|
|
614
505
|
|
|
615
506
|
test("handles 401 unauthorized", async () => {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
status: 401,
|
|
619
|
-
})) as unknown as typeof fetch;
|
|
507
|
+
const http = mockHttp();
|
|
508
|
+
http.on("api.tavily.com", { status: 401, body: "Unauthorized" });
|
|
620
509
|
|
|
621
|
-
const result = await executeWebSearch(
|
|
622
|
-
{ query: "test" },
|
|
623
|
-
"bad-key",
|
|
624
|
-
"tavily",
|
|
625
|
-
);
|
|
510
|
+
const result = await executeWebSearch({ query: "test" }, "tavily");
|
|
626
511
|
expect(result.isError).toBe(true);
|
|
627
512
|
expect(result.content).toContain("Invalid or expired");
|
|
628
513
|
});
|
|
629
514
|
|
|
630
515
|
test("retries on 429 and succeeds", async () => {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
516
|
+
const http = mockHttp();
|
|
517
|
+
http.on("api.tavily.com", [
|
|
518
|
+
{
|
|
519
|
+
status: 429,
|
|
520
|
+
body: "Too Many Requests",
|
|
521
|
+
headers: { "Retry-After": "0" },
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
status: 429,
|
|
525
|
+
body: "Too Many Requests",
|
|
526
|
+
headers: { "Retry-After": "0" },
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
status: 200,
|
|
530
|
+
body: {
|
|
639
531
|
results: [
|
|
640
532
|
{
|
|
641
533
|
title: "Found it",
|
|
@@ -643,435 +535,24 @@ describe("WebSearchTool", () => {
|
|
|
643
535
|
content: "Found it with Tavily",
|
|
644
536
|
},
|
|
645
537
|
],
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}) as unknown as typeof fetch;
|
|
538
|
+
},
|
|
539
|
+
},
|
|
540
|
+
]);
|
|
650
541
|
|
|
651
|
-
const result = await executeWebSearch(
|
|
652
|
-
{ query: "test" },
|
|
653
|
-
"tvly-key",
|
|
654
|
-
"tavily",
|
|
655
|
-
);
|
|
542
|
+
const result = await executeWebSearch({ query: "test" }, "tavily");
|
|
656
543
|
expect(result.isError).toBe(false);
|
|
657
544
|
expect(result.content).toContain("Found it with Tavily");
|
|
658
|
-
expect(callCount).toBe(3);
|
|
545
|
+
expect(http.callCount()).toBe(3);
|
|
659
546
|
});
|
|
660
547
|
|
|
661
|
-
test("
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}) as unknown as typeof fetch;
|
|
548
|
+
test("surfaces the friendly backend copy on network errors", async () => {
|
|
549
|
+
const http = mockHttp();
|
|
550
|
+
http.on("api.tavily.com", new TypeError("fetch failed"));
|
|
665
551
|
|
|
666
|
-
const result = await executeWebSearch(
|
|
667
|
-
{ query: "test" },
|
|
668
|
-
"tvly-key",
|
|
669
|
-
"tavily",
|
|
670
|
-
);
|
|
552
|
+
const result = await executeWebSearch({ query: "test" }, "tavily");
|
|
671
553
|
expect(result.isError).toBe(true);
|
|
672
|
-
expect(result.content).
|
|
554
|
+
expect(result.content).toBe(WEB_SEARCH_BACKEND_FAILURE_MESSAGE);
|
|
555
|
+
expect(http.callCount()).toBe(1);
|
|
673
556
|
});
|
|
674
557
|
});
|
|
675
558
|
});
|
|
676
|
-
|
|
677
|
-
interface BraveSearchResult {
|
|
678
|
-
title: string;
|
|
679
|
-
url: string;
|
|
680
|
-
description?: string;
|
|
681
|
-
age?: string;
|
|
682
|
-
extra_snippets?: string[];
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
interface BraveSearchResponse {
|
|
686
|
-
web?: { results: BraveSearchResult[] };
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
interface PerplexityResponse {
|
|
690
|
-
choices?: Array<{ message: { content: string } }>;
|
|
691
|
-
citations?: string[];
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
interface TavilySearchResult {
|
|
695
|
-
title?: string;
|
|
696
|
-
url?: string;
|
|
697
|
-
content?: string;
|
|
698
|
-
score?: number;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
interface TavilySearchResponse {
|
|
702
|
-
results?: TavilySearchResult[];
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
/**
|
|
706
|
-
* Helper that exercises the web search logic directly, bypassing module
|
|
707
|
-
* registration concerns. This replicates the core execute path from
|
|
708
|
-
* web-search.ts to test it in isolation.
|
|
709
|
-
*/
|
|
710
|
-
async function executeWebSearch(
|
|
711
|
-
input: Record<string, unknown>,
|
|
712
|
-
apiKey?: string,
|
|
713
|
-
provider: "brave" | "perplexity" | "tavily" = "brave",
|
|
714
|
-
): Promise<{ content: string; isError: boolean }> {
|
|
715
|
-
const query = input.query;
|
|
716
|
-
if (!query || typeof query !== "string") {
|
|
717
|
-
return {
|
|
718
|
-
content: "Error: query is required and must be a string",
|
|
719
|
-
isError: true,
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
if (!apiKey) {
|
|
724
|
-
return {
|
|
725
|
-
content:
|
|
726
|
-
"Error: No web search API key configured. Set it via `keys set perplexity <key>`, `keys set brave <key>`, or `keys set tavily <key>`, or configure it from the Settings page under API Keys.",
|
|
727
|
-
isError: true,
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
if (provider === "perplexity") {
|
|
732
|
-
return executePerplexitySearchHelper(query as string, apiKey);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
if (provider === "tavily") {
|
|
736
|
-
return executeTavilySearchHelper(input, query as string, apiKey);
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
return executeBraveSearchHelper(input, query as string, apiKey);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
async function executeBraveSearchHelper(
|
|
743
|
-
input: Record<string, unknown>,
|
|
744
|
-
query: string,
|
|
745
|
-
apiKey: string,
|
|
746
|
-
): Promise<{ content: string; isError: boolean }> {
|
|
747
|
-
const count =
|
|
748
|
-
typeof input.count === "number"
|
|
749
|
-
? Math.min(20, Math.max(1, Math.round(input.count)))
|
|
750
|
-
: 10;
|
|
751
|
-
const offset =
|
|
752
|
-
typeof input.offset === "number"
|
|
753
|
-
? Math.min(9, Math.max(0, Math.round(input.offset)))
|
|
754
|
-
: 0;
|
|
755
|
-
|
|
756
|
-
const params = new URLSearchParams({
|
|
757
|
-
q: query,
|
|
758
|
-
count: String(count),
|
|
759
|
-
offset: String(offset),
|
|
760
|
-
});
|
|
761
|
-
|
|
762
|
-
const validFreshness = ["pd", "pw", "pm", "py"];
|
|
763
|
-
if (
|
|
764
|
-
typeof input.freshness === "string" &&
|
|
765
|
-
validFreshness.includes(input.freshness)
|
|
766
|
-
) {
|
|
767
|
-
params.set("freshness", input.freshness);
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
const url = `https://api.search.brave.com/res/v1/web/search?${params.toString()}`;
|
|
771
|
-
|
|
772
|
-
const MAX_RETRIES = 3;
|
|
773
|
-
const BASE_DELAY_MS = 1; // Use 1ms in tests to avoid slow tests
|
|
774
|
-
|
|
775
|
-
try {
|
|
776
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
777
|
-
const response = await fetch(url, {
|
|
778
|
-
headers: {
|
|
779
|
-
Accept: "application/json",
|
|
780
|
-
"Accept-Encoding": "gzip",
|
|
781
|
-
"X-Subscription-Token": apiKey,
|
|
782
|
-
},
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
if (response.ok) {
|
|
786
|
-
const data = (await response.json()) as BraveSearchResponse;
|
|
787
|
-
const results = data.web?.results ?? [];
|
|
788
|
-
|
|
789
|
-
if (results.length === 0) {
|
|
790
|
-
return {
|
|
791
|
-
content: `No results found for "${query}".`,
|
|
792
|
-
isError: false,
|
|
793
|
-
};
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
const lines: string[] = [`Web search results for "${query}":\n`];
|
|
797
|
-
for (let i = 0; i < results.length; i++) {
|
|
798
|
-
const r = results[i];
|
|
799
|
-
lines.push(`${i + 1}. ${r.title}`);
|
|
800
|
-
lines.push(` URL: ${r.url}`);
|
|
801
|
-
if (r.description) {
|
|
802
|
-
lines.push(` ${r.description}`);
|
|
803
|
-
}
|
|
804
|
-
if (r.age) {
|
|
805
|
-
lines.push(` Age: ${r.age}`);
|
|
806
|
-
}
|
|
807
|
-
if (r.extra_snippets && r.extra_snippets.length > 0) {
|
|
808
|
-
for (const snippet of r.extra_snippets) {
|
|
809
|
-
lines.push(` > ${snippet}`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
812
|
-
lines.push("");
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
return { content: lines.join("\n"), isError: false };
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
await response.text();
|
|
819
|
-
|
|
820
|
-
if (response.status === 401 || response.status === 403) {
|
|
821
|
-
return {
|
|
822
|
-
content: "Error: Invalid or expired Brave Search API key",
|
|
823
|
-
isError: true,
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
828
|
-
const retryAfter = response.headers.get("retry-after");
|
|
829
|
-
const delayMs =
|
|
830
|
-
retryAfter && !isNaN(Number(retryAfter))
|
|
831
|
-
? Number(retryAfter) * 1000
|
|
832
|
-
: BASE_DELAY_MS * Math.pow(2, attempt);
|
|
833
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
if (response.status === 429) {
|
|
838
|
-
return {
|
|
839
|
-
content:
|
|
840
|
-
"Error: Brave Search rate limit exceeded after retries. Try again shortly.",
|
|
841
|
-
isError: true,
|
|
842
|
-
};
|
|
843
|
-
}
|
|
844
|
-
return {
|
|
845
|
-
content: `Error: Brave Search API returned status ${response.status}`,
|
|
846
|
-
isError: true,
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
return {
|
|
851
|
-
content:
|
|
852
|
-
"Error: Brave Search rate limit exceeded after retries. Try again shortly.",
|
|
853
|
-
isError: true,
|
|
854
|
-
};
|
|
855
|
-
} catch (err) {
|
|
856
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
857
|
-
return { content: `Error: Web search failed: ${msg}`, isError: true };
|
|
858
|
-
}
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
async function executePerplexitySearchHelper(
|
|
862
|
-
query: string,
|
|
863
|
-
apiKey: string,
|
|
864
|
-
): Promise<{ content: string; isError: boolean }> {
|
|
865
|
-
const MAX_RETRIES = 3;
|
|
866
|
-
const BASE_DELAY_MS = 1;
|
|
867
|
-
|
|
868
|
-
try {
|
|
869
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
870
|
-
const response = await fetch(
|
|
871
|
-
"https://api.perplexity.ai/chat/completions",
|
|
872
|
-
{
|
|
873
|
-
method: "POST",
|
|
874
|
-
headers: {
|
|
875
|
-
"Content-Type": "application/json",
|
|
876
|
-
Authorization: `Bearer ${apiKey}`,
|
|
877
|
-
},
|
|
878
|
-
body: JSON.stringify({
|
|
879
|
-
model: "sonar",
|
|
880
|
-
messages: [{ role: "user", content: query }],
|
|
881
|
-
}),
|
|
882
|
-
},
|
|
883
|
-
);
|
|
884
|
-
|
|
885
|
-
if (response.ok) {
|
|
886
|
-
const data = (await response.json()) as PerplexityResponse;
|
|
887
|
-
const content = data.choices?.[0]?.message?.content;
|
|
888
|
-
if (!content) {
|
|
889
|
-
return {
|
|
890
|
-
content: `No results found for "${query}".`,
|
|
891
|
-
isError: false,
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
const lines: string[] = [`Web search results for "${query}":\n`];
|
|
896
|
-
lines.push(content);
|
|
897
|
-
|
|
898
|
-
if (data.citations && data.citations.length > 0) {
|
|
899
|
-
lines.push("\nSources:");
|
|
900
|
-
for (let i = 0; i < data.citations.length; i++) {
|
|
901
|
-
lines.push(` [${i + 1}] ${data.citations[i]}`);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
return { content: lines.join("\n"), isError: false };
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
await response.text();
|
|
909
|
-
|
|
910
|
-
if (response.status === 401 || response.status === 403) {
|
|
911
|
-
return {
|
|
912
|
-
content: "Error: Invalid or expired Perplexity API key",
|
|
913
|
-
isError: true,
|
|
914
|
-
};
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
918
|
-
const retryAfter = response.headers.get("retry-after");
|
|
919
|
-
const delayMs =
|
|
920
|
-
retryAfter && !isNaN(Number(retryAfter))
|
|
921
|
-
? Number(retryAfter) * 1000
|
|
922
|
-
: BASE_DELAY_MS * Math.pow(2, attempt);
|
|
923
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
924
|
-
continue;
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (response.status === 429) {
|
|
928
|
-
return {
|
|
929
|
-
content:
|
|
930
|
-
"Error: Perplexity rate limit exceeded after retries. Try again shortly.",
|
|
931
|
-
isError: true,
|
|
932
|
-
};
|
|
933
|
-
}
|
|
934
|
-
return {
|
|
935
|
-
content: `Error: Perplexity API returned status ${response.status}`,
|
|
936
|
-
isError: true,
|
|
937
|
-
};
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
return {
|
|
941
|
-
content:
|
|
942
|
-
"Error: Perplexity rate limit exceeded after retries. Try again shortly.",
|
|
943
|
-
isError: true,
|
|
944
|
-
};
|
|
945
|
-
} catch (err) {
|
|
946
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
947
|
-
return { content: `Error: Web search failed: ${msg}`, isError: true };
|
|
948
|
-
}
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
function tavilyTimeRangeForFreshness(
|
|
952
|
-
freshness: string | undefined,
|
|
953
|
-
): "day" | "week" | "month" | "year" | undefined {
|
|
954
|
-
switch (freshness) {
|
|
955
|
-
case "pd":
|
|
956
|
-
return "day";
|
|
957
|
-
case "pw":
|
|
958
|
-
return "week";
|
|
959
|
-
case "pm":
|
|
960
|
-
return "month";
|
|
961
|
-
case "py":
|
|
962
|
-
return "year";
|
|
963
|
-
default:
|
|
964
|
-
return undefined;
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
async function executeTavilySearchHelper(
|
|
969
|
-
input: Record<string, unknown>,
|
|
970
|
-
query: string,
|
|
971
|
-
apiKey: string,
|
|
972
|
-
): Promise<{ content: string; isError: boolean }> {
|
|
973
|
-
const count =
|
|
974
|
-
typeof input.count === "number"
|
|
975
|
-
? Math.min(20, Math.max(1, Math.round(input.count)))
|
|
976
|
-
: 10;
|
|
977
|
-
const timeRange = tavilyTimeRangeForFreshness(
|
|
978
|
-
typeof input.freshness === "string" ? input.freshness : undefined,
|
|
979
|
-
);
|
|
980
|
-
const body: Record<string, unknown> = {
|
|
981
|
-
query,
|
|
982
|
-
search_depth: "advanced",
|
|
983
|
-
max_results: count,
|
|
984
|
-
};
|
|
985
|
-
if (timeRange) {
|
|
986
|
-
body.time_range = timeRange;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
const MAX_RETRIES = 3;
|
|
990
|
-
const BASE_DELAY_MS = 1;
|
|
991
|
-
|
|
992
|
-
try {
|
|
993
|
-
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
994
|
-
const response = await fetch("https://api.tavily.com/search", {
|
|
995
|
-
method: "POST",
|
|
996
|
-
headers: {
|
|
997
|
-
"Content-Type": "application/json",
|
|
998
|
-
Authorization: `Bearer ${apiKey}`,
|
|
999
|
-
"X-Client-Name": "mycadet",
|
|
1000
|
-
},
|
|
1001
|
-
body: JSON.stringify(body),
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
if (response.ok) {
|
|
1005
|
-
const data = (await response.json()) as TavilySearchResponse;
|
|
1006
|
-
const results = data.results ?? [];
|
|
1007
|
-
|
|
1008
|
-
if (results.length === 0) {
|
|
1009
|
-
return {
|
|
1010
|
-
content: `No results found for "${query}".`,
|
|
1011
|
-
isError: false,
|
|
1012
|
-
};
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
const lines: string[] = [`Web search results for "${query}":\n`];
|
|
1016
|
-
|
|
1017
|
-
for (let i = 0; i < results.length; i++) {
|
|
1018
|
-
const r = results[i];
|
|
1019
|
-
const title = r.title?.trim() || r.url?.trim() || "Untitled result";
|
|
1020
|
-
lines.push(`${i + 1}. ${title}`);
|
|
1021
|
-
if (r.url) {
|
|
1022
|
-
lines.push(` URL: ${r.url}`);
|
|
1023
|
-
}
|
|
1024
|
-
if (r.content) {
|
|
1025
|
-
lines.push(` ${r.content}`);
|
|
1026
|
-
}
|
|
1027
|
-
if (typeof r.score === "number") {
|
|
1028
|
-
lines.push(` Score: ${r.score.toFixed(3)}`);
|
|
1029
|
-
}
|
|
1030
|
-
lines.push("");
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
return { content: lines.join("\n"), isError: false };
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
await response.text();
|
|
1037
|
-
|
|
1038
|
-
if (response.status === 401 || response.status === 403) {
|
|
1039
|
-
return {
|
|
1040
|
-
content: "Error: Invalid or expired Tavily API key",
|
|
1041
|
-
isError: true,
|
|
1042
|
-
};
|
|
1043
|
-
}
|
|
1044
|
-
|
|
1045
|
-
if (response.status === 429 && attempt < MAX_RETRIES) {
|
|
1046
|
-
const retryAfter = response.headers.get("retry-after");
|
|
1047
|
-
const delayMs =
|
|
1048
|
-
retryAfter && !isNaN(Number(retryAfter))
|
|
1049
|
-
? Number(retryAfter) * 1000
|
|
1050
|
-
: BASE_DELAY_MS * Math.pow(2, attempt);
|
|
1051
|
-
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1052
|
-
continue;
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
if (response.status === 429) {
|
|
1056
|
-
return {
|
|
1057
|
-
content:
|
|
1058
|
-
"Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
|
|
1059
|
-
isError: true,
|
|
1060
|
-
};
|
|
1061
|
-
}
|
|
1062
|
-
return {
|
|
1063
|
-
content: `Error: Tavily Search API returned status ${response.status}`,
|
|
1064
|
-
isError: true,
|
|
1065
|
-
};
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
return {
|
|
1069
|
-
content:
|
|
1070
|
-
"Error: Tavily Search rate limit exceeded after retries. Try again shortly.",
|
|
1071
|
-
isError: true,
|
|
1072
|
-
};
|
|
1073
|
-
} catch (err) {
|
|
1074
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1075
|
-
return { content: `Error: Web search failed: ${msg}`, isError: true };
|
|
1076
|
-
}
|
|
1077
|
-
}
|