@vellumai/assistant 0.8.2 → 0.8.4
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/ARCHITECTURE.md +11 -12
- package/docker-entrypoint.sh +13 -2
- package/docker-init-apt-root.sh +79 -6
- package/node_modules/@vellumai/gateway-client/src/types.ts +2 -0
- package/openapi.yaml +945 -36
- package/package.json +1 -1
- package/src/__tests__/agent-loop-exit-reason.test.ts +271 -0
- package/src/__tests__/agent-loop-override-profile.test.ts +1 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +195 -0
- package/src/__tests__/agent-loop.test.ts +88 -3
- package/src/__tests__/anthropic-provider.test.ts +272 -0
- package/src/__tests__/approval-cascade.test.ts +1 -1
- package/src/__tests__/background-workers-disk-pressure.test.ts +2 -1
- package/src/__tests__/channel-delivery-store.test.ts +193 -0
- package/src/__tests__/channel-reply-delivery.test.ts +284 -5
- package/src/__tests__/channel-retry-sweep.test.ts +274 -1
- package/src/__tests__/compaction-events.test.ts +1 -1
- package/src/__tests__/compactor-preserved-tail-count.test.ts +110 -0
- package/src/__tests__/compactor-tail-resolution.test.ts +107 -1
- package/src/__tests__/config-get-vision-flag.test.ts +136 -0
- package/src/__tests__/config-loader-backfill.test.ts +115 -18
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/context-token-estimator.test.ts +112 -57
- package/src/__tests__/conversation-abort-tool-results.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +54 -3
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +31 -6
- package/src/__tests__/conversation-agent-loop.test.ts +77 -3
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-clean-command.test.ts +137 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +161 -0
- package/src/__tests__/conversation-lifecycle.test.ts +1 -1
- package/src/__tests__/conversation-load-cleaned-at.test.ts +279 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +1 -1
- package/src/__tests__/conversation-media-retry.test.ts +19 -8
- package/src/__tests__/conversation-pairing.test.ts +2 -2
- package/src/__tests__/conversation-process-callsite.test.ts +1 -1
- package/src/__tests__/conversation-provider-retry-repair.test.ts +1 -1
- package/src/__tests__/conversation-queue.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +290 -85
- package/src/__tests__/conversation-seed-composer.test.ts +66 -4
- package/src/__tests__/conversation-slash-commands.test.ts +36 -8
- package/src/__tests__/conversation-slash-queue.test.ts +1 -1
- package/src/__tests__/conversation-slash-unknown.test.ts +1 -1
- package/src/__tests__/conversation-speed-override.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +220 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +1 -1
- package/src/__tests__/conversation-workspace-injection.test.ts +5 -1
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +5 -1
- package/src/__tests__/credential-security-invariants.test.ts +6 -0
- package/src/__tests__/cu-unified-flow.test.ts +10 -1
- package/src/__tests__/date-context.test.ts +45 -0
- package/src/__tests__/dm-backfill.test.ts +64 -0
- package/src/__tests__/dm-persistence.test.ts +33 -0
- package/src/__tests__/document-find-replace.test.ts +501 -0
- package/src/__tests__/external-plugin-loader.test.ts +91 -19
- package/src/__tests__/first-greeting.test.ts +23 -2
- package/src/__tests__/guardian-action-no-hardcoded-copy.test.ts +0 -1
- package/src/__tests__/guardian-dispatch.test.ts +1 -0
- package/src/__tests__/headless-browser-navigate.test.ts +172 -0
- package/src/__tests__/heartbeat-service.test.ts +24 -164
- package/src/__tests__/helpers/channel-test-adapter.ts +0 -2
- package/src/__tests__/host-app-control-proxy.test.ts +241 -0
- package/src/__tests__/host-bash-proxy.test.ts +6 -0
- package/src/__tests__/host-browser-proxy.test.ts +10 -0
- package/src/__tests__/host-cu-proxy.test.ts +8 -1
- package/src/__tests__/host-file-proxy.test.ts +8 -1
- package/src/__tests__/host-proxy-preactivation.test.ts +200 -13
- package/src/__tests__/host-transfer-proxy.test.ts +8 -1
- package/src/__tests__/identity-routes.test.ts +57 -0
- package/src/__tests__/inbound-slack-persistence.test.ts +3 -0
- package/src/__tests__/injector-background-turn.test.ts +153 -0
- package/src/__tests__/injector-chain.test.ts +7 -0
- package/src/__tests__/injector-document-comments.test.ts +378 -0
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +4 -25
- package/src/__tests__/lifecycle-memory-v2-seed.test.ts +9 -2
- package/src/__tests__/list-messages-attachments.test.ts +21 -17
- package/src/__tests__/list-messages-hidden-metadata.test.ts +217 -0
- package/src/__tests__/list-messages-page-latest.test.ts +130 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +17 -16
- package/src/__tests__/llm-callsite-catalog.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +3 -0
- package/src/__tests__/llm-context-normalization.test.ts +0 -2
- package/src/__tests__/llm-request-log-agent-loop-exit-reason.test.ts +116 -0
- package/src/__tests__/llm-request-log-error-payload.test.ts +138 -0
- package/src/__tests__/llm-request-log-source-clickhouse.test.ts +2 -0
- package/src/__tests__/llm-resolver.test.ts +340 -3
- package/src/__tests__/log-export-routes.test.ts +99 -2
- package/src/__tests__/managed-profile-guard.test.ts +10 -0
- package/src/__tests__/message-queue-steer.test.ts +114 -0
- package/src/__tests__/notification-decision-fallback.test.ts +0 -91
- package/src/__tests__/notification-decision-strategy.test.ts +14 -31
- package/src/__tests__/notification-deep-link.test.ts +15 -0
- package/src/__tests__/notification-guardian-path.test.ts +1 -2
- package/src/__tests__/notification-platform-adapter.test.ts +5 -4
- package/src/__tests__/notification-telegram-adapter.test.ts +1 -0
- package/src/__tests__/notification-vellum-adapter.test.ts +113 -0
- package/src/__tests__/openai-provider.test.ts +323 -3
- package/src/__tests__/openai-responses-cutover-guard.test.ts +3 -3
- package/src/__tests__/openai-responses-provider.test.ts +4 -4
- package/src/__tests__/openrouter-provider-only.test.ts +51 -3
- package/src/__tests__/openrouter-token-estimation.test.ts +34 -25
- package/src/__tests__/outbound-slack-persistence.test.ts +187 -20
- package/src/__tests__/pending-interactions-resolved-event.test.ts +190 -0
- package/src/__tests__/platform-proxy-context.test.ts +6 -1
- package/src/__tests__/platform.test.ts +0 -3
- package/src/__tests__/plugin-source-watcher.test.ts +302 -0
- package/src/__tests__/plugin-tool-contribution.test.ts +3 -3
- package/src/__tests__/plugin-types.test.ts +2 -2
- package/src/__tests__/process-message-background-slack.test.ts +1 -51
- package/src/__tests__/process-message-display-content.test.ts +21 -16
- package/src/__tests__/provider-catalog-visibility.test.ts +16 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +27 -25
- package/src/__tests__/secret-routes-platform-proxy.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +83 -4
- package/src/__tests__/steer-tool-repair.test.ts +249 -0
- package/src/__tests__/system-prompt.test.ts +57 -101
- package/src/__tests__/terminal-tools.test.ts +11 -1
- package/src/__tests__/thinking-block-replay.test.ts +113 -0
- package/src/__tests__/thread-backfill.test.ts +370 -22
- package/src/__tests__/tool-executor.test.ts +90 -1
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +167 -0
- package/src/__tests__/twilio-routes.test.ts +1 -1
- package/src/__tests__/web-fetch.test.ts +2 -2
- package/src/__tests__/workspace-git-service.test.ts +88 -5
- package/src/__tests__/workspace-migration-087-memory-router-balanced-profile.test.ts +228 -0
- package/src/__tests__/workspace-migration-088-deprecate-background-conversation-override.test.ts +158 -0
- package/src/a2a/__tests__/agent-card.test.ts +98 -0
- package/src/a2a/__tests__/e2e-a2a-channel.test.ts +597 -0
- package/src/a2a/__tests__/protocol-helpers.test.ts +113 -0
- package/src/a2a/__tests__/task-store.test.ts +246 -0
- package/src/a2a/agent-card.ts +58 -0
- package/src/a2a/feature-gate.ts +8 -0
- package/src/a2a/protocol-constants.ts +21 -0
- package/src/a2a/protocol-errors.ts +50 -0
- package/src/a2a/protocol-types.ts +162 -0
- package/src/a2a/task-store.ts +168 -0
- package/src/agent/attachments.ts +1 -0
- package/src/agent/loop.ts +208 -22
- package/src/background-wake/next-wake.test.ts +289 -0
- package/src/background-wake/next-wake.ts +172 -0
- package/src/browser/operations.ts +15 -0
- package/src/channels/config.ts +9 -0
- package/src/channels/types.ts +14 -0
- package/src/cli/commands/__tests__/conversations-slack.test.ts +572 -0
- package/src/cli/commands/__tests__/memory-v2.test.ts +9 -12
- package/src/cli/{__tests__ → commands/__tests__}/notifications.test.ts +201 -28
- package/src/cli/commands/__tests__/schedules.test.ts +469 -0
- package/src/cli/commands/conversations.ts +128 -1
- package/src/cli/commands/inference-providers.ts +147 -1
- package/src/cli/commands/memory-v2.ts +308 -0
- package/src/cli/commands/notifications.ts +89 -37
- package/src/cli/commands/plugins.ts +67 -0
- package/src/cli/commands/schedules.ts +297 -5
- package/src/cli/lib/__tests__/search-plugins.test.ts +261 -0
- package/src/cli/lib/install-from-github.ts +8 -9
- package/src/cli/lib/search-plugins.ts +163 -0
- package/src/cli/program.ts +14 -0
- package/src/cli/utils/conversation-id.ts +17 -5
- package/src/config/assistant-feature-flags.ts +24 -54
- package/src/config/bundled-skills/app-builder/SKILL.md +117 -1
- package/src/config/bundled-skills/document-editor/SKILL.md +115 -0
- package/src/config/bundled-skills/document-editor/TOOLS.json +240 -0
- package/src/config/bundled-skills/document-editor/tools/comment-list.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-reply.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/comment-resolve.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-find.ts +12 -0
- package/src/config/bundled-skills/document-editor/tools/document-replace-text.ts +12 -0
- package/src/config/bundled-skills/media-processing/SKILL.md +8 -0
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/schedule/SKILL.md +8 -0
- package/src/config/bundled-tool-registry.ts +22 -12
- package/src/config/call-site-defaults.ts +124 -0
- package/src/config/feature-flag-registry.json +111 -23
- package/src/config/llm-resolver.ts +66 -1
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/memory-v2.test.ts +7 -3
- package/src/config/schemas/call-site-catalog.ts +21 -0
- package/src/config/schemas/channels.ts +9 -0
- package/src/config/schemas/conversations.ts +10 -0
- package/src/config/schemas/heartbeat.ts +14 -0
- package/src/config/schemas/llm.ts +4 -3
- package/src/config/schemas/memory-retrospective.ts +1 -1
- package/src/config/schemas/memory-v2.ts +51 -4
- package/src/config/schemas/memory.ts +3 -1
- package/src/config/seed-inference-profiles.ts +99 -29
- package/src/context/compactor.ts +80 -13
- package/src/context/token-estimator.ts +72 -31
- package/src/context/window-manager.ts +25 -0
- package/src/credential-health/credential-health-service.ts +34 -19
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +3 -22
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -6
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +357 -0
- package/src/daemon/__tests__/web-search-status-text.test.ts +287 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +231 -23
- package/src/daemon/conversation-agent-loop.ts +252 -56
- package/src/daemon/conversation-lifecycle.ts +142 -116
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +273 -0
- package/src/daemon/conversation-queue-manager.ts +14 -0
- package/src/daemon/conversation-runtime-assembly.ts +144 -75
- package/src/daemon/conversation-slash.ts +37 -5
- package/src/daemon/conversation-surfaces.ts +45 -2
- package/src/daemon/conversation-tool-setup.ts +7 -0
- package/src/daemon/conversation.ts +42 -12
- package/src/daemon/date-context.ts +40 -0
- package/src/daemon/first-greeting.ts +10 -0
- package/src/daemon/guardian-action-generators.ts +1 -125
- package/src/daemon/handlers/__tests__/config-a2a-accept.test.ts +498 -0
- package/src/daemon/handlers/__tests__/config-a2a-complete.test.ts +248 -0
- package/src/daemon/handlers/__tests__/config-a2a-invite.test.ts +154 -0
- package/src/daemon/handlers/__tests__/config-a2a-redeem.test.ts +133 -0
- package/src/daemon/handlers/__tests__/config-a2a.test.ts +95 -0
- package/src/daemon/handlers/config-a2a.ts +449 -0
- package/src/daemon/handlers/config-model.test.ts +1 -0
- package/src/daemon/handlers/conversations.ts +80 -0
- package/src/daemon/handlers/shared.ts +92 -29
- package/src/daemon/host-app-control-proxy.ts +69 -18
- package/src/daemon/host-bash-proxy.ts +1 -1
- package/src/daemon/host-cu-proxy.ts +1 -1
- package/src/daemon/host-file-proxy.ts +1 -1
- package/src/daemon/host-proxy-preactivation.ts +85 -18
- package/src/daemon/host-transfer-proxy.ts +1 -1
- package/src/daemon/lifecycle.ts +67 -65
- package/src/daemon/memory-v2-startup.ts +49 -13
- package/src/daemon/message-protocol.ts +4 -0
- package/src/daemon/message-types/conversations.ts +8 -0
- package/src/daemon/message-types/document-comments.ts +50 -0
- package/src/daemon/message-types/messages.ts +68 -1
- package/src/daemon/message-types/notifications.ts +21 -0
- package/src/daemon/message-types/surfaces.ts +3 -1
- package/src/daemon/message-types/web-activity.ts +57 -0
- package/src/daemon/pkb-reminder-builder.test.ts +10 -53
- package/src/daemon/pkb-reminder-builder.ts +4 -19
- package/src/daemon/plugin-source-watcher.ts +135 -3
- package/src/daemon/process-message.ts +72 -12
- package/src/daemon/query-complexity-router.ts +75 -0
- package/src/daemon/skill-memory-refresh.ts +5 -1
- package/src/daemon/trust-context.ts +6 -0
- package/src/daemon/wake-target-adapter.ts +2 -0
- package/src/documents/document-comments-store.test.ts +338 -0
- package/src/documents/document-comments-store.ts +237 -0
- package/src/documents/document-store.ts +202 -0
- package/src/export/__tests__/transcript-formatter.test.ts +121 -0
- package/src/export/transcript-formatter.ts +54 -20
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +44 -1
- package/src/heartbeat/heartbeat-service.ts +35 -191
- package/src/home/__tests__/feed-types.test.ts +40 -0
- package/src/home/__tests__/suggested-prompts.test.ts +33 -2
- package/src/home/feed-types.ts +20 -3
- package/src/home/home-content-refresh.ts +52 -0
- package/src/home/home-greeting-cache.ts +69 -0
- package/src/home/home-greeting.ts +94 -0
- package/src/home/suggested-prompts.ts +177 -9
- package/src/ipc/cli-client.ts +147 -45
- package/src/memory/__tests__/conversation-queries.test.ts +220 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +135 -2
- package/src/memory/__tests__/memory-retrospective-enqueue.test.ts +2 -50
- package/src/memory/__tests__/memory-retrospective-job.test.ts +407 -10
- package/src/memory/conversation-crud.ts +133 -43
- package/src/memory/conversation-queries.ts +87 -1
- package/src/memory/conversation-title-service.ts +26 -4
- package/src/memory/db-init.ts +22 -0
- package/src/memory/delivery-crud.ts +41 -0
- package/src/memory/delivery-status.ts +141 -15
- package/src/memory/external-conversation-store.ts +32 -1
- package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +84 -3
- package/src/memory/graph/conversation-graph-memory.ts +18 -6
- package/src/memory/graph/tools.ts +6 -37
- package/src/memory/invite-store.ts +53 -0
- package/src/memory/jobs-worker.ts +21 -1
- package/src/memory/llm-request-log-source-clickhouse.ts +7 -2
- package/src/memory/llm-request-log-store.ts +92 -1
- package/src/memory/memory-retrospective-constants.ts +28 -0
- package/src/memory/memory-retrospective-enqueue.ts +4 -22
- package/src/memory/memory-retrospective-job.ts +438 -21
- package/src/memory/memory-retrospective-startup-cleanup.ts +3 -3
- package/src/memory/memory-v2-activation-log-store.ts +26 -8
- package/src/memory/migrations/100-core-tables.ts +1 -0
- package/src/memory/migrations/109-external-conversation-bindings.ts +1 -0
- package/src/memory/migrations/250-provider-connection-base-url-and-models.ts +28 -0
- package/src/memory/migrations/251-a2a-tasks.ts +49 -0
- package/src/memory/migrations/252-llm-request-log-agent-loop-exit-reason.ts +32 -0
- package/src/memory/migrations/253-conversation-last-notified-profile.ts +15 -0
- package/src/memory/migrations/253-document-comments.ts +47 -0
- package/src/memory/migrations/254-external-conversation-binding-chat-name.ts +43 -0
- package/src/memory/migrations/255-channel-inbound-delivery-attempts.ts +24 -0
- package/src/memory/migrations/256-memory-v2-injection-events.ts +113 -0
- package/src/memory/migrations/257-strip-base-url-non-openai-compatible.ts +22 -0
- package/src/memory/migrations/258-onboarding-events-prior-assistants.ts +13 -0
- package/src/memory/migrations/259-conversation-cleaned-at.ts +33 -0
- package/src/memory/migrations/index.ts +20 -0
- package/src/memory/migrations/registry.ts +33 -0
- package/src/memory/onboarding-events-store.ts +7 -0
- package/src/memory/schema/a2a.ts +15 -0
- package/src/memory/schema/calls.ts +1 -0
- package/src/memory/schema/conversations.ts +3 -0
- package/src/memory/schema/index.ts +1 -0
- package/src/memory/schema/inference.ts +2 -0
- package/src/memory/schema/infrastructure.ts +2 -0
- package/src/memory/v2/__tests__/activation-store.test.ts +25 -23
- package/src/memory/v2/__tests__/cli-command-store.test.ts +404 -0
- package/src/memory/v2/__tests__/frontmatter-sweep.test.ts +25 -4
- package/src/memory/v2/__tests__/injection-events.test.ts +318 -0
- package/src/memory/v2/__tests__/injection.test.ts +221 -17
- package/src/memory/v2/__tests__/page-index.test.ts +365 -1
- package/src/memory/v2/__tests__/router.test.ts +489 -1
- package/src/memory/v2/__tests__/static-context.test.ts +12 -1
- package/src/memory/v2/activation-store.ts +14 -16
- package/src/memory/v2/cli-command-content.ts +19 -0
- package/src/memory/v2/cli-command-store.ts +304 -0
- package/src/memory/v2/consolidation-job.ts +14 -0
- package/src/memory/v2/frontmatter-sweep.ts +7 -1
- package/src/memory/v2/injection-events.ts +101 -0
- package/src/memory/v2/injection.ts +69 -29
- package/src/memory/v2/page-index.ts +246 -19
- package/src/memory/v2/page-store.ts +18 -0
- package/src/memory/v2/router.ts +209 -55
- package/src/memory/v2/static-context.ts +4 -4
- package/src/memory/v2/types.ts +23 -0
- package/src/messaging/providers/a2a/__tests__/deliver.test.ts +274 -0
- package/src/messaging/providers/a2a/deliver.ts +156 -0
- package/src/messaging/providers/gmail/client.ts +9 -2
- package/src/messaging/providers/index.ts +18 -3
- package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +329 -3
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +34 -1
- package/src/messaging/providers/slack/adapter.ts +178 -25
- package/src/messaging/providers/slack/api.test.ts +54 -0
- package/src/messaging/providers/slack/api.ts +119 -3
- package/src/messaging/providers/slack/client.ts +12 -0
- package/src/messaging/providers/slack/deep-link.ts +20 -1
- package/src/messaging/providers/slack/message-metadata.test.ts +48 -0
- package/src/messaging/providers/slack/message-metadata.ts +156 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +107 -75
- package/src/messaging/providers/slack/render-transcript.ts +176 -49
- package/src/messaging/providers/slack/send.test.ts +77 -0
- package/src/messaging/providers/slack/send.ts +8 -2
- package/src/messaging/providers/slack/types.ts +14 -0
- package/src/notifications/__tests__/broadcaster.test.ts +203 -0
- package/src/notifications/__tests__/decision-engine.test.ts +283 -0
- package/src/notifications/__tests__/deterministic-checks.test.ts +286 -0
- package/src/notifications/__tests__/emit-signal-home-feed.test.ts +5 -1
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +521 -36
- package/src/notifications/adapters/macos.ts +12 -2
- package/src/notifications/broadcaster.ts +29 -4
- package/src/notifications/conversation-seed-composer.ts +14 -2
- package/src/notifications/copy-composer.ts +17 -64
- package/src/notifications/decision-engine.ts +111 -44
- package/src/notifications/deferred-emit.ts +135 -0
- package/src/notifications/deterministic-checks.ts +96 -0
- package/src/notifications/emit-signal.ts +10 -1
- package/src/notifications/home-feed-side-effect.ts +136 -27
- package/src/notifications/signal.ts +0 -4
- package/src/notifications/types.ts +8 -0
- package/src/oauth/connect-orchestrator.ts +3 -0
- package/src/oauth/credential-token-resolver.ts +2 -0
- package/src/oauth/manual-token-connection.ts +19 -0
- package/src/oauth/oauth-store.ts +12 -0
- package/src/oauth/platform-connection.test.ts +43 -3
- package/src/oauth/platform-connection.ts +13 -4
- package/src/oauth/seed-providers.ts +22 -0
- package/src/permissions/prompter.ts +5 -2
- package/src/permissions/secret-prompter.ts +4 -1
- package/src/plugins/defaults/injectors.ts +118 -26
- package/src/plugins/external-plugin-loader.ts +82 -10
- package/src/plugins/types.ts +16 -7
- package/src/prompts/__tests__/system-prompt.test.ts +44 -45
- package/src/prompts/__tests__/task-progress-hint-section.test.ts +4 -8
- package/src/prompts/normalize-onboarding.ts +40 -0
- package/src/prompts/sections.ts +32 -14
- package/src/prompts/system-prompt.ts +105 -76
- package/src/prompts/template-detection.ts +37 -0
- package/src/prompts/templates/BOOTSTRAP-CONTENT-AUTOMATION.md +141 -0
- package/src/prompts/templates/BOOTSTRAP.md +13 -5
- package/src/prompts/templates/VOICE.md +3 -0
- package/src/prompts/templates/system-sections.ts +51 -10
- package/src/providers/__tests__/inference.test.ts +2 -0
- package/src/providers/anthropic/client.ts +132 -5
- package/src/providers/call-site-routing.ts +24 -6
- package/src/providers/connection-resolution.ts +63 -13
- package/src/providers/fireworks/client.ts +20 -2
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +74 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +342 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +189 -0
- package/src/providers/inference/__tests__/codex-token-refresh.test.ts +254 -0
- package/src/providers/inference/__tests__/connections-openai-compatible.test.ts +175 -0
- package/src/providers/inference/__tests__/connections-status-label.test.ts +15 -0
- package/src/providers/inference/adapter-factory.ts +24 -21
- package/src/providers/inference/auth.ts +15 -3
- package/src/providers/inference/backfill.ts +14 -1
- package/src/providers/inference/codex-token-refresh.ts +128 -0
- package/src/providers/inference/connections.ts +85 -5
- package/src/providers/inference/resolve-auth.ts +50 -5
- package/src/providers/model-catalog.ts +244 -242
- package/src/providers/model-intents.ts +3 -3
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +235 -0
- package/src/providers/openai/chat-completions-provider.ts +215 -25
- package/src/providers/openai/responses-provider.ts +9 -3
- package/src/providers/openrouter/client.ts +46 -4
- package/src/providers/platform-proxy/constants.ts +3 -4
- package/src/providers/provider-catalog-visibility.ts +3 -1
- package/src/providers/provider-send-message.ts +27 -12
- package/src/providers/registry.ts +30 -1
- package/src/providers/types.ts +25 -0
- package/src/runtime/__tests__/agent-wake.test.ts +214 -0
- package/src/runtime/__tests__/background-job-runner.test.ts +128 -0
- package/src/runtime/agent-wake.ts +212 -57
- package/src/runtime/auth/route-policy.ts +20 -3
- package/src/runtime/background-job-runner.ts +26 -0
- package/src/runtime/channel-reply-delivery.ts +182 -47
- package/src/runtime/channel-retry-sweep.ts +141 -16
- package/src/runtime/http-server.ts +7 -16
- package/src/runtime/http-types.ts +7 -51
- package/src/runtime/pending-interactions.ts +51 -8
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +258 -0
- package/src/runtime/routes/__tests__/content-source-routes.test.ts +162 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +121 -5
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +275 -44
- package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +12 -0
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +14 -0
- package/src/runtime/routes/__tests__/memory-v2-simulate-route.test.ts +271 -0
- package/src/runtime/routes/__tests__/sanity-routes.test.ts +280 -0
- package/src/runtime/routes/__tests__/slack-channel-routes.test.ts +266 -0
- package/src/runtime/routes/approval-routes.ts +4 -1
- package/src/runtime/routes/channel-availability-routes.ts +5 -0
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +246 -0
- package/src/runtime/routes/consolidation-routes.ts +100 -0
- package/src/runtime/routes/content-source-routes.ts +78 -0
- package/src/runtime/routes/conversation-cli-routes.ts +146 -1
- package/src/runtime/routes/conversation-query-routes.ts +130 -12
- package/src/runtime/routes/conversation-routes.ts +288 -76
- package/src/runtime/routes/document-comments-routes.ts +287 -0
- package/src/runtime/routes/documents-routes.ts +33 -0
- package/src/runtime/routes/home-feed-routes.ts +6 -3
- package/src/runtime/routes/host-app-control-routes.ts +1 -1
- package/src/runtime/routes/host-browser-routes.ts +8 -1
- package/src/runtime/routes/identity-routes.ts +21 -0
- package/src/runtime/routes/inbound-message-handler.ts +288 -58
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +365 -6
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +283 -82
- package/src/runtime/routes/index.ts +14 -4
- package/src/runtime/routes/inference-provider-connection-routes.ts +192 -3
- package/src/runtime/routes/integrations/a2a.ts +294 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +11 -1
- package/src/runtime/routes/log-export-routes.ts +39 -0
- package/src/runtime/routes/memory-v2-routes.ts +217 -0
- package/src/runtime/routes/notification-routes.ts +19 -2
- package/src/runtime/routes/question-routes.ts +4 -1
- package/src/runtime/routes/sanity-routes.ts +159 -0
- package/src/runtime/routes/slack-channel-routes.ts +187 -0
- package/src/runtime/routes/subagents-routes.ts +41 -0
- package/src/runtime/services/conversation-serializer.ts +30 -4
- package/src/schedule/integration-status.ts +3 -1
- package/src/security/__tests__/oauth2-device-code.test.ts +479 -0
- package/src/security/oauth2-device-code.ts +307 -0
- package/src/security/oauth2.ts +26 -9
- package/src/security/secure-keys.ts +5 -0
- package/src/skills/catalog-install.ts +6 -2
- package/src/subagent/manager.ts +2 -0
- package/src/tools/browser/__tests__/pinned-tabs.test.ts +80 -0
- package/src/tools/browser/browser-execution.ts +93 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +28 -0
- package/src/tools/browser/cdp-client/__tests__/types.test.ts +1 -0
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +10 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +15 -1
- package/src/tools/browser/cdp-client/factory.ts +87 -3
- package/src/tools/browser/cdp-client/local-cdp-client.ts +9 -0
- package/src/tools/browser/cdp-client/types.ts +36 -0
- package/src/tools/browser/pinned-tabs.ts +90 -0
- package/src/tools/document/document-comment-tool.test.ts +379 -0
- package/src/tools/document/document-comment-tool.ts +156 -0
- package/src/tools/document/document-tool.ts +128 -2
- package/src/tools/memory/register.ts +1 -9
- package/src/tools/network/__tests__/web-fetch-metadata.test.ts +229 -0
- package/src/tools/network/__tests__/web-search-metadata.test.ts +346 -0
- package/src/tools/network/domain-normalize.ts +17 -0
- package/src/tools/network/web-fetch.ts +213 -64
- package/src/tools/network/web-search.ts +191 -66
- package/src/tools/registry.ts +2 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/tool-approval-handler.ts +19 -12
- package/src/tools/types.ts +41 -2
- package/src/tools/ui-surface/definitions.ts +3 -1
- package/src/types/onboarding-context.ts +4 -0
- package/src/util/__tests__/favicon.test.ts +84 -0
- package/src/util/favicon.ts +40 -0
- package/src/util/platform.ts +0 -5
- package/src/workspace/git-service.ts +75 -4
- package/src/workspace/migrations/087-memory-router-balanced-profile.ts +91 -0
- package/src/workspace/migrations/088-deprecate-background-conversation-override.ts +103 -0
- package/src/workspace/migrations/registry.ts +4 -0
- package/src/__tests__/guardian-action-conversation-turn.test.ts +0 -441
- package/src/config/bundled-skills/document/SKILL.md +0 -54
- package/src/config/bundled-skills/document/TOOLS.json +0 -106
- package/src/daemon/seed-files.ts +0 -18
- package/src/memory/graph/__tests__/remember-description.test.ts +0 -55
- package/src/runtime/guardian-action-conversation-turn.ts +0 -99
- package/src/runtime/routes/interface-routes.ts +0 -43
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-create.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-delete.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-list.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-read.ts +0 -0
- /package/src/config/bundled-skills/{document → document-editor}/tools/document-update.ts +0 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
|
|
4
|
+
|
|
5
|
+
type ReasoningDetail = {
|
|
6
|
+
type?: string;
|
|
7
|
+
summary?: string | null;
|
|
8
|
+
text?: string | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type MockChunkDelta = {
|
|
12
|
+
content?: string | null;
|
|
13
|
+
reasoning?: string | null;
|
|
14
|
+
reasoning_content?: string | null;
|
|
15
|
+
reasoning_details?: ReasoningDetail[] | null;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type MockChunk = {
|
|
19
|
+
choices: Array<{ delta: MockChunkDelta; finish_reason?: string | null }>;
|
|
20
|
+
model?: string;
|
|
21
|
+
usage?: {
|
|
22
|
+
prompt_tokens: number;
|
|
23
|
+
completion_tokens: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function makeStream(chunks: MockChunk[]): AsyncIterable<MockChunk> {
|
|
28
|
+
return {
|
|
29
|
+
async *[Symbol.asyncIterator]() {
|
|
30
|
+
for (const c of chunks) yield c;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function stubProvider(chunks: MockChunk[]): {
|
|
36
|
+
provider: OpenAIChatCompletionsProvider;
|
|
37
|
+
events: Array<{ type: string; thinking?: string; text?: string }>;
|
|
38
|
+
} {
|
|
39
|
+
const provider = new OpenAIChatCompletionsProvider("test-key", "test-model");
|
|
40
|
+
// Swap the SDK client for a stub whose chat.completions.create returns our
|
|
41
|
+
// canned async iterable.
|
|
42
|
+
(provider as unknown as { client: unknown }).client = {
|
|
43
|
+
chat: {
|
|
44
|
+
completions: {
|
|
45
|
+
create: async () => makeStream(chunks),
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
const events: Array<{ type: string; thinking?: string; text?: string }> = [];
|
|
50
|
+
(provider as unknown as { __events: typeof events }).__events = events;
|
|
51
|
+
return { provider, events };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function runStream(
|
|
55
|
+
provider: OpenAIChatCompletionsProvider,
|
|
56
|
+
events: Array<{ type: string; thinking?: string; text?: string }>,
|
|
57
|
+
): Promise<{
|
|
58
|
+
thinking: string;
|
|
59
|
+
}> {
|
|
60
|
+
const response = await provider.sendMessage(
|
|
61
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
62
|
+
undefined,
|
|
63
|
+
undefined,
|
|
64
|
+
{
|
|
65
|
+
onEvent: (e) => {
|
|
66
|
+
events.push(e as { type: string; thinking?: string; text?: string });
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
);
|
|
70
|
+
const thinkingBlock = response.content.find((b) => b.type === "thinking") as
|
|
71
|
+
| { type: "thinking"; thinking: string }
|
|
72
|
+
| undefined;
|
|
73
|
+
return { thinking: thinkingBlock?.thinking ?? "" };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
|
|
77
|
+
test("emits flat reasoning_content once (Fireworks/DeepSeek/Together/Groq shape)", async () => {
|
|
78
|
+
const { provider, events } = stubProvider([
|
|
79
|
+
{ choices: [{ delta: { reasoning_content: "hello " } }] },
|
|
80
|
+
{ choices: [{ delta: { reasoning_content: "world" } }] },
|
|
81
|
+
{
|
|
82
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
83
|
+
usage: { prompt_tokens: 1, completion_tokens: 2 },
|
|
84
|
+
},
|
|
85
|
+
]);
|
|
86
|
+
const { thinking } = await runStream(provider, events);
|
|
87
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
88
|
+
expect(deltas.map((d) => d.thinking)).toEqual(["hello ", "world"]);
|
|
89
|
+
expect(thinking).toBe("hello world");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("emits flat reasoning once (OpenRouter non-Kimi shape)", async () => {
|
|
93
|
+
const { provider, events } = stubProvider([
|
|
94
|
+
{ choices: [{ delta: { reasoning: "step " } }] },
|
|
95
|
+
{ choices: [{ delta: { reasoning: "two" } }] },
|
|
96
|
+
{
|
|
97
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
98
|
+
usage: { prompt_tokens: 1, completion_tokens: 2 },
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
const { thinking } = await runStream(provider, events);
|
|
102
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
103
|
+
expect(deltas.map((d) => d.thinking)).toEqual(["step ", "two"]);
|
|
104
|
+
expect(thinking).toBe("step two");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("emits reasoning_details once when only details present", async () => {
|
|
108
|
+
const { provider, events } = stubProvider([
|
|
109
|
+
{
|
|
110
|
+
choices: [
|
|
111
|
+
{
|
|
112
|
+
delta: {
|
|
113
|
+
reasoning_details: [{ type: "reasoning.text", text: "alpha " }],
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
choices: [
|
|
120
|
+
{
|
|
121
|
+
delta: {
|
|
122
|
+
reasoning_details: [
|
|
123
|
+
{ type: "reasoning.summary", summary: "beta" },
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
131
|
+
usage: { prompt_tokens: 1, completion_tokens: 2 },
|
|
132
|
+
},
|
|
133
|
+
]);
|
|
134
|
+
const { thinking } = await runStream(provider, events);
|
|
135
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
136
|
+
expect(deltas.map((d) => d.thinking)).toEqual(["alpha ", "beta"]);
|
|
137
|
+
expect(thinking).toBe("alpha beta");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("skips reasoning.encrypted entries entirely", async () => {
|
|
141
|
+
const { provider, events } = stubProvider([
|
|
142
|
+
{
|
|
143
|
+
choices: [
|
|
144
|
+
{
|
|
145
|
+
delta: {
|
|
146
|
+
reasoning_details: [
|
|
147
|
+
{ type: "reasoning.encrypted", text: "opaque" },
|
|
148
|
+
],
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
155
|
+
usage: { prompt_tokens: 1, completion_tokens: 2 },
|
|
156
|
+
},
|
|
157
|
+
]);
|
|
158
|
+
const { thinking } = await runStream(provider, events);
|
|
159
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
160
|
+
expect(deltas).toEqual([]);
|
|
161
|
+
expect(thinking).toBe("");
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
test("falls back to flat reasoning when details carry only encrypted entries", async () => {
|
|
165
|
+
const { provider, events } = stubProvider([
|
|
166
|
+
{
|
|
167
|
+
choices: [
|
|
168
|
+
{
|
|
169
|
+
delta: {
|
|
170
|
+
reasoning: "visible ",
|
|
171
|
+
reasoning_details: [
|
|
172
|
+
{ type: "reasoning.encrypted", text: "opaque" },
|
|
173
|
+
],
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
180
|
+
usage: { prompt_tokens: 1, completion_tokens: 2 },
|
|
181
|
+
},
|
|
182
|
+
]);
|
|
183
|
+
const { thinking } = await runStream(provider, events);
|
|
184
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
185
|
+
expect(deltas.map((d) => d.thinking)).toEqual(["visible "]);
|
|
186
|
+
expect(thinking).toBe("visible ");
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("does NOT double-emit when Kimi K2.6 mirrors text into both fields", async () => {
|
|
190
|
+
// OpenRouter Kimi K2.6 with `reasoning.summary` set sends the same token
|
|
191
|
+
// in both `delta.reasoning` and `delta.reasoning_details[].text`. The
|
|
192
|
+
// structured field is preferred and the flat field is skipped, so each
|
|
193
|
+
// token appears exactly once in the output stream.
|
|
194
|
+
const { provider, events } = stubProvider([
|
|
195
|
+
{
|
|
196
|
+
choices: [
|
|
197
|
+
{
|
|
198
|
+
delta: {
|
|
199
|
+
reasoning: "it ",
|
|
200
|
+
reasoning_details: [{ type: "reasoning.text", text: "it " }],
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
choices: [
|
|
207
|
+
{
|
|
208
|
+
delta: {
|
|
209
|
+
reasoning: "worked",
|
|
210
|
+
reasoning_details: [{ type: "reasoning.text", text: "worked" }],
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
choices: [
|
|
217
|
+
{
|
|
218
|
+
delta: {
|
|
219
|
+
reasoning: "!",
|
|
220
|
+
reasoning_details: [{ type: "reasoning.text", text: "!" }],
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
choices: [{ delta: {}, finish_reason: "stop" }],
|
|
227
|
+
usage: { prompt_tokens: 1, completion_tokens: 3 },
|
|
228
|
+
},
|
|
229
|
+
]);
|
|
230
|
+
const { thinking } = await runStream(provider, events);
|
|
231
|
+
const deltas = events.filter((e) => e.type === "thinking_delta");
|
|
232
|
+
expect(deltas.map((d) => d.thinking)).toEqual(["it ", "worked", "!"]);
|
|
233
|
+
expect(thinking).toBe("it worked!");
|
|
234
|
+
});
|
|
235
|
+
});
|
|
@@ -66,30 +66,56 @@ export interface OpenAIChatCompletionsProviderOptions {
|
|
|
66
66
|
extraCreateParams?: Record<string, unknown>;
|
|
67
67
|
/** Upper bound for `reasoning_effort` sent on the wire. Defaults to "xhigh"
|
|
68
68
|
* (OpenAI's current ceiling). Compatibility providers whose APIs only
|
|
69
|
-
* document `low|medium|high`
|
|
70
|
-
*
|
|
71
|
-
|
|
69
|
+
* document `low|medium|high` should set this to "high" so Vellum's
|
|
70
|
+
* `xhigh`/`max` tiers don't 4xx upstream. Set to "max" for providers like
|
|
71
|
+
* Fireworks DeepSeek V4 that accept the full effort range. Subclasses can
|
|
72
|
+
* override {@link OpenAIChatCompletionsProvider.resolveMaxReasoningEffort}
|
|
73
|
+
* for per-model ceilings. */
|
|
74
|
+
maxReasoningEffort?: "high" | "xhigh" | "max";
|
|
75
|
+
/** Parse `<think>...</think>` tags from the content stream into thinking
|
|
76
|
+
* blocks. MiniMax and similar providers embed reasoning inside XML-style
|
|
77
|
+
* tags in the regular content field rather than using `reasoning_content`. */
|
|
78
|
+
parseThinkTags?: boolean;
|
|
72
79
|
}
|
|
73
80
|
|
|
74
|
-
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
/** Wire-level reasoning_effort values. The OpenAI SDK type doesn't include
|
|
82
|
+
* `"max"`, but Fireworks accepts it for DeepSeek V4; the assignment to
|
|
83
|
+
* `params.reasoning_effort` casts through this union. */
|
|
84
|
+
type ReasoningEffortWire = "none" | "low" | "medium" | "high" | "xhigh" | "max";
|
|
85
|
+
|
|
86
|
+
const REASONING_EFFORT_RANK: Record<ReasoningEffortWire, number> = {
|
|
87
|
+
none: 0,
|
|
88
|
+
low: 1,
|
|
89
|
+
medium: 2,
|
|
90
|
+
high: 3,
|
|
91
|
+
xhigh: 4,
|
|
92
|
+
max: 5,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** Map our internal effort values to a reasoning_effort wire value. `"max"`
|
|
96
|
+
* is emitted raw — providers cap it down to their own ceiling
|
|
97
|
+
* ({@link OpenAIChatCompletionsProviderOptions.maxReasoningEffort}) at send
|
|
98
|
+
* time. `"none"` is passed through explicitly because OpenAI-compatible APIs
|
|
99
|
+
* default `reasoning_effort` to `"medium"` when the field is omitted, so the
|
|
100
|
+
* user's opt-out is only honored when we send it on the wire. */
|
|
101
|
+
export const EFFORT_TO_REASONING_EFFORT: Record<string, ReasoningEffortWire> = {
|
|
85
102
|
none: "none",
|
|
86
103
|
low: "low",
|
|
87
104
|
medium: "medium",
|
|
88
105
|
high: "high",
|
|
89
106
|
xhigh: "xhigh",
|
|
90
|
-
max: "
|
|
107
|
+
max: "max",
|
|
91
108
|
};
|
|
92
109
|
|
|
110
|
+
export function clampReasoningEffort(
|
|
111
|
+
value: ReasoningEffortWire,
|
|
112
|
+
ceiling: "high" | "xhigh" | "max",
|
|
113
|
+
): ReasoningEffortWire {
|
|
114
|
+
return REASONING_EFFORT_RANK[value] > REASONING_EFFORT_RANK[ceiling]
|
|
115
|
+
? ceiling
|
|
116
|
+
: value;
|
|
117
|
+
}
|
|
118
|
+
|
|
93
119
|
const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
|
|
94
120
|
"image/jpeg",
|
|
95
121
|
"image/png",
|
|
@@ -97,6 +123,13 @@ const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
|
|
|
97
123
|
"image/webp",
|
|
98
124
|
]);
|
|
99
125
|
|
|
126
|
+
function partialTagSuffix(text: string, tag: string): number {
|
|
127
|
+
for (let len = Math.min(text.length, tag.length - 1); len > 0; len--) {
|
|
128
|
+
if (text.endsWith(tag.substring(0, len))) return len;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
100
133
|
/**
|
|
101
134
|
* OpenAI-compatible chat-completions transport.
|
|
102
135
|
*
|
|
@@ -111,8 +144,9 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
111
144
|
private model: string;
|
|
112
145
|
private streamTimeoutMs: number;
|
|
113
146
|
private extraCreateParams: Record<string, unknown>;
|
|
114
|
-
private maxReasoningEffort: "high" | "xhigh";
|
|
147
|
+
private maxReasoningEffort: "high" | "xhigh" | "max";
|
|
115
148
|
private requestHeaders: Record<string, string>;
|
|
149
|
+
private parseThinkTags: boolean;
|
|
116
150
|
|
|
117
151
|
constructor(
|
|
118
152
|
apiKey: string,
|
|
@@ -130,6 +164,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
130
164
|
this.extraCreateParams = options.extraCreateParams ?? {};
|
|
131
165
|
this.maxReasoningEffort = options.maxReasoningEffort ?? "xhigh";
|
|
132
166
|
this.requestHeaders = options.requestHeaders ?? {};
|
|
167
|
+
this.parseThinkTags = options.parseThinkTags ?? false;
|
|
133
168
|
}
|
|
134
169
|
|
|
135
170
|
async sendMessage(
|
|
@@ -163,14 +198,24 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
163
198
|
params.max_completion_tokens = maxTokens;
|
|
164
199
|
}
|
|
165
200
|
|
|
201
|
+
// Subclasses (OpenRouter) may already have nested effort under
|
|
202
|
+
// `reasoning.effort` via `buildExtraCreateParams`. Skip the flat
|
|
203
|
+
// `reasoning_effort` assignment in that case to avoid sending both forms,
|
|
204
|
+
// which OpenRouter rejects on reasoning models.
|
|
205
|
+
const nestedReasoningEffort = (
|
|
206
|
+
params as { reasoning?: { effort?: unknown } }
|
|
207
|
+
).reasoning?.effort;
|
|
166
208
|
const reasoningEffort = effort
|
|
167
209
|
? EFFORT_TO_REASONING_EFFORT[effort]
|
|
168
210
|
: undefined;
|
|
169
|
-
if (reasoningEffort) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
211
|
+
if (reasoningEffort && typeof nestedReasoningEffort !== "string") {
|
|
212
|
+
const ceiling = this.resolveMaxReasoningEffort(
|
|
213
|
+
modelOverride ?? this.model,
|
|
214
|
+
);
|
|
215
|
+
params.reasoning_effort = clampReasoningEffort(
|
|
216
|
+
reasoningEffort,
|
|
217
|
+
ceiling,
|
|
218
|
+
) as OpenAI.Chat.Completions.ChatCompletionCreateParams["reasoning_effort"];
|
|
174
219
|
}
|
|
175
220
|
|
|
176
221
|
if (tools && tools.length > 0) {
|
|
@@ -189,6 +234,68 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
189
234
|
|
|
190
235
|
// Accumulate the response from chunks
|
|
191
236
|
let contentText = "";
|
|
237
|
+
let reasoningText = "";
|
|
238
|
+
let insideThinkBlock = false;
|
|
239
|
+
let pendingContent = "";
|
|
240
|
+
|
|
241
|
+
const flushPendingContent = (final: boolean): void => {
|
|
242
|
+
while (pendingContent.length > 0) {
|
|
243
|
+
if (insideThinkBlock) {
|
|
244
|
+
const closeIdx = pendingContent.indexOf("</think>");
|
|
245
|
+
if (closeIdx >= 0) {
|
|
246
|
+
const thinking = pendingContent.substring(0, closeIdx);
|
|
247
|
+
if (thinking) {
|
|
248
|
+
reasoningText += thinking;
|
|
249
|
+
onEvent?.({ type: "thinking_delta", thinking });
|
|
250
|
+
}
|
|
251
|
+
insideThinkBlock = false;
|
|
252
|
+
pendingContent = pendingContent.substring(
|
|
253
|
+
closeIdx + "</think>".length,
|
|
254
|
+
);
|
|
255
|
+
} else {
|
|
256
|
+
const partial = final
|
|
257
|
+
? 0
|
|
258
|
+
: partialTagSuffix(pendingContent, "</think>");
|
|
259
|
+
const safeLen = pendingContent.length - partial;
|
|
260
|
+
if (safeLen > 0) {
|
|
261
|
+
const thinking = pendingContent.substring(0, safeLen);
|
|
262
|
+
reasoningText += thinking;
|
|
263
|
+
onEvent?.({ type: "thinking_delta", thinking });
|
|
264
|
+
}
|
|
265
|
+
pendingContent =
|
|
266
|
+
partial > 0 ? pendingContent.substring(safeLen) : "";
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
const openIdx = pendingContent.indexOf("<think>");
|
|
271
|
+
if (openIdx >= 0) {
|
|
272
|
+
const text = pendingContent.substring(0, openIdx);
|
|
273
|
+
if (text) {
|
|
274
|
+
contentText += text;
|
|
275
|
+
onEvent?.({ type: "text_delta", text });
|
|
276
|
+
}
|
|
277
|
+
insideThinkBlock = true;
|
|
278
|
+
pendingContent = pendingContent.substring(
|
|
279
|
+
openIdx + "<think>".length,
|
|
280
|
+
);
|
|
281
|
+
} else {
|
|
282
|
+
const partial = final
|
|
283
|
+
? 0
|
|
284
|
+
: partialTagSuffix(pendingContent, "<think>");
|
|
285
|
+
const safeLen = pendingContent.length - partial;
|
|
286
|
+
if (safeLen > 0) {
|
|
287
|
+
const t = pendingContent.substring(0, safeLen);
|
|
288
|
+
contentText += t;
|
|
289
|
+
onEvent?.({ type: "text_delta", text: t });
|
|
290
|
+
}
|
|
291
|
+
pendingContent =
|
|
292
|
+
partial > 0 ? pendingContent.substring(safeLen) : "";
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
|
|
192
299
|
const toolCallMap = new Map<
|
|
193
300
|
number,
|
|
194
301
|
{ id: string; name: string; args: string }
|
|
@@ -216,8 +323,62 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
216
323
|
const choice = chunk.choices[0];
|
|
217
324
|
if (choice) {
|
|
218
325
|
if (choice.delta.content) {
|
|
219
|
-
|
|
220
|
-
|
|
326
|
+
if (this.parseThinkTags) {
|
|
327
|
+
pendingContent += choice.delta.content;
|
|
328
|
+
flushPendingContent(false);
|
|
329
|
+
} else {
|
|
330
|
+
contentText += choice.delta.content;
|
|
331
|
+
onEvent?.({ type: "text_delta", text: choice.delta.content });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Compatibility providers disagree on the field name: Fireworks /
|
|
336
|
+
// DeepSeek / Together / Groq stream `reasoning_content`; OpenRouter
|
|
337
|
+
// (per its ChatAssistantMessage spec) streams `reasoning`, and for
|
|
338
|
+
// reasoning summaries (e.g. Kimi K2.6) also populates
|
|
339
|
+
// `delta.reasoning_details[]` (entries are `reasoning.summary`,
|
|
340
|
+
// `reasoning.text`, or opaque `reasoning.encrypted`).
|
|
341
|
+
//
|
|
342
|
+
// Kimi K2.6 mirrors the same token into BOTH `delta.reasoning` and
|
|
343
|
+
// `delta.reasoning_details[].text` per chunk — prefer details when
|
|
344
|
+
// they carry visible text, otherwise fall through to the flat
|
|
345
|
+
// field. The encrypted-only case must fall through too, so the
|
|
346
|
+
// flat `reasoning` field isn't silently dropped.
|
|
347
|
+
const deltaWithReasoning = choice.delta as {
|
|
348
|
+
reasoning?: string | null;
|
|
349
|
+
reasoning_content?: string | null;
|
|
350
|
+
reasoning_details?: Array<{
|
|
351
|
+
type?: string;
|
|
352
|
+
summary?: string | null;
|
|
353
|
+
text?: string | null;
|
|
354
|
+
}> | null;
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
let sawVisibleDetail = false;
|
|
358
|
+
const reasoningDetails = deltaWithReasoning.reasoning_details;
|
|
359
|
+
if (Array.isArray(reasoningDetails)) {
|
|
360
|
+
for (const entry of reasoningDetails) {
|
|
361
|
+
if (entry.type === "reasoning.encrypted") continue;
|
|
362
|
+
const piece = entry.summary ?? entry.text;
|
|
363
|
+
if (piece) {
|
|
364
|
+
sawVisibleDetail = true;
|
|
365
|
+
reasoningText += piece;
|
|
366
|
+
onEvent?.({ type: "thinking_delta", thinking: piece });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (!sawVisibleDetail) {
|
|
372
|
+
const reasoningContent =
|
|
373
|
+
deltaWithReasoning.reasoning_content ??
|
|
374
|
+
deltaWithReasoning.reasoning;
|
|
375
|
+
if (reasoningContent) {
|
|
376
|
+
reasoningText += reasoningContent;
|
|
377
|
+
onEvent?.({
|
|
378
|
+
type: "thinking_delta",
|
|
379
|
+
thinking: reasoningContent,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
221
382
|
}
|
|
222
383
|
|
|
223
384
|
if (choice.delta.tool_calls) {
|
|
@@ -260,10 +421,27 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
260
421
|
cleanupTimeout();
|
|
261
422
|
}
|
|
262
423
|
|
|
424
|
+
if (this.parseThinkTags && pendingContent) {
|
|
425
|
+
flushPendingContent(true);
|
|
426
|
+
}
|
|
427
|
+
|
|
263
428
|
// Build content blocks
|
|
429
|
+
const finalReasoning = this.parseThinkTags
|
|
430
|
+
? reasoningText.trim()
|
|
431
|
+
: reasoningText;
|
|
432
|
+
const finalContent = this.parseThinkTags
|
|
433
|
+
? contentText.trim()
|
|
434
|
+
: contentText;
|
|
264
435
|
const content: ContentBlock[] = [];
|
|
265
|
-
if (
|
|
266
|
-
content.push({
|
|
436
|
+
if (finalReasoning) {
|
|
437
|
+
content.push({
|
|
438
|
+
type: "thinking",
|
|
439
|
+
thinking: finalReasoning,
|
|
440
|
+
signature: "",
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
if (finalContent) {
|
|
444
|
+
content.push({ type: "text", text: finalContent });
|
|
267
445
|
}
|
|
268
446
|
for (const [, tc] of toolCallMap) {
|
|
269
447
|
let input: Record<string, unknown>;
|
|
@@ -393,6 +571,18 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
393
571
|
return this.extraCreateParams;
|
|
394
572
|
}
|
|
395
573
|
|
|
574
|
+
/**
|
|
575
|
+
* Per-request reasoning_effort ceiling. Defaults to the provider-wide
|
|
576
|
+
* `maxReasoningEffort` from constructor options. Subclasses (e.g. Fireworks)
|
|
577
|
+
* override to consult the model catalog so per-model accepted ranges are
|
|
578
|
+
* respected.
|
|
579
|
+
*/
|
|
580
|
+
protected resolveMaxReasoningEffort(
|
|
581
|
+
_model: string,
|
|
582
|
+
): "high" | "xhigh" | "max" {
|
|
583
|
+
return this.maxReasoningEffort;
|
|
584
|
+
}
|
|
585
|
+
|
|
396
586
|
/** Convert neutral messages + system prompt to OpenAI message format. */
|
|
397
587
|
private toOpenAIMessages(
|
|
398
588
|
messages: Message[],
|
|
@@ -23,6 +23,9 @@ export interface OpenAIResponsesProviderOptions {
|
|
|
23
23
|
providerLabel?: string;
|
|
24
24
|
streamTimeoutMs?: number;
|
|
25
25
|
useNativeWebSearch?: boolean;
|
|
26
|
+
/** When true, target the Codex subscription endpoint and strip fields it
|
|
27
|
+
* rejects (`max_output_tokens`, `metadata`). */
|
|
28
|
+
codexSubscription?: boolean;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
31
|
/** Map our internal effort values to the Responses API reasoning.effort parameter.
|
|
@@ -103,6 +106,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
103
106
|
private model: string;
|
|
104
107
|
private streamTimeoutMs: number;
|
|
105
108
|
private useNativeWebSearch: boolean;
|
|
109
|
+
private codexSubscription: boolean;
|
|
106
110
|
|
|
107
111
|
constructor(
|
|
108
112
|
apiKey: string,
|
|
@@ -111,9 +115,12 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
111
115
|
) {
|
|
112
116
|
this.name = options.providerName ?? "openai";
|
|
113
117
|
this.providerLabel = options.providerLabel ?? "OpenAI";
|
|
118
|
+
this.codexSubscription = options.codexSubscription ?? false;
|
|
114
119
|
this.client = new OpenAI({
|
|
115
120
|
apiKey,
|
|
116
|
-
baseURL:
|
|
121
|
+
baseURL: this.codexSubscription
|
|
122
|
+
? "https://chatgpt.com/backend-api/codex"
|
|
123
|
+
: options.baseURL,
|
|
117
124
|
});
|
|
118
125
|
this.model = model;
|
|
119
126
|
this.streamTimeoutMs = options.streamTimeoutMs ?? 1_800_000;
|
|
@@ -142,7 +149,6 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
142
149
|
const params: Record<string, unknown> = {
|
|
143
150
|
model: modelOverride ?? this.model,
|
|
144
151
|
input,
|
|
145
|
-
store: false,
|
|
146
152
|
};
|
|
147
153
|
|
|
148
154
|
if (systemPrompt) {
|
|
@@ -152,7 +158,7 @@ export class OpenAIResponsesProvider implements Provider {
|
|
|
152
158
|
);
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
if (maxTokens) {
|
|
161
|
+
if (maxTokens && !this.codexSubscription) {
|
|
156
162
|
params.max_output_tokens = maxTokens;
|
|
157
163
|
}
|
|
158
164
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { ProviderError } from "../../util/errors.js";
|
|
2
2
|
import { AnthropicProvider } from "../anthropic/client.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
clampReasoningEffort,
|
|
5
|
+
EFFORT_TO_REASONING_EFFORT,
|
|
6
|
+
OpenAIChatCompletionsProvider,
|
|
7
|
+
} from "../openai/chat-completions-provider.js";
|
|
4
8
|
import { isThinkingConfigEnabled } from "../thinking-config.js";
|
|
5
9
|
import type {
|
|
6
10
|
Message,
|
|
@@ -53,6 +57,25 @@ export function extractOnlyList(config: unknown): string[] {
|
|
|
53
57
|
return only.filter((x): x is string => typeof x === "string" && x.length > 0);
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
// OpenRouter's `reasoning.summary` field controls whether reasoning models emit
|
|
61
|
+
// a human-readable summary alongside (or instead of) encrypted reasoning blocks.
|
|
62
|
+
// Models like Kimi K2.6 return only encrypted `reasoning_details` unless a
|
|
63
|
+
// summary level is requested, so the stream carries no visible thinking content.
|
|
64
|
+
// Default to "detailed" so users see thinking by default; allow per-call
|
|
65
|
+
// override via `config.openrouter.reasoning.summary`. Per OpenRouter's
|
|
66
|
+
// ChatRequestReasoning schema, valid values are "auto" | "concise" | "detailed".
|
|
67
|
+
const VALID_REASONING_SUMMARIES = new Set(["auto", "concise", "detailed"]);
|
|
68
|
+
|
|
69
|
+
function extractReasoningSummaryOverride(config: unknown): string | undefined {
|
|
70
|
+
const cfg = config as
|
|
71
|
+
| { openrouter?: { reasoning?: { summary?: unknown } } }
|
|
72
|
+
| undefined;
|
|
73
|
+
const summary = cfg?.openrouter?.reasoning?.summary;
|
|
74
|
+
return typeof summary === "string" && VALID_REASONING_SUMMARIES.has(summary)
|
|
75
|
+
? summary
|
|
76
|
+
: undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
56
79
|
/**
|
|
57
80
|
* Rewrite `options.config` for the Anthropic-compat path so OpenRouter's
|
|
58
81
|
* `provider: { only: [...] }` body field travels through `AnthropicProvider`'s
|
|
@@ -160,14 +183,33 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
160
183
|
// OpenRouter's unified `reasoning` parameter controls extended thinking on
|
|
161
184
|
// its OpenAI-compatible endpoint. Anthropic models skip this path entirely and
|
|
162
185
|
// go through AnthropicProvider, which receives the native `thinking` object.
|
|
186
|
+
//
|
|
187
|
+
// `effort` nests under `reasoning` here (rather than flat `reasoning_effort`)
|
|
188
|
+
// because OpenRouter's documented `ChatRequestReasoning` shape is the union of
|
|
189
|
+
// { effort, summary }. `summary` is required for models like Kimi K2.6 that
|
|
190
|
+
// would otherwise return only encrypted reasoning blocks; we default to
|
|
191
|
+
// "detailed" and let callers override via `config.openrouter.reasoning.summary`.
|
|
163
192
|
protected override buildExtraCreateParams(
|
|
164
193
|
options?: SendMessageOptions,
|
|
165
194
|
): Record<string, unknown> {
|
|
166
195
|
const config = options?.config as Record<string, unknown> | undefined;
|
|
167
196
|
const thinkingEnabled = isThinkingConfigEnabled(config?.thinking);
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
|
|
197
|
+
const effort = config?.effort as string | undefined;
|
|
198
|
+
const mappedEffort = effort
|
|
199
|
+
? EFFORT_TO_REASONING_EFFORT[effort]
|
|
200
|
+
: undefined;
|
|
201
|
+
const summaryOverride = extractReasoningSummaryOverride(config);
|
|
202
|
+
const reasoning: Record<string, unknown> = { enabled: thinkingEnabled };
|
|
203
|
+
if (mappedEffort) {
|
|
204
|
+
reasoning.effort = clampReasoningEffort(
|
|
205
|
+
mappedEffort,
|
|
206
|
+
this.resolveMaxReasoningEffort(this.resolveEffectiveModel(options)),
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
if (thinkingEnabled) {
|
|
210
|
+
reasoning.summary = summaryOverride ?? "detailed";
|
|
211
|
+
}
|
|
212
|
+
const extras: Record<string, unknown> = { reasoning };
|
|
171
213
|
const only = extractOnlyList(config);
|
|
172
214
|
if (only.length > 0) {
|
|
173
215
|
const existingProvider = (config?.provider ?? {}) as Record<
|
|
@@ -43,14 +43,13 @@ export const PLATFORM_PROVIDER_META: Record<string, ManagedProviderMeta> = {
|
|
|
43
43
|
},
|
|
44
44
|
fireworks: {
|
|
45
45
|
name: "fireworks",
|
|
46
|
-
managed:
|
|
46
|
+
managed: true,
|
|
47
|
+
proxyPath: "/v1/runtime-proxy/fireworks",
|
|
47
48
|
},
|
|
48
49
|
openrouter: {
|
|
49
50
|
name: "openrouter",
|
|
50
51
|
managed: false,
|
|
51
52
|
},
|
|
52
53
|
ollama: { name: "ollama", managed: false },
|
|
53
|
-
|
|
54
|
-
deepseek: { name: "deepseek", managed: false },
|
|
55
|
-
minimax: { name: "minimax", managed: false },
|
|
54
|
+
"openai-compatible": { name: "openai-compatible", managed: false },
|
|
56
55
|
};
|