@vellumai/assistant 0.6.3 → 0.6.4
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 +273 -10
- package/Dockerfile +2 -3
- package/bun.lock +5 -13
- package/docs/backup-troubleshooting.md +52 -0
- package/docs/browser-use-architecture-phase2.md +174 -0
- package/docs/stt-provider-onboarding.md +120 -0
- package/knip.json +12 -2
- package/node_modules/@vellumai/ces-contracts/bun.lock +8 -6
- package/node_modules/@vellumai/ces-contracts/package.json +3 -3
- package/openapi.yaml +982 -72
- package/package.json +4 -6
- package/scripts/generate-openapi.ts +0 -1
- package/scripts/test.sh +73 -18
- package/src/__tests__/agent-image-optimize.test.ts +28 -0
- package/src/__tests__/agent-loop.test.ts +123 -0
- package/src/__tests__/anthropic-provider.test.ts +263 -10
- package/src/__tests__/auto-analysis-end-to-end.test.ts +550 -0
- package/src/__tests__/auto-analysis-prompt.test.ts +50 -0
- package/src/__tests__/browser-fill-credential.test.ts +11 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +2 -2
- package/src/__tests__/browser-skill-endstate.test.ts +31 -7
- package/src/__tests__/btw-routes.test.ts +7 -0
- package/src/__tests__/call-controller.test.ts +581 -20
- package/src/__tests__/catalog-files.test.ts +138 -0
- package/src/__tests__/channel-invite-transport.test.ts +2 -2
- package/src/__tests__/channel-readiness-routes.test.ts +16 -20
- package/src/__tests__/channel-readiness-service.test.ts +12 -7
- package/src/__tests__/checker.test.ts +157 -10
- package/src/__tests__/clawhub-files.test.ts +347 -0
- package/src/__tests__/commit-message-enrichment-service.test.ts +36 -19
- package/src/__tests__/config-analysis.test.ts +100 -0
- package/src/__tests__/config-schema.test.ts +1013 -66
- package/src/__tests__/config-watcher-cleanup-throttle.test.ts +339 -0
- package/src/__tests__/config-watcher.test.ts +43 -8
- package/src/__tests__/contact-store-user-file.test.ts +512 -0
- package/src/__tests__/contacts-write.test.ts +197 -0
- package/src/__tests__/context-window-manager.test.ts +88 -0
- package/src/__tests__/conversation-abort-tool-results.test.ts +2 -0
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +1 -0
- package/src/__tests__/conversation-agent-loop.test.ts +98 -2
- package/src/__tests__/conversation-confirmation-signals.test.ts +135 -0
- package/src/__tests__/conversation-error.test.ts +70 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -4
- package/src/__tests__/conversation-init.benchmark.test.ts +6 -1
- package/src/__tests__/conversation-launcher-skill-regression.test.ts +51 -0
- package/src/__tests__/conversation-list-source.test.ts +145 -0
- package/src/__tests__/conversation-pre-run-repair.test.ts +2 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +2 -0
- package/src/__tests__/conversation-queue.test.ts +901 -60
- package/src/__tests__/conversation-routes-disk-view.test.ts +270 -0
- package/src/__tests__/conversation-runtime-assembly.test.ts +55 -0
- package/src/__tests__/conversation-skill-tools.test.ts +7 -4
- package/src/__tests__/conversation-slash-commands.test.ts +33 -0
- package/src/__tests__/conversation-slash-queue.test.ts +89 -18
- package/src/__tests__/conversation-slash-unknown.test.ts +2 -0
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +226 -0
- package/src/__tests__/conversation-workspace-injection.test.ts +2 -0
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +2 -0
- package/src/__tests__/credential-health-service.test.ts +352 -0
- package/src/__tests__/credential-security-invariants.test.ts +5 -3
- package/src/__tests__/credential-vault-unit.test.ts +379 -3
- package/src/__tests__/credentials-cli.test.ts +40 -16
- package/src/__tests__/cross-provider-web-search.test.ts +146 -35
- package/src/__tests__/deterministic-verification-control-plane.test.ts +10 -1
- package/src/__tests__/device-id.test.ts +112 -0
- package/src/__tests__/docker-signing-key-bootstrap.test.ts +167 -4
- package/src/__tests__/dynamic-skill-workflow-prompt.test.ts +1 -3
- package/src/__tests__/email-html-renderer.test.ts +71 -0
- package/src/__tests__/email-invite-adapter.test.ts +36 -32
- package/src/__tests__/emit-event-signal.test.ts +71 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +75 -8
- package/src/__tests__/fixtures/mock-chrome-extension.ts +11 -0
- package/src/__tests__/gateway-only-enforcement.test.ts +206 -1
- package/src/__tests__/gateway-only-guard.test.ts +0 -1
- package/src/__tests__/gemini-provider.test.ts +64 -0
- package/src/__tests__/get-skill-detail-audit.test.ts +325 -0
- package/src/__tests__/gmail-archive-fallback.test.ts +193 -0
- package/src/__tests__/gmail-archive-gate.test.ts +246 -0
- package/src/__tests__/gmail-preferences.test.ts +117 -0
- package/src/__tests__/headless-browser-interactions.test.ts +43 -0
- package/src/__tests__/headless-browser-mode.test.ts +614 -0
- package/src/__tests__/headless-browser-navigate.test.ts +142 -5
- package/src/__tests__/headless-browser-read-tools.test.ts +11 -0
- package/src/__tests__/headless-browser-snapshot.test.ts +10 -0
- package/src/__tests__/heartbeat-service.test.ts +70 -17
- package/src/__tests__/home-state-routes.test.ts +162 -0
- package/src/__tests__/host-bash-proxy.test.ts +0 -5
- package/src/__tests__/host-browser-e2e-cloud.test.ts +138 -4
- package/src/__tests__/host-browser-e2e-self-hosted.test.ts +4 -4
- package/src/__tests__/host-browser-ws-events-e2e.test.ts +103 -0
- package/src/__tests__/host-cu-proxy.test.ts +0 -5
- package/src/__tests__/identity-intro-cache.test.ts +40 -10
- package/src/__tests__/init-feature-flag-overrides.test.ts +38 -112
- package/src/__tests__/jobs-store-upsert-debounced.test.ts +141 -0
- package/src/__tests__/llm-context-normalization.test.ts +488 -0
- package/src/__tests__/llm-context-route-provider.test.ts +86 -5
- package/src/__tests__/llm-usage-store.test.ts +363 -0
- package/src/__tests__/media-stream-output.test.ts +555 -0
- package/src/__tests__/media-stream-parser.test.ts +374 -0
- package/src/__tests__/media-stream-server-integration.test.ts +1234 -0
- package/src/__tests__/media-stream-stt-session.test.ts +588 -0
- package/src/__tests__/media-turn-detector.test.ts +440 -0
- package/src/__tests__/message-queue.test.ts +125 -0
- package/src/__tests__/migration-export-http.test.ts +6 -6
- package/src/__tests__/migration-import-commit-http.test.ts +8 -6
- package/src/__tests__/migration-import-preflight-http.test.ts +6 -5
- package/src/__tests__/migration-validate-http.test.ts +3 -3
- package/src/__tests__/mock-gateway-ipc.ts +151 -0
- package/src/__tests__/model-intents.test.ts +2 -2
- package/src/__tests__/oauth-apps-routes.test.ts +1 -0
- package/src/__tests__/oauth-cli.test.ts +2 -0
- package/src/__tests__/oauth-connect-orchestrator.test.ts +2 -0
- package/src/__tests__/oauth-provider-serializer.test.ts +1 -0
- package/src/__tests__/oauth-providers-routes.test.ts +2 -0
- package/src/__tests__/oauth-store.test.ts +85 -0
- package/src/__tests__/oauth2-gateway-transport.test.ts +249 -6
- package/src/__tests__/onboarding-template-contract.test.ts +6 -13
- package/src/__tests__/openai-provider.test.ts +176 -0
- package/src/__tests__/openai-responses-cutover-guard.test.ts +184 -0
- package/src/__tests__/openai-responses-provider.test.ts +1105 -0
- package/src/__tests__/openrouter-token-estimation.test.ts +100 -0
- package/src/__tests__/outlook-unsubscribe.test.ts +31 -2
- package/src/__tests__/persona-resolver.test.ts +251 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +4 -0
- package/src/__tests__/platform.test.ts +92 -1
- package/src/__tests__/post-turn-tool-result-truncation.test.ts +47 -0
- package/src/__tests__/prechat-onboarding-contract.test.ts +267 -0
- package/src/__tests__/pricing.test.ts +174 -0
- package/src/__tests__/qdrant-manager.test.ts +29 -8
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +194 -0
- package/src/__tests__/relationship-state-contract.test.ts +175 -0
- package/src/__tests__/relay-server.test.ts +423 -5
- package/src/__tests__/search-skills-unified.test.ts +118 -0
- package/src/__tests__/secret-scanner-executor.test.ts +4 -0
- package/src/__tests__/secure-keys.test.ts +107 -0
- package/src/__tests__/send-endpoint-busy.test.ts +5 -1
- package/src/__tests__/sequence-store.test.ts +1 -1
- package/src/__tests__/server-history-render.test.ts +49 -0
- package/src/__tests__/settings-routes.test.ts +201 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +1 -0
- package/src/__tests__/skills-file-content-endpoint.test.ts +276 -145
- package/src/__tests__/skills-files-catalog-fallback.test.ts +381 -93
- package/src/__tests__/skills.test.ts +5 -2
- package/src/__tests__/skillssh-files.test.ts +446 -0
- package/src/__tests__/slack-block-formatting.test.ts +110 -0
- package/src/__tests__/slack-channel-config.test.ts +564 -1
- package/src/__tests__/stt-catalog-parity.test.ts +282 -0
- package/src/__tests__/stt-stream-session.test.ts +535 -0
- package/src/__tests__/system-prompt.test.ts +112 -26
- package/src/__tests__/telephony-stt-routing.test.ts +329 -0
- package/src/__tests__/terminal-tools.test.ts +18 -7
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +4 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +9 -5
- package/src/__tests__/tool-executor-shell-integration.test.ts +4 -0
- package/src/__tests__/tool-executor.test.ts +33 -24
- package/src/__tests__/tool-result-truncation.test.ts +36 -0
- package/src/__tests__/trust-store.test.ts +7 -1
- package/src/__tests__/trusted-contact-approval-notifier.test.ts +1 -1
- package/src/__tests__/tts-catalog-parity.test.ts +345 -0
- package/src/__tests__/twilio-routes-twiml.test.ts +512 -114
- package/src/__tests__/twilio-routes.test.ts +376 -0
- package/src/__tests__/unicode.test.ts +293 -0
- package/src/__tests__/update-bulletin-format.test.ts +59 -0
- package/src/__tests__/update-bulletin.test.ts +206 -5
- package/src/__tests__/usage-routes.test.ts +25 -4
- package/src/__tests__/user-reference.test.ts +46 -61
- package/src/__tests__/verification-control-plane-policy.test.ts +4 -0
- package/src/__tests__/voice-config-update.test.ts +403 -0
- package/src/__tests__/voice-quality.test.ts +434 -19
- package/src/__tests__/workspace-heartbeat-service.test.ts +7 -0
- package/src/__tests__/workspace-migration-033-stt-service-explicit-config.test.ts +547 -0
- package/src/__tests__/workspace-migration-034-remove-calls-voice-transcription-provider.test.ts +596 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +368 -0
- package/src/__tests__/workspace-migration-meets.test.ts +244 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +14 -20
- package/src/__tests__/workspace-policy.test.ts +2 -0
- package/src/agent/image-optimize.ts +24 -12
- package/src/agent/loop.ts +43 -3
- package/src/backup/__tests__/backup-key.test.ts +152 -0
- package/src/backup/__tests__/backup-worker.test.ts +767 -0
- package/src/backup/__tests__/list-snapshots.test.ts +87 -0
- package/src/backup/__tests__/local-writer.test.ts +218 -0
- package/src/backup/__tests__/offsite-writer.test.ts +641 -0
- package/src/backup/__tests__/paths.test.ts +300 -0
- package/src/backup/__tests__/restore.test.ts +498 -0
- package/src/backup/__tests__/snapshot-lock.test.ts +352 -0
- package/src/backup/__tests__/stream-crypt.test.ts +228 -0
- package/src/backup/backup-key.ts +137 -0
- package/src/backup/backup-worker.ts +459 -0
- package/src/backup/list-snapshots.ts +147 -0
- package/src/backup/local-writer.ts +133 -0
- package/src/backup/offsite-writer.ts +222 -0
- package/src/backup/paths.ts +226 -0
- package/src/backup/restore.ts +322 -0
- package/src/backup/snapshot-lock.ts +431 -0
- package/src/backup/stream-crypt.ts +263 -0
- package/src/bundler/package-resolver.ts +4 -0
- package/src/calls/audio-store.ts +11 -5
- package/src/calls/call-controller.ts +226 -71
- package/src/calls/call-domain.ts +9 -0
- package/src/calls/call-speech-output.ts +190 -0
- package/src/calls/call-transport.ts +77 -0
- package/src/calls/media-stream-audio-transcode.ts +173 -0
- package/src/calls/media-stream-output.ts +660 -0
- package/src/calls/media-stream-parser.ts +300 -0
- package/src/calls/media-stream-protocol.ts +166 -0
- package/src/calls/media-stream-server.ts +592 -0
- package/src/calls/media-stream-stt-session.ts +460 -0
- package/src/calls/media-turn-detector.ts +230 -0
- package/src/calls/relay-server.ts +90 -75
- package/src/calls/resolve-call-tts-provider.ts +136 -0
- package/src/calls/telephony-stt-routing.ts +145 -0
- package/src/calls/tts-call-strategy.ts +161 -0
- package/src/calls/tts-text-sanitizer.ts +32 -16
- package/src/calls/twilio-routes.ts +281 -17
- package/src/calls/voice-quality.ts +78 -35
- package/src/calls/voice-session-bridge.ts +8 -1
- package/src/channels/types.ts +16 -0
- package/src/cli/__tests__/run-assistant-command.ts +11 -1
- package/src/cli/commands/__tests__/backup.test.ts +1165 -0
- package/src/cli/commands/__tests__/domain-register.test.ts +234 -0
- package/src/cli/commands/__tests__/domain-status.test.ts +132 -0
- package/src/cli/commands/__tests__/email-attachment.test.ts +422 -0
- package/src/cli/commands/__tests__/email-download.test.ts +16 -1
- package/src/cli/commands/__tests__/email-list.test.ts +22 -4
- package/src/cli/commands/__tests__/email-register.test.ts +4 -4
- package/src/cli/commands/__tests__/email-send.test.ts +37 -4
- package/src/cli/commands/__tests__/email-status.test.ts +5 -1
- package/src/cli/commands/__tests__/email-unregister.test.ts +34 -5
- package/src/cli/commands/backup.ts +993 -0
- package/src/cli/commands/conversations.ts +77 -0
- package/src/cli/commands/credentials.ts +0 -1
- package/src/cli/commands/domain.ts +210 -0
- package/src/cli/commands/email.ts +255 -3
- package/src/cli/commands/oauth/__tests__/connect.test.ts +12 -0
- package/src/cli/commands/oauth/__tests__/providers-delete.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-register.test.ts +1 -0
- package/src/cli/commands/oauth/__tests__/providers-update.test.ts +1 -0
- package/src/cli/commands/oauth/mode.ts +12 -3
- package/src/cli/commands/oauth/providers.ts +15 -0
- package/src/cli/commands/oauth/shared.ts +2 -1
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +4 -9
- package/src/cli/commands/platform/__tests__/connect.test.ts +6 -0
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +7 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +6 -0
- package/src/cli/program.ts +30 -4
- package/src/config/__tests__/backup-schema.test.ts +134 -0
- package/src/config/assistant-feature-flags.ts +61 -62
- package/src/config/bundled-skills/app-builder/references/CUSTOM_ROUTES.md +37 -1
- package/src/config/bundled-skills/browser/SKILL.md +30 -5
- package/src/config/bundled-skills/browser/TOOLS.json +123 -0
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-status.ts +12 -0
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +17 -0
- package/src/config/bundled-skills/contacts/SKILL.md +2 -2
- package/src/config/bundled-skills/gmail/SKILL.md +53 -7
- package/src/config/bundled-skills/gmail/TOOLS.json +33 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +116 -9
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +138 -11
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +59 -0
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +82 -0
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +113 -17
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/media-processing/SKILL.md +3 -9
- package/src/config/bundled-skills/media-processing/TOOLS.json +1 -6
- package/src/config/bundled-skills/media-processing/__tests__/audio-transcribe.test.ts +125 -0
- package/src/config/bundled-skills/media-processing/__tests__/extract-keyframes.test.ts +181 -0
- package/src/config/bundled-skills/media-processing/__tests__/preprocess-audio.test.ts +141 -0
- package/src/config/bundled-skills/media-processing/services/audio-transcribe.ts +32 -87
- package/src/config/bundled-skills/media-processing/services/preprocess.ts +8 -4
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +0 -10
- package/src/config/bundled-skills/messaging/SKILL.md +3 -3
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +2 -2
- package/src/config/bundled-skills/outlook/SKILL.md +2 -2
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +2 -2
- package/src/config/bundled-skills/phone-calls/SKILL.md +2 -2
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +27 -18
- package/src/config/bundled-skills/phone-calls/references/TROUBLESHOOTING.md +3 -3
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +26 -22
- package/src/config/bundled-skills/slack/SKILL.md +1 -0
- package/src/config/bundled-skills/transcribe/SKILL.md +9 -14
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -7
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.test.ts +256 -0
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +38 -188
- package/src/config/bundled-tool-registry.ts +8 -0
- package/src/config/env-registry.ts +24 -0
- package/src/config/env.ts +34 -10
- package/src/config/feature-flag-registry.json +46 -14
- package/src/config/loader.ts +26 -12
- package/src/config/schema.ts +35 -10
- package/src/config/schemas/__tests__/stt.test.ts +43 -0
- package/src/config/schemas/analysis.ts +51 -0
- package/src/config/schemas/backup.ts +72 -0
- package/src/config/schemas/calls.ts +1 -26
- package/src/config/schemas/elevenlabs.ts +0 -59
- package/src/config/schemas/filing.ts +47 -7
- package/src/config/schemas/heartbeat.ts +27 -5
- package/src/config/schemas/host-browser.ts +47 -1
- package/src/config/schemas/inference.ts +1 -1
- package/src/config/schemas/memory-lifecycle.ts +14 -2
- package/src/config/schemas/services.ts +44 -0
- package/src/config/schemas/stt.ts +59 -0
- package/src/config/schemas/tts.ts +230 -0
- package/src/config/schemas/updates.ts +14 -0
- package/src/config/skills.ts +4 -0
- package/src/config/types.ts +4 -0
- package/src/contacts/contact-store.ts +56 -11
- package/src/contacts/contacts-write.ts +38 -1
- package/src/context/post-turn-tool-result-truncation.ts +3 -2
- package/src/context/tool-result-truncation.ts +2 -1
- package/src/context/window-manager.ts +45 -12
- package/src/credential-execution/executable-discovery.ts +12 -2
- package/src/credential-execution/process-manager.ts +33 -2
- package/src/credential-health/credential-health-service.ts +366 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +324 -0
- package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +497 -0
- package/src/daemon/__tests__/conversation-tool-setup.test.ts +17 -8
- package/src/daemon/__tests__/lifecycle-startup-ordering.test.ts +127 -0
- package/src/daemon/config-watcher.ts +99 -5
- package/src/daemon/conversation-agent-loop-handlers.ts +6 -0
- package/src/daemon/conversation-agent-loop.ts +101 -24
- package/src/daemon/conversation-error.ts +11 -0
- package/src/daemon/conversation-history.ts +40 -6
- package/src/daemon/conversation-launch.ts +220 -0
- package/src/daemon/conversation-lifecycle.ts +59 -9
- package/src/daemon/conversation-messaging.ts +37 -3
- package/src/daemon/conversation-notifiers.ts +5 -0
- package/src/daemon/conversation-process.ts +581 -19
- package/src/daemon/conversation-queue-manager.ts +24 -0
- package/src/daemon/conversation-runtime-assembly.ts +11 -1
- package/src/daemon/conversation-slash.ts +36 -0
- package/src/daemon/conversation-surfaces.ts +94 -4
- package/src/daemon/conversation-tool-setup.ts +25 -0
- package/src/daemon/conversation-usage.ts +7 -4
- package/src/daemon/conversation.ts +86 -28
- package/src/daemon/handlers/config-slack-channel.ts +269 -94
- package/src/daemon/handlers/conversations.ts +4 -1
- package/src/daemon/handlers/shared.ts +22 -0
- package/src/daemon/handlers/skills.ts +321 -77
- package/src/daemon/host-browser-proxy.ts +2 -1
- package/src/daemon/lifecycle.ts +122 -25
- package/src/daemon/message-protocol.ts +6 -0
- package/src/daemon/message-types/conversations.ts +34 -1
- package/src/daemon/message-types/home.ts +40 -0
- package/src/daemon/message-types/meet.ts +143 -0
- package/src/daemon/message-types/messages.ts +14 -0
- package/src/daemon/message-types/schedules.ts +34 -2
- package/src/daemon/message-types/skills.ts +16 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/server.ts +347 -2
- package/src/daemon/shutdown-handlers.ts +32 -4
- package/src/daemon/shutdown-registry.ts +40 -0
- package/src/daemon/tool-side-effects.ts +9 -0
- package/src/email/html-renderer.ts +76 -0
- package/src/heartbeat/heartbeat-service.ts +93 -7
- package/src/home/__tests__/assistant-feed-authoring.test.ts +156 -0
- package/src/home/__tests__/emit-feed-event.test.ts +169 -0
- package/src/home/__tests__/feed-scheduler.test.ts +194 -0
- package/src/home/__tests__/feed-types.test.ts +275 -0
- package/src/home/__tests__/feed-writer.test.ts +688 -0
- package/src/home/__tests__/phase5-exit-criteria.test.ts +212 -0
- package/src/home/__tests__/platform-gmail-digest.test.ts +222 -0
- package/src/home/__tests__/progress-formula.test.ts +213 -0
- package/src/home/__tests__/relationship-state-writer.test.ts +740 -0
- package/src/home/__tests__/rollup-producer.test.ts +398 -0
- package/src/home/assistant-feed-authoring.ts +124 -0
- package/src/home/emit-feed-event.ts +158 -0
- package/src/home/feed-scheduler.ts +247 -0
- package/src/home/feed-types.ts +181 -0
- package/src/home/feed-writer.ts +469 -0
- package/src/home/platform-gmail-digest.ts +163 -0
- package/src/home/progress-formula.ts +86 -0
- package/src/home/relationship-state-writer.ts +824 -0
- package/src/home/relationship-state.ts +143 -0
- package/src/home/rollup-producer.ts +384 -0
- package/src/hooks/runner.ts +7 -0
- package/src/inbound/platform-callback-registration.ts +12 -3
- package/src/inbound/public-ingress-urls.ts +12 -0
- package/src/instrument.ts +1 -1
- package/src/ipc/__tests__/cli-ipc.test.ts +200 -0
- package/src/ipc/cli-client.ts +151 -0
- package/src/ipc/cli-server.ts +234 -0
- package/src/ipc/gateway-client.ts +180 -0
- package/src/ipc/routes/index.ts +5 -0
- package/src/ipc/routes/wake-conversation.ts +19 -0
- package/src/memory/__tests__/auto-analysis-enqueue.test.ts +356 -0
- package/src/memory/__tests__/auto-analysis-guard.test.ts +57 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +232 -0
- package/src/memory/__tests__/find-analysis-conversation.test.ts +196 -0
- package/src/memory/app-store.ts +1 -1
- package/src/memory/attachments-store.ts +70 -0
- package/src/memory/auto-analysis-enqueue.ts +127 -0
- package/src/memory/auto-analysis-guard.ts +27 -0
- package/src/memory/cleanup-schedule-state.ts +37 -0
- package/src/memory/conversation-analyze-job.ts +73 -0
- package/src/memory/conversation-crud.ts +99 -0
- package/src/memory/conversation-disk-view.ts +7 -0
- package/src/memory/conversation-group-migration.ts +34 -2
- package/src/memory/conversation-queries.ts +6 -5
- package/src/memory/db-init.ts +6 -0
- package/src/memory/db-maintenance.ts +108 -0
- package/src/memory/db.ts +1 -0
- package/src/memory/graph/conversation-graph-memory.ts +15 -0
- package/src/memory/graph/extraction.test.ts +23 -0
- package/src/memory/graph/extraction.ts +8 -0
- package/src/memory/graph/retriever.ts +27 -18
- package/src/memory/graph/scoring.test.ts +186 -0
- package/src/memory/graph/scoring.ts +31 -1
- package/src/memory/graph/tools.ts +1 -1
- package/src/memory/group-crud.ts +6 -1
- package/src/memory/indexer.ts +95 -16
- package/src/memory/job-handlers/cleanup.ts +11 -8
- package/src/memory/job-handlers/conversation-starters.ts +16 -10
- package/src/memory/jobs-store.ts +64 -4
- package/src/memory/jobs-worker.ts +22 -9
- package/src/memory/llm-usage-store.ts +92 -56
- package/src/memory/migrations/219-oauth-providers-token-exchange-body-format.ts +15 -0
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +190 -0
- package/src/memory/migrations/221-conversations-archived-at.ts +16 -0
- package/src/memory/migrations/index.ts +6 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/qdrant-manager.ts +43 -16
- package/src/memory/schema/conversations.ts +2 -0
- package/src/memory/schema/oauth.ts +3 -0
- package/src/memory/usage-buckets.ts +396 -0
- package/src/messaging/providers/gmail/client.ts +57 -6
- package/src/messaging/providers/slack/__tests__/adapter-token-routing.test.ts +282 -0
- package/src/messaging/providers/slack/adapter.ts +143 -38
- package/src/messaging/providers/slack/client.ts +16 -0
- package/src/messaging/providers/slack/types.ts +4 -0
- package/src/notifications/decision-engine.ts +3 -3
- package/src/notifications/signal.ts +5 -0
- package/src/oauth/__tests__/identity-verifier.test.ts +1 -0
- package/src/oauth/byo-connection.test.ts +18 -1
- package/src/oauth/byo-connection.ts +3 -1
- package/src/oauth/connect-orchestrator.ts +2 -0
- package/src/oauth/connection-resolver.ts +6 -2
- package/src/oauth/connection.ts +2 -0
- package/src/oauth/oauth-store.ts +9 -0
- package/src/oauth/platform-connection.test.ts +98 -0
- package/src/oauth/platform-connection.ts +52 -31
- package/src/oauth/seed-providers.ts +7 -0
- package/src/permissions/checker.ts +16 -6
- package/src/permissions/defaults.ts +49 -1
- package/src/permissions/trust-store.ts +3 -3
- package/src/permissions/workspace-policy.ts +3 -0
- package/src/platform/client.test.ts +10 -0
- package/src/platform/sync-identity.ts +129 -0
- package/src/prompts/persona-resolver.ts +126 -2
- package/src/prompts/system-prompt.ts +59 -18
- package/src/prompts/templates/BOOTSTRAP.md +5 -5
- package/src/prompts/templates/SOUL.md +3 -1
- package/src/prompts/templates/UPDATES.md +12 -0
- package/src/prompts/templates/channels/slack.md +20 -0
- package/src/prompts/update-bulletin-format.ts +26 -9
- package/src/prompts/update-bulletin.ts +34 -23
- package/src/prompts/user-reference.ts +20 -17
- package/src/providers/__tests__/provider-secret-catalog.test.ts +42 -0
- package/src/providers/anthropic/client.ts +157 -61
- package/src/providers/fireworks/client.ts +2 -2
- package/src/providers/gemini/client.ts +9 -1
- package/src/providers/model-catalog.ts +6 -0
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/ollama/client.ts +2 -2
- package/src/providers/openai/chat-completions-provider.ts +474 -0
- package/src/providers/openai/client.ts +25 -440
- package/src/providers/openai/responses-provider.ts +502 -0
- package/src/providers/openrouter/client.ts +101 -4
- package/src/providers/provider-secret-catalog.ts +139 -0
- package/src/providers/registry.ts +2 -2
- package/src/providers/retry.ts +14 -3
- package/src/providers/speech-to-text/__tests__/provider-catalog.test.ts +251 -0
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +828 -0
- package/src/providers/speech-to-text/deepgram-realtime.test.ts +980 -0
- package/src/providers/speech-to-text/deepgram-realtime.ts +767 -0
- package/src/providers/speech-to-text/deepgram.test.ts +332 -0
- package/src/providers/speech-to-text/deepgram.ts +115 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.test.ts +743 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +625 -0
- package/src/providers/speech-to-text/google-gemini.test.ts +226 -0
- package/src/providers/speech-to-text/google-gemini.ts +101 -0
- package/src/providers/speech-to-text/openai-whisper-stream.test.ts +564 -0
- package/src/providers/speech-to-text/openai-whisper-stream.ts +381 -0
- package/src/providers/speech-to-text/openai-whisper.test.ts +1 -37
- package/src/providers/speech-to-text/openai-whisper.ts +63 -33
- package/src/providers/speech-to-text/provider-catalog.ts +306 -0
- package/src/providers/speech-to-text/resolve.ts +386 -6
- package/src/providers/types.ts +9 -0
- package/src/runtime/AGENTS.md +43 -1
- package/src/runtime/__tests__/agent-wake.test.ts +831 -0
- package/src/runtime/__tests__/runtime-mode.test.ts +62 -0
- package/src/runtime/__tests__/slack-block-formatting.test.ts +481 -0
- package/src/runtime/agent-wake.ts +512 -0
- package/src/runtime/auth/__tests__/route-policy.test.ts +40 -0
- package/src/runtime/auth/route-policy.ts +30 -5
- package/src/runtime/auth/token-service.ts +56 -1
- package/src/runtime/btw-sidechain.ts +2 -0
- package/src/runtime/capability-tokens.ts +10 -10
- package/src/runtime/channel-invite-transport.ts +1 -1
- package/src/runtime/channel-invite-transports/email.ts +14 -6
- package/src/runtime/channel-readiness-service.ts +12 -22
- package/src/runtime/chrome-extension-registry.ts +38 -2
- package/src/runtime/http-server.ts +395 -10
- package/src/runtime/http-types.ts +6 -2
- package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +36 -0
- package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +360 -0
- package/src/runtime/migrations/migration-transport.ts +1 -0
- package/src/runtime/migrations/migration-wizard.ts +1 -0
- package/src/runtime/migrations/vbundle-import-analyzer.ts +77 -1
- package/src/runtime/migrations/vbundle-importer.ts +34 -0
- package/src/runtime/pending-interactions.ts +0 -11
- package/src/runtime/routes/__tests__/backup-routes.test.ts +967 -0
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +507 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +208 -0
- package/src/runtime/routes/__tests__/stt-routes.test.ts +406 -0
- package/src/runtime/routes/__tests__/tts-routes.test.ts +474 -0
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +148 -17
- package/src/runtime/routes/app-management-routes.ts +12 -18
- package/src/runtime/routes/attachment-routes.test.ts +9 -3
- package/src/runtime/routes/attachment-routes.ts +216 -17
- package/src/runtime/routes/backup-routes.ts +519 -0
- package/src/runtime/routes/browser-extension-pair-routes.ts +82 -23
- package/src/runtime/routes/btw-routes.ts +8 -6
- package/src/runtime/routes/contact-routes.test.ts +298 -0
- package/src/runtime/routes/contact-routes.ts +132 -5
- package/src/runtime/routes/conversation-analysis-routes.ts +22 -142
- package/src/runtime/routes/conversation-management-routes.ts +115 -0
- package/src/runtime/routes/conversation-routes.ts +367 -146
- package/src/runtime/routes/filing-routes.ts +93 -0
- package/src/runtime/routes/home-feed-routes.ts +334 -0
- package/src/runtime/routes/home-state-routes.ts +138 -0
- package/src/runtime/routes/host-browser-routes.ts +3 -14
- package/src/runtime/routes/identity-intro-cache.ts +7 -3
- package/src/runtime/routes/identity-routes.ts +3 -17
- package/src/runtime/routes/inbound-stages/transcribe-audio.test.ts +46 -39
- package/src/runtime/routes/inbound-stages/transcribe-audio.ts +15 -15
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +137 -0
- package/src/runtime/routes/integrations/slack/__tests__/share.test.ts +179 -0
- package/src/runtime/routes/integrations/slack/channel.ts +11 -3
- package/src/runtime/routes/integrations/slack/share.ts +45 -7
- package/src/runtime/routes/llm-context-normalization.ts +303 -0
- package/src/runtime/routes/memory-item-routes.test.ts +3 -2
- package/src/runtime/routes/migration-routes.ts +40 -5
- package/src/runtime/routes/settings-routes.ts +22 -5
- package/src/runtime/routes/skills-routes.ts +76 -7
- package/src/runtime/routes/stt-routes.ts +233 -0
- package/src/runtime/routes/surface-action-routes.ts +41 -2
- package/src/runtime/routes/tts-routes.ts +108 -24
- package/src/runtime/routes/usage-routes.ts +30 -2
- package/src/runtime/routes/user-route-dispatcher.ts +50 -5
- package/src/runtime/routes/user-routes.ts +13 -1
- package/src/runtime/routes/work-items-routes.ts +8 -1
- package/src/runtime/runtime-mode.ts +33 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +444 -0
- package/src/runtime/services/__tests__/analyze-deps-singleton.test.ts +67 -0
- package/src/runtime/services/__tests__/auto-analysis-prompt.test.ts +53 -0
- package/src/runtime/services/__tests__/manual-analysis-prompt.test.ts +41 -0
- package/src/runtime/services/analyze-conversation.ts +344 -0
- package/src/runtime/services/analyze-deps-singleton.ts +32 -0
- package/src/runtime/services/auto-analysis-prompt.ts +55 -0
- package/src/runtime/skill-route-registry.ts +49 -0
- package/src/runtime/slack-block-formatting.ts +437 -10
- package/src/schedule/scheduler.ts +50 -0
- package/src/security/oauth2.ts +26 -4
- package/src/security/secure-keys.ts +25 -2
- package/src/security/token-manager.ts +8 -0
- package/src/sequence/engine.ts +23 -0
- package/src/sequence/types.ts +1 -1
- package/src/skills/catalog-files.ts +64 -2
- package/src/skills/category-inference.ts +122 -0
- package/src/skills/clawhub-files.ts +213 -0
- package/src/skills/clawhub.ts +84 -23
- package/src/skills/skill-file-provider.ts +40 -0
- package/src/skills/skillssh-files.ts +395 -0
- package/src/skills/skillssh-registry.ts +4 -4
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +392 -0
- package/src/stt/__tests__/types.test.ts +89 -0
- package/src/stt/daemon-batch-transcriber.ts +195 -0
- package/src/stt/stt-stream-session.ts +499 -0
- package/src/stt/types.ts +330 -0
- package/src/stt/wav-encoder.test.ts +373 -0
- package/src/stt/wav-encoder.ts +175 -0
- package/src/subagent/manager.ts +38 -14
- package/src/tools/browser/__tests__/browser-mode.test.ts +119 -0
- package/src/tools/browser/__tests__/browser-status.test.ts +123 -0
- package/src/tools/browser/browser-execution.ts +1163 -23
- package/src/tools/browser/browser-manager.ts +45 -0
- package/src/tools/browser/browser-mode-constants.ts +12 -0
- package/src/tools/browser/browser-mode.ts +92 -0
- package/src/tools/browser/browser-status-constants.ts +33 -0
- package/src/tools/browser/cdp-client/__tests__/cdp-inspect-client.test.ts +393 -0
- package/src/tools/browser/cdp-client/__tests__/extension-cdp-client.test.ts +29 -0
- package/src/tools/browser/cdp-client/__tests__/factory.test.ts +1648 -32
- package/src/tools/browser/cdp-client/cdp-inspect/__tests__/discovery.test.ts +264 -0
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +183 -17
- package/src/tools/browser/cdp-client/cdp-inspect-client.ts +254 -21
- package/src/tools/browser/cdp-client/errors.ts +15 -0
- package/src/tools/browser/cdp-client/extension-cdp-client.ts +39 -16
- package/src/tools/browser/cdp-client/factory.ts +797 -87
- package/src/tools/browser/cdp-client/index.ts +16 -2
- package/src/tools/browser/cdp-client/types.ts +68 -0
- package/src/tools/credentials/vault.ts +35 -6
- package/src/tools/network/web-fetch.ts +5 -2
- package/src/tools/network/web-search.ts +5 -2
- package/src/tools/shared/shell-output.ts +3 -1
- package/src/tools/side-effects.ts +2 -0
- package/src/tools/skills/sandbox-runner.ts +3 -2
- package/src/tools/terminal/safe-env.ts +10 -2
- package/src/tools/terminal/shell.ts +15 -4
- package/src/tools/tool-manifest.ts +21 -0
- package/src/tools/types.ts +17 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tts/__tests__/provider-adapters.test.ts +834 -0
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +196 -0
- package/src/tts/__tests__/provider-catalog.test.ts +183 -0
- package/src/tts/__tests__/provider-registry.test.ts +90 -0
- package/src/tts/provider-catalog.ts +201 -0
- package/src/tts/provider-registry.ts +73 -0
- package/src/tts/providers/deepgram-provider.ts +219 -0
- package/src/tts/providers/elevenlabs-provider.ts +211 -0
- package/src/tts/providers/fish-audio-provider.ts +183 -0
- package/src/tts/providers/index.ts +42 -0
- package/src/tts/providers/register-builtins.ts +130 -0
- package/src/tts/synthesize-text.ts +110 -0
- package/src/tts/tts-config-resolver.ts +78 -0
- package/src/tts/types.ts +153 -0
- package/src/types/onboarding-context.ts +7 -0
- package/src/util/abort-reasons.ts +58 -0
- package/src/util/device-id.ts +32 -16
- package/src/util/errors.ts +9 -1
- package/src/util/platform.ts +54 -10
- package/src/util/pricing.ts +66 -3
- package/src/util/spawn.ts +1 -1
- package/src/util/truncate.ts +4 -2
- package/src/util/unicode.ts +201 -0
- package/src/version.ts +19 -24
- package/src/watcher/engine.ts +23 -0
- package/src/watcher/watcher-store.ts +31 -0
- package/src/workspace/migrations/003-seed-device-id.ts +9 -3
- package/src/workspace/migrations/017-seed-persona-dirs.ts +68 -4
- package/src/workspace/migrations/029-seed-pkb.ts +1 -1
- package/src/workspace/migrations/031-drop-user-md.ts +317 -0
- package/src/workspace/migrations/031-llm-log-retention-zero-to-null.ts +73 -0
- package/src/workspace/migrations/032-tts-provider-unification.ts +227 -0
- package/src/workspace/migrations/033-stt-service-explicit-config.ts +122 -0
- package/src/workspace/migrations/034-remove-calls-voice-transcription-provider.ts +215 -0
- package/src/workspace/migrations/035-seed-slack-channel-persona.ts +50 -0
- package/src/workspace/migrations/036-update-pkb-index-bar.ts +37 -0
- package/src/workspace/migrations/037-create-meets-dir.ts +61 -0
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/top-level-renderer.ts +13 -1
- package/src/workspace/turn-commit.ts +31 -0
- package/src/__tests__/email-cli.test.ts +0 -297
- package/src/__tests__/email-service-config-fallback.test.ts +0 -102
- package/src/cli/commands/browser-relay.ts +0 -466
- package/src/email/guardrails.ts +0 -221
- package/src/email/provider.ts +0 -117
- package/src/email/providers/agentmail.ts +0 -361
- package/src/email/providers/index.ts +0 -65
- package/src/email/service.ts +0 -384
- package/src/email/types.ts +0 -126
- package/src/prompts/templates/USER.md +0 -13
- package/src/providers/speech-to-text/types.ts +0 -17
- package/src/runtime/routes/browser-cdp-routes.ts +0 -229
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timezone-aware bucketing for usage events.
|
|
3
|
+
*
|
|
4
|
+
* SQLite's `strftime(..., 'unixepoch')` is UTC-only, which means bucket
|
|
5
|
+
* boundaries (both daily and hourly) can't respect the user's timezone when
|
|
6
|
+
* computed in SQL. This module performs bucketing in JavaScript using
|
|
7
|
+
* `Intl.DateTimeFormat` so boundaries align to local-hour / local-day in any
|
|
8
|
+
* IANA timezone — including fractional offsets (e.g. Asia/Kolkata, UTC+5:30)
|
|
9
|
+
* and DST transitions.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { UsageDayBucket, UsageTimeRange } from "./llm-usage-store.js";
|
|
13
|
+
|
|
14
|
+
/** Minimal raw row shape needed for bucketing. */
|
|
15
|
+
export interface UsageEventBucketRow {
|
|
16
|
+
created_at: number;
|
|
17
|
+
input_tokens: number;
|
|
18
|
+
output_tokens: number;
|
|
19
|
+
estimated_cost_usd: number | null;
|
|
20
|
+
llm_call_count: number | null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Parts extracted from a single Date in a target timezone. */
|
|
24
|
+
interface LocalParts {
|
|
25
|
+
year: number;
|
|
26
|
+
month: number;
|
|
27
|
+
day: number;
|
|
28
|
+
hour: number;
|
|
29
|
+
/** UTC offset in minutes at this instant in the target tz. */
|
|
30
|
+
offsetMinutes: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate that `tz` is a recognized IANA timezone identifier.
|
|
35
|
+
* Throws a tagged error the route layer can surface as 400.
|
|
36
|
+
*/
|
|
37
|
+
export function validateTimezone(tz: string): void {
|
|
38
|
+
try {
|
|
39
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
40
|
+
} catch {
|
|
41
|
+
const err = new Error(
|
|
42
|
+
`Invalid IANA timezone identifier: "${tz}". Expected a value like "America/Los_Angeles" or "UTC".`,
|
|
43
|
+
);
|
|
44
|
+
(err as Error & { code?: string }).code = "INVALID_TIMEZONE";
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Extract local wall-clock parts + UTC offset for a given instant in `tz`.
|
|
51
|
+
*
|
|
52
|
+
* Uses `formatToParts` with a fixed format to reliably get numeric fields and
|
|
53
|
+
* the short GMT offset. The GMT offset disambiguates the duplicate DST
|
|
54
|
+
* fall-back hour (e.g. 1am EDT and 1am EST both format to "01" in local time
|
|
55
|
+
* but have different offsets).
|
|
56
|
+
*/
|
|
57
|
+
function getLocalParts(epochMillis: number, tz: string): LocalParts {
|
|
58
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
59
|
+
timeZone: tz,
|
|
60
|
+
year: "numeric",
|
|
61
|
+
month: "2-digit",
|
|
62
|
+
day: "2-digit",
|
|
63
|
+
hour: "2-digit",
|
|
64
|
+
minute: "2-digit",
|
|
65
|
+
second: "2-digit",
|
|
66
|
+
hour12: false,
|
|
67
|
+
timeZoneName: "shortOffset",
|
|
68
|
+
});
|
|
69
|
+
const parts = formatter.formatToParts(new Date(epochMillis));
|
|
70
|
+
let year = 0;
|
|
71
|
+
let month = 0;
|
|
72
|
+
let day = 0;
|
|
73
|
+
let hour = 0;
|
|
74
|
+
let offsetMinutes = 0;
|
|
75
|
+
for (const part of parts) {
|
|
76
|
+
switch (part.type) {
|
|
77
|
+
case "year":
|
|
78
|
+
year = Number(part.value);
|
|
79
|
+
break;
|
|
80
|
+
case "month":
|
|
81
|
+
month = Number(part.value);
|
|
82
|
+
break;
|
|
83
|
+
case "day":
|
|
84
|
+
day = Number(part.value);
|
|
85
|
+
break;
|
|
86
|
+
case "hour":
|
|
87
|
+
// `hour12: false` can still yield "24" at midnight in some locales; clamp.
|
|
88
|
+
hour = Number(part.value) % 24;
|
|
89
|
+
break;
|
|
90
|
+
case "timeZoneName":
|
|
91
|
+
offsetMinutes = parseShortOffset(part.value);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return { year, month, day, hour, offsetMinutes };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Parse the `shortOffset` string returned by `Intl.DateTimeFormat` into minutes.
|
|
100
|
+
*
|
|
101
|
+
* Examples: "GMT-8" → -480, "GMT+5:30" → 330, "GMT" → 0, "UTC" → 0.
|
|
102
|
+
*/
|
|
103
|
+
function parseShortOffset(value: string): number {
|
|
104
|
+
const match = value.match(/GMT([+-])(\d{1,2})(?::(\d{2}))?/);
|
|
105
|
+
if (!match) return 0;
|
|
106
|
+
const sign = match[1] === "-" ? -1 : 1;
|
|
107
|
+
const hours = Number(match[2]);
|
|
108
|
+
const minutes = match[3] ? Number(match[3]) : 0;
|
|
109
|
+
return sign * (hours * 60 + minutes);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function pad2(n: number): string {
|
|
113
|
+
return n < 10 ? `0${n}` : `${n}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Build the canonical daily bucket key in `tz`: "YYYY-MM-DD". */
|
|
117
|
+
function dayKey(parts: LocalParts): string {
|
|
118
|
+
return `${parts.year}-${pad2(parts.month)}-${pad2(parts.day)}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build the canonical hourly bucket key in `tz`: "YYYY-MM-DD HH:00".
|
|
123
|
+
*
|
|
124
|
+
* For the DST fall-back hour we need to keep the two physically distinct
|
|
125
|
+
* hours separate even though they share the same local wall-clock label.
|
|
126
|
+
* We track them by their UTC offset internally via a separate map key,
|
|
127
|
+
* but the returned `date` string is identical for both so the display label
|
|
128
|
+
* matches. The caller uses a compound map key that includes the offset.
|
|
129
|
+
*/
|
|
130
|
+
function hourKey(parts: LocalParts): string {
|
|
131
|
+
return `${parts.year}-${pad2(parts.month)}-${pad2(parts.day)} ${pad2(parts.hour)}:00`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Compose a map key that separates duplicate DST fall-back hours. */
|
|
135
|
+
function hourMapKey(parts: LocalParts): string {
|
|
136
|
+
return `${hourKey(parts)}|${parts.offsetMinutes}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Advance `parts` by one local hour, returning the UTC instant of the next
|
|
141
|
+
* hour's start. Used to walk a range and fill empty buckets.
|
|
142
|
+
*
|
|
143
|
+
* We compute the next hour by taking the current instant, adding 1 hour of
|
|
144
|
+
* UTC wall time, and then projecting it back into local parts. This skips the
|
|
145
|
+
* spring-forward hour (23-hour day) and correctly emits both halves of the
|
|
146
|
+
* fall-back hour (25-hour day).
|
|
147
|
+
*/
|
|
148
|
+
function addOneHourUtc(epochMillis: number): number {
|
|
149
|
+
return epochMillis + 60 * 60 * 1000;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function addOneDayUtc(epochMillis: number): number {
|
|
153
|
+
return epochMillis + 24 * 60 * 60 * 1000;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Find the UTC instant corresponding to the start of the local hour that
|
|
158
|
+
* contains `epochMillis` in `tz`.
|
|
159
|
+
*
|
|
160
|
+
* Because Intl doesn't give us this directly, we approximate by:
|
|
161
|
+
* 1. Getting the local parts of `epochMillis`
|
|
162
|
+
* 2. Subtracting the minutes/seconds of the wall clock
|
|
163
|
+
*
|
|
164
|
+
* This returns a UTC instant that, when formatted in `tz`, has hour == parts.hour
|
|
165
|
+
* and minute/second == 0.
|
|
166
|
+
*/
|
|
167
|
+
function alignToLocalHourStart(epochMillis: number, tz: string): number {
|
|
168
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
169
|
+
timeZone: tz,
|
|
170
|
+
minute: "2-digit",
|
|
171
|
+
second: "2-digit",
|
|
172
|
+
hour12: false,
|
|
173
|
+
});
|
|
174
|
+
const parts = formatter.formatToParts(new Date(epochMillis));
|
|
175
|
+
let minute = 0;
|
|
176
|
+
let second = 0;
|
|
177
|
+
for (const part of parts) {
|
|
178
|
+
if (part.type === "minute") minute = Number(part.value);
|
|
179
|
+
if (part.type === "second") second = Number(part.value);
|
|
180
|
+
}
|
|
181
|
+
return epochMillis - (minute * 60 + second) * 1000;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Find the UTC instant corresponding to local midnight of the day containing
|
|
186
|
+
* `epochMillis` in `tz`.
|
|
187
|
+
*
|
|
188
|
+
* DST-aware: the UTC offset at local midnight can differ from the offset at
|
|
189
|
+
* `epochMillis` when a DST transition falls earlier in the same local day.
|
|
190
|
+
* Naively subtracting the current wall-clock hours/minutes/seconds would land
|
|
191
|
+
* on a UTC instant that formats as the wrong local date (e.g. 23:00 of the
|
|
192
|
+
* previous day after spring-forward). We instead derive midnight by locating
|
|
193
|
+
* the UTC instant whose local formatting is Y-M-D 00:00 in `tz`.
|
|
194
|
+
*/
|
|
195
|
+
function alignToLocalDayStart(epochMillis: number, tz: string): number {
|
|
196
|
+
const parts = getLocalParts(epochMillis, tz);
|
|
197
|
+
// UTC midnight of the same Y-M-D is a close-but-incorrect guess. Its offset
|
|
198
|
+
// approximates the offset at local midnight — one iteration of correction
|
|
199
|
+
// handles the common case where a DST transition sits between the two.
|
|
200
|
+
const utcMidnightGuess = Date.UTC(parts.year, parts.month - 1, parts.day);
|
|
201
|
+
const offset1 = getLocalParts(utcMidnightGuess, tz).offsetMinutes;
|
|
202
|
+
let candidate = utcMidnightGuess - offset1 * 60_000;
|
|
203
|
+
const offset2 = getLocalParts(candidate, tz).offsetMinutes;
|
|
204
|
+
if (offset2 !== offset1) {
|
|
205
|
+
candidate = utcMidnightGuess - offset2 * 60_000;
|
|
206
|
+
}
|
|
207
|
+
return candidate;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Format a short human-readable hour label in `tz`, e.g. "3pm". */
|
|
211
|
+
function formatHourLabel(epochMillis: number, tz: string): string {
|
|
212
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
213
|
+
timeZone: tz,
|
|
214
|
+
hour: "numeric",
|
|
215
|
+
hour12: true,
|
|
216
|
+
});
|
|
217
|
+
// "3 PM" → "3pm"
|
|
218
|
+
return formatter.format(new Date(epochMillis)).replace(/\s/g, "").toLowerCase();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Format a short human-readable day label in `tz`, e.g. "Apr 11". */
|
|
222
|
+
function formatDayLabel(epochMillis: number, tz: string): string {
|
|
223
|
+
const formatter = new Intl.DateTimeFormat("en-US", {
|
|
224
|
+
timeZone: tz,
|
|
225
|
+
month: "short",
|
|
226
|
+
day: "numeric",
|
|
227
|
+
});
|
|
228
|
+
return formatter.format(new Date(epochMillis));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface MutableBucket {
|
|
232
|
+
bucketId: string;
|
|
233
|
+
date: string;
|
|
234
|
+
displayLabel: string;
|
|
235
|
+
totalInputTokens: number;
|
|
236
|
+
totalOutputTokens: number;
|
|
237
|
+
totalEstimatedCostUsd: number;
|
|
238
|
+
eventCount: number;
|
|
239
|
+
/** Sort key — the UTC instant of the bucket start. */
|
|
240
|
+
sortKey: number;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function emptyBucket(
|
|
244
|
+
bucketId: string,
|
|
245
|
+
date: string,
|
|
246
|
+
displayLabel: string,
|
|
247
|
+
sortKey: number,
|
|
248
|
+
): MutableBucket {
|
|
249
|
+
return {
|
|
250
|
+
bucketId,
|
|
251
|
+
date,
|
|
252
|
+
displayLabel,
|
|
253
|
+
totalInputTokens: 0,
|
|
254
|
+
totalOutputTokens: 0,
|
|
255
|
+
totalEstimatedCostUsd: 0,
|
|
256
|
+
eventCount: 0,
|
|
257
|
+
sortKey,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function addEventToBucket(
|
|
262
|
+
bucket: MutableBucket,
|
|
263
|
+
row: UsageEventBucketRow,
|
|
264
|
+
): void {
|
|
265
|
+
bucket.totalInputTokens += row.input_tokens;
|
|
266
|
+
bucket.totalOutputTokens += row.output_tokens;
|
|
267
|
+
bucket.totalEstimatedCostUsd += row.estimated_cost_usd ?? 0;
|
|
268
|
+
bucket.eventCount += row.llm_call_count ?? 1;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
function finalize(buckets: Map<string, MutableBucket>): UsageDayBucket[] {
|
|
272
|
+
return Array.from(buckets.values())
|
|
273
|
+
.sort((a, b) => a.sortKey - b.sortKey)
|
|
274
|
+
.map(({ bucketId, date, displayLabel, ...rest }) => ({
|
|
275
|
+
bucketId,
|
|
276
|
+
date,
|
|
277
|
+
displayLabel,
|
|
278
|
+
totalInputTokens: rest.totalInputTokens,
|
|
279
|
+
totalOutputTokens: rest.totalOutputTokens,
|
|
280
|
+
totalEstimatedCostUsd: rest.totalEstimatedCostUsd,
|
|
281
|
+
eventCount: rest.eventCount,
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Options for bucketing behavior. */
|
|
286
|
+
export interface BucketingOptions {
|
|
287
|
+
/**
|
|
288
|
+
* When true, emit a zero-value bucket for every hour (or day) in the
|
|
289
|
+
* requested range even if no events fall inside it. This produces a
|
|
290
|
+
* continuous chart axis for empty periods but adds noise to text output
|
|
291
|
+
* like the CLI. Defaults to false.
|
|
292
|
+
*/
|
|
293
|
+
fillEmpty?: boolean;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Bucket raw usage events by local hour in the given timezone.
|
|
298
|
+
*
|
|
299
|
+
* DST fall-back duplicate hours are preserved as separate buckets with
|
|
300
|
+
* identical display labels. When `options.fillEmpty` is true, the returned
|
|
301
|
+
* array contains a zero-value bucket for every local hour in the range.
|
|
302
|
+
*/
|
|
303
|
+
export function bucketEventsByHour(
|
|
304
|
+
events: UsageEventBucketRow[],
|
|
305
|
+
range: UsageTimeRange,
|
|
306
|
+
tz: string,
|
|
307
|
+
options: BucketingOptions = {},
|
|
308
|
+
): UsageDayBucket[] {
|
|
309
|
+
validateTimezone(tz);
|
|
310
|
+
const buckets = new Map<string, MutableBucket>();
|
|
311
|
+
|
|
312
|
+
if (options.fillEmpty) {
|
|
313
|
+
let cursor = alignToLocalHourStart(range.from, tz);
|
|
314
|
+
let safety = 0;
|
|
315
|
+
const maxIterations = 200_000;
|
|
316
|
+
while (cursor <= range.to && safety++ < maxIterations) {
|
|
317
|
+
const parts = getLocalParts(cursor, tz);
|
|
318
|
+
const key = hourMapKey(parts);
|
|
319
|
+
if (!buckets.has(key)) {
|
|
320
|
+
buckets.set(
|
|
321
|
+
key,
|
|
322
|
+
emptyBucket(key, hourKey(parts), formatHourLabel(cursor, tz), cursor),
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
cursor = addOneHourUtc(cursor);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
for (const row of events) {
|
|
330
|
+
const parts = getLocalParts(row.created_at, tz);
|
|
331
|
+
const key = hourMapKey(parts);
|
|
332
|
+
let bucket = buckets.get(key);
|
|
333
|
+
if (!bucket) {
|
|
334
|
+
const hourStart = alignToLocalHourStart(row.created_at, tz);
|
|
335
|
+
bucket = emptyBucket(
|
|
336
|
+
key,
|
|
337
|
+
hourKey(parts),
|
|
338
|
+
formatHourLabel(hourStart, tz),
|
|
339
|
+
hourStart,
|
|
340
|
+
);
|
|
341
|
+
buckets.set(key, bucket);
|
|
342
|
+
}
|
|
343
|
+
addEventToBucket(bucket, row);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return finalize(buckets);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Bucket raw usage events by local day in the given timezone.
|
|
351
|
+
*
|
|
352
|
+
* When `options.fillEmpty` is true, the returned array contains a zero-value
|
|
353
|
+
* bucket for every local day in the range.
|
|
354
|
+
*/
|
|
355
|
+
export function bucketEventsByDay(
|
|
356
|
+
events: UsageEventBucketRow[],
|
|
357
|
+
range: UsageTimeRange,
|
|
358
|
+
tz: string,
|
|
359
|
+
options: BucketingOptions = {},
|
|
360
|
+
): UsageDayBucket[] {
|
|
361
|
+
validateTimezone(tz);
|
|
362
|
+
const buckets = new Map<string, MutableBucket>();
|
|
363
|
+
|
|
364
|
+
if (options.fillEmpty) {
|
|
365
|
+
let cursor = alignToLocalDayStart(range.from, tz);
|
|
366
|
+
let safety = 0;
|
|
367
|
+
const maxIterations = 10_000;
|
|
368
|
+
while (cursor <= range.to && safety++ < maxIterations) {
|
|
369
|
+
const parts = getLocalParts(cursor, tz);
|
|
370
|
+
const key = dayKey(parts);
|
|
371
|
+
if (!buckets.has(key)) {
|
|
372
|
+
buckets.set(
|
|
373
|
+
key,
|
|
374
|
+
emptyBucket(key, key, formatDayLabel(cursor, tz), cursor),
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
// Advance by 24 UTC hours, then realign to local midnight. Handles
|
|
378
|
+
// DST transitions where a "day" is 23 or 25 hours long in local time.
|
|
379
|
+
cursor = alignToLocalDayStart(addOneDayUtc(cursor), tz);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
for (const row of events) {
|
|
384
|
+
const parts = getLocalParts(row.created_at, tz);
|
|
385
|
+
const key = dayKey(parts);
|
|
386
|
+
let bucket = buckets.get(key);
|
|
387
|
+
if (!bucket) {
|
|
388
|
+
const dayStart = alignToLocalDayStart(row.created_at, tz);
|
|
389
|
+
bucket = emptyBucket(key, key, formatDayLabel(dayStart, tz), dayStart);
|
|
390
|
+
buckets.set(key, bucket);
|
|
391
|
+
}
|
|
392
|
+
addEventToBucket(bucket, row);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return finalize(buckets);
|
|
396
|
+
}
|
|
@@ -53,6 +53,30 @@ function isIdempotent(options?: RequestInit): boolean {
|
|
|
53
53
|
return IDEMPOTENT_METHODS.has(method);
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/** Sleep that wakes immediately when the abort signal fires. */
|
|
57
|
+
async function signalAwareSleep(
|
|
58
|
+
ms: number,
|
|
59
|
+
signal?: AbortSignal,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
if (!signal) {
|
|
62
|
+
await new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const s = signal; // narrow for closures
|
|
66
|
+
s.throwIfAborted();
|
|
67
|
+
await new Promise<void>((resolve, reject) => {
|
|
68
|
+
const timer = setTimeout(() => {
|
|
69
|
+
s.removeEventListener("abort", onAbort);
|
|
70
|
+
resolve();
|
|
71
|
+
}, ms);
|
|
72
|
+
function onAbort() {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
reject(s.reason ?? new Error("aborted"));
|
|
75
|
+
}
|
|
76
|
+
s.addEventListener("abort", onAbort, { once: true });
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
56
80
|
interface GmailRequestOptions extends RequestInit {
|
|
57
81
|
/** Override method-based retry eligibility. When true, retries on 429/5xx even for POST requests. */
|
|
58
82
|
retryable?: boolean;
|
|
@@ -117,11 +141,13 @@ async function request<T>(
|
|
|
117
141
|
path: string,
|
|
118
142
|
options?: GmailRequestOptions,
|
|
119
143
|
query?: Record<string, string | string[]>,
|
|
144
|
+
signal?: AbortSignal,
|
|
120
145
|
): Promise<T> {
|
|
121
146
|
const canRetry = options?.retryable ?? isIdempotent(options);
|
|
122
147
|
const method = (options?.method ?? "GET").toUpperCase();
|
|
123
148
|
|
|
124
149
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
150
|
+
signal?.throwIfAborted();
|
|
125
151
|
let resp: OAuthConnectionResponse;
|
|
126
152
|
try {
|
|
127
153
|
resp = await connection.request({
|
|
@@ -133,9 +159,21 @@ async function request<T>(
|
|
|
133
159
|
...extractNonAuthHeaders(options),
|
|
134
160
|
},
|
|
135
161
|
body: extractBody(options),
|
|
162
|
+
signal,
|
|
136
163
|
});
|
|
137
164
|
} catch (err) {
|
|
138
|
-
//
|
|
165
|
+
// Retry thrown errors that indicate a retryable status (e.g. platform
|
|
166
|
+
// proxy throws BackendError on 429 after exhausting its own retries)
|
|
167
|
+
if (
|
|
168
|
+
canRetry &&
|
|
169
|
+
attempt < MAX_RETRIES &&
|
|
170
|
+
err instanceof Error &&
|
|
171
|
+
/\b(429|5\d{2})\b/.test(err.message)
|
|
172
|
+
) {
|
|
173
|
+
const delayMs = INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
174
|
+
await signalAwareSleep(delayMs, signal);
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
139
177
|
throw err;
|
|
140
178
|
}
|
|
141
179
|
|
|
@@ -146,7 +184,7 @@ async function request<T>(
|
|
|
146
184
|
const delayMs = retryAfter
|
|
147
185
|
? parseInt(retryAfter, 10) * 1000
|
|
148
186
|
: INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
149
|
-
await
|
|
187
|
+
await signalAwareSleep(delayMs, signal);
|
|
150
188
|
continue;
|
|
151
189
|
}
|
|
152
190
|
const bodyStr =
|
|
@@ -202,6 +240,7 @@ export async function getMessage(
|
|
|
202
240
|
format: GmailMessageFormat = "full",
|
|
203
241
|
metadataHeaders?: string[],
|
|
204
242
|
fields?: string,
|
|
243
|
+
signal?: AbortSignal,
|
|
205
244
|
): Promise<GmailMessage> {
|
|
206
245
|
const params = new URLSearchParams({ format });
|
|
207
246
|
if (format === "metadata" && metadataHeaders) {
|
|
@@ -213,6 +252,7 @@ export async function getMessage(
|
|
|
213
252
|
`/messages/${messageId}`,
|
|
214
253
|
undefined,
|
|
215
254
|
paramsToQuery(params),
|
|
255
|
+
signal,
|
|
216
256
|
);
|
|
217
257
|
}
|
|
218
258
|
|
|
@@ -256,6 +296,7 @@ async function executeBatchCall(
|
|
|
256
296
|
format: GmailMessageFormat,
|
|
257
297
|
metadataHeaders: string[] | undefined,
|
|
258
298
|
fields?: string,
|
|
299
|
+
signal?: AbortSignal,
|
|
259
300
|
): Promise<{
|
|
260
301
|
messages: Array<{ index: number; msg: GmailMessage }>;
|
|
261
302
|
failedIds: Array<{ index: number; id: string }>;
|
|
@@ -281,9 +322,13 @@ async function executeBatchCall(
|
|
|
281
322
|
|
|
282
323
|
const doBatchFetch = async (token: string) => {
|
|
283
324
|
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
325
|
+
const timeoutSignal = AbortSignal.timeout(REQUEST_TIMEOUT_MS * 2);
|
|
326
|
+
const combinedSignal = signal
|
|
327
|
+
? AbortSignal.any([signal, timeoutSignal])
|
|
328
|
+
: timeoutSignal;
|
|
284
329
|
const resp = await fetch(GMAIL_BATCH_URL, {
|
|
285
330
|
method: "POST",
|
|
286
|
-
signal:
|
|
331
|
+
signal: combinedSignal,
|
|
287
332
|
headers: {
|
|
288
333
|
Authorization: `Bearer ${token}`,
|
|
289
334
|
"Content-Type": `multipart/mixed; boundary=${boundary}`,
|
|
@@ -297,7 +342,7 @@ async function executeBatchCall(
|
|
|
297
342
|
const delayMs = retryAfter
|
|
298
343
|
? parseInt(retryAfter, 10) * 1000
|
|
299
344
|
: INITIAL_BACKOFF_MS * Math.pow(2, attempt);
|
|
300
|
-
await
|
|
345
|
+
await signalAwareSleep(delayMs, signal);
|
|
301
346
|
continue;
|
|
302
347
|
}
|
|
303
348
|
const errBody = await resp.text().catch(() => "");
|
|
@@ -375,13 +420,15 @@ async function fetchMessagesIndividually(
|
|
|
375
420
|
format: GmailMessageFormat,
|
|
376
421
|
metadataHeaders?: string[],
|
|
377
422
|
fields?: string,
|
|
423
|
+
signal?: AbortSignal,
|
|
378
424
|
): Promise<GmailMessage[]> {
|
|
379
425
|
const results: GmailMessage[] = [];
|
|
380
426
|
for (let i = 0; i < messageIds.length; i += INDIVIDUAL_CONCURRENCY) {
|
|
427
|
+
signal?.throwIfAborted();
|
|
381
428
|
const wave = messageIds.slice(i, i + INDIVIDUAL_CONCURRENCY);
|
|
382
429
|
const waveResults = await Promise.all(
|
|
383
430
|
wave.map((id) =>
|
|
384
|
-
getMessage(connection, id, format, metadataHeaders, fields),
|
|
431
|
+
getMessage(connection, id, format, metadataHeaders, fields, signal),
|
|
385
432
|
),
|
|
386
433
|
);
|
|
387
434
|
results.push(...waveResults);
|
|
@@ -403,6 +450,7 @@ export async function batchGetMessages(
|
|
|
403
450
|
format: GmailMessageFormat = "full",
|
|
404
451
|
metadataHeaders?: string[],
|
|
405
452
|
fields?: string,
|
|
453
|
+
signal?: AbortSignal,
|
|
406
454
|
): Promise<GmailMessage[]> {
|
|
407
455
|
if (messageIds.length === 0) return [];
|
|
408
456
|
|
|
@@ -415,6 +463,7 @@ export async function batchGetMessages(
|
|
|
415
463
|
format,
|
|
416
464
|
metadataHeaders,
|
|
417
465
|
fields,
|
|
466
|
+
signal,
|
|
418
467
|
),
|
|
419
468
|
];
|
|
420
469
|
}
|
|
@@ -436,6 +485,7 @@ export async function batchGetMessages(
|
|
|
436
485
|
format,
|
|
437
486
|
metadataHeaders,
|
|
438
487
|
fields,
|
|
488
|
+
signal,
|
|
439
489
|
);
|
|
440
490
|
}
|
|
441
491
|
|
|
@@ -462,6 +512,7 @@ export async function batchGetMessages(
|
|
|
462
512
|
format,
|
|
463
513
|
metadataHeaders,
|
|
464
514
|
fields,
|
|
515
|
+
signal,
|
|
465
516
|
),
|
|
466
517
|
),
|
|
467
518
|
);
|
|
@@ -479,7 +530,7 @@ export async function batchGetMessages(
|
|
|
479
530
|
if (failedIds.length > 0) {
|
|
480
531
|
const retried = await Promise.all(
|
|
481
532
|
failedIds.map(({ id }) =>
|
|
482
|
-
getMessage(connection, id, format, metadataHeaders, fields),
|
|
533
|
+
getMessage(connection, id, format, metadataHeaders, fields, signal),
|
|
483
534
|
),
|
|
484
535
|
);
|
|
485
536
|
for (let r = 0; r < failedIds.length; r++) {
|