@vellumai/assistant 0.12.1 → 0.12.2-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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +20 -21
- package/docs/architecture/turn-actor.md +15 -8
- package/docs/guardian-request-flow.md +16 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +13 -8
- package/node_modules/@vellumai/slack-text/src/label-resolution-entities.test.ts +95 -0
- package/openapi.yaml +360 -51
- package/package.json +3 -1
- package/scripts/smoke-desktop-window-theme.ts +186 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +18 -10
- package/src/__tests__/anthropic-provider.test.ts +0 -63
- 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__/assistant-event-hub-machine-name.test.ts +6 -7
- package/src/__tests__/assistant-event-hub.test.ts +44 -0
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +1 -1
- package/src/__tests__/computer-use-skill-proxy-bridge.test.ts +5 -2
- package/src/__tests__/computer-use-tools.test.ts +19 -7
- 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-runtime-assembly.test.ts +28 -0
- 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-point-at-budget.test.ts +1 -0
- package/src/__tests__/conversation-surfaces-point-at-capability.test.ts +1 -0
- 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 +250 -3
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/cu-unified-flow.test.ts +14 -4
- package/src/__tests__/document-sync-tags.test.ts +25 -0
- 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__/heartbeat-service.test.ts +56 -40
- 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-cu-proxy.test.ts +596 -0
- 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__/list-messages-camera-frame.test.ts +121 -0
- 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__/notification-vellum-adapter.test.ts +85 -2
- package/src/__tests__/oauth-commands-routes.test.ts +168 -0
- package/src/__tests__/oauth-provider-profiles.test.ts +18 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +22 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/openai-provider.test.ts +1 -0
- package/src/__tests__/outlook-messaging-provider.test.ts +6 -6
- package/src/__tests__/platform-callback-registration.test.ts +8 -16
- package/src/__tests__/platform-client-verify-credential.test.ts +2 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +14 -0
- package/src/__tests__/qdrant-manager.test.ts +132 -0
- package/src/__tests__/resolve-app.test.ts +135 -0
- package/src/__tests__/scaffold-managed-skill-tool.test.ts +229 -6
- package/src/__tests__/script-proxy-certs.test.ts +35 -0
- package/src/__tests__/secret-routes-acp-guard.test.ts +59 -1
- package/src/__tests__/service-account-token.test.ts +247 -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__/skills.test.ts +9 -4
- package/src/__tests__/source-fingerprint.test.ts +184 -0
- package/src/__tests__/subagent-tool-gate-mode.test.ts +70 -3
- package/src/__tests__/subagent-tools.test.ts +3 -2
- package/src/__tests__/telephony-tts-guard.test.ts +38 -3
- package/src/__tests__/test-support/computer-use-skill-harness.ts +3 -2
- package/src/__tests__/ui-channel-variants.test.ts +1 -56
- package/src/__tests__/unicode.test.ts +13 -0
- 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__/acp-claude-oauth.test.ts +257 -6
- package/src/acp/__tests__/acp-credentials.test.ts +13 -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__/claude-token-refresh.test.ts +257 -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__/prepare-agent-env.test.ts +60 -1
- 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/acp-claude-oauth.ts +328 -14
- package/src/acp/acp-credentials.ts +19 -0
- package/src/acp/agent-process.test.ts +4 -1
- package/src/acp/agent-process.ts +96 -10
- package/src/acp/auth-required.ts +50 -1
- package/src/acp/auto-install.test.ts +9 -7
- package/src/acp/claude-token-refresh.ts +150 -0
- 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/prepare-agent-env.ts +21 -6
- 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/agent/loop.ts +1 -0
- 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/events/notification-intent.ts +2 -0
- package/src/api/events/user-message-echo.ts +7 -1
- package/src/api/index.ts +6 -0
- package/src/api/responses/conversation-message.ts +5 -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/avatar/identity-avatar.ts +7 -2
- 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 +120 -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 +56 -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 +155 -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 +92 -72
- package/src/calls/voice-triage-escalate.ts +178 -59
- package/src/cli/commands/__tests__/apps.test.ts +245 -0
- package/src/cli/commands/__tests__/bash.test.ts +53 -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/bash.help.ts +8 -13
- package/src/cli/commands/bash.ts +7 -25
- 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/oauth/status.ts +5 -0
- 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 +255 -0
- package/src/config/__tests__/platform-rehydration.test.ts +99 -9
- 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/computer-use/SKILL.md +29 -2
- package/src/config/bundled-skills/computer-use/TOOLS.json +99 -0
- package/src/config/bundled-skills/computer-use/tools/computer-use-sequence.ts +12 -0
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +2 -1
- package/src/config/bundled-skills/media-processing/services/processing-pipeline.ts +2 -1
- package/src/config/bundled-skills/media-processing/tools/generate-clip.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- 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/playbooks/tools/playbook-create.ts +2 -1
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +2 -1
- package/src/config/bundled-skills/schedule/SKILL.md +3 -4
- 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 +6 -2
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -2
- package/src/config/default-profile-catalog.ts +1 -1
- package/src/config/env.ts +2 -2
- package/src/config/feature-flag-registry.json +33 -13
- package/src/config/loader.ts +2 -0
- 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 +201 -0
- package/src/config/platform-rehydration.ts +11 -27
- 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/services.ts +18 -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/credential-health/credential-health-service.ts +11 -17
- 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-error.ts +3 -2
- package/src/daemon/conversation-media-retry.ts +2 -1
- package/src/daemon/conversation-process.ts +35 -64
- package/src/daemon/conversation-runtime-assembly.ts +20 -7
- package/src/daemon/conversation-skill-tools.ts +13 -31
- package/src/daemon/conversation-store.ts +1 -1
- package/src/daemon/conversation-surfaces.ts +21 -10
- package/src/daemon/conversation-tool-setup.ts +45 -59
- package/src/daemon/conversation.ts +0 -3
- package/src/daemon/dictation-profile-store.ts +2 -1
- package/src/daemon/handlers/shared.ts +2 -1
- package/src/daemon/handlers/skills.ts +6 -3
- package/src/daemon/host-bash-proxy.ts +8 -12
- package/src/daemon/host-browser-proxy.ts +15 -1
- package/src/daemon/host-cu-proxy.ts +224 -19
- package/src/daemon/host-file-proxy.ts +8 -10
- package/src/daemon/host-proxy-preactivation.ts +2 -1
- 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/orphan-reaper.ts +1 -1
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/reaction-history-render.ts +2 -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 +122 -0
- package/src/desktop/desktop-chrome-policy.test.ts +79 -0
- package/src/desktop/desktop-chrome-policy.ts +47 -0
- package/src/desktop/desktop-chrome-session.ts +37 -0
- package/src/desktop/desktop-dependencies.test.ts +102 -0
- package/src/desktop/desktop-dependencies.ts +308 -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 +730 -0
- package/src/desktop/desktop-session-manager.ts +850 -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-drag.ts +177 -0
- package/src/desktop/desktop-window-manager-config.test.ts +230 -0
- package/src/desktop/desktop-window-manager-config.ts +238 -0
- package/src/desktop/desktop-window-theme.ts +170 -0
- package/src/heartbeat/heartbeat-service.ts +21 -3
- package/src/home/__tests__/feed-writer.test.ts +22 -0
- package/src/home/feed-writer.ts +11 -2
- 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-agent-turn.test.ts +121 -0
- package/src/live-voice/__tests__/live-voice-events.test.ts +8 -7
- package/src/live-voice/__tests__/live-voice-metrics.test.ts +0 -3
- package/src/live-voice/__tests__/live-voice-progress.test.ts +94 -15
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +238 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +2 -2
- package/src/live-voice/__tests__/protocol.test.ts +39 -0
- package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +13 -69
- package/src/live-voice/__tests__/session-controls.test.ts +79 -0
- package/src/live-voice/live-voice-photo.ts +25 -6
- package/src/live-voice/live-voice-session.ts +297 -699
- package/src/live-voice/protocol.ts +60 -2
- package/src/live-voice/session-controls.ts +111 -0
- 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__/guardian-feed-projection.test.ts +158 -2
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +153 -3
- package/src/notifications/__tests__/schedule-result-producer.test.ts +99 -3
- package/src/notifications/adapters/macos.ts +3 -0
- 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/guardian-feed-projection.ts +125 -5
- package/src/notifications/home-feed-side-effect.ts +55 -7
- package/src/notifications/schedule-result-producer.ts +66 -22
- package/src/oauth/AGENTS.md +2 -0
- package/src/oauth/__tests__/seed-providers-managed.test.ts +149 -0
- package/src/oauth/byo-connection.test.ts +105 -0
- package/src/oauth/byo-connection.ts +36 -6
- package/src/oauth/connection-resolver.test.ts +100 -0
- package/src/oauth/connection-resolver.ts +120 -3
- package/src/oauth/provider-serializer.ts +9 -0
- package/src/oauth/scope-utils.ts +24 -0
- package/src/oauth/seed-providers.ts +314 -27
- 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-queries.ts +2 -2
- package/src/persistence/conversation-title-placeholders.ts +23 -0
- package/src/persistence/conversation-title-service.ts +9 -26
- package/src/persistence/embeddings/qdrant-manager.ts +163 -18
- 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 +4 -2
- 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-prompt.test.ts +5 -0
- 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/context-search/agent-runner.ts +3 -2
- package/src/plugins/defaults/memory/context-search/format.ts +3 -2
- package/src/plugins/defaults/memory/context-search/sources/memory-v2.ts +6 -3
- package/src/plugins/defaults/memory/context-search/sources/workspace.ts +3 -2
- package/src/plugins/defaults/memory/graph/capability-seed.ts +6 -7
- 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/host-utils.ts +10 -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-prompt.ts +1 -1
- 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 +28 -2
- package/src/plugins/defaults/memory/substrate/__tests__/skill-content.test.ts +7 -9
- package/src/plugins/defaults/memory/substrate/page-index.ts +6 -1
- package/src/plugins/defaults/memory/substrate/skill-content.ts +2 -2
- package/src/plugins/defaults/memory/v1/graph/consolidation.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/extraction.ts +4 -2
- package/src/plugins/defaults/memory/v1/graph/retriever.ts +4 -1
- package/src/plugins/defaults/memory/v2/reranker.ts +6 -1
- package/src/plugins/defaults/memory/v3/__tests__/card.test.ts +24 -0
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +125 -0
- package/src/plugins/defaults/memory/v3/__tests__/sections.test.ts +26 -0
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +30 -0
- package/src/plugins/defaults/memory/v3/card.ts +3 -2
- package/src/plugins/defaults/memory/v3/pool-select.ts +5 -3
- package/src/plugins/defaults/memory/v3/sections.ts +9 -3
- package/src/plugins/defaults/memory/v3/shadow-plugin.ts +5 -2
- 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/__tests__/retry-callsite.test.ts +41 -0
- package/src/providers/__tests__/retry-outbound-sanitize.test.ts +176 -0
- package/src/providers/anthropic/client.ts +0 -47
- 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/inference/__tests__/endpoint-probe.test.ts +42 -3
- package/src/providers/inference/auth.ts +2 -3
- package/src/providers/inference/connection-availability.ts +27 -8
- package/src/providers/inference/endpoint-probe.ts +10 -1
- package/src/providers/inference/resolve-auth.ts +22 -4
- package/src/providers/inference/service-account-token.ts +248 -0
- package/src/providers/model-catalog.ts +34 -0
- package/src/providers/model-intents.ts +10 -5
- package/src/providers/openai/__tests__/chat-completions-output-token-limit.test.ts +141 -0
- 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 +212 -79
- package/src/providers/openai/codex-models.ts +0 -4
- package/src/providers/openai/google-thought-signature.ts +320 -0
- package/src/providers/opencode/client.test.ts +73 -4
- package/src/providers/opencode/client.ts +60 -3
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/outbound-request-sanitize.ts +96 -0
- package/src/providers/retry.ts +30 -19
- package/src/providers/unparseable-tool-args.ts +5 -1
- package/src/runtime/AGENTS.md +3 -1
- 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__/host-observe.test.ts +40 -1
- 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/agent-wake.ts +2 -1
- package/src/runtime/assistant-event-hub.ts +21 -3
- package/src/runtime/auth/same-actor.ts +38 -3
- package/src/runtime/channel-send.ts +316 -0
- package/src/runtime/host-observe.ts +11 -1
- package/src/runtime/http-server.ts +160 -151
- package/src/runtime/routes/__tests__/acp-claude-auth-routes.test.ts +16 -5
- package/src/runtime/routes/__tests__/acp-routes.test.ts +159 -4
- package/src/runtime/routes/__tests__/apps-refresh-route.test.ts +275 -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__/default-provider-routes.test.ts +2 -2
- 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-claude-auth-routes.ts +11 -3
- 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 +90 -2
- 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/conversation-routes.ts +16 -3
- package/src/runtime/routes/credential-routes.ts +20 -14
- 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 +61 -4
- package/src/runtime/routes/events-routes.ts +4 -0
- package/src/runtime/routes/host-cu-routes.ts +6 -0
- package/src/runtime/routes/index.ts +4 -2
- 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 +6 -20
- package/src/runtime/routes/oauth-commands-routes.ts +50 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/platform-routes.ts +18 -15
- package/src/runtime/routes/workspace-routes.test.ts +55 -26
- package/src/runtime/routes/workspace-routes.ts +46 -63
- package/src/runtime/routes/workspace-tree-walk.test.ts +244 -0
- package/src/runtime/routes/workspace-tree-walk.ts +240 -0
- package/src/runtime/routes/workspace-utils.ts +17 -0
- 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/run-script.ts +2 -1
- package/src/schedule/schedule-store.ts +9 -2
- 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/security/untrusted-content.ts +3 -1
- 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/__tests__/side-effect-abort-guard.test.ts +1 -0
- 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/browser/browser-execution.ts +1 -1
- package/src/tools/browser/cdp-client/accessibility-snapshot.ts +9 -3
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +2 -1
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +3 -2
- package/src/tools/browser/network-recorder.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 +121 -2
- package/src/tools/computer-use/skill-proxy-bridge.ts +2 -1
- package/src/tools/credentials/metadata-store.ts +100 -0
- package/src/tools/filesystem/search.ts +2 -1
- 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/network/web-search.ts +6 -1
- package/src/tools/policy-context.ts +6 -7
- package/src/tools/skills/scaffold-managed.ts +61 -3
- package/src/tools/subagent/spawn.ts +5 -8
- package/src/tools/terminal/__tests__/sanitized-bash.test.ts +57 -0
- package/src/tools/terminal/sanitized-bash.ts +105 -0
- package/src/tools/ui-surface/channel-variants.ts +10 -59
- package/src/tools/watch/watch-retro-report.ts +0 -8
- package/src/types/host-capabilities.ts +5 -3
- 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/util/unicode.ts +15 -1
- package/src/watch/watch-retro.ts +6 -8
- package/src/watch/watch-timeline.ts +2 -2
- package/src/workflows/run-manager.ts +2 -1
- package/src/workspace/git-porcelain.ts +66 -0
- package/src/workspace/git-service.ts +6 -55
- 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/runtime/routes/debug-bash-routes.ts +0 -173
- package/src/util/fs-watcher-error.ts +0 -36
- /package/src/{live-voice → calls}/progress-phrases.ts +0 -0
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - platform → build managed proxy URL and fetch the platform API key
|
|
7
7
|
* - none → pass through with no auth headers
|
|
8
8
|
* - oauth_subscription → fetch OAuth token from vault (with auto-refresh) → inject as bearer header
|
|
9
|
-
* - service_account →
|
|
9
|
+
* - service_account → sign JWT and exchange for bearer token at token_uri (Google Vertex AI)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { getSecureKeyAsync } from "../../security/secure-keys.js";
|
|
@@ -119,10 +119,28 @@ export async function resolveAuth(
|
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
case "service_account":
|
|
122
|
+
case "service_account": {
|
|
123
|
+
const { getValidServiceAccountToken } =
|
|
124
|
+
await import("./service-account-token.js");
|
|
125
|
+
const result = await getValidServiceAccountToken(auth.credential);
|
|
126
|
+
if (!result.ok) {
|
|
127
|
+
if (result.reason === "exchange_failed") {
|
|
128
|
+
return { ok: false, error: { code: "platform_unavailable" } };
|
|
129
|
+
}
|
|
130
|
+
// not_found or invalid_config: the credential needs to be supplied or fixed.
|
|
131
|
+
return {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: { code: "credential_not_found", credential: auth.credential },
|
|
134
|
+
};
|
|
135
|
+
}
|
|
123
136
|
return {
|
|
124
|
-
ok:
|
|
125
|
-
|
|
137
|
+
ok: true,
|
|
138
|
+
resolved: {
|
|
139
|
+
kind: "header",
|
|
140
|
+
headers: { Authorization: `Bearer ${result.token}` },
|
|
141
|
+
...(safeBaseUrl ? { baseUrl: safeBaseUrl } : {}),
|
|
142
|
+
},
|
|
126
143
|
};
|
|
144
|
+
}
|
|
127
145
|
}
|
|
128
146
|
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token acquisition for Google service-account credentials (Vertex AI).
|
|
3
|
+
*
|
|
4
|
+
* Reads the service-account JSON from the vault, signs a short-lived JWT
|
|
5
|
+
* with RS256, and exchanges it for a bearer access token at the token_uri
|
|
6
|
+
* embedded in the key file. Caches the resulting token and its expiry as a
|
|
7
|
+
* single JSON blob in the vault so the write is treated as one unit.
|
|
8
|
+
*
|
|
9
|
+
* A per-credential mutex prevents concurrent callers for the same credential
|
|
10
|
+
* from racing to refresh. Callers for different credentials are independent.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createSign } from "node:crypto";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getSecureKeyAsync as _realGetKey,
|
|
17
|
+
setSecureKeyAsync as _realSetKey,
|
|
18
|
+
} from "../../security/secure-keys.js";
|
|
19
|
+
import { getLogger } from "../../util/logger.js";
|
|
20
|
+
|
|
21
|
+
// Mutable references — swapped in tests via _injectVaultAccessors so tests
|
|
22
|
+
// never need mock.module (which contaminates the entire bun worker process).
|
|
23
|
+
let _getKey: typeof _realGetKey = _realGetKey;
|
|
24
|
+
let _setKey: typeof _realSetKey = _realSetKey;
|
|
25
|
+
|
|
26
|
+
const log = getLogger("service-account-token");
|
|
27
|
+
|
|
28
|
+
/** Refresh 5 minutes before expiry to avoid using a nearly-expired token. */
|
|
29
|
+
const REFRESH_MARGIN_SECONDS = 300;
|
|
30
|
+
|
|
31
|
+
/** Google Cloud Platform scope required for Vertex AI inference. */
|
|
32
|
+
const GCP_SCOPE = "https://www.googleapis.com/auth/cloud-platform";
|
|
33
|
+
|
|
34
|
+
export interface ServiceAccountKey {
|
|
35
|
+
client_email: string;
|
|
36
|
+
private_key: string;
|
|
37
|
+
token_uri: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type ServiceAccountTokenResult =
|
|
41
|
+
| { ok: true; token: string }
|
|
42
|
+
| { ok: false; reason: "not_found" | "invalid_config" | "exchange_failed" };
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Parse and validate a service-account JSON string.
|
|
46
|
+
* Returns the key object when valid, or null when the JSON is missing, invalid,
|
|
47
|
+
* or lacks required fields (client_email, private_key, token_uri).
|
|
48
|
+
*/
|
|
49
|
+
export function parseServiceAccountKey(json: string): ServiceAccountKey | null {
|
|
50
|
+
let parsed: unknown;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(json);
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
if (!parsed || typeof parsed !== "object") {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const key = parsed as Record<string, unknown>;
|
|
60
|
+
if (
|
|
61
|
+
typeof key["client_email"] !== "string" ||
|
|
62
|
+
!key["client_email"] ||
|
|
63
|
+
typeof key["private_key"] !== "string" ||
|
|
64
|
+
!key["private_key"] ||
|
|
65
|
+
typeof key["token_uri"] !== "string" ||
|
|
66
|
+
!key["token_uri"]
|
|
67
|
+
) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
// All required fields validated as non-empty strings above.
|
|
71
|
+
return parsed as ServiceAccountKey;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Per-credential in-flight mutex. Prevents concurrent exchange races per account. */
|
|
75
|
+
const exchangesInFlight = new Map<string, Promise<ServiceAccountTokenResult>>();
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Return a valid bearer access token for a Google service account, fetching
|
|
79
|
+
* and caching one if the stored token is absent or about to expire.
|
|
80
|
+
*
|
|
81
|
+
* @param credential - Vault key under which the service-account JSON is stored
|
|
82
|
+
* (e.g. `"credential/my-vertex-ai"`). The derived token is cached as a JSON
|
|
83
|
+
* blob at `<credential>/token_cache`.
|
|
84
|
+
*/
|
|
85
|
+
export async function getValidServiceAccountToken(
|
|
86
|
+
credential: string,
|
|
87
|
+
): Promise<ServiceAccountTokenResult> {
|
|
88
|
+
const cacheKey = `${credential}/token_cache`;
|
|
89
|
+
const cached = await _getKey(cacheKey);
|
|
90
|
+
if (cached) {
|
|
91
|
+
try {
|
|
92
|
+
const blob = JSON.parse(cached) as {
|
|
93
|
+
access_token?: string;
|
|
94
|
+
expires_at?: number;
|
|
95
|
+
};
|
|
96
|
+
if (blob.access_token) {
|
|
97
|
+
if (!blob.expires_at) {
|
|
98
|
+
return { ok: true, token: blob.access_token };
|
|
99
|
+
}
|
|
100
|
+
const now = Date.now() / 1000;
|
|
101
|
+
if (now < blob.expires_at - REFRESH_MARGIN_SECONDS) {
|
|
102
|
+
return { ok: true, token: blob.access_token };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
// Corrupted cache entry — fall through to re-exchange.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const inFlight = exchangesInFlight.get(credential);
|
|
111
|
+
if (inFlight) {
|
|
112
|
+
return await inFlight;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const promise = doExchange(credential, cacheKey);
|
|
116
|
+
exchangesInFlight.set(credential, promise);
|
|
117
|
+
try {
|
|
118
|
+
return await promise;
|
|
119
|
+
} finally {
|
|
120
|
+
exchangesInFlight.delete(credential);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function doExchange(
|
|
125
|
+
credential: string,
|
|
126
|
+
cacheKey: string,
|
|
127
|
+
): Promise<ServiceAccountTokenResult> {
|
|
128
|
+
const keyJson = await _getKey(credential);
|
|
129
|
+
if (!keyJson) {
|
|
130
|
+
return { ok: false, reason: "not_found" };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const key = parseServiceAccountKey(keyJson);
|
|
134
|
+
if (!key) {
|
|
135
|
+
log.error(
|
|
136
|
+
{ credential },
|
|
137
|
+
"Service account credential is not valid JSON or missing required fields",
|
|
138
|
+
);
|
|
139
|
+
return { ok: false, reason: "invalid_config" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const jwt = buildJwt(key.client_email, key.private_key, key.token_uri);
|
|
143
|
+
|
|
144
|
+
let accessToken: string;
|
|
145
|
+
let expiresIn: number;
|
|
146
|
+
try {
|
|
147
|
+
const resp = await fetch(key.token_uri, {
|
|
148
|
+
method: "POST",
|
|
149
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
150
|
+
body: new URLSearchParams({
|
|
151
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
152
|
+
assertion: jwt,
|
|
153
|
+
}).toString(),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!resp.ok) {
|
|
157
|
+
const body = await resp.text();
|
|
158
|
+
log.error(
|
|
159
|
+
{ status: resp.status, body, credential },
|
|
160
|
+
"Service account token exchange failed",
|
|
161
|
+
);
|
|
162
|
+
return { ok: false, reason: "exchange_failed" };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const data = (await resp.json()) as {
|
|
166
|
+
access_token?: string;
|
|
167
|
+
expires_in?: number;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
if (!data.access_token) {
|
|
171
|
+
log.error({ credential }, "Token exchange response missing access_token");
|
|
172
|
+
return { ok: false, reason: "exchange_failed" };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
accessToken = data.access_token;
|
|
176
|
+
expiresIn = data.expires_in ?? 3600;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
log.error({ err, credential }, "Service account token exchange threw");
|
|
179
|
+
return { ok: false, reason: "exchange_failed" };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const newExpiresAt = Math.floor(Date.now() / 1000 + expiresIn);
|
|
183
|
+
const ok = await _setKey(
|
|
184
|
+
cacheKey,
|
|
185
|
+
JSON.stringify({ access_token: accessToken, expires_at: newExpiresAt }),
|
|
186
|
+
);
|
|
187
|
+
if (!ok) {
|
|
188
|
+
log.warn(
|
|
189
|
+
{ credential },
|
|
190
|
+
"Failed to cache service account token — token still valid for this request",
|
|
191
|
+
);
|
|
192
|
+
} else {
|
|
193
|
+
log.info({ credential }, "Service account token exchanged and cached");
|
|
194
|
+
}
|
|
195
|
+
return { ok: true, token: accessToken };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function buildJwt(
|
|
199
|
+
clientEmail: string,
|
|
200
|
+
privateKey: string,
|
|
201
|
+
tokenUri: string,
|
|
202
|
+
): string {
|
|
203
|
+
const now = Math.floor(Date.now() / 1000);
|
|
204
|
+
const header = Buffer.from(
|
|
205
|
+
JSON.stringify({ alg: "RS256", typ: "JWT" }),
|
|
206
|
+
).toString("base64url");
|
|
207
|
+
const payload = Buffer.from(
|
|
208
|
+
JSON.stringify({
|
|
209
|
+
iss: clientEmail,
|
|
210
|
+
scope: GCP_SCOPE,
|
|
211
|
+
aud: tokenUri,
|
|
212
|
+
exp: now + 3600,
|
|
213
|
+
iat: now,
|
|
214
|
+
}),
|
|
215
|
+
).toString("base64url");
|
|
216
|
+
|
|
217
|
+
const signingInput = `${header}.${payload}`;
|
|
218
|
+
const sign = createSign("SHA256");
|
|
219
|
+
sign.update(signingInput);
|
|
220
|
+
sign.end();
|
|
221
|
+
const signature = sign.sign(privateKey, "base64url");
|
|
222
|
+
|
|
223
|
+
return `${signingInput}.${signature}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
// Test helpers
|
|
228
|
+
// ---------------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
/** @internal Test-only: reset all in-flight exchange mutexes. */
|
|
231
|
+
export function _resetServiceAccountMutex(): void {
|
|
232
|
+
exchangesInFlight.clear();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** @internal Test-only: swap vault accessors so tests never need mock.module. */
|
|
236
|
+
export function _injectVaultAccessors(
|
|
237
|
+
get: typeof _realGetKey,
|
|
238
|
+
set: typeof _realSetKey,
|
|
239
|
+
): void {
|
|
240
|
+
_getKey = get;
|
|
241
|
+
_setKey = set;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** @internal Test-only: restore real vault accessors. */
|
|
245
|
+
export function _resetVaultAccessors(): void {
|
|
246
|
+
_getKey = _realGetKey;
|
|
247
|
+
_setKey = _realSetKey;
|
|
248
|
+
}
|
|
@@ -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",
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request-shape coverage for the output-token-limit wire key.
|
|
3
|
+
*
|
|
4
|
+
* The shared OpenAI-compatible transport defaults to `max_completion_tokens`.
|
|
5
|
+
* OpenRouter's parameter router matches `max_tokens` on `require_parameters`
|
|
6
|
+
* routes, except GPT-5.1/5.2 Codex and Chat which advertise only
|
|
7
|
+
* `max_completion_tokens`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
openRouterOutputTokenLimitField,
|
|
14
|
+
OpenRouterProvider,
|
|
15
|
+
} from "../../openrouter/client.js";
|
|
16
|
+
import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
|
|
17
|
+
|
|
18
|
+
type MockChunk = {
|
|
19
|
+
choices: Array<{
|
|
20
|
+
delta: { content?: string | null };
|
|
21
|
+
finish_reason?: string | null;
|
|
22
|
+
}>;
|
|
23
|
+
usage?: { prompt_tokens: number; completion_tokens: number };
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const OK_CHUNKS: MockChunk[] = [
|
|
27
|
+
{
|
|
28
|
+
choices: [{ delta: { content: "ok" }, finish_reason: "stop" }],
|
|
29
|
+
usage: { prompt_tokens: 1, completion_tokens: 1 },
|
|
30
|
+
},
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
function makeStream(chunks: MockChunk[]): AsyncIterable<MockChunk> {
|
|
34
|
+
return {
|
|
35
|
+
async *[Symbol.asyncIterator]() {
|
|
36
|
+
for (const c of chunks) {
|
|
37
|
+
yield c;
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function stubCreate(provider: OpenAIChatCompletionsProvider): {
|
|
44
|
+
requests: Record<string, unknown>[];
|
|
45
|
+
} {
|
|
46
|
+
const requests: Record<string, unknown>[] = [];
|
|
47
|
+
const inner = provider as unknown as {
|
|
48
|
+
client: {
|
|
49
|
+
chat: {
|
|
50
|
+
completions: {
|
|
51
|
+
create: (
|
|
52
|
+
params: Record<string, unknown>,
|
|
53
|
+
) => Promise<AsyncIterable<MockChunk>>;
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
inner.client.chat.completions.create = async (params) => {
|
|
59
|
+
requests.push(params);
|
|
60
|
+
return makeStream(OK_CHUNKS);
|
|
61
|
+
};
|
|
62
|
+
return { requests };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("chat-completions output-token-limit wire key", () => {
|
|
66
|
+
test("default OpenAI path sends max_completion_tokens and omits max_tokens", async () => {
|
|
67
|
+
const provider = new OpenAIChatCompletionsProvider(
|
|
68
|
+
"test-key",
|
|
69
|
+
"test-model",
|
|
70
|
+
);
|
|
71
|
+
const { requests } = stubCreate(provider);
|
|
72
|
+
|
|
73
|
+
await provider.sendMessage(
|
|
74
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
75
|
+
{ config: { max_tokens: 64000 } },
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
expect(requests).toHaveLength(1);
|
|
79
|
+
expect(requests[0].max_completion_tokens).toBe(64000);
|
|
80
|
+
expect(requests[0]).not.toHaveProperty("max_tokens");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("OpenRouter chat-completions path sends max_tokens and omits max_completion_tokens", async () => {
|
|
84
|
+
const provider = new OpenRouterProvider("or-key", "@preset/example");
|
|
85
|
+
const { requests } = stubCreate(provider);
|
|
86
|
+
|
|
87
|
+
await provider.sendMessage(
|
|
88
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
89
|
+
{ config: { max_tokens: 64000 } },
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(requests).toHaveLength(1);
|
|
93
|
+
expect(requests[0].max_tokens).toBe(64000);
|
|
94
|
+
expect(requests[0]).not.toHaveProperty("max_completion_tokens");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("OpenRouter GPT-5.2 Codex sends max_completion_tokens and omits max_tokens", async () => {
|
|
98
|
+
const provider = new OpenRouterProvider("or-key", "openai/gpt-5.2-codex");
|
|
99
|
+
const { requests } = stubCreate(provider);
|
|
100
|
+
|
|
101
|
+
await provider.sendMessage(
|
|
102
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
103
|
+
{ config: { max_tokens: 64000 } },
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
expect(requests).toHaveLength(1);
|
|
107
|
+
expect(requests[0].max_completion_tokens).toBe(64000);
|
|
108
|
+
expect(requests[0]).not.toHaveProperty("max_tokens");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("OpenRouter per-call model override uses the overridden model's wire key", async () => {
|
|
112
|
+
const provider = new OpenRouterProvider("or-key", "x-ai/grok-4.20");
|
|
113
|
+
const { requests } = stubCreate(provider);
|
|
114
|
+
|
|
115
|
+
await provider.sendMessage(
|
|
116
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
117
|
+
{ config: { model: "openai/gpt-5.1-codex-mini", max_tokens: 64000 } },
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
expect(requests).toHaveLength(1);
|
|
121
|
+
expect(requests[0].max_completion_tokens).toBe(64000);
|
|
122
|
+
expect(requests[0]).not.toHaveProperty("max_tokens");
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe("openRouterOutputTokenLimitField", () => {
|
|
127
|
+
test.each([
|
|
128
|
+
["@preset/example", "max_tokens"],
|
|
129
|
+
["x-ai/grok-4.20", "max_tokens"],
|
|
130
|
+
["openai/gpt-5.2", "max_tokens"],
|
|
131
|
+
["openai/gpt-5.2-pro", "max_tokens"],
|
|
132
|
+
["openai/gpt-5.3-codex", "max_tokens"],
|
|
133
|
+
["openai/gpt-5.2-codex", "max_completion_tokens"],
|
|
134
|
+
["openai/gpt-5.2-chat", "max_completion_tokens"],
|
|
135
|
+
["openai/gpt-5.1-codex", "max_completion_tokens"],
|
|
136
|
+
["openai/gpt-5.1-codex-max", "max_completion_tokens"],
|
|
137
|
+
["openai/gpt-5.1-codex-mini", "max_completion_tokens"],
|
|
138
|
+
] as const)("%s -> %s", (model, field) => {
|
|
139
|
+
expect(openRouterOutputTokenLimitField(model)).toBe(field);
|
|
140
|
+
});
|
|
141
|
+
});
|