@vellumai/assistant 0.11.3 → 0.11.4-dev.202608190019.b94dbf2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +9 -3
- package/ARCHITECTURE.md +32 -10
- package/docs/architecture/memory.md +43 -5
- package/docs/architecture/security.md +96 -152
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/browser-use-architecture-phase2.md +128 -56
- package/docs/flux-turn-detection-spike.md +248 -0
- package/docs/guardian-request-flow.md +35 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/knip.json +3 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +166 -1
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/package.json +1 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/url-normalization.test.ts +135 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +60 -0
- package/node_modules/@vellumai/service-contracts/src/url-normalization.ts +107 -0
- package/openapi.yaml +289 -113
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/scripts/write-plugin-api-shim.ts +10 -0
- package/src/__tests__/app-compiler.test.ts +38 -3
- package/src/__tests__/app-control-flow.test.ts +1 -0
- package/src/__tests__/approval-routes-http.test.ts +2 -2
- package/src/__tests__/assistant-feature-flag-guard.test.ts +25 -3
- package/src/__tests__/attachments-store.test.ts +21 -12
- package/src/__tests__/byok-default-profile-ensure.test.ts +17 -0
- package/src/__tests__/call-setup-flow-name-capture.test.ts +0 -1
- package/src/__tests__/call-site-routing-provider.test.ts +1 -1
- package/src/__tests__/channel-availability-routes.test.ts +14 -1
- package/src/__tests__/channel-capabilities-dedupe.test.ts +214 -0
- package/src/__tests__/channel-delivery-store.test.ts +14 -14
- package/src/__tests__/channel-inbound-disk-pressure.test.ts +4 -4
- package/src/__tests__/channel-setup-panel-ack.test.ts +1 -1
- package/src/__tests__/checker.test.ts +20 -314
- package/src/__tests__/cli-memory-v2-reembed-skills.test.ts +6 -2
- package/src/__tests__/compaction-events.test.ts +8 -10
- package/src/__tests__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- package/src/__tests__/context-search-memory-v2-source.test.ts +0 -1
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +8 -11
- package/src/__tests__/conversation-agent-loop.test.ts +32 -21
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-attention-store.test.ts +63 -0
- package/src/__tests__/conversation-confirmation-signals.test.ts +112 -0
- package/src/__tests__/conversation-delete-schedule-cleanup.test.ts +0 -4
- package/src/__tests__/conversation-fork-crud.test.ts +69 -0
- package/src/__tests__/conversation-fork-referential.test.ts +67 -0
- package/src/__tests__/conversation-fork-retrospective.test.ts +24 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +209 -0
- package/src/__tests__/conversation-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +154 -4
- package/src/__tests__/conversation-routes-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +1 -1
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +9 -9
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +1 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +187 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/conversation-slash-queue.test.ts +3 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-activation-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-app-open.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-data-persist.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-history-restored-completion.test.ts +21 -14
- package/src/__tests__/conversation-surfaces-queued-emit.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-state-update.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-table-action.test.ts +1 -1
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +1 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +1 -1
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/credential-security-invariants.test.ts +1 -0
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/custom-profile-ensure.test.ts +5 -1
- package/src/__tests__/default-plugin-names-guard.test.ts +12 -1
- package/src/__tests__/discord-access-request-privacy.test.ts +5 -1
- package/src/__tests__/discord-requester-notice-privacy.test.ts +3 -3
- package/src/__tests__/document-append-idempotency.test.ts +233 -0
- package/src/__tests__/document-sync-tags.test.ts +0 -75
- package/src/__tests__/dynamic-skill-background-guard.test.ts +0 -2
- package/src/__tests__/edit-propagation.test.ts +107 -11
- package/src/__tests__/file-ops-service.test.ts +163 -30
- package/src/__tests__/filesystem-tools.test.ts +23 -24
- package/src/__tests__/gateway-only-guard.test.ts +2 -5
- package/src/__tests__/helpers/gateway-classify-mock.ts +27 -6
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/host-file-read-tool.test.ts +16 -19
- package/src/__tests__/http-user-message-parity.test.ts +1 -1
- package/src/__tests__/init-feature-flag-overrides.test.ts +49 -0
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/inline-skill-load-permissions.test.ts +47 -36
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/managed-skill-lifecycle.test.ts +7 -0
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/media-generate-image.test.ts +131 -21
- package/src/__tests__/memory-retrieval-hook.test.ts +100 -7
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +3 -1
- package/src/__tests__/non-member-access-request.test.ts +0 -20
- package/src/__tests__/outbound-slack-persistence.test.ts +40 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +0 -4
- package/src/__tests__/plugin-api-store-credential.test.ts +261 -0
- package/src/__tests__/plugin-api-webhook-url.test.ts +10 -7
- package/src/__tests__/plugin-disabled-state.test.ts +2 -0
- package/src/__tests__/plugin-effective-enabled-set.test.ts +55 -3
- package/src/__tests__/plugin-execution-context.test.ts +73 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/plugin-import-boundary-reverse-guard.test.ts +7 -4
- package/src/__tests__/plugin-secret-pattern-contribution.test.ts +1 -1
- package/src/__tests__/post-compaction-reinjection-idempotency.test.ts +14 -7
- package/src/__tests__/provider-commit-message-generator.test.ts +20 -0
- package/src/__tests__/proxy-approval-callback.test.ts +1 -0
- package/src/__tests__/qdrant-manager.test.ts +14 -1
- package/src/__tests__/reaction-persistence.test.ts +200 -7
- package/src/__tests__/require-fresh-approval.test.ts +0 -4
- package/src/__tests__/risk-classification-boundary-guard.test.ts +115 -0
- package/src/__tests__/run-conversation-turn-persistence.test.ts +460 -105
- package/src/__tests__/run-due-schedules.test.ts +21 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +187 -18
- package/src/__tests__/schedule-routes.test.ts +23 -0
- package/src/__tests__/schedule-store.test.ts +17 -0
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/secret-ingress-http.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +3 -3
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/starter-task-flow.test.ts +5 -4
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-fork-prompt-role.test.ts +1 -1
- package/src/__tests__/subagent-spawn-and-await.test.ts +18 -17
- package/src/__tests__/subagent-tool-gate-mode.test.ts +1 -1
- package/src/__tests__/subagent-tools.test.ts +81 -101
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +1 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +1 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +132 -5
- package/src/__tests__/tool-executor.test.ts +68 -39
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +1 -1
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-visual-surface.test.ts +1 -1
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/ui-work-result-surface.test.ts +1 -1
- package/src/__tests__/user-plugin-loader.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -2
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +5 -3
- package/src/__tests__/voice-session-bridge.test.ts +85 -29
- package/src/__tests__/worker-entrypoint-guards.test.ts +54 -0
- package/src/__tests__/worker-plugin-surface.test.ts +77 -0
- package/src/__tests__/workspace-migration-142-consolidate-voice-front-door.test.ts +158 -0
- package/src/__tests__/workspace-migration-143-repair-deprecated-codex-model-id.test.ts +133 -0
- package/src/__tests__/workspace-migration-144-convert-stranded-subscription-openai-profiles.test.ts +316 -0
- package/src/__tests__/workspace-migration-145-collapse-profile-bindings-to-entries.test.ts +325 -0
- package/src/__tests__/workspace-migration-146-repair-retired-fireworks-deepseek-flash-model-id.test.ts +235 -0
- package/src/acp/__tests__/acp-claude-oauth.test.ts +10 -2
- package/src/acp/__tests__/auth-required.test.ts +161 -0
- package/src/acp/acp-claude-oauth.ts +19 -2
- package/src/acp/agent-process.test.ts +100 -0
- package/src/acp/agent-process.ts +29 -26
- package/src/acp/auth-required.ts +102 -0
- package/src/acp/session-manager.test.ts +119 -0
- package/src/acp/session-manager.ts +76 -3
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/events/host-file.ts +2 -2
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +12 -3
- package/src/apps/app-store.ts +3 -0
- package/src/bundler/package-resolver.ts +2 -30
- package/src/calls/__tests__/voice-session-bridge.test.ts +194 -11
- package/src/calls/__tests__/voice-triage-escalate.test.ts +102 -0
- package/src/calls/call-controller.ts +19 -3
- package/src/calls/call-setup-flow.ts +0 -1
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/calls/voice-session-bridge.ts +115 -41
- package/src/calls/voice-triage-escalate.ts +105 -2
- package/src/channels/__tests__/plugin-channel-declarations.test.ts +161 -0
- package/src/channels/config.ts +13 -0
- package/src/channels/plugin-channel-declarations.ts +108 -0
- package/src/channels/types.ts +30 -0
- package/src/cli/AGENTS.md +5 -2
- package/src/cli/bundled-modules.ts +29 -0
- package/src/cli/commands/credentials.help.ts +2 -2
- package/src/cli/commands/db/repair.ts +4 -8
- package/src/cli/commands/domain.ts +6 -3
- package/src/cli/commands/email.ts +6 -3
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/keys.ts +8 -3
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v2.test.ts +57 -7
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +84 -28
- package/src/cli/commands/memory/index.ts +2 -0
- package/src/cli/commands/memory/memory-v2.ts +58 -54
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/memory/memory-validate.ts +18 -0
- package/src/cli/commands/plugins.ts +85 -36
- package/src/cli/commands/schedules.ts +35 -1
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/commands/trust.ts +3 -14
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +14 -1
- package/src/cli/lib/upgrade-plugin.ts +42 -0
- package/src/config/__tests__/balanced-model-experiment.test.ts +278 -0
- package/src/config/__tests__/default-profile-catalog.test.ts +34 -2
- package/src/config/__tests__/default-provider.test.ts +6 -1
- package/src/config/__tests__/profile-materialization.test.ts +75 -19
- package/src/config/assistant-feature-flags.ts +36 -15
- package/src/config/balanced-model-experiment.ts +35 -0
- package/src/config/bundled-skills/acp/SKILL.md +6 -7
- package/src/config/bundled-skills/document-editor/SKILL.md +2 -2
- package/src/config/bundled-skills/document-editor/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/SKILL.md +5 -4
- package/src/config/bundled-skills/image-studio/TOOLS.json +1 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +101 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +14 -4
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.test.ts +65 -0
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +7 -1
- package/src/config/bundled-skills/settings/tools/shared.ts +16 -0
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +9 -3
- package/src/config/bundled-skills/subagent/SKILL.md +17 -12
- package/src/config/bundled-skills/subagent/TOOLS.json +4 -4
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +22 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +9 -2
- package/src/config/call-site-defaults.ts +11 -5
- package/src/config/default-profile-catalog.ts +179 -16
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- package/src/config/feature-flag-registry.json +12 -12
- package/src/config/llm-context-resolution.ts +11 -3
- package/src/config/llm-resolver.ts +28 -1
- package/src/config/profile-materialization.ts +70 -22
- package/src/config/schemas/__tests__/live-voice.test.ts +107 -4
- package/src/config/schemas/call-site-catalog.ts +4 -4
- package/src/config/schemas/live-voice.ts +57 -23
- package/src/config/schemas/llm.ts +59 -32
- package/src/config/schemas/mcp.ts +23 -0
- package/src/config/schemas/plugin-updates.ts +6 -2
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/skills.ts +9 -2
- package/src/context/outbound-sanitize.ts +96 -1
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +1 -1
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +82 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +15 -10
- package/src/daemon/conversation-agent-loop.ts +31 -19
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +20 -10
- package/src/daemon/conversation-process.ts +20 -6
- package/src/daemon/conversation-runtime-assembly.ts +12 -6
- package/src/daemon/conversation-store.ts +14 -4
- package/src/daemon/conversation-surfaces.ts +75 -26
- package/src/daemon/conversation-tool-setup.ts +3 -6
- package/src/daemon/conversation.ts +154 -51
- package/src/daemon/doordash-steps.ts +2 -2
- package/src/daemon/lifecycle.ts +14 -1
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +13 -26
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/trust-context.ts +12 -7
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/daemon/windows-compiled-entry.ts +4 -0
- package/src/documents/document-store.ts +143 -240
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +103 -45
- package/src/hooks/types.ts +5 -0
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- package/src/ipc/gateway-client.test.ts +1 -1
- package/src/ipc/gateway-client.ts +14 -21
- package/src/ipc/gateway-flag-listener.ts +17 -3
- package/src/live-voice/__tests__/live-voice-agent-turn.test.ts +1 -104
- package/src/live-voice/__tests__/live-voice-events.test.ts +7 -8
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +1050 -0
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +115 -8
- package/src/live-voice/__tests__/live-voice-photo.test.ts +100 -0
- package/src/live-voice/__tests__/live-voice-progress.test.ts +60 -194
- package/src/live-voice/__tests__/live-voice-stt.test.ts +14 -0
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +29 -0
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +0 -483
- package/src/live-voice/__tests__/live-voice-vad.test.ts +0 -16
- package/src/live-voice/__tests__/progress-narration.test.ts +214 -0
- package/src/live-voice/live-voice-archive.ts +2 -0
- package/src/live-voice/live-voice-manager.ts +16 -3
- package/src/live-voice/live-voice-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +598 -323
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- package/src/live-voice/windows-compiled-live-voice.ts +4 -0
- package/src/mcp/__tests__/effective-config.test.ts +238 -0
- package/src/mcp/__tests__/mcp-auth-orchestrator.test.ts +0 -1
- package/src/mcp/__tests__/mcp-oauth-client-registration.test.ts +200 -0
- package/src/mcp/__tests__/mcp-oauth-provider.test.ts +9 -9
- package/src/mcp/__tests__/plugin-server-credential-isolation.test.ts +95 -0
- package/src/mcp/client.ts +16 -11
- package/src/mcp/effective-config.ts +113 -0
- package/src/mcp/manager.ts +11 -6
- package/src/mcp/mcp-auth-orchestrator.ts +13 -22
- package/src/mcp/mcp-oauth-provider.ts +205 -240
- package/src/monitoring/__tests__/plugin-auto-update.test.ts +166 -3
- package/src/monitoring/control.ts +1 -0
- package/src/monitoring/db-integrity-sample.ts +4 -5
- package/src/monitoring/plugin-auto-update.ts +128 -24
- package/src/notifications/AGENTS.md +2 -0
- package/src/notifications/approval-card-data.ts +33 -0
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/AGENTS.md +16 -0
- package/src/permissions/checker.test.ts +75 -122
- package/src/permissions/checker.ts +44 -560
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- package/src/permissions/prompter.ts +1 -5
- package/src/permissions/question-guardian-request.test.ts +14 -6
- package/src/permissions/question-guardian-request.ts +1 -2
- package/src/persistence/attachments-store.ts +8 -1
- package/src/persistence/bookmark-crud.ts +3 -7
- package/src/persistence/conversation-attention-store.ts +16 -45
- package/src/persistence/conversation-crud.ts +33 -4
- package/src/persistence/conversation-lineage.ts +9 -0
- package/src/persistence/conversation-queries.ts +182 -52
- package/src/persistence/delivery-crud.ts +150 -36
- package/src/persistence/embeddings/qdrant-manager.ts +84 -49
- package/src/persistence/external-conversation-store.ts +32 -4
- package/src/persistence/llm-request-log-store.ts +4 -10
- package/src/persistence/llm-usage-store.ts +8 -3
- package/src/persistence/message-reads.test.ts +197 -0
- package/src/persistence/message-reads.ts +211 -0
- package/src/persistence/migrations/360-add-document-workspace-path.ts +5 -14
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.test.ts +120 -0
- package/src/persistence/migrations/366-chatgpt-subscription-row-identity.ts +62 -0
- package/src/persistence/real-user-turn-filter.ts +27 -3
- package/src/persistence/schema/documents.ts +4 -4
- package/src/persistence/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/constants.ts +8 -0
- package/src/plugin-api/conversation-turn.ts +195 -5
- package/src/plugin-api/index.ts +38 -6
- package/src/plugin-api/resolve-credential.ts +3 -2
- package/src/plugin-api/store-credential.ts +146 -0
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugin-api/webhook-url.ts +13 -11
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/main.ts +21 -7
- package/src/plugins/defaults/memory/AGENTS.md +15 -3
- package/src/plugins/defaults/memory/__tests__/buffer-format.test.ts +204 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +72 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +4 -1
- package/src/plugins/defaults/memory/__tests__/memory-tier-boundary-guard.test.ts +0 -1
- package/src/plugins/defaults/memory/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- package/src/plugins/defaults/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +77 -0
- package/src/plugins/defaults/memory/graph/conversation-graph-memory.ts +24 -6
- package/src/plugins/defaults/memory/graph/image-ref-utils.ts +3 -0
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +1 -30
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.test.ts +34 -0
- package/src/plugins/defaults/memory/graph-topology/pending-buffer.ts +17 -24
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- package/src/plugins/defaults/memory/hooks/user-prompt-submit.ts +53 -5
- package/src/plugins/defaults/memory/indexer.ts +3 -1
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +19 -7
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +4 -4
- package/src/plugins/defaults/memory/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +12 -10
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +147 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-prompt-flag-gating-guard.test.ts +8 -0
- package/src/plugins/defaults/memory/substrate/__tests__/edge-index.test.ts +0 -30
- package/src/plugins/defaults/memory/substrate/__tests__/ingest.test.ts +73 -1
- package/src/plugins/defaults/memory/substrate/__tests__/page-index.test.ts +37 -0
- package/src/plugins/defaults/memory/substrate/__tests__/page-links.test.ts +208 -0
- package/src/plugins/defaults/memory/substrate/__tests__/prompts-consolidation.test.ts +158 -1
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +195 -58
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +93 -24
- package/src/plugins/defaults/memory/substrate/edge-index.ts +0 -31
- package/src/plugins/defaults/memory/substrate/ingest.ts +59 -0
- package/src/plugins/defaults/memory/substrate/page-index.ts +35 -8
- package/src/plugins/defaults/memory/substrate/page-links.ts +133 -0
- package/src/plugins/defaults/memory/substrate/page-store.ts +10 -0
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +124 -34
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -33
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +2 -4
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +3 -1
- package/src/plugins/defaults/memory/v3/__tests__/injection.test.ts +18 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +66 -1
- package/src/plugins/defaults/memory/v3/card.ts +9 -9
- package/src/plugins/defaults/memory/v3/core-set.test.ts +7 -0
- package/src/plugins/defaults/memory/v3/core-set.ts +5 -1
- package/src/plugins/defaults/memory/v3/edge.ts +13 -57
- package/src/plugins/defaults/memory/v3/injector.ts +8 -0
- package/src/plugins/defaults/memory/v3/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +23 -9
- package/src/plugins/defaults/memory/worker-control.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/defaults/worker-entrypoints.ts +3 -0
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +37 -46
- package/src/plugins/pipeline.ts +14 -7
- package/src/plugins/plugin-execution-context.ts +35 -11
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/prompts/templates/system-sections.ts +0 -7
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- package/src/providers/__tests__/context-overflow-error.test.ts +24 -0
- package/src/providers/__tests__/dispatch-connection-routing.test.ts +214 -2
- package/src/providers/__tests__/preflight-resolved-config.test.ts +57 -0
- package/src/providers/__tests__/retry-callsite.test.ts +25 -2
- package/src/providers/anthropic/__tests__/pause-turn-continuation.test.ts +170 -0
- package/src/providers/anthropic/client.ts +332 -241
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- package/src/providers/inference/__tests__/adapter-factory-openai-compatible.test.ts +4 -0
- package/src/providers/inference/adapter-factory.ts +3 -0
- package/src/providers/inference/auth.ts +6 -0
- package/src/providers/inference/connection-availability.ts +24 -2
- package/src/providers/inference/connections.ts +2 -0
- package/src/providers/model-catalog.ts +3 -3
- package/src/providers/model-intents.ts +28 -8
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +163 -40
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +125 -2
- package/src/providers/openai/chat-completions-provider.ts +133 -36
- package/src/providers/openai/codex-models.ts +2 -1
- package/src/providers/provider-send-message.ts +32 -3
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +433 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +625 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +34 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +285 -6
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +395 -0
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +679 -0
- package/src/providers/speech-to-text/provider-catalog.ts +99 -8
- package/src/providers/speech-to-text/resolve.ts +25 -2
- package/src/routes/control.ts +1 -0
- package/src/routes/route-host-client.ts +1 -0
- package/src/routes/route-host-protocol.ts +7 -0
- package/src/routes/worker.ts +48 -15
- package/src/runtime/AGENTS.md +16 -17
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +18 -15
- package/src/runtime/effective-capabilities.test.ts +43 -0
- package/src/runtime/effective-capabilities.ts +27 -0
- package/src/runtime/guardian-reply-router.ts +6 -2
- package/src/runtime/pre-first-message-gate.ts +4 -0
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +12 -4
- package/src/runtime/routes/__tests__/conversation-list-routes.test.ts +522 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +52 -0
- package/src/runtime/routes/__tests__/default-provider-routes.test.ts +61 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +44 -0
- package/src/runtime/routes/__tests__/inference-provider-connection-routes.test.ts +102 -1
- package/src/runtime/routes/__tests__/plugins-routes.test.ts +132 -35
- package/src/runtime/routes/__tests__/schedule-routes-disarm-reason.test.ts +215 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/surface-action-routes.test.ts +55 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher-host.test.ts +52 -1
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +133 -1
- package/src/runtime/routes/channel-availability-routes.ts +32 -14
- package/src/runtime/routes/channel-route-shared.ts +0 -6
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +6 -6
- package/src/runtime/routes/conversation-list-routes.ts +181 -23
- package/src/runtime/routes/conversation-management-routes.ts +17 -12
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/conversation-routes.ts +43 -29
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/credential-routes.ts +26 -82
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/documents-routes.ts +3 -222
- package/src/runtime/routes/inbound-message-handler.ts +21 -45
- package/src/runtime/routes/inbound-stages/acl-enforcement.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +0 -9
- package/src/runtime/routes/inbound-stages/admission-policy.ts +1 -17
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/bootstrap-intercept.ts +2 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +101 -79
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +12 -7
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +91 -8
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +72 -79
- package/src/runtime/routes/inbound-stages/secret-ingress-check.ts +2 -3
- package/src/runtime/routes/inference-profiles-routes.ts +20 -11
- package/src/runtime/routes/inference-provider-connection-routes.ts +77 -15
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/mcp-auth-routes.ts +148 -57
- package/src/runtime/routes/playground/__tests__/inject-failures.test.ts +2 -0
- package/src/runtime/routes/playground/__tests__/reset-circuit.test.ts +3 -0
- package/src/runtime/routes/playground/inject-failures.ts +2 -2
- package/src/runtime/routes/playground/reset-circuit.ts +1 -1
- package/src/runtime/routes/plugins-routes.ts +38 -8
- package/src/runtime/routes/schedule-routes.ts +94 -7
- package/src/runtime/routes/settings-routes.ts +6 -6
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-action-routes.ts +20 -19
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +69 -15
- package/src/runtime/routes/user-route-import.ts +108 -0
- package/src/runtime/routes/user-route-resolution.ts +18 -1
- package/src/runtime/routes/workspace-routes.ts +0 -9
- package/src/runtime/routes/workspace-utils.ts +3 -13
- package/src/runtime/services/conversation-serializer.ts +7 -2
- package/src/schedule/__tests__/plugin-schedule-declarations.test.ts +68 -5
- package/src/schedule/__tests__/plugin-schedule-reconciler.test.ts +81 -0
- package/src/schedule/plugin-schedule-availability.ts +58 -0
- package/src/schedule/plugin-schedule-declarations.ts +23 -27
- package/src/schedule/plugin-schedule-reconciler.ts +12 -3
- package/src/schedule/schedule-store.ts +5 -1
- package/src/schedule/scheduler.ts +9 -4
- package/src/schedule/worker-control.ts +1 -0
- package/src/schedule/worker.ts +6 -0
- package/src/security/oauth2.ts +6 -22
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +22 -0
- package/src/stt/__tests__/types.test.ts +94 -0
- package/src/stt/daemon-batch-transcriber.ts +10 -0
- package/src/stt/stt-stream-session.ts +8 -4
- package/src/stt/types.ts +103 -0
- package/src/subagent/__tests__/consult-prompt.test.ts +26 -15
- package/src/subagent/consult-context.ts +11 -11
- package/src/subagent/consult-prompt.ts +26 -35
- package/src/subagent/manager.ts +21 -40
- package/src/subagent/notify.ts +7 -1
- package/src/subagent/types.ts +15 -12
- package/src/tools/__tests__/tool-input-schemas.test.ts +7 -7
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +38 -4
- package/src/tools/credentials/ref-parse.ts +35 -0
- package/src/tools/credentials/resolve.ts +4 -10
- package/src/tools/credentials/store.ts +168 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/executor.ts +53 -15
- package/src/tools/filesystem/read.ts +27 -10
- package/src/tools/host-filesystem/read.ts +27 -15
- package/src/tools/network/url-safety.ts +9 -16
- package/src/tools/permission-checker.ts +43 -52
- package/src/tools/registry.ts +2 -1
- package/src/tools/shared/filesystem/file-ops-service.ts +63 -35
- package/src/tools/shared/filesystem/legacy-read-args.ts +22 -0
- package/src/tools/shared/filesystem/types.ts +5 -5
- package/src/tools/skills/scaffold-managed.ts +25 -7
- package/src/tools/subagent/spawn.ts +28 -88
- package/src/tools/tool-types.ts +26 -13
- package/src/tools/types.ts +6 -6
- package/src/tools/ui-surface/surface-shape-docs.ts +12 -1
- package/src/tools/workflows/run-workflow.ts +1 -2
- package/src/tts/__tests__/reasoning-tag-filter.test.ts +78 -0
- package/src/tts/reasoning-tag-filter.ts +89 -0
- package/src/util/__tests__/worker-process-command.test.ts +37 -0
- package/src/util/logger.ts +16 -0
- package/src/util/think-tag-stream.ts +95 -0
- package/src/util/worker-process.ts +37 -4
- package/src/windows-compiled-cli.ts +32 -0
- package/src/windows-compiled-entry.ts +4 -0
- package/src/windows-compiled-logger.ts +6 -0
- package/src/windows-compiled-worker-entry.ts +29 -0
- package/src/workspace/byok-default-profile-ensure.ts +76 -24
- package/src/workspace/custom-profile-ensure.ts +4 -24
- package/src/workspace/migrations/142-consolidate-voice-front-door.ts +70 -0
- package/src/workspace/migrations/143-repair-deprecated-codex-model-id.ts +134 -0
- package/src/workspace/migrations/144-convert-stranded-subscription-openai-profiles.ts +265 -0
- package/src/workspace/migrations/145-collapse-profile-bindings-to-entries.ts +328 -0
- package/src/workspace/migrations/146-repair-retired-fireworks-deepseek-flash-model-id.ts +195 -0
- package/src/workspace/migrations/__tests__/141-stt-english-default-to-multilingual.test.ts +0 -10
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/provider-commit-message-generator.ts +7 -5
- package/src/__tests__/document-workspace-file.test.ts +0 -467
- package/src/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
- package/src/permissions/ipc-risk-types.ts +0 -143
- package/src/permissions/risk-types.ts +0 -76
- package/src/subagent/__tests__/consult-transcript.test.ts +0 -184
- package/src/subagent/consult-transcript.ts +0 -90
|
@@ -537,50 +537,47 @@ describe("OpenAIChatCompletionsProvider reasoning parsing", () => {
|
|
|
537
537
|
});
|
|
538
538
|
});
|
|
539
539
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
if (error !== undefined) {
|
|
559
|
-
throw error;
|
|
560
|
-
}
|
|
561
|
-
return makeStream(chunks);
|
|
562
|
-
},
|
|
540
|
+
function stubProviderWithErrors(
|
|
541
|
+
errors: unknown[],
|
|
542
|
+
chunks: MockChunk[],
|
|
543
|
+
): { provider: OpenAIChatCompletionsProvider; requests: unknown[] } {
|
|
544
|
+
const provider = new OpenAIChatCompletionsProvider("test-key", "test-model");
|
|
545
|
+
const requests: unknown[] = [];
|
|
546
|
+
const pending = [...errors];
|
|
547
|
+
(provider as unknown as { client: unknown }).client = {
|
|
548
|
+
chat: {
|
|
549
|
+
completions: {
|
|
550
|
+
create: async (params: unknown) => {
|
|
551
|
+
// Snapshot: the fallback mutates `params` between attempts.
|
|
552
|
+
requests.push(JSON.parse(JSON.stringify(params)));
|
|
553
|
+
const error = pending.shift();
|
|
554
|
+
if (error !== undefined) {
|
|
555
|
+
throw error;
|
|
556
|
+
}
|
|
557
|
+
return makeStream(chunks);
|
|
563
558
|
},
|
|
564
559
|
},
|
|
565
|
-
};
|
|
566
|
-
return { provider, requests };
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const okChunks: MockChunk[] = [
|
|
570
|
-
{
|
|
571
|
-
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
572
|
-
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
|
573
560
|
},
|
|
574
|
-
|
|
561
|
+
};
|
|
562
|
+
return { provider, requests };
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const OK_CHUNKS: MockChunk[] = [
|
|
566
|
+
{
|
|
567
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
568
|
+
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
|
569
|
+
},
|
|
570
|
+
];
|
|
575
571
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
572
|
+
function rejection(message: string, status = 400): Error {
|
|
573
|
+
return Object.assign(new Error(message), { status });
|
|
574
|
+
}
|
|
579
575
|
|
|
576
|
+
describe("reasoning opt-out rejection fallback", () => {
|
|
580
577
|
test("retries once without reasoning params when a model rejects the explicit opt-out", async () => {
|
|
581
578
|
const { provider, requests } = stubProviderWithErrors(
|
|
582
579
|
[rejection("reasoning_effort 'none' is not supported for this model")],
|
|
583
|
-
|
|
580
|
+
OK_CHUNKS,
|
|
584
581
|
);
|
|
585
582
|
|
|
586
583
|
const response = await provider.sendMessage(
|
|
@@ -626,7 +623,7 @@ describe("reasoning opt-out rejection fallback", () => {
|
|
|
626
623
|
// can only fire off the normalized upstream detail.
|
|
627
624
|
expect(/reasoning/i.test(wrapped.message)).toBe(false);
|
|
628
625
|
|
|
629
|
-
const { provider, requests } = stubProviderWithErrors([wrapped],
|
|
626
|
+
const { provider, requests } = stubProviderWithErrors([wrapped], OK_CHUNKS);
|
|
630
627
|
|
|
631
628
|
const response = await provider.sendMessage(
|
|
632
629
|
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
@@ -651,7 +648,7 @@ describe("reasoning opt-out rejection fallback", () => {
|
|
|
651
648
|
test("does not retry when the request did not opt out of reasoning", async () => {
|
|
652
649
|
const { provider, requests } = stubProviderWithErrors(
|
|
653
650
|
[rejection("reasoning_effort is invalid")],
|
|
654
|
-
|
|
651
|
+
OK_CHUNKS,
|
|
655
652
|
);
|
|
656
653
|
|
|
657
654
|
await expect(
|
|
@@ -666,7 +663,7 @@ describe("reasoning opt-out rejection fallback", () => {
|
|
|
666
663
|
test("does not retry a 4xx that does not name reasoning", async () => {
|
|
667
664
|
const { provider, requests } = stubProviderWithErrors(
|
|
668
665
|
[rejection("invalid api key")],
|
|
669
|
-
|
|
666
|
+
OK_CHUNKS,
|
|
670
667
|
);
|
|
671
668
|
|
|
672
669
|
await expect(
|
|
@@ -681,7 +678,7 @@ describe("reasoning opt-out rejection fallback", () => {
|
|
|
681
678
|
test("does not retry server errors", async () => {
|
|
682
679
|
const { provider, requests } = stubProviderWithErrors(
|
|
683
680
|
[rejection("reasoning backend unavailable", 500)],
|
|
684
|
-
|
|
681
|
+
OK_CHUNKS,
|
|
685
682
|
);
|
|
686
683
|
|
|
687
684
|
await expect(
|
|
@@ -694,6 +691,132 @@ describe("reasoning opt-out rejection fallback", () => {
|
|
|
694
691
|
});
|
|
695
692
|
});
|
|
696
693
|
|
|
694
|
+
describe("thinking-mode tool_choice rejection fallback", () => {
|
|
695
|
+
test("retries once without tool_choice when thinking mode rejects it", async () => {
|
|
696
|
+
const { provider, requests } = stubProviderWithErrors(
|
|
697
|
+
[rejection("Thinking mode does not support this tool_choice")],
|
|
698
|
+
OK_CHUNKS,
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
const response = await provider.sendMessage(
|
|
702
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
703
|
+
{
|
|
704
|
+
tools: [
|
|
705
|
+
{
|
|
706
|
+
name: "bash",
|
|
707
|
+
description: "Run a shell command",
|
|
708
|
+
input_schema: { type: "object", properties: {} },
|
|
709
|
+
},
|
|
710
|
+
],
|
|
711
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
712
|
+
},
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
expect(requests).toHaveLength(2);
|
|
716
|
+
const first = requests[0] as {
|
|
717
|
+
tool_choice?: string;
|
|
718
|
+
reasoning_effort?: string;
|
|
719
|
+
};
|
|
720
|
+
const second = requests[1] as {
|
|
721
|
+
tool_choice?: string;
|
|
722
|
+
reasoning_effort?: string;
|
|
723
|
+
};
|
|
724
|
+
expect(first.tool_choice).toBe("none");
|
|
725
|
+
expect(first.reasoning_effort).toBe("high");
|
|
726
|
+
expect(second.tool_choice).toBeUndefined();
|
|
727
|
+
expect(second.reasoning_effort).toBe("high");
|
|
728
|
+
const text = response.content.find((b) => b.type === "text") as
|
|
729
|
+
| { type: "text"; text: string }
|
|
730
|
+
| undefined;
|
|
731
|
+
expect(text?.text).toBe("ok");
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
test("retries once for an OpenRouter-wrapped thinking-mode tool_choice rejection", async () => {
|
|
735
|
+
const wrapped = new OpenAI.APIError(
|
|
736
|
+
400,
|
|
737
|
+
{
|
|
738
|
+
code: 400,
|
|
739
|
+
message: "Provider returned error",
|
|
740
|
+
metadata: {
|
|
741
|
+
raw: "[invalid_request_error] Thinking mode does not support this tool_choice",
|
|
742
|
+
provider_name: "deepseek",
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
undefined,
|
|
746
|
+
new Headers(),
|
|
747
|
+
);
|
|
748
|
+
expect(/tool_choice/i.test(wrapped.message)).toBe(false);
|
|
749
|
+
|
|
750
|
+
const { provider, requests } = stubProviderWithErrors([wrapped], OK_CHUNKS);
|
|
751
|
+
|
|
752
|
+
await provider.sendMessage(
|
|
753
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
754
|
+
{
|
|
755
|
+
tools: [
|
|
756
|
+
{
|
|
757
|
+
name: "bash",
|
|
758
|
+
description: "Run a shell command",
|
|
759
|
+
input_schema: { type: "object", properties: {} },
|
|
760
|
+
},
|
|
761
|
+
],
|
|
762
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
763
|
+
},
|
|
764
|
+
);
|
|
765
|
+
|
|
766
|
+
expect(requests).toHaveLength(2);
|
|
767
|
+
expect((requests[0] as { tool_choice?: string }).tool_choice).toBe("none");
|
|
768
|
+
expect((requests[1] as { tool_choice?: string }).tool_choice).toBeUndefined();
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
test("does not retry a 4xx that does not name tool_choice", async () => {
|
|
772
|
+
const { provider, requests } = stubProviderWithErrors(
|
|
773
|
+
[rejection("invalid api key")],
|
|
774
|
+
OK_CHUNKS,
|
|
775
|
+
);
|
|
776
|
+
|
|
777
|
+
await expect(
|
|
778
|
+
provider.sendMessage(
|
|
779
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
780
|
+
{
|
|
781
|
+
tools: [
|
|
782
|
+
{
|
|
783
|
+
name: "bash",
|
|
784
|
+
description: "Run a shell command",
|
|
785
|
+
input_schema: { type: "object", properties: {} },
|
|
786
|
+
},
|
|
787
|
+
],
|
|
788
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
789
|
+
},
|
|
790
|
+
),
|
|
791
|
+
).rejects.toThrow();
|
|
792
|
+
expect(requests).toHaveLength(1);
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
test("does not retry thinking-mode tool_choice 500s", async () => {
|
|
796
|
+
const { provider, requests } = stubProviderWithErrors(
|
|
797
|
+
[rejection("Thinking mode does not support this tool_choice", 500)],
|
|
798
|
+
OK_CHUNKS,
|
|
799
|
+
);
|
|
800
|
+
|
|
801
|
+
await expect(
|
|
802
|
+
provider.sendMessage(
|
|
803
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
804
|
+
{
|
|
805
|
+
tools: [
|
|
806
|
+
{
|
|
807
|
+
name: "bash",
|
|
808
|
+
description: "Run a shell command",
|
|
809
|
+
input_schema: { type: "object", properties: {} },
|
|
810
|
+
},
|
|
811
|
+
],
|
|
812
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
813
|
+
},
|
|
814
|
+
),
|
|
815
|
+
).rejects.toThrow();
|
|
816
|
+
expect(requests).toHaveLength(1);
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
697
820
|
describe("OpenAIChatCompletionsProvider cache usage parsing", () => {
|
|
698
821
|
test("maps prompt_tokens_details cache fields into usage", async () => {
|
|
699
822
|
// prompt_tokens is the inclusive total; the cached subset surfaces as
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import type { Message, ToolDefinition } from "../../types.js";
|
|
4
4
|
import {
|
|
5
|
+
isThinkingEnabledOnWire,
|
|
5
6
|
mapNeutralToolChoice,
|
|
6
7
|
OpenAIChatCompletionsProvider,
|
|
7
8
|
} from "../chat-completions-provider.js";
|
|
@@ -23,11 +24,17 @@ const USER_MESSAGE: Message[] = [
|
|
|
23
24
|
* Stub the SDK client so we can capture the outgoing chat.completions.create
|
|
24
25
|
* params without hitting the network.
|
|
25
26
|
*/
|
|
26
|
-
function stubChatProvider(
|
|
27
|
+
function stubChatProvider(
|
|
28
|
+
options?: ConstructorParameters<typeof OpenAIChatCompletionsProvider>[2],
|
|
29
|
+
): {
|
|
27
30
|
provider: OpenAIChatCompletionsProvider;
|
|
28
31
|
requests: Array<Record<string, unknown>>;
|
|
29
32
|
} {
|
|
30
|
-
const provider = new OpenAIChatCompletionsProvider(
|
|
33
|
+
const provider = new OpenAIChatCompletionsProvider(
|
|
34
|
+
"test-key",
|
|
35
|
+
"test-model",
|
|
36
|
+
options,
|
|
37
|
+
);
|
|
31
38
|
const requests: Array<Record<string, unknown>> = [];
|
|
32
39
|
(provider as unknown as { client: unknown }).client = {
|
|
33
40
|
chat: {
|
|
@@ -49,6 +56,23 @@ function stubChatProvider(): {
|
|
|
49
56
|
return { provider, requests };
|
|
50
57
|
}
|
|
51
58
|
|
|
59
|
+
describe("isThinkingEnabledOnWire", () => {
|
|
60
|
+
test("detects flat reasoning_effort and nested reasoning flags", () => {
|
|
61
|
+
expect(isThinkingEnabledOnWire({})).toBe(false);
|
|
62
|
+
expect(isThinkingEnabledOnWire({ reasoning_effort: "none" })).toBe(false);
|
|
63
|
+
expect(isThinkingEnabledOnWire({ reasoning_effort: "high" })).toBe(true);
|
|
64
|
+
expect(isThinkingEnabledOnWire({ reasoning: { effort: "none" } })).toBe(
|
|
65
|
+
false,
|
|
66
|
+
);
|
|
67
|
+
expect(isThinkingEnabledOnWire({ reasoning: { effort: "medium" } })).toBe(
|
|
68
|
+
true,
|
|
69
|
+
);
|
|
70
|
+
expect(isThinkingEnabledOnWire({ reasoning: { enabled: true } })).toBe(
|
|
71
|
+
true,
|
|
72
|
+
);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
52
76
|
describe("mapNeutralToolChoice (chat-completions wire format)", () => {
|
|
53
77
|
// Each neutral tool_choice variant maps to its OpenAI-compatible form.
|
|
54
78
|
test("maps the Anthropic-shaped union to OpenAI's tool_choice values", () => {
|
|
@@ -144,4 +168,103 @@ describe("OpenAIChatCompletionsProvider tool_choice wiring", () => {
|
|
|
144
168
|
expect(requests[0].tools).toBeUndefined();
|
|
145
169
|
expect(requests[0].tool_choice).toBeUndefined();
|
|
146
170
|
});
|
|
171
|
+
|
|
172
|
+
// `"auto"` is the chat-completions default when tools are present. Sending it
|
|
173
|
+
// alongside reasoning_effort 400s DeepSeek thinking mode, so it is omitted.
|
|
174
|
+
test("omits redundant tool_choice auto when thinking is on the wire", async () => {
|
|
175
|
+
const { provider, requests } = stubChatProvider();
|
|
176
|
+
|
|
177
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
178
|
+
tools: TOOLS,
|
|
179
|
+
config: { tool_choice: { type: "auto" }, effort: "high" },
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
expect(requests[0].reasoning_effort).toBe("high");
|
|
183
|
+
expect(requests[0].tool_choice).toBeUndefined();
|
|
184
|
+
expect(requests[0].tools).toBeDefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("omits redundant tool_choice auto when nested reasoning.enabled is set", async () => {
|
|
188
|
+
const { provider, requests } = stubChatProvider({
|
|
189
|
+
extraCreateParams: { reasoning: { enabled: true } },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
193
|
+
tools: TOOLS,
|
|
194
|
+
config: { tool_choice: { type: "auto" } },
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
expect(requests[0].tool_choice).toBeUndefined();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
test("forwards tool_choice auto when thinking is off", async () => {
|
|
201
|
+
const { provider, requests } = stubChatProvider();
|
|
202
|
+
|
|
203
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
204
|
+
tools: TOOLS,
|
|
205
|
+
config: { tool_choice: { type: "auto" } },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(requests[0].tool_choice).toBe("auto");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Catalog providers (Fireworks, Together) honor `none` with thinking.
|
|
212
|
+
test("forwards tool_choice none with thinking when omitToolChoiceWhenReasoning is off", async () => {
|
|
213
|
+
const { provider, requests } = stubChatProvider();
|
|
214
|
+
|
|
215
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
216
|
+
tools: TOOLS,
|
|
217
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
expect(requests[0].reasoning_effort).toBe("high");
|
|
221
|
+
expect(requests[0].tool_choice).toBe("none");
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("forwards a forced tool_choice with thinking when omitToolChoiceWhenReasoning is off", async () => {
|
|
225
|
+
const { provider, requests } = stubChatProvider();
|
|
226
|
+
|
|
227
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
228
|
+
tools: TOOLS,
|
|
229
|
+
config: { tool_choice: { type: "tool", name: "bash" }, effort: "high" },
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(requests[0].tool_choice).toEqual({
|
|
233
|
+
type: "function",
|
|
234
|
+
function: { name: "bash" },
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test("omits every tool_choice in thinking mode when omitToolChoiceWhenReasoning is on", async () => {
|
|
239
|
+
const { provider, requests } = stubChatProvider({
|
|
240
|
+
omitToolChoiceWhenReasoning: true,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
244
|
+
tools: TOOLS,
|
|
245
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
246
|
+
});
|
|
247
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
248
|
+
tools: TOOLS,
|
|
249
|
+
config: { tool_choice: { type: "tool", name: "bash" }, effort: "high" },
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(requests[0].reasoning_effort).toBe("high");
|
|
253
|
+
expect(requests[0].tool_choice).toBeUndefined();
|
|
254
|
+
expect(requests[1].tool_choice).toBeUndefined();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("still forwards tool_choice when omitToolChoiceWhenReasoning is on but thinking is off", async () => {
|
|
258
|
+
const { provider, requests } = stubChatProvider({
|
|
259
|
+
omitToolChoiceWhenReasoning: true,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
await provider.sendMessage(USER_MESSAGE, {
|
|
263
|
+
tools: TOOLS,
|
|
264
|
+
config: { tool_choice: { type: "none" } },
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
expect(requests[0].reasoning_effort).toBeUndefined();
|
|
268
|
+
expect(requests[0].tool_choice).toBe("none");
|
|
269
|
+
});
|
|
147
270
|
});
|
|
@@ -5,6 +5,7 @@ import { isAbortReason } from "../../util/abort-reasons.js";
|
|
|
5
5
|
import { ProviderError, type ProviderErrorReason } from "../../util/errors.js";
|
|
6
6
|
import { getLogger } from "../../util/logger.js";
|
|
7
7
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
8
|
+
import { partialTagSuffix as sharedPartialTagSuffix } from "../../util/think-tag-stream.js";
|
|
8
9
|
import { escapeXmlAttr } from "../../util/xml.js";
|
|
9
10
|
import {
|
|
10
11
|
base64Source,
|
|
@@ -84,7 +85,17 @@ export function detectOpenAICompatibleContextOverflow(
|
|
|
84
85
|
/context.?length.?exceeded|context.?window.?exceeded|prompt.?is.?too.?long|prompt_too_long|input.?too.?long|too.?many.?(?:input.?)?tokens|maximum.?context/i.test(
|
|
85
86
|
message,
|
|
86
87
|
);
|
|
87
|
-
|
|
88
|
+
// string_above_max_length is OpenAI's generic oversized-string validation
|
|
89
|
+
// code, so only treat it as overflow when the error points at a message
|
|
90
|
+
// content part (e.g. "Invalid 'input[191].content[1].text': string too
|
|
91
|
+
// long" — OpenAI's per-part 10 MiB cap). The overflow ladder can shrink
|
|
92
|
+
// message content (media stubbing collapses a file's extracted_text to a
|
|
93
|
+
// preview) but cannot fix other oversized fields like tool definitions.
|
|
94
|
+
const oversizedContentPart =
|
|
95
|
+
/string.?too.?long|string_above_max_length/i.test(
|
|
96
|
+
`${code ?? ""} ${message}`,
|
|
97
|
+
) && /\b(?:input|messages)\[\d+\]\.content/i.test(message);
|
|
98
|
+
if (!codeMatches && !messageMatches && !oversizedContentPart) {
|
|
88
99
|
return null;
|
|
89
100
|
}
|
|
90
101
|
// OpenAI-compatible providers rarely report usable token counts; best-effort extract.
|
|
@@ -166,6 +177,13 @@ export interface OpenAIChatCompletionsProviderOptions {
|
|
|
166
177
|
* with minimax-m3 on Fireworks). Off by default; scalars/arrays unaffected.
|
|
167
178
|
* See {@link coerceObjectParamsToJsonString}. */
|
|
168
179
|
coerceObjectArgsToJsonString?: boolean;
|
|
180
|
+
/** Drop `tool_choice` when thinking/reasoning is on the wire. Strict
|
|
181
|
+
* OpenAI-compatible reasoning upstreams (DeepSeek thinking mode) reject any
|
|
182
|
+
* explicit `tool_choice` with `Thinking mode does not support this
|
|
183
|
+
* tool_choice`. Off by default so catalog providers that honor the combo
|
|
184
|
+
* (Fireworks, Together) keep sending `none` / forced choices. Enabled for
|
|
185
|
+
* the generic `openai-compatible` adapter, whose upstream is unknown. */
|
|
186
|
+
omitToolChoiceWhenReasoning?: boolean;
|
|
169
187
|
}
|
|
170
188
|
|
|
171
189
|
const log = getLogger("chat-completions");
|
|
@@ -208,12 +226,29 @@ export function clampReasoningEffort(
|
|
|
208
226
|
: value;
|
|
209
227
|
}
|
|
210
228
|
|
|
229
|
+
/** Human-readable text from an OpenAI-compatible error, including wrapped
|
|
230
|
+
* upstream detail (OpenRouter `metadata.raw`). Used by the one-shot
|
|
231
|
+
* compatibility retries so a generic SDK wrapper message cannot hide the
|
|
232
|
+
* real reason. */
|
|
233
|
+
function openaiCompatErrorHaystack(error: unknown): string {
|
|
234
|
+
return error instanceof OpenAI.APIError
|
|
235
|
+
? normalizedErrorText(normalizeOpenAIAPIError(error))
|
|
236
|
+
: error instanceof Error
|
|
237
|
+
? error.message
|
|
238
|
+
: String(error);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isClientErrorStatus(error: unknown): boolean {
|
|
242
|
+
const status = (error as { status?: unknown }).status;
|
|
243
|
+
return typeof status === "number" && status >= 400 && status < 500;
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
/**
|
|
212
247
|
* True when the request carried an explicit reasoning opt-out (`"none"` sent
|
|
213
248
|
* as flat `reasoning_effort` or nested `reasoning.effort`) and the provider
|
|
214
249
|
* rejected it with a 4xx that names the reasoning field. Reasoning-only
|
|
215
250
|
* models (e.g. DeepSeek R1) reject the opt-out rather than ignore it; that
|
|
216
|
-
* one case is worth a single retry with the reasoning params stripped
|
|
251
|
+
* one case is worth a single retry with the reasoning params stripped:
|
|
217
252
|
* model-default reasoning beats a hard failure.
|
|
218
253
|
*/
|
|
219
254
|
function isReasoningOptOutRejection(error: unknown, params: unknown): boolean {
|
|
@@ -229,22 +264,58 @@ function isReasoningOptOutRejection(error: unknown, params: unknown): boolean {
|
|
|
229
264
|
if (!optedOut) {
|
|
230
265
|
return false;
|
|
231
266
|
}
|
|
232
|
-
|
|
233
|
-
|
|
267
|
+
if (!isClientErrorStatus(error)) {
|
|
268
|
+
return false;
|
|
269
|
+
}
|
|
270
|
+
return /reasoning/i.test(openaiCompatErrorHaystack(error));
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* True when thinking/reasoning is active on the outbound chat-completions
|
|
275
|
+
* body: a non-`"none"` `reasoning_effort`, a nested `reasoning.effort`, or
|
|
276
|
+
* `reasoning.enabled: true` (OpenRouter / Vercel AI Gateway).
|
|
277
|
+
*/
|
|
278
|
+
export function isThinkingEnabledOnWire(params: unknown): boolean {
|
|
279
|
+
const p = params as {
|
|
280
|
+
reasoning_effort?: unknown;
|
|
281
|
+
reasoning?: { effort?: unknown; enabled?: unknown } | null;
|
|
282
|
+
};
|
|
283
|
+
const nested = p.reasoning;
|
|
284
|
+
if (nested && typeof nested === "object") {
|
|
285
|
+
if (nested.enabled === true) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
if (typeof nested.effort === "string" && nested.effort !== "none") {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return (
|
|
293
|
+
typeof p.reasoning_effort === "string" && p.reasoning_effort !== "none"
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* True when the request sent an explicit `tool_choice` and the provider
|
|
299
|
+
* rejected it because thinking/reasoning mode forbids that parameter.
|
|
300
|
+
* DeepSeek thinking mode 400s with `Thinking mode does not support this
|
|
301
|
+
* tool_choice` for any explicit value, including `"auto"` and `"none"`.
|
|
302
|
+
* One retry without `tool_choice` lets the same provider succeed instead of
|
|
303
|
+
* failing over to a different backend.
|
|
304
|
+
*/
|
|
305
|
+
function isThinkingModeToolChoiceRejection(
|
|
306
|
+
error: unknown,
|
|
307
|
+
params: unknown,
|
|
308
|
+
): boolean {
|
|
309
|
+
const p = params as { tool_choice?: unknown };
|
|
310
|
+
if (p.tool_choice === undefined) {
|
|
311
|
+
return false;
|
|
312
|
+
}
|
|
313
|
+
if (!isClientErrorStatus(error)) {
|
|
234
314
|
return false;
|
|
235
315
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
// otherwise the wrapped reasoning rejection is missed and this one-shot
|
|
240
|
-
// fallback never fires.
|
|
241
|
-
const haystack =
|
|
242
|
-
error instanceof OpenAI.APIError
|
|
243
|
-
? normalizedErrorText(normalizeOpenAIAPIError(error))
|
|
244
|
-
: error instanceof Error
|
|
245
|
-
? error.message
|
|
246
|
-
: String(error);
|
|
247
|
-
return /reasoning/i.test(haystack);
|
|
316
|
+
return /does not support this tool_choice/i.test(
|
|
317
|
+
openaiCompatErrorHaystack(error),
|
|
318
|
+
);
|
|
248
319
|
}
|
|
249
320
|
|
|
250
321
|
/**
|
|
@@ -293,13 +364,11 @@ const OPENAI_SUPPORTED_IMAGE_TYPES = new Set([
|
|
|
293
364
|
"image/webp",
|
|
294
365
|
]);
|
|
295
366
|
|
|
367
|
+
// Think-tag scanning primitives are shared with the TTS reasoning filter
|
|
368
|
+
// (util/think-tag-stream.ts) so the two stream parsers cannot drift. This
|
|
369
|
+
// provider keeps its exact historical behavior: case-sensitive, <think> only.
|
|
296
370
|
function partialTagSuffix(text: string, tag: string): number {
|
|
297
|
-
|
|
298
|
-
if (text.endsWith(tag.substring(0, len))) {
|
|
299
|
-
return len;
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
return 0;
|
|
371
|
+
return sharedPartialTagSuffix(text, [tag], false);
|
|
303
372
|
}
|
|
304
373
|
|
|
305
374
|
/**
|
|
@@ -325,6 +394,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
325
394
|
| undefined;
|
|
326
395
|
private backfillEmptyAssistantContent: boolean;
|
|
327
396
|
private coerceObjectArgsToJsonString: boolean;
|
|
397
|
+
private omitToolChoiceWhenReasoning: boolean;
|
|
328
398
|
|
|
329
399
|
constructor(
|
|
330
400
|
apiKey: string,
|
|
@@ -354,6 +424,8 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
354
424
|
options.backfillEmptyAssistantContent ?? false;
|
|
355
425
|
this.coerceObjectArgsToJsonString =
|
|
356
426
|
options.coerceObjectArgsToJsonString ?? false;
|
|
427
|
+
this.omitToolChoiceWhenReasoning =
|
|
428
|
+
options.omitToolChoiceWhenReasoning ?? false;
|
|
357
429
|
}
|
|
358
430
|
|
|
359
431
|
get defaultModel(): string {
|
|
@@ -460,11 +532,24 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
460
532
|
|
|
461
533
|
// Honor a caller-supplied tool_choice (e.g. `{ type: "none" }` to force
|
|
462
534
|
// a text-only answer, or `{ type: "tool", name }` for a forced call).
|
|
463
|
-
// Only meaningful when tools are present
|
|
535
|
+
// Only meaningful when tools are present: OpenAI rejects a named or
|
|
464
536
|
// "required" choice with no tools.
|
|
537
|
+
//
|
|
538
|
+
// Strict reasoning upstreams (DeepSeek thinking mode) reject any
|
|
539
|
+
// explicit tool_choice, including `"auto"` (the API default when tools
|
|
540
|
+
// are present) and `"none"`. Omit `"auto"` whenever thinking is on the
|
|
541
|
+
// wire. Catalog providers that honor `none` / forced choices still
|
|
542
|
+
// receive them; the generic openai-compatible adapter drops every
|
|
543
|
+
// explicit value in thinking mode via `omitToolChoiceWhenReasoning`.
|
|
465
544
|
const toolChoice = mapNeutralToolChoice(configObj?.tool_choice);
|
|
466
545
|
if (toolChoice !== undefined) {
|
|
467
|
-
|
|
546
|
+
const thinkingOn = isThinkingEnabledOnWire(params);
|
|
547
|
+
const skipAutoDefault = thinkingOn && toolChoice === "auto";
|
|
548
|
+
const skipAllChoices =
|
|
549
|
+
thinkingOn && this.omitToolChoiceWhenReasoning;
|
|
550
|
+
if (!skipAutoDefault && !skipAllChoices) {
|
|
551
|
+
params.tool_choice = toolChoice;
|
|
552
|
+
}
|
|
468
553
|
}
|
|
469
554
|
}
|
|
470
555
|
|
|
@@ -564,20 +649,32 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
564
649
|
try {
|
|
565
650
|
stream = await createStream();
|
|
566
651
|
} catch (error) {
|
|
567
|
-
if (
|
|
652
|
+
if (isReasoningOptOutRejection(error, params)) {
|
|
653
|
+
log.warn(
|
|
654
|
+
{
|
|
655
|
+
provider: this.name,
|
|
656
|
+
model: modelOverride ?? this.model,
|
|
657
|
+
error: error instanceof Error ? error.message : String(error),
|
|
658
|
+
},
|
|
659
|
+
"Model rejected the explicit reasoning opt-out; retrying without reasoning params",
|
|
660
|
+
);
|
|
661
|
+
delete params.reasoning_effort;
|
|
662
|
+
delete (params as unknown as Record<string, unknown>).reasoning;
|
|
663
|
+
stream = await createStream();
|
|
664
|
+
} else if (isThinkingModeToolChoiceRejection(error, params)) {
|
|
665
|
+
log.warn(
|
|
666
|
+
{
|
|
667
|
+
provider: this.name,
|
|
668
|
+
model: modelOverride ?? this.model,
|
|
669
|
+
error: error instanceof Error ? error.message : String(error),
|
|
670
|
+
},
|
|
671
|
+
"Upstream rejected tool_choice in thinking mode; retrying without tool_choice",
|
|
672
|
+
);
|
|
673
|
+
delete params.tool_choice;
|
|
674
|
+
stream = await createStream();
|
|
675
|
+
} else {
|
|
568
676
|
throw error;
|
|
569
677
|
}
|
|
570
|
-
log.warn(
|
|
571
|
-
{
|
|
572
|
-
provider: this.name,
|
|
573
|
-
model: modelOverride ?? this.model,
|
|
574
|
-
error: error instanceof Error ? error.message : String(error),
|
|
575
|
-
},
|
|
576
|
-
"Model rejected the explicit reasoning opt-out; retrying without reasoning params",
|
|
577
|
-
);
|
|
578
|
-
delete params.reasoning_effort;
|
|
579
|
-
delete (params as unknown as Record<string, unknown>).reasoning;
|
|
580
|
-
stream = await createStream();
|
|
581
678
|
}
|
|
582
679
|
|
|
583
680
|
for await (const chunk of stream) {
|
|
@@ -13,9 +13,10 @@ export const CODEX_SUBSCRIPTION_MODEL_IDS: ReadonlySet<string> = new Set([
|
|
|
13
13
|
"gpt-5.6-terra",
|
|
14
14
|
"gpt-5.6-luna",
|
|
15
15
|
"gpt-5.5",
|
|
16
|
+
// OpenAI retires these two from ChatGPT sign-in on 2026-08-31; API-key
|
|
17
|
+
// auth is unaffected.
|
|
16
18
|
"gpt-5.4",
|
|
17
19
|
"gpt-5.4-mini",
|
|
18
|
-
"gpt-5.3-codex",
|
|
19
20
|
]);
|
|
20
21
|
|
|
21
22
|
/** True when `model` is allowlisted for Codex subscription routing. */
|