@vellumai/assistant 0.12.0 → 0.12.1-staging.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +5 -1
- package/ARCHITECTURE.md +11 -12
- package/docs/architecture/turn-actor.md +15 -8
- package/openapi.yaml +292 -2
- package/package.json +1 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/app-compiler.test.ts +6 -0
- package/src/__tests__/app-control-tool-schemas.test.ts +5 -2
- package/src/__tests__/app-dir-path-guard.test.ts +1 -3
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +17 -5
- package/src/__tests__/config-schema-cmd.test.ts +2 -2
- package/src/__tests__/config-schema.test.ts +28 -29
- package/src/__tests__/config-watcher.test.ts +23 -8
- package/src/__tests__/connection-model-compat.test.ts +2 -2
- package/src/__tests__/conversation-key-store-disk-view.test.ts +1 -1
- package/src/__tests__/conversation-pairing.test.ts +26 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +4 -10
- package/src/__tests__/conversation-queue.test.ts +48 -207
- package/src/__tests__/conversation-retry-discard.test.ts +54 -1
- package/src/__tests__/conversation-skill-tools.test.ts +27 -0
- package/src/__tests__/conversation-surfaces-app-control.test.ts +8 -3
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +67 -0
- package/src/__tests__/conversation-title-service.test.ts +14 -10
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +3 -5
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +51 -0
- package/src/__tests__/credential-record-retire.test.ts +171 -1
- package/src/__tests__/credential-routes.test.ts +212 -3
- package/src/__tests__/credential-security-invariants.test.ts +2 -1
- package/src/__tests__/cu-unified-flow.test.ts +8 -2
- package/src/__tests__/drain-kick-guard.test.ts +0 -2
- package/src/__tests__/drain-requeue-on-contention.test.ts +0 -6
- package/src/__tests__/evict-conversations-for-reload.test.ts +1 -1
- package/src/__tests__/git-porcelain.test.ts +47 -0
- package/src/__tests__/helpers/set-workspace-mcp.ts +11 -0
- package/src/__tests__/host-bash-proxy.test.ts +26 -45
- package/src/__tests__/host-browser-proxy.test.ts +6 -17
- package/src/__tests__/host-file-proxy-targeted.test.ts +23 -0
- package/src/__tests__/host-file-proxy.test.ts +11 -23
- package/src/__tests__/host-transfer-proxy-targeted.test.ts +30 -9
- package/src/__tests__/llm-catalog-parity.test.ts +25 -0
- package/src/__tests__/mcp-auth-routes.test.ts +6 -9
- package/src/__tests__/mcp-config-secret-boundary.test.ts +78 -136
- package/src/__tests__/mcp-health-check.test.ts +8 -11
- package/src/__tests__/mcp-list-plugin-servers.test.ts +8 -10
- package/src/__tests__/message-deleted-hook-dispatch.test.ts +119 -0
- package/src/__tests__/messages-after-tiebreaker.test.ts +91 -0
- package/src/__tests__/messages-read-boundary-guard.test.ts +1 -1
- package/src/__tests__/messaging-send-tool.test.ts +355 -548
- package/src/__tests__/model-intents.test.ts +3 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +13 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/skill-feature-flags-integration.test.ts +1 -1
- package/src/__tests__/skill-feature-flags.test.ts +2 -2
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +19 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +4 -0
- package/src/__tests__/watch-retro-tool-availability.test.ts +8 -17
- package/src/__tests__/workspace-git-service.test.ts +26 -0
- package/src/__tests__/workspace-migration-154-repair-retired-codex-gpt-5-4-model-ids.test.ts +336 -0
- package/src/acp/__tests__/agent-process.test.ts +258 -4
- package/src/acp/__tests__/auth-required.test.ts +96 -0
- package/src/acp/__tests__/client-handler.test.ts +90 -2
- package/src/acp/__tests__/helpers/acp-model-option.ts +51 -0
- package/src/acp/__tests__/session-manager-persistence.test.ts +5 -1
- package/src/acp/__tests__/session-manager-resume.test.ts +270 -6
- package/src/acp/__tests__/session-manager-usage.test.ts +4 -2
- package/src/acp/__tests__/session-manager.test.ts +895 -4
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +94 -9
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/client-handler.ts +16 -3
- package/src/acp/model-config.test.ts +166 -0
- package/src/acp/model-config.ts +138 -0
- package/src/acp/resolve-agent.test.ts +220 -6
- package/src/acp/resolve-agent.ts +88 -23
- package/src/acp/session-manager.test.ts +196 -3
- package/src/acp/session-manager.ts +441 -18
- package/src/acp/types.ts +50 -5
- package/src/api/events/acp-session-model-update.test.ts +88 -0
- package/src/api/events/acp-session-model-update.ts +46 -0
- package/src/api/events/app-files-changed.ts +4 -3
- package/src/api/index.ts +6 -0
- package/src/apps/app-store.ts +1 -1
- package/src/apps/resolve-app.ts +81 -0
- package/src/apps/source-fingerprint.ts +266 -0
- package/src/bundler/app-compiler.ts +15 -0
- package/src/calls/__tests__/barge-in-guard.test.ts +81 -0
- package/src/{__tests__ → calls/__tests__}/call-controller.test.ts +503 -82
- package/src/{__tests__ → calls/__tests__}/media-stream-audio-transcode.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-output.test.ts +37 -8
- package/src/{__tests__ → calls/__tests__}/media-stream-parser.test.ts +1 -1
- package/src/{__tests__ → calls/__tests__}/media-stream-server-integration.test.ts +150 -57
- package/src/{__tests__ → calls/__tests__}/media-stream-stt-session.test.ts +5 -5
- package/src/{__tests__ → calls/__tests__}/media-turn-detector.test.ts +1 -1
- package/src/{live-voice → calls}/__tests__/progress-narration.test.ts +4 -4
- package/src/{live-voice → calls}/__tests__/progress-phrases.test.ts +1 -1
- package/src/calls/__tests__/voice-control-protocol.test.ts +58 -25
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +229 -0
- package/src/calls/__tests__/voice-progress-cadence.test.ts +274 -0
- package/src/{__tests__/voice-session-bridge.test.ts → calls/__tests__/voice-session-bridge-turn-flow.test.ts} +20 -12
- package/src/calls/__tests__/voice-session-bridge.test.ts +37 -1
- package/src/calls/__tests__/voice-triage-escalate.test.ts +105 -0
- package/src/calls/barge-in-guard.ts +118 -0
- package/src/calls/call-constants.ts +24 -4
- package/src/calls/call-controller.ts +499 -199
- package/src/calls/call-transport.ts +16 -0
- package/src/calls/media-stream-output.ts +32 -0
- package/src/calls/media-stream-server.ts +82 -30
- package/src/calls/media-stream-stt-session.ts +15 -0
- package/src/{live-voice → calls}/progress-narration.ts +2 -2
- package/src/calls/resolve-call-tts-provider.ts +33 -2
- package/src/calls/tts-call-strategy.ts +9 -5
- package/src/calls/types.ts +2 -0
- package/src/calls/voice-control-protocol.ts +53 -21
- package/src/calls/voice-leg-coordinator.ts +189 -0
- package/src/calls/voice-progress-cadence.ts +423 -0
- package/src/calls/voice-session-bridge.ts +59 -46
- package/src/calls/voice-triage-escalate.ts +175 -56
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/roadmap.help.test.ts +19 -0
- package/src/cli/commands/apps.help.ts +67 -13
- package/src/cli/commands/apps.ts +183 -0
- package/src/cli/commands/channels/__tests__/send.test.ts +218 -0
- package/src/cli/commands/channels/index.help.ts +70 -2
- package/src/cli/commands/channels/index.ts +16 -3
- package/src/cli/commands/channels/send.ts +128 -0
- package/src/cli/commands/credentials.help.ts +6 -2
- package/src/cli/commands/credentials.ts +2 -2
- package/src/cli/commands/mcp.help.ts +8 -8
- package/src/cli/commands/mcp.ts +1 -1
- package/src/cli/commands/roadmap.help.ts +5 -1
- package/src/cli/commands/roadmap.ts +1 -1
- package/src/cli/lib/bundled-marketplace.json +103 -90
- package/src/config/__tests__/default-profile-catalog.test.ts +22 -0
- package/src/config/__tests__/memory-retrospective-schema.test.ts +15 -1
- package/src/config/__tests__/platform-identity.test.ts +117 -0
- package/src/config/__tests__/platform-rehydration.test.ts +93 -0
- package/src/config/acp-agent-write.ts +43 -0
- package/src/config/acp-defaults.test.ts +13 -3
- package/src/config/acp-defaults.ts +23 -6
- package/src/config/acp-schema.test.ts +81 -0
- package/src/config/acp-schema.ts +30 -2
- package/src/config/bundled-skills/acp/SKILL.md +22 -14
- package/src/config/bundled-skills/acp/TOOLS.json +5 -1
- package/src/config/bundled-skills/app-builder/SKILL.md +12 -9
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +1 -1
- package/src/config/bundled-skills/messaging/TOOLS.json +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +67 -139
- package/src/config/bundled-skills/phone-calls/references/TRANSCRIPTS.md +1 -1
- package/src/config/bundled-skills/screen-annotation/SKILL.md +8 -1
- package/src/config/bundled-skills/screen-annotation/tools/screen-annotation.test.ts +4 -1
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +8 -12
- package/src/config/memory-v3-gate.test.ts +31 -0
- package/src/config/memory-v3-gate.ts +20 -16
- package/src/config/platform-identity.ts +144 -0
- package/src/config/platform-rehydration.ts +75 -21
- package/src/config/schema.ts +2 -2
- package/src/config/schemas/__tests__/live-voice.test.ts +8 -158
- package/src/config/schemas/__tests__/voice.test.ts +148 -0
- package/src/config/schemas/calls.ts +1 -28
- package/src/config/schemas/live-voice.ts +1 -173
- package/src/config/schemas/mcp.ts +1 -1
- package/src/config/schemas/memory-retrospective.ts +10 -0
- package/src/config/schemas/skills.ts +0 -14
- package/src/config/schemas/voice.ts +150 -0
- package/src/credential-execution/process-manager.test.ts +18 -19
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +66 -121
- package/src/daemon/__tests__/plugin-mcp-reconcile.test.ts +4 -1
- package/src/daemon/__tests__/skill-memory-refresh-mtime.test.ts +133 -0
- package/src/daemon/app-change-notify.ts +45 -0
- package/src/daemon/config-watcher.ts +13 -194
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +10 -8
- package/src/daemon/conversation-tool-setup.ts +37 -43
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-transfer-proxy.ts +14 -18
- package/src/daemon/lifecycle.ts +0 -5
- package/src/daemon/mcp-reload-service.ts +5 -5
- package/src/daemon/message-types/sync.ts +1 -0
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/shutdown-handlers.ts +1 -9
- package/src/daemon/skill-memory-refresh.ts +64 -0
- package/src/daemon/tool-side-effects.ts +7 -18
- package/src/desktop/__tests__/fake-desktop.ts +119 -0
- package/src/desktop/desktop-chrome-policy.test.ts +71 -0
- package/src/desktop/desktop-chrome-policy.ts +42 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +306 -0
- package/src/desktop/desktop-feature.test.ts +18 -0
- package/src/desktop/desktop-feature.ts +19 -0
- package/src/desktop/desktop-panel-config.test.ts +135 -0
- package/src/desktop/desktop-panel-config.ts +149 -0
- package/src/desktop/desktop-session-manager.test.ts +627 -0
- package/src/desktop/desktop-session-manager.ts +814 -0
- package/src/desktop/desktop-stream-bridge.test.ts +395 -0
- package/src/desktop/desktop-stream-bridge.ts +214 -0
- package/src/desktop/desktop-wallpaper.test.ts +191 -0
- package/src/desktop/desktop-wallpaper.ts +113 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/hooks/types.ts +33 -0
- package/src/i18n/AGENTS.md +22 -0
- package/src/i18n/__tests__/i18n.test.ts +108 -0
- package/src/i18n/index.ts +16 -0
- package/src/i18n/locales.ts +93 -0
- package/src/i18n/messages.ts +61 -0
- package/src/i18n/title.ts +25 -0
- package/src/inbound/platform-callback-registration.ts +4 -5
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +15 -15
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/live-voice-session.ts +213 -693
- package/src/live-voice/protocol.ts +0 -2
- package/src/mcp/__tests__/effective-config.test.ts +2 -2
- package/src/mcp/__tests__/workspace-mcp-config.test.ts +135 -0
- package/src/mcp/effective-config.ts +3 -3
- package/src/mcp/mcp-header-store.ts +29 -45
- package/src/mcp/spec-schema.ts +111 -0
- package/src/mcp/startup.ts +8 -4
- package/src/mcp/workspace-mcp-config.ts +188 -0
- package/src/messaging/provider.ts +10 -3
- package/src/messaging/providers/__tests__/proactive-address.test.ts +95 -0
- package/src/messaging/providers/callback-routing.ts +18 -0
- package/src/messaging/providers/channel-transport.ts +56 -0
- package/src/messaging/providers/discord/transport.ts +17 -0
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +6 -17
- package/src/messaging/providers/slack/adapter.ts +7 -44
- package/src/messaging/providers/slack/transport.ts +16 -0
- package/src/messaging/providers/telegram-bot/adapter.ts +1 -48
- package/src/messaging/providers/telegram-bot/client.ts +0 -26
- package/src/messaging/providers/telegram-bot/transport.ts +20 -0
- package/src/messaging/providers/whatsapp/adapter.ts +1 -48
- package/src/messaging/providers/whatsapp/transport.ts +18 -0
- package/src/monitoring/__tests__/workspace-git-heartbeat.test.ts +8 -0
- package/src/notifications/__tests__/assistant-reply-producer.test.ts +55 -0
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/assistant-reply-producer.ts +8 -3
- package/src/notifications/conversation-pairing.ts +36 -16
- package/src/notifications/delivered-post-record.ts +11 -1
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/outbound-proxy/certs.ts +3 -5
- package/src/permissions/checker.test.ts +11 -6
- package/src/persistence/compaction-log-store-clickhouse.ts +3 -3
- package/src/persistence/conversation-crud.ts +71 -63
- package/src/persistence/conversation-lineage.ts +12 -4
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/llm-request-log-sink-clickhouse.ts +3 -3
- package/src/persistence/llm-request-log-source-clickhouse.ts +3 -3
- package/src/persistence/message-cursor.ts +78 -0
- package/src/persistence/message-reads.ts +30 -0
- package/src/persistence/migrations/157-invite-contact-id.ts +18 -6
- package/src/persistence/migrations/__tests__/157-invite-contact-id.test.ts +149 -0
- package/src/persistence/schema/memory-core.ts +5 -0
- package/src/platform/client.test.ts +2 -0
- package/src/platform/client.ts +2 -10
- package/src/plugin-api/constants.ts +2 -0
- package/src/plugin-api/index.ts +1 -0
- package/src/plugin-api/types.ts +2 -0
- package/src/plugins/defaults/index.ts +2 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-accounting.test.ts +46 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-backfill.test.ts +118 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-cursor-preserve.test.ts +83 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-enqueue.test.ts +9 -5
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-job.test.ts +20 -10
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-state.test.ts +219 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-sweep.test.ts +31 -0
- package/src/plugins/defaults/memory/__tests__/memory-retrospective-trigger-check.test.ts +63 -0
- package/src/plugins/defaults/memory/__tests__/shutdown-hook.test.ts +4 -0
- package/src/plugins/defaults/memory/graph/capability-seed.ts +4 -6
- package/src/plugins/defaults/memory/hooks/init.ts +10 -0
- package/src/plugins/defaults/memory/hooks/message-deleted.ts +21 -0
- package/src/plugins/defaults/memory/hooks/shutdown.ts +2 -0
- package/src/plugins/defaults/memory/jobs-worker.ts +13 -0
- package/src/plugins/defaults/memory/memory-retrospective-accounting.ts +34 -60
- package/src/plugins/defaults/memory/memory-retrospective-cursor-backfill.ts +126 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor-preserve.ts +79 -0
- package/src/plugins/defaults/memory/memory-retrospective-cursor.ts +28 -0
- package/src/plugins/defaults/memory/memory-retrospective-enqueue.ts +3 -5
- package/src/plugins/defaults/memory/memory-retrospective-job.ts +12 -3
- package/src/plugins/defaults/memory/memory-retrospective-state.ts +312 -71
- package/src/plugins/defaults/memory/memory-retrospective-sweep.ts +2 -1
- package/src/plugins/defaults/memory/memory-retrospective-trigger-check.ts +5 -3
- package/src/plugins/defaults/memory/skill-md-mtime-poll.ts +9 -0
- package/src/plugins/defaults/memory/startup.ts +6 -1
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +89 -0
- package/src/plugins/defaults/memory/worker.ts +6 -0
- package/src/plugins/mcp-servers.ts +15 -75
- package/src/providers/__tests__/connection-model-compat.test.ts +3 -3
- package/src/providers/gemini/client.ts +9 -23
- package/src/providers/gemini-thought-signature.test.ts +63 -0
- package/src/providers/gemini-thought-signature.ts +44 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +146 -18
- package/src/providers/openai/__tests__/chat-completions-provider-thought-signature.test.ts +613 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +235 -0
- package/src/providers/openai/__tests__/tool-choice-mapping.test.ts +83 -1
- package/src/providers/openai/chat-completions-provider.ts +192 -78
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/runtime/AGENTS.md +2 -0
- package/src/runtime/__tests__/channel-send.test.ts +443 -0
- package/src/runtime/__tests__/desktop-stream-upgrade.test.ts +89 -0
- package/src/runtime/__tests__/live-voice-upgrade.test.ts +58 -0
- package/src/runtime/__tests__/runtime-ws-test-utils.ts +72 -0
- package/src/runtime/__tests__/watch-stream-upgrade.test.ts +59 -0
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +205 -0
- package/src/runtime/routes/__tests__/channel-send-routes.test.ts +127 -0
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +108 -1
- package/src/runtime/routes/__tests__/dictation-cleanup-guard.test.ts +107 -0
- package/src/runtime/routes/__tests__/mcp-add-default-risk.test.ts +16 -12
- package/src/runtime/routes/acp-routes.test.ts +15 -0
- package/src/runtime/routes/acp-routes.ts +72 -9
- package/src/runtime/routes/app-management-routes.ts +73 -0
- package/src/runtime/routes/app-routes.ts +1 -1
- package/src/runtime/routes/channel-send-routes.ts +85 -0
- package/src/runtime/routes/conversation-list-routes.ts +14 -3
- package/src/runtime/routes/conversation-query-routes.ts +40 -0
- package/src/runtime/routes/credential-routes.ts +19 -10
- package/src/runtime/routes/desktop-setup-routes.test.ts +74 -0
- package/src/runtime/routes/desktop-setup-routes.ts +35 -0
- package/src/runtime/routes/diagnostics-routes.ts +55 -3
- package/src/runtime/routes/index.ts +4 -0
- package/src/runtime/routes/log-export-routes.ts +2 -0
- package/src/runtime/routes/mcp-auth-routes.ts +39 -47
- package/src/runtime/routes/migration-routes.ts +8 -21
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/services/__tests__/conversation-serializer.test.ts +28 -0
- package/src/runtime/services/conversation-serializer.ts +16 -3
- package/src/runtime/sync/resource-sync-events.ts +2 -1
- package/src/schedule/worker.ts +1 -1
- package/src/security/__tests__/ces-http-record-backend.test.ts +137 -0
- package/src/security/ces-http-record-backend.ts +120 -0
- package/src/security/ces-rpc-record-backend.ts +7 -0
- package/src/skills/__tests__/workspace-skill-md-signature.test.ts +60 -0
- package/src/skills/workspace-skill-md-signature.ts +50 -0
- package/src/subagent/consult-context.ts +12 -25
- package/src/tools/acp/list-agents.test.ts +3 -1
- package/src/tools/acp/spawn.test.ts +146 -11
- package/src/tools/acp/spawn.ts +21 -3
- package/src/tools/acp/status.test.ts +94 -0
- package/src/tools/acp/status.ts +40 -3
- package/src/tools/app-control/skill-proxy-bridge.ts +2 -1
- package/src/tools/capability-offer.test.ts +22 -2
- package/src/tools/capability-offer.ts +16 -1
- package/src/tools/computer-use/definitions.ts +4 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/host-filesystem/edit.test.ts +2 -1
- package/src/tools/host-filesystem/edit.ts +2 -2
- package/src/tools/host-filesystem/read.test.ts +2 -1
- package/src/tools/host-filesystem/read.ts +2 -2
- package/src/tools/host-filesystem/transfer.test.ts +2 -1
- package/src/tools/host-filesystem/transfer.ts +2 -2
- package/src/tools/host-filesystem/write.test.ts +2 -1
- package/src/tools/host-filesystem/write.ts +2 -2
- package/src/tools/host-terminal/host-shell.test.ts +16 -0
- package/src/tools/host-terminal/host-shell.ts +4 -20
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/safe-env.test.ts +6 -6
- package/src/tools/terminal/safe-env.ts +23 -3
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/util/__tests__/language-subtag.test.ts +23 -1
- package/src/util/ca-bundle.ts +13 -0
- package/src/util/language-subtag.ts +16 -0
- package/src/util/platform.ts +8 -0
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +5 -54
- package/src/workspace/migrations/154-repair-retired-codex-gpt-5-4-model-ids.ts +258 -0
- package/src/workspace/migrations/155-move-front-model-config-to-voice.ts +78 -0
- package/src/workspace/migrations/156-extract-workspace-mcp-json.ts +214 -0
- package/src/workspace/migrations/__tests__/155-move-front-model-config-to-voice.test.ts +102 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +199 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/app-source-watcher.test.ts +0 -218
- package/src/__tests__/config-watcher-skill-reseed.test.ts +0 -359
- package/src/__tests__/speaker-identification.test.ts +0 -56
- package/src/calls/speaker-identification.ts +0 -233
- package/src/daemon/app-source-watcher.ts +0 -218
- package/src/messaging/providers/whatsapp/client.ts +0 -29
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
SendMessageOptions,
|
|
40
40
|
} from "@vellumai/plugin-api";
|
|
41
41
|
|
|
42
|
+
import { OpenRouterProvider } from "../../../../../providers/openrouter/client.js";
|
|
42
43
|
import { ProviderError } from "../../../../../util/errors.js";
|
|
43
44
|
import { sectionHeadLine } from "../sections.js";
|
|
44
45
|
import type { MemoryRoutingTurn, Section } from "../types.js";
|
|
@@ -843,3 +844,91 @@ describe("selectPool: sections and keyword-in-context snippets", () => {
|
|
|
843
844
|
);
|
|
844
845
|
});
|
|
845
846
|
});
|
|
847
|
+
|
|
848
|
+
// ---------------------------------------------------------------------------
|
|
849
|
+
// selectPool: cataloged thinking and forced-tool compatibility.
|
|
850
|
+
// ---------------------------------------------------------------------------
|
|
851
|
+
|
|
852
|
+
describe("selectPool: cataloged thinking and forced-tool compatibility", () => {
|
|
853
|
+
test("OpenRouter Kimi K2.6 omits the forced choice and yields a structured selection", async () => {
|
|
854
|
+
// A real OpenAI chat-completions provider stands in for the cataloged
|
|
855
|
+
// OpenRouter Kimi profile. The request succeeds without a reactive retry
|
|
856
|
+
// because the forced select_pages choice is omitted before dispatch.
|
|
857
|
+
const wireRequests: unknown[] = [];
|
|
858
|
+
const kimi = new OpenRouterProvider(
|
|
859
|
+
"test-key",
|
|
860
|
+
"moonshotai/kimi-k2.6-20260420",
|
|
861
|
+
);
|
|
862
|
+
(kimi as unknown as { client: unknown }).client = {
|
|
863
|
+
chat: {
|
|
864
|
+
completions: {
|
|
865
|
+
create: async (params: unknown) => {
|
|
866
|
+
wireRequests.push(JSON.parse(JSON.stringify(params)));
|
|
867
|
+
return {
|
|
868
|
+
async *[Symbol.asyncIterator]() {
|
|
869
|
+
yield {
|
|
870
|
+
choices: [
|
|
871
|
+
{
|
|
872
|
+
delta: {
|
|
873
|
+
tool_calls: [
|
|
874
|
+
{
|
|
875
|
+
index: 0,
|
|
876
|
+
id: "call-1",
|
|
877
|
+
function: {
|
|
878
|
+
name: "select_pages",
|
|
879
|
+
arguments: '{"ids":[3]}',
|
|
880
|
+
},
|
|
881
|
+
},
|
|
882
|
+
],
|
|
883
|
+
},
|
|
884
|
+
finish_reason: "tool_calls",
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
usage: { prompt_tokens: 10, completion_tokens: 2 },
|
|
888
|
+
};
|
|
889
|
+
},
|
|
890
|
+
};
|
|
891
|
+
},
|
|
892
|
+
},
|
|
893
|
+
},
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
// The profile layer injects the thinking effort onto the call config in
|
|
897
|
+
// production; emulate that here so the forced tool_choice rides alongside
|
|
898
|
+
// reasoning on the wire.
|
|
899
|
+
providerStub = {
|
|
900
|
+
name: "kimi-openai-compat",
|
|
901
|
+
sendMessage: (messages: Message[], options?: SendMessageOptions) =>
|
|
902
|
+
kimi.sendMessage(messages, {
|
|
903
|
+
...options,
|
|
904
|
+
config: {
|
|
905
|
+
...options?.config,
|
|
906
|
+
effort: "high",
|
|
907
|
+
thinking: { enabled: true },
|
|
908
|
+
},
|
|
909
|
+
}),
|
|
910
|
+
};
|
|
911
|
+
|
|
912
|
+
const selection = await selectPool(makePool(), makeTurn("rollout?"));
|
|
913
|
+
|
|
914
|
+
// The preflight path avoids an incompatible first request, and the
|
|
915
|
+
// pool-level re-prompt loop never engages.
|
|
916
|
+
expect(wireRequests).toHaveLength(1);
|
|
917
|
+
const first = wireRequests[0] as {
|
|
918
|
+
tool_choice?: unknown;
|
|
919
|
+
reasoning?: { effort?: string; summary?: string };
|
|
920
|
+
};
|
|
921
|
+
expect(first.tool_choice).toBeUndefined();
|
|
922
|
+
expect(first.reasoning).toMatchObject({
|
|
923
|
+
effort: "high",
|
|
924
|
+
summary: "detailed",
|
|
925
|
+
});
|
|
926
|
+
expect(
|
|
927
|
+
warnPayloads().filter((p) => p.reason === "provider_error"),
|
|
928
|
+
).toEqual([]);
|
|
929
|
+
|
|
930
|
+
// Structured selection survived: ids [3] is the topic-x finder line.
|
|
931
|
+
expect(selection.keptAll).toBe(false);
|
|
932
|
+
expect(selection.pages).toEqual([{ slug: "topic-x", sections: [] }]);
|
|
933
|
+
});
|
|
934
|
+
});
|
|
@@ -16,6 +16,10 @@ import { getConfig } from "../../../config/loader.js";
|
|
|
16
16
|
import { isMemoryEnabled } from "../../../config/memory-v3-gate.js";
|
|
17
17
|
import { rehydratePlatformCredentials } from "../../../config/platform-rehydration.js";
|
|
18
18
|
import { startConversationEvictor } from "../../../daemon/conversation-evictor.js";
|
|
19
|
+
import {
|
|
20
|
+
startWorkspaceSkillMdMtimePoll,
|
|
21
|
+
stopWorkspaceSkillMdMtimePoll,
|
|
22
|
+
} from "../../../daemon/skill-memory-refresh.js";
|
|
19
23
|
import { resetDb } from "../../../persistence/db-connection.js";
|
|
20
24
|
import {
|
|
21
25
|
EMBEDDING_SHUTDOWN_BUDGET_MS,
|
|
@@ -108,6 +112,7 @@ async function main(): Promise<void> {
|
|
|
108
112
|
shuttingDown = true;
|
|
109
113
|
log.info({ signal }, "Memory worker process shutting down");
|
|
110
114
|
worker?.stop();
|
|
115
|
+
stopWorkspaceSkillMdMtimePoll();
|
|
111
116
|
if (keepAlive != null) {
|
|
112
117
|
clearInterval(keepAlive);
|
|
113
118
|
}
|
|
@@ -161,6 +166,7 @@ async function main(): Promise<void> {
|
|
|
161
166
|
}
|
|
162
167
|
|
|
163
168
|
worker = startMemoryJobsWorkerLoop();
|
|
169
|
+
startWorkspaceSkillMdMtimePoll();
|
|
164
170
|
|
|
165
171
|
// Keep-alive: the worker's setTimeout timers are unref'd, so without
|
|
166
172
|
// this interval the process would exit immediately.
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* This module reads those files and projects each entry onto the
|
|
15
15
|
* assistant's own `McpServerConfig` shape, so a plugin-declared server can
|
|
16
16
|
* flow through the same surfaces as one configured in the workspace
|
|
17
|
-
* `
|
|
17
|
+
* `mcp.json`.
|
|
18
18
|
*
|
|
19
19
|
* Failure isolation follows the spec: an invalid top-level `mcp.json`
|
|
20
20
|
* disables MCP for that plugin only, and an invalid individual server
|
|
@@ -39,17 +39,17 @@
|
|
|
39
39
|
import { existsSync, readFileSync } from "node:fs";
|
|
40
40
|
import { join } from "node:path";
|
|
41
41
|
|
|
42
|
-
import { z } from "zod";
|
|
43
|
-
|
|
44
42
|
import {
|
|
45
43
|
type AllPluginInfo,
|
|
46
44
|
listAllPlugins,
|
|
47
45
|
type ListInstalledPluginsOptions,
|
|
48
46
|
} from "../cli/lib/list-installed-plugins.js";
|
|
49
|
-
import type {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
import type { ResolvedMcpServerConfig } from "../config/schemas/mcp.js";
|
|
48
|
+
import {
|
|
49
|
+
projectSpecServerToTransport,
|
|
50
|
+
SpecMcpDocumentSchema,
|
|
51
|
+
SpecMcpServerSchema,
|
|
52
|
+
} from "../mcp/spec-schema.js";
|
|
53
53
|
import { getLogger } from "../util/logger.js";
|
|
54
54
|
|
|
55
55
|
const log = getLogger("plugin-mcp-servers");
|
|
@@ -77,45 +77,6 @@ export const PLUGIN_MCP_MANIFEST = "mcp.json";
|
|
|
77
77
|
* origin risk.
|
|
78
78
|
*/
|
|
79
79
|
|
|
80
|
-
// ---------------------------------------------------------------------------
|
|
81
|
-
// Wire schema (Agent Plugins 1.0.0)
|
|
82
|
-
// ---------------------------------------------------------------------------
|
|
83
|
-
|
|
84
|
-
const PluginStdioServerSchema = z.object({
|
|
85
|
-
type: z.literal("stdio"),
|
|
86
|
-
command: z.string().min(1),
|
|
87
|
-
args: z.array(z.string()).optional(),
|
|
88
|
-
env: z.record(z.string(), z.string()).optional(),
|
|
89
|
-
cwd: z.string().optional(),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Remote MCP entry. Agent Plugins 1.0.0 requires `type` and names no
|
|
94
|
-
* default. MCP's current remote transport is Streamable HTTP, so this
|
|
95
|
-
* host fills omitted `type` and the Claude-style `http` alias as
|
|
96
|
-
* `streamable-http`. `sse` stays explicit.
|
|
97
|
-
*/
|
|
98
|
-
const PluginHttpServerSchema = z.object({
|
|
99
|
-
type: z
|
|
100
|
-
.union([
|
|
101
|
-
z.literal("streamable-http"),
|
|
102
|
-
z.literal("sse"),
|
|
103
|
-
z.literal("http").transform(() => "streamable-http" as const),
|
|
104
|
-
])
|
|
105
|
-
.default("streamable-http"),
|
|
106
|
-
url: z.string().min(1),
|
|
107
|
-
headers: z.record(z.string(), z.string()).optional(),
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
const PluginMcpServerSchema = z.union([
|
|
111
|
-
PluginStdioServerSchema,
|
|
112
|
-
PluginHttpServerSchema,
|
|
113
|
-
]);
|
|
114
|
-
|
|
115
|
-
const PluginMcpManifestSchema = z.object({
|
|
116
|
-
mcpServers: z.record(z.string(), z.unknown()),
|
|
117
|
-
});
|
|
118
|
-
|
|
119
80
|
// ---------------------------------------------------------------------------
|
|
120
81
|
// Public shape
|
|
121
82
|
// ---------------------------------------------------------------------------
|
|
@@ -251,7 +212,7 @@ export function readPluginMcpServers(
|
|
|
251
212
|
continue;
|
|
252
213
|
}
|
|
253
214
|
|
|
254
|
-
const entry =
|
|
215
|
+
const entry = SpecMcpServerSchema.safeParse(raw);
|
|
255
216
|
if (!entry.success) {
|
|
256
217
|
issues.push({
|
|
257
218
|
pluginName: plugin.name,
|
|
@@ -325,7 +286,7 @@ function parseManifest(
|
|
|
325
286
|
};
|
|
326
287
|
}
|
|
327
288
|
|
|
328
|
-
const manifest =
|
|
289
|
+
const manifest = SpecMcpDocumentSchema.safeParse(json);
|
|
329
290
|
if (!manifest.success) {
|
|
330
291
|
return {
|
|
331
292
|
error: `${PLUGIN_MCP_MANIFEST} is missing a valid "mcpServers" object`,
|
|
@@ -334,33 +295,12 @@ function parseManifest(
|
|
|
334
295
|
return { mcpServers: manifest.data.mcpServers };
|
|
335
296
|
}
|
|
336
297
|
|
|
337
|
-
/**
|
|
338
|
-
* Project one spec-shaped entry onto the assistant's transport union. The
|
|
339
|
-
* two vocabularies already agree on type names and required fields, so this
|
|
340
|
-
* is a field copy plus path interpolation for the stdio case.
|
|
341
|
-
*/
|
|
342
298
|
function projectTransport(
|
|
343
|
-
entry:
|
|
299
|
+
entry: Parameters<typeof projectSpecServerToTransport>[0],
|
|
344
300
|
pluginRoot: string,
|
|
345
|
-
)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
type: "stdio",
|
|
352
|
-
command: entry.command,
|
|
353
|
-
args: (entry.args ?? []).map(expand),
|
|
354
|
-
...(entry.env && {
|
|
355
|
-
env: Object.fromEntries(
|
|
356
|
-
Object.entries(entry.env).map(([k, v]) => [k, expand(v)]),
|
|
357
|
-
),
|
|
358
|
-
}),
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
return {
|
|
362
|
-
type: entry.type,
|
|
363
|
-
url: entry.url,
|
|
364
|
-
...(entry.headers && { headers: entry.headers }),
|
|
365
|
-
};
|
|
301
|
+
) {
|
|
302
|
+
const pluginData = join(pluginRoot, "data");
|
|
303
|
+
return projectSpecServerToTransport(entry, (value) =>
|
|
304
|
+
interpolatePluginPaths(value, pluginRoot, pluginData),
|
|
305
|
+
);
|
|
366
306
|
}
|
|
@@ -50,6 +50,8 @@ describe("isConnectionCompatibleWithModel", () => {
|
|
|
50
50
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-nano")).toBe(false);
|
|
51
51
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.5-pro")).toBe(false);
|
|
52
52
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.3-codex")).toBe(false);
|
|
53
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-5.4")).toBe(false);
|
|
54
|
+
expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-mini")).toBe(false);
|
|
53
55
|
});
|
|
54
56
|
|
|
55
57
|
test("oauth_subscription connection is compatible with a Codex model", () => {
|
|
@@ -59,8 +61,6 @@ describe("isConnectionCompatibleWithModel", () => {
|
|
|
59
61
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-terra")).toBe(true);
|
|
60
62
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.6-luna")).toBe(true);
|
|
61
63
|
expect(isConnectionCompatibleWithModel(conn, "gpt-5.5")).toBe(true);
|
|
62
|
-
expect(isConnectionCompatibleWithModel(conn, "gpt-5.4")).toBe(true);
|
|
63
|
-
expect(isConnectionCompatibleWithModel(conn, "gpt-5.4-mini")).toBe(true);
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test("undefined model applies no gating (compatible)", () => {
|
|
@@ -165,7 +165,7 @@ describe("auto-resolution skips oauth_subscription connections for non-Codex mod
|
|
|
165
165
|
|
|
166
166
|
test("Codex model can select the oauth_subscription connection", async () => {
|
|
167
167
|
registerConnections([OPENAI_CODEX, OPENAI_KEY]);
|
|
168
|
-
setOpenAiProfile("gpt-5.
|
|
168
|
+
setOpenAiProfile("gpt-5.5");
|
|
169
169
|
|
|
170
170
|
const result = await getConfiguredProvider("mainAgent", {
|
|
171
171
|
overrideProfile: "openai-any",
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
keepFileAsWorkspaceRef,
|
|
16
16
|
} from "../content-block-size.js";
|
|
17
17
|
import { fileBlockToProviderText } from "../file-block-text.js";
|
|
18
|
+
import { unsignedThoughtSignatureFallback } from "../gemini-thought-signature.js";
|
|
18
19
|
import { base64Source, resolveMediaReferences } from "../media-resolve.js";
|
|
19
20
|
import { PROVIDER_CATALOG } from "../model-catalog.js";
|
|
20
21
|
import { recordProviderRequestDiagnostics } from "../request-diagnostics.js";
|
|
@@ -45,13 +46,6 @@ import {
|
|
|
45
46
|
const GEMINI_CONTEXT_OVERFLOW_TOKEN_PATTERNS =
|
|
46
47
|
/token.?count.*exceeds|exceeds.*maximum.*tokens|prompt.?is.?too.?long|too.?many.?(?:input.?)?tokens|input.?too.?long|context.?length.?exceeded/i;
|
|
47
48
|
|
|
48
|
-
const GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE =
|
|
49
|
-
"context_engineering_is_the_way_to_go";
|
|
50
|
-
|
|
51
|
-
function isGemini3Model(model: string): boolean {
|
|
52
|
-
return model.startsWith("gemini-3") || model.startsWith("models/gemini-3");
|
|
53
|
-
}
|
|
54
|
-
|
|
55
49
|
const THINKING_LEVEL_BY_NAME: Record<ThinkingLevelName, ThinkingLevel> = {
|
|
56
50
|
minimal: ThinkingLevel.MINIMAL,
|
|
57
51
|
low: ThinkingLevel.LOW,
|
|
@@ -435,7 +429,8 @@ export class GeminiProvider implements Provider {
|
|
|
435
429
|
const maxTokens = configObj?.max_tokens as number | undefined;
|
|
436
430
|
const modelOverride = configObj?.model as string | undefined;
|
|
437
431
|
const usageAttributionHeaders = configObj?.usageAttributionHeaders as
|
|
438
|
-
Record<string, string>
|
|
432
|
+
| Record<string, string>
|
|
433
|
+
| undefined;
|
|
439
434
|
const activeModel = modelOverride ?? this.model;
|
|
440
435
|
const thinkingConfig = geminiModelSupportsThinking(activeModel)
|
|
441
436
|
? buildThinkingConfig(
|
|
@@ -902,28 +897,19 @@ export class GeminiProvider implements Provider {
|
|
|
902
897
|
parts: genai.Part[],
|
|
903
898
|
model: string,
|
|
904
899
|
): void {
|
|
905
|
-
if (!isGemini3Model(model)) {
|
|
906
|
-
return;
|
|
907
|
-
}
|
|
908
|
-
|
|
909
900
|
const functionCallParts = parts.filter((part) => part.functionCall);
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
const hasRealThoughtSignature = functionCallParts.some((part) =>
|
|
915
|
-
Boolean(part.thoughtSignature),
|
|
901
|
+
const fallback = unsignedThoughtSignatureFallback(
|
|
902
|
+
functionCallParts.map((part) => part.thoughtSignature),
|
|
903
|
+
{ model },
|
|
916
904
|
);
|
|
917
|
-
if (
|
|
905
|
+
if (!fallback) {
|
|
918
906
|
return;
|
|
919
907
|
}
|
|
920
|
-
|
|
921
|
-
const firstFunctionCallPart = functionCallParts[0];
|
|
908
|
+
const firstFunctionCallPart = functionCallParts[fallback.index];
|
|
922
909
|
if (!firstFunctionCallPart) {
|
|
923
910
|
return;
|
|
924
911
|
}
|
|
925
|
-
firstFunctionCallPart.thoughtSignature =
|
|
926
|
-
GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE;
|
|
912
|
+
firstFunctionCallPart.thoughtSignature = fallback.signature;
|
|
927
913
|
}
|
|
928
914
|
|
|
929
915
|
private supportsGeminiInlineFile(mimeType: string): boolean {
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
|
|
5
|
+
isGemini3Model,
|
|
6
|
+
unsignedThoughtSignatureFallback,
|
|
7
|
+
} from "./gemini-thought-signature.js";
|
|
8
|
+
|
|
9
|
+
describe("isGemini3Model", () => {
|
|
10
|
+
test("matches native Gemini 3 ids", () => {
|
|
11
|
+
expect(isGemini3Model("gemini-3.7-flash")).toBe(true);
|
|
12
|
+
expect(isGemini3Model("models/gemini-3.1-pro-preview")).toBe(true);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test("matches OpenAI-compatible catalog prefixes", () => {
|
|
16
|
+
expect(isGemini3Model("google/gemini-3.7-flash")).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("does not match Gemini 2.5 or unrelated ids", () => {
|
|
20
|
+
expect(isGemini3Model("gemini-2.5-flash")).toBe(false);
|
|
21
|
+
expect(isGemini3Model("gpt-5.2")).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("unsignedThoughtSignatureFallback", () => {
|
|
26
|
+
test("stamps the dummy on the first unsigned Gemini 3 call", () => {
|
|
27
|
+
expect(
|
|
28
|
+
unsignedThoughtSignatureFallback([undefined, undefined], {
|
|
29
|
+
model: "gemini-3.7-flash",
|
|
30
|
+
}),
|
|
31
|
+
).toEqual({
|
|
32
|
+
index: 0,
|
|
33
|
+
signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test("does not stamp when any call already has a signature", () => {
|
|
38
|
+
expect(
|
|
39
|
+
unsignedThoughtSignatureFallback(["signed-thought-1", undefined], {
|
|
40
|
+
model: "gemini-3.7-flash",
|
|
41
|
+
}),
|
|
42
|
+
).toBeUndefined();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("does not stamp for non-Gemini-3 models when a model id is provided", () => {
|
|
46
|
+
expect(
|
|
47
|
+
unsignedThoughtSignatureFallback([undefined], { model: "gpt-5.2" }),
|
|
48
|
+
).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("stamps unsigned calls when no model gate is supplied", () => {
|
|
52
|
+
expect(unsignedThoughtSignatureFallback([undefined])).toEqual({
|
|
53
|
+
index: 0,
|
|
54
|
+
signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("does not stamp an empty function-call list", () => {
|
|
59
|
+
expect(
|
|
60
|
+
unsignedThoughtSignatureFallback([], { model: "gemini-3.7-flash" }),
|
|
61
|
+
).toBeUndefined();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gemini 3.x requires a thought signature on the first function-call part of
|
|
3
|
+
* each step. When history has no captured signature (unsigned tool_use from an
|
|
4
|
+
* earlier provider, or a client-injected call), Google documents these dummy
|
|
5
|
+
* values to skip validation.
|
|
6
|
+
*/
|
|
7
|
+
export const GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE =
|
|
8
|
+
"context_engineering_is_the_way_to_go";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* True for Gemini 3.x model ids, including the native `models/` prefix and
|
|
12
|
+
* OpenAI-compatible catalog prefixes such as `google/gemini-3.7-flash`.
|
|
13
|
+
*/
|
|
14
|
+
export function isGemini3Model(model: string): boolean {
|
|
15
|
+
if (model.startsWith("gemini-3") || model.startsWith("models/gemini-3")) {
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
return /\/gemini-3/.test(model);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decide whether unsigned function-call history should receive Google's
|
|
23
|
+
* documented dummy thought signature. Returns the first call's index when
|
|
24
|
+
* every call is unsigned. Pass `model` to restrict this to Gemini 3.x;
|
|
25
|
+
* omit it for retry backfills that already observed a thought-signature 4xx.
|
|
26
|
+
*/
|
|
27
|
+
export function unsignedThoughtSignatureFallback(
|
|
28
|
+
signatures: ReadonlyArray<string | undefined | null>,
|
|
29
|
+
options?: { model?: string },
|
|
30
|
+
): { index: 0; signature: string } | undefined {
|
|
31
|
+
if (options?.model !== undefined && !isGemini3Model(options.model)) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (signatures.length === 0) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
if (signatures.some((signature) => Boolean(signature))) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
index: 0,
|
|
42
|
+
signature: GEMINI_3_UNSIGNED_TOOL_CALL_THOUGHT_SIGNATURE,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -76,6 +76,13 @@ export interface CatalogModel {
|
|
|
76
76
|
supportsAudioInput?: boolean;
|
|
77
77
|
supportsToolUse?: boolean;
|
|
78
78
|
supportsEffort?: boolean;
|
|
79
|
+
/**
|
|
80
|
+
* Whether this provider/model serving surface accepts a forced OpenAI
|
|
81
|
+
* chat-completions tool choice while thinking is enabled. Omit unless the
|
|
82
|
+
* combination is known incompatible. Daemon-only: not projected into the
|
|
83
|
+
* client catalog (see scripts/sync-llm-catalog.ts).
|
|
84
|
+
*/
|
|
85
|
+
supportsForcedToolChoiceWithThinking?: boolean;
|
|
79
86
|
pricing?: CatalogModelPricing;
|
|
80
87
|
/**
|
|
81
88
|
* Upper bound for `reasoning_effort` accepted by this model's upstream API.
|
|
@@ -856,6 +863,9 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
856
863
|
cacheReadPer1mTokens: 0.03,
|
|
857
864
|
},
|
|
858
865
|
},
|
|
866
|
+
// Limited to grandfathered accounts: other API keys get HTTP 404 "no
|
|
867
|
+
// longer available to new users", so this model is user-selectable
|
|
868
|
+
// only and no intent column may resolve to it.
|
|
859
869
|
{
|
|
860
870
|
id: "gemini-2.5-flash-lite",
|
|
861
871
|
displayName: "Gemini 2.5 Flash Lite",
|
|
@@ -1816,6 +1826,7 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
1816
1826
|
supportsCaching: true,
|
|
1817
1827
|
supportsVision: true,
|
|
1818
1828
|
supportsToolUse: true,
|
|
1829
|
+
supportsForcedToolChoiceWithThinking: false,
|
|
1819
1830
|
pricing: {
|
|
1820
1831
|
inputPer1mTokens: 0.95,
|
|
1821
1832
|
outputPer1mTokens: 4.0,
|
|
@@ -2628,6 +2639,29 @@ export function modelSupportedEfforts(
|
|
|
2628
2639
|
);
|
|
2629
2640
|
}
|
|
2630
2641
|
|
|
2642
|
+
/**
|
|
2643
|
+
* Whether a provider/model serving surface accepts a forced OpenAI
|
|
2644
|
+
* chat-completions tool choice while thinking is enabled. Unknown providers
|
|
2645
|
+
* and models fail open so custom routes retain their existing request shape
|
|
2646
|
+
* and can rely on the bounded provider-error retry if needed.
|
|
2647
|
+
*/
|
|
2648
|
+
export function supportsForcedToolChoiceWithThinking(
|
|
2649
|
+
providerId: string,
|
|
2650
|
+
modelId: string,
|
|
2651
|
+
): boolean {
|
|
2652
|
+
const provider = PROVIDER_CATALOG.find((entry) => entry.id === providerId);
|
|
2653
|
+
if (!provider) {
|
|
2654
|
+
return true;
|
|
2655
|
+
}
|
|
2656
|
+
const stripDateSuffix = (id: string): string => id.replace(/-\d{8}$/, "");
|
|
2657
|
+
const normalizedModelId = stripDateSuffix(modelId);
|
|
2658
|
+
return !provider.models.some(
|
|
2659
|
+
(model) =>
|
|
2660
|
+
model.supportsForcedToolChoiceWithThinking === false &&
|
|
2661
|
+
(model.id === modelId || stripDateSuffix(model.id) === normalizedModelId),
|
|
2662
|
+
);
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2631
2665
|
/**
|
|
2632
2666
|
* Return the catalog provider that owns a model ID, if known. When multiple
|
|
2633
2667
|
* providers list the same ID (e.g. OpenRouter and the Vercel AI Gateway share
|
|
@@ -11,10 +11,15 @@ const PROVIDER_DEFAULT_MODELS: Record<string, string> = Object.fromEntries(
|
|
|
11
11
|
);
|
|
12
12
|
|
|
13
13
|
// `cost-optimized` is the cheapest model a provider serves, `latency-optimized`
|
|
14
|
-
// the fastest to first token. On anthropic, ollama, fireworks
|
|
15
|
-
// same model is both, so those columns repeat by construction
|
|
16
|
-
// oversight (openai's balanced repeats it too: the
|
|
17
|
-
// split those tiers by reasoning effort, not model).
|
|
14
|
+
// the fastest to first token. On anthropic, ollama, fireworks, openai and
|
|
15
|
+
// gemini the same model is both, so those columns repeat by construction
|
|
16
|
+
// rather than by oversight (openai's balanced repeats it too: the
|
|
17
|
+
// default-profile templates split those tiers by reasoning effort, not model).
|
|
18
|
+
//
|
|
19
|
+
// "Serves" means serves to every API key: Google's `gemini-2.5-flash-lite`
|
|
20
|
+
// undercuts every 3.x Flash-Lite but is limited to grandfathered accounts
|
|
21
|
+
// (other keys get HTTP 404 "no longer available to new users"), so the
|
|
22
|
+
// gemini column skips it.
|
|
18
23
|
const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
19
24
|
anthropic: {
|
|
20
25
|
balanced: "claude-sonnet-4-6",
|
|
@@ -32,7 +37,7 @@ const PROVIDER_MODEL_INTENTS: Record<string, Record<ModelIntent, string>> = {
|
|
|
32
37
|
},
|
|
33
38
|
gemini: {
|
|
34
39
|
balanced: "gemini-3-flash-preview",
|
|
35
|
-
"cost-optimized": "gemini-
|
|
40
|
+
"cost-optimized": "gemini-3.1-flash-lite",
|
|
36
41
|
"latency-optimized": "gemini-3.1-flash-lite",
|
|
37
42
|
"quality-optimized": "gemini-3.1-pro-preview",
|
|
38
43
|
"vision-optimized": "gemini-3-flash-preview",
|