@vellumai/assistant 0.6.4 → 0.6.5
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/.prettierignore +5 -0
- package/ARCHITECTURE.md +32 -36
- package/Dockerfile +12 -0
- package/README.md +3 -4
- package/bun.lock +8 -3
- package/docs/architecture/integrations.md +1 -20
- package/docs/architecture/security.md +16 -16
- package/docs/error-handling.md +111 -0
- package/docs/skills.md +10 -10
- package/docs/stt-provider-onboarding.md +2 -1
- package/knip.json +9 -2
- package/node_modules/@vellumai/ces-contracts/package.json +2 -1
- package/node_modules/@vellumai/ces-contracts/src/__tests__/trust-rules.test.ts +471 -0
- package/node_modules/@vellumai/ces-contracts/src/trust-rules.ts +398 -4
- package/node_modules/@vellumai/credential-storage/bun.lock +2 -2
- package/node_modules/@vellumai/credential-storage/package.json +2 -2
- package/node_modules/@vellumai/credential-storage/src/oauth-runtime.ts +20 -2
- package/node_modules/@vellumai/egress-proxy/bun.lock +2 -2
- package/node_modules/@vellumai/egress-proxy/package.json +2 -2
- package/openapi.yaml +123 -11
- package/package.json +6 -3
- package/scripts/generate-openapi.ts +50 -11
- package/src/__tests__/agent-loop-callsite-precedence.test.ts +318 -0
- package/src/__tests__/agent-loop-sentry-hygiene.test.ts +137 -0
- package/src/__tests__/agent-loop.test.ts +112 -1
- package/src/__tests__/anthropic-error-formatting.test.ts +98 -0
- package/src/__tests__/anthropic-provider.test.ts +171 -2
- package/src/__tests__/approval-cascade.test.ts +31 -10
- package/src/__tests__/approval-routes-http.test.ts +134 -10
- package/src/__tests__/assistant-attachments.test.ts +44 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +29 -0
- package/src/__tests__/browser-fill-credential.test.ts +1 -1
- package/src/__tests__/browser-identifier-parity-guard.test.ts +53 -0
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +23 -33
- package/src/__tests__/browser-skill-endstate.test.ts +51 -182
- package/src/__tests__/btw-routes.test.ts +47 -1
- package/src/__tests__/call-controller.test.ts +1 -2
- package/src/__tests__/call-site-routing-provider.test.ts +214 -0
- package/src/__tests__/catalog-cache.test.ts +27 -4
- package/src/__tests__/channel-approval-routes.test.ts +4 -4
- package/src/__tests__/channel-reply-delivery.test.ts +300 -2
- package/src/__tests__/checker.test.ts +428 -501
- package/src/__tests__/cli-command-risk-guard.test.ts +30 -33
- package/src/__tests__/compaction-circuit-breaker.test.ts +336 -0
- package/src/__tests__/compaction.benchmark.test.ts +1 -1
- package/src/__tests__/config-analysis.test.ts +11 -28
- package/src/__tests__/config-loader-backfill.test.ts +174 -0
- package/src/__tests__/config-loader-corrupt.test.ts +183 -0
- package/src/__tests__/config-loader-quarantine-bulletin.test.ts +202 -0
- package/src/__tests__/config-schema-cmd.test.ts +11 -5
- package/src/__tests__/config-schema.test.ts +427 -114
- package/src/__tests__/config-watcher.test.ts +2 -2
- package/src/__tests__/contact-store-user-file.test.ts +72 -73
- package/src/__tests__/contacts-write.test.ts +4 -4
- package/src/__tests__/context-token-estimator.test.ts +191 -1
- package/src/__tests__/context-window-manager.test.ts +530 -2
- package/src/__tests__/conversation-abort-tool-results.test.ts +30 -16
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +61 -17
- package/src/__tests__/conversation-agent-loop.test.ts +412 -82
- package/src/__tests__/conversation-attachments.test.ts +1 -1
- package/src/__tests__/conversation-confirmation-signals.test.ts +30 -9
- package/src/__tests__/conversation-error.test.ts +37 -6
- package/src/__tests__/conversation-history-web-search.test.ts +6 -0
- package/src/__tests__/conversation-init.benchmark.test.ts +36 -0
- package/src/__tests__/conversation-lifecycle.test.ts +336 -0
- package/src/__tests__/conversation-load-history-repair.test.ts +27 -10
- package/src/__tests__/conversation-pre-run-repair.test.ts +30 -16
- package/src/__tests__/conversation-process-callsite.test.ts +306 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +30 -16
- package/src/__tests__/conversation-queue.test.ts +41 -26
- package/src/__tests__/conversation-routes-disk-view.test.ts +29 -1
- package/src/__tests__/conversation-routes-slash-commands.test.ts +31 -3
- package/src/__tests__/conversation-runtime-assembly.test.ts +2735 -55
- package/src/__tests__/conversation-runtime-workspace.test.ts +12 -12
- package/src/__tests__/conversation-skill-tools.test.ts +12 -146
- package/src/__tests__/conversation-slash-queue.test.ts +34 -19
- package/src/__tests__/conversation-slash-unknown.test.ts +30 -16
- package/src/__tests__/conversation-speed-override.test.ts +30 -11
- package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +1035 -0
- package/src/__tests__/conversation-surfaces-standalone.test.ts +630 -0
- package/src/__tests__/conversation-title-service.test.ts +2 -2
- package/src/__tests__/conversation-tool-setup-batch-authorized.test.ts +1 -1
- package/src/__tests__/conversation-unread-route.test.ts +2 -2
- package/src/__tests__/conversation-usage.test.ts +3 -1
- package/src/__tests__/conversation-workspace-cache-state.test.ts +31 -10
- package/src/__tests__/conversation-workspace-injection.test.ts +43 -15
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +44 -16
- package/src/__tests__/credential-broker-browser-fill.test.ts +110 -0
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-storage-oauth-compat.test.ts +18 -0
- package/src/__tests__/credential-storage-static-compat.test.ts +28 -0
- package/src/__tests__/credential-vault-unit.test.ts +135 -19
- package/src/__tests__/credentials-cli.test.ts +1 -9
- package/src/__tests__/cross-provider-web-search.test.ts +84 -0
- package/src/__tests__/daemon-server-persist-and-process-callsite.test.ts +92 -0
- package/src/__tests__/delete-propagation.test.ts +437 -0
- package/src/__tests__/dm-backfill.test.ts +417 -0
- package/src/__tests__/dm-persistence.test.ts +227 -0
- package/src/__tests__/edit-propagation.test.ts +280 -0
- package/src/__tests__/ephemeral-permissions.test.ts +93 -3
- package/src/__tests__/estimator-calibration-integration.test.ts +208 -0
- package/src/__tests__/estimator-calibration.test.ts +213 -0
- package/src/__tests__/extension-id-sync-guard.test.ts +26 -7
- package/src/__tests__/file-write-tool.test.ts +151 -1
- package/src/__tests__/filing-service.test.ts +255 -0
- package/src/__tests__/gemini-provider.test.ts +0 -3
- package/src/__tests__/guardian-grant-minting.test.ts +8 -0
- package/src/__tests__/headless-browser-interactions.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +96 -15
- package/src/__tests__/host-shell-tool.test.ts +124 -18
- package/src/__tests__/http-user-message-parity.test.ts +29 -1
- package/src/__tests__/inbound-slack-persistence.test.ts +340 -0
- package/src/__tests__/intent-routing.test.ts +1 -40
- package/src/__tests__/llm-catalog-parity.test.ts +174 -0
- package/src/__tests__/llm-context-normalization.test.ts +121 -0
- package/src/__tests__/llm-resolver.test.ts +214 -0
- package/src/__tests__/llm-schema.test.ts +223 -0
- package/src/__tests__/managed-proxy-context.test.ts +6 -2
- package/src/__tests__/messaging-skill-split.test.ts +3 -34
- package/src/__tests__/migration-import-from-url.test.ts +684 -0
- package/src/__tests__/model-intents.test.ts +9 -83
- package/src/__tests__/notification-decision-fallback.test.ts +0 -10
- package/src/__tests__/notification-decision-identity.test.ts +0 -9
- package/src/__tests__/notification-decision-recipient-context.test.ts +0 -9
- package/src/__tests__/oauth-store.test.ts +10 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +8 -3
- package/src/__tests__/oauth2-refresh-retry.test.ts +279 -0
- package/src/__tests__/openai-provider.test.ts +7 -0
- package/src/__tests__/openai-responses-provider.test.ts +396 -0
- package/src/__tests__/openrouter-provider-only.test.ts +135 -0
- package/src/__tests__/outbound-slack-persistence.test.ts +293 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +1 -1
- package/src/__tests__/permission-mode.test.ts +16 -0
- package/src/__tests__/permission-types.test.ts +0 -1
- package/src/__tests__/persona-resolver.test.ts +13 -13
- package/src/__tests__/pkb-autoinject.test.ts +37 -1
- package/src/__tests__/platform-bash-auto-approve.test.ts +1 -1
- package/src/__tests__/pricing.test.ts +50 -3
- package/src/__tests__/profiler-routes.test.ts +1 -1
- package/src/__tests__/provider-commit-message-generator.test.ts +14 -84
- package/src/__tests__/provider-env-vars-scope.test.ts +52 -0
- package/src/__tests__/provider-error-scenarios.test.ts +135 -6
- package/src/__tests__/provider-managed-proxy-integration.test.ts +42 -11
- package/src/__tests__/provider-registry-ollama.test.ts +1 -2
- package/src/__tests__/proxy-approval-callback.test.ts +0 -1
- package/src/__tests__/reaction-persistence.test.ts +560 -0
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +1 -1
- package/src/__tests__/retry-openrouter-only-normalization.test.ts +136 -0
- package/src/__tests__/retry-thinking-tool-choice.test.ts +226 -0
- package/src/__tests__/risk-classifier-parity.test.ts +230 -0
- package/src/__tests__/sanitize-config-for-transfer.test.ts +78 -1
- package/src/__tests__/secret-ingress-http.test.ts +28 -0
- package/src/__tests__/secret-prompter-channel-fallback.test.ts +125 -0
- package/src/__tests__/secret-routes-managed-proxy.test.ts +2 -3
- package/src/__tests__/secret-scanner-executor.test.ts +1 -1
- package/src/__tests__/send-endpoint-busy.test.ts +29 -1
- package/src/__tests__/server-history-render.test.ts +31 -0
- package/src/__tests__/shell-parser-property.test.ts +13 -13
- package/src/__tests__/skill-cache-store.test.ts +182 -0
- package/src/__tests__/skills.test.ts +19 -33
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-skill.test.ts +3 -8
- package/src/__tests__/starter-bundle.test.ts +35 -0
- package/src/__tests__/subagent-call-site-routing.test.ts +280 -0
- package/src/__tests__/suggestion-routes.test.ts +160 -3
- package/src/__tests__/system-prompt.test.ts +22 -35
- package/src/__tests__/task-runner.test.ts +3 -1
- package/src/__tests__/tcc-sandbox-deny.test.ts +198 -0
- package/src/__tests__/terminal-tools.test.ts +8 -0
- package/src/__tests__/test-support/browser-skill-harness.ts +2 -52
- package/src/__tests__/thread-backfill.test.ts +941 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +2 -2
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +2 -2
- package/src/__tests__/tool-executor.test.ts +60 -94
- package/src/__tests__/trust-store.test.ts +442 -109
- package/src/__tests__/update-bulletin-job.test.ts +389 -0
- package/src/__tests__/usage-cache-backfill-migration.test.ts +3 -1
- package/src/__tests__/verification-control-plane-policy.test.ts +1 -22
- package/src/__tests__/voice-session-bridge.test.ts +39 -0
- package/src/__tests__/volume-security-guard.test.ts +3 -2
- package/src/__tests__/web-search-history.test.ts +337 -0
- package/src/__tests__/workspace-migration-039-drop-legacy-llm-keys.test.ts +343 -0
- package/src/__tests__/workspace-migration-043-release-notes-latex-rendering.test.ts +202 -0
- package/src/__tests__/workspace-migration-045-release-notes-meet-avatar.test.ts +210 -0
- package/src/__tests__/workspace-migration-drop-user-md.test.ts +11 -11
- package/src/__tests__/workspace-migration-unify-llm-callsite-configs.test.ts +841 -0
- package/src/__tests__/workspace-policy.test.ts +1 -13
- package/src/acp/client-handler.ts +1 -2
- package/src/agent/loop.ts +209 -17
- package/src/avatar/resvg-lazy.test.ts +136 -0
- package/src/avatar/resvg-lazy.ts +82 -9
- package/src/avatar/traits-png-sync.ts +21 -1
- package/src/browser/__tests__/operations.test.ts +163 -0
- package/src/browser/identifiers.ts +51 -0
- package/src/browser/operations.ts +660 -0
- package/src/browser/types.ts +81 -0
- package/src/calls/guardian-question-copy.ts +2 -2
- package/src/calls/telephony-stt-routing.ts +1 -1
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/AGENTS.md +1 -1
- package/src/cli/commands/__tests__/attachment.test.ts +438 -0
- package/src/cli/commands/__tests__/browser.test.ts +554 -0
- package/src/cli/commands/__tests__/cache.test.ts +623 -0
- package/src/cli/commands/__tests__/email-list.test.ts +6 -0
- package/src/cli/commands/__tests__/email-send.test.ts +93 -1
- package/src/cli/commands/__tests__/image-generation.test.ts +666 -0
- package/src/cli/commands/__tests__/inference-send.test.ts +451 -0
- package/src/cli/commands/__tests__/stt-transcribe.test.ts +454 -0
- package/src/cli/commands/__tests__/task.test.ts +913 -0
- package/src/cli/commands/__tests__/tts-synthesize.test.ts +594 -0
- package/src/cli/commands/__tests__/ui-confirm.test.ts +650 -0
- package/src/cli/commands/__tests__/ui.test.ts +1215 -0
- package/src/cli/commands/__tests__/watchers.test.ts +716 -0
- package/src/cli/commands/attachment.ts +182 -0
- package/src/cli/commands/browser.ts +350 -0
- package/src/cli/commands/cache.ts +341 -0
- package/src/cli/commands/completions.ts +0 -3
- package/src/cli/commands/config.ts +6 -6
- package/src/cli/commands/conversations-import.ts +347 -0
- package/src/cli/commands/conversations.ts +14 -1
- package/src/cli/commands/email.ts +234 -194
- package/src/cli/commands/image-generation.ts +300 -0
- package/src/cli/commands/inference.ts +200 -0
- package/src/cli/commands/memory.ts +127 -17
- package/src/cli/commands/platform/__tests__/callback-routes-list.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/connect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +0 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +0 -1
- package/src/cli/commands/stt.ts +339 -0
- package/src/cli/commands/task.ts +795 -0
- package/src/cli/commands/trust.ts +50 -19
- package/src/cli/commands/tts.ts +273 -0
- package/src/cli/commands/ui.ts +670 -0
- package/src/cli/commands/watchers.ts +509 -0
- package/src/cli/lib/daemon-credential-client.ts +0 -19
- package/src/cli/program.ts +23 -4
- package/src/cli.ts +0 -37
- package/src/config/bundled-skills/conversations/tools/rename-conversation.ts +23 -1
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +2 -2
- package/src/config/bundled-skills/messaging/TOOLS.json +4 -0
- package/src/config/bundled-skills/messaging/tools/messaging-archive-by-sender.ts +8 -1
- package/src/config/bundled-skills/messaging/tools/messaging-read.ts +15 -1
- package/src/config/bundled-skills/messaging/tools/messaging-search.ts +21 -1
- package/src/config/bundled-skills/messaging/tools/messaging-send.ts +11 -12
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +9 -8
- package/src/config/bundled-skills/settings/TOOLS.json +3 -3
- package/src/config/bundled-tool-registry.ts +0 -175
- package/src/config/env.ts +7 -2
- package/src/config/feature-flag-registry.json +25 -9
- package/src/config/llm-resolver.ts +128 -0
- package/src/config/loader.ts +194 -10
- package/src/config/raw-config-utils.ts +30 -2
- package/src/config/sanitize-for-transfer.ts +35 -0
- package/src/config/schema.ts +30 -41
- package/src/config/schemas/analysis.ts +3 -22
- package/src/config/schemas/calls.ts +0 -4
- package/src/config/schemas/filing.ts +2 -7
- package/src/config/schemas/heartbeat.ts +0 -5
- package/src/config/schemas/inference.ts +3 -23
- package/src/config/schemas/llm.ts +318 -0
- package/src/config/schemas/memory-processing.ts +1 -9
- package/src/config/schemas/notifications.ts +4 -11
- package/src/config/schemas/platform.ts +3 -9
- package/src/config/schemas/security.ts +33 -0
- package/src/config/schemas/services.ts +9 -4
- package/src/config/schemas/stt.ts +1 -0
- package/src/config/schemas/tts.ts +53 -0
- package/src/config/schemas/updates.ts +1 -1
- package/src/config/schemas/workspace-git.ts +3 -40
- package/src/config/skills.ts +2 -2
- package/src/context/__tests__/compact-prompt.test.ts +45 -0
- package/src/context/__tests__/microcompact.test.ts +805 -0
- package/src/context/estimator-calibration.ts +136 -0
- package/src/context/microcompact.ts +443 -0
- package/src/context/prompts/compact.md +12 -0
- package/src/context/token-estimator.ts +61 -3
- package/src/context/window-manager.ts +229 -25
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/executable-discovery.ts +19 -8
- package/src/credential-execution/process-manager.test.ts +109 -0
- package/src/credential-execution/process-manager.ts +65 -2
- package/src/daemon/approval-generators.ts +29 -4
- package/src/daemon/assistant-attachments.ts +24 -13
- package/src/daemon/classifier.ts +2 -2
- package/src/daemon/config-watcher.ts +0 -1
- package/src/daemon/context-overflow-reducer.ts +4 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +79 -12
- package/src/daemon/conversation-agent-loop.ts +462 -80
- package/src/daemon/conversation-attachments.ts +2 -6
- package/src/daemon/conversation-error.ts +36 -1
- package/src/daemon/conversation-lifecycle.ts +30 -6
- package/src/daemon/conversation-messaging.ts +73 -4
- package/src/daemon/conversation-process.ts +10 -4
- package/src/daemon/conversation-queue-manager.ts +3 -0
- package/src/daemon/conversation-runtime-assembly.ts +760 -29
- package/src/daemon/conversation-slash.ts +2 -2
- package/src/daemon/conversation-surfaces.ts +389 -1
- package/src/daemon/conversation-tool-setup.ts +10 -5
- package/src/daemon/conversation-usage.ts +1 -1
- package/src/daemon/conversation.ts +118 -30
- package/src/daemon/external-skills-bootstrap.ts +41 -0
- package/src/daemon/guardian-action-generators.ts +34 -14
- package/src/daemon/handlers/config-model.test.ts +86 -0
- package/src/daemon/handlers/config-model.ts +54 -12
- package/src/daemon/handlers/conversations.ts +9 -2
- package/src/daemon/handlers/shared.ts +39 -11
- package/src/daemon/handlers/skills.ts +2 -2
- package/src/daemon/handlers/slack-channel-oauth-install.ts +197 -0
- package/src/daemon/lifecycle.ts +76 -14
- package/src/daemon/message-types/conversations.ts +14 -0
- package/src/daemon/message-types/messages.ts +9 -1
- package/src/daemon/message-types/trust.ts +0 -2
- package/src/daemon/parse-actual-tokens-from-error.test.ts +57 -1
- package/src/daemon/parse-actual-tokens-from-error.ts +66 -0
- package/src/daemon/pkb-context-tracker.test.ts +169 -0
- package/src/daemon/pkb-context-tracker.ts +125 -0
- package/src/daemon/pkb-reminder-builder.test.ts +70 -0
- package/src/daemon/pkb-reminder-builder.ts +31 -0
- package/src/daemon/providers-setup.ts +6 -0
- package/src/daemon/server.ts +117 -9
- package/src/daemon/tool-side-effects.ts +0 -9
- package/src/daemon/watch-handler.ts +4 -4
- package/src/daemon/web-search-history.ts +126 -0
- package/src/events/domain-events.ts +0 -1
- package/src/filing/filing-service.ts +9 -10
- package/src/heartbeat/heartbeat-service.ts +76 -28
- package/src/home/__tests__/feed-scheduler.test.ts +39 -11
- package/src/home/__tests__/rollup-producer.test.ts +44 -0
- package/src/home/assistant-feed-authoring.ts +4 -0
- package/src/home/emit-feed-event.ts +4 -0
- package/src/home/feed-scheduler.ts +20 -4
- package/src/home/feed-types.ts +56 -2
- package/src/home/relationship-state-writer.ts +2 -2
- package/src/home/rollup-producer.ts +34 -5
- package/src/home/suggested-prompts.ts +101 -0
- package/src/ipc/__tests__/attachment-ipc.test.ts +213 -0
- package/src/ipc/__tests__/browser-ipc.test.ts +339 -0
- package/src/ipc/__tests__/cache-ipc.test.ts +266 -0
- package/src/ipc/__tests__/socket-path.test.ts +73 -0
- package/src/ipc/__tests__/task-ipc.test.ts +577 -0
- package/src/ipc/__tests__/ui-request-route.test.ts +495 -0
- package/src/ipc/__tests__/watcher-ipc.test.ts +295 -0
- package/src/ipc/cli-client.ts +2 -1
- package/src/ipc/cli-server.ts +26 -8
- package/src/ipc/gateway-client.ts +4 -4
- package/src/ipc/routes/attachment.ts +114 -0
- package/src/ipc/routes/browser-context.ts +61 -0
- package/src/ipc/routes/browser.ts +96 -0
- package/src/ipc/routes/cache.ts +96 -0
- package/src/ipc/routes/index.ts +17 -1
- package/src/ipc/routes/task-queue.ts +226 -0
- package/src/ipc/routes/task.ts +173 -0
- package/src/ipc/routes/ui-request.ts +50 -0
- package/src/ipc/routes/watcher.ts +203 -0
- package/src/ipc/socket-path.ts +100 -0
- package/src/memory/__tests__/conversation-analyze-job.test.ts +9 -8
- package/src/memory/__tests__/conversation-group-migration.test.ts +99 -0
- package/src/memory/admin.ts +18 -0
- package/src/memory/conversation-analyze-job.ts +14 -13
- package/src/memory/conversation-attention-store.ts +13 -6
- package/src/memory/conversation-crud.ts +103 -3
- package/src/memory/conversation-group-migration.ts +38 -6
- package/src/memory/conversation-title-service.ts +7 -4
- package/src/memory/db-init.ts +2 -0
- package/src/memory/embedding-backend.ts +1 -1
- package/src/memory/graph/compaction.ts +299 -0
- package/src/memory/graph/consolidation.ts +4 -4
- package/src/memory/graph/conversation-graph-memory.ts +89 -29
- package/src/memory/graph/extraction.test.ts +272 -2
- package/src/memory/graph/extraction.ts +173 -51
- package/src/memory/graph/graph-search.test.ts +92 -0
- package/src/memory/graph/graph-search.ts +4 -1
- package/src/memory/graph/narrative.ts +2 -2
- package/src/memory/graph/pattern-scan.ts +2 -2
- package/src/memory/graph/retriever.test.ts +459 -0
- package/src/memory/graph/retriever.ts +230 -48
- package/src/memory/graph/store.ts +41 -0
- package/src/memory/graph/tool-handlers.ts +27 -0
- package/src/memory/graph/tools.ts +6 -1
- package/src/memory/indexer.ts +5 -5
- package/src/memory/job-handlers/conversation-starters.ts +23 -20
- package/src/memory/job-handlers/summarization.ts +2 -2
- package/src/memory/job-utils.ts +7 -1
- package/src/memory/jobs/embed-pkb-file.test.ts +168 -0
- package/src/memory/jobs/embed-pkb-file.ts +54 -0
- package/src/memory/jobs-store.ts +44 -3
- package/src/memory/jobs-worker.ts +4 -0
- package/src/memory/migrations/140-backfill-usage-cache-accounting.ts +1 -1
- package/src/memory/migrations/220-normalize-user-file-by-principal.ts +2 -2
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +82 -0
- package/src/memory/migrations/index.ts +1 -0
- package/src/memory/pkb/pkb-index.test.ts +368 -0
- package/src/memory/pkb/pkb-index.ts +255 -0
- package/src/memory/pkb/pkb-reconcile.test.ts +251 -0
- package/src/memory/pkb/pkb-reconcile.ts +148 -0
- package/src/memory/pkb/pkb-search.test.ts +438 -0
- package/src/memory/pkb/pkb-search.ts +137 -0
- package/src/memory/pkb/types.ts +53 -0
- package/src/memory/qdrant-client.ts +122 -1
- package/src/memory/slack-thread-store.ts +37 -0
- package/src/messaging/providers/gmail/adapter.ts +6 -16
- package/src/messaging/providers/gmail/client.ts +22 -0
- package/src/messaging/providers/gmail/types.ts +7 -0
- package/src/messaging/providers/slack/adapter.ts +14 -2
- package/src/messaging/providers/slack/backfill.test.ts +257 -0
- package/src/messaging/providers/slack/backfill.ts +101 -0
- package/src/messaging/providers/slack/message-metadata.test.ts +316 -0
- package/src/messaging/providers/slack/message-metadata.ts +123 -0
- package/src/messaging/providers/slack/render-transcript.test.ts +1373 -0
- package/src/messaging/providers/slack/render-transcript.ts +443 -0
- package/src/messaging/style-analyzer.ts +5 -2
- package/src/notifications/README.md +9 -5
- package/src/notifications/decision-engine.ts +3 -9
- package/src/notifications/preference-extractor.ts +2 -6
- package/src/oauth/oauth-store.ts +1 -0
- package/src/oauth/platform-connection.test.ts +47 -0
- package/src/oauth/platform-connection.ts +15 -5
- package/src/oauth/seed-providers.ts +4 -2
- package/src/permissions/approval-policy.test.ts +948 -0
- package/src/permissions/approval-policy.ts +257 -0
- package/src/permissions/bash-risk-classifier.test.ts +1208 -0
- package/src/permissions/bash-risk-classifier.ts +707 -0
- package/src/permissions/checker.ts +217 -708
- package/src/permissions/command-registry.test.ts +535 -0
- package/src/permissions/command-registry.ts +825 -0
- package/src/permissions/defaults.ts +26 -78
- package/src/permissions/file-risk-classifier.test.ts +535 -0
- package/src/permissions/file-risk-classifier.ts +274 -0
- package/src/permissions/risk-types.ts +205 -0
- package/src/permissions/secret-prompter.ts +53 -2
- package/src/permissions/skill-risk-classifier.test.ts +311 -0
- package/src/permissions/skill-risk-classifier.ts +214 -0
- package/src/permissions/trust-client.ts +52 -25
- package/src/permissions/trust-store-interface.ts +1 -6
- package/src/permissions/trust-store.ts +161 -62
- package/src/permissions/types.ts +23 -14
- package/src/permissions/web-risk-classifier.test.ts +170 -0
- package/src/permissions/web-risk-classifier.ts +89 -0
- package/src/permissions/workspace-policy.ts +1 -16
- package/src/platform/client.ts +19 -1
- package/src/prompts/persona-resolver.ts +3 -3
- package/src/prompts/system-prompt.ts +19 -20
- package/src/prompts/templates/SOUL.md +2 -2
- package/src/prompts/update-bulletin-job.ts +190 -0
- package/src/providers/__tests__/context-overflow-error.test.ts +328 -0
- package/src/providers/__tests__/provider-env-vars.test.ts +102 -0
- package/src/providers/__tests__/retry-callsite.test.ts +424 -0
- package/src/providers/anthropic/client.ts +183 -14
- package/src/providers/call-site-routing.ts +71 -0
- package/src/providers/gemini/client.ts +65 -2
- package/src/providers/managed-proxy/constants.ts +2 -1
- package/src/providers/model-catalog.ts +501 -33
- package/src/providers/model-intents.ts +4 -4
- package/src/providers/openai/chat-completions-provider.ts +57 -1
- package/src/providers/openai/responses-provider.ts +86 -9
- package/src/providers/openrouter/client.ts +76 -9
- package/src/providers/provider-env-vars.ts +56 -0
- package/src/providers/provider-send-message.ts +22 -5
- package/src/providers/ratelimit.ts +4 -0
- package/src/providers/registry.ts +19 -8
- package/src/providers/retry.ts +174 -39
- package/src/providers/speech-to-text/__tests__/resolve.test.ts +55 -0
- package/src/providers/speech-to-text/google-gemini-live-stream.ts +4 -4
- package/src/providers/speech-to-text/provider-catalog.ts +17 -0
- package/src/providers/speech-to-text/resolve.ts +7 -0
- package/src/providers/speech-to-text/xai-realtime.test.ts +578 -0
- package/src/providers/speech-to-text/xai-realtime.ts +796 -0
- package/src/providers/speech-to-text/xai.test.ts +155 -0
- package/src/providers/speech-to-text/xai.ts +97 -0
- package/src/providers/types.ts +93 -3
- package/src/runtime/AGENTS.md +2 -2
- package/src/runtime/__tests__/agent-wake.test.ts +43 -2
- package/src/runtime/__tests__/interactive-ui.test.ts +673 -0
- package/src/runtime/agent-wake.ts +63 -22
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/btw-sidechain.ts +13 -3
- package/src/runtime/channel-reply-delivery.ts +106 -2
- package/src/runtime/decision-token.ts +116 -0
- package/src/runtime/gateway-client.ts +2 -2
- package/src/runtime/http-router.ts +32 -0
- package/src/runtime/http-server.ts +52 -1
- package/src/runtime/http-types.ts +23 -1
- package/src/runtime/interactive-ui.ts +362 -0
- package/src/runtime/invite-instruction-generator.ts +2 -2
- package/src/runtime/migrations/__tests__/gcs-signed-url.test.ts +176 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +390 -0
- package/src/runtime/migrations/__tests__/vbundle-metadata-merge.test.ts +221 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +1540 -0
- package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +453 -0
- package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +222 -0
- package/src/runtime/migrations/gcs-signed-url.ts +162 -0
- package/src/runtime/migrations/vbundle-importer.ts +154 -9
- package/src/runtime/migrations/vbundle-metadata-merge.ts +124 -0
- package/src/runtime/migrations/vbundle-streaming-importer.ts +2522 -0
- package/src/runtime/migrations/vbundle-streaming-validator.ts +244 -0
- package/src/runtime/migrations/vbundle-tar-stream.ts +217 -0
- package/src/runtime/migrations/vbundle-validator.ts +15 -6
- package/src/runtime/routes/__tests__/home-feed-routes.test.ts +111 -0
- package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +114 -75
- package/src/runtime/routes/__tests__/migration-vellum-metadata-reconcile.test.ts +246 -0
- package/src/runtime/routes/approval-prompt-ts-tracker.ts +58 -0
- package/src/runtime/routes/approval-routes.ts +12 -17
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +9 -0
- package/src/runtime/routes/avatar-routes.ts +20 -4
- package/src/runtime/routes/btw-routes.ts +1 -4
- package/src/runtime/routes/conversation-management-routes.ts +20 -2
- package/src/runtime/routes/conversation-routes.ts +133 -27
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +6 -4
- package/src/runtime/routes/events-routes.ts +16 -0
- package/src/runtime/routes/guardian-approval-interception.ts +33 -3
- package/src/runtime/routes/guardian-approval-prompt.ts +13 -3
- package/src/runtime/routes/home-feed-routes.ts +120 -2
- package/src/runtime/routes/inbound-message-handler.ts +912 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.test.ts +113 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +61 -3
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +129 -6
- package/src/runtime/routes/integrations/slack/channel.ts +25 -3
- package/src/runtime/routes/llm-context-normalization.ts +23 -1
- package/src/runtime/routes/migration-routes.ts +720 -124
- package/src/runtime/routes/settings-routes.ts +4 -2
- package/src/runtime/routes/trust-rules-routes.ts +30 -14
- package/src/runtime/routes/work-items-routes.test.ts +1 -1
- package/src/runtime/routes/work-items-routes.ts +3 -2
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +25 -43
- package/src/runtime/services/analyze-conversation.ts +12 -16
- package/src/runtime/skill-route-registry.ts +28 -6
- package/src/schedule/scheduler.ts +8 -0
- package/src/security/__tests__/provider-key-env-fallback.test.ts +119 -0
- package/src/security/__tests__/untrusted-content.test.ts +109 -0
- package/src/security/oauth2.ts +98 -35
- package/src/security/secure-keys.ts +7 -8
- package/src/security/token-manager.ts +27 -13
- package/src/security/untrusted-content.ts +102 -0
- package/src/skills/catalog-cache.ts +26 -7
- package/src/skills/catalog-install.ts +31 -3
- package/src/skills/skill-cache-store.ts +97 -0
- package/src/stt/__tests__/daemon-batch-transcriber.test.ts +76 -0
- package/src/stt/daemon-batch-transcriber.ts +33 -0
- package/src/stt/stt-stream-session.ts +8 -1
- package/src/stt/types.ts +5 -1
- package/src/subagent/manager.ts +41 -13
- package/src/tasks/ephemeral-permissions.ts +9 -4
- package/src/telemetry/usage-telemetry-reporter.ts +27 -5
- package/src/tools/browser/__tests__/browser-status.test.ts +45 -2
- package/src/tools/browser/browser-execution.ts +65 -38
- package/src/tools/browser/cdp-client/cdp-inspect/discovery.ts +22 -0
- package/src/tools/credentials/tool-policy.ts +39 -5
- package/src/tools/credentials/vault.ts +9 -4
- package/src/tools/executor.ts +4 -0
- package/src/tools/filesystem/write.ts +52 -0
- package/src/tools/host-terminal/host-shell.ts +45 -5
- package/src/tools/memory/register.test.ts +185 -0
- package/src/tools/memory/register.ts +3 -1
- package/src/tools/network/web-fetch.ts +20 -10
- package/src/tools/network/web-search.ts +19 -4
- package/src/tools/permission-checker.ts +36 -15
- package/src/tools/policy-context.ts +25 -8
- package/src/tools/registry.ts +55 -3
- package/src/tools/side-effects.ts +0 -11
- package/src/tools/skills/execute.ts +2 -2
- package/src/tools/skills/sandbox-runner.ts +5 -2
- package/src/tools/terminal/backends/native.ts +51 -2
- package/src/tools/terminal/safe-env.ts +3 -2
- package/src/tools/terminal/shell.ts +1 -0
- package/src/tools/tool-manifest.ts +6 -21
- package/src/tools/types.ts +12 -3
- package/src/tools/verification-control-plane-policy.ts +1 -1
- package/src/tts/__tests__/provider-adapters.test.ts +240 -13
- package/src/tts/provider-catalog.ts +18 -0
- package/src/tts/providers/index.ts +2 -0
- package/src/tts/providers/xai-provider.ts +224 -0
- package/src/tts/types.ts +46 -0
- package/src/types/tar-stream.d.ts +66 -0
- package/src/util/json.ts +17 -0
- package/src/util/platform.ts +2 -2
- package/src/util/pricing.ts +15 -5
- package/src/watcher/engine.ts +1 -1
- package/src/watcher/providers/google-calendar.ts +134 -8
- package/src/watcher/providers/outlook-calendar.ts +42 -2
- package/src/workspace/git-service.ts +23 -4
- package/src/workspace/migrations/038-unify-llm-callsite-configs.ts +516 -0
- package/src/workspace/migrations/039-drop-legacy-llm-keys.ts +171 -0
- package/src/workspace/migrations/040-seed-latency-callsite-defaults.ts +154 -0
- package/src/workspace/migrations/041-backfill-google-gmail-settings-scope.ts +57 -0
- package/src/workspace/migrations/042-fix-backfill-google-gmail-settings-scope.ts +70 -0
- package/src/workspace/migrations/043-release-notes-latex-rendering.ts +75 -0
- package/src/workspace/migrations/044-bump-stale-provider-stream-timeout.ts +51 -0
- package/src/workspace/migrations/045-release-notes-meet-avatar.ts +130 -0
- package/src/workspace/migrations/AGENTS.md +1 -1
- package/src/workspace/migrations/registry.ts +16 -0
- package/src/workspace/provider-commit-message-generator.ts +19 -38
- package/src/__tests__/gmail-archive-fallback.test.ts +0 -193
- package/src/__tests__/gmail-archive-gate.test.ts +0 -246
- package/src/__tests__/gmail-preferences.test.ts +0 -117
- package/src/__tests__/outlook-attachments.test.ts +0 -301
- package/src/__tests__/outlook-automation-tools.test.ts +0 -425
- package/src/__tests__/outlook-categories.test.ts +0 -212
- package/src/__tests__/outlook-compose-tools.test.ts +0 -325
- package/src/__tests__/outlook-declutter-tools.test.ts +0 -585
- package/src/__tests__/outlook-follow-up.test.ts +0 -196
- package/src/__tests__/outlook-trash.test.ts +0 -77
- package/src/__tests__/outlook-unsubscribe.test.ts +0 -279
- package/src/__tests__/update-bulletin-format.test.ts +0 -181
- package/src/__tests__/update-bulletin-state.test.ts +0 -135
- package/src/__tests__/update-bulletin.test.ts +0 -478
- package/src/__tests__/update-template-contract.test.ts +0 -29
- package/src/cli/commands/doctor.ts +0 -341
- package/src/config/bundled-skills/browser/SKILL.md +0 -88
- package/src/config/bundled-skills/browser/TOOLS.json +0 -516
- package/src/config/bundled-skills/browser/tools/browser-attach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-click.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-close.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-detach.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-extract.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-fill-credential.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-hover.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-navigate.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-press-key.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-screenshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-scroll.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-select-option.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-snapshot.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-status.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-type.ts +0 -12
- package/src/config/bundled-skills/browser/tools/browser-wait-for-download.ts +0 -49
- package/src/config/bundled-skills/browser/tools/browser-wait-for.ts +0 -12
- package/src/config/bundled-skills/chatgpt-import/SKILL.md +0 -27
- package/src/config/bundled-skills/chatgpt-import/TOOLS.json +0 -27
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +0 -378
- package/src/config/bundled-skills/gmail/SKILL.md +0 -221
- package/src/config/bundled-skills/gmail/TOOLS.json +0 -588
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +0 -256
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +0 -112
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +0 -44
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +0 -81
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +0 -108
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +0 -146
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +0 -53
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-preferences-tool.ts +0 -59
- package/src/config/bundled-skills/gmail/tools/gmail-preferences.ts +0 -82
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +0 -26
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +0 -347
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +0 -29
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +0 -122
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +0 -67
- package/src/config/bundled-skills/gmail/tools/scan-result-store.ts +0 -100
- package/src/config/bundled-skills/gmail/tools/shared.ts +0 -47
- package/src/config/bundled-skills/google-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/google-calendar/TOOLS.json +0 -226
- package/src/config/bundled-skills/google-calendar/calendar-client.ts +0 -223
- package/src/config/bundled-skills/google-calendar/tools/calendar-check-availability.ts +0 -27
- package/src/config/bundled-skills/google-calendar/tools/calendar-create-event.ts +0 -48
- package/src/config/bundled-skills/google-calendar/tools/calendar-get-event.ts +0 -19
- package/src/config/bundled-skills/google-calendar/tools/calendar-list-events.ts +0 -36
- package/src/config/bundled-skills/google-calendar/tools/calendar-rsvp.ts +0 -58
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/google-calendar/types.ts +0 -97
- package/src/config/bundled-skills/outlook/SKILL.md +0 -196
- package/src/config/bundled-skills/outlook/TOOLS.json +0 -530
- package/src/config/bundled-skills/outlook/tools/outlook-attachments.ts +0 -85
- package/src/config/bundled-skills/outlook/tools/outlook-categories.ts +0 -77
- package/src/config/bundled-skills/outlook/tools/outlook-draft.ts +0 -84
- package/src/config/bundled-skills/outlook/tools/outlook-follow-up.ts +0 -94
- package/src/config/bundled-skills/outlook/tools/outlook-forward.ts +0 -49
- package/src/config/bundled-skills/outlook/tools/outlook-outreach-scan.ts +0 -237
- package/src/config/bundled-skills/outlook/tools/outlook-rules.ts +0 -161
- package/src/config/bundled-skills/outlook/tools/outlook-send-draft.ts +0 -32
- package/src/config/bundled-skills/outlook/tools/outlook-sender-digest.ts +0 -272
- package/src/config/bundled-skills/outlook/tools/outlook-trash.ts +0 -29
- package/src/config/bundled-skills/outlook/tools/outlook-unsubscribe.ts +0 -129
- package/src/config/bundled-skills/outlook/tools/outlook-vacation.ts +0 -87
- package/src/config/bundled-skills/outlook/tools/shared.ts +0 -20
- package/src/config/bundled-skills/outlook-calendar/SKILL.md +0 -51
- package/src/config/bundled-skills/outlook-calendar/TOOLS.json +0 -221
- package/src/config/bundled-skills/outlook-calendar/calendar-client.ts +0 -252
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-check-availability.ts +0 -53
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-create-event.ts +0 -74
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-get-event.ts +0 -18
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-list-events.ts +0 -46
- package/src/config/bundled-skills/outlook-calendar/tools/outlook-calendar-rsvp.ts +0 -36
- package/src/config/bundled-skills/outlook-calendar/tools/shared.ts +0 -17
- package/src/config/bundled-skills/outlook-calendar/types.ts +0 -120
- package/src/config/bundled-skills/slack/SKILL.md +0 -108
- package/src/config/bundled-skills/tasks/SKILL.md +0 -37
- package/src/config/bundled-skills/tasks/TOOLS.json +0 -353
- package/src/config/bundled-skills/tasks/icon.svg +0 -34
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-list.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-queue-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-run.ts +0 -12
- package/src/config/bundled-skills/tasks/tools/task-save.ts +0 -12
- package/src/config/bundled-skills/watcher/SKILL.md +0 -31
- package/src/config/bundled-skills/watcher/TOOLS.json +0 -167
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +0 -12
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +0 -12
- package/src/prompts/templates/UPDATES.md +0 -50
- package/src/prompts/update-bulletin-format.ts +0 -85
- package/src/prompts/update-bulletin-state.ts +0 -58
- package/src/prompts/update-bulletin-template-path.ts +0 -13
- package/src/prompts/update-bulletin.ts +0 -139
- package/src/shared/provider-env-vars.ts +0 -19
- package/src/tools/watcher/create.ts +0 -86
- package/src/tools/watcher/delete.ts +0 -36
- package/src/tools/watcher/digest.ts +0 -54
- package/src/tools/watcher/list.ts +0 -83
- package/src/tools/watcher/update.ts +0 -71
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Self-calibrating correction ratios for the token estimator.
|
|
3
|
+
*
|
|
4
|
+
* Every successful provider call returns ground-truth `usage.inputTokens`.
|
|
5
|
+
* By comparing that to the pre-send estimate, we can maintain a per-model
|
|
6
|
+
* EWMA correction ratio that multiplies future estimates — catching
|
|
7
|
+
* miscalibration proactively instead of waiting for provider overflow
|
|
8
|
+
* errors to reverse-engineer a token count from the error message.
|
|
9
|
+
*
|
|
10
|
+
* State is process-local; correction resets on restart. That is acceptable
|
|
11
|
+
* because the ratio converges quickly (EWMA alpha = 0.2, ~5 samples).
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface CalibrationState {
|
|
15
|
+
/** EWMA of (actual / estimated) — multiply estimates by this to correct. */
|
|
16
|
+
ratio: number;
|
|
17
|
+
/** Total samples recorded, for observability. */
|
|
18
|
+
sampleCount: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const CALIBRATIONS: Map<string, CalibrationState> = new Map();
|
|
22
|
+
|
|
23
|
+
/** Fast-adapting EWMA — converges to a steady state in ~5 consistent samples. */
|
|
24
|
+
const EWMA_ALPHA = 0.2;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Below this magnitude both numbers are noisy — a 500-token prompt with a
|
|
28
|
+
* 600-token usage is within normal overhead fluctuation and should not
|
|
29
|
+
* move the correction ratio.
|
|
30
|
+
*/
|
|
31
|
+
const MIN_SAMPLE_MAGNITUDE = 500;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Outlier guard — discard samples where the ratio is more than 3x off in
|
|
35
|
+
* either direction. A ratio that extreme is almost always a bug (wrong
|
|
36
|
+
* estimate site, wrong provider, double-counting) rather than a genuine
|
|
37
|
+
* estimation error the calibrator should learn from.
|
|
38
|
+
*/
|
|
39
|
+
const MIN_ACCEPTABLE_RATIO = 1 / 3;
|
|
40
|
+
const MAX_ACCEPTABLE_RATIO = 3;
|
|
41
|
+
|
|
42
|
+
function key(provider: string, model: string): string {
|
|
43
|
+
return `${provider}::${model}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Apply a single EWMA update at an exact (provider, model) key. */
|
|
47
|
+
function applyEwmaUpdate(provider: string, model: string, ratio: number): void {
|
|
48
|
+
const k = key(provider, model);
|
|
49
|
+
const prev = CALIBRATIONS.get(k);
|
|
50
|
+
const next: CalibrationState = prev
|
|
51
|
+
? {
|
|
52
|
+
ratio: prev.ratio + EWMA_ALPHA * (ratio - prev.ratio),
|
|
53
|
+
sampleCount: prev.sampleCount + 1,
|
|
54
|
+
}
|
|
55
|
+
: { ratio, sampleCount: 1 };
|
|
56
|
+
CALIBRATIONS.set(k, next);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Fold a new (estimated, actual) observation into the EWMA ratio for this
|
|
61
|
+
* (provider, model). No-op when either number is too small to be reliable,
|
|
62
|
+
* or when the ratio is an outlier.
|
|
63
|
+
*
|
|
64
|
+
* When `model` is non-empty, the same observation is also folded into the
|
|
65
|
+
* per-provider aggregate key `(provider, "")` so callers that cannot resolve
|
|
66
|
+
* a specific model (early-init paths, provider-only estimate sites) still
|
|
67
|
+
* pick up a reasonable correction via {@link getCorrection}.
|
|
68
|
+
*/
|
|
69
|
+
export function recordEstimate(
|
|
70
|
+
provider: string,
|
|
71
|
+
model: string,
|
|
72
|
+
estimated: number,
|
|
73
|
+
actual: number,
|
|
74
|
+
): void {
|
|
75
|
+
if (estimated < MIN_SAMPLE_MAGNITUDE || actual < MIN_SAMPLE_MAGNITUDE) return;
|
|
76
|
+
const ratio = actual / estimated;
|
|
77
|
+
if (ratio < MIN_ACCEPTABLE_RATIO || ratio > MAX_ACCEPTABLE_RATIO) return;
|
|
78
|
+
|
|
79
|
+
applyEwmaUpdate(provider, model, ratio);
|
|
80
|
+
|
|
81
|
+
// Also fold into the per-provider aggregate so callers without a modelId
|
|
82
|
+
// fall back to a meaningful rolling correction instead of the 1.0 default.
|
|
83
|
+
// Skip when the caller already passed an empty model (avoids double-counting).
|
|
84
|
+
if (model.length > 0) {
|
|
85
|
+
applyEwmaUpdate(provider, "", ratio);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Correction factor to multiply a raw estimate by. Defaults to 1.0 for any
|
|
91
|
+
* unseen (provider, model) tuple, so first-call behavior is unchanged.
|
|
92
|
+
*
|
|
93
|
+
* When the model-specific key has no recorded samples, falls back to the
|
|
94
|
+
* per-provider aggregate `(provider, "")`. This covers model-alias
|
|
95
|
+
* mismatches where the configured model string (e.g. `claude-opus-4-6`)
|
|
96
|
+
* differs from the model the provider echoes back in its response
|
|
97
|
+
* (e.g. `claude-opus-4-6-20250514`).
|
|
98
|
+
*/
|
|
99
|
+
export function getCorrection(provider: string, model: string): number {
|
|
100
|
+
const specific = CALIBRATIONS.get(key(provider, model));
|
|
101
|
+
if (specific) return specific.ratio;
|
|
102
|
+
if (model.length > 0) {
|
|
103
|
+
return CALIBRATIONS.get(key(provider, ""))?.ratio ?? 1.0;
|
|
104
|
+
}
|
|
105
|
+
return 1.0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Test helper — clears all calibration state. */
|
|
109
|
+
export function resetCalibrations(): void {
|
|
110
|
+
CALIBRATIONS.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Observability — list current calibrations for logging/debugging. */
|
|
114
|
+
export function getCalibrationSnapshot(): Array<{
|
|
115
|
+
provider: string;
|
|
116
|
+
model: string;
|
|
117
|
+
ratio: number;
|
|
118
|
+
samples: number;
|
|
119
|
+
}> {
|
|
120
|
+
const out: Array<{
|
|
121
|
+
provider: string;
|
|
122
|
+
model: string;
|
|
123
|
+
ratio: number;
|
|
124
|
+
samples: number;
|
|
125
|
+
}> = [];
|
|
126
|
+
for (const [k, state] of CALIBRATIONS) {
|
|
127
|
+
const [provider, model] = k.split("::", 2) as [string, string];
|
|
128
|
+
out.push({
|
|
129
|
+
provider,
|
|
130
|
+
model,
|
|
131
|
+
ratio: state.ratio,
|
|
132
|
+
samples: state.sampleCount,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return out;
|
|
136
|
+
}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ContentBlock,
|
|
3
|
+
Message,
|
|
4
|
+
ToolResultContent,
|
|
5
|
+
} from "../providers/types.js";
|
|
6
|
+
import { estimateTextTokens } from "./token-estimator.js";
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Constants
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** Default number of recent user-assistant exchanges to keep untouched. */
|
|
13
|
+
const DEFAULT_PROTECT_RECENT_TURNS = 4;
|
|
14
|
+
|
|
15
|
+
/** Default minimum reclaimable tokens required before we commit a pass. */
|
|
16
|
+
const DEFAULT_MIN_GAIN_TOKENS = 2_000;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Default list of tool names whose results are never wholesale-cleared.
|
|
20
|
+
* Matches opencode's `PRUNE_PROTECTED_TOOLS` and Claude Code's equivalent —
|
|
21
|
+
* protects subagent outputs and curated skill results, which are expensive
|
|
22
|
+
* to regenerate and usually load-bearing for the rest of the conversation.
|
|
23
|
+
*
|
|
24
|
+
* Note: ax-tree stripping still applies to protected tool results. Only the
|
|
25
|
+
* full-body replacement is skipped.
|
|
26
|
+
*/
|
|
27
|
+
const DEFAULT_PROTECTED_TOOLS: readonly string[] = [
|
|
28
|
+
"Task",
|
|
29
|
+
"subagent",
|
|
30
|
+
"skill",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
/** Replacement body for cleared tool-result content. */
|
|
34
|
+
const CLEARED_TOOL_RESULT_TEXT = "[Old tool result content cleared]";
|
|
35
|
+
|
|
36
|
+
/** Replacement text for stubbed image blocks. */
|
|
37
|
+
const CLEARED_IMAGE_TEXT = "[image omitted]";
|
|
38
|
+
|
|
39
|
+
/** Replacement text for stubbed file blocks. */
|
|
40
|
+
const CLEARED_FILE_TEXT = "[file omitted]";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Regex that matches `<ax-tree>...</ax-tree>` blocks (non-greedy).
|
|
44
|
+
* Kept in sync with `AX_TREE_PATTERN` in `assistant/src/agent/loop.ts`; this
|
|
45
|
+
* module subsumes that function's responsibility for stale tool results.
|
|
46
|
+
*/
|
|
47
|
+
const AX_TREE_PATTERN = /<ax-tree>[\s\S]*?<\/ax-tree>/g;
|
|
48
|
+
|
|
49
|
+
/** Placeholder inserted in place of a stripped ax-tree block. */
|
|
50
|
+
const AX_TREE_PLACEHOLDER = "<ax_tree_omitted />";
|
|
51
|
+
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Types
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
|
|
56
|
+
export interface MicrocompactOptions {
|
|
57
|
+
/** Preserve the last N user-assistant exchanges verbatim. Default 4. */
|
|
58
|
+
protectRecentTurns?: number;
|
|
59
|
+
/** Tool names whose results are never microcompacted (e.g. "Task", sub-agents). */
|
|
60
|
+
protectedTools?: string[];
|
|
61
|
+
/** Minimum reclaimable tokens required to bother — skip no-op passes. Default 2000. */
|
|
62
|
+
minGainTokens?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MicrocompactResult {
|
|
66
|
+
messages: Message[];
|
|
67
|
+
reclaimedTokens: number;
|
|
68
|
+
clearedToolResults: number;
|
|
69
|
+
/** Count of image + file blocks that were replaced with text stubs. */
|
|
70
|
+
clearedMedia: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
// Public API
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Deterministically compact stale content in the message history.
|
|
79
|
+
*
|
|
80
|
+
* Walks `messages` from newest to oldest. The most recent
|
|
81
|
+
* `protectRecentTurns` user-assistant exchanges are left untouched. In the
|
|
82
|
+
* older region:
|
|
83
|
+
*
|
|
84
|
+
* - Each `tool_result` whose owning tool is NOT in `protectedTools` has its
|
|
85
|
+
* `content` replaced with a short placeholder.
|
|
86
|
+
* - Every `image` / `file` block is replaced with a text stub.
|
|
87
|
+
* - `<ax-tree>...</ax-tree>` blocks inside any tool_result (including
|
|
88
|
+
* protected ones) are collapsed to a placeholder, subsuming the old
|
|
89
|
+
* `compactAxTreeHistory` in `assistant/src/agent/loop.ts`.
|
|
90
|
+
*
|
|
91
|
+
* The `tool_use` / `tool_result` block structure is preserved — we only
|
|
92
|
+
* mutate block bodies, never the block types or their pairing — so provider
|
|
93
|
+
* serialization remains valid after compaction.
|
|
94
|
+
*
|
|
95
|
+
* If the estimated savings (`reclaimedTokens`) is below `minGainTokens`, the
|
|
96
|
+
* original `messages` reference is returned unchanged. The pass is
|
|
97
|
+
* idempotent: re-invoking on a previously compacted history produces zero
|
|
98
|
+
* incremental reclaim.
|
|
99
|
+
*/
|
|
100
|
+
export function microcompact(
|
|
101
|
+
messages: Message[],
|
|
102
|
+
options?: MicrocompactOptions,
|
|
103
|
+
): MicrocompactResult {
|
|
104
|
+
const protectRecentTurns =
|
|
105
|
+
options?.protectRecentTurns ?? DEFAULT_PROTECT_RECENT_TURNS;
|
|
106
|
+
const minGainTokens = options?.minGainTokens ?? DEFAULT_MIN_GAIN_TOKENS;
|
|
107
|
+
const protectedToolSet = new Set(
|
|
108
|
+
options?.protectedTools ?? DEFAULT_PROTECTED_TOOLS,
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
if (messages.length === 0) {
|
|
112
|
+
return {
|
|
113
|
+
messages,
|
|
114
|
+
reclaimedTokens: 0,
|
|
115
|
+
clearedToolResults: 0,
|
|
116
|
+
clearedMedia: 0,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Build a lookup of tool_use_id -> tool name by scanning every assistant
|
|
121
|
+
// message. Tool_use_ids are globally unique within a conversation so a
|
|
122
|
+
// single map is valid for the whole history.
|
|
123
|
+
const toolNameById = buildToolNameLookup(messages);
|
|
124
|
+
|
|
125
|
+
// Determine the index up to which messages are "protected" (left alone).
|
|
126
|
+
// Anything at index >= firstProtectedIdx is in the protected tail; the
|
|
127
|
+
// older region (indices < firstProtectedIdx) is where we clear.
|
|
128
|
+
const firstProtectedIdx = computeProtectedBoundary(
|
|
129
|
+
messages,
|
|
130
|
+
protectRecentTurns,
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
if (firstProtectedIdx <= 0) {
|
|
134
|
+
// Every message is protected — nothing to clear.
|
|
135
|
+
return {
|
|
136
|
+
messages,
|
|
137
|
+
reclaimedTokens: 0,
|
|
138
|
+
clearedToolResults: 0,
|
|
139
|
+
clearedMedia: 0,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
let reclaimedTokens = 0;
|
|
144
|
+
let clearedToolResults = 0;
|
|
145
|
+
let clearedMedia = 0;
|
|
146
|
+
let anyChange = false;
|
|
147
|
+
|
|
148
|
+
const nextMessages: Message[] = messages.map((msg, idx) => {
|
|
149
|
+
if (idx >= firstProtectedIdx) return msg;
|
|
150
|
+
|
|
151
|
+
let changed = false;
|
|
152
|
+
const nextContent: ContentBlock[] = msg.content.map((block) => {
|
|
153
|
+
// guard:allow-tool-result-only — compaction here operates on locally-
|
|
154
|
+
// executed `tool_result` bodies (string `.content`, possible
|
|
155
|
+
// `.contentBlocks` media). `web_search_tool_result` has an opaque
|
|
156
|
+
// encrypted content shape and is never microcompacted; it's treated as
|
|
157
|
+
// a tool-response only in `isToolResultOnlyUserMessage` above.
|
|
158
|
+
if (block.type === "tool_result") {
|
|
159
|
+
const tr = block as ToolResultContent;
|
|
160
|
+
const toolName = toolNameById.get(tr.tool_use_id);
|
|
161
|
+
const isProtected = toolName != null && protectedToolSet.has(toolName);
|
|
162
|
+
|
|
163
|
+
const { replacement, tokensSaved, didChange, cleared } =
|
|
164
|
+
compactToolResult(tr, isProtected);
|
|
165
|
+
if (didChange) {
|
|
166
|
+
reclaimedTokens += tokensSaved;
|
|
167
|
+
if (cleared) clearedToolResults += 1;
|
|
168
|
+
changed = true;
|
|
169
|
+
return replacement;
|
|
170
|
+
}
|
|
171
|
+
return block;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (block.type === "image") {
|
|
175
|
+
const saved = estimateImageReclaim(block);
|
|
176
|
+
// Only count a reclaim if stubbing actually shrinks the block.
|
|
177
|
+
// The stub is always valid, but avoid double-counting no-op cases.
|
|
178
|
+
reclaimedTokens += saved;
|
|
179
|
+
clearedMedia += 1;
|
|
180
|
+
changed = true;
|
|
181
|
+
return { type: "text", text: CLEARED_IMAGE_TEXT };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (block.type === "file") {
|
|
185
|
+
const saved = estimateFileReclaim(block);
|
|
186
|
+
reclaimedTokens += saved;
|
|
187
|
+
clearedMedia += 1;
|
|
188
|
+
changed = true;
|
|
189
|
+
return { type: "text", text: CLEARED_FILE_TEXT };
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return block;
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (!changed) return msg;
|
|
196
|
+
anyChange = true;
|
|
197
|
+
return { ...msg, content: nextContent };
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
if (!anyChange || reclaimedTokens < minGainTokens) {
|
|
201
|
+
return {
|
|
202
|
+
messages,
|
|
203
|
+
reclaimedTokens: 0,
|
|
204
|
+
clearedToolResults: 0,
|
|
205
|
+
clearedMedia: 0,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return {
|
|
210
|
+
messages: nextMessages,
|
|
211
|
+
reclaimedTokens,
|
|
212
|
+
clearedToolResults,
|
|
213
|
+
clearedMedia,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// ---------------------------------------------------------------------------
|
|
218
|
+
// Internals
|
|
219
|
+
// ---------------------------------------------------------------------------
|
|
220
|
+
|
|
221
|
+
function buildToolNameLookup(messages: Message[]): Map<string, string> {
|
|
222
|
+
const map = new Map<string, string>();
|
|
223
|
+
for (const msg of messages) {
|
|
224
|
+
if (msg.role !== "assistant") continue;
|
|
225
|
+
for (const block of msg.content) {
|
|
226
|
+
if (block.type === "tool_use") {
|
|
227
|
+
map.set(block.id, block.name);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return map;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Return the index of the first message that is inside the "protected tail".
|
|
236
|
+
*
|
|
237
|
+
* A user-assistant exchange begins at a user message that carries real user
|
|
238
|
+
* content (i.e. not a tool_result-only follow-up, which is how the provider
|
|
239
|
+
* encodes the tool response turn). We walk newest-to-oldest and, once we've
|
|
240
|
+
* seen `protectRecentTurns` such user-turn starts, return the index of the
|
|
241
|
+
* oldest such start. Everything at or after that index is protected.
|
|
242
|
+
*
|
|
243
|
+
* If the history has fewer than `protectRecentTurns` user turns, protect the
|
|
244
|
+
* entire history by returning 0.
|
|
245
|
+
*/
|
|
246
|
+
function computeProtectedBoundary(
|
|
247
|
+
messages: Message[],
|
|
248
|
+
protectRecentTurns: number,
|
|
249
|
+
): number {
|
|
250
|
+
if (protectRecentTurns <= 0) return messages.length;
|
|
251
|
+
|
|
252
|
+
let seen = 0;
|
|
253
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
254
|
+
const msg = messages[i];
|
|
255
|
+
if (msg.role !== "user") continue;
|
|
256
|
+
if (isToolResultOnlyUserMessage(msg)) continue;
|
|
257
|
+
seen += 1;
|
|
258
|
+
if (seen >= protectRecentTurns) {
|
|
259
|
+
return i;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
// Fewer user turns than the protection budget — protect everything.
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* A user message that contains only tool-response blocks (or system-injected
|
|
268
|
+
* metadata) represents the response side of a tool call rather than a fresh
|
|
269
|
+
* user turn.
|
|
270
|
+
*
|
|
271
|
+
* Qualifying blocks:
|
|
272
|
+
* - `tool_result` — locally-executed tool responses.
|
|
273
|
+
* - `web_search_tool_result` — server-side web-search responses. Same
|
|
274
|
+
* semantic as `tool_result` (paired with a prior `server_tool_use`), just
|
|
275
|
+
* a distinct discriminant. Missing this variant would cause these
|
|
276
|
+
* messages to masquerade as real user turns and eat `protectRecentTurns`
|
|
277
|
+
* budget on tool churn.
|
|
278
|
+
* - `text` blocks whose body is wholly inside `<system_notice>...</system_notice>`.
|
|
279
|
+
* Those are system-injected reminders/progress checks, not user-authored.
|
|
280
|
+
*/
|
|
281
|
+
function isToolResultOnlyUserMessage(message: Message): boolean {
|
|
282
|
+
if (message.content.length === 0) return false;
|
|
283
|
+
return message.content.every(isToolResponseOrSystemNoticeBlock);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function isToolResponseOrSystemNoticeBlock(block: ContentBlock): boolean {
|
|
287
|
+
if (block.type === "tool_result" || block.type === "web_search_tool_result") {
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
if (block.type === "text") {
|
|
291
|
+
const text = block.text;
|
|
292
|
+
return (
|
|
293
|
+
text.startsWith("<system_notice>") && text.endsWith("</system_notice>")
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Compact a single tool_result block in the stripped region.
|
|
301
|
+
*
|
|
302
|
+
* - If `isProtected` is true, we keep the body but strip `<ax-tree>` blocks
|
|
303
|
+
* and drop media (image/file) entries from `contentBlocks`, preserving
|
|
304
|
+
* any text entries so meaningful tool output isn't silently removed.
|
|
305
|
+
* - Otherwise we replace the body with a short placeholder and drop
|
|
306
|
+
* `contentBlocks` entirely.
|
|
307
|
+
*
|
|
308
|
+
* `tokensSaved` is the delta between the original and the replacement as
|
|
309
|
+
* measured by `estimateTextTokens`, floored at 0. `cleared` is true only when
|
|
310
|
+
* we fully replaced the body (i.e. counted against `clearedToolResults`).
|
|
311
|
+
*/
|
|
312
|
+
function compactToolResult(
|
|
313
|
+
block: ToolResultContent,
|
|
314
|
+
isProtected: boolean,
|
|
315
|
+
): {
|
|
316
|
+
replacement: ToolResultContent;
|
|
317
|
+
tokensSaved: number;
|
|
318
|
+
didChange: boolean;
|
|
319
|
+
cleared: boolean;
|
|
320
|
+
} {
|
|
321
|
+
const originalContent = block.content;
|
|
322
|
+
const originalContentTokens = estimateTextTokens(originalContent);
|
|
323
|
+
|
|
324
|
+
// Rich contentBlocks may include text (meaningful tool output) alongside
|
|
325
|
+
// media (images/files). For UNPROTECTED results we drop the entire array
|
|
326
|
+
// (the body is being wholesale-replaced). For PROTECTED results we keep
|
|
327
|
+
// text entries but strip media — images aren't the expensive part of a
|
|
328
|
+
// subagent result and are rarely load-bearing once the turn is stale, but
|
|
329
|
+
// text entries can carry real content we must not silently erase.
|
|
330
|
+
const originalContentBlocks = block.contentBlocks;
|
|
331
|
+
const hadContentBlocks =
|
|
332
|
+
originalContentBlocks != null && originalContentBlocks.length > 0;
|
|
333
|
+
|
|
334
|
+
let preservedContentBlocks: ContentBlock[] | undefined;
|
|
335
|
+
let droppedBlocksTokens = 0;
|
|
336
|
+
if (hadContentBlocks) {
|
|
337
|
+
if (isProtected) {
|
|
338
|
+
const kept: ContentBlock[] = [];
|
|
339
|
+
for (const cb of originalContentBlocks) {
|
|
340
|
+
if (cb.type === "text") {
|
|
341
|
+
kept.push(cb);
|
|
342
|
+
} else {
|
|
343
|
+
droppedBlocksTokens += estimateContentBlockTokenCost(cb);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
preservedContentBlocks = kept;
|
|
347
|
+
} else {
|
|
348
|
+
for (const cb of originalContentBlocks) {
|
|
349
|
+
droppedBlocksTokens += estimateContentBlockTokenCost(cb);
|
|
350
|
+
}
|
|
351
|
+
preservedContentBlocks = undefined;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let newContent: string;
|
|
356
|
+
let cleared: boolean;
|
|
357
|
+
if (isProtected) {
|
|
358
|
+
// Strip ax-tree blocks but keep the rest of the text body.
|
|
359
|
+
newContent = originalContent.replace(AX_TREE_PATTERN, AX_TREE_PLACEHOLDER);
|
|
360
|
+
cleared = false;
|
|
361
|
+
} else {
|
|
362
|
+
newContent = CLEARED_TOOL_RESULT_TEXT;
|
|
363
|
+
cleared = true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const newContentTokens = estimateTextTokens(newContent);
|
|
367
|
+
const bodyTokensSaved = originalContentTokens - newContentTokens;
|
|
368
|
+
|
|
369
|
+
const bodyChanged = newContent !== originalContent;
|
|
370
|
+
// A protected result with only text contentBlocks would be a no-op — nothing
|
|
371
|
+
// meaningful would be dropped. Only count contentBlocks as a "change" when
|
|
372
|
+
// we actually drop at least one entry.
|
|
373
|
+
const contentBlocksChanged = hadContentBlocks && droppedBlocksTokens > 0;
|
|
374
|
+
const didChange = bodyChanged || contentBlocksChanged;
|
|
375
|
+
|
|
376
|
+
if (!didChange) {
|
|
377
|
+
return {
|
|
378
|
+
replacement: block,
|
|
379
|
+
tokensSaved: 0,
|
|
380
|
+
didChange: false,
|
|
381
|
+
cleared: false,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const tokensSaved = Math.max(0, bodyTokensSaved + droppedBlocksTokens);
|
|
386
|
+
|
|
387
|
+
const replacement: ToolResultContent = {
|
|
388
|
+
type: "tool_result",
|
|
389
|
+
tool_use_id: block.tool_use_id,
|
|
390
|
+
content: newContent,
|
|
391
|
+
};
|
|
392
|
+
if (block.is_error != null) {
|
|
393
|
+
replacement.is_error = block.is_error;
|
|
394
|
+
}
|
|
395
|
+
if (preservedContentBlocks != null && preservedContentBlocks.length > 0) {
|
|
396
|
+
replacement.contentBlocks = preservedContentBlocks;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
replacement,
|
|
401
|
+
tokensSaved,
|
|
402
|
+
didChange,
|
|
403
|
+
cleared: cleared && bodyChanged,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
function estimateImageReclaim(
|
|
408
|
+
block: Extract<ContentBlock, { type: "image" }>,
|
|
409
|
+
): number {
|
|
410
|
+
// Base64 payloads are the expensive part of image blocks for our naive
|
|
411
|
+
// char/4 heuristic. The Anthropic-aware estimator in `token-estimator.ts`
|
|
412
|
+
// charges per-pixel — we deliberately use the simpler heuristic here so
|
|
413
|
+
// the reclaim number is a conservative lower bound on actual savings.
|
|
414
|
+
const payloadTokens = estimateTextTokens(block.source.data);
|
|
415
|
+
const stubTokens = estimateTextTokens(CLEARED_IMAGE_TEXT);
|
|
416
|
+
return Math.max(0, payloadTokens - stubTokens);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function estimateFileReclaim(
|
|
420
|
+
block: Extract<ContentBlock, { type: "file" }>,
|
|
421
|
+
): number {
|
|
422
|
+
const payloadTokens =
|
|
423
|
+
estimateTextTokens(block.source.data) +
|
|
424
|
+
estimateTextTokens(block.extracted_text ?? "");
|
|
425
|
+
const stubTokens = estimateTextTokens(CLEARED_FILE_TEXT);
|
|
426
|
+
return Math.max(0, payloadTokens - stubTokens);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function estimateContentBlockTokenCost(block: ContentBlock): number {
|
|
430
|
+
switch (block.type) {
|
|
431
|
+
case "text":
|
|
432
|
+
return estimateTextTokens(block.text);
|
|
433
|
+
case "image":
|
|
434
|
+
return estimateTextTokens(block.source.data);
|
|
435
|
+
case "file":
|
|
436
|
+
return (
|
|
437
|
+
estimateTextTokens(block.source.data) +
|
|
438
|
+
estimateTextTokens(block.extracted_text ?? "")
|
|
439
|
+
);
|
|
440
|
+
default:
|
|
441
|
+
return 0;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
You compress long assistant conversations into durable working memory.
|
|
2
|
+
Focus on actionable state, not prose.
|
|
3
|
+
Preserve concrete facts: goals, constraints, decisions, pending questions, file paths, commands, errors, and TODOs.
|
|
4
|
+
Remove repetition and stale details that were superseded.
|
|
5
|
+
Thread anchors: when a "Retained Thread References" section is present, each listed reply cites its parent via `→ Mxxxxxx`. If that parent appears in the Transcript, preserve its text verbatim (reactions may be aggregated as "N users reacted"). Omit when the section is absent.
|
|
6
|
+
Return concise markdown using these section headers exactly:
|
|
7
|
+
## Goals
|
|
8
|
+
## Constraints
|
|
9
|
+
## Decisions
|
|
10
|
+
## Open Conversations
|
|
11
|
+
## Key Artifacts
|
|
12
|
+
## Recent Progress
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ContentBlock,
|
|
3
3
|
Message,
|
|
4
|
+
Provider,
|
|
4
5
|
ToolDefinition,
|
|
5
6
|
} from "../providers/types.js";
|
|
7
|
+
import { getCorrection } from "./estimator-calibration.js";
|
|
6
8
|
import { parseImageDimensions } from "./image-dimensions.js";
|
|
7
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Canonical provider key used for calibration lookups and updates. Wrapper
|
|
12
|
+
* providers (e.g. OpenRouter routing `anthropic/*` traffic to the Messages
|
|
13
|
+
* API) set `tokenEstimationProvider` to the upstream provider name so the
|
|
14
|
+
* calibration key matches the one used when the provider actually produces
|
|
15
|
+
* the response. Falls back to `name` when the wrapper hint is unset.
|
|
16
|
+
*
|
|
17
|
+
* Every caller that records a sample or applies a correction must use this
|
|
18
|
+
* helper — otherwise wrapper-provider data is scattered across mismatched
|
|
19
|
+
* keys and the calibration becomes a no-op.
|
|
20
|
+
*/
|
|
21
|
+
export function getCalibrationProviderKey(provider: Provider): string {
|
|
22
|
+
return provider.tokenEstimationProvider ?? provider.name;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
const CHARS_PER_TOKEN = 4;
|
|
9
26
|
const MESSAGE_OVERHEAD_TOKENS = 4;
|
|
10
27
|
const TEXT_BLOCK_OVERHEAD_TOKENS = 2;
|
|
@@ -103,7 +120,9 @@ function estimateAnthropicImageTokens(width: number, height: number): number {
|
|
|
103
120
|
scaledHeight = Math.round(scaledHeight * mpScale);
|
|
104
121
|
}
|
|
105
122
|
|
|
106
|
-
return Math.ceil(
|
|
123
|
+
return Math.ceil(
|
|
124
|
+
scaledWidth * scaledHeight * ANTHROPIC_IMAGE_TOKENS_PER_PIXEL,
|
|
125
|
+
);
|
|
107
126
|
}
|
|
108
127
|
|
|
109
128
|
function estimateImageTokens(
|
|
@@ -139,13 +158,28 @@ export function estimateContentBlockTokens(
|
|
|
139
158
|
estimateTextTokens(stableJson(block.input))
|
|
140
159
|
);
|
|
141
160
|
case "tool_result": {
|
|
161
|
+
// Mirror the Anthropic serializer in providers/anthropic/client.ts
|
|
162
|
+
// (toAnthropicBlockSafe): block.content is always sent as the first
|
|
163
|
+
// text part, and contentBlocks are appended — but only `image` and
|
|
164
|
+
// `text` sub-blocks survive, and `image` is filtered out when
|
|
165
|
+
// is_error is true. Counting every contentBlocks entry regardless
|
|
166
|
+
// of type overestimates the wire size and can trigger spurious
|
|
167
|
+
// compaction on conversations that carry e.g. thinking sub-blocks.
|
|
168
|
+
// OpenAI and Gemini forward error-result images normally, so the
|
|
169
|
+
// is_error image drop is Anthropic-specific.
|
|
170
|
+
const anthropicDropsErrorImage =
|
|
171
|
+
options?.providerName === "anthropic" && block.is_error === true;
|
|
142
172
|
let tokens =
|
|
143
173
|
TOOL_BLOCK_OVERHEAD_TOKENS +
|
|
144
174
|
estimateTextTokens(block.tool_use_id) +
|
|
145
175
|
estimateTextTokens(block.content);
|
|
146
176
|
if (block.contentBlocks) {
|
|
147
177
|
for (const cb of block.contentBlocks) {
|
|
148
|
-
|
|
178
|
+
if (cb.type === "text") {
|
|
179
|
+
tokens += estimateContentBlockTokens(cb, options);
|
|
180
|
+
} else if (cb.type === "image" && !anthropicDropsErrorImage) {
|
|
181
|
+
tokens += estimateContentBlockTokens(cb, options);
|
|
182
|
+
}
|
|
149
183
|
}
|
|
150
184
|
}
|
|
151
185
|
return tokens;
|
|
@@ -224,7 +258,13 @@ export function estimateToolsTokens(tools: ToolDefinition[]): number {
|
|
|
224
258
|
return total;
|
|
225
259
|
}
|
|
226
260
|
|
|
227
|
-
|
|
261
|
+
/**
|
|
262
|
+
* Raw (uncorrected) prompt-token estimate — exposed so the calibrator
|
|
263
|
+
* can record (raw, actual) pairs. Applying calibration to the estimate
|
|
264
|
+
* it uses for training would create a feedback loop that eventually
|
|
265
|
+
* drives the correction ratio back to 1.0 regardless of true bias.
|
|
266
|
+
*/
|
|
267
|
+
export function estimatePromptTokensRaw(
|
|
228
268
|
messages: Message[],
|
|
229
269
|
systemPrompt?: string,
|
|
230
270
|
options?: TokenEstimatorOptions,
|
|
@@ -236,6 +276,24 @@ export function estimatePromptTokens(
|
|
|
236
276
|
return systemTokens + toolTokens + estimateMessagesTokens(messages, options);
|
|
237
277
|
}
|
|
238
278
|
|
|
279
|
+
export function estimatePromptTokens(
|
|
280
|
+
messages: Message[],
|
|
281
|
+
systemPrompt?: string,
|
|
282
|
+
options?: TokenEstimatorOptions,
|
|
283
|
+
): number {
|
|
284
|
+
const raw = estimatePromptTokensRaw(messages, systemPrompt, options);
|
|
285
|
+
|
|
286
|
+
// Apply the self-calibration correction. Default is 1.0 for any
|
|
287
|
+
// (provider, model) pair we haven't recorded a sample for, so first-call
|
|
288
|
+
// behavior is unchanged. As usage data accumulates, the correction ratio
|
|
289
|
+
// pulls estimates toward the provider's ground-truth token count. Lookup
|
|
290
|
+
// uses the per-provider aggregate key — `getCorrection` falls back to
|
|
291
|
+
// `(provider, "")` when a model-specific sample is not available.
|
|
292
|
+
const providerName = options?.providerName ?? "";
|
|
293
|
+
const correction = getCorrection(providerName, "");
|
|
294
|
+
return correction === 1.0 ? raw : Math.ceil(raw * correction);
|
|
295
|
+
}
|
|
296
|
+
|
|
239
297
|
function stableJson(value: unknown): string {
|
|
240
298
|
try {
|
|
241
299
|
return JSON.stringify(value);
|