@vellumai/assistant 0.11.6 → 0.11.7-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 +31 -19
- package/Dockerfile +1 -0
- package/docs/architecture/security.md +3 -3
- package/docs/flux-turn-detection-spike.md +8 -7
- package/docs/runbook-trusted-contacts.md +14 -0
- package/docs/stt-provider-onboarding.md +8 -4
- package/knip.json +1 -0
- package/node_modules/@vellumai/avatar-manifest/package.json +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/manifest.test.ts +173 -0
- package/node_modules/@vellumai/avatar-manifest/src/__tests__/read.test.ts +114 -0
- package/node_modules/@vellumai/avatar-manifest/src/index.ts +29 -0
- package/node_modules/@vellumai/avatar-manifest/src/layout.ts +18 -0
- package/node_modules/@vellumai/avatar-manifest/src/manifest.ts +156 -0
- package/node_modules/@vellumai/avatar-manifest/src/read.ts +109 -0
- package/node_modules/@vellumai/avatar-manifest/tsconfig.json +20 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/ces-client/src/http-credentials.ts +161 -0
- package/node_modules/@vellumai/ces-client/src/index.ts +1 -0
- package/node_modules/@vellumai/environments/package.json +2 -1
- package/node_modules/@vellumai/environments/src/shell.test.ts +56 -0
- package/node_modules/@vellumai/environments/src/shell.ts +65 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/contact-read-contracts.test.ts +32 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/gateway-client.test.ts +1 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/inbound-event-kind.test.ts +82 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/plugin-admission-denied-contract.test.ts +56 -0
- package/node_modules/@vellumai/gateway-client/src/__tests__/trust-verdict-contract.test.ts +18 -0
- package/node_modules/@vellumai/gateway-client/src/admission-policy-contract.ts +5 -11
- package/node_modules/@vellumai/gateway-client/src/channel-permission-contract.ts +3 -1
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +9 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-contract.ts +4 -0
- package/node_modules/@vellumai/gateway-client/src/inbound-event-kind.ts +75 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +17 -0
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +8 -0
- package/node_modules/@vellumai/gateway-client/src/plugin-admission-denied-contract.ts +47 -0
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +11 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/ingress.test.ts +37 -0
- package/node_modules/@vellumai/service-contracts/src/__tests__/remote-web-pairing.test.ts +389 -0
- package/node_modules/@vellumai/service-contracts/src/ingress.ts +22 -0
- package/node_modules/@vellumai/service-contracts/src/remote-web-pairing.ts +508 -18
- package/node_modules/@vellumai/service-contracts/src/rpc.ts +174 -0
- package/openapi.yaml +419 -55
- package/package.json +4 -1
- package/scripts/sync-llm-catalog.ts +6 -0
- package/src/__tests__/approval-card-edit-inbound-handoff.test.ts +199 -0
- package/src/__tests__/approval-card-edit.test.ts +153 -0
- package/src/__tests__/attachments-store-heic-normalize.test.ts +1 -1
- package/src/__tests__/available-skills.test.ts +54 -0
- package/src/__tests__/avatar-identity-sync.test.ts +5 -1
- package/src/__tests__/catalog-install-normalize.test.ts +9 -1
- package/src/__tests__/channel-policy.test.ts +1 -3
- package/src/__tests__/channel-readiness-discord.test.ts +9 -70
- package/src/__tests__/compactor-call-site-logging.test.ts +61 -0
- package/src/__tests__/config-loader-backfill.test.ts +69 -0
- package/src/__tests__/config-schema.test.ts +2 -3
- package/src/__tests__/conversation-agent-loop.test.ts +238 -0
- package/src/__tests__/conversation-attachments.test.ts +0 -1
- package/src/__tests__/conversation-error.test.ts +44 -0
- package/src/__tests__/conversation-evictor.test.ts +19 -0
- package/src/__tests__/conversation-fork-referential.test.ts +73 -63
- package/src/__tests__/conversation-fork-retrospective.test.ts +99 -74
- package/src/__tests__/conversation-runtime-assembly.test.ts +23 -0
- package/src/__tests__/conversation-slash.test.ts +9 -0
- package/src/__tests__/conversation-title-service.test.ts +97 -0
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +16 -0
- package/src/__tests__/credential-broker-server-use.test.ts +0 -218
- package/src/__tests__/credential-broker.test.ts +43 -420
- package/src/__tests__/credential-record-write-through.test.ts +78 -0
- package/src/__tests__/credential-routes.test.ts +17 -1
- package/src/__tests__/credential-security-invariants.test.ts +8 -9
- package/src/__tests__/default-profile-catalog-fallback.test.ts +289 -0
- package/src/__tests__/delete-propagation.test.ts +92 -2
- package/src/__tests__/discord-callback-round-trip.test.ts +93 -0
- package/src/__tests__/discord-channel-config.test.ts +237 -0
- package/src/__tests__/edit-propagation.test.ts +42 -2
- package/src/__tests__/evict-conversations-for-reload.test.ts +51 -1
- package/src/__tests__/fallback-breaker.test.ts +1335 -0
- package/src/__tests__/gateway-threshold-reader-mock.ts +6 -0
- package/src/__tests__/guardian-card-withdrawal.test.ts +30 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +0 -2
- package/src/__tests__/image-conversion.test.ts +38 -4
- package/src/__tests__/install-skill-routing.test.ts +25 -0
- package/src/__tests__/llm-catalog-parity.test.ts +4 -0
- package/src/__tests__/llm-resolver.test.ts +74 -0
- package/src/__tests__/llm-schema-fallback-profile.test.ts +359 -0
- package/src/__tests__/managed-fallback-dispatch.test.ts +430 -0
- package/src/__tests__/managed-profile-guard.test.ts +28 -0
- package/src/__tests__/managed-speech-defaults.test.ts +196 -1
- package/src/__tests__/mcp-cli.test.ts +23 -1
- package/src/__tests__/mcp-tool-annotations-risk.test.ts +43 -34
- package/src/__tests__/media-stream-stt-session.test.ts +2 -0
- package/src/__tests__/notification-decision-fallback.test.ts +106 -0
- package/src/__tests__/notification-discord-adapter.test.ts +162 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +95 -0
- package/src/__tests__/persist-unsendable-image-downscale.test.ts +6 -8
- package/src/__tests__/persist-unsendable-image.test.ts +3 -3
- package/src/__tests__/platform.test.ts +19 -1
- package/src/__tests__/plugin-import-boundary-guard.test.ts +5 -1
- package/src/__tests__/provider-catalog-visibility.test.ts +17 -0
- package/src/__tests__/provider-platform-proxy-integration.test.ts +8 -1
- package/src/__tests__/provider-usage-tracking.test.ts +56 -0
- package/src/__tests__/require-fresh-approval.test.ts +5 -2
- package/src/__tests__/retry-fallback-escalation.test.ts +1093 -0
- package/src/__tests__/schedule-retry.test.ts +183 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +6 -6
- package/src/__tests__/secret-routes-platform-proxy.test.ts +55 -0
- package/src/__tests__/skill-load-tool.test.ts +34 -0
- package/src/__tests__/skill-tool-factory.test.ts +6 -1
- package/src/__tests__/skill-tool-manifest.test.ts +20 -0
- package/src/__tests__/skills.test.ts +25 -0
- package/src/__tests__/subagent-manager-notify.test.ts +38 -0
- package/src/__tests__/terminal-tools.test.ts +2 -0
- package/src/__tests__/tool-approval-handler.test.ts +146 -2
- package/src/__tests__/tool-approval-seed-content-blocks.test.ts +108 -2
- package/src/__tests__/tool-audit.test.ts +111 -40
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +228 -18
- package/src/__tests__/ui-shape-teaching.test.ts +1 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +6 -2
- package/src/__tests__/voice-config-update.test.ts +43 -0
- package/src/__tests__/workspace-migration-147-rename-colliding-backup-profile-names.test.ts +480 -0
- package/src/__tests__/workspace-migration-148-strip-unsupported-fallback-profiles.test.ts +167 -0
- package/src/__tests__/workspace-migration-149-repoint-backup-profile-selections.test.ts +144 -0
- package/src/agent/loop.ts +31 -2
- package/src/approvals/guardian-card-withdrawal.ts +22 -0
- package/src/avatar/__tests__/ensure-raster.test.ts +291 -0
- package/src/avatar/avatar-manifest.ts +48 -100
- package/src/avatar/avatar-store.ts +7 -7
- package/src/avatar/ensure-raster.ts +146 -0
- package/src/avatar/resvg-lazy.test.ts +0 -1
- package/src/avatar/traits-png-sync.ts +9 -10
- package/src/background-wake/wake-intent-hooks.test.ts +0 -1
- package/src/backup/__tests__/paths.test.ts +6 -2
- package/src/backup/paths.ts +6 -4
- package/src/bundler/app-compiler.ts +2 -2
- package/src/bundler/compiler-tools.test.ts +36 -0
- package/src/bundler/compiler-tools.ts +54 -18
- package/src/calls/__tests__/telephony-synthesis-language.test.ts +81 -0
- package/src/calls/__tests__/voice-session-bridge.test.ts +57 -0
- package/src/calls/media-stream-stt-session.ts +2 -1
- package/src/calls/telephony-synthesis-language.ts +13 -5
- package/src/calls/voice-session-bridge.ts +19 -4
- package/src/channels/config.ts +23 -12
- package/src/cli/AGENTS.md +3 -1
- package/src/cli/commands/backup.help.ts +10 -13
- package/src/cli/commands/contacts.help.ts +16 -4
- package/src/cli/commands/contacts.ts +5 -1
- package/src/cli/commands/credentials.help.ts +1 -1
- package/src/cli/commands/inference-providers.ts +18 -53
- package/src/cli/commands/mcp.help.ts +6 -5
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/memory/memory-retrospective.ts +1 -0
- package/src/cli/commands/stt.help.ts +2 -2
- package/src/config/__tests__/backup-schema.test.ts +1 -1
- package/src/config/__tests__/default-profile-catalog.test.ts +83 -2
- package/src/config/__tests__/loader-sparse-array-cleanup.test.ts +1 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +12 -15
- package/src/config/bundled-skills/computer-use/SKILL.md +6 -6
- package/src/config/bundled-skills/computer-use/TOOLS.json +9 -6
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +3 -1
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +1 -1
- package/src/config/bundled-skills/phone-calls/SKILL.md +1 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +16 -15
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +2 -1
- package/src/config/bundled-skills/settings/TOOLS.json +11 -5
- package/src/config/bundled-skills/settings/tools/open-system-settings.test.ts +85 -0
- package/src/config/bundled-skills/settings/tools/open-system-settings.ts +33 -8
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +54 -3
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +1 -1
- package/src/config/default-profile-catalog.ts +195 -10
- package/src/config/default-profile-names.ts +41 -0
- package/src/config/feature-flag-registry.json +34 -2
- package/src/config/inference-profile-validation.ts +11 -2
- package/src/config/llm-resolver.ts +25 -0
- package/src/config/loader.ts +3 -0
- package/src/config/managed-speech-defaults.ts +160 -10
- package/src/config/schema.ts +2 -0
- package/src/config/schemas/__tests__/live-voice.test.ts +7 -6
- package/src/config/schemas/__tests__/stt.test.ts +94 -0
- package/src/config/schemas/backup.ts +1 -1
- package/src/config/schemas/channels.ts +32 -0
- package/src/config/schemas/live-voice.ts +13 -4
- package/src/config/schemas/llm.ts +263 -15
- package/src/config/schemas/mcp.ts +10 -2
- package/src/config/schemas/memory-retrospective.ts +1 -19
- package/src/config/schemas/services.ts +24 -0
- package/src/config/schemas/stt.ts +128 -5
- package/src/config/seed-inference-profiles.ts +32 -3
- package/src/config/skill-state.ts +5 -0
- package/src/config/skills.ts +21 -0
- package/src/contacts/notify-contacts-changed.ts +5 -2
- package/src/context/compactor.ts +74 -1
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +138 -1
- package/src/daemon/config-watcher.ts +2 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +27 -0
- package/src/daemon/conversation-agent-loop.ts +86 -10
- package/src/daemon/conversation-error.ts +25 -0
- package/src/daemon/conversation-evictor.ts +2 -4
- package/src/daemon/conversation-runtime-assembly.ts +19 -7
- package/src/daemon/conversation-skill-tools.ts +5 -1
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-store.ts +8 -1
- package/src/daemon/conversation-tool-setup.ts +37 -0
- package/src/daemon/conversation.ts +0 -4
- package/src/daemon/handlers/channel-config-result.ts +30 -0
- package/src/daemon/handlers/config-discord-channel.ts +301 -0
- package/src/daemon/handlers/config-ingress.ts +9 -0
- package/src/daemon/handlers/config-slack-channel.ts +13 -15
- package/src/daemon/handlers/config-telegram.ts +2 -5
- package/src/daemon/handlers/conversations.ts +0 -2
- package/src/daemon/handlers/skills.ts +25 -7
- package/src/daemon/lifecycle.ts +3 -1
- package/src/daemon/message-types/messages.ts +0 -2
- package/src/daemon/tool-setup-types.ts +5 -3
- package/src/daemon/trust-context-types.test.ts +33 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +0 -1
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +58 -4
- package/src/live-voice/__tests__/live-voice-managed-speech-fallback.test.ts +8 -3
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +73 -11
- package/src/live-voice/__tests__/live-voice-session-preflight.test.ts +148 -2
- package/src/live-voice/__tests__/live-voice-session-telemetry.test.ts +64 -0
- package/src/live-voice/__tests__/live-voice-stt.test.ts +3 -0
- package/src/live-voice/__tests__/live-voice-text-turn.test.ts +391 -0
- package/src/live-voice/__tests__/protocol.test.ts +224 -0
- package/src/live-voice/live-voice-credential-preflight.ts +5 -1
- package/src/live-voice/live-voice-session-manager.ts +39 -2
- package/src/live-voice/live-voice-session.ts +224 -9
- package/src/live-voice/protocol.ts +187 -1
- package/src/messaging/providers/discord/api.ts +2 -1
- package/src/messaging/providers/discord/withdraw.ts +62 -0
- package/src/messaging/read-provider-metadata.ts +32 -0
- package/src/monitoring/__tests__/file-descriptors.test.ts +32 -0
- package/src/monitoring/__tests__/process-memory.test.ts +34 -1
- package/src/monitoring/db-integrity-sample.ts +1 -0
- package/src/monitoring/file-descriptors.ts +39 -6
- package/src/monitoring/process-memory.ts +37 -5
- package/src/monitoring/resource-sampler.ts +25 -8
- package/src/notifications/__tests__/activity-failed-dedupe.test.ts +161 -0
- package/src/notifications/__tests__/background-failure-signal.test.ts +107 -0
- package/src/notifications/__tests__/connected-channels.test.ts +12 -0
- package/src/notifications/__tests__/copy-composer.test.ts +83 -0
- package/src/notifications/__tests__/destination-resolver.test.ts +38 -0
- package/src/notifications/activity-failed-dedupe.ts +95 -0
- package/src/notifications/adapters/discord.ts +132 -0
- package/src/notifications/adapters/shared.ts +16 -0
- package/src/notifications/adapters/slack.ts +6 -1
- package/src/notifications/adapters/telegram.ts +5 -8
- package/src/notifications/approval-card-data.ts +94 -1
- package/src/notifications/background-failure-signal.ts +116 -0
- package/src/notifications/broadcaster.ts +3 -9
- package/src/notifications/copy-composer.ts +41 -8
- package/src/notifications/decision-engine.ts +55 -0
- package/src/notifications/destination-resolver.ts +45 -5
- package/src/notifications/emit-signal.ts +19 -5
- package/src/notifications/guardian-question-mode.ts +57 -0
- package/src/notifications/types.ts +2 -8
- package/src/oauth/__tests__/seed-providers-managed.test.ts +18 -0
- package/src/oauth/connect-orchestrator.ts +16 -0
- package/src/oauth/seed-providers.ts +262 -0
- package/src/permissions/checker.test.ts +21 -0
- package/src/permissions/checker.ts +6 -0
- package/src/permissions/gateway-threshold-reader.test.ts +184 -0
- package/src/permissions/gateway-threshold-reader.ts +144 -10
- package/src/permissions/prompter.ts +0 -6
- package/src/permissions/types.ts +5 -4
- package/src/persistence/__tests__/bulk-write-gate.test.ts +0 -38
- package/src/persistence/bulk-write-gate.ts +9 -10
- package/src/persistence/conversation-crud.ts +88 -134
- package/src/persistence/conversation-title-service.ts +43 -29
- package/src/persistence/embeddings/embedding-local.ts +14 -85
- package/src/persistence/embeddings/embedding-runtime-manager.ts +1 -0
- package/src/persistence/lifecycle-events-store.test.ts +50 -0
- package/src/persistence/lifecycle-events-store.ts +75 -5
- package/src/platform/platform-patch-queue.test.ts +368 -0
- package/src/platform/platform-patch-queue.ts +190 -0
- package/src/platform/sync-avatar.test.ts +527 -0
- package/src/platform/sync-avatar.ts +209 -0
- package/src/platform/sync-identity.test.ts +75 -0
- package/src/platform/sync-identity.ts +17 -110
- package/src/plugin-api/model-profiles.test.ts +2 -2
- package/src/plugin-api/model-profiles.ts +7 -7
- package/src/plugin-api/transcription-session.ts +1 -1
- package/src/plugins/defaults/compaction/window-manager.ts +4 -0
- package/src/plugins/defaults/image-recovery/recover.ts +2 -2
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +133 -34
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-prompt.test.ts +21 -31
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-provider-path.test.ts +13 -9
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-wake-chain.test.ts +31 -4
- package/src/plugins/defaults/memory/memory-retrospective-constants.ts +0 -11
- package/src/plugins/defaults/memory/memory-retrospective-fork-boundary.ts +27 -13
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +183 -113
- package/src/plugins/defaults/memory/memory-retrospective-prompt.ts +13 -13
- package/src/plugins/defaults/memory/src/memory-v2-routes.ts +2 -2
- package/src/plugins/defaults/memory/substrate/__tests__/consolidation-job.test.ts +1 -1
- package/src/plugins/defaults/memory/substrate/consolidation-job.ts +4 -2
- package/src/plugins/defaults/memory/substrate/sweep-job.ts +9 -42
- package/src/plugins/defaults/memory/v2/rerank-local.ts +1 -0
- package/src/plugins/defaults/memory/worker.ts +7 -0
- package/src/providers/__tests__/provider-secret-catalog.test.ts +1 -0
- package/src/providers/__tests__/retry-callsite.test.ts +55 -0
- package/src/providers/__tests__/vellum-connection-routing.test.ts +21 -3
- package/src/providers/connection-resolution.ts +2 -17
- package/src/providers/fallback-breaker.ts +389 -0
- package/src/providers/inference/__tests__/adapter-factory-opencode.test.ts +92 -0
- package/src/providers/inference/__tests__/base-url-route-validation.test.ts +16 -0
- package/src/providers/inference/__tests__/base-url-security.test.ts +12 -0
- package/src/providers/inference/adapter-factory.ts +246 -3
- package/src/providers/inference/auth.ts +15 -14
- package/src/providers/inference/chatgpt-subscription-auth.ts +70 -0
- package/src/providers/inference/chatgpt-subscription-credentials.ts +84 -0
- package/src/providers/inference/resolve-auth.ts +1 -1
- package/src/providers/media-resolve.ts +2 -3
- package/src/providers/model-catalog.ts +100 -0
- package/src/providers/openai/__tests__/api-error-normalization.test.ts +22 -0
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +198 -0
- package/src/providers/openai/api-error-normalization.ts +13 -0
- package/src/providers/openai/chat-completions-provider.ts +84 -0
- package/src/providers/opencode/client.test.ts +150 -0
- package/src/providers/opencode/client.ts +61 -0
- package/src/providers/platform-proxy/constants.ts +5 -0
- package/src/providers/provider-resolvability.ts +20 -0
- package/src/providers/provider-secret-catalog.ts +3 -2
- package/src/providers/retry.ts +956 -68
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +42 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +136 -0
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +120 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +316 -12
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +83 -3
- package/src/providers/speech-to-text/deepgram-flux-realtime.ts +132 -4
- package/src/providers/speech-to-text/provider-catalog.ts +191 -3
- package/src/providers/speech-to-text/resolve.ts +132 -24
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +296 -0
- package/src/providers/types.ts +39 -0
- package/src/providers/usage-tracking.ts +12 -1
- package/src/providers/vellum/client.ts +29 -0
- package/src/providers/vellum-model-routing.test.ts +2 -0
- package/src/providers/vellum-model-routing.ts +3 -3
- package/src/runtime/AGENTS.md +1 -2
- package/src/runtime/__tests__/agent-wake.test.ts +105 -3
- package/src/runtime/__tests__/background-job-runner.test.ts +89 -3
- package/src/runtime/agent-wake.ts +44 -5
- package/src/runtime/background-job-runner.ts +68 -50
- package/src/runtime/channel-readiness-service.ts +0 -39
- package/src/runtime/http-server.ts +12 -4
- package/src/runtime/routes/__tests__/avatar-state-routes.test.ts +18 -18
- package/src/runtime/routes/__tests__/backup-routes.test.ts +9 -5
- package/src/runtime/routes/__tests__/chatgpt-subscription-auth-routes.test.ts +372 -0
- package/src/runtime/routes/__tests__/contact-routes.test.ts +7 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +96 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +37 -0
- package/src/runtime/routes/__tests__/ingress-status-routes.test.ts +2 -1
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +87 -0
- package/src/runtime/routes/acp-claude-auth-routes.ts +25 -54
- package/src/runtime/routes/approval-routes.ts +0 -12
- package/src/runtime/routes/avatar-routes.ts +17 -30
- package/src/runtime/routes/backup-routes.ts +2 -2
- package/src/runtime/routes/chatgpt-subscription-auth-routes.ts +296 -113
- package/src/runtime/routes/contact-prompt-routes.ts +65 -6
- package/src/runtime/routes/contact-routes.ts +20 -15
- package/src/runtime/routes/conversation-query-routes.ts +98 -10
- package/src/runtime/routes/conversation-routes.ts +2 -2
- package/src/runtime/routes/credential-routes.ts +2 -1
- package/src/runtime/routes/guardian-approval-interception.ts +12 -9
- package/src/runtime/routes/inbound-message-handler.ts +47 -48
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +18 -13
- package/src/runtime/routes/inbound-stages/admission-policy.ts +2 -2
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +20 -2
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +9 -8
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +1 -1
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/inference-profile-session-handler.ts +2 -2
- package/src/runtime/routes/inference-profiles-routes.ts +12 -7
- package/src/runtime/routes/inference-provider-connection-routes.ts +2 -2
- package/src/runtime/routes/inference-send-routes.ts +2 -2
- package/src/runtime/routes/ingress-status-routes.ts +2 -2
- package/src/runtime/routes/integrations/discord.ts +119 -0
- package/src/runtime/routes/llm-call-sites-routes.ts +2 -2
- package/src/runtime/routes/mcp-auth-routes.ts +12 -6
- package/src/runtime/routes/migration-routes.ts +3 -1
- package/src/runtime/routes/oauth-pending-flows.ts +136 -0
- package/src/runtime/routes/secret-routes.ts +7 -0
- package/src/runtime/routes/stt-routes.ts +38 -6
- package/src/runtime/routes/wake-conversation-routes.ts +8 -1
- package/src/runtime/routes/watch-routes.ts +9 -5
- package/src/runtime/sync/resource-sync-events.ts +2 -0
- package/src/runtime/trust-verdict-consumer.ts +3 -3
- package/src/schedule/__tests__/worker-feature-flags.test.ts +58 -0
- package/src/schedule/retry-policy.ts +8 -8
- package/src/schedule/run-script.ts +14 -23
- package/src/schedule/schedule-recovery.ts +0 -1
- package/src/schedule/scheduler.ts +89 -51
- package/src/schedule/worker.ts +57 -0
- package/src/security/__tests__/openai-device-auth.test.ts +355 -0
- package/src/security/ces-rpc-record-backend.ts +123 -0
- package/src/security/oauth2.ts +7 -0
- package/src/security/openai-device-auth.ts +364 -0
- package/src/security/secure-keys.ts +15 -0
- package/src/skills/available-skills.ts +10 -1
- package/src/skills/catalog-install.ts +19 -1
- package/src/skills/inline-command-runner.ts +6 -2
- package/src/skills/platform-compatibility.test.ts +49 -0
- package/src/skills/platform-compatibility.ts +65 -0
- package/src/skills/tool-manifest.ts +20 -0
- package/src/stt/__tests__/roles.test.ts +152 -0
- package/src/stt/daemon-batch-transcriber.ts +4 -1
- package/src/stt/roles.ts +180 -0
- package/src/stt/stt-stream-session.ts +3 -2
- package/src/stt/types.ts +33 -3
- package/src/subagent/manager.ts +12 -0
- package/src/telemetry/__tests__/live-voice-funnel.test.ts +34 -0
- package/src/telemetry/live-voice-funnel.ts +12 -2
- package/src/telemetry/telemetry-wire.generated.ts +6 -0
- package/src/telemetry/tool-audit.ts +83 -30
- package/src/telemetry/tool-executed-events-store.test.ts +0 -1
- package/src/telemetry/turn-outcome.ts +75 -4
- package/src/tools/client-os.ts +115 -0
- package/src/tools/computer-use/definitions.ts +11 -7
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -2
- package/src/tools/credentials/broker-types.ts +0 -71
- package/src/tools/credentials/broker.ts +6 -201
- package/src/tools/credentials/metadata-store.ts +65 -16
- package/src/tools/credentials/store.ts +2 -0
- package/src/tools/executor.ts +2 -8
- package/src/tools/host-shell.test.ts +75 -0
- package/src/tools/host-shell.ts +24 -0
- package/src/tools/host-terminal/host-shell.ts +15 -44
- package/src/tools/mcp/mcp-tool-factory.ts +44 -9
- package/src/tools/permission-checker.ts +73 -21
- package/src/tools/policy-context.ts +1 -0
- package/src/tools/shared/filesystem/image-read.ts +1 -1
- package/src/tools/skills/find-similar-skills.ts +10 -2
- package/src/tools/skills/load.ts +41 -6
- package/src/tools/skills/sandbox-runner.ts +8 -16
- package/src/tools/skills/skill-tool-factory.ts +1 -0
- package/src/tools/terminal/__tests__/safe-env.test.ts +64 -1
- package/src/tools/terminal/safe-env.ts +39 -9
- package/src/tools/terminal/shell.ts +12 -31
- package/src/tools/tool-approval-handler.ts +64 -10
- package/src/tools/tool-types.ts +10 -0
- package/src/tools/types.ts +19 -3
- package/src/tools/ui-surface/surface-shape-docs.ts +4 -3
- package/src/tools/workflows/manage-workflows.ts +2 -2
- package/src/tools/workflows/run-workflow.test.ts +2 -1
- package/src/util/__tests__/file-use.test.ts +61 -0
- package/src/util/__tests__/process-table.test.ts +66 -0
- package/src/util/__tests__/process-tree.test.ts +46 -0
- package/src/util/errors.ts +1 -0
- package/src/util/file-use.ts +91 -0
- package/src/util/host-process.test.ts +17 -0
- package/src/util/host-process.ts +75 -0
- package/src/util/image-conversion.ts +39 -7
- package/src/util/platform.ts +32 -13
- package/src/util/process-table.ts +249 -0
- package/src/util/process-tree.ts +85 -129
- package/src/util/provider-error-patterns.ts +20 -0
- package/src/util/spawn.ts +9 -2
- package/src/watch/__tests__/watch-retro.test.ts +48 -1
- package/src/watch/watch-retro.ts +46 -4
- package/src/workspace/git-service.ts +4 -14
- package/src/workspace/migrations/147-rename-colliding-backup-profile-names.ts +360 -0
- package/src/workspace/migrations/148-strip-unsupported-fallback-profiles.ts +117 -0
- package/src/workspace/migrations/149-repoint-backup-profile-selections.ts +204 -0
- package/src/workspace/migrations/150-stt-flux-provider-to-model-family.ts +139 -0
- package/src/workspace/migrations/__tests__/150-stt-flux-provider-to-model-family.test.ts +160 -0
- package/src/workspace/migrations/registry.ts +8 -0
- package/src/__tests__/permission-types.test.ts +0 -17
- package/src/channels/gateway-discord-admission.ts +0 -32
- package/src/persistence/fork-message-copy.ts +0 -239
- package/src/plugins/defaults/memory/__tests__/fork-message-copy.test.ts +0 -221
- package/src/security/__tests__/oauth2-device-code.test.ts +0 -431
- package/src/security/oauth2-device-code.ts +0 -312
|
@@ -1374,6 +1374,32 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1374
1374
|
// usage never includes cached tokens. `supportsCaching` therefore stays
|
|
1375
1375
|
// false; the `cacheReadPer1mTokens` rates below only apply if OpenRouter
|
|
1376
1376
|
// starts reporting cached tokens in usage.
|
|
1377
|
+
{
|
|
1378
|
+
id: "x-ai/grok-4.6",
|
|
1379
|
+
displayName: "Grok 4.6",
|
|
1380
|
+
contextWindowTokens: 500000,
|
|
1381
|
+
// xAI publishes no completion cap; 30K is the tracker-reported
|
|
1382
|
+
// single-response limit used for sibling Grok 4.x catalog rows.
|
|
1383
|
+
maxOutputTokens: 30000,
|
|
1384
|
+
supportsThinking: true,
|
|
1385
|
+
supportsCaching: false,
|
|
1386
|
+
supportsVision: true,
|
|
1387
|
+
supportsToolUse: true,
|
|
1388
|
+
longContextPricingThresholdTokens: 200000,
|
|
1389
|
+
pricing: {
|
|
1390
|
+
inputPer1mTokens: 2,
|
|
1391
|
+
outputPer1mTokens: 6,
|
|
1392
|
+
cacheReadPer1mTokens: 0.5,
|
|
1393
|
+
tiers: [
|
|
1394
|
+
{
|
|
1395
|
+
inputTokenThreshold: 200000,
|
|
1396
|
+
inputPer1mTokens: 4,
|
|
1397
|
+
outputPer1mTokens: 12,
|
|
1398
|
+
cacheReadPer1mTokens: 1,
|
|
1399
|
+
},
|
|
1400
|
+
],
|
|
1401
|
+
},
|
|
1402
|
+
},
|
|
1377
1403
|
{
|
|
1378
1404
|
id: "x-ai/grok-4.5",
|
|
1379
1405
|
displayName: "Grok 4.5",
|
|
@@ -1697,6 +1723,36 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1697
1723
|
pricing: { inputPer1mTokens: 0.2, outputPer1mTokens: 1.1 },
|
|
1698
1724
|
},
|
|
1699
1725
|
// Z.ai
|
|
1726
|
+
{
|
|
1727
|
+
id: "z-ai/glm-5.3",
|
|
1728
|
+
displayName: "GLM-5.3",
|
|
1729
|
+
contextWindowTokens: 1048576,
|
|
1730
|
+
maxOutputTokens: 131072,
|
|
1731
|
+
supportsThinking: true,
|
|
1732
|
+
supportsCaching: true,
|
|
1733
|
+
supportsVision: false,
|
|
1734
|
+
supportsToolUse: true,
|
|
1735
|
+
pricing: {
|
|
1736
|
+
inputPer1mTokens: 1.4,
|
|
1737
|
+
outputPer1mTokens: 4.4,
|
|
1738
|
+
cacheReadPer1mTokens: 0.26,
|
|
1739
|
+
},
|
|
1740
|
+
},
|
|
1741
|
+
{
|
|
1742
|
+
id: "z-ai/glm-5.3-flash",
|
|
1743
|
+
displayName: "GLM-5.3 Flash",
|
|
1744
|
+
contextWindowTokens: 1310720,
|
|
1745
|
+
maxOutputTokens: 131072,
|
|
1746
|
+
supportsThinking: true,
|
|
1747
|
+
supportsCaching: true,
|
|
1748
|
+
supportsVision: true,
|
|
1749
|
+
supportsToolUse: true,
|
|
1750
|
+
pricing: {
|
|
1751
|
+
inputPer1mTokens: 0.075,
|
|
1752
|
+
outputPer1mTokens: 0.25,
|
|
1753
|
+
cacheReadPer1mTokens: 0.015,
|
|
1754
|
+
},
|
|
1755
|
+
},
|
|
1700
1756
|
{
|
|
1701
1757
|
id: "z-ai/glm-5.2",
|
|
1702
1758
|
displayName: "GLM-5.2",
|
|
@@ -2038,6 +2094,25 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2038
2094
|
models: [],
|
|
2039
2095
|
defaultModel: "",
|
|
2040
2096
|
},
|
|
2097
|
+
{
|
|
2098
|
+
id: "opencode",
|
|
2099
|
+
displayName: "OpenCode",
|
|
2100
|
+
subtitle:
|
|
2101
|
+
"OpenCode Zen and OpenCode Go. Models come from the endpoint, not a fixed catalog.",
|
|
2102
|
+
setupMode: "api-key",
|
|
2103
|
+
setupHint:
|
|
2104
|
+
"Enter your OpenCode API key. Leave the base URL empty for Zen, or set it to the Go endpoint.",
|
|
2105
|
+
envVar: "OPENCODE_API_KEY",
|
|
2106
|
+
credentialsGuide: {
|
|
2107
|
+
description:
|
|
2108
|
+
"Create an OpenCode API key, then choose Zen or Go as the endpoint.",
|
|
2109
|
+
url: "https://opencode.ai",
|
|
2110
|
+
linkLabel: "OpenCode",
|
|
2111
|
+
},
|
|
2112
|
+
apiKeyPlaceholder: "sk-...",
|
|
2113
|
+
models: [],
|
|
2114
|
+
defaultModel: "",
|
|
2115
|
+
},
|
|
2041
2116
|
{
|
|
2042
2117
|
id: "openai-compatible",
|
|
2043
2118
|
displayName: "OpenAI-compatible",
|
|
@@ -2196,6 +2271,31 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2196
2271
|
apiKeyUrl: "https://poolside.ai",
|
|
2197
2272
|
apiKeyPlaceholder: "Your Poolside API key",
|
|
2198
2273
|
},
|
|
2274
|
+
{
|
|
2275
|
+
id: "vellum",
|
|
2276
|
+
displayName: "Vellum",
|
|
2277
|
+
subtitle:
|
|
2278
|
+
"Models served on Vellum GPU nodes through the managed connection.",
|
|
2279
|
+
setupMode: "api-key",
|
|
2280
|
+
setupHint:
|
|
2281
|
+
"Uses the assistant API key through the Vellum managed connection. These models cannot use a bring-your-own key.",
|
|
2282
|
+
featureFlag: "settings-developer-nav",
|
|
2283
|
+
models: [
|
|
2284
|
+
{
|
|
2285
|
+
id: "qwen/qwen3-8b",
|
|
2286
|
+
displayName: "Qwen3 8B",
|
|
2287
|
+
contextWindowTokens: 32768,
|
|
2288
|
+
maxOutputTokens: 32768,
|
|
2289
|
+
supportsThinking: false,
|
|
2290
|
+
supportsCaching: false,
|
|
2291
|
+
supportsVision: false,
|
|
2292
|
+
supportsToolUse: true,
|
|
2293
|
+
pricing: { inputPer1mTokens: 0.3, outputPer1mTokens: 0.3 },
|
|
2294
|
+
featureFlag: "settings-developer-nav",
|
|
2295
|
+
},
|
|
2296
|
+
],
|
|
2297
|
+
defaultModel: "qwen/qwen3-8b",
|
|
2298
|
+
},
|
|
2199
2299
|
];
|
|
2200
2300
|
|
|
2201
2301
|
export const PROVIDER_CATALOG: ProviderCatalogEntry[] =
|
|
@@ -387,6 +387,28 @@ describe("deriveReason", () => {
|
|
|
387
387
|
).toBe("vision_unsupported");
|
|
388
388
|
});
|
|
389
389
|
|
|
390
|
+
test("chat-template failure prose on a 400 → request_shape_unsupported", () => {
|
|
391
|
+
// Together's server-side renderer error for MiniMax M3, verbatim.
|
|
392
|
+
expect(
|
|
393
|
+
deriveReason(
|
|
394
|
+
n({
|
|
395
|
+
message:
|
|
396
|
+
"Failed to apply chat template: invalid operation: object is not callable (in chat:22)",
|
|
397
|
+
}),
|
|
398
|
+
400,
|
|
399
|
+
),
|
|
400
|
+
).toBe("request_shape_unsupported");
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
test("chat-template prose on a 5xx stays server-side, not request_shape_unsupported", () => {
|
|
404
|
+
expect(
|
|
405
|
+
deriveReason(
|
|
406
|
+
n({ message: "Failed to apply chat template: renderer crashed" }),
|
|
407
|
+
500,
|
|
408
|
+
),
|
|
409
|
+
).not.toBe("request_shape_unsupported");
|
|
410
|
+
});
|
|
411
|
+
|
|
390
412
|
test("402 → insufficient_credits", () => {
|
|
391
413
|
expect(deriveReason(n(), 402)).toBe("insufficient_credits");
|
|
392
414
|
});
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { ProviderError } from "../../../util/errors.js";
|
|
4
|
+
import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
|
|
5
|
+
|
|
6
|
+
type MockChunk = {
|
|
7
|
+
choices: Array<{
|
|
8
|
+
delta: { content?: string | null };
|
|
9
|
+
finish_reason?: string | null;
|
|
10
|
+
}>;
|
|
11
|
+
usage?: { prompt_tokens: number; completion_tokens: number };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function makeStream(chunks: MockChunk[]): AsyncIterable<MockChunk> {
|
|
15
|
+
return {
|
|
16
|
+
async *[Symbol.asyncIterator]() {
|
|
17
|
+
for (const c of chunks) {
|
|
18
|
+
yield c;
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const OK_CHUNKS: MockChunk[] = [
|
|
25
|
+
{
|
|
26
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
27
|
+
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
function stubProviderWithErrors(errors: unknown[]): {
|
|
32
|
+
provider: OpenAIChatCompletionsProvider;
|
|
33
|
+
requests: unknown[];
|
|
34
|
+
} {
|
|
35
|
+
const provider = new OpenAIChatCompletionsProvider("test-key", "test-model");
|
|
36
|
+
const requests: unknown[] = [];
|
|
37
|
+
const pending = [...errors];
|
|
38
|
+
(provider as unknown as { client: unknown }).client = {
|
|
39
|
+
chat: {
|
|
40
|
+
completions: {
|
|
41
|
+
create: async (params: unknown) => {
|
|
42
|
+
// Snapshot: the fallback mutates `params` between attempts.
|
|
43
|
+
requests.push(JSON.parse(JSON.stringify(params)));
|
|
44
|
+
const error = pending.shift();
|
|
45
|
+
if (error !== undefined) {
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
return makeStream(OK_CHUNKS);
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
return { provider, requests };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Together's server-side renderer error for MiniMax M3, verbatim.
|
|
57
|
+
const CHAT_TEMPLATE_400 =
|
|
58
|
+
"Failed to apply chat template: invalid operation: object is not callable (in chat:22)";
|
|
59
|
+
|
|
60
|
+
function rejection(message: string, status = 400): Error {
|
|
61
|
+
return Object.assign(new Error(message), { status });
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const TINY_PNG =
|
|
65
|
+
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
|
|
66
|
+
|
|
67
|
+
describe("chat-template rejection flatten fallback", () => {
|
|
68
|
+
test("retries once with flattened string content when the chat template rejects a text-only content-parts array", async () => {
|
|
69
|
+
// GIVEN an endpoint whose chat template 400s on structured message content
|
|
70
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
71
|
+
rejection(CHAT_TEMPLATE_400),
|
|
72
|
+
]);
|
|
73
|
+
|
|
74
|
+
// WHEN a multi-block user message of purely textual parts is sent
|
|
75
|
+
const response = await provider.sendMessage([
|
|
76
|
+
{
|
|
77
|
+
role: "user",
|
|
78
|
+
content: [
|
|
79
|
+
{ type: "text", text: "first paragraph" },
|
|
80
|
+
{ type: "text", text: "second paragraph" },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
// THEN the first request carried a content-parts array
|
|
86
|
+
expect(requests).toHaveLength(2);
|
|
87
|
+
const first = requests[0] as {
|
|
88
|
+
messages: Array<{ content: unknown }>;
|
|
89
|
+
};
|
|
90
|
+
expect(Array.isArray(first.messages[0].content)).toBe(true);
|
|
91
|
+
|
|
92
|
+
// AND the retry flattened it to a plain string keeping all the text
|
|
93
|
+
const second = requests[1] as {
|
|
94
|
+
messages: Array<{ content: unknown }>;
|
|
95
|
+
};
|
|
96
|
+
const flattened = second.messages[0].content;
|
|
97
|
+
expect(flattened).toBe("first paragraph\n\nsecond paragraph");
|
|
98
|
+
|
|
99
|
+
// AND the retry succeeded
|
|
100
|
+
const text = response.content.find((b) => b.type === "text") as
|
|
101
|
+
| { type: "text"; text: string }
|
|
102
|
+
| undefined;
|
|
103
|
+
expect(text?.text).toBe("ok");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("does not retry when a content-parts array carries media (never silently drop an image)", async () => {
|
|
107
|
+
// GIVEN an endpoint whose chat template 400s on structured message content
|
|
108
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
109
|
+
rejection(CHAT_TEMPLATE_400),
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// WHEN a multi-block user message with an image part is sent
|
|
113
|
+
const promise = provider.sendMessage([
|
|
114
|
+
{
|
|
115
|
+
role: "user",
|
|
116
|
+
content: [
|
|
117
|
+
{ type: "text", text: "what is in this image?" },
|
|
118
|
+
{
|
|
119
|
+
type: "image",
|
|
120
|
+
source: {
|
|
121
|
+
type: "base64",
|
|
122
|
+
media_type: "image/png",
|
|
123
|
+
data: TINY_PNG,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
129
|
+
|
|
130
|
+
// THEN the error propagates with no retry, so downstream classification
|
|
131
|
+
// can surface the capability mismatch instead of answering without the image
|
|
132
|
+
await expect(promise).rejects.toBeInstanceOf(ProviderError);
|
|
133
|
+
expect(requests).toHaveLength(1);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("does not retry when the messages are already plain strings", async () => {
|
|
137
|
+
// GIVEN a chat-template 400 on a request whose content is already a
|
|
138
|
+
// plain string (flattening would change nothing, so retrying is futile)
|
|
139
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
140
|
+
rejection(CHAT_TEMPLATE_400),
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// WHEN a single-text-block user message is sent
|
|
144
|
+
const promise = provider.sendMessage([
|
|
145
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
// THEN the error propagates as a ProviderError with no retry attempt
|
|
149
|
+
await expect(promise).rejects.toBeInstanceOf(ProviderError);
|
|
150
|
+
expect(requests).toHaveLength(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test("does not retry on 5xx even when the message mentions the chat template", async () => {
|
|
154
|
+
// GIVEN a 500 whose body happens to mention the chat template (a server
|
|
155
|
+
// fault, not a request-shape rejection)
|
|
156
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
157
|
+
rejection(CHAT_TEMPLATE_400, 500),
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
// WHEN a multi-block user message is sent
|
|
161
|
+
const promise = provider.sendMessage([
|
|
162
|
+
{
|
|
163
|
+
role: "user",
|
|
164
|
+
content: [
|
|
165
|
+
{ type: "text", text: "a" },
|
|
166
|
+
{ type: "text", text: "b" },
|
|
167
|
+
],
|
|
168
|
+
},
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
// THEN the error propagates with no retry attempt
|
|
172
|
+
await expect(promise).rejects.toBeInstanceOf(ProviderError);
|
|
173
|
+
expect(requests).toHaveLength(1);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test("surfaces the retry's error when the flattened request also fails", async () => {
|
|
177
|
+
// GIVEN an endpoint that rejects both the structured and the flattened shape
|
|
178
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
179
|
+
rejection(CHAT_TEMPLATE_400),
|
|
180
|
+
rejection(CHAT_TEMPLATE_400),
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
// WHEN a multi-block user message is sent
|
|
184
|
+
const promise = provider.sendMessage([
|
|
185
|
+
{
|
|
186
|
+
role: "user",
|
|
187
|
+
content: [
|
|
188
|
+
{ type: "text", text: "a" },
|
|
189
|
+
{ type: "text", text: "b" },
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
// THEN the failure propagates after exactly one retry
|
|
195
|
+
await expect(promise).rejects.toBeInstanceOf(ProviderError);
|
|
196
|
+
expect(requests).toHaveLength(2);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
@@ -4,6 +4,7 @@ import type { ProviderErrorReason } from "../../util/errors.js";
|
|
|
4
4
|
import {
|
|
5
5
|
DAILY_LIMIT_PATTERNS,
|
|
6
6
|
INSUFFICIENT_CREDITS_PATTERNS,
|
|
7
|
+
isChatTemplateFailureError,
|
|
7
8
|
VISION_NOT_SUPPORTED_PATTERNS,
|
|
8
9
|
} from "../../util/provider-error-patterns.js";
|
|
9
10
|
|
|
@@ -81,6 +82,18 @@ export function deriveReason(
|
|
|
81
82
|
return "vision_unsupported";
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
// A 4xx from the endpoint's server-side chat-template renderer: the request
|
|
86
|
+
// shape (content-parts arrays, tool payloads) is what the template can't
|
|
87
|
+
// handle, not the credentials or the model.
|
|
88
|
+
if (
|
|
89
|
+
status !== undefined &&
|
|
90
|
+
status >= 400 &&
|
|
91
|
+
status < 500 &&
|
|
92
|
+
isChatTemplateFailureError(haystack)
|
|
93
|
+
) {
|
|
94
|
+
return "request_shape_unsupported";
|
|
95
|
+
}
|
|
96
|
+
|
|
84
97
|
// The managed proxy's daily-limit 402 shares the status with generic credit
|
|
85
98
|
// exhaustion; match its specific body code first so it isn't swallowed.
|
|
86
99
|
if (DAILY_LIMIT_PATTERNS.some((re) => re.test(haystack))) {
|
|
@@ -4,6 +4,7 @@ import { SYSTEM_PROMPT_CACHE_BOUNDARY } from "../../prompts/cache-boundary.js";
|
|
|
4
4
|
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
|
+
import { isChatTemplateFailureError } from "../../util/provider-error-patterns.js";
|
|
7
8
|
import { extractRetryAfterMs } from "../../util/retry.js";
|
|
8
9
|
import { partialTagSuffix as sharedPartialTagSuffix } from "../../util/think-tag-stream.js";
|
|
9
10
|
import { escapeXmlAttr } from "../../util/xml.js";
|
|
@@ -529,6 +530,74 @@ function isUnknownAssistantReasoningFieldRejection(
|
|
|
529
530
|
);
|
|
530
531
|
}
|
|
531
532
|
|
|
533
|
+
function isTextualContentPart(part: { type: string }): boolean {
|
|
534
|
+
return part.type === "text" || part.type === "refusal";
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function messagesCarryFlattenableContentPartsArrays(params: unknown): boolean {
|
|
538
|
+
const messages = paramsMessages(params);
|
|
539
|
+
if (!messages) {
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
const arrays = messages.filter((msg) => Array.isArray(msg.content));
|
|
543
|
+
if (arrays.length === 0) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
return arrays.every((msg) =>
|
|
547
|
+
(msg.content as Array<{ type: string }>).every(isTextualContentPart),
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* True when the endpoint's server-side chat-template renderer rejected the
|
|
553
|
+
* request and every content-parts array in the outbound messages is purely
|
|
554
|
+
* textual, so flattening to plain strings loses nothing. Some template
|
|
555
|
+
* engines behind OpenAI-compatible endpoints only render string message
|
|
556
|
+
* content (Together serving MiniMax M3 400s with `Failed to apply chat
|
|
557
|
+
* template: invalid operation: object is not callable`); one retry with
|
|
558
|
+
* flattened content lets the same endpoint succeed instead of surfacing the
|
|
559
|
+
* raw template error. Requests carrying media parts are never flattened:
|
|
560
|
+
* silently dropping an image or audio blob would let the model answer
|
|
561
|
+
* without it, so those rejections propagate to error classification.
|
|
562
|
+
*/
|
|
563
|
+
function isChatTemplateRejection(error: unknown, params: unknown): boolean {
|
|
564
|
+
if (!isClientErrorStatus(error)) {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
if (!isChatTemplateFailureError(openaiCompatErrorHaystack(error))) {
|
|
568
|
+
return false;
|
|
569
|
+
}
|
|
570
|
+
return messagesCarryFlattenableContentPartsArrays(params);
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Rewrite every purely-textual content-parts array in `params.messages` into
|
|
575
|
+
* a plain string. Returns whether any message changed.
|
|
576
|
+
*/
|
|
577
|
+
function flattenContentPartsToStrings(params: unknown): boolean {
|
|
578
|
+
const messages = paramsMessages(params);
|
|
579
|
+
if (!messages) {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
let flattened = false;
|
|
583
|
+
for (const msg of messages) {
|
|
584
|
+
if (!Array.isArray(msg.content)) {
|
|
585
|
+
continue;
|
|
586
|
+
}
|
|
587
|
+
const parts = msg.content as Array<
|
|
588
|
+
{ type: "text"; text: string } | { type: "refusal"; refusal: string }
|
|
589
|
+
>;
|
|
590
|
+
if (!parts.every(isTextualContentPart)) {
|
|
591
|
+
continue;
|
|
592
|
+
}
|
|
593
|
+
msg.content = parts
|
|
594
|
+
.map((part) => (part.type === "text" ? part.text : part.refusal))
|
|
595
|
+
.join("\n\n");
|
|
596
|
+
flattened = true;
|
|
597
|
+
}
|
|
598
|
+
return flattened;
|
|
599
|
+
}
|
|
600
|
+
|
|
532
601
|
/**
|
|
533
602
|
* Translate the neutral (Anthropic-shaped) `tool_choice` carried on the call
|
|
534
603
|
* config into the OpenAI chat-completions wire format. Callers express
|
|
@@ -656,6 +725,9 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
656
725
|
const usageAttributionHeaders = configObj?.usageAttributionHeaders as
|
|
657
726
|
| Record<string, string>
|
|
658
727
|
| undefined;
|
|
728
|
+
const perRequestHeaders = configObj?.requestHeaders as
|
|
729
|
+
| Record<string, string>
|
|
730
|
+
| undefined;
|
|
659
731
|
|
|
660
732
|
// Per-tool keys whose object schemas were rewritten to JSON strings for the
|
|
661
733
|
// wire, to be decoded back on the response. Empty unless
|
|
@@ -844,6 +916,7 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
844
916
|
const requestHeaders = {
|
|
845
917
|
...this.requestHeaders,
|
|
846
918
|
...(usageAttributionHeaders ?? {}),
|
|
919
|
+
...(perRequestHeaders ?? {}),
|
|
847
920
|
};
|
|
848
921
|
const createStream = () =>
|
|
849
922
|
this.client.chat.completions.create(params, {
|
|
@@ -901,6 +974,17 @@ export class OpenAIChatCompletionsProvider implements Provider {
|
|
|
901
974
|
);
|
|
902
975
|
stripAssistantReasoningFields(params);
|
|
903
976
|
stream = await createStream();
|
|
977
|
+
} else if (isChatTemplateRejection(error, params)) {
|
|
978
|
+
log.warn(
|
|
979
|
+
{
|
|
980
|
+
provider: this.name,
|
|
981
|
+
model: modelOverride ?? this.model,
|
|
982
|
+
error: error instanceof Error ? error.message : String(error),
|
|
983
|
+
},
|
|
984
|
+
"Upstream chat template rejected structured message content; retrying with flattened plain-text content",
|
|
985
|
+
);
|
|
986
|
+
flattenContentPartsToStrings(params);
|
|
987
|
+
stream = await createStream();
|
|
904
988
|
} else {
|
|
905
989
|
throw error;
|
|
906
990
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
EMPTY_ASSISTANT_TURN_PLACEHOLDER,
|
|
5
|
+
OpenAIChatCompletionsProvider,
|
|
6
|
+
} from "../openai/chat-completions-provider.js";
|
|
7
|
+
import {
|
|
8
|
+
buildOpenCodeRequestHeaders,
|
|
9
|
+
OPENCODE_GO_BASE_URL,
|
|
10
|
+
OPENCODE_REQUEST_HEADER,
|
|
11
|
+
OPENCODE_SESSION_HEADER,
|
|
12
|
+
OPENCODE_ZEN_BASE_URL,
|
|
13
|
+
OpenCodeProvider,
|
|
14
|
+
resolveOpenCodeBaseURL,
|
|
15
|
+
} from "./client.js";
|
|
16
|
+
|
|
17
|
+
describe("resolveOpenCodeBaseURL", () => {
|
|
18
|
+
test("defaults to OpenCode Zen", () => {
|
|
19
|
+
expect(resolveOpenCodeBaseURL()).toBe(OPENCODE_ZEN_BASE_URL);
|
|
20
|
+
expect(resolveOpenCodeBaseURL("")).toBe(OPENCODE_ZEN_BASE_URL);
|
|
21
|
+
expect(resolveOpenCodeBaseURL(" ")).toBe(OPENCODE_ZEN_BASE_URL);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("keeps a configured origin, including OpenCode Go", () => {
|
|
25
|
+
expect(resolveOpenCodeBaseURL(OPENCODE_GO_BASE_URL)).toBe(
|
|
26
|
+
OPENCODE_GO_BASE_URL,
|
|
27
|
+
);
|
|
28
|
+
expect(resolveOpenCodeBaseURL(" https://example.com/v1 ")).toBe(
|
|
29
|
+
"https://example.com/v1",
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("buildOpenCodeRequestHeaders", () => {
|
|
35
|
+
test("omits headers when ids are missing", () => {
|
|
36
|
+
expect(buildOpenCodeRequestHeaders({})).toEqual({});
|
|
37
|
+
expect(buildOpenCodeRequestHeaders({ conversationId: " " })).toEqual({});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test("sets session and request headers without session_id", () => {
|
|
41
|
+
const headers = buildOpenCodeRequestHeaders({
|
|
42
|
+
conversationId: "conv-xyz",
|
|
43
|
+
requestId: "req-123",
|
|
44
|
+
});
|
|
45
|
+
expect(headers[OPENCODE_SESSION_HEADER]).toBe("conv-xyz");
|
|
46
|
+
expect(headers[OPENCODE_REQUEST_HEADER]).toBe("req-123");
|
|
47
|
+
expect(headers).not.toHaveProperty("session_id");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("sets only the ids that are present", () => {
|
|
51
|
+
expect(
|
|
52
|
+
buildOpenCodeRequestHeaders({ conversationId: "conv-xyz" }),
|
|
53
|
+
).toEqual({ [OPENCODE_SESSION_HEADER]: "conv-xyz" });
|
|
54
|
+
expect(buildOpenCodeRequestHeaders({ requestId: "req-123" })).toEqual({
|
|
55
|
+
[OPENCODE_REQUEST_HEADER]: "req-123",
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("OpenCodeProvider", () => {
|
|
61
|
+
test("uses OpenAI chat-completions with reasoning-compatible knobs", () => {
|
|
62
|
+
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
63
|
+
expect(provider).toBeInstanceOf(OpenAIChatCompletionsProvider);
|
|
64
|
+
expect(provider.name).toBe("opencode");
|
|
65
|
+
expect(
|
|
66
|
+
(provider as unknown as { assistantReasoningField?: string })
|
|
67
|
+
.assistantReasoningField,
|
|
68
|
+
).toBe("reasoning_content");
|
|
69
|
+
expect(
|
|
70
|
+
(provider as unknown as { omitToolChoiceWhenReasoning: boolean })
|
|
71
|
+
.omitToolChoiceWhenReasoning,
|
|
72
|
+
).toBe(true);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test("merges per-request OpenCode headers onto the transport", async () => {
|
|
76
|
+
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
77
|
+
const seen: Array<{ params: unknown; options: unknown }> = [];
|
|
78
|
+
(provider as unknown as { client: unknown }).client = {
|
|
79
|
+
chat: {
|
|
80
|
+
completions: {
|
|
81
|
+
create: async (params: unknown, options: unknown) => {
|
|
82
|
+
seen.push({ params, options });
|
|
83
|
+
return {
|
|
84
|
+
async *[Symbol.asyncIterator]() {
|
|
85
|
+
yield {
|
|
86
|
+
choices: [
|
|
87
|
+
{ delta: { content: "ok" }, finish_reason: "stop" },
|
|
88
|
+
],
|
|
89
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
await provider.sendMessage(
|
|
99
|
+
[{ role: "user", content: [{ type: "text", text: "question" }] }],
|
|
100
|
+
{
|
|
101
|
+
config: {
|
|
102
|
+
requestHeaders: {
|
|
103
|
+
[OPENCODE_SESSION_HEADER]: "conv-xyz",
|
|
104
|
+
[OPENCODE_REQUEST_HEADER]: "req-123",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const options = seen[0]!.options as { headers?: Record<string, string> };
|
|
111
|
+
expect(options.headers?.[OPENCODE_SESSION_HEADER]).toBe("conv-xyz");
|
|
112
|
+
expect(options.headers?.[OPENCODE_REQUEST_HEADER]).toBe("req-123");
|
|
113
|
+
expect(options.headers).not.toHaveProperty("session_id");
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("backfills placeholder content after an aborted empty assistant turn", async () => {
|
|
117
|
+
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
118
|
+
const requests: unknown[] = [];
|
|
119
|
+
(provider as unknown as { client: unknown }).client = {
|
|
120
|
+
chat: {
|
|
121
|
+
completions: {
|
|
122
|
+
create: async (params: unknown) => {
|
|
123
|
+
requests.push(params);
|
|
124
|
+
return {
|
|
125
|
+
async *[Symbol.asyncIterator]() {
|
|
126
|
+
yield {
|
|
127
|
+
choices: [
|
|
128
|
+
{ delta: { content: "ok" }, finish_reason: "stop" },
|
|
129
|
+
],
|
|
130
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
await provider.sendMessage([
|
|
140
|
+
{ role: "user", content: [{ type: "text", text: "question" }] },
|
|
141
|
+
{ role: "assistant", content: [] },
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
const params = requests[0] as {
|
|
145
|
+
messages: Array<{ role: string; content: string | null }>;
|
|
146
|
+
};
|
|
147
|
+
const assistantMsg = params.messages.find((m) => m.role === "assistant")!;
|
|
148
|
+
expect(assistantMsg.content).toBe(EMPTY_ASSISTANT_TURN_PLACEHOLDER);
|
|
149
|
+
});
|
|
150
|
+
});
|