@vellumai/assistant 0.8.7 → 0.8.8
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/Dockerfile +20 -4
- package/docker-entrypoint.sh +4 -2
- package/docker-init-apt-root.sh +3 -1
- package/docker-kata-apt-env.sh +3 -1
- package/docker-kata-runtime-family.sh +12 -0
- package/docs/architecture/memory.md +1 -1
- package/docs/plugins.md +75 -79
- package/examples/plugins/echo/README.md +6 -12
- package/examples/plugins/echo/register.ts +0 -41
- package/node_modules/@vellumai/skill-host-contracts/src/server-message.ts +3 -3
- package/openapi.yaml +3381 -348
- package/package.json +1 -1
- package/scripts/generate-openapi.ts +68 -41
- package/src/__tests__/agent-loop-exit-reason.test.ts +34 -39
- package/src/__tests__/agent-loop-provider-error-recording.test.ts +1 -1
- package/src/__tests__/agent-loop.test.ts +37 -87
- package/src/__tests__/agent-wake-disk-pressure-callsite.test.ts +2 -0
- package/src/__tests__/annotate-activity-metadata.test.ts +262 -0
- package/src/__tests__/annotate-risk-options.test.ts +2 -3
- package/src/__tests__/anthropic-provider.test.ts +95 -2
- package/src/__tests__/assistant-event-hub.test.ts +25 -0
- package/src/__tests__/assistant-events-sse-shed.test.ts +8 -0
- package/src/__tests__/{conversation-stream-state.test.ts → assistant-stream-state.test.ts} +252 -91
- package/src/__tests__/auth-fallback-events-store.test.ts +116 -0
- package/src/__tests__/background-workers-disk-pressure.test.ts +6 -0
- package/src/__tests__/btw-routes.test.ts +62 -3
- package/src/__tests__/build-persisted-content.test.ts +184 -0
- package/src/__tests__/catalog-files.test.ts +1 -1
- package/src/__tests__/clawhub-files.test.ts +1 -1
- package/src/__tests__/compaction-pipeline.test.ts +1 -1
- package/src/__tests__/compaction.benchmark.test.ts +0 -30
- package/src/__tests__/config-watcher.test.ts +1 -1
- package/src/__tests__/conversation-abort-tool-results.test.ts +57 -19
- package/src/__tests__/conversation-agent-loop-disk-pressure.test.ts +6 -2
- package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +10 -4
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +313 -1136
- package/src/__tests__/conversation-agent-loop.test.ts +596 -1616
- package/src/__tests__/conversation-analysis-routes.test.ts +6 -0
- package/src/__tests__/conversation-history-web-search.test.ts +11 -1
- package/src/__tests__/conversation-pairing.test.ts +4 -31
- package/src/__tests__/conversation-process-app-control-preactivation.test.ts +6 -0
- package/src/__tests__/conversation-provider-retry-repair.test.ts +26 -5
- package/src/__tests__/conversation-queue.test.ts +2 -0
- package/src/__tests__/conversation-routes-disk-view.test.ts +3 -0
- package/src/__tests__/conversation-routes-slash-commands.test.ts +6 -5
- package/src/__tests__/conversation-runtime-assembly.test.ts +170 -229
- package/src/__tests__/conversation-runtime-workspace.test.ts +3 -24
- package/src/__tests__/conversation-slash-commands.test.ts +8 -42
- package/src/__tests__/conversation-slash-queue.test.ts +6 -1
- package/src/__tests__/conversation-surfaces-action-delivery.test.ts +84 -0
- package/src/__tests__/conversation-sync-tags.test.ts +27 -15
- package/src/__tests__/conversation-title-service.test.ts +135 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +6 -1
- package/src/__tests__/cross-provider-web-search.test.ts +214 -1
- package/src/__tests__/db-schedule-syntax-migration.test.ts +5 -0
- package/src/__tests__/dm-persistence.test.ts +5 -1
- package/src/__tests__/empty-response-hook.test.ts +304 -0
- package/src/__tests__/feature-flag-test-helpers.ts +2 -2
- package/src/__tests__/gemini-image-service.test.ts +13 -0
- package/src/__tests__/helpers/mock-provider.ts +110 -0
- package/src/__tests__/helpers/native-web-search-harness.ts +129 -0
- package/src/__tests__/history-repair-hook.test.ts +1 -0
- package/src/__tests__/identity-intro-cache.test.ts +12 -100
- package/src/__tests__/identity-routes.test.ts +248 -7
- package/src/__tests__/inbound-slack-persistence.test.ts +5 -1
- package/src/__tests__/injector-background-turn.test.ts +2 -8
- package/src/__tests__/injector-chain.test.ts +106 -270
- package/src/__tests__/injector-disk-pressure.test.ts +3 -12
- package/src/__tests__/injector-document-comments.test.ts +2 -2
- package/src/__tests__/injector-pkb-v2-silenced.test.ts +30 -22
- package/src/__tests__/injector-v3-suppression.test.ts +31 -37
- package/src/__tests__/internal-telemetry-routes.test.ts +109 -0
- package/src/__tests__/list-messages-page-latest.test.ts +60 -0
- package/src/__tests__/list-messages-tool-merge.test.ts +20 -0
- package/src/__tests__/llm-usage-store.test.ts +223 -1
- package/src/__tests__/memory-retrieval-hook.test.ts +297 -0
- package/src/__tests__/memory-v2-static-injector.test.ts +103 -35
- package/src/__tests__/native-web-search.test.ts +191 -0
- package/src/__tests__/onboarding-template-contract.test.ts +2 -0
- package/src/__tests__/openai-image-service.test.ts +17 -0
- package/src/__tests__/openai-provider.test.ts +31 -1
- package/src/__tests__/persist-unsendable-image.test.ts +215 -0
- package/src/__tests__/persistence-secret-redaction.test.ts +1 -0
- package/src/__tests__/pipeline-runner.test.ts +29 -39
- package/src/__tests__/pkb-autoinject.test.ts +2 -5
- package/src/__tests__/plugin-bootstrap.test.ts +13 -28
- package/src/__tests__/plugin-registry.test.ts +0 -27
- package/src/__tests__/plugin-types.test.ts +2 -125
- package/src/__tests__/process-message-display-content.test.ts +6 -2
- package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +5 -1
- package/src/__tests__/resolve-trust-class.test.ts +4 -4
- package/src/__tests__/runtime-events-sse-reconnect.test.ts +60 -23
- package/src/__tests__/schedule-routes.test.ts +603 -2
- package/src/__tests__/schedule-store.test.ts +41 -0
- package/src/__tests__/schedule-tools.test.ts +35 -0
- package/src/__tests__/server-history-render.test.ts +314 -1
- package/src/__tests__/skillssh-files.test.ts +1 -1
- package/src/__tests__/system-prompt.test.ts +20 -0
- package/src/__tests__/task-scheduler.test.ts +162 -1
- package/src/__tests__/terminal-tools.test.ts +6 -1
- package/src/__tests__/title-generate-hook.test.ts +319 -0
- package/src/__tests__/tool-error-hook.test.ts +278 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +468 -5
- package/src/__tests__/tool-result-metadata-plumbing.test.ts +1 -0
- package/src/__tests__/tool-result-truncate-hook.test.ts +127 -0
- package/src/__tests__/tool-result-truncation.test.ts +0 -2
- package/src/__tests__/ui-choice-copy-surfaces.test.ts +254 -0
- package/src/__tests__/ui-work-result-surface.test.ts +159 -0
- package/src/__tests__/usage-routes.test.ts +285 -1
- package/src/__tests__/user-plugin-loader.test.ts +2 -2
- package/src/__tests__/voice-session-bridge.test.ts +6 -3
- package/src/__tests__/web-search-backend-failure.test.ts +166 -0
- package/src/agent/loop.ts +346 -442
- package/src/api/events/assistant-thinking-delta.ts +33 -0
- package/src/api/events/tool-output-chunk.ts +45 -0
- package/src/api/events/tool-use-preview-start.ts +32 -0
- package/src/api/events/trace-event.ts +69 -0
- package/src/api/index.ts +48 -13
- package/src/api/responses/conversation-message.ts +368 -0
- package/src/avatar/__tests__/avatar-store.test.ts +34 -29
- package/src/cli/commands/__tests__/notifications.test.ts +58 -14
- package/src/cli/commands/notifications.ts +112 -60
- package/src/config/assistant-feature-flags.ts +22 -11
- package/src/config/bundled-skills/app-builder/SKILL.md +3 -20
- package/src/config/bundled-skills/app-builder/references/examples/README.md +17 -0
- package/src/config/bundled-skills/app-builder/references/examples/expense-tracker.md +515 -0
- package/src/config/bundled-skills/app-builder/references/examples/focus-timer.md +342 -0
- package/src/config/bundled-skills/app-builder/references/examples/habit-tracker.md +490 -0
- package/src/config/bundled-skills/document-editor/SKILL.md +1 -1
- package/src/config/bundled-skills/messaging/SKILL.md +0 -7
- package/src/config/feature-flag-cache.ts +3 -3
- package/src/config/feature-flag-registry.json +35 -3
- package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
- package/src/config/schemas/__tests__/memory-v3.test.ts +25 -0
- package/src/config/schemas/llm.ts +1 -0
- package/src/config/schemas/memory-v2.ts +8 -0
- package/src/config/schemas/memory-v3.ts +8 -0
- package/src/config/schemas/platform.ts +8 -0
- package/src/config/seed-inference-profiles.ts +2 -2
- package/src/config/skills.ts +13 -0
- package/src/context/compactor.ts +1 -1
- package/src/context/strip-injections.ts +122 -0
- package/src/context/token-estimator.ts +23 -0
- package/src/context/tool-result-truncation.ts +0 -23
- package/src/context/window-manager.ts +3 -6
- package/src/credential-execution/executable-discovery.ts +16 -0
- package/src/daemon/__tests__/conversation-lifecycle-auto-analyze.test.ts +6 -0
- package/src/daemon/__tests__/inference-profile-notification.test.ts +153 -0
- package/src/daemon/__tests__/native-web-search-metadata.test.ts +10 -8
- package/src/daemon/assistant-attachments.ts +1 -1
- package/src/daemon/config-watcher.ts +2 -2
- package/src/daemon/context-overflow-reducer.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +605 -153
- package/src/daemon/conversation-agent-loop.ts +281 -760
- package/src/daemon/conversation-history.ts +5 -4
- package/src/daemon/conversation-lifecycle.ts +3 -4
- package/src/daemon/conversation-messaging.ts +7 -6
- package/src/daemon/conversation-process.ts +11 -16
- package/src/daemon/conversation-runtime-assembly.ts +130 -347
- package/src/daemon/conversation-slash.ts +6 -25
- package/src/daemon/conversation-surfaces.ts +222 -4
- package/src/daemon/conversation-tool-setup.ts +2 -29
- package/src/daemon/conversation.ts +32 -14
- package/src/daemon/external-plugins-bootstrap.ts +9 -10
- package/src/daemon/handlers/config-a2a.ts +51 -36
- package/src/daemon/handlers/config-slack-channel.ts +20 -14
- package/src/daemon/handlers/config-telegram.ts +16 -2
- package/src/daemon/handlers/shared.ts +156 -84
- package/src/daemon/handlers/skills.ts +39 -10
- package/src/daemon/lifecycle.ts +4 -0
- package/src/daemon/message-types/apps.ts +1 -29
- package/src/daemon/message-types/messages.ts +9 -57
- package/src/daemon/message-types/skills.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +136 -3
- package/src/daemon/now-scratchpad.ts +21 -0
- package/src/daemon/orphan-reaper.test.ts +210 -0
- package/src/daemon/orphan-reaper.ts +240 -0
- package/src/daemon/persist-unsendable-image.ts +117 -0
- package/src/daemon/process-message.ts +1 -3
- package/src/daemon/trace-emitter.ts +6 -4
- package/src/daemon/trust-context.ts +19 -0
- package/src/daemon/wake-target-adapter.ts +3 -1
- package/src/home/home-greeting-cache.ts +24 -1
- package/src/ipc/gateway-client.test.ts +2 -2
- package/src/ipc/gateway-client.ts +3 -3
- package/src/media/gemini-image-service.ts +15 -0
- package/src/media/openai-image-service.ts +14 -0
- package/src/media/types.ts +34 -0
- package/src/memory/__tests__/jobs-worker-v2-schedule.test.ts +56 -0
- package/src/memory/auth-fallback-events-store.ts +94 -0
- package/src/memory/conversation-title-service.ts +65 -41
- package/src/memory/db-init.ts +4 -0
- package/src/memory/graph/__tests__/conversation-graph-memory-registry.test.ts +119 -0
- package/src/memory/graph/conversation-graph-memory.ts +65 -0
- package/src/memory/jobs-store.ts +33 -0
- package/src/memory/jobs-worker.ts +31 -4
- package/src/memory/llm-usage-store.ts +224 -50
- package/src/memory/migrations/222-strip-placeholder-sentinels-from-messages.ts +6 -5
- package/src/memory/migrations/270-schedule-source-conversation.ts +13 -0
- package/src/memory/migrations/271-create-auth-fallback-events.ts +21 -0
- package/src/memory/migrations/index.ts +2 -0
- package/src/memory/pkb/autoinject.ts +61 -0
- package/src/memory/pkb/context.ts +50 -0
- package/src/memory/pkb/types.ts +14 -0
- package/src/memory/schedule-attribution-sql.ts +104 -0
- package/src/memory/schema/infrastructure.ts +16 -0
- package/src/memory/usage-grouped-buckets.ts +6 -1
- package/src/memory/v2/__tests__/consolidation-job.test.ts +1 -1
- package/src/memory/v2/consolidation-job.ts +1 -1
- package/src/memory/v3/__tests__/health.test.ts +16 -0
- package/src/memory/v3/__tests__/orchestrate.test.ts +45 -9
- package/src/memory/v3/__tests__/provider-blocks.test.ts +13 -0
- package/src/memory/v3/__tests__/router.test.ts +101 -29
- package/src/memory/v3/__tests__/selector.test.ts +93 -27
- package/src/memory/v3/__tests__/shadow-plugin.test.ts +23 -5
- package/src/memory/v3/health.ts +0 -0
- package/src/memory/v3/llm-retry.ts +32 -0
- package/src/memory/v3/orchestrate.ts +26 -14
- package/src/memory/v3/provider-blocks.ts +15 -5
- package/src/memory/v3/router.ts +48 -42
- package/src/memory/v3/selector.ts +57 -42
- package/src/memory/v3/shadow-plugin.ts +47 -15
- package/src/memory/v3/types.ts +8 -0
- package/src/notifications/conversation-pairing.ts +8 -15
- package/src/notifications/decision-engine.ts +6 -3
- package/src/notifications/home-feed-side-effect.ts +12 -1
- package/src/permissions/prompter.ts +4 -0
- package/src/plugin-api/constants.ts +4 -0
- package/src/plugin-api/index.ts +8 -1
- package/src/plugin-api/types.ts +151 -1
- package/src/plugins/defaults/empty-response/hooks/stop.ts +126 -0
- package/src/plugins/defaults/empty-response/register.ts +8 -13
- package/src/plugins/defaults/index.ts +1 -15
- package/src/plugins/defaults/injectors/register.ts +243 -74
- package/src/plugins/defaults/memory-retrieval/hooks/post-compact.ts +91 -0
- package/src/plugins/defaults/memory-retrieval/hooks/user-prompt-submit-temp.ts +216 -0
- package/src/plugins/defaults/memory-retrieval/injector-chain.ts +35 -0
- package/src/plugins/defaults/title-generate/hooks/stop.ts +75 -0
- package/src/plugins/defaults/title-generate/hooks/user-prompt-submit.ts +35 -0
- package/src/plugins/defaults/title-generate/package.json +1 -1
- package/src/plugins/defaults/title-generate/register.ts +18 -18
- package/src/plugins/defaults/tool-error/hooks/post-tool-use.ts +118 -0
- package/src/plugins/defaults/tool-error/package.json +1 -1
- package/src/plugins/defaults/tool-error/register.ts +9 -21
- package/src/plugins/defaults/tool-result-truncate/hooks/post-tool-use.ts +32 -0
- package/src/plugins/defaults/tool-result-truncate/register.ts +10 -21
- package/src/plugins/defaults/tool-result-truncate/terminal.ts +37 -18
- package/src/plugins/pipeline.ts +6 -18
- package/src/plugins/registry.ts +8 -25
- package/src/plugins/types.ts +43 -474
- package/src/proactive-artifact/aux-message-injector.ts +3 -3
- package/src/proactive-artifact/job.test.ts +7 -12
- package/src/prompts/__tests__/system-prompt.test.ts +36 -0
- package/src/prompts/templates/BOOTSTRAP-ACTIVATION-RAIL.md +62 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -2
- package/src/prompts/templates/system-sections.ts +15 -0
- package/src/providers/anthropic/client.ts +37 -29
- package/src/providers/openai/__tests__/chat-completions-provider-reasoning.test.ts +112 -0
- package/src/providers/openai/chat-completions-provider.ts +44 -0
- package/src/providers/openrouter/client.ts +1 -0
- package/src/providers/placeholder-sentinels.ts +35 -0
- package/src/runtime/__tests__/agent-wake.test.ts +5 -1
- package/src/runtime/agent-wake.ts +2 -2
- package/src/runtime/assistant-event-hub.ts +36 -6
- package/src/runtime/{conversation-stream-state.ts → assistant-stream-state.ts} +132 -58
- package/src/runtime/http-router.ts +16 -21
- package/src/runtime/http-types.ts +16 -70
- package/src/runtime/pending-interactions.ts +1 -0
- package/src/runtime/routes/__tests__/consolidation-routes.test.ts +265 -2
- package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +31 -1
- package/src/runtime/routes/__tests__/memory-v2-routes.test.ts +6 -2
- package/src/runtime/routes/__tests__/tts-routes.test.ts +6 -2
- package/src/runtime/routes/app-management-routes.ts +6 -117
- package/src/runtime/routes/app-routes.ts +13 -15
- package/src/runtime/routes/attachment-routes.ts +26 -15
- package/src/runtime/routes/avatar-routes.ts +26 -0
- package/src/runtime/routes/btw-routes.ts +29 -23
- package/src/runtime/routes/consolidation-routes.ts +120 -20
- package/src/runtime/routes/conversation-query-routes.ts +2 -0
- package/src/runtime/routes/conversation-routes.ts +358 -184
- package/src/runtime/routes/documents-routes.ts +4 -0
- package/src/runtime/routes/domain-routes.ts +51 -37
- package/src/runtime/routes/epoch-millis-range.ts +34 -0
- package/src/runtime/routes/events-routes.ts +28 -34
- package/src/runtime/routes/gateway-log-routes.ts +26 -4
- package/src/runtime/routes/heartbeat-routes.ts +32 -12
- package/src/runtime/routes/identity-intro-cache.ts +11 -34
- package/src/runtime/routes/identity-routes.ts +208 -17
- package/src/runtime/routes/image-generation-routes.ts +40 -2
- package/src/runtime/routes/index.ts +2 -0
- package/src/runtime/routes/integrations/a2a.ts +12 -10
- package/src/runtime/routes/integrations/slack/__tests__/channel.test.ts +16 -0
- package/src/runtime/routes/integrations/slack/channel.ts +4 -0
- package/src/runtime/routes/integrations/slack/share.ts +27 -6
- package/src/runtime/routes/integrations/telegram.ts +6 -0
- package/src/runtime/routes/integrations/twilio.ts +42 -0
- package/src/runtime/routes/internal-telemetry-routes.ts +88 -0
- package/src/runtime/routes/log-export-routes.ts +8 -0
- package/src/runtime/routes/memory-v2-routes.ts +15 -8
- package/src/runtime/routes/memory-v3-routes.ts +50 -28
- package/src/runtime/routes/oauth-apps.ts +66 -12
- package/src/runtime/routes/oauth-providers.ts +44 -5
- package/src/runtime/routes/platform-routes.ts +81 -5
- package/src/runtime/routes/playground/__tests__/force-compact.test.ts +6 -4
- package/src/runtime/routes/playground/force-compact.ts +1 -1
- package/src/runtime/routes/rename-conversation-routes.ts +5 -0
- package/src/runtime/routes/schedule-routes.ts +152 -42
- package/src/runtime/routes/secret-routes.ts +14 -2
- package/src/runtime/routes/skills-routes.ts +43 -14
- package/src/runtime/routes/tool-call-confirmation-enrichment.test.ts +161 -0
- package/src/runtime/routes/tool-call-confirmation-enrichment.ts +107 -0
- package/src/runtime/routes/trust-rules-routes.ts +26 -2
- package/src/runtime/routes/tts-routes.ts +35 -0
- package/src/runtime/routes/types.ts +66 -8
- package/src/runtime/routes/usage-routes.ts +47 -39
- package/src/runtime/routes/webhook-routes.ts +41 -2
- package/src/runtime/routes/workspace-routes.ts +4 -0
- package/src/runtime/services/__tests__/analyze-conversation.test.ts +6 -0
- package/src/runtime/services/analyze-conversation.ts +2 -2
- package/src/schedule/schedule-store.ts +20 -1
- package/src/schedule/schedule-usage-store.ts +83 -0
- package/src/schedule/scheduler.ts +12 -5
- package/src/skills/catalog-files.ts +2 -2
- package/src/skills/catalog-install.ts +3 -0
- package/src/skills/categories-cache.ts +118 -0
- package/src/skills/clawhub-files.ts +1 -2
- package/src/skills/skillssh-files.ts +1 -2
- package/src/telemetry/types.ts +29 -1
- package/src/telemetry/usage-telemetry-reporter.test.ts +112 -3
- package/src/telemetry/usage-telemetry-reporter.ts +57 -2
- package/src/tools/executor.ts +1 -53
- package/src/tools/network/__tests__/web-search-metadata.test.ts +7 -1
- package/src/tools/network/__tests__/web-search.test.ts +11 -3
- package/src/tools/network/web-search-error.test.ts +248 -0
- package/src/tools/network/web-search-error.ts +267 -0
- package/src/tools/network/web-search.ts +207 -48
- package/src/tools/schedule/create.ts +2 -0
- package/src/tools/terminal/safe-env.ts +10 -1
- package/src/tools/ui-surface/definitions.ts +9 -1
- package/src/tts/__tests__/provider-catalog-consistency.test.ts +85 -1
- package/src/tts/provider-catalog.ts +76 -1
- package/src/util/mutex.ts +47 -0
- package/src/workspace/git-service.ts +1 -42
- package/src/workspace/migrations/095-bump-heartbeat-interval-30m-to-60m.ts +51 -0
- package/src/workspace/migrations/096-reduce-quality-profile-effort.ts +72 -0
- package/src/workspace/migrations/097-enable-adaptive-thinking-managed-profiles.ts +93 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/__tests__/bootstrap-turn-cleanup.test.ts +0 -44
- package/src/__tests__/empty-response-pipeline.test.ts +0 -423
- package/src/__tests__/llm-call-pipeline.test.ts +0 -287
- package/src/__tests__/memory-retrieval-pipeline.test.ts +0 -418
- package/src/__tests__/persistence-pipeline.test.ts +0 -503
- package/src/__tests__/title-generate-pipeline.test.ts +0 -211
- package/src/__tests__/token-estimate-pipeline.test.ts +0 -479
- package/src/__tests__/tool-error-pipeline.test.ts +0 -241
- package/src/__tests__/tool-execute-pipeline.test.ts +0 -417
- package/src/__tests__/tool-result-truncate-pipeline.test.ts +0 -341
- package/src/daemon/bootstrap-turn-cleanup.ts +0 -45
- package/src/gallery/default-gallery.ts +0 -1359
- package/src/gallery/gallery-manifest.ts +0 -28
- package/src/home/feature-gate.ts +0 -22
- package/src/plugins/defaults/empty-response/middlewares/emptyResponse.ts +0 -22
- package/src/plugins/defaults/empty-response/terminal.ts +0 -106
- package/src/plugins/defaults/injectors/package.json +0 -15
- package/src/plugins/defaults/llm-call/middlewares/llmCall.ts +0 -17
- package/src/plugins/defaults/llm-call/package.json +0 -15
- package/src/plugins/defaults/llm-call/register.ts +0 -45
- package/src/plugins/defaults/memory-retrieval/middlewares/memoryRetrieval.ts +0 -17
- package/src/plugins/defaults/memory-retrieval/package.json +0 -15
- package/src/plugins/defaults/memory-retrieval/register.ts +0 -181
- package/src/plugins/defaults/persistence/middlewares/persistence.ts +0 -19
- package/src/plugins/defaults/persistence/package.json +0 -15
- package/src/plugins/defaults/persistence/register.ts +0 -38
- package/src/plugins/defaults/persistence/terminal.ts +0 -83
- package/src/plugins/defaults/title-generate/terminal.ts +0 -31
- package/src/plugins/defaults/token-estimate/middlewares/tokenEstimate.ts +0 -23
- package/src/plugins/defaults/token-estimate/package.json +0 -15
- package/src/plugins/defaults/token-estimate/register.ts +0 -34
- package/src/plugins/defaults/token-estimate/terminal.ts +0 -40
- package/src/plugins/defaults/tool-error/middlewares/toolError.ts +0 -21
- package/src/plugins/defaults/tool-error/terminal.ts +0 -47
- package/src/plugins/defaults/tool-execute/middlewares/toolExecute.ts +0 -23
- package/src/plugins/defaults/tool-execute/package.json +0 -15
- package/src/plugins/defaults/tool-execute/register.ts +0 -49
- package/src/plugins/defaults/tool-result-truncate/middlewares/toolResultTruncate.ts +0 -23
- package/src/plugins/defaults/tool-result-truncate/types.ts +0 -22
- package/src/skills/category-inference.ts +0 -111
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for render-time enrichment of history tool calls with confirmation
|
|
3
|
+
* context: derived scope ladders for scope-aware tools and outstanding prompts
|
|
4
|
+
* read from the pending-interactions registry.
|
|
5
|
+
*/
|
|
6
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import type { ConversationMessageToolCall } from "../../api/responses/conversation-message.js";
|
|
9
|
+
import { clear, register } from "../pending-interactions.js";
|
|
10
|
+
import {
|
|
11
|
+
collectPendingConfirmations,
|
|
12
|
+
enrichToolCallsWithConfirmation,
|
|
13
|
+
} from "./tool-call-confirmation-enrichment.js";
|
|
14
|
+
|
|
15
|
+
const WORKSPACE = "/home/user/project";
|
|
16
|
+
|
|
17
|
+
function toolCall(
|
|
18
|
+
overrides: Partial<ConversationMessageToolCall>,
|
|
19
|
+
): ConversationMessageToolCall {
|
|
20
|
+
return {
|
|
21
|
+
name: "file_read",
|
|
22
|
+
input: {},
|
|
23
|
+
...overrides,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
clear();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
describe("collectPendingConfirmations", () => {
|
|
32
|
+
test("keys confirmation interactions by toolUseId", () => {
|
|
33
|
+
// GIVEN a confirmation interaction registered for a conversation with a
|
|
34
|
+
// tool-use id and confirmation details
|
|
35
|
+
register("req-1", {
|
|
36
|
+
conversationId: "conv-1",
|
|
37
|
+
kind: "confirmation",
|
|
38
|
+
toolUseId: "tool-abc",
|
|
39
|
+
confirmationDetails: {
|
|
40
|
+
toolName: "file_read",
|
|
41
|
+
input: { path: "/home/user/project/a.txt" },
|
|
42
|
+
riskLevel: "low",
|
|
43
|
+
allowlistOptions: [],
|
|
44
|
+
scopeOptions: [],
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// WHEN we collect the conversation's pending confirmations
|
|
49
|
+
const byToolUseId = collectPendingConfirmations("conv-1");
|
|
50
|
+
|
|
51
|
+
// THEN the interaction is keyed by its tool-use id
|
|
52
|
+
expect(byToolUseId.size).toBe(1);
|
|
53
|
+
expect(byToolUseId.get("tool-abc")?.requestId).toBe("req-1");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("ignores interactions lacking a toolUseId or confirmation details", () => {
|
|
57
|
+
// GIVEN a confirmation without a toolUseId AND a non-confirmation
|
|
58
|
+
// interaction in the same conversation
|
|
59
|
+
register("req-no-tool", {
|
|
60
|
+
conversationId: "conv-2",
|
|
61
|
+
kind: "confirmation",
|
|
62
|
+
confirmationDetails: {
|
|
63
|
+
toolName: "file_read",
|
|
64
|
+
input: {},
|
|
65
|
+
riskLevel: "low",
|
|
66
|
+
allowlistOptions: [],
|
|
67
|
+
scopeOptions: [],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
register("req-secret", {
|
|
71
|
+
conversationId: "conv-2",
|
|
72
|
+
kind: "secret",
|
|
73
|
+
toolUseId: "tool-xyz",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// WHEN we collect the conversation's pending confirmations
|
|
77
|
+
const byToolUseId = collectPendingConfirmations("conv-2");
|
|
78
|
+
|
|
79
|
+
// THEN neither is included — one has no toolUseId, the other is not a
|
|
80
|
+
// confirmation
|
|
81
|
+
expect(byToolUseId.size).toBe(0);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("enrichToolCallsWithConfirmation", () => {
|
|
86
|
+
test("derives the scope ladder for scope-aware tools", () => {
|
|
87
|
+
// GIVEN a completed scope-aware tool call with no registry entry
|
|
88
|
+
const calls = [toolCall({ id: "tool-1", name: "file_read" })];
|
|
89
|
+
|
|
90
|
+
// WHEN we enrich it
|
|
91
|
+
const [enriched] = enrichToolCallsWithConfirmation(calls, {
|
|
92
|
+
workspaceDir: WORKSPACE,
|
|
93
|
+
pendingConfirmations: new Map(),
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// THEN the scope ladder is derived from the workspace and tool name
|
|
97
|
+
expect(enriched?.scopeOptions?.[0]).toEqual({
|
|
98
|
+
label: WORKSPACE,
|
|
99
|
+
scope: WORKSPACE,
|
|
100
|
+
});
|
|
101
|
+
// AND no pending confirmation is stamped
|
|
102
|
+
expect(enriched?.pendingConfirmation).toBeUndefined();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("leaves non-scope-aware tool calls untouched", () => {
|
|
106
|
+
// GIVEN a tool call for a tool that has no scope ladder
|
|
107
|
+
const original = toolCall({ id: "tool-2", name: "web_search" });
|
|
108
|
+
|
|
109
|
+
// WHEN we enrich it with no matching registry entry
|
|
110
|
+
const [enriched] = enrichToolCallsWithConfirmation([original], {
|
|
111
|
+
workspaceDir: WORKSPACE,
|
|
112
|
+
pendingConfirmations: new Map(),
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// THEN the tool call is returned unchanged (same reference)
|
|
116
|
+
expect(enriched).toBe(original);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("stamps the pending confirmation when the registry has a match", () => {
|
|
120
|
+
// GIVEN a registry entry matching the tool call by id
|
|
121
|
+
const pendingConfirmations = collectPendingConfirmationsFixture();
|
|
122
|
+
const calls = [toolCall({ id: "tool-abc", name: "file_read" })];
|
|
123
|
+
|
|
124
|
+
// WHEN we enrich it
|
|
125
|
+
const [enriched] = enrichToolCallsWithConfirmation(calls, {
|
|
126
|
+
workspaceDir: WORKSPACE,
|
|
127
|
+
pendingConfirmations,
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
// THEN the outstanding prompt is projected onto the tool call
|
|
131
|
+
expect(enriched?.pendingConfirmation?.requestId).toBe("req-1");
|
|
132
|
+
expect(enriched?.pendingConfirmation?.toolName).toBe("file_read");
|
|
133
|
+
expect(enriched?.pendingConfirmation?.riskLevel).toBe("high");
|
|
134
|
+
// AND the directory scope ladder carries through from the registry so a
|
|
135
|
+
// restored prompt offers the same scope the live event did
|
|
136
|
+
expect(enriched?.pendingConfirmation?.directoryScopeOptions).toEqual([
|
|
137
|
+
{ label: "Anywhere in project/", scope: "/home/user/project" },
|
|
138
|
+
]);
|
|
139
|
+
// AND the derived scope ladder is still present
|
|
140
|
+
expect(enriched?.scopeOptions?.length).toBeGreaterThan(0);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
function collectPendingConfirmationsFixture() {
|
|
145
|
+
register("req-1", {
|
|
146
|
+
conversationId: "conv-fixture",
|
|
147
|
+
kind: "confirmation",
|
|
148
|
+
toolUseId: "tool-abc",
|
|
149
|
+
confirmationDetails: {
|
|
150
|
+
toolName: "file_read",
|
|
151
|
+
input: { path: "/home/user/project/a.txt" },
|
|
152
|
+
riskLevel: "high",
|
|
153
|
+
allowlistOptions: [],
|
|
154
|
+
scopeOptions: [],
|
|
155
|
+
directoryScopeOptions: [
|
|
156
|
+
{ label: "Anywhere in project/", scope: "/home/user/project" },
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
});
|
|
160
|
+
return collectPendingConfirmations("conv-fixture");
|
|
161
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render-time enrichment of history tool calls with confirmation context.
|
|
3
|
+
*
|
|
4
|
+
* Two pieces of state the persisted content does not itself carry are layered
|
|
5
|
+
* onto each rendered tool call here so the web/API clients can render the same
|
|
6
|
+
* confirmation UI on a cold reconnect (or a history reopen after the live
|
|
7
|
+
* event buffer has aged out) that the live `confirmation_request` SSE stream
|
|
8
|
+
* would have produced:
|
|
9
|
+
*
|
|
10
|
+
* 1. `scopeOptions` — the confirmation scope ladder for scope-aware tools
|
|
11
|
+
* (file/bash). It is a pure function of the workspace directory and the
|
|
12
|
+
* tool name, so it is *derived* at render rather than persisted. Completed
|
|
13
|
+
* tool calls regain the ladder the rule editor's trust-rule suggestion
|
|
14
|
+
* fallback consumes.
|
|
15
|
+
*
|
|
16
|
+
* 2. `pendingConfirmation` — the in-flight prompt for a tool call still
|
|
17
|
+
* awaiting a user decision. It is read from the in-memory
|
|
18
|
+
* `pending-interactions` registry (the authoritative store of unresolved
|
|
19
|
+
* prompts), so it appears only while the prompt is genuinely outstanding.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import type { ConversationMessageToolCall } from "../../api/responses/conversation-message.js";
|
|
23
|
+
import { generateScopeOptions } from "../../permissions/checker.js";
|
|
24
|
+
import {
|
|
25
|
+
type ConfirmationDetails,
|
|
26
|
+
getByConversation,
|
|
27
|
+
} from "../pending-interactions.js";
|
|
28
|
+
|
|
29
|
+
/** A pending confirmation matched to the tool call it prompts for, keyed by `toolUseId`. */
|
|
30
|
+
interface PendingConfirmationMatch {
|
|
31
|
+
requestId: string;
|
|
32
|
+
details: ConfirmationDetails;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Build the `toolUseId → pending confirmation` lookup for a conversation from
|
|
37
|
+
* the registry. Only confirmation interactions that carry both a `toolUseId`
|
|
38
|
+
* and `confirmationDetails` can be stamped onto a wire tool call.
|
|
39
|
+
*/
|
|
40
|
+
export function collectPendingConfirmations(
|
|
41
|
+
conversationId: string,
|
|
42
|
+
): Map<string, PendingConfirmationMatch> {
|
|
43
|
+
const byToolUseId = new Map<string, PendingConfirmationMatch>();
|
|
44
|
+
for (const interaction of getByConversation(conversationId)) {
|
|
45
|
+
if (
|
|
46
|
+
interaction.kind === "confirmation" &&
|
|
47
|
+
interaction.confirmationDetails &&
|
|
48
|
+
interaction.toolUseId
|
|
49
|
+
) {
|
|
50
|
+
byToolUseId.set(interaction.toolUseId, {
|
|
51
|
+
requestId: interaction.requestId,
|
|
52
|
+
details: interaction.confirmationDetails,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return byToolUseId;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Project a registry `ConfirmationDetails` into the wire `pendingConfirmation` shape. */
|
|
60
|
+
function toPendingConfirmation(
|
|
61
|
+
requestId: string,
|
|
62
|
+
details: ConfirmationDetails,
|
|
63
|
+
): NonNullable<ConversationMessageToolCall["pendingConfirmation"]> {
|
|
64
|
+
return {
|
|
65
|
+
requestId,
|
|
66
|
+
toolName: details.toolName,
|
|
67
|
+
riskLevel: details.riskLevel,
|
|
68
|
+
input: details.input,
|
|
69
|
+
allowlistOptions: details.allowlistOptions,
|
|
70
|
+
scopeOptions: details.scopeOptions,
|
|
71
|
+
directoryScopeOptions: details.directoryScopeOptions,
|
|
72
|
+
persistentDecisionsAllowed: details.persistentDecisionsAllowed,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Layer derived `scopeOptions` and any outstanding `pendingConfirmation` onto a
|
|
78
|
+
* message's rendered tool calls. Returns a new array; tool calls without
|
|
79
|
+
* enrichment are returned unchanged.
|
|
80
|
+
*/
|
|
81
|
+
export function enrichToolCallsWithConfirmation(
|
|
82
|
+
toolCalls: ConversationMessageToolCall[],
|
|
83
|
+
opts: {
|
|
84
|
+
workspaceDir: string;
|
|
85
|
+
pendingConfirmations: ReadonlyMap<string, PendingConfirmationMatch>;
|
|
86
|
+
},
|
|
87
|
+
): ConversationMessageToolCall[] {
|
|
88
|
+
return toolCalls.map((tc) => {
|
|
89
|
+
const scopeOptions = generateScopeOptions(opts.workspaceDir, tc.name);
|
|
90
|
+
const match = tc.id ? opts.pendingConfirmations.get(tc.id) : undefined;
|
|
91
|
+
if (scopeOptions.length === 0 && !match) {
|
|
92
|
+
return tc;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
...tc,
|
|
96
|
+
...(scopeOptions.length > 0 ? { scopeOptions } : {}),
|
|
97
|
+
...(match
|
|
98
|
+
? {
|
|
99
|
+
pendingConfirmation: toPendingConfirmation(
|
|
100
|
+
match.requestId,
|
|
101
|
+
match.details,
|
|
102
|
+
),
|
|
103
|
+
}
|
|
104
|
+
: {}),
|
|
105
|
+
};
|
|
106
|
+
});
|
|
107
|
+
}
|
|
@@ -43,9 +43,30 @@ const TrustRulesListParams = z
|
|
|
43
43
|
})
|
|
44
44
|
.strict();
|
|
45
45
|
|
|
46
|
+
const TrustRuleSchema = z.object({
|
|
47
|
+
id: z.string(),
|
|
48
|
+
tool: z.string(),
|
|
49
|
+
pattern: z.string(),
|
|
50
|
+
risk: z.enum(["low", "medium", "high"]),
|
|
51
|
+
description: z.string(),
|
|
52
|
+
origin: z.enum(["default", "user_defined"]),
|
|
53
|
+
userModified: z.boolean(),
|
|
54
|
+
deleted: z.boolean(),
|
|
55
|
+
createdAt: z.string(),
|
|
56
|
+
updatedAt: z.string(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const TrustRulesListResponseSchema = z.object({
|
|
60
|
+
rules: z.array(TrustRuleSchema),
|
|
61
|
+
});
|
|
62
|
+
type TrustRulesListResponse = z.infer<typeof TrustRulesListResponseSchema>;
|
|
63
|
+
|
|
46
64
|
// ── Handlers ────────────────────────────────────────────────────────────
|
|
47
65
|
|
|
48
|
-
async function handleList({
|
|
66
|
+
async function handleList({
|
|
67
|
+
queryParams = {},
|
|
68
|
+
body = {},
|
|
69
|
+
}: RouteHandlerArgs): Promise<TrustRulesListResponse> {
|
|
49
70
|
// HTTP GET delivers filters via queryParams; CLI IPC puts them in body.
|
|
50
71
|
const source = Object.keys(queryParams).length > 0 ? queryParams : body;
|
|
51
72
|
const p = TrustRulesListParams.parse(source);
|
|
@@ -54,7 +75,9 @@ async function handleList({ queryParams = {}, body = {} }: RouteHandlerArgs) {
|
|
|
54
75
|
if (p.origin) qs.set("origin", p.origin);
|
|
55
76
|
if (p.include_all) qs.set("include_all", "true");
|
|
56
77
|
const query = qs.toString();
|
|
57
|
-
return gatewayFetch(
|
|
78
|
+
return gatewayFetch(
|
|
79
|
+
`/v1/trust-rules${query ? `?${query}` : ""}`,
|
|
80
|
+
) as Promise<TrustRulesListResponse>;
|
|
58
81
|
}
|
|
59
82
|
|
|
60
83
|
// ── Route definitions ───────────────────────────────────────────────────
|
|
@@ -73,6 +96,7 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
73
96
|
description:
|
|
74
97
|
"List trust rules, optionally filtered by tool, origin, or include_all.",
|
|
75
98
|
tags: ["trust-rules"],
|
|
99
|
+
responseBody: TrustRulesListResponseSchema,
|
|
76
100
|
queryParams: [
|
|
77
101
|
{ name: "tool", description: "Filter by tool name" },
|
|
78
102
|
{ name: "origin", description: "Filter by origin" },
|
|
@@ -15,6 +15,7 @@ import { sanitizeForTts } from "../../calls/tts-text-sanitizer.js";
|
|
|
15
15
|
import { isAssistantFeatureFlagEnabled } from "../../config/assistant-feature-flags.js";
|
|
16
16
|
import { getConfig } from "../../config/loader.js";
|
|
17
17
|
import { getMessageContent } from "../../daemon/handlers/conversation-history.js";
|
|
18
|
+
import { listCatalogProvidersForDisplay } from "../../tts/provider-catalog.js";
|
|
18
19
|
import {
|
|
19
20
|
synthesizeText,
|
|
20
21
|
TtsSynthesisError,
|
|
@@ -136,6 +137,10 @@ const ttsResponseHeaders = () => ({
|
|
|
136
137
|
// Handlers
|
|
137
138
|
// ---------------------------------------------------------------------------
|
|
138
139
|
|
|
140
|
+
function handleListTtsProviders() {
|
|
141
|
+
return { providers: listCatalogProvidersForDisplay() };
|
|
142
|
+
}
|
|
143
|
+
|
|
139
144
|
async function handleMessageTts({ pathParams, queryParams }: RouteHandlerArgs) {
|
|
140
145
|
const config = getConfig();
|
|
141
146
|
|
|
@@ -226,6 +231,36 @@ async function handleSynthesizeCliTts({ body }: RouteHandlerArgs) {
|
|
|
226
231
|
// ---------------------------------------------------------------------------
|
|
227
232
|
|
|
228
233
|
export const ROUTES: RouteDefinition[] = [
|
|
234
|
+
{
|
|
235
|
+
operationId: "tts_providers",
|
|
236
|
+
endpoint: "tts/providers",
|
|
237
|
+
method: "GET",
|
|
238
|
+
policy: {
|
|
239
|
+
requiredScopes: ["settings.read"],
|
|
240
|
+
allowedPrincipalTypes: ACTOR_PRINCIPALS,
|
|
241
|
+
},
|
|
242
|
+
summary: "List TTS providers",
|
|
243
|
+
description:
|
|
244
|
+
"Return the catalog of available TTS providers with client-facing metadata.",
|
|
245
|
+
tags: ["tts"],
|
|
246
|
+
responseBody: z.object({
|
|
247
|
+
providers: z.array(
|
|
248
|
+
z.object({
|
|
249
|
+
id: z.string(),
|
|
250
|
+
displayName: z.string(),
|
|
251
|
+
subtitle: z.string(),
|
|
252
|
+
supportsVoiceSelection: z.boolean(),
|
|
253
|
+
apiKeyPlaceholder: z.string(),
|
|
254
|
+
credentialsGuide: z.object({
|
|
255
|
+
description: z.string(),
|
|
256
|
+
url: z.string(),
|
|
257
|
+
linkLabel: z.string(),
|
|
258
|
+
}),
|
|
259
|
+
}),
|
|
260
|
+
),
|
|
261
|
+
}),
|
|
262
|
+
handler: handleListTtsProviders,
|
|
263
|
+
},
|
|
229
264
|
{
|
|
230
265
|
operationId: "messages_tts",
|
|
231
266
|
endpoint: "messages/:messageId/tts",
|
|
@@ -31,6 +31,70 @@ export interface RoutePathParam {
|
|
|
31
31
|
description?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Content types a route can declare a request body for. `application/json`
|
|
36
|
+
* is the implicit default when `requestBody` is a bare Zod schema, so it is
|
|
37
|
+
* only spelled out here for the explicit `{ contentType, schema }` form.
|
|
38
|
+
*/
|
|
39
|
+
export type RouteRequestContentType =
|
|
40
|
+
| "application/json"
|
|
41
|
+
| "application/octet-stream"
|
|
42
|
+
| "multipart/form-data";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A route's request body. Either:
|
|
46
|
+
* - a bare Zod schema, which is advertised as `application/json`, or
|
|
47
|
+
* - an explicit `{ contentType, schema }` pair for non-JSON bodies (e.g. a
|
|
48
|
+
* raw `application/octet-stream` upload). `schema` may be a Zod schema or a
|
|
49
|
+
* plain JSON Schema fragment (e.g. `{ type: "string", format: "binary" }`).
|
|
50
|
+
*
|
|
51
|
+
* The OpenAPI generator turns this into the operation's `requestBody`, so the
|
|
52
|
+
* generated client SDK describes a real body type instead of `never`. The HTTP
|
|
53
|
+
* adapter parses the body off the request `Content-Type` header, so this field
|
|
54
|
+
* is a codegen signal only and does not change runtime request handling.
|
|
55
|
+
*/
|
|
56
|
+
export type RouteRequestBody =
|
|
57
|
+
| z.ZodType
|
|
58
|
+
| {
|
|
59
|
+
contentType: RouteRequestContentType;
|
|
60
|
+
schema: z.ZodType | Record<string, unknown>;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Content types a route can declare a success response body for.
|
|
65
|
+
* `application/json` is the implicit default when `responseBody` is a bare
|
|
66
|
+
* Zod schema, so it is only spelled out here for the explicit
|
|
67
|
+
* `{ contentType, schema }` form (e.g. a binary `application/octet-stream`
|
|
68
|
+
* download or an `application/gzip` archive).
|
|
69
|
+
*/
|
|
70
|
+
export type RouteResponseContentType =
|
|
71
|
+
| "application/json"
|
|
72
|
+
| "application/octet-stream"
|
|
73
|
+
| "application/gzip"
|
|
74
|
+
| "application/pdf"
|
|
75
|
+
| "application/zip";
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* A route's success response body. Either:
|
|
79
|
+
* - a bare Zod schema, which is advertised as `application/json`, or
|
|
80
|
+
* - an explicit `{ contentType, schema }` pair for non-JSON responses (e.g. a
|
|
81
|
+
* binary download). `schema` may be a Zod schema or a plain JSON Schema
|
|
82
|
+
* fragment (e.g. `{ type: "string", format: "binary" }`, which is not
|
|
83
|
+
* expressible as a bare Zod type).
|
|
84
|
+
*
|
|
85
|
+
* The OpenAPI generator turns this into the operation's success response, so
|
|
86
|
+
* the generated client SDK describes a real response type (e.g. `Blob`)
|
|
87
|
+
* instead of `unknown`. Handlers serialize their own bytes via `RouteResponse`,
|
|
88
|
+
* so this field is a codegen signal only and does not change runtime
|
|
89
|
+
* response handling.
|
|
90
|
+
*/
|
|
91
|
+
export type RouteResponseBody =
|
|
92
|
+
| z.ZodType
|
|
93
|
+
| {
|
|
94
|
+
contentType: RouteResponseContentType;
|
|
95
|
+
schema: z.ZodType | Record<string, unknown>;
|
|
96
|
+
};
|
|
97
|
+
|
|
34
98
|
export interface RouteHandlerArgs {
|
|
35
99
|
pathParams?: Record<string, string>;
|
|
36
100
|
queryParams?: Record<string, string>;
|
|
@@ -102,8 +166,8 @@ export interface RouteDefinition {
|
|
|
102
166
|
tags?: string[];
|
|
103
167
|
pathParams?: RoutePathParam[];
|
|
104
168
|
queryParams?: RouteQueryParam[];
|
|
105
|
-
requestBody?:
|
|
106
|
-
responseBody?:
|
|
169
|
+
requestBody?: RouteRequestBody;
|
|
170
|
+
responseBody?: RouteResponseBody;
|
|
107
171
|
/**
|
|
108
172
|
* HTTP status code for the success response. Defaults to "200".
|
|
109
173
|
* Use "201" for resource creation, "204" for no-content responses.
|
|
@@ -145,12 +209,6 @@ export interface RouteDefinition {
|
|
|
145
209
|
* RouteError subclasses rather than explicit Response objects.
|
|
146
210
|
*/
|
|
147
211
|
additionalResponses?: Record<string, { description: string }>;
|
|
148
|
-
/**
|
|
149
|
-
* When true, the route expects a raw binary body (e.g. file uploads).
|
|
150
|
-
* The HTTP adapter already reads `rawBody` for non-JSON content types;
|
|
151
|
-
* this flag is a declarative signal for documentation and tooling.
|
|
152
|
-
*/
|
|
153
|
-
rawBody?: boolean;
|
|
154
212
|
/**
|
|
155
213
|
* Per-route request-log control. Routes that opt in can suppress the
|
|
156
214
|
* per-request INFO log line after a confirmed run of successful
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Route handlers for usage and cost summary endpoints.
|
|
3
3
|
*
|
|
4
|
-
* GET /v1/usage/totals?from=&to=
|
|
5
|
-
* GET /v1/usage/daily?from=&to=
|
|
6
|
-
* GET /v1/usage/breakdown?from=&to=&groupBy= — grouped breakdown
|
|
7
|
-
* GET /v1/usage/series?from=&to=&granularity=&groupBy= — grouped time-series buckets
|
|
4
|
+
* GET /v1/usage/totals?from=&to=&scheduleId= — aggregate totals for a time range
|
|
5
|
+
* GET /v1/usage/daily?from=&to=&scheduleId= — per-day buckets for a time range
|
|
6
|
+
* GET /v1/usage/breakdown?from=&to=&groupBy=&scheduleId= — grouped breakdown
|
|
7
|
+
* GET /v1/usage/series?from=&to=&granularity=&groupBy=&scheduleId= — grouped time-series buckets
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { z } from "zod";
|
|
@@ -18,10 +18,12 @@ import {
|
|
|
18
18
|
type GroupByDimension,
|
|
19
19
|
USAGE_GROUP_BY_DIMENSIONS,
|
|
20
20
|
USAGE_SERIES_GROUP_BY_DIMENSIONS,
|
|
21
|
+
type UsageAggregationFilter,
|
|
21
22
|
type UsageGranularity,
|
|
22
23
|
} from "../../memory/llm-usage-store.js";
|
|
23
24
|
import { validateTimezone } from "../../memory/usage-buckets.js";
|
|
24
25
|
import { ACTOR_PRINCIPALS } from "../auth/route-policy.js";
|
|
26
|
+
import { parseEpochMillisRange } from "./epoch-millis-range.js";
|
|
25
27
|
import { BadRequestError } from "./errors.js";
|
|
26
28
|
import type { RouteDefinition, RouteHandlerArgs } from "./types.js";
|
|
27
29
|
|
|
@@ -29,6 +31,8 @@ const VALID_GROUP_BY = new Set<string>(USAGE_GROUP_BY_DIMENSIONS);
|
|
|
29
31
|
const VALID_SERIES_GROUP_BY = new Set<string>(USAGE_SERIES_GROUP_BY_DIMENSIONS);
|
|
30
32
|
const GROUP_BY_DESCRIPTION = USAGE_GROUP_BY_DIMENSIONS.join(", ");
|
|
31
33
|
const SERIES_GROUP_BY_DESCRIPTION = USAGE_SERIES_GROUP_BY_DIMENSIONS.join(", ");
|
|
34
|
+
const SCHEDULE_ID_FILTER_DESCRIPTION =
|
|
35
|
+
"Optional schedule id. When set, usage is attributed by cron run windows for that schedule.";
|
|
32
36
|
|
|
33
37
|
const usageTotalsSchema = z.object({
|
|
34
38
|
totalInputTokens: z.number(),
|
|
@@ -86,43 +90,23 @@ function resolveTimezone(queryParams: Record<string, string>): string {
|
|
|
86
90
|
return tz;
|
|
87
91
|
}
|
|
88
92
|
|
|
89
|
-
function
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const toRaw = queryParams.to;
|
|
95
|
-
|
|
96
|
-
if (!fromRaw || !toRaw) {
|
|
97
|
-
throw new BadRequestError(
|
|
98
|
-
'Missing required query parameters: "from" and "to" (epoch milliseconds)',
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const from = Number(fromRaw);
|
|
103
|
-
const to = Number(toRaw);
|
|
104
|
-
|
|
105
|
-
if (!Number.isFinite(from) || !Number.isFinite(to)) {
|
|
106
|
-
throw new BadRequestError(
|
|
107
|
-
'"from" and "to" must be valid numbers (epoch milliseconds)',
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (from > to) {
|
|
112
|
-
throw new BadRequestError('"from" must be less than or equal to "to"');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return { from, to };
|
|
93
|
+
function parseUsageAggregationFilter(
|
|
94
|
+
queryParams: Record<string, string>,
|
|
95
|
+
): UsageAggregationFilter {
|
|
96
|
+
const scheduleId = queryParams.scheduleId?.trim();
|
|
97
|
+
return scheduleId ? { scheduleId } : {};
|
|
116
98
|
}
|
|
117
99
|
|
|
118
100
|
function handleUsageTotals({ queryParams }: RouteHandlerArgs) {
|
|
119
|
-
const
|
|
120
|
-
|
|
101
|
+
const qp = queryParams ?? {};
|
|
102
|
+
const range = parseEpochMillisRange(qp);
|
|
103
|
+
const filter = parseUsageAggregationFilter(qp);
|
|
104
|
+
return getUsageTotals(range, filter);
|
|
121
105
|
}
|
|
122
106
|
|
|
123
107
|
function handleUsageDaily({ queryParams }: RouteHandlerArgs) {
|
|
124
108
|
const qp = queryParams ?? {};
|
|
125
|
-
const range =
|
|
109
|
+
const range = parseEpochMillisRange(qp);
|
|
126
110
|
const granularity = qp.granularity ?? "daily";
|
|
127
111
|
if (granularity !== "daily" && granularity !== "hourly") {
|
|
128
112
|
throw new BadRequestError(
|
|
@@ -130,16 +114,17 @@ function handleUsageDaily({ queryParams }: RouteHandlerArgs) {
|
|
|
130
114
|
);
|
|
131
115
|
}
|
|
132
116
|
const tz = resolveTimezone(qp);
|
|
117
|
+
const filter = parseUsageAggregationFilter(qp);
|
|
133
118
|
const buckets =
|
|
134
119
|
granularity === "hourly"
|
|
135
|
-
? getUsageHourBuckets(range, tz, { fillEmpty: true })
|
|
136
|
-
: getUsageDayBuckets(range, tz, { fillEmpty: true });
|
|
120
|
+
? getUsageHourBuckets(range, tz, { fillEmpty: true }, filter)
|
|
121
|
+
: getUsageDayBuckets(range, tz, { fillEmpty: true }, filter);
|
|
137
122
|
return { buckets };
|
|
138
123
|
}
|
|
139
124
|
|
|
140
125
|
function handleUsageBreakdown({ queryParams }: RouteHandlerArgs) {
|
|
141
126
|
const qp = queryParams ?? {};
|
|
142
|
-
const range =
|
|
127
|
+
const range = parseEpochMillisRange(qp);
|
|
143
128
|
|
|
144
129
|
const groupBy = qp.groupBy;
|
|
145
130
|
if (!groupBy) {
|
|
@@ -153,13 +138,18 @@ function handleUsageBreakdown({ queryParams }: RouteHandlerArgs) {
|
|
|
153
138
|
);
|
|
154
139
|
}
|
|
155
140
|
|
|
156
|
-
const
|
|
141
|
+
const filter = parseUsageAggregationFilter(qp);
|
|
142
|
+
const breakdown = getUsageGroupBreakdown(
|
|
143
|
+
range,
|
|
144
|
+
groupBy as GroupByDimension,
|
|
145
|
+
filter,
|
|
146
|
+
);
|
|
157
147
|
return { breakdown };
|
|
158
148
|
}
|
|
159
149
|
|
|
160
150
|
function handleUsageSeries({ queryParams }: RouteHandlerArgs) {
|
|
161
151
|
const qp = queryParams ?? {};
|
|
162
|
-
const range =
|
|
152
|
+
const range = parseEpochMillisRange(qp);
|
|
163
153
|
const granularity = qp.granularity ?? "daily";
|
|
164
154
|
if (granularity !== "daily" && granularity !== "hourly") {
|
|
165
155
|
throw new BadRequestError(
|
|
@@ -180,12 +170,14 @@ function handleUsageSeries({ queryParams }: RouteHandlerArgs) {
|
|
|
180
170
|
}
|
|
181
171
|
|
|
182
172
|
const tz = resolveTimezone(qp);
|
|
173
|
+
const filter = parseUsageAggregationFilter(qp);
|
|
183
174
|
const buckets = getUsageGroupedSeries(
|
|
184
175
|
range,
|
|
185
176
|
groupBy as Exclude<GroupByDimension, "conversation">,
|
|
186
177
|
granularity as UsageGranularity,
|
|
187
178
|
tz,
|
|
188
179
|
{ fillEmpty: true },
|
|
180
|
+
filter,
|
|
189
181
|
);
|
|
190
182
|
return { buckets };
|
|
191
183
|
}
|
|
@@ -213,6 +205,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
213
205
|
type: "integer",
|
|
214
206
|
description: "End epoch millis (required)",
|
|
215
207
|
},
|
|
208
|
+
{
|
|
209
|
+
name: "scheduleId",
|
|
210
|
+
description: SCHEDULE_ID_FILTER_DESCRIPTION,
|
|
211
|
+
},
|
|
216
212
|
],
|
|
217
213
|
responseBody: usageTotalsSchema,
|
|
218
214
|
handler: handleUsageTotals,
|
|
@@ -249,6 +245,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
249
245
|
description:
|
|
250
246
|
'IANA timezone identifier (e.g. "America/Los_Angeles"). Bucket boundaries and display labels are computed in this timezone. Defaults to "UTC" for backwards compatibility.',
|
|
251
247
|
},
|
|
248
|
+
{
|
|
249
|
+
name: "scheduleId",
|
|
250
|
+
description: SCHEDULE_ID_FILTER_DESCRIPTION,
|
|
251
|
+
},
|
|
252
252
|
],
|
|
253
253
|
responseBody: z.object({
|
|
254
254
|
buckets: z.array(usageDayBucketSchema).describe("Usage bucket objects"),
|
|
@@ -282,6 +282,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
282
282
|
name: "groupBy",
|
|
283
283
|
description: `Group by: ${GROUP_BY_DESCRIPTION} (required)`,
|
|
284
284
|
},
|
|
285
|
+
{
|
|
286
|
+
name: "scheduleId",
|
|
287
|
+
description: SCHEDULE_ID_FILTER_DESCRIPTION,
|
|
288
|
+
},
|
|
285
289
|
],
|
|
286
290
|
responseBody: z.object({
|
|
287
291
|
breakdown: z
|
|
@@ -327,6 +331,10 @@ export const ROUTES: RouteDefinition[] = [
|
|
|
327
331
|
description:
|
|
328
332
|
'IANA timezone identifier (e.g. "America/Los_Angeles"). Bucket boundaries and display labels are computed in this timezone. Defaults to "UTC".',
|
|
329
333
|
},
|
|
334
|
+
{
|
|
335
|
+
name: "scheduleId",
|
|
336
|
+
description: SCHEDULE_ID_FILTER_DESCRIPTION,
|
|
337
|
+
},
|
|
330
338
|
],
|
|
331
339
|
responseBody: z.object({
|
|
332
340
|
buckets: z
|