@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
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isGemini3Model,
|
|
3
|
+
unsignedThoughtSignatureFallback,
|
|
4
|
+
} from "../gemini-thought-signature.js";
|
|
5
|
+
import type { Message, ToolUseContent } from "../types.js";
|
|
6
|
+
|
|
7
|
+
export type GoogleToolCallExtraContent = {
|
|
8
|
+
extra_content?: {
|
|
9
|
+
google?: {
|
|
10
|
+
thought_signature?: string;
|
|
11
|
+
};
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type GoogleThoughtSignatureRetryKind =
|
|
16
|
+
| "missing-thought-signature"
|
|
17
|
+
| "unknown-extra-content";
|
|
18
|
+
|
|
19
|
+
function asNonEmptyString(value: unknown): string | undefined {
|
|
20
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function googleObjectThoughtSignature(google: unknown): string | undefined {
|
|
24
|
+
if (google === null || typeof google !== "object") {
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
const record = google as Record<string, unknown>;
|
|
28
|
+
return (
|
|
29
|
+
asNonEmptyString(record.thought_signature) ??
|
|
30
|
+
asNonEmptyString(record.thoughtSignature)
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gemini's OpenAI-compatible wire format puts the thought signature on
|
|
36
|
+
* `tool_calls[].extra_content.google.thought_signature`.
|
|
37
|
+
*/
|
|
38
|
+
export function googleThoughtSignatureFromUnknown(
|
|
39
|
+
value: unknown,
|
|
40
|
+
): string | undefined {
|
|
41
|
+
if (value === null || typeof value !== "object") {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
const extra = (value as { extra_content?: unknown }).extra_content;
|
|
45
|
+
if (extra === null || typeof extra !== "object") {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
return googleObjectThoughtSignature((extra as { google?: unknown }).google);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function toolUseMetadataFromChatCompletionsDelta(
|
|
52
|
+
delta: unknown,
|
|
53
|
+
): ToolUseContent["providerMetadata"] | undefined {
|
|
54
|
+
const thoughtSignature = googleThoughtSignatureFromUnknown(delta);
|
|
55
|
+
if (!thoughtSignature) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
return { gemini: { thoughtSignature } };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function thoughtSignatureFromToolUseMetadata(
|
|
62
|
+
metadata: ToolUseContent["providerMetadata"] | undefined,
|
|
63
|
+
): string | undefined {
|
|
64
|
+
return metadata?.gemini?.thoughtSignature;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function attachGoogleThoughtSignature<T extends object>(
|
|
68
|
+
toolCall: T,
|
|
69
|
+
thoughtSignature: string | undefined,
|
|
70
|
+
): T & GoogleToolCallExtraContent {
|
|
71
|
+
if (!thoughtSignature) {
|
|
72
|
+
return toolCall;
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
...toolCall,
|
|
76
|
+
extra_content: {
|
|
77
|
+
google: { thought_signature: thoughtSignature },
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Attach Gemini extra_content only when the destination model is Gemini 3.x.
|
|
84
|
+
* Non-Gemini Chat Completions endpoints never see the vendor field on the
|
|
85
|
+
* first request. Remapped model ids that still require a signature recover
|
|
86
|
+
* via {@link backfillGoogleThoughtSignatures} after a 4xx.
|
|
87
|
+
*/
|
|
88
|
+
export function attachGoogleThoughtSignatureIfNeeded<T extends object>(
|
|
89
|
+
toolCall: T,
|
|
90
|
+
thoughtSignature: string | undefined,
|
|
91
|
+
model: string,
|
|
92
|
+
): T & GoogleToolCallExtraContent {
|
|
93
|
+
if (!isGemini3Model(model)) {
|
|
94
|
+
return toolCall;
|
|
95
|
+
}
|
|
96
|
+
return attachGoogleThoughtSignature(toolCall, thoughtSignature);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function stampUnsignedGoogleThoughtSignature(
|
|
100
|
+
toolCalls: GoogleToolCallExtraContent[],
|
|
101
|
+
options?: { model?: string },
|
|
102
|
+
): boolean {
|
|
103
|
+
const fallback = unsignedThoughtSignatureFallback(
|
|
104
|
+
toolCalls.map((tc) => googleThoughtSignatureFromUnknown(tc)),
|
|
105
|
+
options,
|
|
106
|
+
);
|
|
107
|
+
if (!fallback) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
const target = toolCalls[fallback.index];
|
|
111
|
+
if (!target) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
target.extra_content = {
|
|
115
|
+
google: { thought_signature: fallback.signature },
|
|
116
|
+
};
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function stampCapturedGoogleThoughtSignatures(
|
|
121
|
+
toolCalls: Array<GoogleToolCallExtraContent & { id?: unknown }>,
|
|
122
|
+
capturedByCallId: ReadonlyMap<string, string>,
|
|
123
|
+
): boolean {
|
|
124
|
+
let added = false;
|
|
125
|
+
for (const tc of toolCalls) {
|
|
126
|
+
if (typeof tc.id !== "string") {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const captured = capturedByCallId.get(tc.id);
|
|
130
|
+
if (!captured || googleThoughtSignatureFromUnknown(tc)) {
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
tc.extra_content = {
|
|
134
|
+
google: { thought_signature: captured },
|
|
135
|
+
};
|
|
136
|
+
added = true;
|
|
137
|
+
}
|
|
138
|
+
return added;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Gemini 3.x validates the first function call of each step. When none of the
|
|
143
|
+
* serialized tool_calls carry a captured signature, attach the documented dummy
|
|
144
|
+
* so unsigned history (or a non-capturing earlier turn) does not 400.
|
|
145
|
+
*/
|
|
146
|
+
export function applyGemini3UnsignedToolCallFallback(
|
|
147
|
+
toolCalls: GoogleToolCallExtraContent[],
|
|
148
|
+
model: string,
|
|
149
|
+
): void {
|
|
150
|
+
stampUnsignedGoogleThoughtSignature(toolCalls, { model });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function paramsMessages(params: unknown): unknown[] | undefined {
|
|
154
|
+
const messages = (params as { messages?: unknown }).messages;
|
|
155
|
+
return Array.isArray(messages) ? messages : undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function assistantToolCalls(
|
|
159
|
+
msg: unknown,
|
|
160
|
+
): Array<GoogleToolCallExtraContent & { id?: unknown }> | undefined {
|
|
161
|
+
if (msg === null || typeof msg !== "object") {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const record = msg as { role?: unknown; tool_calls?: unknown };
|
|
165
|
+
if (record.role !== "assistant" || !Array.isArray(record.tool_calls)) {
|
|
166
|
+
return undefined;
|
|
167
|
+
}
|
|
168
|
+
return record.tool_calls as Array<
|
|
169
|
+
GoogleToolCallExtraContent & { id?: unknown }
|
|
170
|
+
>;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function messagesCarryGoogleThoughtSignature(params: unknown): boolean {
|
|
174
|
+
const messages = paramsMessages(params);
|
|
175
|
+
if (!messages) {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
return messages.some((msg) => {
|
|
179
|
+
const toolCalls = assistantToolCalls(msg);
|
|
180
|
+
return toolCalls?.some((tc) => googleThoughtSignatureFromUnknown(tc));
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function assistantToolCallsNeedThoughtSignatureBackfill(
|
|
185
|
+
params: unknown,
|
|
186
|
+
): boolean {
|
|
187
|
+
const messages = paramsMessages(params);
|
|
188
|
+
if (!messages) {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
return messages.some((msg) => {
|
|
192
|
+
const toolCalls = assistantToolCalls(msg);
|
|
193
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
return !toolCalls.some((tc) => googleThoughtSignatureFromUnknown(tc));
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
export function stripGoogleThoughtSignatures(params: unknown): boolean {
|
|
201
|
+
const messages = paramsMessages(params);
|
|
202
|
+
if (!messages) {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
let stripped = false;
|
|
206
|
+
for (const msg of messages) {
|
|
207
|
+
const toolCalls = assistantToolCalls(msg);
|
|
208
|
+
if (!toolCalls) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
for (const tc of toolCalls) {
|
|
212
|
+
if (tc.extra_content !== undefined) {
|
|
213
|
+
delete tc.extra_content;
|
|
214
|
+
stripped = true;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return stripped;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export function geminiThoughtSignaturesByToolCallId(
|
|
222
|
+
messages: ReadonlyArray<Message>,
|
|
223
|
+
): Map<string, string> {
|
|
224
|
+
const out = new Map<string, string>();
|
|
225
|
+
for (const msg of messages) {
|
|
226
|
+
if (msg.role !== "assistant") {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
for (const block of msg.content) {
|
|
230
|
+
if (block.type !== "tool_use") {
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
const signature = block.providerMetadata?.gemini?.thoughtSignature;
|
|
234
|
+
if (signature) {
|
|
235
|
+
out.set(block.id, signature);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export function backfillGoogleThoughtSignatures(
|
|
243
|
+
params: unknown,
|
|
244
|
+
capturedByCallId?: ReadonlyMap<string, string>,
|
|
245
|
+
): boolean {
|
|
246
|
+
const messages = paramsMessages(params);
|
|
247
|
+
if (!messages) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
let added = false;
|
|
251
|
+
for (const msg of messages) {
|
|
252
|
+
const toolCalls = assistantToolCalls(msg);
|
|
253
|
+
if (!toolCalls || toolCalls.length === 0) {
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (
|
|
257
|
+
capturedByCallId &&
|
|
258
|
+
stampCapturedGoogleThoughtSignatures(toolCalls, capturedByCallId)
|
|
259
|
+
) {
|
|
260
|
+
added = true;
|
|
261
|
+
}
|
|
262
|
+
if (stampUnsignedGoogleThoughtSignature(toolCalls)) {
|
|
263
|
+
added = true;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
return added;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function backfillUnsignedGoogleThoughtSignatures(
|
|
270
|
+
params: unknown,
|
|
271
|
+
): boolean {
|
|
272
|
+
return backfillGoogleThoughtSignatures(params);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const UNKNOWN_EXTRA_CONTENT_REJECTION =
|
|
276
|
+
/unknown|unexpected|unrecognized|additional propert|extra (?:field|property)|not (?:a )?valid|invalid (?:argument|parameter|field|property)/i;
|
|
277
|
+
|
|
278
|
+
export function classifyGoogleThoughtSignatureRetry(
|
|
279
|
+
params: unknown,
|
|
280
|
+
options: {
|
|
281
|
+
isClientError: boolean;
|
|
282
|
+
haystack: string;
|
|
283
|
+
capturedByCallId?: ReadonlyMap<string, string>;
|
|
284
|
+
},
|
|
285
|
+
): {
|
|
286
|
+
kind: GoogleThoughtSignatureRetryKind;
|
|
287
|
+
message: string;
|
|
288
|
+
apply: () => void;
|
|
289
|
+
} | null {
|
|
290
|
+
if (!options.isClientError) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (
|
|
294
|
+
assistantToolCallsNeedThoughtSignatureBackfill(params) &&
|
|
295
|
+
/thought[_\s-]?signature/i.test(options.haystack)
|
|
296
|
+
) {
|
|
297
|
+
return {
|
|
298
|
+
kind: "missing-thought-signature",
|
|
299
|
+
message:
|
|
300
|
+
"Upstream requires thought signature round-trip; retrying with signatures on unsigned tool_calls",
|
|
301
|
+
apply: () => {
|
|
302
|
+
backfillGoogleThoughtSignatures(params, options.capturedByCallId);
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (
|
|
307
|
+
messagesCarryGoogleThoughtSignature(params) &&
|
|
308
|
+
/extra_content/i.test(options.haystack) &&
|
|
309
|
+
UNKNOWN_EXTRA_CONTENT_REJECTION.test(options.haystack)
|
|
310
|
+
) {
|
|
311
|
+
return {
|
|
312
|
+
kind: "unknown-extra-content",
|
|
313
|
+
message: "Upstream rejected tool_call extra_content; retrying without it",
|
|
314
|
+
apply: () => {
|
|
315
|
+
stripGoogleThoughtSignatures(params);
|
|
316
|
+
},
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
let mockDeviceId: string | null = null;
|
|
4
|
+
mock.module("../../util/device-id.js", () => ({
|
|
5
|
+
getExistingDeviceId: () => mockDeviceId,
|
|
6
|
+
}));
|
|
2
7
|
|
|
3
8
|
import {
|
|
4
9
|
EMPTY_ASSISTANT_TURN_PLACEHOLDER,
|
|
@@ -11,7 +16,9 @@ import {
|
|
|
11
16
|
OPENCODE_SESSION_HEADER,
|
|
12
17
|
OPENCODE_ZEN_BASE_URL,
|
|
13
18
|
OpenCodeProvider,
|
|
19
|
+
resetOpenCodeFallbackSessionForTests,
|
|
14
20
|
resolveOpenCodeBaseURL,
|
|
21
|
+
resolveOpenCodeRequestHeaders,
|
|
15
22
|
} from "./client.js";
|
|
16
23
|
|
|
17
24
|
describe("resolveOpenCodeBaseURL", () => {
|
|
@@ -72,15 +79,44 @@ describe("buildOpenCodeRequestHeaders", () => {
|
|
|
72
79
|
});
|
|
73
80
|
|
|
74
81
|
test("sets only the ids that are present", () => {
|
|
75
|
-
expect(
|
|
76
|
-
|
|
77
|
-
)
|
|
82
|
+
expect(buildOpenCodeRequestHeaders({ conversationId: "conv-xyz" })).toEqual(
|
|
83
|
+
{ [OPENCODE_SESSION_HEADER]: "conv-xyz" },
|
|
84
|
+
);
|
|
78
85
|
expect(buildOpenCodeRequestHeaders({ requestId: "req-123" })).toEqual({
|
|
79
86
|
[OPENCODE_REQUEST_HEADER]: "req-123",
|
|
80
87
|
});
|
|
81
88
|
});
|
|
82
89
|
});
|
|
83
90
|
|
|
91
|
+
describe("resolveOpenCodeRequestHeaders", () => {
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
mockDeviceId = null;
|
|
94
|
+
resetOpenCodeFallbackSessionForTests();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("uses the conversation id over the device id", () => {
|
|
98
|
+
mockDeviceId = "dev-123";
|
|
99
|
+
const headers = resolveOpenCodeRequestHeaders("conv-xyz");
|
|
100
|
+
expect(headers[OPENCODE_SESSION_HEADER]).toBe("conv-xyz");
|
|
101
|
+
expect(headers[OPENCODE_REQUEST_HEADER]).toMatch(/\S/);
|
|
102
|
+
expect(resolveOpenCodeRequestHeaders()[OPENCODE_SESSION_HEADER]).toBe(
|
|
103
|
+
"dev-123",
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("mints one process-stable session when there is no device id", () => {
|
|
108
|
+
const first = resolveOpenCodeRequestHeaders();
|
|
109
|
+
expect(first[OPENCODE_SESSION_HEADER]).toMatch(/\S/);
|
|
110
|
+
expect(first).not.toHaveProperty("session_id");
|
|
111
|
+
// Pinned: a device.json created later in the process does not move
|
|
112
|
+
// background traffic onto a second session.
|
|
113
|
+
mockDeviceId = "dev-123";
|
|
114
|
+
expect(resolveOpenCodeRequestHeaders()[OPENCODE_SESSION_HEADER]).toBe(
|
|
115
|
+
first[OPENCODE_SESSION_HEADER]!,
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
84
120
|
describe("OpenCodeProvider", () => {
|
|
85
121
|
test("uses OpenAI chat-completions with reasoning-compatible knobs", () => {
|
|
86
122
|
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
@@ -137,6 +173,39 @@ describe("OpenCodeProvider", () => {
|
|
|
137
173
|
expect(options.headers).not.toHaveProperty("session_id");
|
|
138
174
|
});
|
|
139
175
|
|
|
176
|
+
test("stamps session and request headers on a configless call", async () => {
|
|
177
|
+
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
178
|
+
const seen: Array<{ options: unknown }> = [];
|
|
179
|
+
(provider as unknown as { client: unknown }).client = {
|
|
180
|
+
chat: {
|
|
181
|
+
completions: {
|
|
182
|
+
create: async (_params: unknown, options: unknown) => {
|
|
183
|
+
seen.push({ options });
|
|
184
|
+
return {
|
|
185
|
+
async *[Symbol.asyncIterator]() {
|
|
186
|
+
yield {
|
|
187
|
+
choices: [
|
|
188
|
+
{ delta: { content: "ok" }, finish_reason: "stop" },
|
|
189
|
+
],
|
|
190
|
+
usage: { prompt_tokens: 2, completion_tokens: 1 },
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
await provider.sendMessage([
|
|
200
|
+
{ role: "user", content: [{ type: "text", text: "question" }] },
|
|
201
|
+
]);
|
|
202
|
+
|
|
203
|
+
const options = seen[0]!.options as { headers?: Record<string, string> };
|
|
204
|
+
expect(options.headers?.[OPENCODE_SESSION_HEADER]).toMatch(/\S/);
|
|
205
|
+
expect(options.headers?.[OPENCODE_REQUEST_HEADER]).toMatch(/\S/);
|
|
206
|
+
expect(options.headers).not.toHaveProperty("session_id");
|
|
207
|
+
});
|
|
208
|
+
|
|
140
209
|
test("backfills placeholder content after an aborted empty assistant turn", async () => {
|
|
141
210
|
const provider = new OpenCodeProvider("sk-test", "mimo-v2.5-free");
|
|
142
211
|
const requests: unknown[] = [];
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { getExistingDeviceId } from "../../util/device-id.js";
|
|
1
4
|
import { OpenAIChatCompletionsProvider } from "../openai/chat-completions-provider.js";
|
|
5
|
+
import type {
|
|
6
|
+
Message,
|
|
7
|
+
ProviderResponse,
|
|
8
|
+
SendMessageOptions,
|
|
9
|
+
} from "../types.js";
|
|
2
10
|
|
|
3
11
|
export const OPENCODE_ZEN_BASE_URL = "https://opencode.ai/zen/v1";
|
|
4
12
|
export const OPENCODE_GO_BASE_URL = "https://opencode.ai/zen/go/v1";
|
|
@@ -27,9 +35,8 @@ export function resolveOpenCodeBaseURL(configuredBaseURL?: string): string {
|
|
|
27
35
|
* request ids only when they exist. Never sets `session_id` (zen/go
|
|
28
36
|
* returns 500 when that header is present).
|
|
29
37
|
*
|
|
30
|
-
* `conversationId` wins for the session header; `fallbackSessionId`
|
|
31
|
-
*
|
|
32
|
-
* background calls so they still identify a session to zen/go.
|
|
38
|
+
* `conversationId` wins for the session header; `fallbackSessionId` covers
|
|
39
|
+
* non-conversation calls so they still identify a session to zen/go.
|
|
33
40
|
*/
|
|
34
41
|
export function buildOpenCodeRequestHeaders(opts: {
|
|
35
42
|
conversationId?: string;
|
|
@@ -48,6 +55,33 @@ export function buildOpenCodeRequestHeaders(opts: {
|
|
|
48
55
|
return headers;
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
let fallbackSessionId: string | undefined;
|
|
59
|
+
|
|
60
|
+
/** @internal */
|
|
61
|
+
export function resetOpenCodeFallbackSessionForTests(): void {
|
|
62
|
+
fallbackSessionId = undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Headers for one outgoing OpenCode request. The session header is never
|
|
67
|
+
* omitted, because zen/go rejects requests without `x-opencode-session`:
|
|
68
|
+
* `conversationId` when the call has one, otherwise a fallback resolved
|
|
69
|
+
* once per process (the stable per-device ID if device.json exists at that
|
|
70
|
+
* point, else a fresh UUID) and pinned, so non-conversation traffic stays on
|
|
71
|
+
* one session even if device.json is created later. Transport metadata
|
|
72
|
+
* only; nothing here enters the request body.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveOpenCodeRequestHeaders(
|
|
75
|
+
conversationId?: string,
|
|
76
|
+
): Record<string, string> {
|
|
77
|
+
fallbackSessionId ??= getExistingDeviceId() ?? randomUUID();
|
|
78
|
+
return buildOpenCodeRequestHeaders({
|
|
79
|
+
conversationId,
|
|
80
|
+
fallbackSessionId,
|
|
81
|
+
requestId: randomUUID(),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
51
85
|
export class OpenCodeProvider extends OpenAIChatCompletionsProvider {
|
|
52
86
|
constructor(
|
|
53
87
|
apiKey: string,
|
|
@@ -63,4 +97,27 @@ export class OpenCodeProvider extends OpenAIChatCompletionsProvider {
|
|
|
63
97
|
omitToolChoiceWhenReasoning: true,
|
|
64
98
|
});
|
|
65
99
|
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Transport-level backstop so a call that reaches this provider without
|
|
103
|
+
* going through `RetryProvider` (or with no config at all) still carries
|
|
104
|
+
* the session and request headers zen/go requires. Headers the caller
|
|
105
|
+
* already resolved (conversation-derived) win.
|
|
106
|
+
*/
|
|
107
|
+
override async sendMessage(
|
|
108
|
+
messages: Message[],
|
|
109
|
+
options?: SendMessageOptions,
|
|
110
|
+
): Promise<ProviderResponse> {
|
|
111
|
+
const config = options?.config ?? {};
|
|
112
|
+
return super.sendMessage(messages, {
|
|
113
|
+
...options,
|
|
114
|
+
config: {
|
|
115
|
+
...config,
|
|
116
|
+
requestHeaders: {
|
|
117
|
+
...resolveOpenCodeRequestHeaders(),
|
|
118
|
+
...config.requestHeaders,
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
}
|
|
66
123
|
}
|
|
@@ -36,6 +36,19 @@ const OPENROUTER_APP_ATTRIBUTION_HEADERS = {
|
|
|
36
36
|
|
|
37
37
|
const OPENROUTER_MODEL_EFFORT_CEILINGS = modelEffortCeilings("openrouter");
|
|
38
38
|
|
|
39
|
+
const OPENROUTER_MAX_COMPLETION_TOKENS_ONLY = /gpt-5\.[12]-(?:codex|chat)/;
|
|
40
|
+
|
|
41
|
+
/** Output-token-limit wire key for an OpenRouter model id. Most endpoints
|
|
42
|
+
* advertise `max_tokens`; GPT-5.1/5.2 Codex and Chat advertise only
|
|
43
|
+
* `max_completion_tokens`. Exported for tests. */
|
|
44
|
+
export function openRouterOutputTokenLimitField(
|
|
45
|
+
model: string,
|
|
46
|
+
): "max_completion_tokens" | "max_tokens" {
|
|
47
|
+
return OPENROUTER_MAX_COMPLETION_TOKENS_ONLY.test(model)
|
|
48
|
+
? "max_completion_tokens"
|
|
49
|
+
: "max_tokens";
|
|
50
|
+
}
|
|
51
|
+
|
|
39
52
|
/**
|
|
40
53
|
* Extract the normalized `openrouter.only` list from a per-call config. Returns
|
|
41
54
|
* an empty array when the field is absent, empty, or contains no usable string
|
|
@@ -143,6 +156,7 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
143
156
|
streamTimeoutMs: options.streamTimeoutMs,
|
|
144
157
|
requestHeaders: OPENROUTER_APP_ATTRIBUTION_HEADERS,
|
|
145
158
|
assistantReasoningField: "reasoning",
|
|
159
|
+
outputTokenLimitField: "max_tokens",
|
|
146
160
|
});
|
|
147
161
|
this.openRouterApiKey = apiKey;
|
|
148
162
|
this.resolvedBaseURL = baseURL;
|
|
@@ -250,6 +264,12 @@ export class OpenRouterProvider extends OpenAIChatCompletionsProvider {
|
|
|
250
264
|
);
|
|
251
265
|
}
|
|
252
266
|
|
|
267
|
+
protected override resolveOutputTokenLimitField(
|
|
268
|
+
model: string,
|
|
269
|
+
): "max_completion_tokens" | "max_tokens" {
|
|
270
|
+
return openRouterOutputTokenLimitField(model);
|
|
271
|
+
}
|
|
272
|
+
|
|
253
273
|
private resolveEffectiveModel(options?: SendMessageOptions): string {
|
|
254
274
|
const config = options?.config as Record<string, unknown> | undefined;
|
|
255
275
|
const override =
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound-request Unicode guard for every provider adapter.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript strings are UTF-16, and any `.slice()` on a code-unit boundary
|
|
5
|
+
* can orphan half of a surrogate pair. `JSON.stringify` emits the orphan as a
|
|
6
|
+
* `\udXXX` escape, which strict API parsers reject with a 400 before the model
|
|
7
|
+
* runs (Anthropic: "no low surrogate in string"; OpenAI: `invalid_json`).
|
|
8
|
+
* `RetryProvider` sanitizes the caller's messages and options once at entry,
|
|
9
|
+
* so every attempt, corrective resend, credential refresh, and backup route
|
|
10
|
+
* sends a clean payload, and sanitizes token-count requests the same way.
|
|
11
|
+
* Nothing beneath the wrapper truncates text, so this is the one place the
|
|
12
|
+
* invariant is enforced.
|
|
13
|
+
*
|
|
14
|
+
* The replacement is lossy (U+FFFD), so the warning names the block types
|
|
15
|
+
* involved: the durable fix for any offender is a surrogate-safe slice at the
|
|
16
|
+
* truncation site (`safeStringSlice` in `util/unicode.ts`).
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { getLogger } from "../util/logger.js";
|
|
20
|
+
import { stripOrphanedSurrogatesDeep } from "../util/unicode.js";
|
|
21
|
+
import type { Message, SendMessageOptions } from "./types.js";
|
|
22
|
+
|
|
23
|
+
const log = getLogger("outbound-request-sanitize");
|
|
24
|
+
|
|
25
|
+
/** Rate-limit the warning so a poisoned conversation cannot flood the log. */
|
|
26
|
+
const ORPHAN_WARNING_THROTTLE_MS = 60_000;
|
|
27
|
+
let lastOrphanWarningMs = 0;
|
|
28
|
+
|
|
29
|
+
export interface OutboundRequest {
|
|
30
|
+
messages: Message[];
|
|
31
|
+
options: SendMessageOptions | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Fields that hold provider-bound base64 (an inline media source's payload,
|
|
36
|
+
* a redacted-thinking blob) are ASCII by construction and can run to over a
|
|
37
|
+
* hundred megabytes, so scanning them is pure event-loop cost.
|
|
38
|
+
*/
|
|
39
|
+
function isBase64Payload(
|
|
40
|
+
key: string,
|
|
41
|
+
parent: Record<string, unknown>,
|
|
42
|
+
): boolean {
|
|
43
|
+
return (
|
|
44
|
+
key === "data" &&
|
|
45
|
+
(parent.type === "base64" || parent.type === "redacted_thinking")
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Replace every orphaned UTF-16 surrogate in the request's messages and
|
|
51
|
+
* options with U+FFFD. Returns the same references when nothing needed
|
|
52
|
+
* fixing, so the happy path allocates nothing.
|
|
53
|
+
*/
|
|
54
|
+
export function sanitizeOutboundRequest(
|
|
55
|
+
providerName: string,
|
|
56
|
+
request: OutboundRequest,
|
|
57
|
+
): OutboundRequest {
|
|
58
|
+
const sanitized = stripOrphanedSurrogatesDeep(request, {
|
|
59
|
+
skipKey: isBase64Payload,
|
|
60
|
+
});
|
|
61
|
+
if (!sanitized.changed) {
|
|
62
|
+
return request;
|
|
63
|
+
}
|
|
64
|
+
logOrphanedSurrogateWarning(
|
|
65
|
+
providerName,
|
|
66
|
+
sanitized.fixedStringCount,
|
|
67
|
+
request.messages,
|
|
68
|
+
);
|
|
69
|
+
return sanitized.value;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function logOrphanedSurrogateWarning(
|
|
73
|
+
providerName: string,
|
|
74
|
+
fixedStringCount: number,
|
|
75
|
+
messages: Message[],
|
|
76
|
+
): void {
|
|
77
|
+
const now = Date.now();
|
|
78
|
+
if (now - lastOrphanWarningMs < ORPHAN_WARNING_THROTTLE_MS) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
lastOrphanWarningMs = now;
|
|
82
|
+
const blockTypes = new Set<string>();
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
for (const block of msg.content) {
|
|
85
|
+
blockTypes.add(block.type);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
log.warn(
|
|
89
|
+
{
|
|
90
|
+
provider: providerName,
|
|
91
|
+
fixedStringCount,
|
|
92
|
+
blockTypes: Array.from(blockTypes),
|
|
93
|
+
},
|
|
94
|
+
"stripped orphaned UTF-16 surrogates from an outbound LLM request; an upstream truncation is not surrogate-aware",
|
|
95
|
+
);
|
|
96
|
+
}
|