@vellumai/assistant 0.10.2-dev.202606250318.5e7cfb0 → 0.10.2
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/bun.lock +0 -20
- package/docs/workspace-tools.md +33 -42
- package/eslint-rules/cli-no-daemon-internals.js +0 -6
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +0 -31
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +0 -44
- package/node_modules/@vellumai/gateway-client/src/index.ts +0 -14
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +0 -17
- package/node_modules/@vellumai/service-contracts/package.json +0 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +0 -1
- package/openapi.yaml +0 -155
- package/package.json +1 -4
- package/scripts/test.sh +15 -36
- package/src/__tests__/actor-token-service.test.ts +14 -36
- package/src/__tests__/agent-loop-override-profile.test.ts +0 -1
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +0 -2
- package/src/__tests__/agent-wake-override-profile.test.ts +0 -2
- package/src/__tests__/annotate-activity-metadata.test.ts +0 -2
- package/src/__tests__/annotate-risk-options.test.ts +0 -2
- package/src/__tests__/approval-cascade.test.ts +0 -2
- package/src/__tests__/assistant-attachments.test.ts +0 -42
- package/src/__tests__/background-workers-disk-pressure.test.ts +0 -2
- package/src/__tests__/btw-routes.test.ts +0 -2
- package/src/__tests__/build-persisted-content.test.ts +0 -2
- package/src/__tests__/call-controller.test.ts +0 -19
- package/src/__tests__/channel-guardian.test.ts +58 -94
- package/src/__tests__/channel-reply-delivery.test.ts +0 -2
- package/src/__tests__/compaction-events.test.ts +0 -2
- package/src/__tests__/compaction.benchmark.test.ts +0 -2
- package/src/__tests__/compactor-call-site-logging.test.ts +0 -2
- package/src/__tests__/compactor-low-watermark-cut.test.ts +0 -2
- package/src/__tests__/compactor-preserved-tail-count.test.ts +0 -2
- package/src/__tests__/compactor-summary-call-truncation.test.ts +0 -2
- package/src/__tests__/compactor-web-search-strip.test.ts +0 -2
- package/src/__tests__/config-loader-backfill.test.ts +10 -123
- package/src/__tests__/config-schema.test.ts +0 -1
- package/src/__tests__/confirmation-request-guardian-bridge.test.ts +29 -31
- package/src/__tests__/contacts-relay-reads.test.ts +15 -13
- package/src/__tests__/conversation-abort-tool-results.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +0 -2
- package/src/__tests__/conversation-agent-loop.test.ts +0 -134
- package/src/__tests__/conversation-analysis-routes.test.ts +0 -2
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +0 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +0 -2
- package/src/__tests__/conversation-history-web-search.test.ts +0 -2
- package/src/__tests__/conversation-load-history-repair.test.ts +0 -2
- package/src/__tests__/conversation-load-history-stripped.test.ts +0 -2
- package/src/__tests__/conversation-pairing.test.ts +0 -2
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +0 -2
- package/src/__tests__/conversation-process-callsite.test.ts +0 -2
- package/src/__tests__/conversation-provider-retry-repair.test.ts +0 -2
- package/src/__tests__/conversation-queue.test.ts +0 -91
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -14
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -14
- package/src/__tests__/conversation-slash-queue.test.ts +0 -2
- package/src/__tests__/conversation-slash-unknown.test.ts +0 -2
- package/src/__tests__/conversation-speed-override.test.ts +0 -2
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +0 -29
- package/src/__tests__/conversation-title-service.test.ts +0 -2
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +0 -47
- package/src/__tests__/conversation-usage.test.ts +0 -2
- package/src/__tests__/conversation-workspace-cache-state.test.ts +0 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +0 -2
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +0 -2
- package/src/__tests__/credential-security-invariants.test.ts +1 -1
- package/src/__tests__/db-migration-rollback.test.ts +171 -205
- package/src/__tests__/db-test-helpers.ts +4 -5
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -4
- package/src/__tests__/disk-pressure-guard.test.ts +0 -41
- package/src/__tests__/dm-persistence.test.ts +0 -2
- package/src/__tests__/emit-signal-routing-intent.test.ts +5 -10
- package/src/__tests__/events-dev-bypass-actor.test.ts +1 -7
- package/src/__tests__/exploration-drift-hook.test.ts +2 -3
- package/src/__tests__/filing-service.test.ts +0 -2
- package/src/__tests__/guardian-binding-drift-heal.test.ts +10 -75
- package/src/__tests__/guardian-dispatch.test.ts +1 -95
- package/src/__tests__/guardian-outbound-http.test.ts +0 -13
- package/src/__tests__/heartbeat-disk-pressure.test.ts +0 -2
- package/src/__tests__/heartbeat-service.test.ts +0 -2
- package/src/__tests__/helpers/channel-test-adapter.ts +7 -1
- package/src/__tests__/host-app-control-routes.test.ts +30 -24
- package/src/__tests__/host-bash-routes.test.ts +41 -31
- package/src/__tests__/host-browser-routes.test.ts +32 -26
- package/src/__tests__/host-cu-routes-targeted.test.ts +33 -25
- package/src/__tests__/host-file-routes-targeted.test.ts +52 -40
- package/src/__tests__/host-transfer-routes-targeted.test.ts +43 -31
- package/src/__tests__/http-user-message-parity.test.ts +8 -290
- package/src/__tests__/inbound-invite-redemption.test.ts +0 -28
- package/src/__tests__/inbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/invite-redemption-service.test.ts +0 -198
- package/src/__tests__/llm-context-normalization.test.ts +0 -105
- package/src/__tests__/llm-request-log-error-payload.test.ts +9 -71
- package/src/__tests__/llm-usage-store.test.ts +0 -25
- package/src/__tests__/mcp-health-check.test.ts +1 -2
- package/src/__tests__/media-stream-server-integration.test.ts +0 -127
- package/src/__tests__/memory-retrieval-hook.test.ts +0 -2
- package/src/__tests__/messaging-send-tool.test.ts +0 -2
- package/src/__tests__/migration-import-from-url.test.ts +2 -2
- package/src/__tests__/mtime-cache.test.ts +5 -146
- package/src/__tests__/native-web-search.test.ts +0 -2
- package/src/__tests__/non-member-access-request.test.ts +17 -189
- package/src/__tests__/notification-broadcaster.test.ts +0 -4
- package/src/__tests__/notification-decision-recipient-context.test.ts +32 -33
- package/src/__tests__/notification-deep-link.test.ts +0 -6
- package/src/__tests__/notification-guardian-path.test.ts +0 -19
- package/src/__tests__/openai-provider.test.ts +12 -22
- package/src/__tests__/openai-responses-provider.test.ts +2 -12
- package/src/__tests__/outbound-slack-persistence.test.ts +0 -2
- package/src/__tests__/pending-interactions-resolved-event.test.ts +4 -7
- package/src/__tests__/persistence-secret-redaction.test.ts +0 -2
- package/src/__tests__/plugin-bootstrap.test.ts +73 -3
- package/src/__tests__/plugin-route-contribution.test.ts +17 -4
- package/src/__tests__/plugin-tool-contribution.test.ts +18 -3
- package/src/__tests__/plugin-types.test.ts +2 -0
- package/src/__tests__/process-message-background-slack.test.ts +0 -2
- package/src/__tests__/process-message-display-content.test.ts +0 -2
- package/src/__tests__/provider-error-scenarios.test.ts +4 -5
- package/src/__tests__/provider-usage-tracking.test.ts +0 -39
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +0 -2
- package/src/__tests__/registry.test.ts +1 -4
- package/src/__tests__/relay-server.test.ts +25 -694
- package/src/__tests__/runtime-attachment-metadata.test.ts +1 -0
- package/src/__tests__/secret-ingress-http.test.ts +0 -14
- package/src/__tests__/send-endpoint-busy.test.ts +8 -30
- package/src/__tests__/skills.test.ts +0 -44
- package/src/__tests__/slack-inbound-verification.test.ts +2 -47
- package/src/__tests__/stt-hints.test.ts +13 -44
- package/src/__tests__/subagent-detail.test.ts +0 -27
- package/src/__tests__/subagent-disposal.test.ts +0 -65
- package/src/__tests__/subagent-notify-parent.test.ts +0 -2
- package/src/__tests__/subagent-role-registry.test.ts +2 -7
- package/src/__tests__/subagent-spawn-tool-fork.test.ts +0 -2
- package/src/__tests__/subagent-tools.test.ts +0 -2
- package/src/__tests__/suggestion-routes.test.ts +0 -2
- package/src/__tests__/title-generate-hook.test.ts +0 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -2
- package/src/__tests__/tool-executor.test.ts +11 -16
- package/src/__tests__/tool-preview-lifecycle.test.ts +0 -2
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +0 -2
- package/src/__tests__/tool-start-timestamp.test.ts +0 -2
- package/src/__tests__/trusted-contact-inline-approval-integration.test.ts +10 -10
- package/src/__tests__/twilio-routes.test.ts +0 -96
- package/src/__tests__/ui-file-upload-surface.test.ts +0 -86
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/voice-invite-redemption.test.ts +0 -33
- package/src/__tests__/web-search-backend-failure.test.ts +0 -2
- package/src/__tests__/workspace-migration-remove-hooks.test.ts +35 -14
- package/src/__tests__/workspace-tool-loader.test.ts +2 -195
- package/src/__tests__/workspace-tools-watcher-flag.test.ts +70 -0
- package/src/agent/loop.ts +0 -56
- package/src/api/index.ts +1 -19
- package/src/api/responses/llm-request-log-entry.ts +0 -29
- package/src/api/responses/subagent-detail.ts +0 -17
- package/src/api/surfaces.ts +3 -39
- package/src/approvals/guardian-request-resolvers.ts +11 -1
- package/src/calls/__tests__/relay-setup-router.test.ts +4 -262
- package/src/calls/call-domain.ts +3 -3
- package/src/calls/guardian-dispatch.ts +8 -10
- package/src/calls/inbound-trust-reader.ts +1 -17
- package/src/calls/media-stream-server.ts +0 -21
- package/src/calls/relay-server.ts +50 -167
- package/src/calls/relay-setup-router.ts +7 -37
- package/src/calls/relay-verification.ts +4 -4
- package/src/calls/stt-hints.ts +12 -9
- package/src/calls/twilio-routes.ts +4 -14
- package/src/channels/types.ts +20 -10
- package/src/cli/commands/__tests__/cache.test.ts +1 -8
- package/src/cli/commands/cache.ts +181 -194
- package/src/cli/commands/db/__tests__/repair.test.ts +5 -6
- package/src/cli/commands/db/status.ts +1 -37
- package/src/cli/commands/mcp.ts +218 -252
- package/src/cli/commands/memory/index.ts +0 -2
- package/src/cli/commands/plugins.ts +3 -75
- package/src/cli/lib/__tests__/install-from-github.test.ts +0 -102
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +1 -160
- package/src/cli/lib/list-installed-plugins.ts +1 -179
- package/src/config/__tests__/sync-gated-profiles.test.ts +3 -11
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +17 -27
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +3 -13
- package/src/config/bundled-skills/subagent/SKILL.md +1 -1
- package/src/config/bundled-skills/subagent/TOOLS.json +1 -1
- package/src/config/feature-flag-registry.json +13 -5
- package/src/config/loader.ts +5 -38
- package/src/config/schemas/__tests__/memory-v3.test.ts +0 -1
- package/src/config/schemas/memory-lifecycle.ts +0 -12
- package/src/config/schemas/memory-v3.ts +0 -7
- package/src/config/schemas/memory.ts +0 -4
- package/src/config/schemas/timeouts.ts +0 -8
- package/src/config/seed-inference-profiles.ts +11 -21
- package/src/config/skills.ts +5 -27
- package/src/config/sync-gated-profiles.ts +13 -12
- package/src/contacts/contacts-write.ts +0 -3
- package/src/daemon/assistant-attachments.ts +4 -27
- package/src/daemon/conversation-agent-loop.ts +0 -28
- package/src/daemon/conversation-process.ts +16 -35
- package/src/daemon/conversation-surfaces.ts +38 -111
- package/src/daemon/conversation-tool-setup.ts +16 -50
- package/src/daemon/conversation.ts +1 -13
- package/src/daemon/disk-pressure-guard.ts +2 -12
- package/src/daemon/event-loop-watchdog.ts +1 -28
- package/src/daemon/external-plugins-bootstrap.ts +34 -4
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +0 -25
- package/src/daemon/handlers/config-a2a.ts +14 -6
- package/src/daemon/handlers/config-channels.ts +22 -78
- package/src/daemon/handlers/conversations.ts +0 -77
- package/src/daemon/lifecycle.ts +0 -4
- package/src/daemon/mcp-reload-service.ts +0 -10
- package/src/daemon/memory-v2-startup.test.ts +0 -72
- package/src/daemon/memory-v2-startup.ts +19 -87
- package/src/daemon/message-types/conversations.ts +0 -2
- package/src/daemon/message-types/surfaces.ts +12 -12
- package/src/daemon/server.ts +4 -0
- package/src/daemon/shutdown-handlers.ts +0 -20
- package/src/daemon/tool-setup-types.ts +0 -9
- package/src/daemon/workspace-tools-watcher.ts +328 -0
- package/src/ipc/__tests__/clients-list-ipc.test.ts +1 -1
- package/src/ipc/assistant-server.ts +2 -2
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/client.ts +1 -15
- package/src/mcp/mcp-auth-orchestrator.ts +1 -6
- package/src/mcp/mcp-oauth-provider.ts +8 -19
- package/src/memory/__tests__/memory-retrospective-job.test.ts +0 -8
- package/src/memory/conversation-crud.ts +0 -38
- package/src/memory/db-connection.ts +3 -22
- package/src/memory/db-init.ts +502 -36
- package/src/memory/db-singleton.ts +4 -6
- package/src/memory/jobs-worker.ts +0 -58
- package/src/memory/llm-request-log-store.ts +1 -26
- package/src/memory/llm-usage-store.ts +20 -48
- package/src/memory/memory-retrospective-job.ts +8 -9
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +56 -130
- package/src/memory/migrations/__tests__/run-migrations.test.ts +2 -2
- package/src/memory/migrations/registry.ts +573 -0
- package/src/memory/migrations/run-migrations.ts +6 -90
- package/src/memory/migrations/validate-migration-state.ts +66 -101
- package/src/memory/schema/conversations.ts +0 -9
- package/src/memory/schema/infrastructure.ts +0 -20
- package/src/memory/v2/__tests__/cli-command-store.test.ts +0 -25
- package/src/memory/v2/__tests__/skill-store.test.ts +0 -80
- package/src/memory/v2/cli-command-store.ts +38 -75
- package/src/memory/v2/prompts/consolidation.ts +82 -13
- package/src/memory/v2/prompts/router.ts +93 -21
- package/src/memory/v2/skill-store.ts +31 -68
- package/src/notifications/__tests__/broadcaster.test.ts +8 -16
- package/src/notifications/__tests__/decision-engine.test.ts +9 -78
- package/src/notifications/broadcaster.ts +1 -8
- package/src/notifications/decision-engine.ts +7 -15
- package/src/notifications/destination-resolver.ts +24 -68
- package/src/notifications/emit-signal.ts +14 -39
- package/src/permissions/question-prompter.test.ts +1 -1
- package/src/permissions/question-prompter.ts +4 -7
- package/src/plugin-api/index.ts +6 -6
- package/src/plugin-api/types.ts +5 -3
- package/src/plugin-api/vision-support.test.ts +4 -28
- package/src/plugin-api/vision-support.ts +31 -66
- package/src/plugins/defaults/advisor/__tests__/consult.test.ts +0 -161
- package/src/plugins/defaults/advisor/consult.ts +6 -110
- package/src/plugins/defaults/advisor/steering.ts +2 -14
- package/src/plugins/defaults/advisor/tools/advisor.ts +5 -32
- package/src/plugins/defaults/exploration-drift/hooks/post-tool-use.ts +1 -2
- package/src/plugins/defaults/image-fallback/__tests__/image-fallback.test.ts +7 -47
- package/src/plugins/defaults/image-fallback/hooks/post-tool-use.ts +11 -10
- package/src/plugins/defaults/image-fallback/hooks/user-prompt-submit.ts +20 -12
- package/src/plugins/defaults/image-fallback/src/caption-blocks.ts +11 -42
- package/src/plugins/defaults/memory-v3-shadow/__tests__/injection.test.ts +3 -33
- package/src/plugins/defaults/memory-v3-shadow/__tests__/pool-select.test.ts +4 -48
- package/src/plugins/defaults/memory-v3-shadow/__tests__/shadow-plugin.test.ts +8 -4
- package/src/plugins/defaults/memory-v3-shadow/injector.ts +15 -43
- package/src/plugins/defaults/memory-v3-shadow/orchestrate.ts +2 -11
- package/src/plugins/defaults/memory-v3-shadow/pool-select.ts +13 -77
- package/src/plugins/defaults/memory-v3-shadow/shadow-plugin.ts +11 -12
- package/src/plugins/mtime-cache.ts +291 -76
- package/src/plugins/pipeline.ts +13 -111
- package/src/plugins/types.ts +2 -0
- package/src/providers/anthropic/client.ts +0 -5
- package/src/providers/call-site-routing.ts +0 -4
- package/src/providers/model-catalog.ts +0 -16
- package/src/providers/openai/__tests__/api-error-detail.test.ts +120 -0
- package/src/providers/openai/chat-completions-provider.ts +83 -37
- package/src/providers/openai/responses-provider.ts +46 -50
- package/src/providers/openrouter/client.ts +0 -5
- package/src/providers/provider-send-message.ts +0 -4
- package/src/providers/ratelimit.ts +0 -4
- package/src/providers/retry.ts +0 -4
- package/src/providers/types.ts +0 -9
- package/src/providers/usage-tracking.ts +0 -4
- package/src/runtime/__tests__/trust-verdict-consumer.test.ts +3 -335
- package/src/runtime/access-request-helper.ts +39 -19
- package/src/runtime/actor-trust-resolver.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +1 -1
- package/src/runtime/assistant-stream-state.ts +2 -9
- package/src/runtime/auth/require-bound-guardian.ts +11 -21
- package/src/runtime/channel-verification-service.ts +31 -56
- package/src/runtime/confirmation-request-guardian-bridge.ts +3 -3
- package/src/runtime/guardian-vellum-migration.ts +7 -66
- package/src/runtime/invite-redemption-service.ts +187 -198
- package/src/runtime/local-actor-identity.ts +11 -76
- package/src/runtime/pending-interactions.ts +1 -11
- package/src/runtime/routes/__tests__/channel-verification-revoke.test.ts +5 -56
- package/src/runtime/routes/__tests__/channel-verification-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +0 -187
- package/src/runtime/routes/browser-routes.ts +1 -1
- package/src/runtime/routes/canonical-guardian-expiry-sweep.ts +5 -13
- package/src/runtime/routes/channel-verification-routes.ts +3 -3
- package/src/runtime/routes/contact-routes.ts +32 -8
- package/src/runtime/routes/conversation-cli-routes.ts +5 -4
- package/src/runtime/routes/conversation-list-routes.ts +7 -4
- package/src/runtime/routes/conversation-query-routes.ts +0 -72
- package/src/runtime/routes/conversation-routes.ts +85 -84
- package/src/runtime/routes/events-routes.ts +2 -2
- package/src/runtime/routes/global-search-routes.ts +1 -3
- package/src/runtime/routes/guardian-action-routes.ts +5 -4
- package/src/runtime/routes/host-app-control-routes.ts +4 -5
- package/src/runtime/routes/host-bash-routes.ts +4 -5
- package/src/runtime/routes/host-browser-routes.ts +11 -9
- package/src/runtime/routes/host-cu-routes.ts +4 -5
- package/src/runtime/routes/host-file-routes.ts +4 -5
- package/src/runtime/routes/host-transfer-routes.ts +6 -6
- package/src/runtime/routes/http-adapter.ts +1 -1
- package/src/runtime/routes/identity-routes.ts +2 -3
- package/src/runtime/routes/inbound-message-handler.ts +5 -5
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +5 -97
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +49 -61
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -16
- package/src/runtime/routes/inbound-stages/escalation-intercept.ts +7 -7
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.test.ts +8 -21
- package/src/runtime/routes/inbound-stages/guardian-activation-intercept.ts +3 -14
- package/src/runtime/routes/index.ts +0 -2
- package/src/runtime/routes/llm-context-normalization.ts +0 -83
- package/src/runtime/routes/mcp-auth-routes.ts +19 -171
- package/src/runtime/routes/migration-rollback-routes.ts +3 -4
- package/src/runtime/routes/migration-routes.ts +1 -4
- package/src/runtime/routes/subagents-routes.ts +0 -5
- package/src/runtime/routes/surface-action-routes.ts +56 -42
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +0 -1
- package/src/runtime/services/conversation-serializer.ts +9 -7
- package/src/runtime/tool-grant-request-helper.ts +3 -3
- package/src/runtime/trust-verdict-consumer.ts +9 -85
- package/src/runtime/verification-outbound-actions.ts +18 -18
- package/src/signals/user-message.ts +0 -16
- package/src/subagent/manager.ts +0 -9
- package/src/subagent/types.ts +3 -3
- package/src/telemetry/types.ts +1 -34
- package/src/telemetry/usage-telemetry-reporter.test.ts +2 -3
- package/src/telemetry/usage-telemetry-reporter.ts +3 -87
- package/src/tools/ask-question/ask-question-tool.test.ts +0 -29
- package/src/tools/ask-question/ask-question-tool.ts +0 -13
- package/src/tools/executor.ts +4 -4
- package/src/tools/registry.ts +0 -18
- package/src/tools/shared/filesystem/path-policy.ts +5 -12
- package/src/tools/tool-approval-handler.ts +1 -1
- package/src/tools/tool-defaults.ts +2 -9
- package/src/tools/tool-manifest.ts +0 -3
- package/src/tools/types.ts +2 -17
- package/src/tools/workspace-tools/loader.ts +244 -348
- package/src/util/errors.ts +1 -26
- package/src/util/platform.ts +0 -5
- package/src/workflows/library.test.ts +0 -140
- package/src/workflows/library.ts +28 -82
- package/src/workspace/migrations/017-seed-persona-dirs.ts +34 -3
- package/src/workspace/migrations/019-scope-journal-to-guardian.ts +24 -3
- package/src/workspace/migrations/048-remove-workspace-hooks.ts +66 -14
- package/src/workspace/migrations/registry.ts +0 -2
- package/node_modules/@vellumai/gateway-client/src/__tests__/guardian-delivery-contract.test.ts +0 -91
- package/node_modules/@vellumai/gateway-client/src/guardian-delivery-contract.ts +0 -48
- package/node_modules/@vellumai/service-contracts/src/__tests__/channels.test.ts +0 -28
- package/node_modules/@vellumai/service-contracts/src/channels.ts +0 -41
- package/src/__tests__/code-search-tool.test.ts +0 -585
- package/src/__tests__/guardian-expiry-notifier.test.ts +0 -282
- package/src/__tests__/mcp-config-secret-boundary.test.ts +0 -390
- package/src/__tests__/plugin-pipeline.test.ts +0 -96
- package/src/__tests__/sse-actor-principal-guardian-source.test.ts +0 -102
- package/src/__tests__/steer-on-enqueue-question.test.ts +0 -181
- package/src/__tests__/workspace-migration-111-prune-seeded-callsite-defaults.test.ts +0 -208
- package/src/agent/loop-exclusive-tool.test.ts +0 -150
- package/src/api/constants/sse-replay.ts +0 -41
- package/src/api/events/conversation-notice.ts +0 -26
- package/src/approvals/guardian-channel-delivery.ts +0 -30
- package/src/approvals/guardian-expiry-notifier.ts +0 -148
- package/src/cli/commands/memory/__tests__/worker.test.ts +0 -302
- package/src/cli/commands/memory/worker.ts +0 -175
- package/src/config/__tests__/loader-callsite-strip-fallback.test.ts +0 -143
- package/src/config/prune-seeded-callsite-defaults.ts +0 -110
- package/src/contacts/__tests__/contacts-write-revoke-relay.test.ts +0 -129
- package/src/contacts/__tests__/guardian-delivery-reader.test.ts +0 -312
- package/src/contacts/__tests__/member-write-relay.test.ts +0 -202
- package/src/contacts/guardian-delivery-reader.ts +0 -223
- package/src/contacts/member-write-relay.ts +0 -189
- package/src/daemon/conversation-notices.ts +0 -60
- package/src/daemon/handlers/__tests__/config-channels.test.ts +0 -225
- package/src/hooks/hook-loader.ts +0 -341
- package/src/mcp/mcp-header-store.ts +0 -134
- package/src/memory/__tests__/301-create-watchdog-events.test.ts +0 -110
- package/src/memory/__tests__/prompt-override.test.ts +0 -192
- package/src/memory/__tests__/watchdog-events-store.test.ts +0 -161
- package/src/memory/migrations/300-add-processing-started-at.ts +0 -30
- package/src/memory/migrations/301-create-watchdog-events.ts +0 -45
- package/src/memory/migrations/__tests__/209-strip-thinking-from-consolidated.test.ts +0 -224
- package/src/memory/prompt-override.ts +0 -129
- package/src/memory/steps.ts +0 -573
- package/src/memory/watchdog-events-store.ts +0 -87
- package/src/memory/worker-control.ts +0 -118
- package/src/memory/worker-process.ts +0 -72
- package/src/notifications/__tests__/connected-channels.test.ts +0 -114
- package/src/notifications/__tests__/destination-resolver.test.ts +0 -256
- package/src/onboarding/checkin-event.test.ts +0 -222
- package/src/onboarding/checkin-event.ts +0 -321
- package/src/onboarding/schedule-checkin.ts +0 -190
- package/src/plugins/defaults/advisor/__tests__/context-pack-gating.test.ts +0 -106
- package/src/plugins/defaults/advisor/__tests__/context-pack.test.ts +0 -60
- package/src/plugins/defaults/advisor/context-pack.ts +0 -288
- package/src/plugins/defaults/memory-v3-shadow/pool-select.test.ts +0 -146
- package/src/plugins/surface-import.ts +0 -121
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +0 -321
- package/src/providers/openai/api-error-normalization.ts +0 -270
- package/src/runtime/__tests__/channel-verification-service.test.ts +0 -133
- package/src/runtime/__tests__/guardian-vellum-migration.test.ts +0 -181
- package/src/runtime/__tests__/is-guardian-bound-for-channel.test.ts +0 -66
- package/src/runtime/__tests__/local-principal-trust.test.ts +0 -164
- package/src/runtime/anchored-guardian.test.ts +0 -156
- package/src/runtime/anchored-guardian.ts +0 -135
- package/src/runtime/auth/__tests__/require-bound-guardian.test.ts +0 -99
- package/src/runtime/local-principal-trust.ts +0 -52
- package/src/runtime/routes/__tests__/contact-routes.test.ts +0 -212
- package/src/runtime/routes/__tests__/global-search-routes.test.ts +0 -93
- package/src/runtime/routes/onboarding-checkin-routes.ts +0 -86
- package/src/tools/filesystem/search.ts +0 -543
- package/src/util/telemetry-db-path.ts +0 -24
- package/src/workspace/migrations/111-prune-seeded-callsite-defaults.ts +0 -134
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for `pool-select.ts` `selectPool` — focused on error SURFACING:
|
|
3
|
-
* - a provider call that THROWS on every attempt surfaces the underlying
|
|
4
|
-
* provider error (e.g. an upstream HTTP 4xx) in the thrown
|
|
5
|
-
* `MemoryV3RetrievalUnavailableError` message, rather than the generic
|
|
6
|
-
* "no usable selection" string that hid it;
|
|
7
|
-
* - a 200 response carrying no usable `tool_use` still throws the generic
|
|
8
|
-
* "no usable selection" message;
|
|
9
|
-
* - happy paths preserved: explicit ids → selection, omitted ids → keepAll,
|
|
10
|
-
* empty pool → [].
|
|
11
|
-
*
|
|
12
|
-
* `mock.module` is process-global and leaks into sibling files in a directory
|
|
13
|
-
* run, so the provider-send-message stub DELEGATES to the real implementation
|
|
14
|
-
* (keeping the real `extractToolUse`) unless this test is actively running
|
|
15
|
-
* (`selectMockActive`) — mirrors `prune.test.ts` / `ever-injected-store.test.ts`.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
19
|
-
|
|
20
|
-
import type {
|
|
21
|
-
ContentBlock,
|
|
22
|
-
Provider,
|
|
23
|
-
ProviderResponse,
|
|
24
|
-
} from "../../../providers/types.js";
|
|
25
|
-
import type { MemoryRoutingTurn, Slug } from "./types.js";
|
|
26
|
-
|
|
27
|
-
const realProviderSend = {
|
|
28
|
-
...(await import("../../../providers/provider-send-message.js")),
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
let selectMockActive = false;
|
|
32
|
-
let sendMessageImpl: (() => Promise<ProviderResponse>) | null = null;
|
|
33
|
-
|
|
34
|
-
const mockProvider = {
|
|
35
|
-
name: "mock-memory-v3-selector",
|
|
36
|
-
async sendMessage(): Promise<ProviderResponse> {
|
|
37
|
-
if (!sendMessageImpl) throw new Error("sendMessageImpl not configured");
|
|
38
|
-
return sendMessageImpl();
|
|
39
|
-
},
|
|
40
|
-
} as unknown as Provider;
|
|
41
|
-
|
|
42
|
-
mock.module("../../../providers/provider-send-message.js", () => ({
|
|
43
|
-
...realProviderSend,
|
|
44
|
-
getConfiguredProvider: (
|
|
45
|
-
...args: Parameters<typeof realProviderSend.getConfiguredProvider>
|
|
46
|
-
) =>
|
|
47
|
-
selectMockActive
|
|
48
|
-
? Promise.resolve(mockProvider)
|
|
49
|
-
: realProviderSend.getConfiguredProvider(...args),
|
|
50
|
-
}));
|
|
51
|
-
|
|
52
|
-
const { MemoryV3RetrievalUnavailableError, selectPool } =
|
|
53
|
-
await import("./pool-select.js");
|
|
54
|
-
|
|
55
|
-
function response(content: ContentBlock[]): ProviderResponse {
|
|
56
|
-
return {
|
|
57
|
-
content,
|
|
58
|
-
model: "mock",
|
|
59
|
-
usage: { inputTokens: 0, outputTokens: 0 },
|
|
60
|
-
} as ProviderResponse;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const turn: MemoryRoutingTurn = {
|
|
64
|
-
conversationId: "conv-1",
|
|
65
|
-
turnNumber: 0,
|
|
66
|
-
currentMessage: "echo something back",
|
|
67
|
-
recentContext: "",
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const pool = {
|
|
71
|
-
stable: [],
|
|
72
|
-
finder: [{ slug: "page-a" as Slug, descriptor: "a descriptor" }],
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
describe("selectPool", () => {
|
|
76
|
-
beforeEach(() => {
|
|
77
|
-
selectMockActive = true;
|
|
78
|
-
sendMessageImpl = null;
|
|
79
|
-
});
|
|
80
|
-
afterAll(() => {
|
|
81
|
-
selectMockActive = false;
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
test("a provider throw surfaces the underlying error in the thrown message", async () => {
|
|
85
|
-
const upstream = "Together AI API error (400): 400 status code (no body)";
|
|
86
|
-
sendMessageImpl = async () => {
|
|
87
|
-
throw new Error(upstream);
|
|
88
|
-
};
|
|
89
|
-
let caught: unknown;
|
|
90
|
-
try {
|
|
91
|
-
await selectPool(pool, turn);
|
|
92
|
-
} catch (err) {
|
|
93
|
-
caught = err;
|
|
94
|
-
}
|
|
95
|
-
expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
|
|
96
|
-
expect((caught as Error).message).toContain(
|
|
97
|
-
"provider call failed after retries",
|
|
98
|
-
);
|
|
99
|
-
// The real upstream error is no longer hidden behind the generic message.
|
|
100
|
-
expect((caught as Error).message).toContain(upstream);
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
test("a 200 with no usable tool_use throws the generic message", async () => {
|
|
104
|
-
sendMessageImpl = async () =>
|
|
105
|
-
response([{ type: "text", text: "I cannot call the tool." }]);
|
|
106
|
-
let caught: unknown;
|
|
107
|
-
try {
|
|
108
|
-
await selectPool(pool, turn);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
caught = err;
|
|
111
|
-
}
|
|
112
|
-
expect(caught).toBeInstanceOf(MemoryV3RetrievalUnavailableError);
|
|
113
|
-
expect((caught as Error).message).toBe(
|
|
114
|
-
"memory-v3 pool selector returned no usable selection after retries",
|
|
115
|
-
);
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test("explicit ids select the matching candidates", async () => {
|
|
119
|
-
sendMessageImpl = async () =>
|
|
120
|
-
response([
|
|
121
|
-
{
|
|
122
|
-
type: "tool_use",
|
|
123
|
-
id: "call-1",
|
|
124
|
-
name: "select_pages",
|
|
125
|
-
input: { ids: [1] },
|
|
126
|
-
},
|
|
127
|
-
]);
|
|
128
|
-
expect(await selectPool(pool, turn)).toEqual([
|
|
129
|
-
{ slug: "page-a", pinned: false },
|
|
130
|
-
]);
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
test("omitted ids keep all candidates (recall-safe)", async () => {
|
|
134
|
-
sendMessageImpl = async () =>
|
|
135
|
-
response([
|
|
136
|
-
{ type: "tool_use", id: "call-1", name: "select_pages", input: {} },
|
|
137
|
-
]);
|
|
138
|
-
expect(await selectPool(pool, turn)).toEqual([
|
|
139
|
-
{ slug: "page-a", pinned: false },
|
|
140
|
-
]);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test("an empty candidate pool returns no selections", async () => {
|
|
144
|
-
expect(await selectPool({ stable: [], finder: [] }, turn)).toEqual([]);
|
|
145
|
-
});
|
|
146
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared low-level import machinery for user-surface files (plugin tools,
|
|
3
|
-
* plugin hooks, and standalone workspace hooks).
|
|
4
|
-
*
|
|
5
|
-
* Both the plugin/tool cache (`mtime-cache.ts`) and the hook cache
|
|
6
|
-
* (`../hooks/hook-loader.ts`) re-import surface files keyed by mtime. This
|
|
7
|
-
* module owns the pieces they share — mtime stat, the per-process import
|
|
8
|
-
* timeout, and a timeout-guarded, concurrency-deduplicated dynamic import —
|
|
9
|
-
* so neither surface duplicates the logic and the import-timeout knob is
|
|
10
|
-
* configured in exactly one place.
|
|
11
|
-
*
|
|
12
|
-
* Kept dependency-light (only the logger and `importDefault`) so it can sit
|
|
13
|
-
* below both caches without introducing an import cycle.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import { statSync } from "node:fs";
|
|
17
|
-
|
|
18
|
-
import { getLogger } from "../util/logger.js";
|
|
19
|
-
import { importDefault } from "./external-plugin-loader.js";
|
|
20
|
-
|
|
21
|
-
const log = getLogger("surface-import");
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Per-process upper bound on how long a single surface import may take.
|
|
25
|
-
* Set once at boot by `populateCacheAtBoot`; read by every re-import. A
|
|
26
|
-
* plugin/hook with a hanging top-level `await` is skipped rather than
|
|
27
|
-
* blocking the daemon.
|
|
28
|
-
*/
|
|
29
|
-
let importTimeoutMs = 10_000;
|
|
30
|
-
|
|
31
|
-
/** Override the surface import timeout (called once at boot). */
|
|
32
|
-
export function setSurfaceImportTimeout(ms: number): void {
|
|
33
|
-
importTimeoutMs = ms;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Current surface import timeout in milliseconds. */
|
|
37
|
-
export function getSurfaceImportTimeout(): number {
|
|
38
|
-
return importTimeoutMs;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Get the mtimeMs of a file, or 0 if the file doesn't exist or can't be
|
|
43
|
-
* stat'd. Callers treat 0 as "absent" (cache-evict / skip).
|
|
44
|
-
*/
|
|
45
|
-
export function getMtime(filePath: string): number {
|
|
46
|
-
try {
|
|
47
|
-
return statSync(filePath).mtimeMs;
|
|
48
|
-
} catch {
|
|
49
|
-
return 0;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* In-flight import promises, keyed by file path. Prevents duplicate
|
|
55
|
-
* `import()` calls when multiple readers request the same surface
|
|
56
|
-
* concurrently.
|
|
57
|
-
*/
|
|
58
|
-
const inflight = new Map<string, Promise<unknown>>();
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Import a module's default export with a timeout. If the import doesn't
|
|
62
|
-
* resolve within `timeoutMs`, logs a warning and returns `undefined` so a
|
|
63
|
-
* hanging module doesn't block daemon startup indefinitely. Defaults to the
|
|
64
|
-
* module-level {@link getSurfaceImportTimeout}.
|
|
65
|
-
*/
|
|
66
|
-
export async function importWithTimeout<T>(
|
|
67
|
-
filePath: string,
|
|
68
|
-
timeoutMs: number = importTimeoutMs,
|
|
69
|
-
): Promise<T | undefined> {
|
|
70
|
-
let timeoutHandle: ReturnType<typeof setTimeout> | undefined;
|
|
71
|
-
try {
|
|
72
|
-
const timeoutSentinel = Symbol("import-timeout");
|
|
73
|
-
const importPromise = importWithDedup<T>(filePath);
|
|
74
|
-
const timeoutPromise = new Promise<typeof timeoutSentinel>((resolve) => {
|
|
75
|
-
timeoutHandle = setTimeout(() => resolve(timeoutSentinel), timeoutMs);
|
|
76
|
-
});
|
|
77
|
-
const result = await Promise.race([importPromise, timeoutPromise]);
|
|
78
|
-
if (result === timeoutSentinel) {
|
|
79
|
-
importPromise.catch(() => {
|
|
80
|
-
/* swallow — late rejection from abandoned import */
|
|
81
|
-
});
|
|
82
|
-
log.warn(
|
|
83
|
-
{ filePath, timeoutMs },
|
|
84
|
-
`Import timed out after ${timeoutMs}ms — skipping surface`,
|
|
85
|
-
);
|
|
86
|
-
return undefined;
|
|
87
|
-
}
|
|
88
|
-
return result as T;
|
|
89
|
-
} finally {
|
|
90
|
-
if (timeoutHandle !== undefined) clearTimeout(timeoutHandle);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Import a module's default export, deduplicating concurrent imports for
|
|
96
|
-
* the same file path. This prevents two readers from triggering duplicate
|
|
97
|
-
* `import()` calls when they request the same surface simultaneously.
|
|
98
|
-
*
|
|
99
|
-
* Note: Bun caches `import()` by resolved path within a process and does not
|
|
100
|
-
* bust on query-string or hash changes, so the dedup is about avoiding
|
|
101
|
-
* redundant async work, not cache-busting. A content edit to an existing
|
|
102
|
-
* file is only picked up after a process restart (added and removed files
|
|
103
|
-
* are picked up live, since discovery is by directory listing).
|
|
104
|
-
*/
|
|
105
|
-
async function importWithDedup<T>(filePath: string): Promise<T> {
|
|
106
|
-
let promise = inflight.get(filePath);
|
|
107
|
-
if (promise === undefined) {
|
|
108
|
-
promise = importDefault<T>(filePath);
|
|
109
|
-
inflight.set(filePath, promise);
|
|
110
|
-
}
|
|
111
|
-
try {
|
|
112
|
-
return (await promise) as T;
|
|
113
|
-
} finally {
|
|
114
|
-
inflight.delete(filePath);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** Clear in-flight import state. Test-only. */
|
|
119
|
-
export function clearSurfaceImportInflight(): void {
|
|
120
|
-
inflight.clear();
|
|
121
|
-
}
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
|
|
3
|
-
import type OpenAI from "openai";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
captureRawErrorBodyFetch,
|
|
7
|
-
formatNormalizedOpenAIAPIError,
|
|
8
|
-
normalizeOpenAIAPIError,
|
|
9
|
-
} from "../api-error-normalization.js";
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Stand-in for an `OpenAI.APIError`. The real SDK constructor stringifies the
|
|
13
|
-
* body into `.message` and renders unparseable bodies as "(no body)" — exactly
|
|
14
|
-
* the lossy behavior we work around by capturing the raw body separately — so
|
|
15
|
-
* the tests pass the raw body explicitly the way the provider does.
|
|
16
|
-
*/
|
|
17
|
-
function apiError(
|
|
18
|
-
status: number,
|
|
19
|
-
opts: { message?: string; sdkBody?: unknown; headers?: Headers } = {},
|
|
20
|
-
): InstanceType<typeof OpenAI.APIError> {
|
|
21
|
-
return {
|
|
22
|
-
status,
|
|
23
|
-
message: opts.message ?? `${status} status code (no body)`,
|
|
24
|
-
headers: opts.headers ?? new Headers(),
|
|
25
|
-
error: opts.sdkBody,
|
|
26
|
-
} as unknown as InstanceType<typeof OpenAI.APIError>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
describe("normalizeOpenAIAPIError", () => {
|
|
30
|
-
test("surfaces Django {detail} bodies the SDK would drop", () => {
|
|
31
|
-
const n = normalizeOpenAIAPIError(
|
|
32
|
-
apiError(400),
|
|
33
|
-
JSON.stringify({
|
|
34
|
-
detail: "Model 'MiniMax-M3' is not yet supported on Vellum.",
|
|
35
|
-
}),
|
|
36
|
-
);
|
|
37
|
-
expect(n.message).toBe(
|
|
38
|
-
"Model 'MiniMax-M3' is not yet supported on Vellum.",
|
|
39
|
-
);
|
|
40
|
-
expect(formatNormalizedOpenAIAPIError("Together AI", 400, n)).toBe(
|
|
41
|
-
"Together AI API error (400): Model 'MiniMax-M3' is not yet supported on Vellum.",
|
|
42
|
-
);
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
test("extracts OpenAI-shaped error metadata", () => {
|
|
46
|
-
const n = normalizeOpenAIAPIError(
|
|
47
|
-
apiError(401),
|
|
48
|
-
JSON.stringify({
|
|
49
|
-
error: {
|
|
50
|
-
message: "Invalid API key provided",
|
|
51
|
-
code: "invalid_api_key",
|
|
52
|
-
type: "invalid_request_error",
|
|
53
|
-
param: "api_key",
|
|
54
|
-
},
|
|
55
|
-
}),
|
|
56
|
-
);
|
|
57
|
-
expect(n).toMatchObject({
|
|
58
|
-
message: "Invalid API key provided",
|
|
59
|
-
apiErrorCode: "invalid_api_key",
|
|
60
|
-
apiErrorType: "invalid_request_error",
|
|
61
|
-
apiErrorParam: "api_key",
|
|
62
|
-
});
|
|
63
|
-
expect(formatNormalizedOpenAIAPIError("OpenAI", 401, n)).toContain(
|
|
64
|
-
"code=invalid_api_key; type=invalid_request_error; param=api_key",
|
|
65
|
-
);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("promotes OpenRouter metadata.raw over the generic wrapper message", () => {
|
|
69
|
-
const n = normalizeOpenAIAPIError(
|
|
70
|
-
apiError(400, {
|
|
71
|
-
sdkBody: {
|
|
72
|
-
error: {
|
|
73
|
-
code: 400,
|
|
74
|
-
message: "Provider returned error",
|
|
75
|
-
metadata: {
|
|
76
|
-
raw: "messages.4: tool_use_id must reference a prior tool_use block",
|
|
77
|
-
provider_name: "Anthropic",
|
|
78
|
-
},
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
}),
|
|
82
|
-
);
|
|
83
|
-
expect(n.message).toBe(
|
|
84
|
-
"messages.4: tool_use_id must reference a prior tool_use block",
|
|
85
|
-
);
|
|
86
|
-
expect(n.detail).toBe("provider=Anthropic");
|
|
87
|
-
expect(n.apiErrorCode).toBe("400");
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
test("prefers the captured raw body over the SDK's parsed error", () => {
|
|
91
|
-
// SDK collapsed the body; raw body still has the real detail.
|
|
92
|
-
const n = normalizeOpenAIAPIError(
|
|
93
|
-
apiError(400, { sdkBody: {} }),
|
|
94
|
-
JSON.stringify({ detail: "managed proxy rejected model" }),
|
|
95
|
-
);
|
|
96
|
-
expect(n.message).toBe("managed proxy rejected model");
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
test("falls back to the SDK-parsed error when the captured JSON was truncated", () => {
|
|
100
|
-
// encodeCapturedBody slices oversized bodies, which can leave an invalid
|
|
101
|
-
// JSON prefix. Rather than render that fragment (and lose metadata), fall
|
|
102
|
-
// back to the body the SDK already parsed from the full response.
|
|
103
|
-
const truncated =
|
|
104
|
-
'{"error":{"message":"too long","code":"context_length_exceeded","type":"invalid_request_error","param":"messag';
|
|
105
|
-
const n = normalizeOpenAIAPIError(
|
|
106
|
-
apiError(400, {
|
|
107
|
-
sdkBody: {
|
|
108
|
-
error: {
|
|
109
|
-
message: "too long",
|
|
110
|
-
code: "context_length_exceeded",
|
|
111
|
-
type: "invalid_request_error",
|
|
112
|
-
param: "messages",
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
}),
|
|
116
|
-
truncated,
|
|
117
|
-
);
|
|
118
|
-
expect(n.message).toBe("too long");
|
|
119
|
-
expect(n.apiErrorCode).toBe("context_length_exceeded");
|
|
120
|
-
expect(n.apiErrorType).toBe("invalid_request_error");
|
|
121
|
-
expect(n.apiErrorParam).toBe("messages");
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
test("uses a plain-text raw body when JSON parsing fails", () => {
|
|
125
|
-
const n = normalizeOpenAIAPIError(apiError(502), "upstream timeout");
|
|
126
|
-
expect(n.message).toBe("upstream timeout");
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("reads the upstream request id from headers", () => {
|
|
130
|
-
const n = normalizeOpenAIAPIError(
|
|
131
|
-
apiError(400, { headers: new Headers({ "x-request-id": "req_abc123" }) }),
|
|
132
|
-
JSON.stringify({ detail: "bad request" }),
|
|
133
|
-
);
|
|
134
|
-
expect(n.requestId).toBe("req_abc123");
|
|
135
|
-
expect(formatNormalizedOpenAIAPIError("OpenAI", 400, n)).toContain(
|
|
136
|
-
"request_id=req_abc123",
|
|
137
|
-
);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
test("falls back to x-openrouter-request-id", () => {
|
|
141
|
-
const n = normalizeOpenAIAPIError(
|
|
142
|
-
apiError(400, {
|
|
143
|
-
headers: new Headers({ "x-openrouter-request-id": "gen-or-xyz" }),
|
|
144
|
-
}),
|
|
145
|
-
JSON.stringify({ detail: "bad request" }),
|
|
146
|
-
);
|
|
147
|
-
expect(n.requestId).toBe("gen-or-xyz");
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
test("truncates very long details with an ellipsis", () => {
|
|
151
|
-
const n = normalizeOpenAIAPIError(
|
|
152
|
-
apiError(400),
|
|
153
|
-
JSON.stringify({ detail: "X".repeat(5000) }),
|
|
154
|
-
);
|
|
155
|
-
expect(n.message.length).toBeLessThanOrEqual(2001);
|
|
156
|
-
expect(n.message.endsWith("…")).toBe(true);
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
test("degrades to the status-stripped SDK message when there is no body", () => {
|
|
160
|
-
const n = normalizeOpenAIAPIError(
|
|
161
|
-
apiError(500, { message: "500 Internal Server Error" }),
|
|
162
|
-
);
|
|
163
|
-
expect(n.message).toBe("Internal Server Error");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
test("falls back to 'Request failed' for the SDK '(no body)' sentinel", () => {
|
|
167
|
-
const n = normalizeOpenAIAPIError(
|
|
168
|
-
apiError(500, { message: "500 status code (no body)" }),
|
|
169
|
-
);
|
|
170
|
-
expect(n.message).toBe("Request failed");
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
test("surfaces the verbatim captured body as rawBody for downstream persistence", () => {
|
|
174
|
-
const raw = JSON.stringify({ detail: "model gone", extra: "kept" });
|
|
175
|
-
const n = normalizeOpenAIAPIError(apiError(400), raw);
|
|
176
|
-
// Extracted message is the clean detail...
|
|
177
|
-
expect(n.message).toBe("model gone");
|
|
178
|
-
// ...but the raw body is carried verbatim so the Raw tab can show it all.
|
|
179
|
-
expect(n.rawBody).toBe(raw);
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
test("keeps sibling code/type/param when the body is {error: <string>}", () => {
|
|
183
|
-
const n = normalizeOpenAIAPIError(
|
|
184
|
-
apiError(400),
|
|
185
|
-
JSON.stringify({
|
|
186
|
-
error: "bad request",
|
|
187
|
-
code: "context_length_exceeded",
|
|
188
|
-
type: "invalid_request_error",
|
|
189
|
-
}),
|
|
190
|
-
);
|
|
191
|
-
expect(n.message).toBe("bad request");
|
|
192
|
-
expect(n.apiErrorCode).toBe("context_length_exceeded");
|
|
193
|
-
expect(n.apiErrorType).toBe("invalid_request_error");
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
describe("captureRawErrorBodyFetch", () => {
|
|
198
|
-
// The fetch wrapper stashes the dropped body in a WeakMap keyed by the
|
|
199
|
-
// response's headers object — which the SDK passes through to
|
|
200
|
-
// APIError.headers — so normalize() recovers it with no explicit rawBody
|
|
201
|
-
// argument and nothing is written to a (loggable) header.
|
|
202
|
-
function fakeFetch(body: string, status: number): typeof globalThis.fetch {
|
|
203
|
-
return (async () =>
|
|
204
|
-
new Response(body, { status })) as unknown as typeof globalThis.fetch;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function errFor(res: Response, status: number) {
|
|
208
|
-
return {
|
|
209
|
-
status,
|
|
210
|
-
message: `${status} status code (no body)`,
|
|
211
|
-
headers: res.headers,
|
|
212
|
-
error: undefined,
|
|
213
|
-
} as unknown as InstanceType<typeof import("openai").APIError>;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
test("recovers a non-2xx body into normalize() via the headers WeakMap", async () => {
|
|
217
|
-
const original = globalThis.fetch;
|
|
218
|
-
globalThis.fetch = fakeFetch(JSON.stringify({ detail: "model gone" }), 400);
|
|
219
|
-
try {
|
|
220
|
-
const res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
221
|
-
// No rawBody arg: normalize recovers the body keyed by res.headers alone.
|
|
222
|
-
expect(normalizeOpenAIAPIError(errFor(res, 400)).message).toBe(
|
|
223
|
-
"model gone",
|
|
224
|
-
);
|
|
225
|
-
} finally {
|
|
226
|
-
globalThis.fetch = original;
|
|
227
|
-
}
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("never writes the captured body to a header", async () => {
|
|
231
|
-
const original = globalThis.fetch;
|
|
232
|
-
globalThis.fetch = fakeFetch(JSON.stringify({ detail: "secret" }), 400);
|
|
233
|
-
try {
|
|
234
|
-
const res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
235
|
-
const headerNames = [...res.headers.keys()];
|
|
236
|
-
expect(headerNames.some((h) => h.includes("vellum"))).toBe(false);
|
|
237
|
-
} finally {
|
|
238
|
-
globalThis.fetch = original;
|
|
239
|
-
}
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
test("passes OK responses through untouched", async () => {
|
|
243
|
-
const original = globalThis.fetch;
|
|
244
|
-
globalThis.fetch = fakeFetch("ok", 200);
|
|
245
|
-
try {
|
|
246
|
-
const res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
247
|
-
expect(await res.text()).toBe("ok");
|
|
248
|
-
} finally {
|
|
249
|
-
globalThis.fetch = original;
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
|
|
253
|
-
test("recovers (truncated) detail for oversized terminal errors", async () => {
|
|
254
|
-
const big = "y".repeat(40_000);
|
|
255
|
-
const original = globalThis.fetch;
|
|
256
|
-
globalThis.fetch = fakeFetch(JSON.stringify({ detail: big }), 400);
|
|
257
|
-
try {
|
|
258
|
-
const res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
259
|
-
const n = normalizeOpenAIAPIError(errFor(res, 400));
|
|
260
|
-
// The captured body is bounded (16 KB) and the message re-truncated to
|
|
261
|
-
// MAX_DETAIL_CHARS, so an oversized error can't balloon either.
|
|
262
|
-
expect(n.message).toContain("yyyy");
|
|
263
|
-
expect(n.message.length).toBeLessThanOrEqual(2001);
|
|
264
|
-
} finally {
|
|
265
|
-
globalThis.fetch = original;
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
test("does not drain SDK-retryable bodies (408/409/429/5xx)", async () => {
|
|
270
|
-
const original = globalThis.fetch;
|
|
271
|
-
// Mirror the full SDK retry predicate, not just 429/5xx.
|
|
272
|
-
for (const status of [408, 409, 429, 500, 503]) {
|
|
273
|
-
globalThis.fetch = fakeFetch(
|
|
274
|
-
JSON.stringify({ detail: "retry me" }),
|
|
275
|
-
status,
|
|
276
|
-
);
|
|
277
|
-
try {
|
|
278
|
-
const res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
279
|
-
// Pass-through: nothing captured, and the body is still readable by the
|
|
280
|
-
// SDK (we never consumed it).
|
|
281
|
-
expect(normalizeOpenAIAPIError(errFor(res, status)).message).not.toBe(
|
|
282
|
-
"retry me",
|
|
283
|
-
);
|
|
284
|
-
expect(res.status).toBe(status);
|
|
285
|
-
expect(await res.text()).toContain("retry me");
|
|
286
|
-
} finally {
|
|
287
|
-
globalThis.fetch = original;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
test("honors the x-should-retry header override", async () => {
|
|
293
|
-
const original = globalThis.fetch;
|
|
294
|
-
const withHeader = (
|
|
295
|
-
status: number,
|
|
296
|
-
value: string,
|
|
297
|
-
): typeof globalThis.fetch =>
|
|
298
|
-
(async () =>
|
|
299
|
-
new Response(JSON.stringify({ detail: "header says so" }), {
|
|
300
|
-
status,
|
|
301
|
-
headers: { "x-should-retry": value },
|
|
302
|
-
})) as unknown as typeof globalThis.fetch;
|
|
303
|
-
try {
|
|
304
|
-
// x-should-retry:true on an otherwise-terminal 400 → SDK retries → skip.
|
|
305
|
-
globalThis.fetch = withHeader(400, "true");
|
|
306
|
-
let res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
307
|
-
expect(normalizeOpenAIAPIError(errFor(res, 400)).message).not.toBe(
|
|
308
|
-
"header says so",
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
// x-should-retry:false on a 500 → SDK won't retry → capture it.
|
|
312
|
-
globalThis.fetch = withHeader(500, "false");
|
|
313
|
-
res = await captureRawErrorBodyFetch("https://x/v1/chat", {});
|
|
314
|
-
expect(normalizeOpenAIAPIError(errFor(res, 500)).message).toBe(
|
|
315
|
-
"header says so",
|
|
316
|
-
);
|
|
317
|
-
} finally {
|
|
318
|
-
globalThis.fetch = original;
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
});
|