@vellumai/assistant 0.12.3-staging.1 → 0.12.3-staging.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.md +43 -29
- package/Dockerfile +63 -0
- package/docs/architecture/security.md +10 -5
- package/docs/architecture/turn-actor.md +2 -0
- package/docs/desktop-browser-cli.md +45 -4
- package/docs/live-voice-task-outcomes.md +100 -0
- package/docs/mode-sessions.md +183 -0
- package/docs/voice-input-diagnostics.md +58 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/ces-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/gateway-client/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/gateway-client/src/gateway-ipc-contracts.ts +20 -0
- package/node_modules/@vellumai/gateway-client/src/index.ts +4 -1
- package/node_modules/@vellumai/gateway-client/src/outbound-contract.ts +38 -7
- package/node_modules/@vellumai/gateway-client/src/trust-verdict-contract.ts +5 -6
- package/node_modules/@vellumai/service-contracts/package.json +2 -1
- package/node_modules/@vellumai/service-contracts/src/index.ts +1 -0
- package/node_modules/@vellumai/service-contracts/src/integration-categories.ts +36 -0
- package/node_modules/@vellumai/slack-text/src/index.ts +4 -8
- package/openapi.yaml +665 -14
- package/package.json +1 -1
- package/scripts/smoke-desktop-browser-cli.ts +10 -26
- package/scripts/smoke-desktop-window-theme.ts +2 -3
- package/src/__tests__/agent-loop-output-hooks.test.ts +150 -1
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +87 -19
- package/src/__tests__/annotate-activity-metadata.test.ts +3 -1
- package/src/__tests__/annotate-risk-options.test.ts +2 -0
- package/src/__tests__/answered-question-persistence.test.ts +2 -0
- package/src/__tests__/anthropic-gateway-shared.test.ts +22 -0
- package/src/__tests__/approval-message-composer.test.ts +1 -118
- package/src/__tests__/approval-routes-http.test.ts +3 -0
- package/src/__tests__/assistant-event-hub-connection-history.test.ts +111 -0
- package/src/__tests__/assistant-event-hub-targeted.test.ts +50 -0
- package/src/__tests__/assistant-event-targeting.test.ts +186 -0
- package/src/__tests__/assistant-stream-state.test.ts +29 -9
- package/src/__tests__/browser-fill-credential.test.ts +11 -22
- package/src/__tests__/btw-routes.test.ts +4 -1
- package/src/__tests__/build-persisted-content.test.ts +1 -1
- package/src/__tests__/call-conversation-messages.test.ts +5 -0
- package/src/__tests__/call-recovery.test.ts +7 -2
- package/src/__tests__/call-routes-http.test.ts +12 -0
- package/src/__tests__/call-store.test.ts +7 -2
- package/src/__tests__/channel-retry-sweep.test.ts +9 -0
- package/src/__tests__/chat-reveal-guard-priming.test.ts +2 -0
- package/src/__tests__/checker.test.ts +11 -3
- package/src/__tests__/client-os-metadata-persistence.test.ts +35 -1
- package/src/__tests__/computer-use-skill-manifest-regression.test.ts +0 -25
- package/src/__tests__/computer-use-tools.test.ts +1 -233
- package/src/__tests__/config-model-image-provider.test.ts +39 -0
- package/src/__tests__/config-schema.test.ts +16 -0
- package/src/__tests__/config-watcher.test.ts +24 -0
- package/src/__tests__/context-search-agent-runner.test.ts +260 -1
- package/src/__tests__/context-search-fanout.test.ts +1 -1
- package/src/__tests__/context-search-types.test.ts +7 -7
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +8 -2
- package/src/__tests__/conversation-agent-loop-fatal-cleanup.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop.test.ts +546 -16
- package/src/__tests__/conversation-app-control-lifecycle.test.ts +53 -1
- package/src/__tests__/conversation-evictor.test.ts +62 -37
- package/src/__tests__/conversation-history-boundary-repair-failure.test.ts +174 -0
- package/src/__tests__/conversation-loop-db-lock-resilience.test.ts +2 -0
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +2 -0
- package/src/__tests__/conversation-retry-route.test.ts +22 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +2 -0
- package/src/__tests__/conversation-routes-enabled-plugins.test.ts +2 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +10 -0
- package/src/__tests__/conversation-routes-hidden-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +29 -0
- package/src/__tests__/conversation-surface-snapshots.test.ts +543 -0
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +151 -0
- package/src/__tests__/conversation-surfaces-point-at-budget.test.ts +63 -1
- package/src/__tests__/conversation-tool-setup-attribution.test.ts +23 -0
- package/src/__tests__/conversation-undo.test.ts +61 -0
- package/src/__tests__/credential-execution-client.test.ts +6 -6
- package/src/__tests__/credential-prompt-route.test.ts +35 -3
- package/src/__tests__/cu-unified-flow.test.ts +82 -1
- package/src/__tests__/db-mode-sessions-migration.test.ts +175 -0
- package/src/__tests__/deterministic-verification-control-plane.test.ts +2 -0
- package/src/__tests__/dm-backfill.test.ts +91 -1
- package/src/__tests__/document-create-route.test.ts +88 -0
- package/src/__tests__/document-sync-tags.test.ts +27 -0
- package/src/__tests__/emit-signal-routing-intent.test.ts +142 -2
- package/src/__tests__/events-tail-route.test.ts +25 -0
- package/src/__tests__/evict-conversations-for-reload.test.ts +57 -8
- package/src/__tests__/guardian-action-sweep.test.ts +16 -0
- package/src/__tests__/guardian-dispatch.test.ts +13 -0
- package/src/__tests__/headless-browser-interactions.test.ts +64 -20
- package/src/__tests__/heartbeat-service.test.ts +2 -59
- package/src/__tests__/helpers/mock-conversation.ts +21 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +2 -0
- package/src/__tests__/helpers/verification-sessions-ipc-sim.ts +5 -0
- package/src/__tests__/host-cu-proxy.test.ts +46 -2
- package/src/__tests__/http-user-message-parity.test.ts +2 -0
- package/src/__tests__/image-credentials.test.ts +49 -0
- package/src/__tests__/image-service-dispatcher.test.ts +45 -0
- package/src/__tests__/inference-profile-session-handler.test.ts +1 -1
- package/src/__tests__/interrupt-on-send.test.ts +57 -43
- package/src/__tests__/interrupt-turn-note.test.ts +5 -6
- package/src/__tests__/llm-catalog-parity.test.ts +2 -2
- package/src/__tests__/media-generate-image.test.ts +49 -1
- package/src/__tests__/message-provenance-contact-id.test.ts +69 -0
- package/src/__tests__/migration-export-to-gcs.test.ts +142 -0
- package/src/__tests__/mode-session-history.test.ts +245 -0
- package/src/__tests__/mode-session-recovery.test.ts +222 -0
- package/src/__tests__/mode-session-wire.test.ts +141 -0
- package/src/__tests__/notification-guardian-path.test.ts +7 -0
- package/src/__tests__/notification-telegram-adapter.test.ts +2 -2
- package/src/__tests__/notification-vellum-adapter.test.ts +34 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +12 -0
- package/src/__tests__/oauth-providers-routes.test.ts +1 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +2 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +2 -0
- package/src/__tests__/plugin-api-provider.test.ts +5 -0
- package/src/__tests__/plugin-api-resolve-credential.test.ts +97 -0
- package/src/__tests__/plugin-import-boundary-guard.test.ts +9 -0
- package/src/__tests__/process-message-display-content.test.ts +56 -0
- package/src/__tests__/reaction-persistence.test.ts +5 -0
- package/src/__tests__/request-secret-standalone-channel-gate.test.ts +25 -9
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +1 -1
- package/src/__tests__/scripted-turn-metadata-persistence.test.ts +73 -0
- package/src/__tests__/secret-ingress-http.test.ts +2 -0
- package/src/__tests__/secure-keys.test.ts +43 -1
- package/src/__tests__/send-endpoint-busy.test.ts +163 -1
- package/src/__tests__/server-history-render.test.ts +91 -1
- package/src/__tests__/settings-routes.test.ts +50 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +32 -0
- package/src/__tests__/subagent-fork-notifications.test.ts +1 -2
- package/src/__tests__/subagent-manager-notify.test.ts +1 -2
- package/src/__tests__/subagent-notify-parent.test.ts +146 -39
- package/src/__tests__/subagent-notify-stale-rebuild.test.ts +2 -4
- package/src/__tests__/surface-completion-in-flight-snapshot.test.ts +2 -0
- package/src/__tests__/thread-backfill.test.ts +53 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +2 -0
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +3 -1
- package/src/__tests__/tool-start-timestamp.test.ts +2 -0
- package/src/__tests__/twilio-routes.test.ts +1 -0
- package/src/__tests__/web-search-backend-failure.test.ts +1 -1
- package/src/activation/progress-store.test.ts +12 -7
- package/src/agent/loop.ts +99 -41
- package/src/api/events/assistant-turn-start.ts +3 -0
- package/src/api/events/generation-handoff.ts +2 -0
- package/src/api/events/message-complete.ts +2 -0
- package/src/api/events/notification-conversation-created.ts +14 -16
- package/src/api/events/notification-intent.ts +4 -2
- package/src/api/events/tool-result.ts +85 -0
- package/src/api/events/tool-use-start.ts +3 -0
- package/src/api/events/user-message-echo.ts +3 -0
- package/src/api/index.ts +31 -6
- package/src/api/mode-session.test.ts +131 -0
- package/src/api/mode-session.ts +148 -0
- package/src/api/remember-tool.ts +15 -0
- package/src/api/responses/conversation-message.ts +6 -0
- package/src/approvals/AGENTS.md +4 -2
- package/src/browser/__tests__/operations.test.ts +29 -0
- package/src/browser/operation-meta.ts +2 -1
- package/src/browser/operations.ts +29 -0
- package/src/browser/virtual-desktop-target.ts +2 -8
- package/src/bundler/bundle-signer.ts +1 -3
- package/src/calls/__tests__/barge-in-guard.test.ts +3 -0
- package/src/calls/__tests__/call-controller.test.ts +94 -0
- package/src/calls/__tests__/call-funnel-telemetry.test.ts +204 -0
- package/src/calls/__tests__/voice-control-protocol.test.ts +15 -0
- package/src/calls/__tests__/voice-escalation-judge.test.ts +209 -0
- package/src/calls/__tests__/voice-leg-coordinator.test.ts +168 -0
- package/src/{live-voice/__tests__/live-voice-metrics.test.ts → calls/__tests__/voice-metrics.test.ts} +32 -38
- package/src/calls/__tests__/voice-session-bridge.test.ts +576 -122
- package/src/calls/barge-in-guard.ts +5 -0
- package/src/calls/call-controller.ts +196 -1
- package/src/calls/call-domain.ts +4 -0
- package/src/calls/call-store.ts +87 -2
- package/src/calls/guardian-action-sweep.ts +22 -8
- package/src/calls/media-stream-audio-transcode.ts +1 -3
- package/src/calls/typesafe-noul.ts +156 -0
- package/src/calls/voice-control-protocol.ts +6 -0
- package/src/calls/voice-escalation-judge.ts +145 -0
- package/src/calls/voice-escalation-target.ts +10 -0
- package/src/calls/voice-leg-coordinator.ts +121 -3
- package/src/{live-voice/live-voice-metrics.ts → calls/voice-metrics.ts} +94 -87
- package/src/calls/voice-session-bridge.ts +256 -112
- package/src/calls/voice-triage-escalate.ts +7 -0
- package/src/cli/__tests__/roadmap-cli.test.ts +4 -41
- package/src/cli/commands/__tests__/notifications.test.ts +35 -10
- package/src/cli/commands/browser.help.ts +4 -5
- package/src/cli/commands/browser.ts +0 -1
- package/src/cli/commands/clients.help.ts +46 -0
- package/src/cli/commands/clients.ts +116 -0
- package/src/cli/commands/credentials.help.ts +6 -1
- package/src/cli/commands/notifications.help.ts +16 -2
- package/src/cli/commands/notifications.ts +30 -8
- package/src/cli/commands/roadmap.help.ts +18 -21
- package/src/cli/commands/roadmap.ts +4 -21
- package/src/cli/lib/__tests__/list-installed-plugins.test.ts +5 -2
- package/src/cli/lib/bundled-marketplace.json +84 -34
- package/src/cli/lib/plugin-marketplace.ts +8 -0
- package/src/cli/lib/publish-plugin.ts +1 -2
- package/src/config/__tests__/default-provider.test.ts +3 -1
- package/src/config/__tests__/memory-retrospective-schema.test.ts +14 -1
- package/src/config/bundled-skills/computer-use/SKILL.md +1 -1
- package/src/config/bundled-skills/computer-use/TOOLS.json +89 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +17 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +7 -11
- package/src/config/bundled-skills/sequences/TOOLS.json +1 -5
- package/src/config/call-site-defaults.ts +9 -0
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/profile-text-generation.test.ts +2 -2
- package/src/config/schemas/__tests__/tools.test.ts +31 -0
- package/src/config/schemas/call-site-catalog.ts +14 -0
- package/src/config/schemas/channels.ts +0 -18
- package/src/config/schemas/llm.ts +3 -1
- package/src/config/schemas/mcp.ts +2 -1
- package/src/config/schemas/memory-retrospective.ts +11 -2
- package/src/config/schemas/services.ts +15 -6
- package/src/config/schemas/tools.ts +10 -0
- package/src/config/session-groups-gate.ts +21 -0
- package/src/config/skills.ts +2 -6
- package/src/conversations/__tests__/message-consolidation.test.ts +149 -0
- package/src/conversations/message-consolidation.ts +102 -5
- package/src/credential-execution/ces-connect.test.ts +69 -0
- package/src/credential-execution/ces-connect.ts +115 -0
- package/src/credential-execution/ces-runtime.ts +55 -93
- package/src/credential-execution/executable-discovery.ts +1 -11
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +10 -1
- package/src/daemon/__tests__/inference-profile-notification.test.ts +2 -0
- package/src/daemon/browser-mode-session.test.ts +402 -0
- package/src/daemon/browser-mode-session.ts +186 -0
- package/src/daemon/computer-use-mode-session.test.ts +210 -0
- package/src/daemon/computer-use-mode-session.ts +105 -0
- package/src/daemon/config-watcher.ts +10 -0
- package/src/daemon/conversation-agent-loop-handlers.ts +58 -10
- package/src/daemon/conversation-agent-loop.ts +219 -25
- package/src/daemon/conversation-evictor.ts +5 -24
- package/src/daemon/conversation-history.ts +10 -0
- package/src/daemon/conversation-interrupt-repair.ts +4 -43
- package/src/daemon/conversation-interrupt.ts +7 -11
- package/src/daemon/conversation-messaging.ts +58 -5
- package/src/daemon/conversation-mode-session.test.ts +1667 -0
- package/src/daemon/conversation-mode-session.ts +1008 -0
- package/src/daemon/conversation-process.ts +5 -0
- package/src/daemon/conversation-runtime-assembly.ts +10 -20
- package/src/daemon/conversation-skill-tools.ts +1 -0
- package/src/daemon/conversation-store.ts +4 -4
- package/src/daemon/conversation-surface-snapshots.ts +135 -0
- package/src/daemon/conversation-surfaces.ts +113 -5
- package/src/daemon/conversation-tool-setup.ts +8 -3
- package/src/daemon/conversation.ts +143 -41
- package/src/daemon/handlers/config-model.ts +7 -5
- package/src/daemon/handlers/shared.ts +55 -16
- package/src/daemon/host-cu-proxy.ts +51 -22
- package/src/daemon/lifecycle.ts +30 -13
- package/src/daemon/message-protocol.ts +0 -1
- package/src/daemon/message-provenance.ts +28 -4
- package/src/daemon/mode-session-startup-recovery.ts +23 -0
- package/src/daemon/mode-session-tracking.ts +54 -0
- package/src/daemon/process-message.ts +13 -4
- package/src/daemon/virtual-desktop-context.ts +21 -0
- package/src/desktop/__tests__/fake-desktop.ts +1 -0
- package/src/desktop/desktop-accessibility-script.ts +88 -0
- package/src/desktop/desktop-accessibility.test.ts +160 -0
- package/src/desktop/desktop-accessibility.ts +169 -0
- package/src/desktop/desktop-automation-lease.test.ts +76 -68
- package/src/desktop/desktop-automation-lease.ts +41 -50
- package/src/desktop/desktop-browser-endpoint.ts +4 -0
- package/src/desktop/desktop-computer-use-routing.test.ts +330 -0
- package/src/desktop/desktop-computer-use.test.ts +449 -0
- package/src/desktop/desktop-computer-use.ts +502 -0
- package/src/desktop/desktop-dependencies.ts +47 -293
- package/src/desktop/desktop-display.ts +2 -2
- package/src/desktop/desktop-panel-config.test.ts +13 -3
- package/src/desktop/desktop-panel-config.ts +109 -15
- package/src/desktop/desktop-session-manager.test.ts +69 -21
- package/src/desktop/desktop-session-manager.ts +76 -22
- package/src/desktop/desktop-stream-bridge.test.ts +11 -122
- package/src/desktop/desktop-stream-bridge.ts +6 -8
- package/src/desktop/desktop-window-theme.ts +5 -25
- package/src/desktop/virtual-desktop-feature.ts +52 -2
- package/src/desktop/virtual-desktop-platform.test.ts +5 -9
- package/src/documents/document-store.ts +23 -0
- package/src/heartbeat/__tests__/heartbeat-service.test.ts +22 -26
- package/src/heartbeat/heartbeat-service.ts +5 -33
- package/src/ipc/__tests__/browser-ipc.test.ts +215 -6
- package/src/ipc/assistant-server.ts +2 -0
- package/src/ipc/routes/__tests__/channel-reply-ipc-routes.test.ts +111 -0
- package/src/ipc/routes/channel-reply-ipc-routes.ts +73 -0
- package/src/live-voice/__tests__/continuation-judge.test.ts +151 -0
- package/src/live-voice/__tests__/live-activity-reporter.test.ts +154 -15
- package/src/live-voice/__tests__/live-voice-flux-turn-end.test.ts +743 -31
- package/src/live-voice/__tests__/live-voice-look-follow-up.test.ts +191 -10
- package/src/live-voice/__tests__/live-voice-session-groups-gate.test.ts +87 -0
- package/src/live-voice/__tests__/live-voice-session-manager.test.ts +5 -1
- package/src/live-voice/__tests__/live-voice-sight-frame-echo.test.ts +41 -0
- package/src/live-voice/__tests__/live-voice-sight-frame.test.ts +1017 -9
- package/src/live-voice/__tests__/live-voice-triage-escalate.test.ts +224 -1
- package/src/live-voice/__tests__/live-voice-tts-session.test.ts +101 -0
- package/src/live-voice/__tests__/live-voice-tts.test.ts +44 -0
- package/src/live-voice/__tests__/live-voice-vad.test.ts +939 -75
- package/src/live-voice/__tests__/microphone-idle-gate.test.ts +87 -0
- package/src/live-voice/__tests__/session-controls.test.ts +23 -0
- package/src/live-voice/__tests__/task-outcomes.test.ts +117 -0
- package/src/live-voice/__tests__/voice-input-diagnostics.test.ts +117 -0
- package/src/live-voice/camera-mode-session.test.ts +265 -0
- package/src/live-voice/camera-mode-session.ts +277 -0
- package/src/live-voice/continuation-judge.ts +123 -0
- package/src/live-voice/live-activity-reporter.ts +115 -6
- package/src/live-voice/live-voice-manager.ts +36 -1
- package/src/live-voice/live-voice-photo.ts +88 -7
- package/src/live-voice/live-voice-session-manager.ts +15 -0
- package/src/live-voice/live-voice-session.ts +1443 -747
- package/src/live-voice/live-voice-tts.ts +22 -4
- package/src/live-voice/microphone-idle-gate.ts +56 -0
- package/src/live-voice/protocol.ts +171 -4
- package/src/live-voice/session-controls.ts +11 -3
- package/src/live-voice/task-outcomes.ts +131 -0
- package/src/live-voice/voice-input-diagnostics.ts +117 -0
- package/src/mcp/__tests__/manager-tool-caps.test.ts +63 -18
- package/src/mcp/__tests__/tool-caps.test.ts +28 -1
- package/src/mcp/manager.ts +3 -0
- package/src/mcp/tool-caps.ts +12 -4
- package/src/media/__tests__/image-models.test.ts +58 -0
- package/src/media/image-credentials.ts +5 -0
- package/src/media/image-models.ts +52 -0
- package/src/media/image-service.ts +8 -0
- package/src/media/openrouter-image-service.test.ts +133 -0
- package/src/media/openrouter-image-service.ts +153 -0
- package/src/media/types.ts +1 -1
- package/src/messaging/providers/__tests__/transport-dispatch.test.ts +65 -11
- package/src/messaging/providers/discord/send.ts +4 -17
- package/src/messaging/providers/discord/transport.ts +2 -2
- package/src/messaging/providers/outlook/client.ts +16 -22
- package/src/messaging/providers/telegram-bot/send.test.ts +236 -1
- package/src/messaging/providers/telegram-bot/send.ts +10 -7
- package/src/messaging/providers/telegram-bot/transport.ts +3 -10
- package/src/messaging/providers/whatsapp/send.test.ts +169 -4
- package/src/monitoring/__tests__/control.test.ts +17 -5
- package/src/monitoring/control.ts +15 -5
- package/src/notifications/AGENTS.md +1 -1
- package/src/notifications/README.md +83 -99
- package/src/notifications/__tests__/broadcaster.test.ts +1 -1
- package/src/notifications/__tests__/channel-allowlist.test.ts +39 -0
- package/src/notifications/__tests__/copy-composer.test.ts +1 -1
- package/src/notifications/__tests__/decision-engine.test.ts +79 -2
- package/src/notifications/__tests__/deterministic-checks.test.ts +10 -10
- package/src/notifications/__tests__/home-feed-side-effect.test.ts +93 -4
- package/src/notifications/__tests__/notification-utils.test.ts +18 -0
- package/src/notifications/adapters/macos.ts +37 -46
- package/src/notifications/broadcaster.ts +5 -6
- package/src/notifications/channel-allowlist.ts +44 -0
- package/src/notifications/copy-composer.ts +0 -23
- package/src/notifications/decision-engine.ts +24 -14
- package/src/notifications/deterministic-checks.ts +1 -1
- package/src/notifications/emit-signal.ts +123 -37
- package/src/notifications/home-feed-side-effect.ts +9 -7
- package/src/notifications/notification-utils.ts +19 -0
- package/src/notifications/signal.ts +4 -19
- package/src/notifications/types.ts +15 -0
- package/src/oauth/connection-resolver.ts +1 -2
- package/src/oauth/oauth-store.ts +1 -3
- package/src/oauth/provider-serializer.ts +7 -0
- package/src/oauth/seed-providers.ts +30 -0
- package/src/onboarding/onboarding-events-store.ts +99 -12
- package/src/persistence/__tests__/client-connection-events-store.test.ts +322 -0
- package/src/persistence/client-connection-events-store.ts +377 -0
- package/src/persistence/conversation-crud.ts +55 -6
- package/src/persistence/conversation-mode-sessions.test.ts +424 -0
- package/src/persistence/conversation-mode-sessions.ts +460 -0
- package/src/persistence/conversation-queries.ts +6 -19
- package/src/persistence/llm-usage-store.ts +3 -9
- package/src/persistence/message-metadata.ts +25 -0
- package/src/persistence/migrations/381-create-conversation-mode-sessions.ts +41 -0
- package/src/persistence/migrations/382-create-client-connection-events.ts +40 -0
- package/src/persistence/migrations/__tests__/382-create-client-connection-events.test.ts +56 -0
- package/src/persistence/schema/conversations.ts +67 -0
- package/src/persistence/schema/infrastructure.ts +29 -0
- package/src/persistence/steps.ts +17 -0
- package/src/plugin-api/__tests__/plugin-name-env.test.ts +249 -0
- package/src/plugin-api/effective-context-window.ts +33 -0
- package/src/plugin-api/index.ts +24 -5
- package/src/plugin-api/plugin-name-env.ts +125 -0
- package/src/plugin-api/resolve-credential.ts +79 -20
- package/src/plugin-api/vision-support.test.ts +22 -0
- package/src/plugin-api/vision-support.ts +17 -5
- package/src/plugins/__tests__/active-plugin-env.test.ts +136 -0
- package/src/plugins/active-plugin-env.ts +51 -0
- package/src/plugins/defaults/index.ts +24 -0
- package/src/plugins/defaults/memory/__tests__/archive-removal-framing.test.ts +64 -0
- package/src/plugins/defaults/memory/__tests__/worker-control.test.ts +19 -6
- package/src/plugins/defaults/memory/context-search/agent-protocol.ts +9 -12
- package/src/plugins/defaults/memory/context-search/agent-runner.ts +78 -22
- package/src/plugins/defaults/memory/context-search/format.ts +51 -7
- package/src/plugins/defaults/memory/context-search/limits.ts +10 -12
- package/src/plugins/defaults/memory/context-search/recall-input.ts +38 -0
- package/src/plugins/defaults/memory/context-search/search.ts +8 -14
- package/src/plugins/defaults/memory/context-search/sources/conversations.ts +1 -0
- package/src/plugins/defaults/memory/context-search/types.ts +1 -11
- package/src/plugins/defaults/memory/graph/__tests__/handle-remember-v2.test.ts +11 -4
- package/src/plugins/defaults/memory/graph/tool-handlers.ts +12 -21
- package/src/plugins/defaults/memory/graph/tools.ts +35 -64
- package/src/plugins/defaults/memory/src/memory-eval-routes.ts +2 -3
- package/src/plugins/defaults/memory/src/memory-item-routes.test.ts +5 -0
- package/src/plugins/defaults/memory/src/memory-item-routes.ts +2 -1
- package/src/plugins/defaults/memory/substrate/prompts/consolidation.ts +2 -2
- package/src/plugins/defaults/memory/tools.test.ts +124 -9
- package/src/plugins/defaults/memory/tools.ts +38 -11
- package/src/plugins/defaults/memory/v3/__tests__/pool-log-store.test.ts +165 -27
- package/src/plugins/defaults/memory/v3/__tests__/pool-select.test.ts +626 -27
- package/src/plugins/defaults/memory/v3/__tests__/shadow-plugin.test.ts +46 -27
- package/src/plugins/defaults/memory/v3/orchestrate.ts +78 -38
- package/src/plugins/defaults/memory/v3/pool-log-store.ts +65 -79
- package/src/plugins/defaults/memory/v3/pool-select.test.ts +14 -5
- package/src/plugins/defaults/memory/v3/pool-select.ts +683 -44
- package/src/plugins/defaults/memory/worker-control.ts +15 -5
- package/src/plugins/defaults/task-progress/__tests__/registration.test.ts +70 -0
- package/src/plugins/defaults/task-progress/__tests__/task-progress-context.test.ts +380 -0
- package/src/plugins/defaults/task-progress/hooks/post-compact.ts +19 -0
- package/src/plugins/defaults/task-progress/hooks/user-prompt-submit.ts +24 -0
- package/src/plugins/defaults/task-progress/package.json +14 -0
- package/src/plugins/defaults/task-progress/src/apply-task-progress-context.ts +93 -0
- package/src/plugins/defaults/task-progress/src/format-task-progress-context.ts +67 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +10 -0
- package/src/providers/__tests__/search-provider-catalog.test.ts +18 -0
- package/src/providers/anthropic/client.ts +10 -1
- package/src/providers/anthropic-gateway-shared.ts +6 -0
- package/src/providers/fetch-provider-catalog.ts +11 -0
- package/src/providers/gemini/client.ts +25 -8
- package/src/providers/inference/adapter-factory.ts +1 -1
- package/src/providers/jev/client.test.ts +10 -0
- package/src/providers/jev/client.ts +20 -4
- package/src/providers/model-catalog.ts +4 -3
- package/src/providers/openai/__tests__/chat-completions-extra-body.test.ts +94 -1
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +106 -1
- package/src/providers/openai/__tests__/chat-template-flatten.test.ts +24 -0
- package/src/providers/openai/__tests__/google-thought-signature.test.ts +1 -2
- package/src/providers/openai/chat-completions-provider.ts +100 -29
- package/src/providers/openai/google-thought-signature.ts +0 -6
- package/src/providers/openai/responses-provider.ts +23 -1
- package/src/providers/ratelimit.ts +1 -3
- package/src/providers/search-provider-catalog.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-frames.test.ts +24 -0
- package/src/providers/speech-to-text/__tests__/deepgram-flux-realtime.test.ts +7 -1
- package/src/providers/speech-to-text/__tests__/vellum-managed-realtime.test.ts +50 -1
- package/src/providers/speech-to-text/deepgram-flux-frames.ts +11 -0
- package/src/providers/speech-to-text/vellum-managed-flux-realtime.ts +13 -0
- package/src/providers/types.ts +5 -0
- package/src/routes/control.ts +7 -5
- package/src/runtime/AGENTS.md +4 -2
- package/src/runtime/actor-trust-resolver.ts +3 -3
- package/src/runtime/agent-wake.ts +1 -1
- package/src/runtime/approval-message-composer.ts +3 -69
- package/src/runtime/assistant-event-hub.ts +73 -75
- package/src/runtime/assistant-event-publish-options.ts +7 -0
- package/src/runtime/assistant-event-targeting.ts +79 -0
- package/src/runtime/assistant-stream-state.ts +13 -100
- package/src/runtime/background-job-runner.ts +5 -5
- package/src/runtime/channel-retry-sweep.ts +1 -0
- package/src/runtime/guardian-action-message-composer.ts +8 -161
- package/src/runtime/guardian-reply-router.ts +28 -32
- package/src/runtime/http-types.ts +6 -0
- package/src/runtime/message-composer-types.ts +3 -61
- package/src/runtime/migrations/__tests__/vbundle-import-parity.test.ts +67 -1
- package/src/runtime/migrations/vbundle-builder.ts +24 -1
- package/src/runtime/migrations/vbundle-import-analyzer.ts +13 -4
- package/src/runtime/migrations/vbundle-import-policy.ts +10 -0
- package/src/runtime/migrations/vbundle-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-streaming-importer.ts +4 -1
- package/src/runtime/migrations/vbundle-validator.ts +1 -0
- package/src/runtime/pending-interactions-mode-session.test.ts +62 -0
- package/src/runtime/pending-interactions.ts +29 -0
- package/src/runtime/routes/__tests__/client-history-routes.test.ts +104 -0
- package/src/runtime/routes/__tests__/image-generation-routes.test.ts +97 -0
- package/src/runtime/routes/__tests__/inference-profiles-routes.test.ts +1 -1
- package/src/runtime/routes/__tests__/roadmap-routes.test.ts +29 -6
- package/src/runtime/routes/browser-context.ts +31 -11
- package/src/runtime/routes/browser-routes.ts +43 -4
- package/src/runtime/routes/btw-routes.ts +9 -4
- package/src/runtime/routes/client-routes.ts +108 -0
- package/src/runtime/routes/conversation-management-routes.ts +4 -1
- package/src/runtime/routes/conversation-routes.ts +113 -27
- package/src/runtime/routes/credential-prompt-routes.ts +4 -3
- package/src/runtime/routes/credential-routes.ts +1 -2
- package/src/runtime/routes/desktop-setup-routes.test.ts +9 -18
- package/src/runtime/routes/desktop-setup-routes.ts +4 -6
- package/src/runtime/routes/documents-routes.ts +51 -0
- package/src/runtime/routes/email-routes.ts +3 -7
- package/src/runtime/routes/events-routes.ts +75 -54
- package/src/runtime/routes/identity-routes.ts +1 -5
- package/src/runtime/routes/image-generation-routes.ts +8 -14
- package/src/runtime/routes/inbound-message-handler.ts +35 -38
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.test.ts +168 -0
- package/src/runtime/routes/inbound-stages/backfill-sender-provenance.ts +96 -0
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +38 -9
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +2 -5
- package/src/runtime/routes/inbound-stages/reaction-intercept.ts +2 -0
- package/src/runtime/routes/llm-context-normalization.ts +1 -7
- package/src/runtime/routes/migration-routes.ts +73 -2
- package/src/runtime/routes/notification-routes.ts +10 -0
- package/src/runtime/routes/oauth-providers.ts +7 -0
- package/src/runtime/routes/plugins-routes.ts +6 -0
- package/src/runtime/routes/roadmap-routes.ts +47 -8
- package/src/runtime/routes/secret-routes.ts +1 -1
- package/src/runtime/routes/settings-routes.ts +10 -4
- package/src/runtime/routes/watcher-routes.ts +1 -3
- package/src/runtime/trust-class.test.ts +7 -6
- package/src/runtime/trust-class.ts +9 -17
- package/src/schedule/__tests__/worker-control.test.ts +36 -5
- package/src/schedule/worker-control.ts +15 -5
- package/src/security/secure-keys.ts +53 -103
- package/src/skills/clawhub.ts +3 -8
- package/src/skills/managed-store.ts +1 -5
- package/src/stt/types.ts +4 -0
- package/src/subagent/notify.ts +54 -34
- package/src/subagent/parent-notification.ts +6 -0
- package/src/telemetry/__tests__/phone-call-funnel.test.ts +72 -0
- package/src/telemetry/phone-call-funnel.ts +144 -0
- package/src/tools/__tests__/tool-schema-root-combinator-guard.test.ts +81 -0
- package/src/tools/ask-question/ask-question-tool.ts +6 -4
- package/src/tools/browser/browser-execution.ts +22 -59
- package/src/tools/browser/cdp-client/__tests__/cdp-dom-helpers.test.ts +561 -39
- package/src/tools/browser/cdp-client/cdp-dom-helpers.ts +435 -39
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +1 -8
- package/src/tools/client-os.ts +15 -0
- package/src/tools/computer-use/skill-proxy-bridge.ts +1 -1
- package/src/tools/computer-use/target.ts +42 -0
- package/src/tools/document/document-tool.ts +13 -18
- package/src/tools/execution-target.ts +14 -16
- package/src/tools/host-terminal/host-shell.ts +4 -1
- package/src/tools/network/__tests__/web-fetch-tinyfish.test.ts +250 -0
- package/src/tools/network/__tests__/web-search.test.ts +203 -0
- package/src/tools/network/web-fetch.ts +274 -7
- package/src/tools/network/web-search.ts +468 -6
- package/src/tools/permission-checker.ts +3 -3
- package/src/tools/policy-context.ts +3 -1
- package/src/tools/skills/load.ts +2 -1
- package/src/tools/skills/sandbox-runner.ts +11 -2
- package/src/tools/skills/skill-script-runner.ts +7 -0
- package/src/tools/skills/skill-tool-factory.ts +15 -1
- package/src/tools/terminal/__tests__/safe-env.test.ts +11 -0
- package/src/tools/terminal/safe-env.ts +4 -0
- package/src/tools/terminal/shell.ts +2 -0
- package/src/tools/tool-approval-handler.ts +5 -1
- package/src/tools/tool-types.ts +3 -3
- package/src/tools/types.ts +8 -2
- package/src/util/__tests__/worker-process-command.test.ts +5 -4
- package/src/util/abort-reasons.ts +18 -26
- package/src/util/canonicalize-identity.test.ts +21 -0
- package/src/util/canonicalize-identity.ts +16 -0
- package/src/util/errors.ts +13 -3
- package/src/util/provider-error-patterns.ts +3 -0
- package/src/util/worker-process.ts +77 -35
- package/src/workspace/migrations/157-add-desktop-files-launcher.ts +94 -0
- package/src/workspace/migrations/__tests__/156-extract-workspace-mcp-json.test.ts +4 -10
- package/src/workspace/migrations/__tests__/157-add-desktop-files-launcher.test.ts +113 -0
- package/src/workspace/migrations/registry.ts +2 -0
- package/src/__tests__/computer-use-window-schema.test.ts +0 -24
- package/src/api/events/heartbeat-alert.ts +0 -20
- package/src/daemon/message-types/web-activity.ts +0 -65
- package/src/desktop/desktop-dependencies.test.ts +0 -131
- package/src/tools/computer-use/definitions.ts +0 -607
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { ConversationSurfaceSnapshot } from "../../../../daemon/conversation-surface-snapshots.js";
|
|
2
|
+
import { getTaskProgressDataFromSurfaceData } from "../../../../runtime/task-progress.js";
|
|
3
|
+
|
|
4
|
+
export const ACTIVE_TASK_PROGRESS_OPEN = "<active_task_progress>";
|
|
5
|
+
export const ACTIVE_TASK_PROGRESS_CLOSE = "</active_task_progress>";
|
|
6
|
+
|
|
7
|
+
export const ACTIVE_TASK_PROGRESS_BLOCK =
|
|
8
|
+
/<active_task_progress\b[\s\S]*?<\/active_task_progress>/gi;
|
|
9
|
+
|
|
10
|
+
const STEP_INSTRUCTION =
|
|
11
|
+
"Update this card with ui_update as the steps change.";
|
|
12
|
+
|
|
13
|
+
function normalizeField(value: string): string {
|
|
14
|
+
return value
|
|
15
|
+
.replace(/[<>]/g, "")
|
|
16
|
+
.replace(/[\r\n\u2028\u2029]+/g, " ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function isActiveTaskProgressSurface(
|
|
22
|
+
snapshot: ConversationSurfaceSnapshot,
|
|
23
|
+
): boolean {
|
|
24
|
+
if (snapshot.surfaceType !== "card" || snapshot.completed) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const plan = getTaskProgressDataFromSurfaceData(snapshot.data);
|
|
28
|
+
if (!plan) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return plan.steps.some(
|
|
32
|
+
(step) => step.status === "pending" || step.status === "in_progress",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function formatActiveTaskProgressSnapshot(
|
|
37
|
+
snapshots: ConversationSurfaceSnapshot[],
|
|
38
|
+
): string {
|
|
39
|
+
const cards = snapshots.filter(isActiveTaskProgressSurface);
|
|
40
|
+
if (cards.length === 0) {
|
|
41
|
+
return "";
|
|
42
|
+
}
|
|
43
|
+
return cards
|
|
44
|
+
.map((snapshot) => {
|
|
45
|
+
const plan = getTaskProgressDataFromSurfaceData(snapshot.data);
|
|
46
|
+
if (!plan) {
|
|
47
|
+
return "";
|
|
48
|
+
}
|
|
49
|
+
const lines = [
|
|
50
|
+
ACTIVE_TASK_PROGRESS_OPEN,
|
|
51
|
+
`surface_id: ${normalizeField(snapshot.surfaceId)}`,
|
|
52
|
+
];
|
|
53
|
+
const title = plan.title ? normalizeField(plan.title) : "";
|
|
54
|
+
if (title.length > 0) {
|
|
55
|
+
lines.push(`title: ${title}`);
|
|
56
|
+
}
|
|
57
|
+
lines.push("steps:");
|
|
58
|
+
for (const step of plan.steps) {
|
|
59
|
+
lines.push(`- [${step.status}] ${normalizeField(step.label)}`);
|
|
60
|
+
}
|
|
61
|
+
lines.push(STEP_INSTRUCTION);
|
|
62
|
+
lines.push(ACTIVE_TASK_PROGRESS_CLOSE);
|
|
63
|
+
return lines.join("\n");
|
|
64
|
+
})
|
|
65
|
+
.filter((block) => block.length > 0)
|
|
66
|
+
.join("\n");
|
|
67
|
+
}
|
|
@@ -47,6 +47,16 @@ describe("ContextOverflowError", () => {
|
|
|
47
47
|
expect((err as Error & { cause?: unknown }).cause).toBe(cause);
|
|
48
48
|
});
|
|
49
49
|
|
|
50
|
+
test("forwards rawRequest onto the ProviderError", () => {
|
|
51
|
+
const rawRequest = { model: "claude-opus-4.6", messages: [] };
|
|
52
|
+
const err = new ContextOverflowError("m", "anthropic", {
|
|
53
|
+
actualTokens: 10,
|
|
54
|
+
maxTokens: 5,
|
|
55
|
+
rawRequest,
|
|
56
|
+
});
|
|
57
|
+
expect(err.rawRequest).toEqual(rawRequest);
|
|
58
|
+
});
|
|
59
|
+
|
|
50
60
|
test("omits optional fields when unset", () => {
|
|
51
61
|
const err = new ContextOverflowError("m", "gemini");
|
|
52
62
|
expect(err.actualTokens).toBeUndefined();
|
|
@@ -86,4 +86,22 @@ describe("search-provider-catalog", () => {
|
|
|
86
86
|
expect(getSearchProvider("brave")?.displayName).toBe("Brave");
|
|
87
87
|
expect(getSearchProvider("unknown-xyz")).toBeUndefined();
|
|
88
88
|
});
|
|
89
|
+
|
|
90
|
+
test("SearXNG is a keyless BYOK provider that requires an instance URL", () => {
|
|
91
|
+
const searxng = getSearchProvider("searxng");
|
|
92
|
+
expect(searxng?.kind).toBe("byok");
|
|
93
|
+
expect(searxng?.keyless).toBe(true);
|
|
94
|
+
expect(searxng?.supportsApiBase).toBe(true);
|
|
95
|
+
expect(searxng?.defaultApiBase).toBeUndefined();
|
|
96
|
+
expect(searxng?.envVar).toBe("SEARXNG_API_KEY");
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("TinyFish is a keyed BYOK provider with a cloud API base", () => {
|
|
100
|
+
const tinyfish = getSearchProvider("tinyfish");
|
|
101
|
+
expect(tinyfish?.kind).toBe("byok");
|
|
102
|
+
expect(tinyfish?.keyless).toBeUndefined();
|
|
103
|
+
expect(tinyfish?.supportsApiBase).toBe(true);
|
|
104
|
+
expect(tinyfish?.defaultApiBase).toBe("https://api.search.tinyfish.ai");
|
|
105
|
+
expect(tinyfish?.envVar).toBe("TINYFISH_API_KEY");
|
|
106
|
+
});
|
|
89
107
|
});
|
|
@@ -958,6 +958,7 @@ export class AnthropicProvider implements Provider {
|
|
|
958
958
|
// (30 min default) from an external transport abort (bun fetch deadline,
|
|
959
959
|
// edge LB, NAT idle) — only the latter should be retried.
|
|
960
960
|
let innerTimeoutSignal: AbortSignal | undefined;
|
|
961
|
+
let inspectableRequest: unknown | undefined;
|
|
961
962
|
try {
|
|
962
963
|
sentMessages = await this.buildSentMessages(messages);
|
|
963
964
|
const {
|
|
@@ -1344,6 +1345,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1344
1345
|
const streamOnce = async (
|
|
1345
1346
|
streamParams: Anthropic.MessageStreamParams,
|
|
1346
1347
|
): Promise<Anthropic.Message> => {
|
|
1348
|
+
inspectableRequest = streamParams;
|
|
1347
1349
|
const stream: UnifiedStream = useFastMode
|
|
1348
1350
|
? (this.client.beta.messages.stream(
|
|
1349
1351
|
{
|
|
@@ -1768,6 +1770,7 @@ export class AnthropicProvider implements Provider {
|
|
|
1768
1770
|
statusCode: error.status,
|
|
1769
1771
|
reason: "context_overflow",
|
|
1770
1772
|
cause: error,
|
|
1773
|
+
rawRequest: inspectableRequest,
|
|
1771
1774
|
},
|
|
1772
1775
|
);
|
|
1773
1776
|
}
|
|
@@ -1779,7 +1782,11 @@ export class AnthropicProvider implements Provider {
|
|
|
1779
1782
|
reason?: ProviderErrorReason;
|
|
1780
1783
|
apiErrorType?: string;
|
|
1781
1784
|
apiErrorCode?: string;
|
|
1785
|
+
rawRequest?: unknown;
|
|
1782
1786
|
} = {};
|
|
1787
|
+
if (inspectableRequest !== undefined) {
|
|
1788
|
+
errorOptions.rawRequest = inspectableRequest;
|
|
1789
|
+
}
|
|
1783
1790
|
if (retryAfterMs !== undefined) {
|
|
1784
1791
|
errorOptions.retryAfterMs = retryAfterMs;
|
|
1785
1792
|
}
|
|
@@ -1832,7 +1839,9 @@ export class AnthropicProvider implements Provider {
|
|
|
1832
1839
|
}`,
|
|
1833
1840
|
"anthropic",
|
|
1834
1841
|
undefined,
|
|
1835
|
-
abortReason
|
|
1842
|
+
abortReason
|
|
1843
|
+
? { cause: error, abortReason, rawRequest: inspectableRequest }
|
|
1844
|
+
: { cause: error, rawRequest: inspectableRequest },
|
|
1836
1845
|
);
|
|
1837
1846
|
}
|
|
1838
1847
|
}
|
|
@@ -45,6 +45,9 @@ export function retagDelegateError(
|
|
|
45
45
|
maxTokens: error.maxTokens,
|
|
46
46
|
statusCode: error.statusCode,
|
|
47
47
|
cause: error,
|
|
48
|
+
...(error.rawRequest !== undefined
|
|
49
|
+
? { rawRequest: error.rawRequest }
|
|
50
|
+
: {}),
|
|
48
51
|
});
|
|
49
52
|
}
|
|
50
53
|
if (error instanceof ProviderError && error.provider !== providerName) {
|
|
@@ -57,6 +60,9 @@ export function retagDelegateError(
|
|
|
57
60
|
apiErrorCode: error.apiErrorCode,
|
|
58
61
|
apiErrorParam: error.apiErrorParam,
|
|
59
62
|
rawBody: error.rawBody,
|
|
63
|
+
...(error.rawRequest !== undefined
|
|
64
|
+
? { rawRequest: error.rawRequest }
|
|
65
|
+
: {}),
|
|
60
66
|
});
|
|
61
67
|
}
|
|
62
68
|
throw error;
|
|
@@ -84,6 +84,17 @@ export const FETCH_PROVIDER_CATALOG: readonly FetchProviderCatalogEntry[] = [
|
|
|
84
84
|
supportsApiBase: true,
|
|
85
85
|
defaultApiBase: "https://api.fastcrw.com",
|
|
86
86
|
},
|
|
87
|
+
{
|
|
88
|
+
id: "tinyfish",
|
|
89
|
+
displayName: "TinyFish",
|
|
90
|
+
kind: "byok",
|
|
91
|
+
apiKeyPrefix: "TinyFish API key...",
|
|
92
|
+
envVar: "TINYFISH_API_KEY",
|
|
93
|
+
secretKey: "tinyfish",
|
|
94
|
+
privacyPolicyUrl: "https://www.tinyfish.ai/privacy-policy",
|
|
95
|
+
supportsApiBase: true,
|
|
96
|
+
defaultApiBase: "https://api.fetch.tinyfish.ai",
|
|
97
|
+
},
|
|
87
98
|
];
|
|
88
99
|
|
|
89
100
|
/** Provider ids accepted by the web-fetch config schema. */
|
|
@@ -439,6 +439,14 @@ export class GeminiProvider implements Provider {
|
|
|
439
439
|
)
|
|
440
440
|
: undefined;
|
|
441
441
|
|
|
442
|
+
let inspectableRequest:
|
|
443
|
+
| {
|
|
444
|
+
model: string;
|
|
445
|
+
contents: unknown;
|
|
446
|
+
config: genai.GenerateContentConfig;
|
|
447
|
+
}
|
|
448
|
+
| undefined;
|
|
449
|
+
|
|
442
450
|
try {
|
|
443
451
|
recordProviderRequestDiagnostics({ model_id: activeModel });
|
|
444
452
|
const geminiContents = await this.toGeminiContents(messages, activeModel);
|
|
@@ -476,6 +484,12 @@ export class GeminiProvider implements Provider {
|
|
|
476
484
|
geminiConfig.httpOptions = { headers: usageAttributionHeaders };
|
|
477
485
|
}
|
|
478
486
|
|
|
487
|
+
inspectableRequest = {
|
|
488
|
+
model: activeModel,
|
|
489
|
+
contents: geminiContents,
|
|
490
|
+
config: stripGeminiHttpOptions(geminiConfig),
|
|
491
|
+
};
|
|
492
|
+
|
|
479
493
|
// Accumulate from streaming chunks
|
|
480
494
|
let fullText = "";
|
|
481
495
|
const functionCalls: Array<{
|
|
@@ -583,11 +597,6 @@ export class GeminiProvider implements Provider {
|
|
|
583
597
|
content.push(block);
|
|
584
598
|
}
|
|
585
599
|
|
|
586
|
-
const rawRequest = {
|
|
587
|
-
model: activeModel,
|
|
588
|
-
contents: geminiContents,
|
|
589
|
-
config: stripGeminiHttpOptions(geminiConfig),
|
|
590
|
-
};
|
|
591
600
|
const rawResponse = {
|
|
592
601
|
model: responseModel,
|
|
593
602
|
text: fullText || null,
|
|
@@ -609,7 +618,7 @@ export class GeminiProvider implements Provider {
|
|
|
609
618
|
...(cachedTokens > 0 ? { cacheReadInputTokens: cachedTokens } : {}),
|
|
610
619
|
},
|
|
611
620
|
stopReason: finishReason,
|
|
612
|
-
rawRequest,
|
|
621
|
+
rawRequest: inspectableRequest,
|
|
613
622
|
rawResponse,
|
|
614
623
|
};
|
|
615
624
|
} catch (error) {
|
|
@@ -630,6 +639,7 @@ export class GeminiProvider implements Provider {
|
|
|
630
639
|
maxTokens: overflow.maxTokens,
|
|
631
640
|
statusCode: error.status,
|
|
632
641
|
cause: error,
|
|
642
|
+
rawRequest: inspectableRequest,
|
|
633
643
|
},
|
|
634
644
|
);
|
|
635
645
|
}
|
|
@@ -639,7 +649,12 @@ export class GeminiProvider implements Provider {
|
|
|
639
649
|
error.status,
|
|
640
650
|
// Skip reason on caller-abort: abortReason already carries the intent
|
|
641
651
|
// and short-circuits classification/retry (mirrors the Anthropic client).
|
|
642
|
-
|
|
652
|
+
abortReason
|
|
653
|
+
? { abortReason, rawRequest: inspectableRequest }
|
|
654
|
+
: {
|
|
655
|
+
reason: deriveGeminiReason(error),
|
|
656
|
+
rawRequest: inspectableRequest,
|
|
657
|
+
},
|
|
643
658
|
);
|
|
644
659
|
}
|
|
645
660
|
throw new ProviderError(
|
|
@@ -648,7 +663,9 @@ export class GeminiProvider implements Provider {
|
|
|
648
663
|
}`,
|
|
649
664
|
"gemini",
|
|
650
665
|
undefined,
|
|
651
|
-
abortReason
|
|
666
|
+
abortReason
|
|
667
|
+
? { cause: error, abortReason, rawRequest: inspectableRequest }
|
|
668
|
+
: { cause: error, rawRequest: inspectableRequest },
|
|
652
669
|
);
|
|
653
670
|
}
|
|
654
671
|
}
|
|
@@ -206,7 +206,7 @@ const ADAPTER_FACTORIES: Record<string, AdapterFactory> = {
|
|
|
206
206
|
streamTimeoutMs,
|
|
207
207
|
...(baseURL ? { baseURL } : {}),
|
|
208
208
|
}),
|
|
209
|
-
|
|
209
|
+
typesafe: ({ apiKey, model, streamTimeoutMs, baseURL }) =>
|
|
210
210
|
new JevProvider(apiKey, model, {
|
|
211
211
|
streamTimeoutMs,
|
|
212
212
|
...(baseURL ? { baseURL } : {}),
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
conversationToState,
|
|
6
6
|
DEFAULT_JEV_MODEL,
|
|
7
7
|
JevProvider,
|
|
8
|
+
noulFromAnswer,
|
|
8
9
|
parseSystemOneOverride,
|
|
9
10
|
validateJevApiKey,
|
|
10
11
|
} from "./client.js";
|
|
@@ -66,6 +67,15 @@ describe("parseSystemOneOverride", () => {
|
|
|
66
67
|
});
|
|
67
68
|
});
|
|
68
69
|
|
|
70
|
+
describe("noulFromAnswer", () => {
|
|
71
|
+
test("reads a typed noul object or a bare number", () => {
|
|
72
|
+
expect(noulFromAnswer({ type: "noul", noul: 0.91 })).toBe(0.91);
|
|
73
|
+
expect(noulFromAnswer(0.4)).toBe(0.4);
|
|
74
|
+
expect(noulFromAnswer({ type: "choice", choice: "keep" })).toBeUndefined();
|
|
75
|
+
expect(noulFromAnswer(null)).toBeUndefined();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
69
79
|
describe("conversationToState", () => {
|
|
70
80
|
test("flattens system prompt and turns into a string", () => {
|
|
71
81
|
const state = conversationToState(
|
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
|
|
16
16
|
const log = getLogger("jev-client");
|
|
17
17
|
|
|
18
|
-
export const JEV_PROVIDER_ID = "
|
|
18
|
+
export const JEV_PROVIDER_ID = "typesafe";
|
|
19
19
|
export const DEFAULT_JEV_BASE_URL = "https://api.typesafe.ai";
|
|
20
20
|
export const DEFAULT_JEV_MODEL = "jev-latest";
|
|
21
21
|
|
|
@@ -115,6 +115,21 @@ function isJevQuestions(value: unknown): value is JevQuestions {
|
|
|
115
115
|
return entries.every(([, question]) => isJevQuestion(question));
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
/** Calibrated P(yes) from a System One noul answer, or undefined when absent. */
|
|
119
|
+
export function noulFromAnswer(answer: unknown): number | undefined {
|
|
120
|
+
if (typeof answer === "number" && Number.isFinite(answer)) {
|
|
121
|
+
return answer;
|
|
122
|
+
}
|
|
123
|
+
if (
|
|
124
|
+
isRecord(answer) &&
|
|
125
|
+
typeof answer.noul === "number" &&
|
|
126
|
+
Number.isFinite(answer.noul)
|
|
127
|
+
) {
|
|
128
|
+
return answer.noul;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
118
133
|
/**
|
|
119
134
|
* If the last user message is a TypeSafe System One payload, use it as the
|
|
120
135
|
* evaluation request. `state` is optional; callers fall back to the
|
|
@@ -411,7 +426,7 @@ export class JevProvider implements Provider {
|
|
|
411
426
|
extractErrorMessage(bodyText, response.status),
|
|
412
427
|
JEV_PROVIDER_ID,
|
|
413
428
|
response.status,
|
|
414
|
-
{ reason, rawBody: bodyText },
|
|
429
|
+
{ reason, rawBody: bodyText, rawRequest: request },
|
|
415
430
|
);
|
|
416
431
|
}
|
|
417
432
|
let parsed: unknown;
|
|
@@ -422,7 +437,7 @@ export class JevProvider implements Provider {
|
|
|
422
437
|
"TypeSafe returned a non-JSON response.",
|
|
423
438
|
JEV_PROVIDER_ID,
|
|
424
439
|
response.status,
|
|
425
|
-
{ reason: "bad_request", rawBody: bodyText },
|
|
440
|
+
{ reason: "bad_request", rawBody: bodyText, rawRequest: request },
|
|
426
441
|
);
|
|
427
442
|
}
|
|
428
443
|
if (!isRecord(parsed) || !isRecord(parsed.answers)) {
|
|
@@ -430,7 +445,7 @@ export class JevProvider implements Provider {
|
|
|
430
445
|
"TypeSafe returned a response without answers.",
|
|
431
446
|
JEV_PROVIDER_ID,
|
|
432
447
|
response.status,
|
|
433
|
-
{ reason: "bad_request", rawBody: bodyText },
|
|
448
|
+
{ reason: "bad_request", rawBody: bodyText, rawRequest: request },
|
|
434
449
|
);
|
|
435
450
|
}
|
|
436
451
|
const usage = isRecord(parsed.usage) ? parsed.usage : undefined;
|
|
@@ -471,6 +486,7 @@ export class JevProvider implements Provider {
|
|
|
471
486
|
cause: error,
|
|
472
487
|
abortReason,
|
|
473
488
|
reason: abortReason ? undefined : "network_error",
|
|
489
|
+
rawRequest: request,
|
|
474
490
|
},
|
|
475
491
|
);
|
|
476
492
|
} finally {
|
|
@@ -77,7 +77,8 @@ export interface CatalogModel {
|
|
|
77
77
|
* Whether the model produces free-form chat text. Omit (or true) for
|
|
78
78
|
* ordinary chat models. False for structured-decision models that return
|
|
79
79
|
* answers rather than generated text; those stay out of conversation
|
|
80
|
-
* pickers and cannot be the conversation model.
|
|
80
|
+
* pickers and cannot be the conversation model. They can still back a
|
|
81
|
+
* saved profile and a call-site pin.
|
|
81
82
|
*/
|
|
82
83
|
supportsText?: boolean;
|
|
83
84
|
supportsEffort?: boolean;
|
|
@@ -2532,8 +2533,8 @@ const RAW_PROVIDER_CATALOG: ProviderCatalogEntry[] = [
|
|
|
2532
2533
|
apiKeyPlaceholder: "Your Poolside API key",
|
|
2533
2534
|
},
|
|
2534
2535
|
{
|
|
2535
|
-
id: "
|
|
2536
|
-
displayName: "
|
|
2536
|
+
id: "typesafe",
|
|
2537
|
+
displayName: "TypeSafe",
|
|
2537
2538
|
subtitle:
|
|
2538
2539
|
"TypeSafe System One decision model. Returns structured answers, not generated text. Requires a TypeSafe API key.",
|
|
2539
2540
|
setupMode: "api-key",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import OpenAI from "openai";
|
|
4
|
+
|
|
5
|
+
import { ProviderError } from "../../../util/errors.js";
|
|
6
|
+
import { ContextOverflowError, type SendMessageOptions } from "../../types.js";
|
|
4
7
|
import { OpenAIChatCompletionsProvider } from "../chat-completions-provider.js";
|
|
5
8
|
|
|
6
9
|
class ExtraBodyProvider extends OpenAIChatCompletionsProvider {
|
|
@@ -48,4 +51,94 @@ describe("chat-completions extraBody", () => {
|
|
|
48
51
|
expect(seenParams?.directions).toEqual({ companion: 0.4 });
|
|
49
52
|
expect(seenOptions?.extraBody).toBeUndefined();
|
|
50
53
|
});
|
|
54
|
+
|
|
55
|
+
test("attaches the SDK create params as ProviderError.rawRequest on 4xx", async () => {
|
|
56
|
+
const provider = new ExtraBodyProvider("test-key", "qwen/qwen3-8b", {
|
|
57
|
+
providerName: "vellum",
|
|
58
|
+
providerLabel: "Vellum",
|
|
59
|
+
});
|
|
60
|
+
(
|
|
61
|
+
provider as unknown as {
|
|
62
|
+
client: {
|
|
63
|
+
chat: {
|
|
64
|
+
completions: {
|
|
65
|
+
create: () => Promise<AsyncIterable<unknown>>;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
).client.chat.completions.create = async () => {
|
|
71
|
+
throw new OpenAI.APIError(
|
|
72
|
+
400,
|
|
73
|
+
{ detail: "directions are not loaded" },
|
|
74
|
+
undefined,
|
|
75
|
+
new Headers(),
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
try {
|
|
80
|
+
await provider.sendMessage([
|
|
81
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
82
|
+
]);
|
|
83
|
+
throw new Error("expected ProviderError");
|
|
84
|
+
} catch (error) {
|
|
85
|
+
expect(error).toBeInstanceOf(ProviderError);
|
|
86
|
+
const rejected = error as ProviderError;
|
|
87
|
+
expect(rejected.provider).toBe("vellum");
|
|
88
|
+
expect(rejected.statusCode).toBe(400);
|
|
89
|
+
expect(rejected.rawRequest).toEqual(
|
|
90
|
+
expect.objectContaining({
|
|
91
|
+
model: "qwen/qwen3-8b",
|
|
92
|
+
directions: { companion: 0.4 },
|
|
93
|
+
}),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test("attaches the SDK create params as ContextOverflowError.rawRequest", async () => {
|
|
99
|
+
const provider = new ExtraBodyProvider("test-key", "qwen/qwen3-8b", {
|
|
100
|
+
providerName: "vellum",
|
|
101
|
+
providerLabel: "Vellum",
|
|
102
|
+
});
|
|
103
|
+
(
|
|
104
|
+
provider as unknown as {
|
|
105
|
+
client: {
|
|
106
|
+
chat: {
|
|
107
|
+
completions: {
|
|
108
|
+
create: () => Promise<AsyncIterable<unknown>>;
|
|
109
|
+
};
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
).client.chat.completions.create = async () => {
|
|
114
|
+
throw new OpenAI.APIError(
|
|
115
|
+
400,
|
|
116
|
+
{
|
|
117
|
+
message:
|
|
118
|
+
"This model's maximum context length is 128000 tokens. However, your messages resulted in 150000 tokens.",
|
|
119
|
+
type: "invalid_request_error",
|
|
120
|
+
code: "context_length_exceeded",
|
|
121
|
+
},
|
|
122
|
+
undefined,
|
|
123
|
+
new Headers(),
|
|
124
|
+
);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
await provider.sendMessage([
|
|
129
|
+
{ role: "user", content: [{ type: "text", text: "hi" }] },
|
|
130
|
+
]);
|
|
131
|
+
throw new Error("expected ContextOverflowError");
|
|
132
|
+
} catch (error) {
|
|
133
|
+
expect(error).toBeInstanceOf(ContextOverflowError);
|
|
134
|
+
const rejected = error as ContextOverflowError;
|
|
135
|
+
expect(rejected.provider).toBe("vellum");
|
|
136
|
+
expect(rejected.rawRequest).toEqual(
|
|
137
|
+
expect.objectContaining({
|
|
138
|
+
model: "qwen/qwen3-8b",
|
|
139
|
+
directions: { companion: 0.4 },
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
51
144
|
});
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
1
|
+
import { beforeEach, describe, expect, test } from "bun:test";
|
|
2
2
|
|
|
3
3
|
import OpenAI from "openai";
|
|
4
4
|
|
|
5
|
+
import { ProviderError } from "../../../util/errors.js";
|
|
5
6
|
import { isPlaceholderSentinelText } from "../../placeholder-sentinels.js";
|
|
6
7
|
import {
|
|
7
8
|
EMPTY_ASSISTANT_TURN_PLACEHOLDER,
|
|
8
9
|
OpenAIChatCompletionsProvider,
|
|
9
10
|
type OpenAIChatCompletionsProviderOptions,
|
|
11
|
+
resetReasoningOptOutRejectersForTests,
|
|
10
12
|
} from "../chat-completions-provider.js";
|
|
11
13
|
|
|
12
14
|
type ReasoningDetail = {
|
|
@@ -777,6 +779,65 @@ function rejection(message: string, status = 400): Error {
|
|
|
777
779
|
}
|
|
778
780
|
|
|
779
781
|
describe("reasoning opt-out rejection fallback", () => {
|
|
782
|
+
beforeEach(() => {
|
|
783
|
+
resetReasoningOptOutRejectersForTests();
|
|
784
|
+
});
|
|
785
|
+
|
|
786
|
+
test("remembers a rejecting model and skips the opt-out on later requests", async () => {
|
|
787
|
+
const { provider, requests } = stubProviderWithErrors(
|
|
788
|
+
[rejection("reasoning_effort 'none' is not supported for this model")],
|
|
789
|
+
OK_CHUNKS,
|
|
790
|
+
);
|
|
791
|
+
const send = () =>
|
|
792
|
+
provider.sendMessage(
|
|
793
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
794
|
+
{ config: { effort: "none" } },
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
await send();
|
|
798
|
+
await send();
|
|
799
|
+
|
|
800
|
+
expect(requests).toHaveLength(3);
|
|
801
|
+
expect(
|
|
802
|
+
(requests[2] as { reasoning_effort?: string }).reasoning_effort,
|
|
803
|
+
).toBeUndefined();
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
test("does not remember when success needed a further compat retry", async () => {
|
|
807
|
+
// The broad /reasoning/ match misreads the reasoning_content round-trip
|
|
808
|
+
// error as an opt-out rejection; the request only succeeds after the
|
|
809
|
+
// backfill retry, so the opt-out itself was never proven unsupported.
|
|
810
|
+
const { provider, requests } = stubProviderWithErrors(
|
|
811
|
+
[
|
|
812
|
+
rejection("reasoning_content must be passed back to the API"),
|
|
813
|
+
rejection("reasoning_content must be passed back to the API"),
|
|
814
|
+
],
|
|
815
|
+
OK_CHUNKS,
|
|
816
|
+
);
|
|
817
|
+
const messages = [
|
|
818
|
+
{
|
|
819
|
+
role: "user" as const,
|
|
820
|
+
content: [{ type: "text" as const, text: "hi" }],
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
role: "assistant" as const,
|
|
824
|
+
content: [{ type: "text" as const, text: "hello" }],
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
role: "user" as const,
|
|
828
|
+
content: [{ type: "text" as const, text: "again" }],
|
|
829
|
+
},
|
|
830
|
+
];
|
|
831
|
+
|
|
832
|
+
await provider.sendMessage(messages, { config: { effort: "none" } });
|
|
833
|
+
await provider.sendMessage(messages, { config: { effort: "none" } });
|
|
834
|
+
|
|
835
|
+
expect(requests).toHaveLength(4);
|
|
836
|
+
expect(
|
|
837
|
+
(requests[3] as { reasoning_effort?: string }).reasoning_effort,
|
|
838
|
+
).toBe("none");
|
|
839
|
+
});
|
|
840
|
+
|
|
780
841
|
test("retries once without reasoning params when a model rejects the explicit opt-out", async () => {
|
|
781
842
|
const { provider, requests } = stubProviderWithErrors(
|
|
782
843
|
[rejection("reasoning_effort 'none' is not supported for this model")],
|
|
@@ -928,12 +989,56 @@ describe("thinking-mode tool_choice rejection fallback", () => {
|
|
|
928
989
|
expect(first.reasoning_effort).toBe("high");
|
|
929
990
|
expect(second.tool_choice).toBeUndefined();
|
|
930
991
|
expect(second.reasoning_effort).toBe("high");
|
|
992
|
+
expect(
|
|
993
|
+
(response.rawRequest as { tool_choice?: unknown }).tool_choice,
|
|
994
|
+
).toBeUndefined();
|
|
931
995
|
const text = response.content.find((b) => b.type === "text") as
|
|
932
996
|
| { type: "text"; text: string }
|
|
933
997
|
| undefined;
|
|
934
998
|
expect(text?.text).toBe("ok");
|
|
935
999
|
});
|
|
936
1000
|
|
|
1001
|
+
test("ProviderError.rawRequest is the post-retry params when the retried call fails", async () => {
|
|
1002
|
+
const { provider } = stubProviderWithErrors(
|
|
1003
|
+
[
|
|
1004
|
+
rejection("Thinking mode does not support this tool_choice"),
|
|
1005
|
+
Object.assign(
|
|
1006
|
+
new Error("Thinking mode does not support this tool_choice"),
|
|
1007
|
+
{ status: 400 },
|
|
1008
|
+
),
|
|
1009
|
+
],
|
|
1010
|
+
OK_CHUNKS,
|
|
1011
|
+
);
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1014
|
+
await provider.sendMessage(
|
|
1015
|
+
[{ role: "user", content: [{ type: "text", text: "hi" }] }],
|
|
1016
|
+
{
|
|
1017
|
+
tools: [
|
|
1018
|
+
{
|
|
1019
|
+
name: "bash",
|
|
1020
|
+
description: "Run a shell command",
|
|
1021
|
+
input_schema: { type: "object", properties: {} },
|
|
1022
|
+
},
|
|
1023
|
+
],
|
|
1024
|
+
config: { tool_choice: { type: "none" }, effort: "high" },
|
|
1025
|
+
},
|
|
1026
|
+
);
|
|
1027
|
+
throw new Error("expected ProviderError");
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
expect(error).toBeInstanceOf(ProviderError);
|
|
1030
|
+
const rejected = error as ProviderError;
|
|
1031
|
+
expect(
|
|
1032
|
+
(rejected.rawRequest as { tool_choice?: unknown } | undefined)
|
|
1033
|
+
?.tool_choice,
|
|
1034
|
+
).toBeUndefined();
|
|
1035
|
+
expect(
|
|
1036
|
+
(rejected.rawRequest as { reasoning_effort?: string } | undefined)
|
|
1037
|
+
?.reasoning_effort,
|
|
1038
|
+
).toBe("high");
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
|
|
937
1042
|
test("retries once when Kimi rejects a specified tool_choice in thinking mode", async () => {
|
|
938
1043
|
const { provider, requests } = stubProviderWithErrors(
|
|
939
1044
|
[
|
|
@@ -57,6 +57,10 @@ function stubProviderWithErrors(errors: unknown[]): {
|
|
|
57
57
|
const CHAT_TEMPLATE_400 =
|
|
58
58
|
"Failed to apply chat template: invalid operation: object is not callable (in chat:22)";
|
|
59
59
|
|
|
60
|
+
// Fireworks' renderer error for DeepSeek, verbatim.
|
|
61
|
+
const FIREWORKS_STR_JOIN_400 =
|
|
62
|
+
"sequence item 0: expected str instance, list found";
|
|
63
|
+
|
|
60
64
|
function rejection(message: string, status = 400): Error {
|
|
61
65
|
return Object.assign(new Error(message), { status });
|
|
62
66
|
}
|
|
@@ -103,6 +107,26 @@ describe("chat-template rejection flatten fallback", () => {
|
|
|
103
107
|
expect(text?.text).toBe("ok");
|
|
104
108
|
});
|
|
105
109
|
|
|
110
|
+
test("retries with flattened content on Fireworks' str-join template error", async () => {
|
|
111
|
+
const { provider, requests } = stubProviderWithErrors([
|
|
112
|
+
rejection(FIREWORKS_STR_JOIN_400),
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
await provider.sendMessage([
|
|
116
|
+
{
|
|
117
|
+
role: "user",
|
|
118
|
+
content: [
|
|
119
|
+
{ type: "text", text: "[Image: a cat]" },
|
|
120
|
+
{ type: "text", text: "what is this?" },
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
]);
|
|
124
|
+
|
|
125
|
+
expect(requests).toHaveLength(2);
|
|
126
|
+
const retry = requests[1] as { messages: Array<{ content: unknown }> };
|
|
127
|
+
expect(retry.messages[0].content).toBe("[Image: a cat]\n\nwhat is this?");
|
|
128
|
+
});
|
|
129
|
+
|
|
106
130
|
test("does not retry when a content-parts array carries media (never silently drop an image)", async () => {
|
|
107
131
|
// GIVEN an endpoint whose chat template 400s on structured message content
|
|
108
132
|
const { provider, requests } = stubProviderWithErrors([
|