@vellumai/assistant 0.4.52 → 0.4.54
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 +2 -2
- package/bun.lock +62 -349
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/keychain-broker.md +91 -40
- package/docs/architecture/memory.md +3 -3
- package/docs/architecture/security.md +2 -2
- package/knip.json +7 -29
- package/package.json +2 -9
- package/src/__tests__/agent-loop.test.ts +1 -1
- package/src/__tests__/app-git-history.test.ts +0 -2
- package/src/__tests__/app-git-service.test.ts +1 -6
- package/src/__tests__/approval-cascade.test.ts +3 -2
- package/src/__tests__/approval-routes-http.test.ts +0 -1
- package/src/__tests__/asset-materialize-tool.test.ts +0 -1
- package/src/__tests__/asset-search-tool.test.ts +0 -1
- package/src/__tests__/assistant-events-sse-hardening.test.ts +0 -1
- package/src/__tests__/attachments-store.test.ts +0 -1
- package/src/__tests__/avatar-e2e.test.ts +5 -1
- package/src/__tests__/browser-fill-credential.test.ts +4 -6
- package/src/__tests__/btw-routes.test.ts +39 -0
- package/src/__tests__/call-controller.test.ts +0 -1
- package/src/__tests__/call-domain.test.ts +1 -1
- package/src/__tests__/call-routes-http.test.ts +1 -3
- package/src/__tests__/canonical-guardian-store.test.ts +33 -2
- package/src/__tests__/channel-guardian.test.ts +4 -4
- package/src/__tests__/channel-readiness-routes.test.ts +0 -1
- package/src/__tests__/channel-readiness-service.test.ts +1 -1
- package/src/__tests__/checker.test.ts +13 -11
- package/src/__tests__/claude-code-skill-regression.test.ts +5 -2
- package/src/__tests__/claude-code-tool-profiles.test.ts +7 -3
- package/src/__tests__/config-loader-backfill.test.ts +1 -5
- package/src/__tests__/config-schema.test.ts +9 -46
- package/src/__tests__/config-watcher.test.ts +11 -3
- package/src/__tests__/conversation-routes-slash-commands.test.ts +0 -1
- package/src/__tests__/credential-broker-browser-fill.test.ts +27 -24
- package/src/__tests__/credential-broker-server-use.test.ts +76 -40
- package/src/__tests__/credential-security-e2e.test.ts +1 -6
- package/src/__tests__/credential-security-invariants.test.ts +27 -8
- package/src/__tests__/credential-vault-unit.test.ts +32 -16
- package/src/__tests__/credential-vault.test.ts +40 -28
- package/src/__tests__/credentials-cli.test.ts +1 -21
- package/src/__tests__/email-invite-adapter.test.ts +0 -1
- package/src/__tests__/error-handler-friendly-messages.test.ts +4 -5
- package/src/__tests__/fixtures/credential-security-fixtures.ts +3 -3
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -79
- package/src/__tests__/gateway-only-enforcement.test.ts +1 -23
- package/src/__tests__/guardian-action-conversation-turn.test.ts +8 -8
- package/src/__tests__/guardian-action-late-reply.test.ts +13 -14
- package/src/__tests__/guardian-action-store.test.ts +0 -57
- package/src/__tests__/guardian-outbound-http.test.ts +1 -1
- package/src/__tests__/guardian-verification-voice-binding.test.ts +1 -3
- package/src/__tests__/hooks-blocking.test.ts +1 -1
- package/src/__tests__/hooks-config.test.ts +5 -29
- package/src/__tests__/hooks-discovery.test.ts +1 -1
- package/src/__tests__/hooks-integration.test.ts +1 -1
- package/src/__tests__/hooks-manager.test.ts +1 -1
- package/src/__tests__/hooks-runner.test.ts +1 -23
- package/src/__tests__/hooks-settings.test.ts +1 -1
- package/src/__tests__/hooks-templates.test.ts +1 -1
- package/src/__tests__/host-shell-tool.test.ts +0 -1
- package/src/__tests__/http-user-message-parity.test.ts +19 -0
- package/src/__tests__/integration-status.test.ts +0 -1
- package/src/__tests__/invite-routes-http.test.ts +0 -3
- package/src/__tests__/list-messages-attachments.test.ts +0 -1
- package/src/__tests__/llm-usage-store.test.ts +50 -0
- package/src/__tests__/log-export-workspace.test.ts +233 -0
- package/src/__tests__/managed-proxy-context.test.ts +41 -41
- package/src/__tests__/managed-skill-lifecycle.test.ts +0 -1
- package/src/__tests__/media-generate-image.test.ts +9 -4
- package/src/__tests__/media-reuse-story.e2e.test.ts +1 -7
- package/src/__tests__/memory-regressions.experimental.test.ts +4 -4
- package/src/__tests__/memory-regressions.test.ts +27 -28
- package/src/__tests__/memory-retrieval.benchmark.test.ts +1 -1
- package/src/__tests__/memory-upsert-concurrency.test.ts +4 -4
- package/src/__tests__/migration-cross-version-compatibility.test.ts +0 -1
- package/src/__tests__/migration-export-http.test.ts +0 -1
- package/src/__tests__/migration-import-commit-http.test.ts +0 -1
- package/src/__tests__/migration-import-preflight-http.test.ts +0 -1
- package/src/__tests__/migration-validate-http.test.ts +0 -1
- package/src/__tests__/notification-decision-fallback.test.ts +1 -1
- package/src/__tests__/notification-schedule-dedup.test.ts +237 -0
- package/src/__tests__/oauth-cli.test.ts +2 -14
- package/src/__tests__/oauth-store.test.ts +3 -7
- package/src/__tests__/oauth2-gateway-transport.test.ts +5 -4
- package/src/__tests__/onboarding-starter-tasks.test.ts +1 -1
- package/src/__tests__/onboarding-template-contract.test.ts +1 -2
- package/src/__tests__/openai-provider.test.ts +7 -7
- package/src/__tests__/platform.test.ts +14 -4
- package/src/__tests__/pricing.test.ts +0 -234
- package/src/__tests__/provider-commit-message-generator.test.ts +19 -15
- package/src/__tests__/provider-fail-open-selection.test.ts +67 -62
- package/src/__tests__/provider-managed-proxy-integration.test.ts +88 -85
- package/src/__tests__/provider-registry-ollama.test.ts +10 -4
- package/src/__tests__/public-ingress-urls.test.ts +1 -1
- package/src/__tests__/recording-handler.test.ts +0 -1
- package/src/__tests__/registry.test.ts +3 -103
- package/src/__tests__/relay-server.test.ts +0 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +0 -1
- package/src/__tests__/runtime-events-sse-parity.test.ts +0 -1
- package/src/__tests__/runtime-events-sse.test.ts +0 -1
- package/src/__tests__/script-proxy-injection-runtime.test.ts +2 -7
- package/src/__tests__/secret-onetime-send.test.ts +1 -6
- package/src/__tests__/secret-routes-managed-proxy.test.ts +6 -14
- package/src/__tests__/secret-scanner-executor.test.ts +0 -1
- package/src/__tests__/secure-keys.test.ts +241 -229
- package/src/__tests__/send-endpoint-busy.test.ts +0 -1
- package/src/__tests__/session-abort-tool-results.test.ts +3 -2
- package/src/__tests__/session-agent-loop-overflow.test.ts +1012 -838
- package/src/__tests__/session-agent-loop.test.ts +2 -2
- package/src/__tests__/session-confirmation-signals.test.ts +3 -2
- package/src/__tests__/session-error.test.ts +5 -4
- package/src/__tests__/session-history-web-search.test.ts +34 -9
- package/src/__tests__/session-messaging-secret-redirect.test.ts +1 -7
- package/src/__tests__/session-pre-run-repair.test.ts +3 -2
- package/src/__tests__/session-provider-retry-repair.test.ts +31 -27
- package/src/__tests__/session-queue.test.ts +5 -5
- package/src/__tests__/session-runtime-assembly.test.ts +118 -0
- package/src/__tests__/session-slash-known.test.ts +31 -14
- package/src/__tests__/session-slash-queue.test.ts +3 -2
- package/src/__tests__/session-slash-unknown.test.ts +3 -2
- package/src/__tests__/session-workspace-cache-state.test.ts +3 -1
- package/src/__tests__/session-workspace-injection.test.ts +3 -2
- package/src/__tests__/session-workspace-tool-tracking.test.ts +3 -2
- package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -1
- package/src/__tests__/skill-projection-feature-flag.test.ts +0 -1
- package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -1
- package/src/__tests__/skillssh-registry.test.ts +21 -0
- package/src/__tests__/slack-channel-config.test.ts +1 -7
- package/src/__tests__/slack-share-routes.test.ts +1 -1
- package/src/__tests__/swarm-recursion.test.ts +4 -1
- package/src/__tests__/swarm-session-integration.test.ts +24 -14
- package/src/__tests__/swarm-tool.test.ts +4 -2
- package/src/__tests__/task-compiler.test.ts +1 -1
- package/src/__tests__/telegram-bot-username-resolution.test.ts +2 -4
- package/src/__tests__/test-support/browser-skill-harness.ts +0 -18
- package/src/__tests__/test-support/computer-use-skill-harness.ts +0 -23
- package/src/__tests__/token-estimator-accuracy.benchmark.test.ts +1521 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +0 -1
- package/src/__tests__/tool-executor-shell-integration.test.ts +0 -1
- package/src/__tests__/tool-executor.test.ts +1 -2
- package/src/__tests__/trust-store.test.ts +8 -83
- package/src/__tests__/twilio-config.test.ts +0 -1
- package/src/__tests__/twilio-provider.test.ts +0 -5
- package/src/__tests__/twilio-routes.test.ts +2 -3
- package/src/__tests__/usage-cache-backfill-migration.test.ts +10 -10
- package/src/__tests__/verification-control-plane-policy.test.ts +0 -1
- package/src/__tests__/voice-quality.test.ts +2 -1
- package/src/__tests__/voice-scoped-grant-consumer.test.ts +0 -1
- package/src/__tests__/web-search.test.ts +1 -1
- package/src/agent/loop.ts +17 -1
- package/src/bundler/app-bundler.ts +40 -24
- package/src/calls/call-controller.ts +16 -0
- package/src/calls/guardian-question-copy.ts +1 -1
- package/src/calls/relay-server.ts +29 -13
- package/src/calls/voice-control-protocol.ts +1 -0
- package/src/calls/voice-quality.ts +1 -1
- package/src/calls/voice-session-bridge.ts +9 -3
- package/src/channels/types.ts +16 -0
- package/src/cli/commands/bash.ts +173 -0
- package/src/cli/commands/doctor.ts +15 -57
- package/src/cli/commands/memory.ts +3 -5
- package/src/cli/commands/oauth/connections.ts +4 -2
- package/src/cli/commands/oauth/providers.ts +1 -13
- package/src/cli/commands/sessions.ts +1 -1
- package/src/cli/commands/usage.ts +359 -0
- package/src/cli/http-client.ts +22 -12
- package/src/cli/program.ts +4 -0
- package/src/cli/reference.ts +2 -0
- package/src/cli.ts +251 -181
- package/src/config/assistant-feature-flags.ts +0 -7
- package/src/config/bundled-skills/chatgpt-import/tools/chatgpt-import.ts +1 -1
- package/src/config/bundled-skills/claude-code/SKILL.md +1 -1
- package/src/config/bundled-skills/claude-code/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/SKILL.md +0 -1
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +4 -3
- package/src/config/bundled-skills/media-processing/services/reduce.ts +1 -1
- package/src/config/bundled-skills/media-processing/tools/analyze-keyframes.ts +3 -5
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -3
- package/src/config/bundled-skills/messaging/SKILL.md +0 -1
- package/src/config/bundled-skills/phone-calls/references/CONFIG.md +1 -1
- package/src/config/bundled-skills/sequences/SKILL.md +0 -1
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +5 -6
- package/src/config/env.ts +13 -0
- package/src/config/feature-flag-registry.json +15 -39
- package/src/config/loader.ts +7 -135
- package/src/config/schema.ts +0 -6
- package/src/config/schemas/channels.ts +1 -0
- package/src/config/schemas/elevenlabs.ts +2 -2
- package/src/config/schemas/security.ts +1 -2
- package/src/config/skills.ts +1 -1
- package/src/contacts/contact-store.ts +21 -75
- package/src/contacts/contacts-write.ts +6 -6
- package/src/contacts/types.ts +2 -0
- package/src/context/token-estimator.ts +35 -2
- package/src/context/window-manager.ts +16 -2
- package/src/daemon/approved-devices-store.ts +0 -44
- package/src/daemon/classifier.ts +1 -1
- package/src/daemon/config-watcher.ts +35 -11
- package/src/daemon/context-overflow-reducer.ts +13 -2
- package/src/daemon/handlers/config-ingress.ts +25 -8
- package/src/daemon/handlers/config-model.ts +22 -16
- package/src/daemon/handlers/config-telegram.ts +18 -6
- package/src/daemon/handlers/dictation.ts +0 -429
- package/src/daemon/handlers/sessions.ts +4 -116
- package/src/daemon/handlers/skills.ts +2 -201
- package/src/daemon/lifecycle.ts +21 -20
- package/src/daemon/message-types/contacts.ts +2 -0
- package/src/daemon/message-types/integrations.ts +1 -0
- package/src/daemon/message-types/sessions.ts +2 -0
- package/src/daemon/parse-actual-tokens-from-error.test.ts +75 -0
- package/src/daemon/providers-setup.ts +1 -1
- package/src/daemon/server.ts +42 -5
- package/src/daemon/session-agent-loop-handlers.ts +1 -1
- package/src/daemon/session-agent-loop.ts +27 -79
- package/src/daemon/session-error.ts +5 -4
- package/src/daemon/session-process.ts +17 -10
- package/src/daemon/session-runtime-assembly.ts +50 -0
- package/src/daemon/session-slash.ts +34 -22
- package/src/daemon/session.ts +1 -0
- package/src/daemon/shutdown-handlers.ts +15 -0
- package/src/daemon/watch-handler.ts +2 -2
- package/src/email/guardrails.ts +1 -1
- package/src/email/service.ts +0 -5
- package/src/events/domain-events.ts +1 -0
- package/src/hooks/templates.ts +1 -1
- package/src/media/app-icon-generator.ts +4 -3
- package/src/media/avatar-router.ts +5 -4
- package/src/media/gemini-image-service.ts +5 -5
- package/src/memory/admin.ts +2 -2
- package/src/memory/app-git-service.ts +0 -7
- package/src/memory/canonical-guardian-store.ts +25 -3
- package/src/memory/conversation-crud.ts +1 -1
- package/src/memory/conversation-title-service.ts +2 -2
- package/src/memory/db-init.ts +12 -0
- package/src/memory/embedding-backend.ts +46 -33
- package/src/memory/external-conversation-store.ts +0 -30
- package/src/memory/guardian-action-store.ts +0 -31
- package/src/memory/guardian-approvals.ts +1 -56
- package/src/memory/indexer.ts +4 -3
- package/src/memory/items-extractor.ts +1 -1
- package/src/memory/job-handlers/backfill.ts +5 -2
- package/src/memory/job-handlers/index-maintenance.ts +2 -2
- package/src/memory/job-handlers/media-processing.ts +2 -2
- package/src/memory/job-handlers/summarization.ts +1 -1
- package/src/memory/job-utils.ts +1 -2
- package/src/memory/jobs-worker.ts +2 -2
- package/src/memory/llm-usage-store.ts +57 -11
- package/src/memory/media-store.ts +4 -535
- package/src/memory/migrations/032-guardian-delivery-conversation-index.ts +2 -2
- package/src/memory/migrations/110-channel-guardian.ts +0 -1
- package/src/memory/migrations/158-channel-interaction-columns.ts +18 -0
- package/src/memory/migrations/159-drop-contact-interaction-columns.ts +16 -0
- package/src/memory/migrations/160-drop-loopback-port-column.ts +13 -0
- package/src/memory/migrations/index.ts +3 -0
- package/src/memory/published-pages-store.ts +0 -83
- package/src/memory/qdrant-circuit-breaker.ts +0 -8
- package/src/memory/retriever.test.ts +19 -12
- package/src/memory/retriever.ts +1 -1
- package/src/memory/schema/contacts.ts +2 -2
- package/src/memory/schema/oauth.ts +0 -1
- package/src/memory/search/semantic.ts +1 -8
- package/src/memory/shared-app-links-store.ts +0 -15
- package/src/messaging/registry.ts +0 -5
- package/src/messaging/style-analyzer.ts +1 -1
- package/src/notifications/copy-composer.ts +5 -13
- package/src/notifications/decision-engine.ts +2 -2
- package/src/notifications/deliveries-store.ts +0 -39
- package/src/notifications/guardian-question-mode.ts +6 -10
- package/src/notifications/preference-extractor.ts +1 -1
- package/src/oauth/byo-connection.test.ts +29 -20
- package/src/oauth/connect-orchestrator.ts +5 -3
- package/src/oauth/connect-types.ts +9 -2
- package/src/oauth/manual-token-connection.ts +9 -7
- package/src/oauth/oauth-store.ts +2 -8
- package/src/oauth/provider-behaviors.ts +11 -1
- package/src/oauth/seed-providers.ts +13 -5
- package/src/permissions/checker.ts +21 -2
- package/src/permissions/shell-identity.ts +0 -5
- package/src/permissions/trust-store.ts +0 -37
- package/src/prompts/__tests__/build-cli-reference-section.test.ts +1 -1
- package/src/prompts/system-prompt.ts +5 -14
- package/src/prompts/templates/BOOTSTRAP.md +1 -3
- package/src/providers/anthropic/client.ts +16 -8
- package/src/providers/managed-proxy/constants.ts +9 -11
- package/src/providers/managed-proxy/context.ts +14 -9
- package/src/providers/provider-send-message.ts +4 -52
- package/src/providers/registry.ts +29 -57
- package/src/providers/types.ts +1 -1
- package/src/runtime/actor-token-store.ts +0 -23
- package/src/runtime/auth/route-policy.ts +4 -0
- package/src/runtime/channel-invite-transports/telegram.ts +12 -6
- package/src/runtime/channel-retry-sweep.ts +6 -0
- package/src/runtime/http-router.ts +5 -1
- package/src/runtime/http-server.ts +101 -4
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/invite-instruction-generator.ts +25 -51
- package/src/runtime/invite-service.ts +0 -20
- package/src/runtime/middleware/error-handler.ts +1 -2
- package/src/runtime/routes/app-management-routes.ts +1 -0
- package/src/runtime/routes/attachment-routes.ts +1 -1
- package/src/runtime/routes/brain-graph-routes.ts +1 -1
- package/src/runtime/routes/btw-routes.ts +20 -1
- package/src/runtime/routes/call-routes.ts +1 -1
- package/src/runtime/routes/conversation-routes.ts +64 -24
- package/src/runtime/routes/debug-routes.ts +1 -1
- package/src/runtime/routes/diagnostics-routes.ts +2 -2
- package/src/runtime/routes/documents-routes.ts +3 -3
- package/src/runtime/routes/global-search-routes.ts +1 -1
- package/src/runtime/routes/guardian-bootstrap-routes.ts +0 -20
- package/src/runtime/routes/guardian-refresh-routes.ts +0 -20
- package/src/runtime/routes/inbound-message-handler.ts +10 -2
- package/src/runtime/routes/inbound-stages/background-dispatch.ts +4 -0
- package/src/runtime/routes/inbound-stages/edit-intercept.ts +5 -5
- package/src/runtime/routes/integrations/slack/share.ts +5 -5
- package/src/runtime/routes/log-export-routes.ts +122 -10
- package/src/runtime/routes/secret-routes.ts +4 -4
- package/src/runtime/routes/session-query-routes.ts +3 -3
- package/src/runtime/routes/settings-routes.ts +53 -0
- package/src/runtime/routes/trust-rules-routes.ts +1 -1
- package/src/runtime/routes/workspace-routes.ts +3 -0
- package/src/runtime/verification-templates.ts +1 -1
- package/src/security/credential-backend.ts +148 -0
- package/src/security/oauth2.ts +5 -5
- package/src/security/secret-allowlist.ts +1 -1
- package/src/security/secure-keys.ts +98 -160
- package/src/security/token-manager.ts +0 -7
- package/src/sequence/guardrails.ts +0 -4
- package/src/sequence/store.ts +1 -20
- package/src/sequence/types.ts +1 -36
- package/src/signals/bash.ts +157 -0
- package/src/signals/cancel.ts +69 -0
- package/src/signals/conversation-undo.ts +127 -0
- package/src/signals/trust-rule.ts +174 -0
- package/src/skills/clawhub.ts +5 -5
- package/src/skills/managed-store.ts +4 -4
- package/src/skills/skillssh-registry.ts +6 -1
- package/src/swarm/backend-claude-code.ts +6 -6
- package/src/swarm/worker-backend.ts +1 -1
- package/src/swarm/worker-runner.ts +1 -1
- package/src/telegram/bot-username.ts +11 -0
- package/src/telemetry/usage-telemetry-reporter.test.ts +366 -0
- package/src/telemetry/usage-telemetry-reporter.ts +181 -0
- package/src/tools/claude-code/claude-code.ts +6 -6
- package/src/tools/credentials/broker.ts +7 -5
- package/src/tools/credentials/vault.ts +11 -6
- package/src/tools/memory/handlers.test.ts +24 -26
- package/src/tools/memory/handlers.ts +1 -13
- package/src/tools/network/__tests__/web-search.test.ts +18 -86
- package/src/tools/network/web-search.ts +9 -15
- package/src/tools/registry.ts +5 -100
- package/src/tools/terminal/parser.ts +34 -4
- package/src/tools/tool-manifest.ts +0 -10
- package/src/usage/actors.ts +0 -12
- package/src/util/canonicalize-identity.ts +0 -9
- package/src/util/errors.ts +0 -3
- package/src/util/platform.ts +31 -8
- package/src/util/pricing.ts +0 -39
- package/src/watcher/constants.ts +0 -7
- package/src/watcher/providers/linear.ts +1 -1
- package/src/work-items/work-item-store.ts +4 -4
- package/src/workspace/commit-message-provider.ts +1 -1
- package/src/workspace/git-service.ts +44 -1
- package/src/workspace/provider-commit-message-generator.ts +11 -7
- package/src/__tests__/fixtures/proxy-fixtures.ts +0 -147
- package/src/browser-extension-relay/client.ts +0 -155
- package/src/contacts/index.ts +0 -18
- package/src/daemon/tls-certs.ts +0 -270
- package/src/errors.ts +0 -41
- package/src/events/index.ts +0 -18
- package/src/followups/index.ts +0 -10
- package/src/playbooks/index.ts +0 -10
- package/src/runtime/auth/index.ts +0 -44
- package/src/tasks/candidate-store.ts +0 -95
- package/src/tools/browser/api-map.ts +0 -313
- package/src/tools/browser/auto-navigate.ts +0 -469
- package/src/tools/browser/headless-browser.ts +0 -590
- package/src/tools/browser/recording-store.ts +0 -75
- package/src/tools/computer-use/registry.ts +0 -21
- package/src/tools/tasks/index.ts +0 -27
|
@@ -0,0 +1,1521 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Token Estimator Accuracy Benchmark
|
|
3
|
+
*
|
|
4
|
+
* Validates estimatePromptTokens() against Anthropic's countTokens API
|
|
5
|
+
* to measure the estimation gap. Requires ANTHROPIC_API_KEY to run.
|
|
6
|
+
*
|
|
7
|
+
* Run: cd assistant && ANTHROPIC_API_KEY=<key> bun test src/__tests__/token-estimator-accuracy.benchmark.test.ts
|
|
8
|
+
*/
|
|
9
|
+
import { describe, expect, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
estimatePromptTokens,
|
|
13
|
+
estimateToolsTokens,
|
|
14
|
+
} from "../context/token-estimator.js";
|
|
15
|
+
import type { Message, ToolDefinition } from "../providers/types.js";
|
|
16
|
+
|
|
17
|
+
const API_KEY = process.env.ANTHROPIC_API_KEY;
|
|
18
|
+
const MODEL = "claude-sonnet-4-20250514";
|
|
19
|
+
|
|
20
|
+
// Skip all tests if no API key is available
|
|
21
|
+
const describeWithApi = API_KEY ? describe : describe.skip;
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers to construct realistic payloads matching a desktop session
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
/** Generates a system prompt similar to production (~35-40K chars) */
|
|
28
|
+
function makeSystemPrompt(size: "small" | "production" = "small"): string {
|
|
29
|
+
const base = [
|
|
30
|
+
"You are a helpful AI assistant integrated into a desktop application.",
|
|
31
|
+
"You have access to the user's workspace, files, and tools.",
|
|
32
|
+
"Follow the user's instructions carefully and use tools when needed.",
|
|
33
|
+
"",
|
|
34
|
+
"## Guidelines",
|
|
35
|
+
"- Be concise and helpful",
|
|
36
|
+
"- Use tools to accomplish tasks rather than asking the user to do them",
|
|
37
|
+
"- When editing files, read them first to understand context",
|
|
38
|
+
"- Follow existing code style and conventions",
|
|
39
|
+
"- Ask clarifying questions when the request is ambiguous",
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
if (size === "small") {
|
|
43
|
+
return base.join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Production-sized system prompt (~35K chars) with realistic sections
|
|
47
|
+
const sections: string[] = [...base];
|
|
48
|
+
|
|
49
|
+
// Identity section (~1K chars)
|
|
50
|
+
sections.push(
|
|
51
|
+
"",
|
|
52
|
+
"## Identity",
|
|
53
|
+
"You are Jarvis, a personal AI assistant. Your emoji is 🤖.",
|
|
54
|
+
"You live in San Francisco, California.",
|
|
55
|
+
"You are curious, thorough, and always eager to help.",
|
|
56
|
+
"You express yourself with warmth and precision.",
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Soul section (~3K chars) - personality, boundaries, communication style
|
|
60
|
+
sections.push(
|
|
61
|
+
"",
|
|
62
|
+
"## Soul & Personality",
|
|
63
|
+
"You are an AI assistant with a distinct personality. You are warm, helpful, and thorough.",
|
|
64
|
+
"You have boundaries: you do not pretend to be human, you acknowledge uncertainty honestly,",
|
|
65
|
+
"and you prioritize the user's safety and privacy above all else.",
|
|
66
|
+
"",
|
|
67
|
+
"### Communication Style",
|
|
68
|
+
"- Be direct and concise, but not curt",
|
|
69
|
+
"- Use technical language when appropriate, but explain jargon",
|
|
70
|
+
"- Match the user's energy level and formality",
|
|
71
|
+
"- Use humor sparingly and only when the context is light",
|
|
72
|
+
"- Never use excessive exclamation marks or emojis unless the user does",
|
|
73
|
+
"",
|
|
74
|
+
"### Decision Making",
|
|
75
|
+
"- When faced with ambiguity, ask clarifying questions",
|
|
76
|
+
"- When multiple approaches exist, recommend the best one with reasoning",
|
|
77
|
+
"- When you make a mistake, acknowledge it immediately and correct course",
|
|
78
|
+
"- When you don't know something, say so rather than guessing",
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
// CLI reference section (~5K chars)
|
|
82
|
+
sections.push(
|
|
83
|
+
"",
|
|
84
|
+
"## CLI Reference",
|
|
85
|
+
"The assistant CLI provides these commands:",
|
|
86
|
+
"",
|
|
87
|
+
"### File Operations",
|
|
88
|
+
"- `file read <path>` — Read file contents",
|
|
89
|
+
"- `file write <path> <content>` — Write file contents",
|
|
90
|
+
"- `file edit <path> --old <old> --new <new>` — Edit file",
|
|
91
|
+
"- `file list [path]` — List directory contents",
|
|
92
|
+
"- `file search <query> [--type <type>]` — Search files",
|
|
93
|
+
"- `file move <from> <to>` — Move/rename file",
|
|
94
|
+
"- `file copy <from> <to>` — Copy file",
|
|
95
|
+
"- `file delete <path>` — Delete file (requires confirmation)",
|
|
96
|
+
"",
|
|
97
|
+
"### Terminal Operations",
|
|
98
|
+
"- `bash <command>` — Execute shell command",
|
|
99
|
+
"- `bash --timeout <seconds> <command>` — Execute with timeout",
|
|
100
|
+
"- `bash --background <command>` — Run in background",
|
|
101
|
+
"",
|
|
102
|
+
"### Memory Operations",
|
|
103
|
+
"- `memory recall <query>` — Search memories",
|
|
104
|
+
"- `memory store <key> <content>` — Store memory",
|
|
105
|
+
"- `memory forget <key>` — Delete memory",
|
|
106
|
+
"- `memory list` — List all memories",
|
|
107
|
+
"",
|
|
108
|
+
"### Web Operations",
|
|
109
|
+
"- `web search <query>` — Search the web",
|
|
110
|
+
"- `web fetch <url>` — Fetch URL content",
|
|
111
|
+
"- `web screenshot <url>` — Take screenshot",
|
|
112
|
+
"",
|
|
113
|
+
"### Skill Operations",
|
|
114
|
+
"- `skill list` — List available skills",
|
|
115
|
+
"- `skill run <name> [args]` — Execute skill",
|
|
116
|
+
"- `skill create <name>` — Create new skill",
|
|
117
|
+
"- `skill edit <name>` — Edit existing skill",
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// Tool permission section (~3K chars)
|
|
121
|
+
sections.push(
|
|
122
|
+
"",
|
|
123
|
+
"## Tool Permissions & Approval Gates",
|
|
124
|
+
"Some tools require explicit user approval before execution:",
|
|
125
|
+
"",
|
|
126
|
+
"### High-Risk Tools (always require approval)",
|
|
127
|
+
"- `bash` — Shell commands that modify the system",
|
|
128
|
+
"- `file_write` — Creating or overwriting files",
|
|
129
|
+
"- `file_edit` — Modifying existing files",
|
|
130
|
+
"- `file_delete` — Deleting files",
|
|
131
|
+
"- `credential_store set` — Storing credentials",
|
|
132
|
+
"",
|
|
133
|
+
"### Medium-Risk Tools (require approval on first use per session)",
|
|
134
|
+
"- `web_fetch` — Fetching external URLs",
|
|
135
|
+
"- `computer_use_*` — All computer use tools",
|
|
136
|
+
"- `messaging_send` — Sending messages",
|
|
137
|
+
"- `gmail_send` — Sending emails",
|
|
138
|
+
"",
|
|
139
|
+
"### Low-Risk Tools (auto-approved)",
|
|
140
|
+
"- `file_read` — Reading files",
|
|
141
|
+
"- `memory_recall` — Searching memories",
|
|
142
|
+
"- `web_search` — Web searches",
|
|
143
|
+
"- `tasks_list` — Listing tasks",
|
|
144
|
+
"- `contacts_search` — Searching contacts",
|
|
145
|
+
"",
|
|
146
|
+
"When a tool requires approval, explain what you're about to do and why,",
|
|
147
|
+
"then wait for the user's confirmation before proceeding.",
|
|
148
|
+
"Never bypass approval gates or attempt to run commands that circumvent them.",
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Channel awareness section (~2K chars)
|
|
152
|
+
sections.push(
|
|
153
|
+
"",
|
|
154
|
+
"## Channel Awareness",
|
|
155
|
+
"You may be accessed through different channels, each with different capabilities:",
|
|
156
|
+
"",
|
|
157
|
+
"### Desktop App (full capabilities)",
|
|
158
|
+
"- File system access, terminal, computer use, all tools available",
|
|
159
|
+
"- Rich text rendering with markdown support",
|
|
160
|
+
"- Image and file attachment support",
|
|
161
|
+
"",
|
|
162
|
+
"### Voice Channel (limited capabilities)",
|
|
163
|
+
"- Text-to-speech output, push-to-talk input",
|
|
164
|
+
"- No file system access, no computer use",
|
|
165
|
+
"- Keep responses concise for audio consumption",
|
|
166
|
+
"",
|
|
167
|
+
"### Dashboard (read-only view)",
|
|
168
|
+
"- Can view conversation history and memories",
|
|
169
|
+
"- Cannot execute tools or modify files",
|
|
170
|
+
"- Used for monitoring and reviewing assistant activity",
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
// Memory & continuity section (~2K chars)
|
|
174
|
+
sections.push(
|
|
175
|
+
"",
|
|
176
|
+
"## Memory & Continuity",
|
|
177
|
+
"You have access to persistent memory that survives across conversations.",
|
|
178
|
+
"Use memory to store important context, user preferences, project details,",
|
|
179
|
+
"and anything that would be useful to recall in future conversations.",
|
|
180
|
+
"",
|
|
181
|
+
"### Memory Best Practices",
|
|
182
|
+
"- Store user preferences when explicitly stated (e.g., 'I prefer tabs over spaces')",
|
|
183
|
+
"- Store project-specific context (e.g., 'This project uses PostgreSQL 15')",
|
|
184
|
+
"- Store decisions and their reasoning (e.g., 'We chose Redis over Memcached because...')",
|
|
185
|
+
"- Update memories when information changes",
|
|
186
|
+
"- Don't store trivial or ephemeral information",
|
|
187
|
+
"- Don't store sensitive information (passwords, API keys, etc.)",
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// Integration guidance (~3K chars)
|
|
191
|
+
sections.push(
|
|
192
|
+
"",
|
|
193
|
+
"## Integration Guidance",
|
|
194
|
+
"The assistant supports MCP (Model Context Protocol) servers for extending capabilities.",
|
|
195
|
+
"When the user asks about integrations:",
|
|
196
|
+
"",
|
|
197
|
+
"### Supported Integrations",
|
|
198
|
+
"- Google Workspace (Gmail, Calendar, Drive, Contacts)",
|
|
199
|
+
"- Slack (messaging, channel management)",
|
|
200
|
+
"- GitHub (repositories, issues, pull requests)",
|
|
201
|
+
"- Linear (project management, issue tracking)",
|
|
202
|
+
"- Notion (documents, databases)",
|
|
203
|
+
"- Sentry (error tracking, issue management)",
|
|
204
|
+
"",
|
|
205
|
+
"### OAuth Setup",
|
|
206
|
+
"Most integrations use OAuth for authentication.",
|
|
207
|
+
"Guide the user through the OAuth flow when setting up a new integration:",
|
|
208
|
+
"1. Navigate to Settings > Integrations",
|
|
209
|
+
"2. Click 'Connect' for the desired service",
|
|
210
|
+
"3. Authorize in the browser popup",
|
|
211
|
+
"4. Confirm the connection is active",
|
|
212
|
+
"",
|
|
213
|
+
"### MCP Servers",
|
|
214
|
+
"Custom MCP servers can be added via the config file.",
|
|
215
|
+
"The config lives at ~/.vellum/config.json.",
|
|
216
|
+
"Each MCP server entry requires: name, command, args, and optional env.",
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
// Dynamic skills catalog (~5K chars)
|
|
220
|
+
sections.push("", "## Available Skills", "<available_skills>");
|
|
221
|
+
const skillCategories = [
|
|
222
|
+
{
|
|
223
|
+
id: "gmail",
|
|
224
|
+
name: "Gmail",
|
|
225
|
+
desc: "Send, search, draft, and manage Gmail messages",
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
id: "calendar",
|
|
229
|
+
name: "Google Calendar",
|
|
230
|
+
desc: "Create, list, update, and delete calendar events",
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
id: "slack",
|
|
234
|
+
name: "Slack",
|
|
235
|
+
desc: "Send messages, search channels, manage threads",
|
|
236
|
+
},
|
|
237
|
+
{ id: "contacts", name: "Contacts", desc: "Search and manage contacts" },
|
|
238
|
+
{
|
|
239
|
+
id: "tasks",
|
|
240
|
+
name: "Tasks",
|
|
241
|
+
desc: "Create, list, update, and complete tasks",
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "browser",
|
|
245
|
+
name: "Browser",
|
|
246
|
+
desc: "Navigate web pages, take screenshots, interact with web content",
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: "schedule",
|
|
250
|
+
name: "Schedule",
|
|
251
|
+
desc: "Set reminders and schedule recurring tasks",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "messaging",
|
|
255
|
+
name: "Messaging",
|
|
256
|
+
desc: "Send iMessage and SMS messages",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "sequences",
|
|
260
|
+
name: "Sequences",
|
|
261
|
+
desc: "Create and manage multi-step automation workflows",
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: "playbooks",
|
|
265
|
+
name: "Playbooks",
|
|
266
|
+
desc: "Execute pre-defined operational playbooks",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
id: "notes",
|
|
270
|
+
name: "Notes",
|
|
271
|
+
desc: "Create and manage notes in Apple Notes",
|
|
272
|
+
},
|
|
273
|
+
{ id: "music", name: "Music", desc: "Control Apple Music playback" },
|
|
274
|
+
{
|
|
275
|
+
id: "photos",
|
|
276
|
+
name: "Photos",
|
|
277
|
+
desc: "Search and manage photos in Apple Photos",
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "maps",
|
|
281
|
+
name: "Maps",
|
|
282
|
+
desc: "Search locations, get directions, find nearby places",
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
id: "weather",
|
|
286
|
+
name: "Weather",
|
|
287
|
+
desc: "Get current weather and forecasts",
|
|
288
|
+
},
|
|
289
|
+
];
|
|
290
|
+
for (const skill of skillCategories) {
|
|
291
|
+
sections.push(
|
|
292
|
+
` <skill id="${skill.id}" name="${skill.name}" description="${skill.desc}" ` +
|
|
293
|
+
`credential_setup="oauth" enabled="true" />`,
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
sections.push("</available_skills>");
|
|
297
|
+
|
|
298
|
+
// Attachment handling (~1K chars)
|
|
299
|
+
sections.push(
|
|
300
|
+
"",
|
|
301
|
+
"## Attachment Handling",
|
|
302
|
+
"When sending files to the user, use the <vellum-attachment> tag:",
|
|
303
|
+
'`<vellum-attachment path="/path/to/file" type="image/png" />`',
|
|
304
|
+
"",
|
|
305
|
+
"Supported attachment types:",
|
|
306
|
+
"- Images: png, jpg, gif, webp, svg",
|
|
307
|
+
"- Documents: pdf, docx, xlsx, pptx",
|
|
308
|
+
"- Code: any text file with syntax highlighting",
|
|
309
|
+
"- Archives: zip, tar.gz",
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
// Task/schedule routing (~2K chars)
|
|
313
|
+
sections.push(
|
|
314
|
+
"",
|
|
315
|
+
"## Task & Schedule Routing",
|
|
316
|
+
"When the user asks to 'remind me' or 'schedule something', disambiguate:",
|
|
317
|
+
"",
|
|
318
|
+
"- **One-time reminder** → Use `schedule_reminder` tool",
|
|
319
|
+
"- **Recurring task** → Use `tasks_create` with recurrence",
|
|
320
|
+
"- **Calendar event** → Use `google_calendar_create_event`",
|
|
321
|
+
"- **Notification** → Use `send_notification` for immediate alerts",
|
|
322
|
+
"",
|
|
323
|
+
"Ask the user to clarify if the intent is ambiguous.",
|
|
324
|
+
"Default to `schedule_reminder` for simple time-based reminders.",
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Pad to ~35K chars with additional realistic instruction content
|
|
328
|
+
const currentLength = sections.join("\n").length;
|
|
329
|
+
if (currentLength < 35000) {
|
|
330
|
+
sections.push("", "## Additional Guidelines");
|
|
331
|
+
// Add realistic padding content to reach ~35K
|
|
332
|
+
const guidelines = [
|
|
333
|
+
"When working with code, always read the file before editing it.",
|
|
334
|
+
"When running shell commands, explain what each command does.",
|
|
335
|
+
"When searching the web, summarize the most relevant results.",
|
|
336
|
+
"When managing files, confirm destructive operations with the user.",
|
|
337
|
+
"When scheduling events, confirm the timezone with the user.",
|
|
338
|
+
"When sending messages, confirm the recipient and content before sending.",
|
|
339
|
+
"When managing credentials, never display sensitive values in plain text.",
|
|
340
|
+
"When creating tasks, include a clear due date and priority level.",
|
|
341
|
+
"When editing documents, preserve formatting and structure.",
|
|
342
|
+
"When processing images, describe what you see in detail.",
|
|
343
|
+
];
|
|
344
|
+
while (sections.join("\n").length < 35000) {
|
|
345
|
+
for (const g of guidelines) {
|
|
346
|
+
sections.push(`- ${g}`);
|
|
347
|
+
if (sections.join("\n").length >= 35000) break;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return sections.join("\n");
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Generates a runtime-injected user message with workspace HTML content */
|
|
356
|
+
function makeRuntimeInjectedMessage(): Message {
|
|
357
|
+
// Simulates the <active_workspace> XML block with app schema and page HTML
|
|
358
|
+
const appSchema = `<app_schema>
|
|
359
|
+
<component name="Sidebar" props="items: NavigationItem[], collapsed: boolean">
|
|
360
|
+
<component name="NavigationItem" props="label: string, icon: string, href: string, active: boolean" />
|
|
361
|
+
</component>
|
|
362
|
+
<component name="MainContent" props="children: ReactNode">
|
|
363
|
+
<component name="Header" props="title: string, breadcrumbs: Breadcrumb[]" />
|
|
364
|
+
<component name="DataTable" props="columns: Column[], rows: Row[], sortBy: string, filterText: string">
|
|
365
|
+
<component name="TableRow" props="cells: Cell[], selected: boolean, onSelect: () => void" />
|
|
366
|
+
</component>
|
|
367
|
+
</component>
|
|
368
|
+
<component name="Modal" props="open: boolean, title: string, onClose: () => void">
|
|
369
|
+
<component name="Form" props="fields: Field[], onSubmit: (data: FormData) => void" />
|
|
370
|
+
</component>
|
|
371
|
+
</app_schema>`;
|
|
372
|
+
|
|
373
|
+
// Simulate ~30K chars of page HTML (realistic for a complex web app page)
|
|
374
|
+
const pageHtmlLines: string[] = [];
|
|
375
|
+
for (let i = 0; i < 200; i++) {
|
|
376
|
+
pageHtmlLines.push(
|
|
377
|
+
`<div class="row-${i}" data-id="${i}" role="listitem">` +
|
|
378
|
+
`<span class="cell name">Item ${i}: ${`Lorem ipsum dolor sit amet, consectetur adipiscing elit. `.repeat(2)}</span>` +
|
|
379
|
+
`<span class="cell status">${i % 3 === 0 ? "active" : i % 3 === 1 ? "pending" : "completed"}</span>` +
|
|
380
|
+
`<span class="cell date">2026-03-${String((i % 28) + 1).padStart(2, "0")}</span>` +
|
|
381
|
+
`</div>`,
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const fileTree = Array.from(
|
|
386
|
+
{ length: 50 },
|
|
387
|
+
(_, i) => ` src/modules/feature-${i}/index.ts`,
|
|
388
|
+
).join("\n");
|
|
389
|
+
|
|
390
|
+
const workspaceXml = [
|
|
391
|
+
"<active_workspace>",
|
|
392
|
+
appSchema,
|
|
393
|
+
"<file_tree>",
|
|
394
|
+
fileTree,
|
|
395
|
+
"</file_tree>",
|
|
396
|
+
"<current_page>",
|
|
397
|
+
"<html>",
|
|
398
|
+
'<body class="app-root">',
|
|
399
|
+
pageHtmlLines.join("\n"),
|
|
400
|
+
"</body>",
|
|
401
|
+
"</html>",
|
|
402
|
+
"</current_page>",
|
|
403
|
+
"</active_workspace>",
|
|
404
|
+
].join("\n");
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
role: "user",
|
|
408
|
+
content: [
|
|
409
|
+
{
|
|
410
|
+
type: "text",
|
|
411
|
+
text:
|
|
412
|
+
workspaceXml +
|
|
413
|
+
"\n\nPlease help me refactor the data table component to support pagination.",
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/** Generates tool definitions matching a realistic desktop session */
|
|
420
|
+
function makeToolDefinitions(): Array<{
|
|
421
|
+
name: string;
|
|
422
|
+
description: string;
|
|
423
|
+
input_schema: object;
|
|
424
|
+
}> {
|
|
425
|
+
const tools: Array<{
|
|
426
|
+
name: string;
|
|
427
|
+
description: string;
|
|
428
|
+
input_schema: object;
|
|
429
|
+
}> = [];
|
|
430
|
+
|
|
431
|
+
// Core tools (11)
|
|
432
|
+
tools.push(
|
|
433
|
+
{
|
|
434
|
+
name: "bash",
|
|
435
|
+
description:
|
|
436
|
+
"Execute a shell command on the local machine. Use this for running scripts, installing packages, git operations, and other terminal tasks.",
|
|
437
|
+
input_schema: {
|
|
438
|
+
type: "object",
|
|
439
|
+
properties: {
|
|
440
|
+
command: {
|
|
441
|
+
type: "string",
|
|
442
|
+
description: "The shell command to execute",
|
|
443
|
+
},
|
|
444
|
+
reason: {
|
|
445
|
+
type: "string",
|
|
446
|
+
description:
|
|
447
|
+
"Brief non-technical explanation of why this command is being run, shown to the user for approval",
|
|
448
|
+
},
|
|
449
|
+
timeout_seconds: {
|
|
450
|
+
type: "number",
|
|
451
|
+
description:
|
|
452
|
+
"Optional timeout in seconds. Defaults to 120. Maximum 600.",
|
|
453
|
+
},
|
|
454
|
+
},
|
|
455
|
+
required: ["command", "reason"],
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: "file_read",
|
|
460
|
+
description:
|
|
461
|
+
"Read the contents of a file from the local filesystem. Returns the full file content as text. Use this before editing files to understand their current state.",
|
|
462
|
+
input_schema: {
|
|
463
|
+
type: "object",
|
|
464
|
+
properties: {
|
|
465
|
+
path: {
|
|
466
|
+
type: "string",
|
|
467
|
+
description: "Absolute or relative path to the file to read",
|
|
468
|
+
},
|
|
469
|
+
offset: {
|
|
470
|
+
type: "number",
|
|
471
|
+
description: "Line number to start reading from (0-indexed)",
|
|
472
|
+
},
|
|
473
|
+
limit: {
|
|
474
|
+
type: "number",
|
|
475
|
+
description: "Maximum number of lines to read",
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
required: ["path"],
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
{
|
|
482
|
+
name: "file_write",
|
|
483
|
+
description:
|
|
484
|
+
"Write content to a file, creating it if it doesn't exist or overwriting if it does. Use file_edit for surgical changes to existing files.",
|
|
485
|
+
input_schema: {
|
|
486
|
+
type: "object",
|
|
487
|
+
properties: {
|
|
488
|
+
path: {
|
|
489
|
+
type: "string",
|
|
490
|
+
description: "Path to the file to write",
|
|
491
|
+
},
|
|
492
|
+
content: {
|
|
493
|
+
type: "string",
|
|
494
|
+
description: "The full content to write to the file",
|
|
495
|
+
},
|
|
496
|
+
reason: {
|
|
497
|
+
type: "string",
|
|
498
|
+
description: "Brief explanation of why this file is being written",
|
|
499
|
+
},
|
|
500
|
+
},
|
|
501
|
+
required: ["path", "content", "reason"],
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
name: "file_edit",
|
|
506
|
+
description:
|
|
507
|
+
"Apply a surgical edit to an existing file by replacing a specific string with a new string. The old_string must appear exactly once in the file.",
|
|
508
|
+
input_schema: {
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: {
|
|
511
|
+
path: { type: "string", description: "Path to the file to edit" },
|
|
512
|
+
old_string: {
|
|
513
|
+
type: "string",
|
|
514
|
+
description:
|
|
515
|
+
"The exact string to find and replace (must be unique in the file)",
|
|
516
|
+
},
|
|
517
|
+
new_string: {
|
|
518
|
+
type: "string",
|
|
519
|
+
description: "The replacement string",
|
|
520
|
+
},
|
|
521
|
+
reason: {
|
|
522
|
+
type: "string",
|
|
523
|
+
description: "Brief explanation of the edit",
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
required: ["path", "old_string", "new_string", "reason"],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: "web_search",
|
|
531
|
+
description:
|
|
532
|
+
"Search the web for information. Returns a list of search results with titles, URLs, and snippets.",
|
|
533
|
+
input_schema: {
|
|
534
|
+
type: "object",
|
|
535
|
+
properties: {
|
|
536
|
+
query: { type: "string", description: "The search query" },
|
|
537
|
+
num_results: {
|
|
538
|
+
type: "number",
|
|
539
|
+
description: "Number of results to return (default 5, max 10)",
|
|
540
|
+
},
|
|
541
|
+
},
|
|
542
|
+
required: ["query"],
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "web_fetch",
|
|
547
|
+
description:
|
|
548
|
+
"Fetch the content of a URL. Returns the page content as text (HTML stripped to readable text by default).",
|
|
549
|
+
input_schema: {
|
|
550
|
+
type: "object",
|
|
551
|
+
properties: {
|
|
552
|
+
url: { type: "string", description: "The URL to fetch" },
|
|
553
|
+
format: {
|
|
554
|
+
type: "string",
|
|
555
|
+
enum: ["text", "html", "markdown"],
|
|
556
|
+
description: "Output format (default: text)",
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
required: ["url"],
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "memory_recall",
|
|
564
|
+
description:
|
|
565
|
+
"Search across your memory using hybrid semantic and recency-based retrieval. Use this to find information from past conversations, stored facts, or contextual knowledge.",
|
|
566
|
+
input_schema: {
|
|
567
|
+
type: "object",
|
|
568
|
+
properties: {
|
|
569
|
+
query: {
|
|
570
|
+
type: "string",
|
|
571
|
+
description: "The search query to find relevant memories",
|
|
572
|
+
},
|
|
573
|
+
scope: {
|
|
574
|
+
type: "string",
|
|
575
|
+
enum: ["default", "conversation"],
|
|
576
|
+
description:
|
|
577
|
+
"Search scope: 'default' searches all memories, 'conversation' searches only the current conversation",
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
required: ["query"],
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: "memory_manage",
|
|
585
|
+
description:
|
|
586
|
+
"Create, update, or delete a memory entry. Use this to store important information for future reference.",
|
|
587
|
+
input_schema: {
|
|
588
|
+
type: "object",
|
|
589
|
+
properties: {
|
|
590
|
+
action: {
|
|
591
|
+
type: "string",
|
|
592
|
+
enum: ["create", "update", "delete"],
|
|
593
|
+
description: "The memory operation to perform",
|
|
594
|
+
},
|
|
595
|
+
key: {
|
|
596
|
+
type: "string",
|
|
597
|
+
description: "Unique key identifying this memory",
|
|
598
|
+
},
|
|
599
|
+
content: {
|
|
600
|
+
type: "string",
|
|
601
|
+
description: "The content to store (required for create/update)",
|
|
602
|
+
},
|
|
603
|
+
tags: {
|
|
604
|
+
type: "array",
|
|
605
|
+
items: { type: "string" },
|
|
606
|
+
description: "Optional tags for categorizing the memory",
|
|
607
|
+
},
|
|
608
|
+
},
|
|
609
|
+
required: ["action", "key"],
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
{
|
|
613
|
+
name: "skill_execute",
|
|
614
|
+
description:
|
|
615
|
+
"Execute a loaded skill by name. Skills are pre-defined automation routines that can perform complex multi-step tasks.",
|
|
616
|
+
input_schema: {
|
|
617
|
+
type: "object",
|
|
618
|
+
properties: {
|
|
619
|
+
skill_name: {
|
|
620
|
+
type: "string",
|
|
621
|
+
description: "The name of the skill to execute",
|
|
622
|
+
},
|
|
623
|
+
arguments: {
|
|
624
|
+
type: "object",
|
|
625
|
+
description: "Arguments to pass to the skill",
|
|
626
|
+
},
|
|
627
|
+
},
|
|
628
|
+
required: ["skill_name"],
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: "asset_search",
|
|
633
|
+
description:
|
|
634
|
+
"Search for assets (images, documents, files) in the workspace by name, type, or content.",
|
|
635
|
+
input_schema: {
|
|
636
|
+
type: "object",
|
|
637
|
+
properties: {
|
|
638
|
+
query: { type: "string", description: "Search query" },
|
|
639
|
+
type: {
|
|
640
|
+
type: "string",
|
|
641
|
+
enum: ["image", "document", "code", "any"],
|
|
642
|
+
description: "Filter by asset type",
|
|
643
|
+
},
|
|
644
|
+
},
|
|
645
|
+
required: ["query"],
|
|
646
|
+
},
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: "credential_store",
|
|
650
|
+
description:
|
|
651
|
+
"Securely store or retrieve credentials for external services. Credentials are encrypted at rest.",
|
|
652
|
+
input_schema: {
|
|
653
|
+
type: "object",
|
|
654
|
+
properties: {
|
|
655
|
+
action: {
|
|
656
|
+
type: "string",
|
|
657
|
+
enum: ["get", "set", "delete", "list"],
|
|
658
|
+
},
|
|
659
|
+
service: {
|
|
660
|
+
type: "string",
|
|
661
|
+
description: "The service name (e.g., 'github', 'slack')",
|
|
662
|
+
},
|
|
663
|
+
key: {
|
|
664
|
+
type: "string",
|
|
665
|
+
description: "Credential key within the service",
|
|
666
|
+
},
|
|
667
|
+
value: {
|
|
668
|
+
type: "string",
|
|
669
|
+
description: "Credential value (required for 'set')",
|
|
670
|
+
},
|
|
671
|
+
},
|
|
672
|
+
required: ["action", "service"],
|
|
673
|
+
},
|
|
674
|
+
},
|
|
675
|
+
);
|
|
676
|
+
|
|
677
|
+
// Computer-use proxy tools (11)
|
|
678
|
+
const computerUseTools = [
|
|
679
|
+
{
|
|
680
|
+
name: "computer_use_click",
|
|
681
|
+
description:
|
|
682
|
+
"Click an element on screen. Prefer element_id from the accessibility tree over x/y coordinates for reliability.",
|
|
683
|
+
props: {
|
|
684
|
+
click_type: {
|
|
685
|
+
type: "string",
|
|
686
|
+
enum: ["single", "double", "right"],
|
|
687
|
+
description: "Type of click",
|
|
688
|
+
},
|
|
689
|
+
element_id: {
|
|
690
|
+
type: "integer",
|
|
691
|
+
description: "Accessibility tree element ID",
|
|
692
|
+
},
|
|
693
|
+
x: { type: "integer", description: "Screen x coordinate" },
|
|
694
|
+
y: { type: "integer", description: "Screen y coordinate" },
|
|
695
|
+
reasoning: {
|
|
696
|
+
type: "string",
|
|
697
|
+
description: "Explanation of what you see and why you're clicking",
|
|
698
|
+
},
|
|
699
|
+
reason: {
|
|
700
|
+
type: "string",
|
|
701
|
+
description: "Brief non-technical explanation for the user",
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
required: ["reasoning"],
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
name: "computer_use_type_text",
|
|
708
|
+
description:
|
|
709
|
+
"Type text into the currently focused element. The element should already be focused via a click.",
|
|
710
|
+
props: {
|
|
711
|
+
text: { type: "string", description: "The text to type" },
|
|
712
|
+
reasoning: {
|
|
713
|
+
type: "string",
|
|
714
|
+
description: "Why this text is being typed",
|
|
715
|
+
},
|
|
716
|
+
reason: {
|
|
717
|
+
type: "string",
|
|
718
|
+
description: "Brief user-facing explanation",
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
required: ["text", "reasoning"],
|
|
722
|
+
},
|
|
723
|
+
{
|
|
724
|
+
name: "computer_use_key",
|
|
725
|
+
description:
|
|
726
|
+
"Press a keyboard key or key combination (e.g., 'Return', 'cmd+c', 'shift+tab').",
|
|
727
|
+
props: {
|
|
728
|
+
key: {
|
|
729
|
+
type: "string",
|
|
730
|
+
description: "Key or key combination to press",
|
|
731
|
+
},
|
|
732
|
+
reasoning: { type: "string", description: "Why this key is pressed" },
|
|
733
|
+
reason: {
|
|
734
|
+
type: "string",
|
|
735
|
+
description: "Brief user-facing explanation",
|
|
736
|
+
},
|
|
737
|
+
},
|
|
738
|
+
required: ["key", "reasoning"],
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
name: "computer_use_scroll",
|
|
742
|
+
description:
|
|
743
|
+
"Scroll in a direction at the current cursor position or a specified element.",
|
|
744
|
+
props: {
|
|
745
|
+
direction: {
|
|
746
|
+
type: "string",
|
|
747
|
+
enum: ["up", "down", "left", "right"],
|
|
748
|
+
},
|
|
749
|
+
amount: { type: "integer", description: "Scroll amount in pixels" },
|
|
750
|
+
element_id: {
|
|
751
|
+
type: "integer",
|
|
752
|
+
description: "Element to scroll within",
|
|
753
|
+
},
|
|
754
|
+
reasoning: { type: "string", description: "Why scrolling" },
|
|
755
|
+
},
|
|
756
|
+
required: ["direction", "reasoning"],
|
|
757
|
+
},
|
|
758
|
+
{
|
|
759
|
+
name: "computer_use_drag",
|
|
760
|
+
description: "Drag from one point to another on screen.",
|
|
761
|
+
props: {
|
|
762
|
+
start_x: { type: "integer" },
|
|
763
|
+
start_y: { type: "integer" },
|
|
764
|
+
end_x: { type: "integer" },
|
|
765
|
+
end_y: { type: "integer" },
|
|
766
|
+
reasoning: { type: "string" },
|
|
767
|
+
},
|
|
768
|
+
required: ["start_x", "start_y", "end_x", "end_y", "reasoning"],
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
name: "computer_use_wait",
|
|
772
|
+
description:
|
|
773
|
+
"Wait for a specified duration before taking the next action. Use when UI needs time to load.",
|
|
774
|
+
props: {
|
|
775
|
+
seconds: {
|
|
776
|
+
type: "number",
|
|
777
|
+
description: "Number of seconds to wait (max 10)",
|
|
778
|
+
},
|
|
779
|
+
reasoning: { type: "string", description: "Why waiting" },
|
|
780
|
+
},
|
|
781
|
+
required: ["seconds", "reasoning"],
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
name: "computer_use_open_app",
|
|
785
|
+
description: "Open a macOS application by name.",
|
|
786
|
+
props: {
|
|
787
|
+
app_name: {
|
|
788
|
+
type: "string",
|
|
789
|
+
description: "The application name (e.g., 'Safari', 'Terminal')",
|
|
790
|
+
},
|
|
791
|
+
reasoning: { type: "string" },
|
|
792
|
+
reason: { type: "string" },
|
|
793
|
+
},
|
|
794
|
+
required: ["app_name", "reasoning"],
|
|
795
|
+
},
|
|
796
|
+
{
|
|
797
|
+
name: "computer_use_run_applescript",
|
|
798
|
+
description:
|
|
799
|
+
"Execute an AppleScript on the user's machine. Use for macOS automation tasks.",
|
|
800
|
+
props: {
|
|
801
|
+
script: { type: "string", description: "The AppleScript code to run" },
|
|
802
|
+
reasoning: { type: "string" },
|
|
803
|
+
reason: { type: "string" },
|
|
804
|
+
},
|
|
805
|
+
required: ["script", "reasoning"],
|
|
806
|
+
},
|
|
807
|
+
{
|
|
808
|
+
name: "computer_use_observe",
|
|
809
|
+
description:
|
|
810
|
+
"Capture and analyze the current screen state. Returns a screenshot and accessibility tree of visible UI elements.",
|
|
811
|
+
props: {
|
|
812
|
+
reasoning: {
|
|
813
|
+
type: "string",
|
|
814
|
+
description: "What you expect to see and why you're observing",
|
|
815
|
+
},
|
|
816
|
+
},
|
|
817
|
+
required: ["reasoning"],
|
|
818
|
+
},
|
|
819
|
+
{
|
|
820
|
+
name: "computer_use_done",
|
|
821
|
+
description:
|
|
822
|
+
"Signal that the computer-use task is complete. Call this when the UI task is finished.",
|
|
823
|
+
props: {
|
|
824
|
+
result: {
|
|
825
|
+
type: "string",
|
|
826
|
+
description: "Summary of what was accomplished",
|
|
827
|
+
},
|
|
828
|
+
reasoning: { type: "string" },
|
|
829
|
+
},
|
|
830
|
+
required: ["result"],
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
name: "computer_use_respond",
|
|
834
|
+
description:
|
|
835
|
+
"Send a text response to the user during a computer-use session without performing a UI action.",
|
|
836
|
+
props: {
|
|
837
|
+
message: {
|
|
838
|
+
type: "string",
|
|
839
|
+
description: "The response message",
|
|
840
|
+
},
|
|
841
|
+
reasoning: { type: "string" },
|
|
842
|
+
},
|
|
843
|
+
required: ["message"],
|
|
844
|
+
},
|
|
845
|
+
];
|
|
846
|
+
|
|
847
|
+
for (const cu of computerUseTools) {
|
|
848
|
+
tools.push({
|
|
849
|
+
name: cu.name,
|
|
850
|
+
description: cu.description,
|
|
851
|
+
input_schema: {
|
|
852
|
+
type: "object",
|
|
853
|
+
properties: cu.props,
|
|
854
|
+
required: cu.required,
|
|
855
|
+
},
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
// Bundled skill tools (~15 representative ones from gmail, calendar, slack, etc.)
|
|
860
|
+
const skillTools = [
|
|
861
|
+
{
|
|
862
|
+
name: "gmail_send",
|
|
863
|
+
description:
|
|
864
|
+
"Send an email via Gmail. Supports to, cc, bcc, subject, body (plain text or HTML), and attachments.",
|
|
865
|
+
props: {
|
|
866
|
+
account: { type: "string", description: "Gmail account to send from" },
|
|
867
|
+
to: { type: "string", description: "Recipient email address" },
|
|
868
|
+
cc: { type: "string", description: "CC recipients (comma-separated)" },
|
|
869
|
+
bcc: { type: "string", description: "BCC recipients" },
|
|
870
|
+
subject: { type: "string", description: "Email subject line" },
|
|
871
|
+
body: { type: "string", description: "Email body content" },
|
|
872
|
+
html: { type: "boolean", description: "Whether body is HTML" },
|
|
873
|
+
reply_to_message_id: {
|
|
874
|
+
type: "string",
|
|
875
|
+
description: "Message ID to reply to",
|
|
876
|
+
},
|
|
877
|
+
thread_id: { type: "string", description: "Thread ID to add to" },
|
|
878
|
+
},
|
|
879
|
+
required: ["to", "subject", "body"],
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
name: "gmail_search",
|
|
883
|
+
description:
|
|
884
|
+
"Search Gmail messages using Gmail search syntax. Returns message summaries.",
|
|
885
|
+
props: {
|
|
886
|
+
query: { type: "string", description: "Gmail search query" },
|
|
887
|
+
max_results: {
|
|
888
|
+
type: "number",
|
|
889
|
+
description: "Max results (default 10)",
|
|
890
|
+
},
|
|
891
|
+
account: { type: "string" },
|
|
892
|
+
},
|
|
893
|
+
required: ["query"],
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
name: "gmail_draft",
|
|
897
|
+
description: "Create a draft email in Gmail without sending it.",
|
|
898
|
+
props: {
|
|
899
|
+
account: { type: "string" },
|
|
900
|
+
to: { type: "string" },
|
|
901
|
+
subject: { type: "string" },
|
|
902
|
+
body: { type: "string" },
|
|
903
|
+
html: { type: "boolean" },
|
|
904
|
+
},
|
|
905
|
+
required: ["to", "subject", "body"],
|
|
906
|
+
},
|
|
907
|
+
{
|
|
908
|
+
name: "google_calendar_create_event",
|
|
909
|
+
description:
|
|
910
|
+
"Create a new event on Google Calendar with attendees, location, and recurrence.",
|
|
911
|
+
props: {
|
|
912
|
+
calendar_id: { type: "string" },
|
|
913
|
+
title: { type: "string", description: "Event title" },
|
|
914
|
+
start: { type: "string", description: "Start time (ISO 8601)" },
|
|
915
|
+
end: { type: "string", description: "End time (ISO 8601)" },
|
|
916
|
+
description: { type: "string", description: "Event description" },
|
|
917
|
+
location: { type: "string" },
|
|
918
|
+
attendees: {
|
|
919
|
+
type: "array",
|
|
920
|
+
items: { type: "string" },
|
|
921
|
+
description: "Email addresses",
|
|
922
|
+
},
|
|
923
|
+
recurrence: { type: "string", description: "RRULE string" },
|
|
924
|
+
timezone: { type: "string" },
|
|
925
|
+
},
|
|
926
|
+
required: ["title", "start", "end"],
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
name: "google_calendar_list_events",
|
|
930
|
+
description:
|
|
931
|
+
"List upcoming events from Google Calendar within a time range.",
|
|
932
|
+
props: {
|
|
933
|
+
calendar_id: { type: "string" },
|
|
934
|
+
time_min: { type: "string" },
|
|
935
|
+
time_max: { type: "string" },
|
|
936
|
+
max_results: { type: "number" },
|
|
937
|
+
query: { type: "string" },
|
|
938
|
+
},
|
|
939
|
+
required: [],
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: "slack_send_message",
|
|
943
|
+
description:
|
|
944
|
+
"Send a message to a Slack channel or DM. Supports threads and formatted text.",
|
|
945
|
+
props: {
|
|
946
|
+
channel: { type: "string", description: "Channel name or ID" },
|
|
947
|
+
text: { type: "string", description: "Message text" },
|
|
948
|
+
thread_ts: {
|
|
949
|
+
type: "string",
|
|
950
|
+
description: "Thread timestamp for replies",
|
|
951
|
+
},
|
|
952
|
+
blocks: { type: "array", description: "Rich text blocks (Block Kit)" },
|
|
953
|
+
},
|
|
954
|
+
required: ["channel", "text"],
|
|
955
|
+
},
|
|
956
|
+
{
|
|
957
|
+
name: "slack_search_messages",
|
|
958
|
+
description:
|
|
959
|
+
"Search Slack messages across channels using Slack search syntax.",
|
|
960
|
+
props: {
|
|
961
|
+
query: { type: "string" },
|
|
962
|
+
sort: { type: "string", enum: ["score", "timestamp"] },
|
|
963
|
+
count: { type: "number" },
|
|
964
|
+
},
|
|
965
|
+
required: ["query"],
|
|
966
|
+
},
|
|
967
|
+
{
|
|
968
|
+
name: "slack_list_channels",
|
|
969
|
+
description:
|
|
970
|
+
"List Slack channels the user has access to, filtered by type.",
|
|
971
|
+
props: {
|
|
972
|
+
types: {
|
|
973
|
+
type: "string",
|
|
974
|
+
description: "Channel types: public, private, im, mpim",
|
|
975
|
+
},
|
|
976
|
+
limit: { type: "number" },
|
|
977
|
+
},
|
|
978
|
+
required: [],
|
|
979
|
+
},
|
|
980
|
+
{
|
|
981
|
+
name: "contacts_search",
|
|
982
|
+
description:
|
|
983
|
+
"Search the user's contacts by name, email, phone, or organization.",
|
|
984
|
+
props: {
|
|
985
|
+
query: { type: "string" },
|
|
986
|
+
limit: { type: "number" },
|
|
987
|
+
},
|
|
988
|
+
required: ["query"],
|
|
989
|
+
},
|
|
990
|
+
{
|
|
991
|
+
name: "tasks_list",
|
|
992
|
+
description:
|
|
993
|
+
"List tasks from the user's task manager, filtered by status, project, or due date.",
|
|
994
|
+
props: {
|
|
995
|
+
status: { type: "string", enum: ["pending", "completed", "all"] },
|
|
996
|
+
project: { type: "string" },
|
|
997
|
+
due_before: { type: "string", description: "ISO date" },
|
|
998
|
+
},
|
|
999
|
+
required: [],
|
|
1000
|
+
},
|
|
1001
|
+
{
|
|
1002
|
+
name: "tasks_create",
|
|
1003
|
+
description: "Create a new task in the user's task manager.",
|
|
1004
|
+
props: {
|
|
1005
|
+
title: { type: "string" },
|
|
1006
|
+
description: { type: "string" },
|
|
1007
|
+
due_date: { type: "string" },
|
|
1008
|
+
project: { type: "string" },
|
|
1009
|
+
priority: { type: "string", enum: ["low", "medium", "high", "urgent"] },
|
|
1010
|
+
},
|
|
1011
|
+
required: ["title"],
|
|
1012
|
+
},
|
|
1013
|
+
{
|
|
1014
|
+
name: "browser_navigate",
|
|
1015
|
+
description: "Navigate the browser to a URL and return the page content.",
|
|
1016
|
+
props: {
|
|
1017
|
+
url: { type: "string" },
|
|
1018
|
+
wait_for: { type: "string", description: "CSS selector to wait for" },
|
|
1019
|
+
timeout: { type: "number" },
|
|
1020
|
+
},
|
|
1021
|
+
required: ["url"],
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
name: "browser_screenshot",
|
|
1025
|
+
description:
|
|
1026
|
+
"Take a screenshot of the current browser page or a specific element.",
|
|
1027
|
+
props: {
|
|
1028
|
+
selector: {
|
|
1029
|
+
type: "string",
|
|
1030
|
+
description: "CSS selector to screenshot (default: full page)",
|
|
1031
|
+
},
|
|
1032
|
+
full_page: { type: "boolean" },
|
|
1033
|
+
},
|
|
1034
|
+
required: [],
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
name: "schedule_reminder",
|
|
1038
|
+
description:
|
|
1039
|
+
"Set a reminder for the user at a specific time or relative delay.",
|
|
1040
|
+
props: {
|
|
1041
|
+
message: { type: "string" },
|
|
1042
|
+
at: { type: "string", description: "ISO 8601 datetime" },
|
|
1043
|
+
delay_minutes: { type: "number", description: "Minutes from now" },
|
|
1044
|
+
},
|
|
1045
|
+
required: ["message"],
|
|
1046
|
+
},
|
|
1047
|
+
{
|
|
1048
|
+
name: "messaging_send",
|
|
1049
|
+
description: "Send an iMessage or SMS to a contact.",
|
|
1050
|
+
props: {
|
|
1051
|
+
to: { type: "string", description: "Phone number or contact name" },
|
|
1052
|
+
message: { type: "string" },
|
|
1053
|
+
service: { type: "string", enum: ["imessage", "sms"] },
|
|
1054
|
+
},
|
|
1055
|
+
required: ["to", "message"],
|
|
1056
|
+
},
|
|
1057
|
+
];
|
|
1058
|
+
|
|
1059
|
+
for (const st of skillTools) {
|
|
1060
|
+
tools.push({
|
|
1061
|
+
name: st.name,
|
|
1062
|
+
description: st.description,
|
|
1063
|
+
input_schema: {
|
|
1064
|
+
type: "object",
|
|
1065
|
+
properties: st.props,
|
|
1066
|
+
required: st.required,
|
|
1067
|
+
},
|
|
1068
|
+
});
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
return tools;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Generate additional bundled skill tools to scale up to production counts.
|
|
1076
|
+
* Production sessions have ~135 bundled skill tools across 20+ categories.
|
|
1077
|
+
*/
|
|
1078
|
+
function generateBundledSkillTools(
|
|
1079
|
+
count: number,
|
|
1080
|
+
): Array<{ name: string; description: string; input_schema: object }> {
|
|
1081
|
+
const categories = [
|
|
1082
|
+
"gmail",
|
|
1083
|
+
"calendar",
|
|
1084
|
+
"slack",
|
|
1085
|
+
"contacts",
|
|
1086
|
+
"tasks",
|
|
1087
|
+
"browser",
|
|
1088
|
+
"schedule",
|
|
1089
|
+
"messaging",
|
|
1090
|
+
"sequences",
|
|
1091
|
+
"playbooks",
|
|
1092
|
+
"notes",
|
|
1093
|
+
"music",
|
|
1094
|
+
"photos",
|
|
1095
|
+
"maps",
|
|
1096
|
+
"weather",
|
|
1097
|
+
"reminders",
|
|
1098
|
+
"shortcuts",
|
|
1099
|
+
"finder",
|
|
1100
|
+
"system",
|
|
1101
|
+
"notifications",
|
|
1102
|
+
];
|
|
1103
|
+
const actions = [
|
|
1104
|
+
"list",
|
|
1105
|
+
"search",
|
|
1106
|
+
"create",
|
|
1107
|
+
"update",
|
|
1108
|
+
"delete",
|
|
1109
|
+
"get",
|
|
1110
|
+
"send",
|
|
1111
|
+
"archive",
|
|
1112
|
+
"export",
|
|
1113
|
+
"import",
|
|
1114
|
+
"sync",
|
|
1115
|
+
"share",
|
|
1116
|
+
];
|
|
1117
|
+
|
|
1118
|
+
const tools: Array<{
|
|
1119
|
+
name: string;
|
|
1120
|
+
description: string;
|
|
1121
|
+
input_schema: object;
|
|
1122
|
+
}> = [];
|
|
1123
|
+
|
|
1124
|
+
for (let i = 0; i < count; i++) {
|
|
1125
|
+
const cat = categories[i % categories.length];
|
|
1126
|
+
const action = actions[i % actions.length];
|
|
1127
|
+
const name = `${cat}_${action}_${Math.floor(i / categories.length)}`;
|
|
1128
|
+
|
|
1129
|
+
// Generate realistic parameter schemas of varying complexity
|
|
1130
|
+
const paramCount = 3 + (i % 5); // 3-7 parameters
|
|
1131
|
+
const properties: Record<string, object> = {};
|
|
1132
|
+
const required: string[] = [];
|
|
1133
|
+
|
|
1134
|
+
for (let p = 0; p < paramCount; p++) {
|
|
1135
|
+
const paramNames = [
|
|
1136
|
+
"query",
|
|
1137
|
+
"id",
|
|
1138
|
+
"filter",
|
|
1139
|
+
"limit",
|
|
1140
|
+
"offset",
|
|
1141
|
+
"sort_by",
|
|
1142
|
+
"sort_order",
|
|
1143
|
+
"include_archived",
|
|
1144
|
+
"format",
|
|
1145
|
+
"output_path",
|
|
1146
|
+
"account",
|
|
1147
|
+
"workspace",
|
|
1148
|
+
"project",
|
|
1149
|
+
"label",
|
|
1150
|
+
"priority",
|
|
1151
|
+
"assignee",
|
|
1152
|
+
"due_date",
|
|
1153
|
+
"description",
|
|
1154
|
+
"title",
|
|
1155
|
+
"content",
|
|
1156
|
+
];
|
|
1157
|
+
const pName = paramNames[p % paramNames.length];
|
|
1158
|
+
properties[pName] = {
|
|
1159
|
+
type: p % 3 === 0 ? "string" : p % 3 === 1 ? "number" : "boolean",
|
|
1160
|
+
description:
|
|
1161
|
+
`The ${pName} parameter for ${cat} ${action} operation. ` +
|
|
1162
|
+
`Used to ${action} ${cat} items matching the specified criteria.`,
|
|
1163
|
+
};
|
|
1164
|
+
if (p < 2) required.push(pName); // First 2 params are required
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
tools.push({
|
|
1168
|
+
name,
|
|
1169
|
+
description:
|
|
1170
|
+
`${action.charAt(0).toUpperCase() + action.slice(1)} ${cat} items. ` +
|
|
1171
|
+
`Supports filtering by multiple criteria including date range, status, ` +
|
|
1172
|
+
`and custom labels. Returns paginated results with metadata.`,
|
|
1173
|
+
input_schema: {
|
|
1174
|
+
type: "object",
|
|
1175
|
+
properties,
|
|
1176
|
+
required,
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
return tools;
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
/** Build a multi-turn conversation with tool use */
|
|
1185
|
+
function makeConversationMessages(): Message[] {
|
|
1186
|
+
const messages: Message[] = [];
|
|
1187
|
+
|
|
1188
|
+
// Turn 1: User sends initial request (with runtime injection)
|
|
1189
|
+
messages.push(makeRuntimeInjectedMessage());
|
|
1190
|
+
|
|
1191
|
+
// Turn 2: Assistant responds with a tool call
|
|
1192
|
+
messages.push({
|
|
1193
|
+
role: "assistant",
|
|
1194
|
+
content: [
|
|
1195
|
+
{
|
|
1196
|
+
type: "text",
|
|
1197
|
+
text: "I'll help you add pagination to the data table. Let me first read the current component to understand its structure.",
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
type: "tool_use",
|
|
1201
|
+
id: "tu_01",
|
|
1202
|
+
name: "file_read",
|
|
1203
|
+
input: { path: "src/components/DataTable.tsx" },
|
|
1204
|
+
},
|
|
1205
|
+
],
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
// Turn 3: Tool result with realistic file content
|
|
1209
|
+
const fileContent = Array.from(
|
|
1210
|
+
{ length: 80 },
|
|
1211
|
+
(_, i) =>
|
|
1212
|
+
` // Line ${i + 1}: ${
|
|
1213
|
+
i < 10
|
|
1214
|
+
? "import statements and type definitions"
|
|
1215
|
+
: i < 30
|
|
1216
|
+
? "interface and props definitions with generics"
|
|
1217
|
+
: i < 60
|
|
1218
|
+
? "component implementation with hooks and handlers"
|
|
1219
|
+
: "render JSX with table rows and cells"
|
|
1220
|
+
}`,
|
|
1221
|
+
).join("\n");
|
|
1222
|
+
|
|
1223
|
+
messages.push({
|
|
1224
|
+
role: "user",
|
|
1225
|
+
content: [
|
|
1226
|
+
{
|
|
1227
|
+
type: "tool_result",
|
|
1228
|
+
tool_use_id: "tu_01",
|
|
1229
|
+
content: fileContent,
|
|
1230
|
+
},
|
|
1231
|
+
],
|
|
1232
|
+
});
|
|
1233
|
+
|
|
1234
|
+
// Turn 4: Assistant reads another file
|
|
1235
|
+
messages.push({
|
|
1236
|
+
role: "assistant",
|
|
1237
|
+
content: [
|
|
1238
|
+
{
|
|
1239
|
+
type: "text",
|
|
1240
|
+
text: "Now let me check the existing pagination utilities.",
|
|
1241
|
+
},
|
|
1242
|
+
{
|
|
1243
|
+
type: "tool_use",
|
|
1244
|
+
id: "tu_02",
|
|
1245
|
+
name: "bash",
|
|
1246
|
+
input: {
|
|
1247
|
+
command: "find src -name '*pagina*' -o -name '*Pagina*'",
|
|
1248
|
+
reason: "Looking for existing pagination utilities",
|
|
1249
|
+
},
|
|
1250
|
+
},
|
|
1251
|
+
],
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
// Turn 5: Tool result
|
|
1255
|
+
messages.push({
|
|
1256
|
+
role: "user",
|
|
1257
|
+
content: [
|
|
1258
|
+
{
|
|
1259
|
+
type: "tool_result",
|
|
1260
|
+
tool_use_id: "tu_02",
|
|
1261
|
+
content:
|
|
1262
|
+
"src/hooks/usePagination.ts\nsrc/components/Pagination.tsx\nsrc/utils/pagination.ts",
|
|
1263
|
+
},
|
|
1264
|
+
],
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
return messages;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// ---------------------------------------------------------------------------
|
|
1271
|
+
// Anthropic countTokens helper
|
|
1272
|
+
// ---------------------------------------------------------------------------
|
|
1273
|
+
|
|
1274
|
+
interface CountTokensResult {
|
|
1275
|
+
input_tokens: number;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
async function countTokensViaApi(
|
|
1279
|
+
systemPrompt: string,
|
|
1280
|
+
messages: Message[],
|
|
1281
|
+
tools?: Array<{ name: string; description: string; input_schema: object }>,
|
|
1282
|
+
): Promise<CountTokensResult> {
|
|
1283
|
+
// Use the SDK directly
|
|
1284
|
+
const Anthropic = (await import("@anthropic-ai/sdk")).default;
|
|
1285
|
+
const client = new Anthropic({ apiKey: API_KEY });
|
|
1286
|
+
|
|
1287
|
+
// Convert our Message type to Anthropic's expected format
|
|
1288
|
+
const anthropicMessages = messages.map((m) => ({
|
|
1289
|
+
role: m.role as "user" | "assistant",
|
|
1290
|
+
content: m.content.map((block) => {
|
|
1291
|
+
switch (block.type) {
|
|
1292
|
+
case "text":
|
|
1293
|
+
return { type: "text" as const, text: block.text };
|
|
1294
|
+
case "tool_use":
|
|
1295
|
+
return {
|
|
1296
|
+
type: "tool_use" as const,
|
|
1297
|
+
id: block.id,
|
|
1298
|
+
name: block.name,
|
|
1299
|
+
input: block.input,
|
|
1300
|
+
};
|
|
1301
|
+
case "tool_result":
|
|
1302
|
+
return {
|
|
1303
|
+
type: "tool_result" as const,
|
|
1304
|
+
tool_use_id: block.tool_use_id,
|
|
1305
|
+
content: block.content,
|
|
1306
|
+
};
|
|
1307
|
+
default:
|
|
1308
|
+
return { type: "text" as const, text: String(block) };
|
|
1309
|
+
}
|
|
1310
|
+
}),
|
|
1311
|
+
}));
|
|
1312
|
+
|
|
1313
|
+
const params: Record<string, unknown> = {
|
|
1314
|
+
model: MODEL,
|
|
1315
|
+
messages: anthropicMessages,
|
|
1316
|
+
system: systemPrompt,
|
|
1317
|
+
};
|
|
1318
|
+
|
|
1319
|
+
if (tools && tools.length > 0) {
|
|
1320
|
+
params.tools = tools.map((t) => ({
|
|
1321
|
+
name: t.name,
|
|
1322
|
+
description: t.description,
|
|
1323
|
+
input_schema: t.input_schema,
|
|
1324
|
+
}));
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
const result = await client.messages.countTokens(
|
|
1328
|
+
params as unknown as Parameters<typeof client.messages.countTokens>[0],
|
|
1329
|
+
);
|
|
1330
|
+
return { input_tokens: result.input_tokens };
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// ---------------------------------------------------------------------------
|
|
1334
|
+
// Tests
|
|
1335
|
+
// ---------------------------------------------------------------------------
|
|
1336
|
+
|
|
1337
|
+
describeWithApi("Token estimator accuracy (requires ANTHROPIC_API_KEY)", () => {
|
|
1338
|
+
test("estimation gap: messages + system prompt (no tools)", async () => {
|
|
1339
|
+
const systemPrompt = makeSystemPrompt();
|
|
1340
|
+
const messages = makeConversationMessages();
|
|
1341
|
+
|
|
1342
|
+
const estimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1343
|
+
providerName: "anthropic",
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
const actual = await countTokensViaApi(systemPrompt, messages);
|
|
1347
|
+
|
|
1348
|
+
const ratio = actual.input_tokens / estimated;
|
|
1349
|
+
|
|
1350
|
+
console.log("=== No tools ===");
|
|
1351
|
+
console.log(` Estimated: ${estimated.toLocaleString()} tokens`);
|
|
1352
|
+
console.log(
|
|
1353
|
+
` Actual: ${actual.input_tokens.toLocaleString()} tokens`,
|
|
1354
|
+
);
|
|
1355
|
+
console.log(` Ratio: ${ratio.toFixed(2)}x`);
|
|
1356
|
+
|
|
1357
|
+
// Even without tools, we expect some gap because structured content
|
|
1358
|
+
// (HTML, JSON) tokenizes at ~2-3 chars/token vs our assumed 4
|
|
1359
|
+
expect(ratio).toBeGreaterThan(0.5); // Sanity: we're not wildly over-estimating
|
|
1360
|
+
expect(ratio).toBeLessThan(3.0); // Without tools, gap should be moderate
|
|
1361
|
+
});
|
|
1362
|
+
|
|
1363
|
+
test("estimation gap with tools: old vs new estimator", async () => {
|
|
1364
|
+
const systemPrompt = makeSystemPrompt();
|
|
1365
|
+
const messages = makeConversationMessages();
|
|
1366
|
+
const tools = makeToolDefinitions() as ToolDefinition[];
|
|
1367
|
+
|
|
1368
|
+
const toolTokenBudget = estimateToolsTokens(tools);
|
|
1369
|
+
|
|
1370
|
+
// Old estimator: completely ignores tools
|
|
1371
|
+
const oldEstimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1372
|
+
providerName: "anthropic",
|
|
1373
|
+
});
|
|
1374
|
+
|
|
1375
|
+
// New estimator: includes tool token budget
|
|
1376
|
+
const newEstimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1377
|
+
providerName: "anthropic",
|
|
1378
|
+
toolTokenBudget,
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
// Anthropic's countTokens includes tool definitions
|
|
1382
|
+
const actual = await countTokensViaApi(systemPrompt, messages, tools);
|
|
1383
|
+
|
|
1384
|
+
const oldRatio = actual.input_tokens / oldEstimated;
|
|
1385
|
+
const newRatio = actual.input_tokens / newEstimated;
|
|
1386
|
+
|
|
1387
|
+
console.log("=== With tools (old vs new estimator) ===");
|
|
1388
|
+
console.log(` Tools: ${tools.length}`);
|
|
1389
|
+
console.log(` Tool budget: ${toolTokenBudget.toLocaleString()} tokens`);
|
|
1390
|
+
console.log(
|
|
1391
|
+
` Old estimated: ${oldEstimated.toLocaleString()} tokens (ratio ${oldRatio.toFixed(2)}x)`,
|
|
1392
|
+
);
|
|
1393
|
+
console.log(
|
|
1394
|
+
` New estimated: ${newEstimated.toLocaleString()} tokens (ratio ${newRatio.toFixed(2)}x)`,
|
|
1395
|
+
);
|
|
1396
|
+
console.log(
|
|
1397
|
+
` Actual: ${actual.input_tokens.toLocaleString()} tokens`,
|
|
1398
|
+
);
|
|
1399
|
+
|
|
1400
|
+
// New estimator should be closer to actual
|
|
1401
|
+
expect(newEstimated).toBeGreaterThan(oldEstimated);
|
|
1402
|
+
expect(newRatio).toBeLessThan(oldRatio);
|
|
1403
|
+
// New ratio should be within 30% of actual
|
|
1404
|
+
expect(newRatio).toBeLessThan(1.3);
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
test("tool definitions contribute significant tokens", async () => {
|
|
1408
|
+
const systemPrompt = makeSystemPrompt();
|
|
1409
|
+
const messages = makeConversationMessages();
|
|
1410
|
+
const tools = makeToolDefinitions();
|
|
1411
|
+
|
|
1412
|
+
const withoutTools = await countTokensViaApi(systemPrompt, messages);
|
|
1413
|
+
const withTools = await countTokensViaApi(systemPrompt, messages, tools);
|
|
1414
|
+
|
|
1415
|
+
const toolTokens = withTools.input_tokens - withoutTools.input_tokens;
|
|
1416
|
+
|
|
1417
|
+
console.log("=== Tool token contribution ===");
|
|
1418
|
+
console.log(
|
|
1419
|
+
` Without tools: ${withoutTools.input_tokens.toLocaleString()} tokens`,
|
|
1420
|
+
);
|
|
1421
|
+
console.log(
|
|
1422
|
+
` With tools: ${withTools.input_tokens.toLocaleString()} tokens`,
|
|
1423
|
+
);
|
|
1424
|
+
console.log(` Tool overhead: ${toolTokens.toLocaleString()} tokens`);
|
|
1425
|
+
console.log(
|
|
1426
|
+
` Per tool avg: ${Math.round(toolTokens / tools.length)} tokens`,
|
|
1427
|
+
);
|
|
1428
|
+
|
|
1429
|
+
// Tools should contribute a meaningful number of tokens
|
|
1430
|
+
expect(toolTokens).toBeGreaterThan(1000);
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
test("structured content (HTML/XML) tokenizes more densely than 4 chars/token", async () => {
|
|
1434
|
+
// Test with HTML-heavy content to measure actual chars/token ratio
|
|
1435
|
+
const htmlContent = Array.from(
|
|
1436
|
+
{ length: 100 },
|
|
1437
|
+
(_, i) =>
|
|
1438
|
+
`<div class="item-${i}" data-testid="row-${i}"><span class="name">${"Content ".repeat(5)}</span></div>`,
|
|
1439
|
+
).join("\n");
|
|
1440
|
+
|
|
1441
|
+
const messages: Message[] = [
|
|
1442
|
+
{ role: "user", content: [{ type: "text", text: htmlContent }] },
|
|
1443
|
+
];
|
|
1444
|
+
const systemPrompt = "You are a helpful assistant.";
|
|
1445
|
+
|
|
1446
|
+
const estimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1447
|
+
providerName: "anthropic",
|
|
1448
|
+
});
|
|
1449
|
+
const actual = await countTokensViaApi(systemPrompt, messages);
|
|
1450
|
+
|
|
1451
|
+
const actualCharsPerToken = htmlContent.length / actual.input_tokens;
|
|
1452
|
+
const ratio = actual.input_tokens / estimated;
|
|
1453
|
+
|
|
1454
|
+
console.log("=== HTML content tokenization ===");
|
|
1455
|
+
console.log(
|
|
1456
|
+
` Content length: ${htmlContent.length.toLocaleString()} chars`,
|
|
1457
|
+
);
|
|
1458
|
+
console.log(` Estimated: ${estimated.toLocaleString()} tokens`);
|
|
1459
|
+
console.log(
|
|
1460
|
+
` Actual: ${actual.input_tokens.toLocaleString()} tokens`,
|
|
1461
|
+
);
|
|
1462
|
+
console.log(` Assumed chars/token: 4`);
|
|
1463
|
+
console.log(` Actual chars/token: ${actualCharsPerToken.toFixed(2)}`);
|
|
1464
|
+
console.log(` Ratio: ${ratio.toFixed(2)}x`);
|
|
1465
|
+
|
|
1466
|
+
// HTML/XML typically tokenizes at 2-3 chars per token, not 4
|
|
1467
|
+
// This means our estimate underestimates HTML-heavy content
|
|
1468
|
+
expect(actualCharsPerToken).toBeLessThan(4);
|
|
1469
|
+
});
|
|
1470
|
+
|
|
1471
|
+
test("production-scale scenario: old vs new estimator with 160 tools", async () => {
|
|
1472
|
+
const systemPrompt = makeSystemPrompt("production");
|
|
1473
|
+
const messages = makeConversationMessages();
|
|
1474
|
+
const baseTools = makeToolDefinitions() as ToolDefinition[];
|
|
1475
|
+
const extraTools = generateBundledSkillTools(123) as ToolDefinition[];
|
|
1476
|
+
const tools = [...baseTools, ...extraTools];
|
|
1477
|
+
|
|
1478
|
+
const toolTokenBudget = estimateToolsTokens(tools);
|
|
1479
|
+
|
|
1480
|
+
// Old estimator: no tool awareness
|
|
1481
|
+
const oldEstimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1482
|
+
providerName: "anthropic",
|
|
1483
|
+
});
|
|
1484
|
+
|
|
1485
|
+
// New estimator: includes tool token budget
|
|
1486
|
+
const newEstimated = estimatePromptTokens(messages, systemPrompt, {
|
|
1487
|
+
providerName: "anthropic",
|
|
1488
|
+
toolTokenBudget,
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
const actual = await countTokensViaApi(systemPrompt, messages, tools);
|
|
1492
|
+
|
|
1493
|
+
const oldRatio = actual.input_tokens / oldEstimated;
|
|
1494
|
+
const newRatio = actual.input_tokens / newEstimated;
|
|
1495
|
+
|
|
1496
|
+
console.log("=== Production-scale scenario (old vs new) ===");
|
|
1497
|
+
console.log(` Tools: ${tools.length}`);
|
|
1498
|
+
console.log(
|
|
1499
|
+
` System: ${systemPrompt.length.toLocaleString()} chars`,
|
|
1500
|
+
);
|
|
1501
|
+
console.log(` Tool budget: ${toolTokenBudget.toLocaleString()} tokens`);
|
|
1502
|
+
console.log(
|
|
1503
|
+
` Old estimated: ${oldEstimated.toLocaleString()} tokens (ratio ${oldRatio.toFixed(2)}x)`,
|
|
1504
|
+
);
|
|
1505
|
+
console.log(
|
|
1506
|
+
` New estimated: ${newEstimated.toLocaleString()} tokens (ratio ${newRatio.toFixed(2)}x)`,
|
|
1507
|
+
);
|
|
1508
|
+
console.log(
|
|
1509
|
+
` Actual: ${actual.input_tokens.toLocaleString()} tokens`,
|
|
1510
|
+
);
|
|
1511
|
+
console.log(` Production observed: 3.01x (73,416 est vs 220,964 actual)`);
|
|
1512
|
+
|
|
1513
|
+
// Old estimator should have a large gap
|
|
1514
|
+
expect(oldRatio).toBeGreaterThan(1.5);
|
|
1515
|
+
// New estimator should be significantly better
|
|
1516
|
+
expect(newRatio).toBeLessThan(oldRatio);
|
|
1517
|
+
// New ratio should be within 50% of actual (allowing for remaining
|
|
1518
|
+
// tokenization density gap on structured content)
|
|
1519
|
+
expect(newRatio).toBeLessThan(1.5);
|
|
1520
|
+
});
|
|
1521
|
+
});
|