@vellumai/assistant 0.8.5 → 0.8.6
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/AGENTS.md +33 -1
- package/ARCHITECTURE.md +1 -1
- package/bunfig.toml +6 -1
- package/docs/credential-execution-service.md +6 -6
- package/docs/plugins.md +4 -3
- package/node_modules/@vellumai/skill-host-contracts/src/client.ts +12 -13
- package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +4 -1
- package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +16 -14
- package/openapi.yaml +1900 -166
- package/package.json +1 -1
- package/src/__tests__/actor-token-service.test.ts +3 -2
- package/src/__tests__/agent-loop-exit-reason.test.ts +102 -9
- package/src/__tests__/agent-loop-override-profile.test.ts +2 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +1 -0
- package/src/__tests__/agent-wake-override-profile.test.ts +1 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +2 -2
- package/src/__tests__/annotate-risk-options.test.ts +1 -0
- package/src/__tests__/approval-cascade.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +9 -13
- package/src/__tests__/assert-not-live-db.ts +79 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +9 -25
- package/src/__tests__/audit-log-rotation.test.ts +2 -2
- package/src/__tests__/auto-analysis-end-to-end.test.ts +6 -6
- package/src/__tests__/background-workers-disk-pressure.test.ts +5 -8
- package/src/__tests__/browser-skill-endstate.test.ts +3 -3
- package/src/__tests__/btw-routes.test.ts +3 -2
- package/src/__tests__/call-controller.test.ts +3 -2
- package/src/__tests__/channel-approval-routes.test.ts +3 -2
- package/src/__tests__/channel-guardian.test.ts +3 -2
- package/src/__tests__/channel-readiness-slack-remote.test.ts +175 -0
- package/src/__tests__/channel-reply-delivery.test.ts +35 -0
- package/src/__tests__/channel-retry-sweep.test.ts +320 -3
- package/src/__tests__/checker.test.ts +12 -12
- package/src/__tests__/compaction-events.test.ts +1 -0
- package/src/__tests__/compaction-trail-store.test.ts +264 -0
- package/src/__tests__/compactor-call-site-logging.test.ts +1 -0
- package/src/__tests__/compactor-preserved-tail-count.test.ts +1 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +7 -5
- package/src/__tests__/computer-use-tools.test.ts +12 -14
- package/src/__tests__/config-loader-backfill.test.ts +13 -28
- package/src/__tests__/config-loader-corrupt.test.ts +5 -5
- package/src/__tests__/config-loader-platform-defaults.test.ts +93 -26
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +3 -3
- package/src/__tests__/config-managed-gemini-defaults.test.ts +3 -4
- package/src/__tests__/config-schema.test.ts +10 -10
- package/src/__tests__/connection-model-compat.test.ts +83 -0
- package/src/__tests__/contacts-tools.test.ts +3 -2
- package/src/__tests__/context-token-estimator.test.ts +22 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +5 -0
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-handlers-max-tokens.test.ts +55 -0
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +34 -0
- package/src/__tests__/conversation-agent-loop.test.ts +488 -2
- package/src/__tests__/conversation-analysis-routes.test.ts +1 -0
- package/src/__tests__/conversation-app-control-instantiation.test.ts +29 -19
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-attention-store.test.ts +101 -0
- package/src/__tests__/conversation-attention-telegram.test.ts +3 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -0
- package/src/__tests__/conversation-error.test.ts +30 -0
- package/src/__tests__/conversation-fork-crud.test.ts +69 -8
- package/src/__tests__/conversation-fork-route.test.ts +3 -2
- package/src/__tests__/conversation-history-web-search.test.ts +1 -0
- package/src/__tests__/conversation-inference-profile-list.test.ts +3 -2
- package/src/__tests__/conversation-inference-profile-route.test.ts +3 -2
- package/src/__tests__/conversation-lifecycle.test.ts +1 -0
- package/src/__tests__/conversation-list-source.test.ts +3 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +2 -1
- package/src/__tests__/conversation-load-history-stripped.test.ts +1 -0
- package/src/__tests__/conversation-pairing.test.ts +53 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +26 -7
- package/src/__tests__/conversation-process-callsite.test.ts +1 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +5 -0
- package/src/__tests__/conversation-queue.test.ts +333 -291
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -18
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +33 -8
- package/src/__tests__/conversation-routes-slash-commands.test.ts +33 -2
- package/src/__tests__/conversation-runtime-assembly.test.ts +78 -0
- package/src/__tests__/conversation-skill-tools.test.ts +38 -142
- package/src/__tests__/conversation-slash-queue.test.ts +84 -32
- package/src/__tests__/conversation-slash-unknown.test.ts +5 -0
- package/src/__tests__/conversation-speed-override.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +46 -0
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-standalone.test.ts +6 -3
- package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -3
- package/src/__tests__/conversation-surfaces-table-action.test.ts +7 -17
- package/src/__tests__/conversation-sync-tags.test.ts +128 -12
- package/src/__tests__/conversation-title-service.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +30 -0
- package/src/__tests__/conversation-usage.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -0
- package/src/__tests__/credential-broker-browser-fill.test.ts +3 -3
- package/src/__tests__/credential-broker-server-use.test.ts +5 -5
- package/src/__tests__/credential-execution-client.test.ts +72 -1
- package/src/__tests__/credential-execution-feature-gates.test.ts +10 -12
- package/src/__tests__/credential-health-service.test.ts +252 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -5
- package/src/__tests__/credential-vault-unit.test.ts +19 -19
- package/src/__tests__/credential-vault.test.ts +5 -5
- package/src/__tests__/cross-provider-web-search.test.ts +56 -2
- package/src/__tests__/db-connection-isolation.test.ts +7 -6
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +8 -10
- package/src/__tests__/db-conversation-inference-profile-migration.test.ts +7 -10
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +9 -15
- package/src/__tests__/db-test-helpers.ts +58 -0
- package/src/__tests__/disk-pressure-guard.test.ts +58 -41
- package/src/__tests__/disk-pressure-lifecycle.test.ts +13 -10
- package/src/__tests__/disk-pressure-routes.test.ts +0 -33
- package/src/__tests__/disk-pressure-tools.test.ts +0 -4
- package/src/__tests__/dm-persistence.test.ts +26 -40
- package/src/__tests__/document-create-dedupe.test.ts +189 -0
- package/src/__tests__/document-find-replace.test.ts +3 -2
- package/src/__tests__/document-tool-security.test.ts +81 -2
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +5 -4
- package/src/__tests__/encrypted-store-test-helpers.ts +56 -0
- package/src/__tests__/encrypted-store.test.ts +11 -9
- package/src/__tests__/feature-flag-test-helpers.ts +53 -0
- package/src/__tests__/filing-service.test.ts +1 -0
- package/src/__tests__/first-greeting.test.ts +62 -12
- package/src/__tests__/gateway-flag-listener.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +26 -0
- package/src/__tests__/guardian-action-sweep.test.ts +3 -2
- package/src/__tests__/guardian-outbound-http.test.ts +3 -2
- package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +48 -3
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +1 -0
- package/src/__tests__/heartbeat-disk-pressure.test.ts +1 -0
- package/src/__tests__/heartbeat-service.test.ts +1 -0
- package/src/__tests__/helpers/mock-logger.ts +26 -0
- package/src/__tests__/host-bash-routes.test.ts +1 -0
- package/src/__tests__/host-cu-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-file-routes-targeted.test.ts +1 -0
- package/src/__tests__/host-shell-tool.test.ts +5 -4
- package/src/__tests__/host-transfer-routes-targeted.test.ts +1 -0
- package/src/__tests__/http-conversation-lineage.test.ts +3 -2
- package/src/__tests__/http-user-message-parity.test.ts +29 -7
- package/src/__tests__/identity-intro-cache.test.ts +133 -22
- package/src/__tests__/inbound-slack-persistence.test.ts +44 -72
- package/src/__tests__/inference-profile-reaper.test.ts +3 -2
- package/src/__tests__/inference-profile-session-ipc.test.ts +3 -2
- package/src/__tests__/injector-disk-pressure.test.ts +3 -17
- package/src/__tests__/inline-skill-load-permissions.test.ts +4 -4
- package/src/__tests__/list-messages-hidden-metadata.test.ts +80 -0
- package/src/__tests__/llm-context-normalization.test.ts +42 -0
- package/src/__tests__/llm-resolver.test.ts +331 -0
- package/src/__tests__/llm-schema.test.ts +1 -1
- package/src/__tests__/manual-token-reconciliation.test.ts +76 -1
- package/src/__tests__/mcp-abort-signal.test.ts +14 -0
- package/src/__tests__/mcp-client-auth.test.ts +14 -0
- package/src/__tests__/messaging-send-tool.test.ts +1 -0
- package/src/__tests__/migration-import-from-url.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +18 -2
- package/src/__tests__/model-intents.test.ts +3 -3
- package/src/__tests__/native-web-search.test.ts +30 -2
- package/src/__tests__/notification-deep-link.test.ts +62 -0
- package/src/__tests__/oauth-commands-routes.test.ts +37 -0
- package/src/__tests__/oauth-provider-visibility.test.ts +8 -8
- package/src/__tests__/oauth-store.test.ts +3 -2
- package/src/__tests__/onboarding-template-contract.test.ts +3 -2
- package/src/__tests__/openai-provider.test.ts +8 -9
- package/src/__tests__/openai-responses-provider.test.ts +70 -10
- package/src/__tests__/openrouter-provider-only.test.ts +27 -5
- package/src/__tests__/outbound-slack-persistence.test.ts +46 -1
- package/src/__tests__/persistence-pipeline.test.ts +139 -1
- package/src/__tests__/persistence-secret-redaction.test.ts +83 -12
- package/src/__tests__/plugin-bootstrap.test.ts +9 -11
- package/src/__tests__/plugin-tool-contribution.test.ts +41 -38
- package/src/__tests__/process-message-background-slack.test.ts +21 -16
- package/src/__tests__/process-message-display-content.test.ts +19 -22
- package/src/__tests__/provider-catalog-visibility.test.ts +9 -9
- package/src/__tests__/provider-platform-proxy-integration.test.ts +216 -4
- package/src/__tests__/provider-registry-ollama.test.ts +45 -22
- package/src/__tests__/recording-handler.test.ts +1 -0
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +1 -0
- package/src/__tests__/registry.test.ts +82 -76
- package/src/__tests__/relay-server.test.ts +10 -10
- package/src/__tests__/runtime-attachment-metadata.test.ts +3 -2
- package/src/__tests__/schedule-store.test.ts +16 -1
- package/src/__tests__/scheduler-reuse-conversation.test.ts +48 -3
- package/src/__tests__/secret-ingress-http.test.ts +5 -1
- package/src/__tests__/secure-keys.test.ts +3 -3
- package/src/__tests__/send-endpoint-busy.test.ts +81 -42
- package/src/__tests__/server-history-render.test.ts +4 -1
- package/src/__tests__/skill-feature-flags-integration.test.ts +8 -10
- package/src/__tests__/skill-feature-flags.test.ts +14 -16
- package/src/__tests__/skill-load-feature-flag.test.ts +5 -5
- package/src/__tests__/skill-projection-feature-flag.test.ts +44 -30
- package/src/__tests__/skill-projection.benchmark.test.ts +5 -7
- package/src/__tests__/skill-tool-factory.test.ts +96 -95
- package/src/__tests__/slack-channel-config.test.ts +3 -3
- package/src/__tests__/subagent-call-site-routing.test.ts +11 -3
- package/src/__tests__/subagent-disposal.test.ts +27 -8
- package/src/__tests__/subagent-fork-notifications.test.ts +24 -9
- package/src/__tests__/subagent-fork-spawn.test.ts +13 -4
- package/src/__tests__/subagent-manager-notify.test.ts +20 -8
- package/src/__tests__/subagent-notify-parent.test.ts +5 -4
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +58 -0
- package/src/__tests__/subagent-tools.test.ts +2 -1
- package/src/__tests__/suggestion-routes.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +38 -0
- package/src/__tests__/test-preload-verifier.ts +68 -0
- package/src/__tests__/test-preload.ts +32 -39
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +20 -7
- package/src/__tests__/tool-executor.test.ts +55 -10
- package/src/__tests__/tool-preview-lifecycle.test.ts +1 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/twilio-routes.test.ts +3 -2
- package/src/__tests__/validate-input.test.ts +381 -0
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +2 -1
- package/src/__tests__/voice-session-bridge.test.ts +37 -28
- package/src/__tests__/workspace-migration-090-memory-router-cost-optimized-profile.test.ts +326 -0
- package/src/__tests__/workspace-migration-091-retighten-migration-onboarding-thread.test.ts +166 -0
- package/src/acp/session-manager.ts +5 -6
- package/src/agent/loop.ts +80 -0
- package/src/api/README.md +124 -2
- package/src/api/constants/call-sites.ts +27 -0
- package/src/api/events/assistant-outbound-attachment.ts +51 -0
- package/src/api/events/assistant-text-delta.ts +32 -0
- package/src/api/events/assistant-turn-start.ts +33 -0
- package/src/api/events/document-comment-created.ts +48 -0
- package/src/api/events/document-comment-deleted.ts +24 -0
- package/src/api/events/document-comment-reopened.ts +25 -0
- package/src/api/events/document-comment-resolved.ts +27 -0
- package/src/api/events/generation-cancelled.ts +24 -0
- package/src/api/events/generation-handoff.ts +41 -0
- package/src/api/events/message-complete.ts +42 -0
- package/src/api/events/open-url.ts +30 -0
- package/src/{events → api/events}/relationship-state-updated.ts +3 -3
- package/src/api/events/tool-use-start.ts +32 -0
- package/src/api/index.ts +128 -3
- package/src/api/responses/llm-context-response.ts +39 -0
- package/src/api/responses/llm-request-log-entry.ts +93 -0
- package/src/api/responses/memory-recall-log.ts +65 -0
- package/src/api/responses/memory-v2-activation-log.ts +78 -0
- package/src/background-wake/background-wake-routes.test.ts +687 -52
- package/src/background-wake/platform-client.test.ts +308 -0
- package/src/background-wake/platform-client.ts +167 -0
- package/src/background-wake/publisher.ts +91 -0
- package/src/background-wake/runtime-registry.ts +2 -2
- package/src/background-wake/wake-intent-hooks.test.ts +282 -0
- package/src/calls/guardian-dispatch.ts +1 -0
- package/src/calls/voice-session-bridge.ts +4 -4
- package/src/cli/commands/__tests__/conversations-slack.test.ts +16 -0
- package/src/cli/commands/__tests__/notifications.test.ts +184 -40
- package/src/cli/commands/channels/__tests__/channels.test.ts +143 -0
- package/src/cli/commands/channels/index.ts +229 -0
- package/src/cli/commands/memory-v3-render.ts +147 -0
- package/src/cli/commands/memory-v3.ts +255 -4
- package/src/cli/commands/notifications.ts +365 -55
- package/src/cli/lib/open-browser.ts +7 -2
- package/src/cli/program.ts +2 -0
- package/src/config/assistant-feature-flags.ts +23 -42
- package/src/config/bundled-skills/document-editor/SKILL.md +5 -1
- package/src/config/bundled-skills/schedule/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/TOOLS.json +2 -2
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +1 -0
- package/src/config/call-site-defaults.ts +1 -1
- package/src/config/feature-flag-cache.ts +86 -0
- package/src/config/feature-flag-registry.json +17 -17
- package/src/config/llm-context-resolution.ts +10 -1
- package/src/config/llm-resolver.ts +121 -15
- package/src/config/loader.ts +4 -5
- package/src/config/schemas/__tests__/memory-v2.test.ts +15 -0
- package/src/config/schemas/heartbeat.ts +1 -1
- package/src/config/schemas/llm.ts +90 -1
- package/src/config/schemas/memory-v2.ts +26 -0
- package/src/config/schemas/services.ts +6 -2
- package/src/config/seed-inference-profiles.ts +36 -16
- package/src/context/token-estimator.ts +10 -5
- package/src/credential-execution/executable-discovery.ts +40 -0
- package/src/credential-execution/process-manager.ts +6 -2
- package/src/credential-health/credential-health-service.ts +125 -40
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -6
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +13 -15
- package/src/daemon/__tests__/conversation-tool-setup-exclude.test.ts +1 -2
- package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -0
- package/src/daemon/__tests__/meet-manifest-loader.test.ts +25 -12
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +1 -0
- package/src/daemon/__tests__/switch-inference-profile-tool.test.ts +107 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +1 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +389 -68
- package/src/daemon/conversation-agent-loop.ts +132 -28
- package/src/daemon/conversation-error.ts +33 -5
- package/src/daemon/conversation-messaging.ts +84 -43
- package/src/daemon/conversation-process.ts +74 -37
- package/src/daemon/conversation-runtime-assembly.ts +29 -9
- package/src/daemon/conversation-skill-tools.ts +14 -30
- package/src/daemon/conversation-surfaces.ts +69 -34
- package/src/daemon/conversation-tool-setup.ts +33 -48
- package/src/daemon/conversation.ts +26 -46
- package/src/daemon/daemon-control.ts +1 -1
- package/src/daemon/daemon-skill-host.ts +9 -2
- package/src/daemon/disk-pressure-guard.ts +27 -29
- package/src/daemon/first-greeting.ts +31 -13
- package/src/daemon/handlers/shared.ts +6 -1
- package/src/daemon/lifecycle.ts +12 -12
- package/src/daemon/mcp-reload-service.ts +1 -1
- package/src/daemon/meet-manifest-loader.ts +10 -17
- package/src/daemon/message-types/conversations.ts +20 -22
- package/src/daemon/message-types/document-comments.ts +8 -44
- package/src/daemon/message-types/home.ts +2 -2
- package/src/daemon/message-types/integrations.ts +2 -7
- package/src/daemon/message-types/messages.ts +23 -38
- package/src/daemon/message-types/subagents.ts +6 -0
- package/src/daemon/process-message.ts +9 -9
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +16 -0
- package/src/daemon/switch-inference-profile-tool.ts +13 -3
- package/src/daemon/tool-setup-types.ts +0 -6
- package/src/daemon/wake-target-adapter.ts +10 -0
- package/src/documents/document-store.ts +38 -0
- package/src/export/__tests__/transcript-formatter.test.ts +1 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +29 -0
- package/src/heartbeat/heartbeat-service.ts +63 -0
- package/src/home/__tests__/feed-writer.test.ts +161 -0
- package/src/home/__tests__/post-connect-feed.test.ts +1 -0
- package/src/home/__tests__/suggested-prompts.test.ts +55 -59
- package/src/home/feed-writer.ts +146 -7
- package/src/home/suggested-prompts.ts +27 -145
- package/src/ipc/__tests__/cli-ipc.test.ts +1 -0
- package/src/ipc/gateway-client.test.ts +4 -1
- package/src/ipc/skill-routes/__tests__/memory.test.ts +1 -0
- package/src/ipc/skill-routes/__tests__/registries.test.ts +36 -7
- package/src/ipc/skill-routes/memory.ts +4 -3
- package/src/ipc/skill-routes/registries.ts +28 -29
- package/src/memory/__tests__/jobs-store-enqueue-gate.test.ts +1 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +26 -5
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-job.test.ts +1 -0
- package/src/memory/__tests__/memory-retrospective-startup-cleanup.test.ts +1 -0
- package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +31 -0
- package/src/memory/conversation-attention-store.ts +17 -3
- package/src/memory/conversation-crud.ts +352 -112
- package/src/memory/db-connection.ts +29 -19
- package/src/memory/db-init.ts +4 -0
- package/src/memory/db-singleton.ts +77 -0
- package/src/memory/delivery-channels.ts +82 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +2 -4
- package/src/memory/graph/retriever.test.ts +3 -3
- package/src/memory/job-handlers/embedding.test.ts +3 -2
- package/src/memory/jobs/__tests__/embed-concept-page.test.ts +5 -2
- package/src/memory/jobs-worker.ts +12 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +80 -0
- package/src/memory/llm-request-log-source-local.ts +24 -0
- package/src/memory/llm-request-log-source.ts +31 -0
- package/src/memory/llm-request-log-store.ts +188 -3
- package/src/memory/memory-v2-activation-log-store.ts +95 -1
- package/src/memory/migrations/265-drop-provider-connection-status.ts +26 -0
- package/src/memory/migrations/266-messages-client-message-id.ts +43 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/schema/conversations.ts +9 -1
- package/src/memory/schema/inference.ts +0 -1
- package/src/memory/v2/__tests__/backfill-jobs.test.ts +5 -2
- package/src/memory/v2/__tests__/harness-metrics.test.ts +9 -0
- package/src/memory/v2/__tests__/harness-replay-input.test.ts +9 -4
- package/src/memory/v2/__tests__/harness-runner.test.ts +26 -0
- package/src/memory/v2/__tests__/sweep-job.test.ts +6 -3
- package/src/memory/v2/harness/metrics.ts +5 -1
- package/src/memory/v2/harness/replay-input.ts +19 -3
- package/src/memory/v2/harness/runner.ts +6 -0
- package/src/memory/v2/harness/trace.ts +6 -0
- package/src/memory/v3/__tests__/consolidation-job.test.ts +2 -4
- package/src/memory/v3/__tests__/coretrieval-seed.test.ts +270 -0
- package/src/memory/v3/__tests__/edges.test.ts +144 -1
- package/src/memory/v3/__tests__/filter.test.ts +48 -0
- package/src/memory/v3/__tests__/gate.test.ts +96 -33
- package/src/memory/v3/__tests__/index-composition.test.ts +58 -0
- package/src/memory/v3/__tests__/loop.test.ts +250 -5
- package/src/memory/v3/__tests__/scouts.test.ts +49 -0
- package/src/memory/v3/__tests__/shadow-diff.test.ts +225 -0
- package/src/memory/v3/__tests__/shadow-middleware.test.ts +88 -2
- package/src/memory/v3/__tests__/traversal.test.ts +39 -0
- package/src/memory/v3/__tests__/tree-walk.test.ts +77 -0
- package/src/memory/v3/__tests__/validate.test.ts +32 -0
- package/src/memory/v3/coretrieval-seed.ts +240 -0
- package/src/memory/v3/edges.ts +58 -21
- package/src/memory/v3/filter.ts +27 -22
- package/src/memory/v3/gate.ts +51 -36
- package/src/memory/v3/index-composition.ts +18 -5
- package/src/memory/v3/loop.ts +65 -17
- package/src/memory/v3/scouts.ts +15 -4
- package/src/memory/v3/shadow-diff.ts +287 -0
- package/src/memory/v3/shadow-middleware.ts +44 -2
- package/src/memory/v3/traversal.ts +6 -1
- package/src/memory/v3/tree-walk.ts +6 -1
- package/src/memory/v3/validate.ts +56 -33
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +1 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +1 -0
- package/src/notifications/adapters/slack.ts +45 -11
- package/src/notifications/broadcaster.ts +114 -63
- package/src/notifications/conversation-pairing.ts +23 -3
- package/src/notifications/decisions-store.ts +32 -1
- package/src/notifications/deliveries-store.ts +45 -0
- package/src/notifications/edit-notification.ts +201 -0
- package/src/notifications/emit-signal.ts +11 -1
- package/src/notifications/signal.ts +10 -0
- package/src/notifications/types.ts +37 -0
- package/src/oauth/byo-connection.test.ts +67 -3
- package/src/oauth/byo-connection.ts +32 -5
- package/src/oauth/connect-orchestrator.ts +9 -0
- package/src/oauth/connection-resolver.test.ts +76 -0
- package/src/oauth/connection-resolver.ts +49 -10
- package/src/oauth/manual-token-connection.ts +51 -3
- package/src/oauth/seed-providers.ts +3 -0
- package/src/permissions/approval-policy.test.ts +19 -5
- package/src/permissions/approval-policy.ts +14 -3
- package/src/permissions/checker.ts +21 -8
- package/src/platform/client.test.ts +24 -1
- package/src/platform/client.ts +8 -0
- package/src/platform/feature-gate.ts +15 -0
- package/src/plugins/defaults/injectors.ts +2 -8
- package/src/plugins/defaults/persistence.ts +25 -6
- package/src/plugins/types.ts +57 -13
- package/src/proactive-artifact/job.test.ts +1 -0
- package/src/prompts/__tests__/system-prompt.test.ts +4 -4
- package/src/prompts/system-prompt.ts +38 -40
- package/src/prompts/template-detection.ts +10 -4
- package/src/prompts/templates/BOOTSTRAP.md +7 -11
- package/src/prompts/templates/IDENTITY.md +0 -2
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -4
- package/src/providers/__tests__/registry-native-web-search.test.ts +122 -0
- package/src/providers/call-site-routing.ts +33 -9
- package/src/providers/connection-model-compat.ts +23 -0
- package/src/providers/connection-resolution.ts +39 -20
- package/src/providers/fireworks/client.ts +1 -0
- package/src/providers/gemini/client.ts +24 -3
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +0 -2
- package/src/providers/inference/__tests__/base-url-security.test.ts +2 -3
- package/src/providers/inference/__tests__/{connections-status-label.test.ts → connections-label.test.ts} +12 -111
- package/src/providers/inference/auth.ts +0 -8
- package/src/providers/inference/connections.ts +3 -66
- package/src/providers/inference/resolve-auth.ts +2 -3
- package/src/providers/model-catalog.ts +35 -1
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +157 -5
- package/src/providers/openai/chat-completions-provider.ts +110 -12
- package/src/providers/openai/codex-models.ts +2 -0
- package/src/providers/openai/responses-provider.ts +53 -53
- package/src/providers/openrouter/client.ts +13 -8
- package/src/providers/provider-send-message.ts +18 -9
- package/src/providers/registry.ts +48 -8
- package/src/providers/retry.ts +16 -4
- package/src/providers/search-provider-catalog.ts +17 -9
- package/src/providers/types.ts +9 -0
- package/src/runtime/__tests__/agent-wake.test.ts +1 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +1 -0
- package/src/runtime/access-request-helper.ts +1 -0
- package/src/runtime/auth/route-policy.ts +10 -0
- package/src/runtime/channel-readiness-service.ts +68 -0
- package/src/runtime/channel-reply-delivery.ts +23 -0
- package/src/runtime/channel-retry-sweep.ts +47 -14
- package/src/runtime/confirmation-request-guardian-bridge.ts +1 -1
- package/src/runtime/migrations/vbundle-builder.ts +3 -2
- package/src/runtime/routes/__tests__/bookmark-routes.test.ts +1 -0
- package/src/runtime/routes/__tests__/conversation-compaction-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +98 -0
- package/src/runtime/routes/__tests__/heartbeat-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +209 -1
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +13 -50
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +51 -3
- package/src/runtime/routes/__tests__/memory-v3-simulate-params.test.ts +35 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +3 -2
- package/src/runtime/routes/__tests__/surface-content-routes.test.ts +294 -0
- package/src/runtime/routes/__tests__/task-routes.test.ts +48 -3
- package/src/runtime/routes/acp-routes-list.test.ts +3 -0
- package/src/runtime/routes/app-management-routes.ts +111 -4
- package/src/runtime/routes/background-wake-routes.ts +188 -20
- package/src/runtime/routes/btw-routes.ts +4 -4
- package/src/runtime/routes/conversation-analysis-routes.ts +6 -0
- package/src/runtime/routes/conversation-compaction-routes.ts +263 -0
- package/src/runtime/routes/conversation-list-routes.ts +147 -0
- package/src/runtime/routes/conversation-management-routes.ts +39 -14
- package/src/runtime/routes/conversation-query-routes.ts +60 -10
- package/src/runtime/routes/conversation-routes.ts +186 -140
- package/src/runtime/routes/conversations-import-routes.ts +19 -6
- package/src/runtime/routes/documents-routes.ts +10 -1
- package/src/runtime/routes/group-routes.ts +11 -0
- package/src/runtime/routes/home-feed-routes.ts +129 -0
- package/src/runtime/routes/identity-intro-cache.ts +61 -16
- package/src/runtime/routes/identity-routes.ts +30 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +530 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +57 -8
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-provider-connection-routes.ts +5 -26
- package/src/runtime/routes/integrations/vercel.ts +15 -0
- package/src/runtime/routes/llm-context-normalization.ts +7 -2
- package/src/runtime/routes/memory-v3-routes.ts +160 -2
- package/src/runtime/routes/migration-routes.ts +20 -13
- package/src/runtime/routes/notification-routes.ts +63 -1
- package/src/runtime/routes/oauth-commands-routes.ts +6 -1
- package/src/runtime/routes/surface-action-routes.ts +1 -38
- package/src/runtime/routes/surface-content-routes.ts +12 -5
- package/src/runtime/routes/surface-conversation-resolver.ts +65 -0
- package/src/runtime/routes/wipe-conversation-routes.ts +3 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -0
- package/src/runtime/slack-dm-text-delivery.ts +177 -0
- package/src/runtime/sync/resource-sync-events.ts +1 -1
- package/src/runtime/tool-grant-request-helper.ts +1 -0
- package/src/schedule/schedule-store.ts +8 -1
- package/src/schedule/scheduler.ts +111 -15
- package/src/security/__tests__/provider-key-env-fallback.test.ts +3 -3
- package/src/security/encrypted-store.ts +7 -16
- package/src/security/store-path-override.ts +61 -0
- package/src/signals/user-message.ts +5 -8
- package/src/skills/validate-input.ts +177 -0
- package/src/subagent/manager.ts +13 -13
- package/src/subagent/types.ts +6 -0
- package/src/tasks/tool-sanitizer.ts +2 -2
- package/src/tools/apps/definitions.ts +35 -21
- package/src/tools/browser/__tests__/browser-execution-acquire.test.ts +2 -8
- package/src/tools/computer-use/definitions.ts +268 -266
- package/src/tools/document/document-tool.ts +131 -8
- package/src/tools/execution-target.ts +2 -5
- package/src/tools/executor.ts +18 -55
- package/src/tools/host-filesystem/edit.test.ts +1 -0
- package/src/tools/host-filesystem/read.test.ts +1 -0
- package/src/tools/host-filesystem/transfer.test.ts +31 -6
- package/src/tools/host-filesystem/write.test.ts +1 -0
- package/src/tools/mcp/mcp-tool-factory.ts +0 -2
- package/src/tools/network/__tests__/managed-search-proxy.test.ts +282 -0
- package/src/tools/network/__tests__/web-search.test.ts +211 -3
- package/src/tools/network/managed-search-proxy.ts +183 -0
- package/src/tools/network/web-search.ts +199 -44
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/registry.ts +146 -103
- package/src/tools/schedule/create.ts +1 -1
- package/src/tools/skills/skill-tool-factory.ts +17 -36
- package/src/tools/subagent/spawn.ts +3 -0
- package/src/tools/tool-approval-handler.ts +10 -4
- package/src/tools/tool-name-aliases.ts +72 -14
- package/src/tools/types.ts +17 -15
- package/src/tools/ui-surface/definitions.ts +98 -86
- package/src/types/onboarding-context.ts +6 -0
- package/src/usage/attribution.ts +32 -1
- package/src/util/browser.ts +7 -2
- package/src/workspace/migrations/090-memory-router-cost-optimized-profile.ts +109 -0
- package/src/workspace/migrations/091-retighten-migration-onboarding-thread.ts +41 -0
- package/src/workspace/migrations/registry.ts +4 -0
|
@@ -24,7 +24,6 @@ describe("openai-compatible adapter factory", () => {
|
|
|
24
24
|
name: "my-vllm",
|
|
25
25
|
provider: "openai-compatible",
|
|
26
26
|
auth: { type: "api_key", credential: "cred-vllm" },
|
|
27
|
-
status: "active",
|
|
28
27
|
label: "vLLM",
|
|
29
28
|
baseUrl: "http://localhost:8080/v1",
|
|
30
29
|
models: [{ id: "my-model" }],
|
|
@@ -52,7 +51,6 @@ describe("openai-compatible adapter factory", () => {
|
|
|
52
51
|
name: "my-vllm",
|
|
53
52
|
provider: "openai-compatible",
|
|
54
53
|
auth: { type: "none" },
|
|
55
|
-
status: "active",
|
|
56
54
|
label: null,
|
|
57
55
|
baseUrl: "http://localhost:8080/v1",
|
|
58
56
|
models: [{ id: "my-model" }],
|
|
@@ -7,6 +7,7 @@ import { migrateCreateProviderConnections } from "../../../memory/migrations/243
|
|
|
7
7
|
import { migrateProviderConnectionStatusLabel } from "../../../memory/migrations/244-provider-connection-status-label.js";
|
|
8
8
|
import { migrateProviderConnectionBaseUrlAndModels } from "../../../memory/migrations/250-provider-connection-base-url-and-models.js";
|
|
9
9
|
import { migrateStripBaseUrlNonOpenaiCompatible } from "../../../memory/migrations/257-strip-base-url-non-openai-compatible.js";
|
|
10
|
+
import { migrateDropProviderConnectionStatus } from "../../../memory/migrations/265-drop-provider-connection-status.js";
|
|
10
11
|
import * as schema from "../../../memory/schema.js";
|
|
11
12
|
import { providerConnections } from "../../../memory/schema/inference.js";
|
|
12
13
|
import { getConnection } from "../connections.js";
|
|
@@ -22,6 +23,7 @@ function bootDb() {
|
|
|
22
23
|
const db = createTestDb();
|
|
23
24
|
migrateCreateProviderConnections(db);
|
|
24
25
|
migrateProviderConnectionStatusLabel(db);
|
|
26
|
+
migrateDropProviderConnectionStatus(db);
|
|
25
27
|
migrateProviderConnectionBaseUrlAndModels(db);
|
|
26
28
|
return db;
|
|
27
29
|
}
|
|
@@ -42,7 +44,6 @@ describe("migration 257: strip base_url from non-openai-compatible connections",
|
|
|
42
44
|
name: "bad-anthropic",
|
|
43
45
|
provider: "anthropic",
|
|
44
46
|
auth: JSON.stringify({ type: "api_key", credential: "cred-abc" }),
|
|
45
|
-
status: "active",
|
|
46
47
|
baseUrl: "https://evil.example.com/v1",
|
|
47
48
|
createdAt: now,
|
|
48
49
|
updatedAt: now,
|
|
@@ -65,7 +66,6 @@ describe("migration 257: strip base_url from non-openai-compatible connections",
|
|
|
65
66
|
name: "good-vllm",
|
|
66
67
|
provider: "openai-compatible",
|
|
67
68
|
auth: JSON.stringify({ type: "api_key", credential: "cred-vllm" }),
|
|
68
|
-
status: "active",
|
|
69
69
|
baseUrl: "https://my-vllm.example.com/v1",
|
|
70
70
|
models: JSON.stringify([{ id: "my-model" }]),
|
|
71
71
|
createdAt: now,
|
|
@@ -89,7 +89,6 @@ describe("migration 257: strip base_url from non-openai-compatible connections",
|
|
|
89
89
|
name: "bad-openai",
|
|
90
90
|
provider: "openai",
|
|
91
91
|
auth: JSON.stringify({ type: "api_key", credential: "cred-abc" }),
|
|
92
|
-
status: "active",
|
|
93
92
|
baseUrl: "https://evil.example.com/v1",
|
|
94
93
|
createdAt: now,
|
|
95
94
|
updatedAt: now,
|
|
@@ -6,10 +6,10 @@ import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
|
6
6
|
import { migrateCreateProviderConnections } from "../../../memory/migrations/243-provider-connections.js";
|
|
7
7
|
import { migrateProviderConnectionStatusLabel } from "../../../memory/migrations/244-provider-connection-status-label.js";
|
|
8
8
|
import { migrateProviderConnectionBaseUrlAndModels } from "../../../memory/migrations/250-provider-connection-base-url-and-models.js";
|
|
9
|
+
import { migrateDropProviderConnectionStatus } from "../../../memory/migrations/265-drop-provider-connection-status.js";
|
|
9
10
|
import * as schema from "../../../memory/schema.js";
|
|
10
11
|
import {
|
|
11
12
|
createConnection,
|
|
12
|
-
disableManagedConnectionsForByokHatch,
|
|
13
13
|
getConnection,
|
|
14
14
|
listConnections,
|
|
15
15
|
seedCanonicalConnections,
|
|
@@ -25,13 +25,17 @@ function createTestDb() {
|
|
|
25
25
|
function bootDb() {
|
|
26
26
|
const db = createTestDb();
|
|
27
27
|
migrateCreateProviderConnections(db);
|
|
28
|
+
// 244 adds status + label columns. 265 drops status. This mirrors the
|
|
29
|
+
// production migration sequence so the Drizzle schema (which no longer
|
|
30
|
+
// declares status) stays consistent with the DB shape.
|
|
28
31
|
migrateProviderConnectionStatusLabel(db);
|
|
32
|
+
migrateDropProviderConnectionStatus(db);
|
|
29
33
|
migrateProviderConnectionBaseUrlAndModels(db);
|
|
30
34
|
return db;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
|
-
describe("connection CRUD
|
|
34
|
-
test("new connection without
|
|
37
|
+
describe("connection CRUD label defaults", () => {
|
|
38
|
+
test("new connection without label gets label=null", () => {
|
|
35
39
|
const db = bootDb();
|
|
36
40
|
const result = createConnection(db, {
|
|
37
41
|
name: "my-conn",
|
|
@@ -40,61 +44,38 @@ describe("connection CRUD status + label defaults", () => {
|
|
|
40
44
|
});
|
|
41
45
|
expect(result.ok).toBe(true);
|
|
42
46
|
if (result.ok) {
|
|
43
|
-
expect(result.connection.status).toBe("active");
|
|
44
47
|
expect(result.connection.label).toBeNull();
|
|
45
48
|
}
|
|
46
49
|
});
|
|
47
50
|
|
|
48
|
-
test("createConnection passes explicit
|
|
51
|
+
test("createConnection passes explicit label", () => {
|
|
49
52
|
const db = bootDb();
|
|
50
53
|
const result = createConnection(db, {
|
|
51
|
-
name: "
|
|
54
|
+
name: "labeled-conn",
|
|
52
55
|
provider: "openai",
|
|
53
56
|
auth: { type: "platform" },
|
|
54
|
-
status: "disabled",
|
|
55
57
|
label: "My OpenAI",
|
|
56
58
|
});
|
|
57
59
|
expect(result.ok).toBe(true);
|
|
58
60
|
if (result.ok) {
|
|
59
|
-
expect(result.connection.status).toBe("disabled");
|
|
60
61
|
expect(result.connection.label).toBe("My OpenAI");
|
|
61
62
|
}
|
|
62
63
|
});
|
|
63
64
|
|
|
64
|
-
test("getConnection returns
|
|
65
|
+
test("getConnection returns label from DB", () => {
|
|
65
66
|
const db = bootDb();
|
|
66
67
|
createConnection(db, {
|
|
67
68
|
name: "get-me",
|
|
68
69
|
provider: "gemini",
|
|
69
70
|
auth: { type: "platform" },
|
|
70
|
-
status: "disabled",
|
|
71
71
|
label: "Gemini Pro",
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
const conn = getConnection(db, "get-me");
|
|
75
75
|
expect(conn).not.toBeNull();
|
|
76
|
-
expect(conn!.status).toBe("disabled");
|
|
77
76
|
expect(conn!.label).toBe("Gemini Pro");
|
|
78
77
|
});
|
|
79
78
|
|
|
80
|
-
test("updateConnection updates status", () => {
|
|
81
|
-
const db = bootDb();
|
|
82
|
-
createConnection(db, {
|
|
83
|
-
name: "toggle-me",
|
|
84
|
-
provider: "anthropic",
|
|
85
|
-
auth: { type: "platform" },
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
const result = updateConnection(db, "toggle-me", {
|
|
89
|
-
auth: { type: "platform" },
|
|
90
|
-
status: "disabled",
|
|
91
|
-
});
|
|
92
|
-
expect(result.ok).toBe(true);
|
|
93
|
-
if (result.ok) {
|
|
94
|
-
expect(result.connection.status).toBe("disabled");
|
|
95
|
-
}
|
|
96
|
-
});
|
|
97
|
-
|
|
98
79
|
test("updateConnection clears label when set to null", () => {
|
|
99
80
|
const db = bootDb();
|
|
100
81
|
createConnection(db, {
|
|
@@ -116,7 +97,7 @@ describe("connection CRUD status + label defaults", () => {
|
|
|
116
97
|
});
|
|
117
98
|
|
|
118
99
|
describe("seedCanonicalConnections labels", () => {
|
|
119
|
-
test("first boot seeds default labels on all
|
|
100
|
+
test("first boot seeds default labels on all managed connections", () => {
|
|
120
101
|
const db = bootDb();
|
|
121
102
|
seedCanonicalConnections(db);
|
|
122
103
|
|
|
@@ -162,7 +143,7 @@ describe("seedCanonicalConnections labels", () => {
|
|
|
162
143
|
expect(after?.label).toBe("Anthropic");
|
|
163
144
|
});
|
|
164
145
|
|
|
165
|
-
test("backfill
|
|
146
|
+
test("backfill fills null label on subsequent boot", () => {
|
|
166
147
|
const db = bootDb();
|
|
167
148
|
seedCanonicalConnections(db);
|
|
168
149
|
|
|
@@ -183,83 +164,3 @@ describe("seedCanonicalConnections labels", () => {
|
|
|
183
164
|
expect(conn?.label).toBe("OpenAI");
|
|
184
165
|
});
|
|
185
166
|
});
|
|
186
|
-
|
|
187
|
-
describe("disableManagedConnectionsForByokHatch", () => {
|
|
188
|
-
test("flips all three canonical managed connections to status='disabled'", () => {
|
|
189
|
-
const db = bootDb();
|
|
190
|
-
seedCanonicalConnections(db);
|
|
191
|
-
|
|
192
|
-
// Sanity: seeded rows default to active.
|
|
193
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("active");
|
|
194
|
-
expect(getConnection(db, "openai-managed")?.status).toBe("active");
|
|
195
|
-
expect(getConnection(db, "gemini-managed")?.status).toBe("active");
|
|
196
|
-
|
|
197
|
-
disableManagedConnectionsForByokHatch(db);
|
|
198
|
-
|
|
199
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("disabled");
|
|
200
|
-
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
201
|
-
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
test("leaves an excluded managed connection active", () => {
|
|
205
|
-
const db = bootDb();
|
|
206
|
-
seedCanonicalConnections(db);
|
|
207
|
-
|
|
208
|
-
disableManagedConnectionsForByokHatch(db, {
|
|
209
|
-
excludeConnection: "anthropic-managed",
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("active");
|
|
213
|
-
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
214
|
-
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
test("subsequent seedCanonicalConnections call does NOT re-flip a user-re-enabled connection", () => {
|
|
218
|
-
// Models the post-hatch lifecycle: at hatch we disable; the user
|
|
219
|
-
// later flips one back to active (e.g. after Vellum login). Every
|
|
220
|
-
// subsequent daemon boot runs seedCanonicalConnections — and that
|
|
221
|
-
// boot must NOT revert the user's choice. The hatch-disable helper
|
|
222
|
-
// is only ever called from the seedInferenceProfiles hatch branch,
|
|
223
|
-
// so it does not run on a non-hatch boot; this test confirms the
|
|
224
|
-
// ambient seed pass leaves status alone.
|
|
225
|
-
const db = bootDb();
|
|
226
|
-
seedCanonicalConnections(db);
|
|
227
|
-
disableManagedConnectionsForByokHatch(db);
|
|
228
|
-
|
|
229
|
-
// User re-enables anthropic post-hatch.
|
|
230
|
-
updateConnection(db, "anthropic-managed", {
|
|
231
|
-
auth: { type: "platform" },
|
|
232
|
-
status: "active",
|
|
233
|
-
});
|
|
234
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("active");
|
|
235
|
-
|
|
236
|
-
// Simulate a normal restart: seedCanonicalConnections runs every boot,
|
|
237
|
-
// disableManagedConnectionsForByokHatch does NOT.
|
|
238
|
-
seedCanonicalConnections(db);
|
|
239
|
-
|
|
240
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("active");
|
|
241
|
-
// The two the user didn't touch stay disabled.
|
|
242
|
-
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
243
|
-
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
test("idempotent re-hatch leaves all three at disabled", () => {
|
|
247
|
-
// Workspace reset / re-hatch scenario: helper runs again and any
|
|
248
|
-
// user re-enable from before the reset is intentionally undone —
|
|
249
|
-
// re-hatch means re-onboard.
|
|
250
|
-
const db = bootDb();
|
|
251
|
-
seedCanonicalConnections(db);
|
|
252
|
-
disableManagedConnectionsForByokHatch(db);
|
|
253
|
-
|
|
254
|
-
updateConnection(db, "anthropic-managed", {
|
|
255
|
-
auth: { type: "platform" },
|
|
256
|
-
status: "active",
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
disableManagedConnectionsForByokHatch(db);
|
|
260
|
-
|
|
261
|
-
expect(getConnection(db, "anthropic-managed")?.status).toBe("disabled");
|
|
262
|
-
expect(getConnection(db, "openai-managed")?.status).toBe("disabled");
|
|
263
|
-
expect(getConnection(db, "gemini-managed")?.status).toBe("disabled");
|
|
264
|
-
});
|
|
265
|
-
});
|
|
@@ -79,13 +79,6 @@ export const ConnectionProviderSchema = z.enum(
|
|
|
79
79
|
VALID_CONNECTION_PROVIDERS as readonly [string, ...string[]],
|
|
80
80
|
);
|
|
81
81
|
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
// Connection status
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
|
|
86
|
-
export const ConnectionStatusSchema = z.enum(["active", "disabled"]);
|
|
87
|
-
export type ConnectionStatus = z.infer<typeof ConnectionStatusSchema>;
|
|
88
|
-
|
|
89
82
|
// ---------------------------------------------------------------------------
|
|
90
83
|
// Per-connection model entries (openai-compatible)
|
|
91
84
|
// ---------------------------------------------------------------------------
|
|
@@ -104,7 +97,6 @@ export const ProviderConnectionSchema = z.object({
|
|
|
104
97
|
name: z.string().min(1),
|
|
105
98
|
provider: ConnectionProviderSchema,
|
|
106
99
|
auth: AuthSchema,
|
|
107
|
-
status: ConnectionStatusSchema,
|
|
108
100
|
label: z.string().min(1).nullable(),
|
|
109
101
|
baseUrl: z.string().url().nullable(),
|
|
110
102
|
models: z.array(ConnectionModelSchema).nullable(),
|
|
@@ -11,8 +11,6 @@ import {
|
|
|
11
11
|
ConnectionModelSchema,
|
|
12
12
|
type ConnectionProvider,
|
|
13
13
|
ConnectionProviderSchema,
|
|
14
|
-
type ConnectionStatus,
|
|
15
|
-
ConnectionStatusSchema,
|
|
16
14
|
type ProviderConnection,
|
|
17
15
|
VALID_CONNECTION_PROVIDERS,
|
|
18
16
|
} from "./auth.js";
|
|
@@ -55,16 +53,11 @@ export function listConnections(
|
|
|
55
53
|
if (!auth.success) return [];
|
|
56
54
|
const provider = ConnectionProviderSchema.safeParse(row.provider);
|
|
57
55
|
if (!provider.success) return [];
|
|
58
|
-
const statusResult = ConnectionStatusSchema.safeParse(row.status);
|
|
59
|
-
const status: ConnectionStatus = statusResult.success
|
|
60
|
-
? statusResult.data
|
|
61
|
-
: "active";
|
|
62
56
|
return [
|
|
63
57
|
{
|
|
64
58
|
...row,
|
|
65
59
|
auth: auth.data,
|
|
66
60
|
provider: provider.data,
|
|
67
|
-
status,
|
|
68
61
|
label: row.label ?? null,
|
|
69
62
|
baseUrl: row.baseUrl ?? null,
|
|
70
63
|
models: parseModelsColumn(row.models),
|
|
@@ -89,15 +82,10 @@ export function getConnection(
|
|
|
89
82
|
if (!auth.success) return null;
|
|
90
83
|
const provider = ConnectionProviderSchema.safeParse(row.provider);
|
|
91
84
|
if (!provider.success) return null;
|
|
92
|
-
const statusResult = ConnectionStatusSchema.safeParse(row.status);
|
|
93
|
-
const status: ConnectionStatus = statusResult.success
|
|
94
|
-
? statusResult.data
|
|
95
|
-
: "active";
|
|
96
85
|
return {
|
|
97
86
|
...row,
|
|
98
87
|
auth: auth.data,
|
|
99
88
|
provider: provider.data,
|
|
100
|
-
status,
|
|
101
89
|
label: row.label ?? null,
|
|
102
90
|
baseUrl: row.baseUrl ?? null,
|
|
103
91
|
models: parseModelsColumn(row.models),
|
|
@@ -113,7 +101,6 @@ export type CreateConnectionInput = {
|
|
|
113
101
|
name: string;
|
|
114
102
|
provider: string;
|
|
115
103
|
auth: Auth;
|
|
116
|
-
status?: ConnectionStatus;
|
|
117
104
|
label?: string | null;
|
|
118
105
|
baseUrl?: string | null;
|
|
119
106
|
models?: ConnectionModel[] | null;
|
|
@@ -121,7 +108,6 @@ export type CreateConnectionInput = {
|
|
|
121
108
|
|
|
122
109
|
export type UpdateConnectionInput = {
|
|
123
110
|
auth: Auth;
|
|
124
|
-
status?: ConnectionStatus;
|
|
125
111
|
label?: string | null;
|
|
126
112
|
baseUrl?: string | null;
|
|
127
113
|
models?: ConnectionModel[] | null;
|
|
@@ -173,7 +159,6 @@ export function createConnection(
|
|
|
173
159
|
return { ok: false, error: { code: "already_exists" } };
|
|
174
160
|
}
|
|
175
161
|
|
|
176
|
-
const status = input.status ?? "active";
|
|
177
162
|
const label = input.label ?? null;
|
|
178
163
|
const baseUrl = input.baseUrl ?? null;
|
|
179
164
|
const models = input.models ?? null;
|
|
@@ -191,7 +176,6 @@ export function createConnection(
|
|
|
191
176
|
name: input.name,
|
|
192
177
|
provider,
|
|
193
178
|
auth: JSON.stringify(authResult.data),
|
|
194
|
-
status,
|
|
195
179
|
label,
|
|
196
180
|
baseUrl,
|
|
197
181
|
models: models === null ? null : JSON.stringify(models),
|
|
@@ -210,7 +194,6 @@ export function createConnection(
|
|
|
210
194
|
name: input.name,
|
|
211
195
|
provider,
|
|
212
196
|
auth: authResult.data,
|
|
213
|
-
status,
|
|
214
197
|
label,
|
|
215
198
|
baseUrl,
|
|
216
199
|
models,
|
|
@@ -255,12 +238,10 @@ export function updateConnection(
|
|
|
255
238
|
const setClause: {
|
|
256
239
|
auth: string;
|
|
257
240
|
updatedAt: number;
|
|
258
|
-
status?: string;
|
|
259
241
|
label?: string | null;
|
|
260
242
|
baseUrl?: string | null;
|
|
261
243
|
models?: string | null;
|
|
262
244
|
} = { auth: JSON.stringify(authResult.data), updatedAt: now };
|
|
263
|
-
if (input.status !== undefined) setClause.status = input.status;
|
|
264
245
|
if (input.label !== undefined) setClause.label = input.label;
|
|
265
246
|
if (input.baseUrl !== undefined) setClause.baseUrl = input.baseUrl;
|
|
266
247
|
if (input.models !== undefined)
|
|
@@ -280,7 +261,6 @@ export function updateConnection(
|
|
|
280
261
|
connection: {
|
|
281
262
|
...existing,
|
|
282
263
|
auth: authResult.data,
|
|
283
|
-
status: input.status !== undefined ? input.status : existing.status,
|
|
284
264
|
label: input.label !== undefined ? input.label : existing.label,
|
|
285
265
|
baseUrl: nextBaseUrl,
|
|
286
266
|
models: nextModels,
|
|
@@ -376,10 +356,9 @@ const CANONICAL_CONNECTIONS: Array<{
|
|
|
376
356
|
* blocking prevents a confusing delete → re-appear loop).
|
|
377
357
|
* - PATCH that changes `auth` is blocked (auth is locked to `{type:"platform"}`
|
|
378
358
|
* so any other value would be reverted on the next boot upsert).
|
|
379
|
-
* - PATCH that changes `label`
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
* on subsequent boots so pre-seed installs pick up the default; a non-null
|
|
359
|
+
* - PATCH that changes `label` is allowed — users may legitimately relabel the
|
|
360
|
+
* managed connection. `label` is seeded on initial INSERT and backfilled when
|
|
361
|
+
* null on subsequent boots so pre-seed installs pick up the default; a non-null
|
|
383
362
|
* user-customized label is preserved (see `seedCanonicalConnections`).
|
|
384
363
|
*
|
|
385
364
|
* Mirrors `MANAGED_PROFILE_NAMES` (config/seed-inference-profiles.ts).
|
|
@@ -399,14 +378,6 @@ export const MANAGED_CONNECTION_NAMES: ReadonlySet<string> = new Set(
|
|
|
399
378
|
* customization is preserved; the separate backfill step below assigns the
|
|
400
379
|
* default only when the existing row has `label IS NULL`, covering installs
|
|
401
380
|
* that pre-date the label seed.
|
|
402
|
-
*
|
|
403
|
-
* Status handling: the upsert never touches `status` so user customization
|
|
404
|
-
* is preserved across reboots. New rows default to `status: "active"` via the
|
|
405
|
-
* column default. Off-platform installs flip canonical managed rows to
|
|
406
|
-
* `status: "disabled"` ONCE at hatch time via
|
|
407
|
-
* `disableManagedConnectionsForByokHatch` (called from `seedInferenceProfiles`
|
|
408
|
-
* when `isHatch && !isPlatform`); subsequent boots leave whatever the user
|
|
409
|
-
* has chosen alone, so a post-hatch re-enable persists.
|
|
410
381
|
*/
|
|
411
382
|
export function seedCanonicalConnections(db: DrizzleDb): void {
|
|
412
383
|
const now = Date.now();
|
|
@@ -445,37 +416,3 @@ export function seedCanonicalConnections(db: DrizzleDb): void {
|
|
|
445
416
|
}
|
|
446
417
|
}
|
|
447
418
|
|
|
448
|
-
/**
|
|
449
|
-
* Flip canonical managed connections to `status: "disabled"` at
|
|
450
|
-
* hatch time on BYOK (off-platform) installs.
|
|
451
|
-
*
|
|
452
|
-
* Why hatch-time only: managed connections need platform auth that a fresh
|
|
453
|
-
* BYOK user doesn't have yet, so surfacing them as enabled in the picker
|
|
454
|
-
* would let users pick an unusable option on day one. But this is a
|
|
455
|
-
* first-time-only default — the moment the user explicitly flips one
|
|
456
|
-
* back to active (e.g. after logging into Vellum), we never want a daemon
|
|
457
|
-
* restart to revert that. `seedCanonicalConnections` leaves `status` alone
|
|
458
|
-
* on the UPDATE path, and this helper is invoked ONLY from
|
|
459
|
-
* `seedInferenceProfiles`'s `isHatch && !isPlatform` branch. Subsequent
|
|
460
|
-
* non-hatch boots never call it.
|
|
461
|
-
*
|
|
462
|
-
* Idempotent: a second hatch (workspace reset) re-disables the rows, which
|
|
463
|
-
* is the right call — re-hatch means re-onboard.
|
|
464
|
-
*
|
|
465
|
-
* When onboarding explicitly selected a managed profile, callers may exclude
|
|
466
|
-
* that selected connection so the managed route remains usable for the first
|
|
467
|
-
* post-onboarding message.
|
|
468
|
-
*/
|
|
469
|
-
export function disableManagedConnectionsForByokHatch(
|
|
470
|
-
db: DrizzleDb,
|
|
471
|
-
options: { excludeConnection?: string } = {},
|
|
472
|
-
): void {
|
|
473
|
-
const now = Date.now();
|
|
474
|
-
for (const name of MANAGED_CONNECTION_NAMES) {
|
|
475
|
-
if (name === options.excludeConnection) continue;
|
|
476
|
-
db.update(providerConnections)
|
|
477
|
-
.set({ status: "disabled", updatedAt: now })
|
|
478
|
-
.where(eq(providerConnections.name, name))
|
|
479
|
-
.run();
|
|
480
|
-
}
|
|
481
|
-
}
|
|
@@ -89,9 +89,8 @@ export async function resolveAuth(
|
|
|
89
89
|
// we need the prefix "credential/openai-codex" for the refresh logic.
|
|
90
90
|
const credentialPrefix = auth.credential.replace(/\/access_token$/, "");
|
|
91
91
|
|
|
92
|
-
const { getValidCodexAccessToken } =
|
|
93
|
-
"./codex-token-refresh.js"
|
|
94
|
-
);
|
|
92
|
+
const { getValidCodexAccessToken } =
|
|
93
|
+
await import("./codex-token-refresh.js");
|
|
95
94
|
const token = await getValidCodexAccessToken(credentialPrefix);
|
|
96
95
|
|
|
97
96
|
if (!token) {
|
|
@@ -144,6 +144,23 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
144
144
|
linkLabel: "Open Anthropic Console",
|
|
145
145
|
},
|
|
146
146
|
models: [
|
|
147
|
+
{
|
|
148
|
+
id: "claude-opus-4-8",
|
|
149
|
+
displayName: "Claude Opus 4.8",
|
|
150
|
+
contextWindowTokens: 1000000,
|
|
151
|
+
maxOutputTokens: 128000,
|
|
152
|
+
longContextPricingThresholdTokens: 200000,
|
|
153
|
+
supportsThinking: true,
|
|
154
|
+
supportsCaching: true,
|
|
155
|
+
supportsVision: true,
|
|
156
|
+
supportsToolUse: true,
|
|
157
|
+
pricing: {
|
|
158
|
+
inputPer1mTokens: 5,
|
|
159
|
+
outputPer1mTokens: 25,
|
|
160
|
+
cacheWritePer1mTokens: 6.25,
|
|
161
|
+
cacheReadPer1mTokens: 0.5,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
147
164
|
{
|
|
148
165
|
id: "claude-opus-4-7",
|
|
149
166
|
displayName: "Claude Opus 4.7",
|
|
@@ -244,7 +261,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
244
261
|
},
|
|
245
262
|
},
|
|
246
263
|
],
|
|
247
|
-
defaultModel: "claude-opus-4-
|
|
264
|
+
defaultModel: "claude-opus-4-8",
|
|
248
265
|
apiKeyUrl: "https://console.anthropic.com/settings/keys",
|
|
249
266
|
apiKeyPlaceholder: "sk-ant-api03-...",
|
|
250
267
|
},
|
|
@@ -703,6 +720,23 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
703
720
|
// OpenRouter proxies anthropic/* through Anthropic's Messages API, so
|
|
704
721
|
// prompt caching and cache TTL metadata pass through unchanged and
|
|
705
722
|
// billing matches Anthropic's direct rates.
|
|
723
|
+
{
|
|
724
|
+
id: "anthropic/claude-opus-4.8",
|
|
725
|
+
displayName: "Claude Opus 4.8",
|
|
726
|
+
contextWindowTokens: 1000000,
|
|
727
|
+
maxOutputTokens: 128000,
|
|
728
|
+
longContextPricingThresholdTokens: 200000,
|
|
729
|
+
supportsThinking: true,
|
|
730
|
+
supportsCaching: true,
|
|
731
|
+
supportsVision: true,
|
|
732
|
+
supportsToolUse: true,
|
|
733
|
+
pricing: {
|
|
734
|
+
inputPer1mTokens: 5,
|
|
735
|
+
outputPer1mTokens: 25,
|
|
736
|
+
cacheWritePer1mTokens: 6.25,
|
|
737
|
+
cacheReadPer1mTokens: 0.5,
|
|
738
|
+
},
|
|
739
|
+
},
|
|
706
740
|
{
|
|
707
741
|
id: "anthropic/claude-opus-4.7",
|
|
708
742
|
displayName: "Claude Opus 4.7",
|
|
@@ -13,7 +13,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
|
13
13
|
anthropic: {
|
|
14
14
|
balanced: "claude-sonnet-4-6",
|
|
15
15
|
"latency-optimized": "claude-haiku-4-5-20251001",
|
|
16
|
-
"quality-optimized": "claude-opus-4-
|
|
16
|
+
"quality-optimized": "claude-opus-4-8",
|
|
17
17
|
"vision-optimized": "claude-opus-4-6",
|
|
18
18
|
},
|
|
19
19
|
openai: {
|
|
@@ -43,12 +43,12 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
|
43
43
|
openrouter: {
|
|
44
44
|
balanced: "anthropic/claude-sonnet-4.6",
|
|
45
45
|
"latency-optimized": "anthropic/claude-haiku-4.5",
|
|
46
|
-
"quality-optimized": "anthropic/claude-opus-4.
|
|
46
|
+
"quality-optimized": "anthropic/claude-opus-4.8",
|
|
47
47
|
"vision-optimized": "anthropic/claude-opus-4.6",
|
|
48
48
|
},
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
-
const FALLBACK_DEFAULT_MODEL = "claude-opus-4-
|
|
51
|
+
const FALLBACK_DEFAULT_MODEL = "claude-opus-4-8";
|
|
52
52
|
|
|
53
53
|
const MODEL_INTENTS = new Set<ModelIntent>([
|
|
54
54
|
"balanced",
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import OpenAI from "openai";
|
|
4
|
+
|
|
5
|
+
import { extractApiErrorDetail } from "../chat-completions-provider.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Construct a real `OpenAI.APIError` so production code paths that use
|
|
9
|
+
* `instanceof OpenAI.APIError` and SDK-defined getters keep matching.
|
|
10
|
+
*/
|
|
11
|
+
function buildApiError(
|
|
12
|
+
status: number,
|
|
13
|
+
body: unknown,
|
|
14
|
+
headers?: Headers,
|
|
15
|
+
): InstanceType<typeof OpenAI.APIError> {
|
|
16
|
+
return new OpenAI.APIError(
|
|
17
|
+
status,
|
|
18
|
+
body as Record<string, unknown>,
|
|
19
|
+
undefined,
|
|
20
|
+
headers ?? new Headers(),
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
describe("extractApiErrorDetail", () => {
|
|
25
|
+
test("serializes a structured body so logs show the upstream error", () => {
|
|
26
|
+
// This is the canonical OpenRouter shape that motivated the helper:
|
|
27
|
+
// the SDK's `error.message` collapses to "400 Provider returned error"
|
|
28
|
+
// and the real detail lives nested under `error.metadata.raw`.
|
|
29
|
+
const err = buildApiError(400, {
|
|
30
|
+
error: {
|
|
31
|
+
code: 400,
|
|
32
|
+
message: "Provider returned error",
|
|
33
|
+
metadata: {
|
|
34
|
+
raw: "messages.4: tool_use_id must reference a prior tool_use block",
|
|
35
|
+
provider_name: "Anthropic",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const { detail, requestId } = extractApiErrorDetail(err);
|
|
40
|
+
expect(detail).toContain("Provider returned error");
|
|
41
|
+
expect(detail).toContain("tool_use_id must reference a prior tool_use");
|
|
42
|
+
expect(detail).toContain("Anthropic");
|
|
43
|
+
expect(requestId).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("surfaces the upstream request id when present in headers", () => {
|
|
47
|
+
const headers = new Headers({ "x-request-id": "req_abc123" });
|
|
48
|
+
const err = buildApiError(
|
|
49
|
+
400,
|
|
50
|
+
{ error: { message: "Provider returned error" } },
|
|
51
|
+
headers,
|
|
52
|
+
);
|
|
53
|
+
const { requestId } = extractApiErrorDetail(err);
|
|
54
|
+
expect(requestId).toBe("req_abc123");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("falls back to x-openrouter-request-id when x-request-id is absent", () => {
|
|
58
|
+
const headers = new Headers({
|
|
59
|
+
"x-openrouter-request-id": "gen-or-xyz",
|
|
60
|
+
});
|
|
61
|
+
const err = buildApiError(
|
|
62
|
+
400,
|
|
63
|
+
{ error: { message: "Provider returned error" } },
|
|
64
|
+
headers,
|
|
65
|
+
);
|
|
66
|
+
const { requestId } = extractApiErrorDetail(err);
|
|
67
|
+
expect(requestId).toBe("gen-or-xyz");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("returns empty detail when the body is missing", () => {
|
|
71
|
+
// The SDK constructs an APIError even when the upstream response had
|
|
72
|
+
// no JSON body (e.g. network-layer 5xx). The helper must degrade
|
|
73
|
+
// gracefully rather than emit `undefined` or `null` strings.
|
|
74
|
+
const err = buildApiError(500, undefined);
|
|
75
|
+
const { detail, requestId } = extractApiErrorDetail(err);
|
|
76
|
+
expect(detail).toBe("");
|
|
77
|
+
expect(requestId).toBeUndefined();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("returns empty detail when the body serializes to an empty object", () => {
|
|
81
|
+
// `{}` carries zero signal and just adds noise to log lines.
|
|
82
|
+
const err = buildApiError(429, {});
|
|
83
|
+
const { detail } = extractApiErrorDetail(err);
|
|
84
|
+
expect(detail).toBe("");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("truncates very long bodies with an ellipsis", () => {
|
|
88
|
+
const huge = "X".repeat(5000);
|
|
89
|
+
const err = buildApiError(400, { error: { message: huge } });
|
|
90
|
+
const { detail } = extractApiErrorDetail(err);
|
|
91
|
+
// Cap is 2000 chars; the helper appends a single-char ellipsis when truncating.
|
|
92
|
+
expect(detail.length).toBeLessThanOrEqual(2001);
|
|
93
|
+
expect(detail.endsWith("…")).toBe(true);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("preserves string bodies verbatim (no double-encoding)", () => {
|
|
97
|
+
// Some providers return a non-JSON body (HTML error page, plain text).
|
|
98
|
+
// The SDK stores it on `error.error` as a string in that case.
|
|
99
|
+
const err = buildApiError(502, "upstream timeout");
|
|
100
|
+
const { detail } = extractApiErrorDetail(err);
|
|
101
|
+
expect(detail).toBe("upstream timeout");
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
test("returns empty detail when JSON.stringify throws (cyclic body)", () => {
|
|
105
|
+
// Pathological body — should not propagate the TypeError to callers.
|
|
106
|
+
// OpenAI's `APIError` constructor itself JSON.stringifies the body to
|
|
107
|
+
// build its own `.message`, so we can't get to this branch via the SDK
|
|
108
|
+
// constructor; build a structural stand-in instead.
|
|
109
|
+
const cyclic: Record<string, unknown> = {};
|
|
110
|
+
cyclic.self = cyclic;
|
|
111
|
+
const stub = {
|
|
112
|
+
status: 400,
|
|
113
|
+
message: "synthetic",
|
|
114
|
+
headers: new Headers(),
|
|
115
|
+
error: cyclic,
|
|
116
|
+
} as unknown as InstanceType<typeof OpenAI.APIError>;
|
|
117
|
+
const { detail } = extractApiErrorDetail(stub);
|
|
118
|
+
expect(detail).toBe("");
|
|
119
|
+
});
|
|
120
|
+
});
|