@vellumai/assistant 0.11.3 → 0.11.4-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +11 -6
- package/docs/architecture/memory.md +11 -0
- package/docs/architecture/turn-actor.md +70 -0
- package/docs/flux-turn-detection-spike.md +243 -0
- package/docs/stt-provider-onboarding.md +3 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +34 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +2 -0
- package/node_modules/@vellumai/service-contracts/src/channels.ts +11 -0
- package/openapi.yaml +139 -37
- package/package.json +1 -1
- package/scripts/voice-ttft-spike.ts +3 -3
- package/src/__tests__/app-compiler.test.ts +38 -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__/config-loader-backfill.test.ts +3 -3
- package/src/__tests__/config-schema.test.ts +25 -10
- 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 +28 -20
- package/src/__tests__/conversation-attention-store.test.ts +63 -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-notifiers-provenance.test.ts +59 -0
- package/src/__tests__/conversation-queue.test.ts +177 -4
- package/src/__tests__/conversation-runtime-assembly.test.ts +134 -102
- package/src/__tests__/conversation-runtime-workspace.test.ts +14 -10
- package/src/__tests__/credential-prompt-route.test.ts +7 -10
- package/src/__tests__/custom-profile-ensure.test.ts +5 -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__/edit-propagation.test.ts +0 -7
- package/src/__tests__/helpers/mock-actor-context.ts +49 -0
- package/src/__tests__/helpers/mock-conversation.ts +13 -1
- package/src/__tests__/injector-chain.test.ts +63 -41
- package/src/__tests__/injector-disk-pressure.test.ts +11 -23
- package/src/__tests__/llm-context-resolution.test.ts +73 -1
- package/src/__tests__/llm-schema.test.ts +5 -2
- package/src/__tests__/mcp-list-plugin-servers.test.ts +250 -0
- package/src/__tests__/memory-retrieval-hook.test.ts +6 -5
- package/src/__tests__/messages-read-boundary-guard.test.ts +134 -0
- package/src/__tests__/mtime-cache.test.ts +1 -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__/plugin-import-boundary-guard.test.ts +5 -0
- 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__/run-conversation-turn-persistence.test.ts +434 -105
- package/src/__tests__/scoped-approval-grants.test.ts +11 -6
- package/src/__tests__/secret-ingress-channel.test.ts +0 -1
- package/src/__tests__/skills.test.ts +32 -0
- package/src/__tests__/slack-edit-ordering-characterization.test.ts +0 -1
- package/src/__tests__/subagent-call-site-routing.test.ts +31 -19
- package/src/__tests__/subagent-spawn-and-await.test.ts +14 -10
- package/src/__tests__/turn-events-store.test.ts +43 -0
- package/src/__tests__/ui-shape-teaching.test.ts +33 -0
- package/src/__tests__/ui-voice-picker-surface.test.ts +128 -0
- package/src/__tests__/user-plugin-loader.test.ts +1 -1
- package/src/__tests__/visible-app-context.test.ts +16 -9
- package/src/__tests__/voice-config-update.test.ts +40 -0
- 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 +68 -2
- package/src/api/events/acp-auth-required.ts +55 -0
- package/src/api/index.ts +7 -0
- package/src/api/surfaces.ts +7 -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 +173 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +94 -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 +71 -16
- package/src/calls/voice-triage-escalate.ts +104 -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/commands/credentials.help.ts +2 -2
- package/src/cli/commands/inference-providers.ts +1 -1
- package/src/cli/commands/mcp.help.ts +13 -4
- package/src/cli/commands/mcp.ts +9 -0
- package/src/cli/commands/memory/__tests__/memory-v3.test.ts +128 -5
- package/src/cli/commands/memory/index.help.ts +43 -1
- package/src/cli/commands/memory/memory-v3.ts +64 -0
- package/src/cli/commands/stt.help.ts +27 -2
- package/src/cli/lib/__tests__/upgrade-plugin.test.ts +39 -0
- package/src/cli/lib/bundled-marketplace.json +13 -0
- package/src/cli/lib/upgrade-plugin.ts +42 -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/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/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/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 +4 -5
- package/src/config/default-profile-catalog.ts +83 -12
- package/src/config/default-profile-names.ts +4 -1
- package/src/config/default-provider-resolution.ts +4 -0
- 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/context/outbound-sanitize.ts +96 -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 +17 -6
- package/src/daemon/conversation-messaging.ts +5 -1
- package/src/daemon/conversation-notifiers.ts +9 -1
- package/src/daemon/conversation-process.ts +36 -6
- package/src/daemon/conversation-runtime-assembly.ts +3 -4
- package/src/daemon/conversation-surfaces.ts +27 -5
- package/src/daemon/conversation-tool-setup.ts +1 -2
- package/src/daemon/conversation.ts +48 -0
- package/src/daemon/interactive-turn-sender.ts +59 -0
- package/src/daemon/mcp-reload-service.ts +36 -6
- package/src/daemon/process-message.ts +24 -24
- package/src/daemon/providers-setup.ts +6 -3
- package/src/daemon/trust-context-types.ts +29 -0
- package/src/daemon/wake-conversation-ops.ts +3 -2
- package/src/documents/document-store.ts +138 -5
- package/src/hooks/hook-loader.ts +3 -3
- package/src/hooks/registry.ts +50 -6
- package/src/inbound/__tests__/oauth-callback-url.test.ts +83 -0
- package/src/inbound/oauth-callback-url.ts +61 -0
- 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 +932 -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-metrics.ts +57 -32
- package/src/live-voice/live-voice-photo.ts +1 -2
- package/src/live-voice/live-voice-session.ts +535 -314
- package/src/live-voice/progress-narration.ts +277 -0
- package/src/live-voice/protocol.ts +21 -1
- 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/plugin-auto-update.ts +128 -24
- package/src/notifications/signal.ts +1 -0
- package/src/permissions/confirmation-guardian-request.test.ts +15 -11
- package/src/permissions/confirmation-guardian-request.ts +2 -2
- 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 +108 -41
- package/src/persistence/delivery-crud.ts +38 -29
- 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/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/steps.ts +9 -0
- package/src/plugin-api/__tests__/oauth-callback-url-export.test.ts +29 -0
- package/src/plugin-api/conversation-turn.ts +168 -5
- package/src/plugin-api/index.ts +21 -5
- package/src/plugin-api/vision-support.test.ts +1 -1
- package/src/plugins/__tests__/mcp-servers.test.ts +371 -0
- package/src/plugins/defaults/memory/AGENTS.md +4 -0
- 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/buffer-format.ts +165 -0
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +6 -0
- 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 +8 -12
- package/src/plugins/defaults/memory/hooks/post-compact.ts +1 -4
- 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/src/__tests__/memory-v3-gate-stats.test.ts +281 -0
- package/src/plugins/defaults/memory/src/memory-v3-routes.ts +207 -0
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +33 -0
- package/src/plugins/defaults/memory/substrate/__tests__/static-context.test.ts +199 -2
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +25 -18
- package/src/plugins/defaults/memory/substrate/skill-content.ts +8 -1
- package/src/plugins/defaults/memory/substrate/static-context.ts +160 -4
- 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/prune.ts +2 -0
- package/src/plugins/defaults/memory/v3/selection-log-store.ts +2 -0
- package/src/plugins/defaults/memory/worker.ts +6 -3
- package/src/plugins/external-plugin-loader.ts +47 -0
- package/src/plugins/mcp-servers.ts +361 -0
- package/src/plugins/mtime-cache.ts +23 -49
- package/src/plugins/worker-plugin-surface.ts +33 -0
- package/src/providers/__tests__/connection-model-compat.test.ts +1 -1
- 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 +5 -2
- package/src/providers/call-site-routing.ts +30 -3
- package/src/providers/connection-resolution.ts +194 -11
- 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/chat-completions-provider.ts +5 -6
- 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 +620 -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 +719 -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/worker.ts +17 -5
- package/src/runtime/access-request-helper.ts +9 -12
- package/src/runtime/agent-wake.ts +3 -3
- 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 +219 -1
- 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 +44 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +25 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +62 -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 +112 -1
- package/src/runtime/routes/conversation-query-routes.ts +40 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -7
- package/src/runtime/routes/default-provider-routes.ts +15 -0
- package/src/runtime/routes/inbound-message-handler.ts +17 -41
- 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 +1 -3
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/guardian-reply-intercept.ts +3 -4
- package/src/runtime/routes/inbound-stages/reaction-intercept.test.ts +0 -1
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +11 -20
- 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/plugins-routes.ts +21 -3
- package/src/runtime/routes/stt-routes.ts +31 -25
- package/src/runtime/routes/surface-conversation-resolver.ts +3 -0
- package/src/runtime/routes/user-route-dispatcher.ts +39 -14
- package/src/runtime/routes/user-route-import.ts +108 -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/manager.ts +1 -3
- package/src/subagent/types.ts +7 -6
- package/src/tools/acp/spawn.test.ts +97 -0
- package/src/tools/acp/spawn.ts +32 -0
- package/src/tools/document/document-tool.ts +12 -3
- package/src/tools/registry.ts +2 -1
- package/src/tools/ui-surface/surface-shape-docs.ts +11 -0
- 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/think-tag-stream.ts +95 -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/live-voice/__tests__/front-decision.test.ts +0 -645
- package/src/live-voice/front-decision.ts +0 -476
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
isModelInCatalog,
|
|
5
5
|
} from "../providers/model-catalog.js";
|
|
6
6
|
import { resolveModelIntent } from "../providers/model-intents.js";
|
|
7
|
+
import { isCodexSubscriptionModel } from "../providers/openai/codex-models.js";
|
|
7
8
|
import type { ModelIntent } from "../providers/types.js";
|
|
8
9
|
import { getManagedUpstream } from "../providers/vellum-model-routing.js";
|
|
9
10
|
import {
|
|
@@ -31,7 +32,8 @@ import {
|
|
|
31
32
|
* structured as an intent × provider matrix: each default profile is an
|
|
32
33
|
* intent, and each provider that can serve default profiles has a concrete
|
|
33
34
|
* implementation of that intent (model, token budget, effort, thinking).
|
|
34
|
-
* The `vellum` column is the platform-managed implementation
|
|
35
|
+
* The `vellum` column is the platform-managed implementation and the
|
|
36
|
+
* `chatgpt` column is the ChatGPT-subscription implementation; the other
|
|
35
37
|
* columns are the BYOK implementations resolved through `llm.defaultProvider`
|
|
36
38
|
* on off-platform installs.
|
|
37
39
|
*
|
|
@@ -96,7 +98,7 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
|
|
|
96
98
|
},
|
|
97
99
|
},
|
|
98
100
|
"cost-optimized": {
|
|
99
|
-
model: "accounts/fireworks/models/deepseek-v4-flash",
|
|
101
|
+
model: "accounts/fireworks/models/deepseek-v4-flash-0731",
|
|
100
102
|
provider: "vellum",
|
|
101
103
|
source: "managed",
|
|
102
104
|
label: "Cost",
|
|
@@ -147,6 +149,69 @@ const VELLUM_PROFILE_IMPLS: ProfileImpls = {
|
|
|
147
149
|
},
|
|
148
150
|
};
|
|
149
151
|
|
|
152
|
+
/**
|
|
153
|
+
* The `chatgpt` column: ChatGPT-subscription implementations, stamped
|
|
154
|
+
* `provider: "chatgpt"` so dispatch routes through the canonical
|
|
155
|
+
* `chatgpt-subscription` row via `resolveRoutingIdentity` with no pinned
|
|
156
|
+
* connection. Models are pinned (never intents): the intent tables are
|
|
157
|
+
* keyed by concrete dispatch providers, and the Codex endpoint serves only
|
|
158
|
+
* `CODEX_SUBSCRIPTION_MODEL_IDS`. Cost and Speed are identical
|
|
159
|
+
* implementations here: the subscription serves no tier cheaper or faster
|
|
160
|
+
* than luna, and both profiles advertise reasoning off.
|
|
161
|
+
*/
|
|
162
|
+
const CHATGPT_PROFILE_IMPLS: ProfileImpls = {
|
|
163
|
+
balanced: {
|
|
164
|
+
model: "gpt-5.6-luna",
|
|
165
|
+
provider: "chatgpt",
|
|
166
|
+
source: "managed",
|
|
167
|
+
label: "Balanced",
|
|
168
|
+
description: "Good balance of quality, cost, and speed",
|
|
169
|
+
// Matches the vellum column's Balanced (same model): the Codex path
|
|
170
|
+
// sends no max_output_tokens, so this only sizes internal budgeting.
|
|
171
|
+
maxTokens: 32000,
|
|
172
|
+
effort: "high",
|
|
173
|
+
thinking: { enabled: true, streamThinking: true },
|
|
174
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
175
|
+
},
|
|
176
|
+
"quality-optimized": {
|
|
177
|
+
model: "gpt-5.6-sol",
|
|
178
|
+
provider: "chatgpt",
|
|
179
|
+
source: "managed",
|
|
180
|
+
label: "Quality",
|
|
181
|
+
description: "Best results with the most capable model",
|
|
182
|
+
maxTokens: 32000,
|
|
183
|
+
effort: "high",
|
|
184
|
+
thinking: { enabled: true, streamThinking: true },
|
|
185
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
186
|
+
},
|
|
187
|
+
"cost-optimized": {
|
|
188
|
+
model: "gpt-5.6-luna",
|
|
189
|
+
provider: "chatgpt",
|
|
190
|
+
source: "managed",
|
|
191
|
+
label: "Cost",
|
|
192
|
+
description: "Cheapest responses, for high-volume work",
|
|
193
|
+
maxTokens: 8192,
|
|
194
|
+
effort: "none",
|
|
195
|
+
thinking: { enabled: false, streamThinking: false },
|
|
196
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
197
|
+
},
|
|
198
|
+
"latency-optimized": {
|
|
199
|
+
model: "gpt-5.6-luna",
|
|
200
|
+
provider: "chatgpt",
|
|
201
|
+
source: "managed",
|
|
202
|
+
label: "Speed",
|
|
203
|
+
description: "Fastest responses, with reasoning turned off",
|
|
204
|
+
maxTokens: 8192,
|
|
205
|
+
// Explicit reasoning opt-out, matching the other columns: this profile
|
|
206
|
+
// advertises reasoning as off, and OpenAI-compat APIs default reasoning
|
|
207
|
+
// to "medium" when the field is omitted, so the opt-out has to be stated
|
|
208
|
+
// rather than implied.
|
|
209
|
+
effort: "none",
|
|
210
|
+
thinking: { enabled: false, streamThinking: false },
|
|
211
|
+
contextWindow: { maxInputTokens: DEFAULT_CONTEXT_WINDOW_MAX_INPUT_TOKENS },
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
|
|
150
215
|
/**
|
|
151
216
|
* The BYOK implementation of each default profile intent, shared by every
|
|
152
217
|
* non-vellum provider. The concrete model resolves per provider from the
|
|
@@ -219,7 +284,9 @@ export const PROFILE_IMPLS: Record<
|
|
|
219
284
|
provider,
|
|
220
285
|
provider === "vellum"
|
|
221
286
|
? VELLUM_PROFILE_IMPLS[key]
|
|
222
|
-
:
|
|
287
|
+
: provider === "chatgpt"
|
|
288
|
+
? CHATGPT_PROFILE_IMPLS[key]
|
|
289
|
+
: { ...BYOK_PROFILE_IMPLS[key], provider },
|
|
223
290
|
]),
|
|
224
291
|
) as Record<DefaultProfileProvider, DefaultProfileTemplate>,
|
|
225
292
|
]),
|
|
@@ -302,9 +369,9 @@ export const OS_BETA_PROFILE_TEMPLATE: DefaultProfileTemplate = {
|
|
|
302
369
|
/**
|
|
303
370
|
* Profiles whose body is code-owned outright: no workspace overlay, and no
|
|
304
371
|
* user-owned shadow. The shadow rule below lets a user replace a default they
|
|
305
|
-
* can select, but `latency-optimized`
|
|
306
|
-
* `
|
|
307
|
-
*
|
|
372
|
+
* can select, but `latency-optimized` serves `voiceFrontDoor` and
|
|
373
|
+
* `voiceProgressNarration`, where a model outside the latency envelope is
|
|
374
|
+
* audible dead air rather than a slow reply. A same-named
|
|
308
375
|
* workspace entry stays on disk and stays listed; it just never governs what
|
|
309
376
|
* this name resolves to.
|
|
310
377
|
*/
|
|
@@ -372,21 +439,23 @@ for (const key of DEFAULT_PROFILE_KEYS) {
|
|
|
372
439
|
`PROFILE_IMPLS[${key}][${provider}] must set exactly one of \`intent\` or \`model\`.`,
|
|
373
440
|
);
|
|
374
441
|
}
|
|
375
|
-
if (impl.provider
|
|
442
|
+
if (ROUTING_IDENTITY_PROVIDERS.has(impl.provider) && impl.model == null) {
|
|
376
443
|
throw new Error(
|
|
377
|
-
`PROFILE_IMPLS[${key}][${provider}] must pin a \`model\`:
|
|
378
|
-
`
|
|
444
|
+
`PROFILE_IMPLS[${key}][${provider}] must pin a \`model\`: routing ` +
|
|
445
|
+
`identities have no intent table, and the model selects the route.`,
|
|
379
446
|
);
|
|
380
447
|
}
|
|
381
448
|
if (impl.model != null) {
|
|
382
449
|
const routable =
|
|
383
450
|
impl.provider === "vellum"
|
|
384
451
|
? getManagedUpstream(impl.model) !== null
|
|
385
|
-
:
|
|
452
|
+
: impl.provider === "chatgpt"
|
|
453
|
+
? isCodexSubscriptionModel(impl.model)
|
|
454
|
+
: isModelInCatalog(impl.provider, impl.model);
|
|
386
455
|
if (!routable) {
|
|
387
456
|
throw new Error(
|
|
388
457
|
`PROFILE_IMPLS[${key}][${provider}] references model "${impl.model}" ` +
|
|
389
|
-
`which is not ${impl.provider === "vellum" ? "served by any managed upstream" : `in PROVIDER_CATALOG for provider "${impl.provider}"`}. ` +
|
|
458
|
+
`which is not ${impl.provider === "vellum" ? "served by any managed upstream" : impl.provider === "chatgpt" ? "in CODEX_SUBSCRIPTION_MODEL_IDS" : `in PROVIDER_CATALOG for provider "${impl.provider}"`}. ` +
|
|
390
459
|
`Update model-catalog.ts or default-profile-catalog.ts.`,
|
|
391
460
|
);
|
|
392
461
|
}
|
|
@@ -517,7 +586,9 @@ function resolveAgainstBody(
|
|
|
517
586
|
* Non-obvious rules:
|
|
518
587
|
*
|
|
519
588
|
* - The `vellum` column stamps `provider: "vellum"` with no connection —
|
|
520
|
-
* dispatch derives the upstream from the model per-request.
|
|
589
|
+
* dispatch derives the upstream from the model per-request. The `chatgpt`
|
|
590
|
+
* column likewise stamps its routing identity with no connection;
|
|
591
|
+
* dispatch resolves the canonical subscription row per-request.
|
|
521
592
|
* - A default provider without a named matrix column materializes from the
|
|
522
593
|
* shared `BYOK_PROFILE_IMPLS` templates, with `resolveModelIntent`
|
|
523
594
|
* falling back to the provider's catalog `defaultModel`.
|
|
@@ -38,7 +38,9 @@ export const OS_BETA_PROFILE_KEY = "os-beta";
|
|
|
38
38
|
/**
|
|
39
39
|
* The named columns of the intent × provider matrix. `vellum` is the
|
|
40
40
|
* platform-managed column (routed through the single `vellum` connection to
|
|
41
|
-
* an underlying provider per profile)
|
|
41
|
+
* an underlying provider per profile) and `chatgpt` is the
|
|
42
|
+
* ChatGPT-subscription column (routed through the `chatgpt-subscription`
|
|
43
|
+
* connection to the Codex endpoint); the rest are BYOK columns whose
|
|
42
44
|
* models resolve per provider via `resolveModelIntent`. The full set of
|
|
43
45
|
* providers that can back `llm.defaultProvider` is wider, see
|
|
44
46
|
* `DEFAULT_PROVIDER_CHOICES` in `schemas/llm.ts`.
|
|
@@ -53,6 +55,7 @@ export const DEFAULT_PROFILE_PROVIDERS = [
|
|
|
53
55
|
"gemini",
|
|
54
56
|
"fireworks",
|
|
55
57
|
"openrouter",
|
|
58
|
+
"chatgpt",
|
|
56
59
|
"vellum",
|
|
57
60
|
] as const;
|
|
58
61
|
export type DefaultProfileProvider = (typeof DEFAULT_PROFILE_PROVIDERS)[number];
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* conveniences (`getDefaultProvider()` without an argument,
|
|
7
7
|
* `setDefaultProvider`) live in `default-provider.ts`.
|
|
8
8
|
*/
|
|
9
|
+
import { CHATGPT_SUBSCRIPTION_CONNECTION_NAME } from "../providers/inference/auth.js";
|
|
9
10
|
import { VELLUM_MANAGED_CONNECTION_NAME } from "../providers/vellum-model-routing.js";
|
|
10
11
|
import type { DefaultProviderConfig } from "./schemas/llm.js";
|
|
11
12
|
import type { AssistantConfig } from "./types.js";
|
|
@@ -29,5 +30,8 @@ export function resolveDefaultConnectionName(
|
|
|
29
30
|
if (dp.provider === "vellum") {
|
|
30
31
|
return VELLUM_MANAGED_CONNECTION_NAME;
|
|
31
32
|
}
|
|
33
|
+
if (dp.provider === "chatgpt") {
|
|
34
|
+
return CHATGPT_SUBSCRIPTION_CONNECTION_NAME;
|
|
35
|
+
}
|
|
32
36
|
return `${dp.provider}-personal`;
|
|
33
37
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveEntryProviderKind } from "../providers/connection-resolution.js";
|
|
1
2
|
import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js";
|
|
2
3
|
import {
|
|
3
4
|
getCatalogProviderForModel,
|
|
@@ -54,11 +55,18 @@ export function resolveEffectiveContextWindow({
|
|
|
54
55
|
forceOverrideProfile,
|
|
55
56
|
selectionSeed,
|
|
56
57
|
});
|
|
57
|
-
// Routing identities
|
|
58
|
-
// carries
|
|
58
|
+
// Routing identities dispatch built-in catalog models, so the model's
|
|
59
|
+
// catalog owner carries their limits. An entry-name provider resolves
|
|
60
|
+
// through its row's kind instead: a custom-endpoint kind has no catalog
|
|
61
|
+
// models, so its models keep the conservative default even when a model
|
|
62
|
+
// id collides with a built-in one (the custom endpoint's "gpt-5.5" is not
|
|
63
|
+
// OpenAI's). Labels with no row fall back to the model's catalog owner.
|
|
59
64
|
const catalogProviderId = ROUTING_IDENTITY_PROVIDERS.has(resolved.provider)
|
|
60
65
|
? getCatalogProviderForModel(resolved.model)
|
|
61
|
-
: resolved.provider
|
|
66
|
+
: PROVIDER_CATALOG.some((p) => p.id === resolved.provider)
|
|
67
|
+
? resolved.provider
|
|
68
|
+
: (resolveEntryProviderKind(resolved.provider, resolved.model) ??
|
|
69
|
+
getCatalogProviderForModel(resolved.model));
|
|
62
70
|
const catalogModel = PROVIDER_CATALOG.find(
|
|
63
71
|
(provider) => provider.id === catalogProviderId,
|
|
64
72
|
)?.models.find((model) => model.id === resolved.model);
|
|
@@ -76,6 +76,15 @@ import {
|
|
|
76
76
|
*/
|
|
77
77
|
export interface ResolveCallSiteOpts {
|
|
78
78
|
overrideProfile?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Whether a profile's provider value can actually dispatch: a known
|
|
81
|
+
* vendor, or a connection entry row. Selection is pure and DB-blind, so
|
|
82
|
+
* dispatch-side callers supply this (see `dispatchProviderResolvable` in
|
|
83
|
+
* `connection-resolution.ts`); a profile failing it is unusable and
|
|
84
|
+
* selection falls through to the next rung, keeping model and transport
|
|
85
|
+
* coherent. When absent, every provider is assumed resolvable.
|
|
86
|
+
*/
|
|
87
|
+
isResolvableProvider?: (provider: string) => boolean;
|
|
79
88
|
/**
|
|
80
89
|
* Float `overrideProfile` above the call-site layers for non-main-agent call
|
|
81
90
|
* sites. Retained for API compatibility; under single-winner selection the
|
|
@@ -112,7 +121,11 @@ export interface ResolveCallSiteOpts {
|
|
|
112
121
|
}) => void;
|
|
113
122
|
}
|
|
114
123
|
|
|
115
|
-
export type ResolutionFallbackReason =
|
|
124
|
+
export type ResolutionFallbackReason =
|
|
125
|
+
| "missing"
|
|
126
|
+
| "disabled"
|
|
127
|
+
| "incomplete"
|
|
128
|
+
| "unresolvable";
|
|
116
129
|
|
|
117
130
|
export interface ResolvedCallSiteConfig {
|
|
118
131
|
config: z.infer<typeof LLMConfigBase>;
|
|
@@ -313,6 +326,20 @@ function usableEntry(
|
|
|
313
326
|
report(name, "incomplete");
|
|
314
327
|
return undefined;
|
|
315
328
|
}
|
|
329
|
+
// A provider the caller cannot resolve (not a known vendor, not an entry
|
|
330
|
+
// row) makes the profile unusable, and selection falls through to the
|
|
331
|
+
// next rung the same way an incomplete profile does: the fallback keeps
|
|
332
|
+
// model and transport coherent, where a dispatch-time fallback would pair
|
|
333
|
+
// this profile's model with the default transport. Selection is DB-blind,
|
|
334
|
+
// so the predicate is supplied by dispatch-side callers; when absent,
|
|
335
|
+
// every provider is assumed resolvable.
|
|
336
|
+
if (
|
|
337
|
+
opts.isResolvableProvider != null &&
|
|
338
|
+
!opts.isResolvableProvider(entry.provider)
|
|
339
|
+
) {
|
|
340
|
+
report(name, "unresolvable");
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
316
343
|
return { name, entry };
|
|
317
344
|
}
|
|
318
345
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { getDb } from "../persistence/db-connection.js";
|
|
1
2
|
import { ROUTING_IDENTITY_PROVIDERS } from "../providers/inference/auth.js";
|
|
3
|
+
import { getConnection } from "../providers/inference/connections.js";
|
|
2
4
|
import {
|
|
3
5
|
getCatalogProviderForModel,
|
|
4
6
|
isModelInCatalog,
|
|
5
7
|
} from "../providers/model-catalog.js";
|
|
6
8
|
import {
|
|
9
|
+
getManagedUpstream,
|
|
7
10
|
MANAGED_ROUTABLE_PROVIDERS,
|
|
8
11
|
VELLUM_MANAGED_CONNECTION_NAME,
|
|
9
12
|
} from "../providers/vellum-model-routing.js";
|
|
@@ -97,35 +100,80 @@ export function completeCustomProfile(
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// would
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
103
|
+
// Completion never stamps a `provider_connection`; an inherited binding
|
|
104
|
+
// would only re-introduce the collapsed field on disk. The default's
|
|
105
|
+
// explicit binding is instead inherited IN the provider value, the
|
|
106
|
+
// entries-model representation: the vellum binding becomes the routing
|
|
107
|
+
// identity (only when it can serve the model, or the read-path schema
|
|
108
|
+
// would strip the profile), and a same-vendor binding becomes the entry
|
|
109
|
+
// name, so a completed profile keeps signing with the credential the
|
|
110
|
+
// workspace default names rather than whatever auto-resolution finds
|
|
111
|
+
// first.
|
|
109
112
|
if (
|
|
110
113
|
completed.provider !== undefined &&
|
|
111
|
-
ROUTING_IDENTITY_PROVIDERS.has(completed.provider)
|
|
114
|
+
!ROUTING_IDENTITY_PROVIDERS.has(completed.provider) &&
|
|
115
|
+
profile.provider_connection === undefined &&
|
|
116
|
+
dflt.provider_connection !== undefined
|
|
112
117
|
) {
|
|
113
|
-
|
|
118
|
+
if (dflt.provider_connection === VELLUM_MANAGED_CONNECTION_NAME) {
|
|
119
|
+
// Only managed-servable pairs inherit the managed binding; anything
|
|
120
|
+
// else inherits nothing and lets dispatch auto-resolve by vendor,
|
|
121
|
+
// matching the pre-entries inheritance contract.
|
|
122
|
+
if (
|
|
123
|
+
MANAGED_ROUTABLE_PROVIDERS.has(completed.provider) &&
|
|
124
|
+
completed.model !== undefined &&
|
|
125
|
+
getManagedUpstream(completed.model) !== null
|
|
126
|
+
) {
|
|
127
|
+
completed.provider = "vellum";
|
|
128
|
+
}
|
|
129
|
+
} else if (completed.provider === dflt.provider) {
|
|
130
|
+
// Folding to the entry name is only safe against a verified row; a
|
|
131
|
+
// dangling or kind-disagreeing binding stays in the legacy field,
|
|
132
|
+
// where the collapse migration's recovery can judge it.
|
|
133
|
+
if (bindingRowKind(dflt.provider_connection) === completed.provider) {
|
|
134
|
+
completed.provider = dflt.provider_connection;
|
|
135
|
+
} else {
|
|
136
|
+
completed.provider_connection = dflt.provider_connection;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
114
139
|
}
|
|
140
|
+
return structuredClone(completed);
|
|
141
|
+
}
|
|
115
142
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
143
|
+
/**
|
|
144
|
+
* `{...raw, ...completed}` recursively: completed (schema-known) values win,
|
|
145
|
+
* raw keys the schema stripped survive at every depth. Used by boot
|
|
146
|
+
* materialization and the config write path so both preserve unknown keys
|
|
147
|
+
* the same way after `safeParse`.
|
|
148
|
+
*/
|
|
149
|
+
export function mergePreservingUnknownKeys(
|
|
150
|
+
raw: Record<string, unknown>,
|
|
151
|
+
completed: Record<string, unknown>,
|
|
152
|
+
): Record<string, unknown> {
|
|
153
|
+
const out: Record<string, unknown> = { ...raw, ...completed };
|
|
154
|
+
for (const [key, value] of Object.entries(completed)) {
|
|
155
|
+
const rawValue = raw[key];
|
|
156
|
+
if (isRecord(value) && isRecord(rawValue)) {
|
|
157
|
+
out[key] = mergePreservingUnknownKeys(rawValue, value);
|
|
158
|
+
}
|
|
126
159
|
}
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
127
162
|
|
|
128
|
-
|
|
163
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
164
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The provider kind stored on a connection row, or null when the row is
|
|
169
|
+
* missing or the DB is unavailable (both mean "unverifiable" to callers).
|
|
170
|
+
*/
|
|
171
|
+
function bindingRowKind(name: string): string | null {
|
|
172
|
+
try {
|
|
173
|
+
return getConnection(getDb(), name)?.provider ?? null;
|
|
174
|
+
} catch {
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
129
177
|
}
|
|
130
178
|
|
|
131
179
|
type PlainObject = Record<string, unknown>;
|
|
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
LiveVoiceConfigSchema,
|
|
5
|
+
LiveVoiceFluxConfigSchema,
|
|
5
6
|
LiveVoiceFrontModelConfigSchema,
|
|
6
7
|
LiveVoiceVadConfigSchema,
|
|
7
8
|
VALID_LIVE_VOICE_MODES,
|
|
@@ -21,11 +22,18 @@ const FRONT_MODEL_DEFAULTS = {
|
|
|
21
22
|
endpointDecisionTimeoutMs: 1200,
|
|
22
23
|
endpointExtensionMs: 1500,
|
|
23
24
|
endpointMaxExtensions: 2,
|
|
24
|
-
ackFirstDeltaTimeoutMs: 2500,
|
|
25
|
-
ackGenerationTimeoutMs: 600,
|
|
26
25
|
progress: PROGRESS_DEFAULTS,
|
|
27
26
|
};
|
|
28
27
|
|
|
28
|
+
// `eagerEotThreshold` is deliberately absent: it has no default, and leaving it
|
|
29
|
+
// unset is what keeps Deepgram from emitting speculative turn events.
|
|
30
|
+
const FLUX_DEFAULTS = {
|
|
31
|
+
turnEnd: { enabled: false },
|
|
32
|
+
model: "flux-general-en",
|
|
33
|
+
eotThreshold: 0.7,
|
|
34
|
+
eotTimeoutMs: 5_000,
|
|
35
|
+
};
|
|
36
|
+
|
|
29
37
|
describe("LiveVoiceVadConfigSchema", () => {
|
|
30
38
|
test("empty object parses to defaults", () => {
|
|
31
39
|
const parsed = LiveVoiceVadConfigSchema.parse({});
|
|
@@ -124,8 +132,15 @@ describe("LiveVoiceFrontModelConfigSchema", () => {
|
|
|
124
132
|
expect(parsed.endpointMaxExtensions).toBe(0);
|
|
125
133
|
// Unspecified fields still get defaults
|
|
126
134
|
expect(parsed.endpointExtensionMs).toBe(1500);
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("strips retired generated-ack settings", () => {
|
|
138
|
+
const parsed = LiveVoiceFrontModelConfigSchema.parse({
|
|
139
|
+
ackFirstDeltaTimeoutMs: 2500,
|
|
140
|
+
ackGenerationTimeoutMs: 600,
|
|
141
|
+
});
|
|
142
|
+
expect(parsed).not.toHaveProperty("ackFirstDeltaTimeoutMs");
|
|
143
|
+
expect(parsed).not.toHaveProperty("ackGenerationTimeoutMs");
|
|
129
144
|
});
|
|
130
145
|
|
|
131
146
|
test("rejects non-positive endpointDecisionTimeoutMs", () => {
|
|
@@ -213,6 +228,87 @@ describe("LiveVoiceFrontModelConfigSchema", () => {
|
|
|
213
228
|
});
|
|
214
229
|
});
|
|
215
230
|
|
|
231
|
+
describe("LiveVoiceFluxConfigSchema", () => {
|
|
232
|
+
test("empty object parses to defaults, with turn-end off", () => {
|
|
233
|
+
expect(LiveVoiceFluxConfigSchema.parse({})).toEqual(FLUX_DEFAULTS);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test("an unset eagerEotThreshold is absent, not zero", () => {
|
|
237
|
+
expect("eagerEotThreshold" in LiveVoiceFluxConfigSchema.parse({})).toBe(
|
|
238
|
+
false,
|
|
239
|
+
);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test("accepts overrides", () => {
|
|
243
|
+
const parsed = LiveVoiceFluxConfigSchema.parse({
|
|
244
|
+
turnEnd: { enabled: true },
|
|
245
|
+
model: "flux-general-multi",
|
|
246
|
+
eotThreshold: 0.85,
|
|
247
|
+
eagerEotThreshold: 0.45,
|
|
248
|
+
eotTimeoutMs: 12_000,
|
|
249
|
+
});
|
|
250
|
+
expect(parsed.turnEnd.enabled).toBe(true);
|
|
251
|
+
expect(parsed.model).toBe("flux-general-multi");
|
|
252
|
+
expect(parsed.eotThreshold).toBe(0.85);
|
|
253
|
+
expect(parsed.eagerEotThreshold).toBe(0.45);
|
|
254
|
+
expect(parsed.eotTimeoutMs).toBe(12_000);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("partial overrides merge with defaults", () => {
|
|
258
|
+
const parsed = LiveVoiceFluxConfigSchema.parse({ eotThreshold: 0.6 });
|
|
259
|
+
expect(parsed.eotThreshold).toBe(0.6);
|
|
260
|
+
expect(parsed.turnEnd.enabled).toBe(false);
|
|
261
|
+
expect(parsed.model).toBe("flux-general-en");
|
|
262
|
+
expect(parsed.eotTimeoutMs).toBe(5_000);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("rejects an eotThreshold outside 0.5..0.9", () => {
|
|
266
|
+
const above = LiveVoiceFluxConfigSchema.safeParse({ eotThreshold: 0.95 });
|
|
267
|
+
expect(above.success).toBe(false);
|
|
268
|
+
expect(above.error?.issues.map((i) => i.message)).toEqual([
|
|
269
|
+
"liveVoice.flux.eotThreshold must be <= 0.9",
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
const below = LiveVoiceFluxConfigSchema.safeParse({ eotThreshold: 0.4 });
|
|
273
|
+
expect(below.success).toBe(false);
|
|
274
|
+
expect(below.error?.issues.map((i) => i.message)).toEqual([
|
|
275
|
+
"liveVoice.flux.eotThreshold must be >= 0.5",
|
|
276
|
+
]);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("rejects an eagerEotThreshold outside 0.3..0.9", () => {
|
|
280
|
+
expect(
|
|
281
|
+
LiveVoiceFluxConfigSchema.safeParse({ eagerEotThreshold: 0.2 }).success,
|
|
282
|
+
).toBe(false);
|
|
283
|
+
expect(
|
|
284
|
+
LiveVoiceFluxConfigSchema.safeParse({ eagerEotThreshold: 0.95 }).success,
|
|
285
|
+
).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test("rejects an eotTimeoutMs outside 500..60000, and non-integers", () => {
|
|
289
|
+
expect(
|
|
290
|
+
LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 499 }).success,
|
|
291
|
+
).toBe(false);
|
|
292
|
+
expect(
|
|
293
|
+
LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 60_001 }).success,
|
|
294
|
+
).toBe(false);
|
|
295
|
+
expect(
|
|
296
|
+
LiveVoiceFluxConfigSchema.safeParse({ eotTimeoutMs: 1_500.5 }).success,
|
|
297
|
+
).toBe(false);
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
test("rejects a non-boolean turnEnd.enabled", () => {
|
|
301
|
+
const result = LiveVoiceFluxConfigSchema.safeParse({
|
|
302
|
+
turnEnd: { enabled: "yes" },
|
|
303
|
+
});
|
|
304
|
+
expect(result.success).toBe(false);
|
|
305
|
+
const msgs = result.error?.issues.map((i) => i.message) ?? [];
|
|
306
|
+
expect(msgs.some((m) => m.includes("liveVoice.flux.turnEnd.enabled"))).toBe(
|
|
307
|
+
true,
|
|
308
|
+
);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
216
312
|
describe("LiveVoiceConfigSchema", () => {
|
|
217
313
|
test("empty object parses to defaults", () => {
|
|
218
314
|
const parsed = LiveVoiceConfigSchema.parse({});
|
|
@@ -228,6 +324,9 @@ describe("LiveVoiceConfigSchema", () => {
|
|
|
228
324
|
echoDrainSlackMs: 300,
|
|
229
325
|
},
|
|
230
326
|
frontModel: FRONT_MODEL_DEFAULTS,
|
|
327
|
+
// Off by default: Flux turn detection is opt-in, so the front-door hold
|
|
328
|
+
// verdict keeps committing turns until it is enabled.
|
|
329
|
+
flux: FLUX_DEFAULTS,
|
|
231
330
|
maxSessionDurationSeconds: 1800,
|
|
232
331
|
// Off by default: voice turns carry only their transcript, no audio
|
|
233
332
|
// artifacts on the conversation messages (JARVIS-1283).
|
|
@@ -255,6 +354,7 @@ describe("LiveVoiceConfigSchema", () => {
|
|
|
255
354
|
mode: "ptt",
|
|
256
355
|
vad: { silenceThresholdMs: 900 },
|
|
257
356
|
frontModel: { endpointDecisionTimeoutMs: 300 },
|
|
357
|
+
flux: { turnEnd: { enabled: true } },
|
|
258
358
|
maxSessionDurationSeconds: 600,
|
|
259
359
|
});
|
|
260
360
|
expect(parsed.mode).toBe("ptt");
|
|
@@ -265,6 +365,9 @@ describe("LiveVoiceConfigSchema", () => {
|
|
|
265
365
|
// Partial frontModel overrides merge with defaults
|
|
266
366
|
expect(parsed.frontModel.endpointDecisionTimeoutMs).toBe(300);
|
|
267
367
|
expect(parsed.frontModel.endpointExtensionMs).toBe(1500);
|
|
368
|
+
// Partial flux overrides merge with defaults
|
|
369
|
+
expect(parsed.flux.turnEnd.enabled).toBe(true);
|
|
370
|
+
expect(parsed.flux.eotThreshold).toBe(0.7);
|
|
268
371
|
expect(parsed.maxSessionDurationSeconds).toBe(600);
|
|
269
372
|
});
|
|
270
373
|
|
|
@@ -289,11 +289,11 @@ const CATALOG_RECORD: CatalogRecord = {
|
|
|
289
289
|
"Captions images via a vision-capable profile for text-only model fallback.",
|
|
290
290
|
domain: "skills",
|
|
291
291
|
},
|
|
292
|
-
|
|
293
|
-
id: "
|
|
294
|
-
displayName: "Voice
|
|
292
|
+
voiceProgressNarration: {
|
|
293
|
+
id: "voiceProgressNarration",
|
|
294
|
+
displayName: "Voice Progress Narration",
|
|
295
295
|
description:
|
|
296
|
-
"
|
|
296
|
+
"Phrases short spoken progress updates during long-running live voice turns.",
|
|
297
297
|
domain: "agentLoop",
|
|
298
298
|
},
|
|
299
299
|
voiceFrontDoor: {
|
|
@@ -207,34 +207,64 @@ export const LiveVoiceFrontModelConfigSchema = z
|
|
|
207
207
|
)
|
|
208
208
|
.default(2)
|
|
209
209
|
.describe("Cap on consecutive 'hold' extensions per utterance"),
|
|
210
|
-
ackFirstDeltaTimeoutMs: z
|
|
211
|
-
.number({
|
|
212
|
-
error: "liveVoice.frontModel.ackFirstDeltaTimeoutMs must be a number",
|
|
213
|
-
})
|
|
214
|
-
.int("liveVoice.frontModel.ackFirstDeltaTimeoutMs must be an integer")
|
|
215
|
-
.positive(
|
|
216
|
-
"liveVoice.frontModel.ackFirstDeltaTimeoutMs must be a positive integer",
|
|
217
|
-
)
|
|
218
|
-
.default(2500)
|
|
219
|
-
.describe(
|
|
220
|
-
"Keyword-delay budget (ms): a spoken ack fires if no first assistant delta has arrived by then",
|
|
221
|
-
),
|
|
222
|
-
ackGenerationTimeoutMs: z
|
|
223
|
-
.number({
|
|
224
|
-
error: "liveVoice.frontModel.ackGenerationTimeoutMs must be a number",
|
|
225
|
-
})
|
|
226
|
-
.int("liveVoice.frontModel.ackGenerationTimeoutMs must be an integer")
|
|
227
|
-
.positive(
|
|
228
|
-
"liveVoice.frontModel.ackGenerationTimeoutMs must be a positive integer",
|
|
229
|
-
)
|
|
230
|
-
.default(600)
|
|
231
|
-
.describe("Budget (ms) for LLM-generated ack text"),
|
|
232
210
|
progress: LiveVoiceProgressConfigSchema.default(
|
|
233
211
|
LiveVoiceProgressConfigSchema.parse({}),
|
|
234
212
|
),
|
|
235
213
|
})
|
|
236
214
|
.describe(
|
|
237
|
-
"
|
|
215
|
+
"Voice front-door endpointing and long-turn progress narration tuning",
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const LiveVoiceFluxTurnEndConfigSchema = z
|
|
219
|
+
.object({
|
|
220
|
+
enabled: z
|
|
221
|
+
.boolean({ error: "liveVoice.flux.turnEnd.enabled must be a boolean" })
|
|
222
|
+
.default(false)
|
|
223
|
+
.describe(
|
|
224
|
+
"Commit the live-voice turn on Flux's EndOfTurn instead of the front-door [0] hold verdict. Requires services.stt.provider to be deepgram-flux; ignored otherwise.",
|
|
225
|
+
),
|
|
226
|
+
})
|
|
227
|
+
.describe(
|
|
228
|
+
"Which signal commits a live voice turn when Deepgram Flux is the STT provider",
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
export const LiveVoiceFluxConfigSchema = z
|
|
232
|
+
.object({
|
|
233
|
+
turnEnd: LiveVoiceFluxTurnEndConfigSchema.default(
|
|
234
|
+
LiveVoiceFluxTurnEndConfigSchema.parse({}),
|
|
235
|
+
),
|
|
236
|
+
model: z
|
|
237
|
+
.string({ error: "liveVoice.flux.model must be a string" })
|
|
238
|
+
.default("flux-general-en")
|
|
239
|
+
.describe("Deepgram Flux model requested when opening the STT stream"),
|
|
240
|
+
eotThreshold: z
|
|
241
|
+
.number({ error: "liveVoice.flux.eotThreshold must be a number" })
|
|
242
|
+
.min(0.5, "liveVoice.flux.eotThreshold must be >= 0.5")
|
|
243
|
+
.max(0.9, "liveVoice.flux.eotThreshold must be <= 0.9")
|
|
244
|
+
.default(0.7)
|
|
245
|
+
.describe(
|
|
246
|
+
"End-of-turn confidence Flux must reach before it emits EndOfTurn. Lower values commit sooner and cut speakers off more often; higher values wait longer and add end-of-turn latency.",
|
|
247
|
+
),
|
|
248
|
+
eagerEotThreshold: z
|
|
249
|
+
.number({ error: "liveVoice.flux.eagerEotThreshold must be a number" })
|
|
250
|
+
.min(0.3, "liveVoice.flux.eagerEotThreshold must be >= 0.3")
|
|
251
|
+
.max(0.9, "liveVoice.flux.eagerEotThreshold must be <= 0.9")
|
|
252
|
+
.optional()
|
|
253
|
+
.describe(
|
|
254
|
+
"Confidence at which Flux starts speculating that the turn has ended. Leaving it unset disables Deepgram's EagerEndOfTurn / TurnResumed events; enabling it raises LLM calls 50-70% because speculative turns that resume are thrown away.",
|
|
255
|
+
),
|
|
256
|
+
eotTimeoutMs: z
|
|
257
|
+
.number({ error: "liveVoice.flux.eotTimeoutMs must be a number" })
|
|
258
|
+
.int("liveVoice.flux.eotTimeoutMs must be an integer")
|
|
259
|
+
.min(500, "liveVoice.flux.eotTimeoutMs must be >= 500")
|
|
260
|
+
.max(60_000, "liveVoice.flux.eotTimeoutMs must be <= 60000")
|
|
261
|
+
.default(5_000)
|
|
262
|
+
.describe(
|
|
263
|
+
"Silence (ms) after which Flux force-ends the turn even though its end-of-turn confidence never reached eotThreshold",
|
|
264
|
+
),
|
|
265
|
+
})
|
|
266
|
+
.describe(
|
|
267
|
+
"Deepgram Flux turn-detection tuning for live voice sessions (model-integrated end-of-turn)",
|
|
238
268
|
);
|
|
239
269
|
|
|
240
270
|
export const LiveVoiceConfigSchema = z
|
|
@@ -251,6 +281,9 @@ export const LiveVoiceConfigSchema = z
|
|
|
251
281
|
frontModel: LiveVoiceFrontModelConfigSchema.default(
|
|
252
282
|
LiveVoiceFrontModelConfigSchema.parse({}),
|
|
253
283
|
),
|
|
284
|
+
flux: LiveVoiceFluxConfigSchema.default(
|
|
285
|
+
LiveVoiceFluxConfigSchema.parse({}),
|
|
286
|
+
),
|
|
254
287
|
maxSessionDurationSeconds: z
|
|
255
288
|
.number({
|
|
256
289
|
error: "liveVoice.maxSessionDurationSeconds must be a number",
|
|
@@ -280,3 +313,4 @@ export type LiveVoiceFrontModelConfig = z.infer<
|
|
|
280
313
|
export type LiveVoiceProgressConfig = z.infer<
|
|
281
314
|
typeof LiveVoiceProgressConfigSchema
|
|
282
315
|
>;
|
|
316
|
+
export type LiveVoiceFluxConfig = z.infer<typeof LiveVoiceFluxConfigSchema>;
|