vellum 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -2
- package/bun.lock +5 -2
- package/package.json +4 -2
- package/scripts/capture-x-graphql.ts +562 -0
- package/scripts/ipc/check-swift-decoder-drift.ts +2 -1
- package/scripts/test.sh +5 -0
- package/src/__tests__/__snapshots__/ipc-snapshot.test.ts.snap +161 -34
- package/src/__tests__/account-registry.test.ts +2 -1
- package/src/__tests__/agent-heartbeat-service.test.ts +250 -0
- package/src/__tests__/app-bundler.test.ts +12 -33
- package/src/__tests__/asset-materialize-tool.test.ts +16 -15
- package/src/__tests__/asset-search-tool.test.ts +23 -22
- package/src/__tests__/attachments-store.test.ts +56 -127
- package/src/__tests__/browser-skill-baseline-tool-payload.test.ts +5 -4
- package/src/__tests__/browser-skill-endstate.test.ts +5 -8
- package/src/__tests__/call-bridge.test.ts +385 -0
- package/src/__tests__/call-constants.test.ts +40 -0
- package/src/__tests__/call-orchestrator.test.ts +454 -0
- package/src/__tests__/call-recovery.test.ts +518 -0
- package/src/__tests__/call-routes-http.test.ts +459 -0
- package/src/__tests__/call-state-machine.test.ts +143 -0
- package/src/__tests__/call-state.test.ts +133 -0
- package/src/__tests__/call-store.test.ts +691 -0
- package/src/__tests__/cli-discover.test.ts +1 -1
- package/src/__tests__/commit-message-enrichment-service.test.ts +550 -0
- package/src/__tests__/compaction.benchmark.test.ts +176 -0
- package/src/__tests__/computer-use-tools.test.ts +250 -0
- package/src/__tests__/config-schema.test.ts +348 -3
- package/src/__tests__/conflict-store.test.ts +2 -1
- package/src/__tests__/contacts-tools.test.ts +331 -0
- package/src/__tests__/conversation-store.test.ts +30 -32
- package/src/__tests__/credential-security-invariants.test.ts +4 -0
- package/src/__tests__/date-context.test.ts +373 -0
- package/src/__tests__/db-schedule-syntax-migration.test.ts +129 -0
- package/src/__tests__/doordash-session.test.ts +9 -0
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +3 -3
- package/src/__tests__/followup-tools.test.ts +303 -0
- package/src/__tests__/handlers-twitter-config.test.ts +718 -0
- package/src/__tests__/intent-routing.test.ts +64 -57
- package/src/__tests__/ipc-roundtrip.benchmark.test.ts +237 -0
- package/src/__tests__/ipc-snapshot.test.ts +96 -28
- package/src/__tests__/llm-usage-store.test.ts +3 -8
- package/src/__tests__/media-generate-image.test.ts +1 -1
- package/src/__tests__/media-reuse-story.e2e.test.ts +7 -7
- package/src/__tests__/memory-retrieval.benchmark.test.ts +430 -0
- package/src/__tests__/parallel-tool.benchmark.test.ts +294 -0
- package/src/__tests__/playbook-tools.test.ts +342 -0
- package/src/__tests__/profile-compiler.test.ts +2 -1
- package/src/__tests__/provider-streaming.benchmark.test.ts +773 -0
- package/src/__tests__/recurrence-engine-rruleset.test.ts +78 -0
- package/src/__tests__/recurrence-engine.test.ts +69 -0
- package/src/__tests__/recurrence-types.test.ts +71 -0
- package/src/__tests__/registry.test.ts +17 -10
- package/src/__tests__/relay-server.test.ts +633 -0
- package/src/__tests__/reminder-store.test.ts +6 -3
- package/src/__tests__/reminder.test.ts +43 -77
- package/src/__tests__/run-orchestrator-assistant-events.test.ts +222 -0
- package/src/__tests__/run-orchestrator.test.ts +7 -7
- package/src/__tests__/runtime-attachment-metadata.test.ts +19 -20
- package/src/__tests__/runtime-runs-http.test.ts +5 -23
- package/src/__tests__/runtime-runs.test.ts +11 -11
- package/src/__tests__/schedule-store.test.ts +482 -0
- package/src/__tests__/schedule-tools.test.ts +700 -0
- package/src/__tests__/scheduler-recurrence.test.ts +329 -0
- package/src/__tests__/server-history-render.test.ts +14 -13
- package/src/__tests__/session-error.test.ts +28 -0
- package/src/__tests__/session-init.benchmark.test.ts +462 -0
- package/src/__tests__/session-queue.test.ts +89 -16
- package/src/__tests__/session-runtime-assembly.test.ts +161 -0
- package/src/__tests__/session-surfaces-task-progress.test.ts +104 -0
- package/src/__tests__/signup-e2e.test.ts +2 -1
- package/src/__tests__/skill-projection.benchmark.test.ts +328 -0
- package/src/__tests__/skill-script-runner.test.ts +159 -0
- package/src/__tests__/speaker-identification.test.ts +52 -0
- package/src/__tests__/subagent-manager-notify.test.ts +42 -10
- package/src/__tests__/subagent-tools.test.ts +141 -41
- package/src/__tests__/task-compiler.test.ts +2 -1
- package/src/__tests__/task-runner.test.ts +2 -1
- package/src/__tests__/task-scheduler.test.ts +2 -1
- package/src/__tests__/task-tools.test.ts +49 -56
- package/src/__tests__/tool-audit-listener.test.ts +1 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +2 -0
- package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +500 -0
- package/src/__tests__/tool-executor.test.ts +13 -17
- package/src/__tests__/turn-commit.test.ts +273 -2
- package/src/__tests__/twilio-provider.test.ts +143 -0
- package/src/__tests__/twilio-routes.test.ts +789 -0
- package/src/__tests__/twitter-auth-handler.test.ts +581 -0
- package/src/__tests__/view-image-tool.test.ts +217 -0
- package/src/__tests__/workspace-git-service.test.ts +403 -0
- package/src/__tests__/workspace-heartbeat-service.test.ts +141 -2
- package/src/agent-heartbeat/agent-heartbeat-service.ts +155 -0
- package/src/bundler/app-bundler.ts +35 -14
- package/src/calls/call-bridge.ts +95 -0
- package/src/calls/call-constants.ts +48 -0
- package/src/calls/call-domain.ts +276 -0
- package/src/calls/call-orchestrator.ts +390 -0
- package/src/calls/call-recovery.ts +207 -0
- package/src/calls/call-state-machine.ts +68 -0
- package/src/calls/call-state.ts +64 -0
- package/src/calls/call-store.ts +416 -0
- package/src/calls/relay-server.ts +335 -0
- package/src/calls/speaker-identification.ts +213 -0
- package/src/calls/twilio-config.ts +34 -0
- package/src/calls/twilio-provider.ts +173 -0
- package/src/calls/twilio-routes.ts +250 -0
- package/src/calls/types.ts +37 -0
- package/src/calls/voice-provider.ts +14 -0
- package/src/cli/config-commands.ts +334 -0
- package/src/cli/core-commands.ts +776 -0
- package/src/cli/doordash.ts +256 -25
- package/src/cli/ipc-client.ts +82 -0
- package/src/cli/map.ts +246 -0
- package/src/cli/twitter.ts +575 -0
- package/src/cli.ts +7 -5
- package/src/commands/__tests__/cc-command-registry.test.ts +319 -0
- package/src/commands/cc-command-registry.ts +209 -0
- package/src/config/bundled-skills/contacts/SKILL.md +39 -0
- package/src/config/bundled-skills/contacts/TOOLS.json +122 -0
- package/src/config/bundled-skills/contacts/tools/contact-merge.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-search.ts +9 -0
- package/src/config/bundled-skills/contacts/tools/contact-upsert.ts +9 -0
- package/src/config/bundled-skills/document/SKILL.md +18 -0
- package/src/config/bundled-skills/document/TOOLS.json +53 -0
- package/src/config/bundled-skills/document/tools/document-create.ts +9 -0
- package/src/config/bundled-skills/document/tools/document-update.ts +9 -0
- package/src/config/bundled-skills/doordash/SKILL.md +163 -0
- package/src/config/bundled-skills/followups/SKILL.md +32 -0
- package/src/config/bundled-skills/followups/TOOLS.json +100 -0
- package/src/config/bundled-skills/followups/tools/followup-create.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-list.ts +9 -0
- package/src/config/bundled-skills/followups/tools/followup-resolve.ts +9 -0
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +2 -24
- package/src/config/bundled-skills/messaging/tools/messaging-analyze-style.ts +2 -1
- package/src/config/bundled-skills/playbooks/SKILL.md +31 -0
- package/src/config/bundled-skills/playbooks/TOOLS.json +126 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-create.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-delete.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-list.ts +9 -0
- package/src/config/bundled-skills/playbooks/tools/playbook-update.ts +9 -0
- package/src/config/bundled-skills/reminder/SKILL.md +20 -0
- package/src/config/bundled-skills/reminder/TOOLS.json +67 -0
- package/src/config/bundled-skills/reminder/tools/reminder-cancel.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-create.ts +9 -0
- package/src/config/bundled-skills/reminder/tools/reminder-list.ts +9 -0
- package/src/config/bundled-skills/schedule/SKILL.md +74 -0
- package/src/config/bundled-skills/schedule/TOOLS.json +135 -0
- package/src/config/bundled-skills/schedule/tools/schedule-create.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-delete.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-list.ts +9 -0
- package/src/config/bundled-skills/schedule/tools/schedule-update.ts +9 -0
- package/src/config/bundled-skills/subagent/SKILL.md +25 -0
- package/src/config/bundled-skills/subagent/TOOLS.json +107 -0
- package/src/config/bundled-skills/subagent/tools/subagent-abort.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-message.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-read.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-spawn.ts +9 -0
- package/src/config/bundled-skills/subagent/tools/subagent-status.ts +9 -0
- package/src/config/bundled-skills/tasks/SKILL.md +28 -0
- package/src/config/bundled-skills/tasks/TOOLS.json +256 -0
- package/src/config/bundled-skills/tasks/tools/task-delete.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-add.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-remove.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-show.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list-update.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-list.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-run.ts +9 -0
- package/src/config/bundled-skills/tasks/tools/task-save.ts +9 -0
- package/src/config/bundled-skills/twitter/SKILL.md +134 -0
- package/src/config/bundled-skills/watcher/SKILL.md +27 -0
- package/src/config/bundled-skills/watcher/TOOLS.json +147 -0
- package/src/config/bundled-skills/watcher/tools/watcher-create.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-delete.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-digest.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-list.ts +9 -0
- package/src/config/bundled-skills/watcher/tools/watcher-update.ts +9 -0
- package/src/config/defaults.ts +44 -0
- package/src/config/loader.ts +4 -1
- package/src/config/schema.ts +218 -1
- package/src/config/system-prompt.ts +100 -6
- package/src/config/templates/IDENTITY.md +7 -0
- package/src/config/types.ts +5 -0
- package/src/contacts/contact-store.ts +4 -4
- package/src/daemon/assistant-attachments.ts +10 -0
- package/src/daemon/classifier.ts +3 -1
- package/src/daemon/computer-use-session.ts +3 -1
- package/src/daemon/date-context.ts +136 -0
- package/src/daemon/handlers/apps.ts +16 -1
- package/src/daemon/handlers/browser.ts +54 -0
- package/src/daemon/handlers/computer-use.ts +7 -1
- package/src/daemon/handlers/config.ts +192 -4
- package/src/daemon/handlers/diagnostics.ts +5 -1
- package/src/daemon/handlers/documents.ts +18 -29
- package/src/daemon/handlers/home-base.ts +5 -1
- package/src/daemon/handlers/index.ts +40 -271
- package/src/daemon/handlers/misc.ts +9 -1
- package/src/daemon/handlers/publish.ts +6 -1
- package/src/daemon/handlers/sessions.ts +65 -12
- package/src/daemon/handlers/shared.ts +36 -1
- package/src/daemon/handlers/signing.ts +37 -0
- package/src/daemon/handlers/skills.ts +20 -6
- package/src/daemon/handlers/subagents.ts +8 -3
- package/src/daemon/handlers/twitter-auth.ts +169 -0
- package/src/daemon/handlers/work-items.ts +495 -39
- package/src/daemon/ipc-contract-inventory.json +40 -4
- package/src/daemon/ipc-contract.ts +185 -37
- package/src/daemon/ipc-protocol.ts +7 -2
- package/src/daemon/lifecycle.ts +48 -5
- package/src/daemon/main.ts +10 -4
- package/src/daemon/ride-shotgun-handler.ts +74 -10
- package/src/daemon/server.ts +144 -29
- package/src/daemon/session-agent-loop.ts +887 -0
- package/src/daemon/session-attachments.ts +28 -5
- package/src/daemon/session-error.ts +24 -3
- package/src/daemon/session-lifecycle.ts +147 -0
- package/src/daemon/session-media-retry.ts +147 -0
- package/src/daemon/session-messaging.ts +145 -0
- package/src/daemon/session-notifiers.ts +164 -0
- package/src/daemon/session-process.ts +2 -2
- package/src/daemon/session-queue-manager.ts +1 -0
- package/src/daemon/session-runtime-assembly.ts +52 -0
- package/src/daemon/session-skill-tools.ts +124 -5
- package/src/daemon/session-slash.ts +3 -0
- package/src/daemon/session-surfaces.ts +77 -2
- package/src/daemon/session-tool-setup.ts +222 -2
- package/src/daemon/session-usage.ts +0 -2
- package/src/daemon/session.ts +114 -1365
- package/src/daemon/video-thumbnail.ts +60 -0
- package/src/doordash/client.ts +121 -27
- package/src/doordash/queries.ts +1 -2
- package/src/export/formatter.ts +3 -1
- package/src/followups/followup-store.ts +4 -2
- package/src/followups/types.ts +6 -0
- package/src/hooks/templates.ts +1 -1
- package/src/index.ts +32 -1151
- package/src/media/gemini-image-service.ts +1 -1
- package/src/memory/attachments-store.ts +28 -83
- package/src/memory/channel-delivery-store.ts +7 -21
- package/src/memory/clarification-resolver.ts +6 -5
- package/src/memory/contradiction-checker.ts +3 -2
- package/src/memory/conversation-key-store.ts +10 -29
- package/src/memory/conversation-store.ts +2 -1
- package/src/memory/db.ts +362 -2
- package/src/memory/entity-extractor.ts +6 -3
- package/src/memory/items-extractor.ts +5 -4
- package/src/memory/jobs-store.ts +3 -2
- package/src/memory/llm-usage-store.ts +1 -2
- package/src/memory/runs-store.ts +1 -2
- package/src/memory/schema.ts +65 -2
- package/src/messaging/style-analyzer.ts +3 -2
- package/src/messaging/thread-summarizer.ts +8 -12
- package/src/messaging/triage-engine.ts +4 -2
- package/src/providers/openrouter/client.ts +20 -0
- package/src/providers/registry.ts +8 -0
- package/src/runtime/http-server.ts +277 -25
- package/src/runtime/http-types.ts +0 -2
- package/src/runtime/routes/attachment-routes.ts +5 -6
- package/src/runtime/routes/call-routes.ts +140 -0
- package/src/runtime/routes/channel-routes.ts +12 -19
- package/src/runtime/routes/conversation-routes.ts +5 -9
- package/src/runtime/routes/run-routes.ts +4 -8
- package/src/runtime/run-orchestrator.ts +39 -6
- package/src/schedule/recurrence-engine.ts +138 -0
- package/src/schedule/recurrence-types.ts +67 -0
- package/src/schedule/schedule-store.ts +102 -57
- package/src/schedule/scheduler.ts +9 -6
- package/src/security/oauth2.ts +29 -4
- package/src/security/secret-allowlist.ts +46 -0
- package/src/skills/clawhub.ts +1 -1
- package/src/subagent/manager.ts +40 -8
- package/src/swarm/backend-claude-code.ts +64 -9
- package/src/swarm/worker-prompts.ts +2 -1
- package/src/tasks/SPEC.md +34 -28
- package/src/tasks/ephemeral-permissions.ts +16 -7
- package/src/tasks/task-compiler.ts +5 -4
- package/src/tasks/task-runner.ts +10 -5
- package/src/tasks/task-scheduler.ts +1 -1
- package/src/tasks/tool-sanitizer.ts +36 -0
- package/src/tools/assets/search.ts +4 -4
- package/src/tools/browser/api-map.ts +220 -0
- package/src/tools/browser/auto-navigate.ts +270 -0
- package/src/tools/browser/browser-execution.ts +2 -1
- package/src/tools/browser/browser-manager.ts +2 -2
- package/src/tools/browser/network-recorder.ts +5 -4
- package/src/tools/browser/x-auto-navigate.ts +207 -0
- package/src/tools/calls/call-end.ts +67 -0
- package/src/tools/calls/call-start.ts +73 -0
- package/src/tools/calls/call-status.ts +81 -0
- package/src/tools/claude-code/claude-code.ts +77 -11
- package/src/tools/contacts/contact-merge.ts +46 -78
- package/src/tools/contacts/contact-search.ts +35 -79
- package/src/tools/contacts/contact-upsert.ts +35 -108
- package/src/tools/credentials/vault.ts +21 -5
- package/src/tools/document/document-tool.ts +71 -144
- package/src/tools/executor.ts +129 -10
- package/src/tools/followups/followup_create.ts +46 -88
- package/src/tools/followups/followup_list.ts +34 -74
- package/src/tools/followups/followup_resolve.ts +31 -66
- package/src/tools/host-terminal/cli-discover.ts +2 -1
- package/src/tools/host-terminal/host-shell.ts +10 -0
- package/src/tools/memory/handlers.ts +5 -4
- package/src/tools/network/__tests__/web-search.test.ts +427 -0
- package/src/tools/network/script-proxy/__tests__/logging.test.ts +248 -0
- package/src/tools/network/script-proxy/__tests__/policy.test.ts +234 -0
- package/src/tools/network/script-proxy/__tests__/router.test.ts +76 -0
- package/src/tools/network/web-fetch.ts +18 -6
- package/src/tools/playbooks/index.ts +4 -5
- package/src/tools/playbooks/playbook-create.ts +3 -47
- package/src/tools/playbooks/playbook-delete.ts +1 -25
- package/src/tools/playbooks/playbook-list.ts +1 -28
- package/src/tools/playbooks/playbook-update.ts +3 -51
- package/src/tools/registry.ts +2 -4
- package/src/tools/reminder/reminder.ts +5 -78
- package/src/tools/schedule/create.ts +69 -74
- package/src/tools/schedule/delete.ts +21 -47
- package/src/tools/schedule/list.ts +55 -74
- package/src/tools/schedule/update.ts +77 -84
- package/src/tools/subagent/abort.ts +29 -58
- package/src/tools/subagent/message.ts +30 -63
- package/src/tools/subagent/read.ts +53 -84
- package/src/tools/subagent/spawn.ts +43 -82
- package/src/tools/subagent/status.ts +42 -71
- package/src/tools/swarm/delegate.ts +2 -1
- package/src/tools/tasks/index.ts +8 -6
- package/src/tools/tasks/task-delete.ts +69 -56
- package/src/tools/tasks/task-list.ts +31 -52
- package/src/tools/tasks/task-run.ts +74 -102
- package/src/tools/tasks/task-save.ts +33 -65
- package/src/tools/tasks/work-item-enqueue.ts +192 -134
- package/src/tools/tasks/work-item-list.ts +33 -78
- package/src/tools/tasks/work-item-remove.ts +60 -0
- package/src/tools/tasks/work-item-update.ts +114 -0
- package/src/tools/terminal/backends/native.ts +3 -1
- package/src/tools/tool-manifest.ts +20 -74
- package/src/tools/types.ts +6 -0
- package/src/tools/ui-surface/definitions.ts +6 -1
- package/src/tools/watch/screen-watch.ts +3 -1
- package/src/tools/watcher/create.ts +52 -98
- package/src/tools/watcher/delete.ts +20 -46
- package/src/tools/watcher/digest.ts +36 -70
- package/src/tools/watcher/list.ts +49 -79
- package/src/tools/watcher/update.ts +45 -91
- package/src/twitter/client.ts +690 -0
- package/src/twitter/session.ts +91 -0
- package/src/usage/types.ts +0 -1
- package/src/util/truncate.ts +6 -0
- package/src/watcher/providers/slack.ts +2 -1
- package/src/watcher/watcher-store.ts +3 -2
- package/src/work-items/work-item-store.ts +236 -2
- package/src/workspace/commit-message-enrichment-service.ts +284 -0
- package/src/workspace/commit-message-provider.ts +95 -0
- package/src/workspace/git-service.ts +272 -52
- package/src/workspace/heartbeat-service.ts +70 -13
- package/src/workspace/provider-commit-message-generator.ts +242 -0
- package/src/workspace/turn-commit.ts +100 -51
- package/src/tools/contacts/index.ts +0 -4
- package/src/tools/document/index.ts +0 -5
- package/src/tools/followups/index.ts +0 -3
- package/src/tools/subagent/index.ts +0 -5
- /package/src/__tests__/{memory-context-benchmark.test.ts → memory-context-benchmark.benchmark.test.ts} +0 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import type { Message as ProviderMessage } from './provider-types.js';
|
|
10
10
|
import type { Message, ToolDefinition } from '../providers/types.js';
|
|
11
|
+
import { truncate } from '../util/truncate.js';
|
|
11
12
|
import { getProvider } from '../providers/registry.js';
|
|
12
13
|
import { getConfig } from '../config/loader.js';
|
|
13
14
|
|
|
@@ -97,7 +98,7 @@ function buildCorpus(messages: ProviderMessage[]): string[] {
|
|
|
97
98
|
for (const msg of messages) {
|
|
98
99
|
if (!msg.text.trim()) continue;
|
|
99
100
|
const to = msg.conversationId;
|
|
100
|
-
const truncatedBody = msg.text
|
|
101
|
+
const truncatedBody = truncate(msg.text, 500, '');
|
|
101
102
|
entries.push(`To: ${to}\n\n${truncatedBody}`);
|
|
102
103
|
}
|
|
103
104
|
return entries;
|
|
@@ -143,7 +144,7 @@ export async function extractStylePatterns(
|
|
|
143
144
|
|
|
144
145
|
const stylePatterns: StylePattern[] = (result.style_patterns ?? []).map((p) => ({
|
|
145
146
|
aspect: p.aspect,
|
|
146
|
-
summary: p.summary
|
|
147
|
+
summary: truncate(p.summary, 500, ''),
|
|
147
148
|
importance: p.importance,
|
|
148
149
|
examples: p.examples,
|
|
149
150
|
}));
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import Anthropic from '@anthropic-ai/sdk';
|
|
2
2
|
import { getConfig } from '../config/loader.js';
|
|
3
3
|
import { getLogger } from '../util/logger.js';
|
|
4
|
+
import { truncate } from '../util/truncate.js';
|
|
4
5
|
import type { ThreadMessage, ThreadSummary } from './types.js';
|
|
5
6
|
|
|
6
7
|
const log = getLogger('thread-summarizer');
|
|
7
8
|
|
|
8
9
|
const SUMMARIZATION_MODEL = 'claude-haiku-4-5-20251001';
|
|
10
|
+
const SUMMARIZATION_TIMEOUT_MS = 20_000;
|
|
9
11
|
const DEFAULT_MAX_TOKENS = 4000;
|
|
10
12
|
const CHARS_PER_TOKEN = 4;
|
|
11
13
|
|
|
@@ -172,14 +174,10 @@ function extractParticipants(messages: ThreadMessage[]): Array<{ name: string }>
|
|
|
172
174
|
|
|
173
175
|
function summarizeSingleMessage(message: ThreadMessage): ThreadSummary {
|
|
174
176
|
return {
|
|
175
|
-
summary: message.body
|
|
176
|
-
? message.body.slice(0, 197) + '...'
|
|
177
|
-
: message.body,
|
|
177
|
+
summary: truncate(message.body, 200),
|
|
178
178
|
participants: [{ name: message.sender }],
|
|
179
179
|
openQuestions: [],
|
|
180
|
-
lastAction: message.body
|
|
181
|
-
? message.body.slice(0, 197) + '...'
|
|
182
|
-
: message.body,
|
|
180
|
+
lastAction: truncate(message.body, 200),
|
|
183
181
|
sentiment: 'neutral',
|
|
184
182
|
messageCount: 1,
|
|
185
183
|
};
|
|
@@ -225,7 +223,7 @@ async function summarizeWithLLM(
|
|
|
225
223
|
timer = setTimeout(() => {
|
|
226
224
|
abortController.abort();
|
|
227
225
|
reject(new Error('Thread summarization LLM timeout'));
|
|
228
|
-
},
|
|
226
|
+
}, SUMMARIZATION_TIMEOUT_MS);
|
|
229
227
|
}),
|
|
230
228
|
]);
|
|
231
229
|
|
|
@@ -249,7 +247,7 @@ async function summarizeWithLLM(
|
|
|
249
247
|
: 'neutral';
|
|
250
248
|
|
|
251
249
|
return {
|
|
252
|
-
summary: String(input.summary ?? '')
|
|
250
|
+
summary: truncate(String(input.summary ?? ''), 2000, ''),
|
|
253
251
|
participants: Array.isArray(input.participants)
|
|
254
252
|
? input.participants.map((p) => ({
|
|
255
253
|
name: String(p.name),
|
|
@@ -259,7 +257,7 @@ async function summarizeWithLLM(
|
|
|
259
257
|
openQuestions: Array.isArray(input.openQuestions)
|
|
260
258
|
? input.openQuestions.map((q) => String(q))
|
|
261
259
|
: [],
|
|
262
|
-
lastAction: String(input.lastAction ?? '')
|
|
260
|
+
lastAction: truncate(String(input.lastAction ?? ''), 500, ''),
|
|
263
261
|
sentiment,
|
|
264
262
|
messageCount: messages.length,
|
|
265
263
|
};
|
|
@@ -280,9 +278,7 @@ function buildFallbackSummary(messages: ThreadMessage[]): ThreadSummary {
|
|
|
280
278
|
summary: `Thread with ${messages.length} message(s) from ${extractParticipants(messages).map((p) => p.name).join(', ')}.`,
|
|
281
279
|
participants: extractParticipants(messages),
|
|
282
280
|
openQuestions: [],
|
|
283
|
-
lastAction: lastMsg.body
|
|
284
|
-
? lastMsg.body.slice(0, 197) + '...'
|
|
285
|
-
: lastMsg.body,
|
|
281
|
+
lastAction: truncate(lastMsg.body, 200),
|
|
286
282
|
sentiment: 'neutral',
|
|
287
283
|
messageCount: messages.length,
|
|
288
284
|
};
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import Anthropic from '@anthropic-ai/sdk';
|
|
11
|
+
import { truncate } from '../util/truncate.js';
|
|
11
12
|
import { v4 as uuid } from 'uuid';
|
|
12
13
|
import { and, eq, isNull, desc } from 'drizzle-orm';
|
|
13
14
|
import { getConfig } from '../config/loader.js';
|
|
@@ -24,6 +25,7 @@ import { DEFAULT_TRIAGE_CATEGORIES } from './types.js';
|
|
|
24
25
|
const log = getLogger('triage-engine');
|
|
25
26
|
|
|
26
27
|
const TRIAGE_MODEL = 'claude-haiku-4-5-20251001';
|
|
28
|
+
const TRIAGE_CLASSIFICATION_TIMEOUT_MS = 15_000;
|
|
27
29
|
|
|
28
30
|
// ── Playbook fetching ────────────────────────────────────────────────
|
|
29
31
|
|
|
@@ -244,7 +246,7 @@ async function classifyWithLLM(
|
|
|
244
246
|
timer = setTimeout(() => {
|
|
245
247
|
abortController.abort();
|
|
246
248
|
reject(new Error('Triage classification LLM timeout'));
|
|
247
|
-
},
|
|
249
|
+
}, TRIAGE_CLASSIFICATION_TIMEOUT_MS);
|
|
248
250
|
}),
|
|
249
251
|
]);
|
|
250
252
|
|
|
@@ -282,7 +284,7 @@ async function classifyWithLLM(
|
|
|
282
284
|
category: typeof input.category === 'string' ? input.category : 'needs_response',
|
|
283
285
|
confidence,
|
|
284
286
|
suggestedAction: typeof input.suggestedAction === 'string'
|
|
285
|
-
? input.suggestedAction
|
|
287
|
+
? truncate(input.suggestedAction, 500, '')
|
|
286
288
|
: 'Review manually',
|
|
287
289
|
matchedPlaybooks,
|
|
288
290
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OpenAIProvider } from '../openai/client.js';
|
|
2
|
+
|
|
3
|
+
export interface OpenRouterProviderOptions {
|
|
4
|
+
apiKey?: string;
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
streamTimeoutMs?: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const DEFAULT_OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1';
|
|
10
|
+
|
|
11
|
+
export class OpenRouterProvider extends OpenAIProvider {
|
|
12
|
+
constructor(apiKey: string, model: string, options: OpenRouterProviderOptions = {}) {
|
|
13
|
+
super(apiKey, model, {
|
|
14
|
+
baseURL: options.baseURL?.trim() || DEFAULT_OPENROUTER_BASE_URL,
|
|
15
|
+
providerName: 'openrouter',
|
|
16
|
+
providerLabel: 'OpenRouter',
|
|
17
|
+
streamTimeoutMs: options.streamTimeoutMs,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -4,6 +4,7 @@ import { OpenAIProvider } from "./openai/client.js";
|
|
|
4
4
|
import { GeminiProvider } from "./gemini/client.js";
|
|
5
5
|
import { OllamaProvider } from "./ollama/client.js";
|
|
6
6
|
import { FireworksProvider } from "./fireworks/client.js";
|
|
7
|
+
import { OpenRouterProvider } from "./openrouter/client.js";
|
|
7
8
|
import { RetryProvider } from "./retry.js";
|
|
8
9
|
import { FailoverProvider } from "./failover.js";
|
|
9
10
|
import { wrapWithLogfire } from "../logfire.js";
|
|
@@ -15,6 +16,7 @@ const DEFAULT_MODELS: Record<string, string> = {
|
|
|
15
16
|
gemini: 'gemini-3-flash',
|
|
16
17
|
ollama: 'llama3.2',
|
|
17
18
|
fireworks: 'accounts/fireworks/models/kimi-k2p5',
|
|
19
|
+
openrouter: 'x-ai/grok-4',
|
|
18
20
|
};
|
|
19
21
|
|
|
20
22
|
const providers = new Map<string, Provider>();
|
|
@@ -135,4 +137,10 @@ export function initializeProviders(config: ProvidersConfig): void {
|
|
|
135
137
|
wrapWithLogfire(new FireworksProvider(config.apiKeys.fireworks, model, { streamTimeoutMs })),
|
|
136
138
|
));
|
|
137
139
|
}
|
|
140
|
+
if (config.apiKeys.openrouter) {
|
|
141
|
+
const model = resolveModel(config, 'openrouter');
|
|
142
|
+
registerProvider('openrouter', new RetryProvider(
|
|
143
|
+
wrapWithLogfire(new OpenRouterProvider(config.apiKeys.openrouter, model, { streamTimeoutMs })),
|
|
144
|
+
));
|
|
145
|
+
}
|
|
138
146
|
}
|
|
@@ -10,6 +10,7 @@ import { resolve } from 'node:path';
|
|
|
10
10
|
import { timingSafeEqual } from 'node:crypto';
|
|
11
11
|
import { ConfigError, IngressBlockedError } from '../util/errors.js';
|
|
12
12
|
import { getLogger } from '../util/logger.js';
|
|
13
|
+
import { TwilioConversationRelayProvider } from '../calls/twilio-provider.js';
|
|
13
14
|
import type { RunOrchestrator } from './run-orchestrator.js';
|
|
14
15
|
|
|
15
16
|
// Route handlers — grouped by domain
|
|
@@ -45,6 +46,19 @@ import {
|
|
|
45
46
|
handleDeleteSharedApp,
|
|
46
47
|
} from './routes/app-routes.js';
|
|
47
48
|
import { handleAddSecret } from './routes/secret-routes.js';
|
|
49
|
+
import {
|
|
50
|
+
handleStartCall,
|
|
51
|
+
handleGetCallStatus,
|
|
52
|
+
handleCancelCall,
|
|
53
|
+
handleAnswerCall,
|
|
54
|
+
} from './routes/call-routes.js';
|
|
55
|
+
import {
|
|
56
|
+
handleVoiceWebhook,
|
|
57
|
+
handleStatusCallback,
|
|
58
|
+
handleConnectAction,
|
|
59
|
+
} from '../calls/twilio-routes.js';
|
|
60
|
+
import { RelayConnection, activeRelayConnections } from '../calls/relay-server.js';
|
|
61
|
+
import type { RelayWebSocketData } from '../calls/relay-server.js';
|
|
48
62
|
|
|
49
63
|
// Re-export shared types so existing consumers don't need to update imports
|
|
50
64
|
export type {
|
|
@@ -95,6 +109,108 @@ function getDiskSpaceInfo(): DiskSpaceInfo | null {
|
|
|
95
109
|
}
|
|
96
110
|
}
|
|
97
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Regex to extract the Twilio webhook subpath from both top-level and
|
|
114
|
+
* assistant-scoped route shapes:
|
|
115
|
+
* /v1/calls/twilio/<subpath>
|
|
116
|
+
* /v1/assistants/<id>/calls/twilio/<subpath>
|
|
117
|
+
*/
|
|
118
|
+
const TWILIO_WEBHOOK_RE = /^\/v1\/(?:assistants\/[^/]+\/)?calls\/twilio\/(.+)$/;
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Gateway-compatible Twilio webhook paths:
|
|
122
|
+
* /webhooks/twilio/<subpath>
|
|
123
|
+
*
|
|
124
|
+
* Maps gateway path segments to the internal subpath names used by the
|
|
125
|
+
* dispatcher below (e.g. "voice" -> "voice-webhook").
|
|
126
|
+
*/
|
|
127
|
+
const TWILIO_GATEWAY_WEBHOOK_RE = /^\/webhooks\/twilio\/(.+)$/;
|
|
128
|
+
const GATEWAY_SUBPATH_MAP: Record<string, string> = {
|
|
129
|
+
voice: 'voice-webhook',
|
|
130
|
+
status: 'status',
|
|
131
|
+
'connect-action': 'connect-action',
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Validate a Twilio webhook request's X-Twilio-Signature header.
|
|
136
|
+
*
|
|
137
|
+
* Returns the raw body text on success so callers can reconstruct the Request
|
|
138
|
+
* for downstream handlers (which also need to read the body).
|
|
139
|
+
* Returns a 403 Response if signature validation fails.
|
|
140
|
+
*
|
|
141
|
+
* Fail-closed: if the auth token is not configured, the request is rejected
|
|
142
|
+
* with 403 rather than silently skipping validation. An explicit local-dev
|
|
143
|
+
* bypass is available via TWILIO_WEBHOOK_VALIDATION_DISABLED=true.
|
|
144
|
+
*/
|
|
145
|
+
async function validateTwilioWebhook(
|
|
146
|
+
req: Request,
|
|
147
|
+
): Promise<{ body: string } | Response> {
|
|
148
|
+
const rawBody = await req.text();
|
|
149
|
+
|
|
150
|
+
// Allow explicit local-dev bypass — must be exactly "true"
|
|
151
|
+
if (process.env.TWILIO_WEBHOOK_VALIDATION_DISABLED === 'true') {
|
|
152
|
+
log.warn('Twilio webhook signature validation explicitly disabled via TWILIO_WEBHOOK_VALIDATION_DISABLED');
|
|
153
|
+
return { body: rawBody };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const authToken = TwilioConversationRelayProvider.getAuthToken();
|
|
157
|
+
|
|
158
|
+
// Fail-closed: reject if no auth token is configured
|
|
159
|
+
if (!authToken) {
|
|
160
|
+
log.error('Twilio auth token not configured — rejecting webhook request (fail-closed)');
|
|
161
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const signature = req.headers.get('x-twilio-signature');
|
|
165
|
+
if (!signature) {
|
|
166
|
+
log.warn('Twilio webhook request missing X-Twilio-Signature header');
|
|
167
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse form-urlencoded body into key-value params for signature computation
|
|
171
|
+
const params: Record<string, string> = {};
|
|
172
|
+
const formData = new URLSearchParams(rawBody);
|
|
173
|
+
for (const [key, value] of formData.entries()) {
|
|
174
|
+
params[key] = value;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reconstruct the public-facing URL that Twilio signed against.
|
|
178
|
+
// Behind proxies/gateways, req.url is the local server URL (e.g.
|
|
179
|
+
// http://127.0.0.1:7821/...) which differs from the public URL Twilio
|
|
180
|
+
// used to compute the HMAC-SHA1 signature.
|
|
181
|
+
const publicBaseUrl = process.env.TWILIO_WEBHOOK_BASE_URL;
|
|
182
|
+
const parsedUrl = new URL(req.url);
|
|
183
|
+
const publicUrl = publicBaseUrl
|
|
184
|
+
? publicBaseUrl.replace(/\/$/, '') + parsedUrl.pathname + parsedUrl.search
|
|
185
|
+
: req.url;
|
|
186
|
+
|
|
187
|
+
const isValid = TwilioConversationRelayProvider.verifyWebhookSignature(
|
|
188
|
+
publicUrl,
|
|
189
|
+
params,
|
|
190
|
+
signature,
|
|
191
|
+
authToken,
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
if (!isValid) {
|
|
195
|
+
log.warn('Twilio webhook signature validation failed');
|
|
196
|
+
return Response.json({ error: 'Forbidden' }, { status: 403 });
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return { body: rawBody };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Re-create a Request with the same method, headers, and URL but with a
|
|
204
|
+
* pre-read body string so downstream handlers can call req.text() again.
|
|
205
|
+
*/
|
|
206
|
+
function cloneRequestWithBody(original: Request, body: string): Request {
|
|
207
|
+
return new Request(original.url, {
|
|
208
|
+
method: original.method,
|
|
209
|
+
headers: original.headers,
|
|
210
|
+
body,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
98
214
|
export class RuntimeHttpServer {
|
|
99
215
|
private server: ReturnType<typeof Bun.serve> | null = null;
|
|
100
216
|
private port: number;
|
|
@@ -120,11 +236,37 @@ export class RuntimeHttpServer {
|
|
|
120
236
|
}
|
|
121
237
|
|
|
122
238
|
async start(): Promise<void> {
|
|
123
|
-
this.server = Bun.serve({
|
|
239
|
+
this.server = Bun.serve<RelayWebSocketData>({
|
|
124
240
|
port: this.port,
|
|
125
241
|
hostname: this.hostname,
|
|
126
242
|
maxRequestBodySize: MAX_REQUEST_BODY_BYTES,
|
|
127
|
-
fetch: (req) => this.handleRequest(req),
|
|
243
|
+
fetch: (req, server) => this.handleRequest(req, server),
|
|
244
|
+
websocket: {
|
|
245
|
+
open(ws) {
|
|
246
|
+
const callSessionId = ws.data?.callSessionId;
|
|
247
|
+
log.info({ callSessionId }, 'ConversationRelay WebSocket opened');
|
|
248
|
+
if (callSessionId) {
|
|
249
|
+
const connection = new RelayConnection(ws, callSessionId);
|
|
250
|
+
activeRelayConnections.set(callSessionId, connection);
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
message(ws, message) {
|
|
254
|
+
const callSessionId = ws.data?.callSessionId;
|
|
255
|
+
if (callSessionId) {
|
|
256
|
+
const connection = activeRelayConnections.get(callSessionId);
|
|
257
|
+
connection?.handleMessage(typeof message === 'string' ? message : new TextDecoder().decode(message));
|
|
258
|
+
}
|
|
259
|
+
},
|
|
260
|
+
close(ws, code, reason) {
|
|
261
|
+
const callSessionId = ws.data?.callSessionId;
|
|
262
|
+
log.info({ callSessionId, code, reason: reason?.toString() }, 'ConversationRelay WebSocket closed');
|
|
263
|
+
if (callSessionId) {
|
|
264
|
+
const connection = activeRelayConnections.get(callSessionId);
|
|
265
|
+
connection?.destroy();
|
|
266
|
+
activeRelayConnections.delete(callSessionId);
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
},
|
|
128
270
|
});
|
|
129
271
|
|
|
130
272
|
// Sweep failed channel inbound events for retry every 30 seconds
|
|
@@ -162,7 +304,7 @@ export class RuntimeHttpServer {
|
|
|
162
304
|
return timingSafeEqual(a, b);
|
|
163
305
|
}
|
|
164
306
|
|
|
165
|
-
private async handleRequest(req: Request): Promise<Response> {
|
|
307
|
+
private async handleRequest(req: Request, server: ReturnType<typeof Bun.serve>): Promise<Response> {
|
|
166
308
|
const url = new URL(req.url);
|
|
167
309
|
const path = url.pathname;
|
|
168
310
|
|
|
@@ -171,6 +313,56 @@ export class RuntimeHttpServer {
|
|
|
171
313
|
return this.handleHealth();
|
|
172
314
|
}
|
|
173
315
|
|
|
316
|
+
// WebSocket upgrade for ConversationRelay — before auth check because
|
|
317
|
+
// Twilio WebSocket connections don't use bearer tokens.
|
|
318
|
+
if (path.startsWith('/v1/calls/relay') && req.headers.get('upgrade')?.toLowerCase() === 'websocket') {
|
|
319
|
+
const wsUrl = new URL(req.url);
|
|
320
|
+
const callSessionId = wsUrl.searchParams.get('callSessionId');
|
|
321
|
+
if (!callSessionId) {
|
|
322
|
+
return new Response('Missing callSessionId', { status: 400 });
|
|
323
|
+
}
|
|
324
|
+
const upgraded = server.upgrade(req, { data: { callSessionId } });
|
|
325
|
+
if (!upgraded) {
|
|
326
|
+
return new Response('WebSocket upgrade failed', { status: 500 });
|
|
327
|
+
}
|
|
328
|
+
// Bun handles the response after a successful upgrade.
|
|
329
|
+
// The RelayConnection is created in the websocket.open handler.
|
|
330
|
+
return undefined as unknown as Response;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ── Twilio webhook endpoints — before auth check because Twilio
|
|
334
|
+
// webhook POSTs don't include bearer tokens.
|
|
335
|
+
// Supports /v1/calls/twilio/*, /v1/assistants/:id/calls/twilio/*,
|
|
336
|
+
// and gateway-compatible /webhooks/twilio/* paths.
|
|
337
|
+
// Validates X-Twilio-Signature to prevent unauthorized access. ──
|
|
338
|
+
const twilioMatch = path.match(TWILIO_WEBHOOK_RE);
|
|
339
|
+
const gatewayTwilioMatch = !twilioMatch ? path.match(TWILIO_GATEWAY_WEBHOOK_RE) : null;
|
|
340
|
+
const resolvedTwilioSubpath = twilioMatch
|
|
341
|
+
? twilioMatch[1]
|
|
342
|
+
: gatewayTwilioMatch
|
|
343
|
+
? GATEWAY_SUBPATH_MAP[gatewayTwilioMatch[1]]
|
|
344
|
+
: null;
|
|
345
|
+
if (resolvedTwilioSubpath && req.method === 'POST') {
|
|
346
|
+
const twilioSubpath = resolvedTwilioSubpath;
|
|
347
|
+
|
|
348
|
+
// Validate Twilio request signature before dispatching
|
|
349
|
+
const validation = await validateTwilioWebhook(req);
|
|
350
|
+
if (validation instanceof Response) return validation;
|
|
351
|
+
|
|
352
|
+
// Reconstruct request so handlers can read the body
|
|
353
|
+
const validatedReq = cloneRequestWithBody(req, validation.body);
|
|
354
|
+
|
|
355
|
+
if (twilioSubpath === 'voice-webhook') {
|
|
356
|
+
return await handleVoiceWebhook(validatedReq);
|
|
357
|
+
}
|
|
358
|
+
if (twilioSubpath === 'status') {
|
|
359
|
+
return await handleStatusCallback(validatedReq);
|
|
360
|
+
}
|
|
361
|
+
if (twilioSubpath === 'connect-action') {
|
|
362
|
+
return await handleConnectAction(validatedReq);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
174
366
|
// Require bearer token when configured
|
|
175
367
|
if ((process.env.DISABLE_HTTP_AUTH ?? "").toLowerCase() !== "true" && this.bearerToken) {
|
|
176
368
|
const authHeader = req.headers.get('authorization');
|
|
@@ -247,7 +439,7 @@ export class RuntimeHttpServer {
|
|
|
247
439
|
// Paths already handled above (/v1/apps/..., /v1/secrets) will never reach here.
|
|
248
440
|
const newRouteMatch = path.match(/^\/v1\/(?!assistants\/)(.+)$/);
|
|
249
441
|
if (newRouteMatch) {
|
|
250
|
-
return this.dispatchEndpoint(
|
|
442
|
+
return this.dispatchEndpoint(newRouteMatch[1], req, url);
|
|
251
443
|
}
|
|
252
444
|
|
|
253
445
|
// Legacy: /v1/assistants/:assistantId/<endpoint>
|
|
@@ -259,7 +451,7 @@ export class RuntimeHttpServer {
|
|
|
259
451
|
const assistantId = match[1];
|
|
260
452
|
const endpoint = match[2];
|
|
261
453
|
log.warn({ endpoint, assistantId }, '[deprecated] /v1/assistants/:assistantId/... route used; migrate to /v1/...');
|
|
262
|
-
return this.dispatchEndpoint(
|
|
454
|
+
return this.dispatchEndpoint(endpoint, req, url);
|
|
263
455
|
}
|
|
264
456
|
|
|
265
457
|
/**
|
|
@@ -268,7 +460,6 @@ export class RuntimeHttpServer {
|
|
|
268
460
|
* legacy assistant-scoped routes (/v1/assistants/:assistantId/<endpoint>).
|
|
269
461
|
*/
|
|
270
462
|
private async dispatchEndpoint(
|
|
271
|
-
assistantId: string,
|
|
272
463
|
endpoint: string,
|
|
273
464
|
req: Request,
|
|
274
465
|
url: URL,
|
|
@@ -279,32 +470,32 @@ export class RuntimeHttpServer {
|
|
|
279
470
|
}
|
|
280
471
|
|
|
281
472
|
if (endpoint === 'messages' && req.method === 'GET') {
|
|
282
|
-
return handleListMessages(
|
|
473
|
+
return handleListMessages(url, this.interfacesDir);
|
|
283
474
|
}
|
|
284
475
|
|
|
285
476
|
if (endpoint === 'messages' && req.method === 'POST') {
|
|
286
|
-
return await handleSendMessage(
|
|
477
|
+
return await handleSendMessage(req, {
|
|
287
478
|
processMessage: this.processMessage,
|
|
288
479
|
persistAndProcessMessage: this.persistAndProcessMessage,
|
|
289
480
|
});
|
|
290
481
|
}
|
|
291
482
|
|
|
292
483
|
if (endpoint === 'attachments' && req.method === 'POST') {
|
|
293
|
-
return await handleUploadAttachment(
|
|
484
|
+
return await handleUploadAttachment(req);
|
|
294
485
|
}
|
|
295
486
|
|
|
296
487
|
if (endpoint === 'attachments' && req.method === 'DELETE') {
|
|
297
|
-
return await handleDeleteAttachment(
|
|
488
|
+
return await handleDeleteAttachment(req);
|
|
298
489
|
}
|
|
299
490
|
|
|
300
491
|
// Match attachments/:attachmentId
|
|
301
492
|
const attachmentMatch = endpoint.match(/^attachments\/([^/]+)$/);
|
|
302
493
|
if (attachmentMatch && req.method === 'GET') {
|
|
303
|
-
return handleGetAttachment(
|
|
494
|
+
return handleGetAttachment(attachmentMatch[1]);
|
|
304
495
|
}
|
|
305
496
|
|
|
306
497
|
if (endpoint === 'suggestion' && req.method === 'GET') {
|
|
307
|
-
return await handleGetSuggestion(
|
|
498
|
+
return await handleGetSuggestion(url, {
|
|
308
499
|
suggestionCache: this.suggestionCache,
|
|
309
500
|
suggestionInFlight: this.suggestionInFlight,
|
|
310
501
|
});
|
|
@@ -314,7 +505,7 @@ export class RuntimeHttpServer {
|
|
|
314
505
|
if (!this.runOrchestrator) {
|
|
315
506
|
return Response.json({ error: 'Run orchestration not configured' }, { status: 503 });
|
|
316
507
|
}
|
|
317
|
-
return await handleCreateRun(
|
|
508
|
+
return await handleCreateRun(req, this.runOrchestrator);
|
|
318
509
|
}
|
|
319
510
|
|
|
320
511
|
// Match runs/:runId, runs/:runId/decision, runs/:runId/trust-rule
|
|
@@ -325,17 +516,17 @@ export class RuntimeHttpServer {
|
|
|
325
516
|
}
|
|
326
517
|
const runId = runsMatch[1];
|
|
327
518
|
if (runsMatch[2] === '/decision' && req.method === 'POST') {
|
|
328
|
-
return await handleRunDecision(
|
|
519
|
+
return await handleRunDecision(runId, req, this.runOrchestrator);
|
|
329
520
|
}
|
|
330
521
|
if (runsMatch[2] === '/trust-rule' && req.method === 'POST') {
|
|
331
522
|
const run = this.runOrchestrator.getRun(runId);
|
|
332
|
-
if (!run
|
|
523
|
+
if (!run) {
|
|
333
524
|
return Response.json({ error: 'Run not found' }, { status: 404 });
|
|
334
525
|
}
|
|
335
526
|
return await handleAddTrustRule(runId, req);
|
|
336
527
|
}
|
|
337
528
|
if (req.method === 'GET') {
|
|
338
|
-
return handleGetRun(
|
|
529
|
+
return handleGetRun(runId, this.runOrchestrator);
|
|
339
530
|
}
|
|
340
531
|
}
|
|
341
532
|
|
|
@@ -345,36 +536,98 @@ export class RuntimeHttpServer {
|
|
|
345
536
|
}
|
|
346
537
|
|
|
347
538
|
if (endpoint === 'channels/conversation' && req.method === 'DELETE') {
|
|
348
|
-
return await handleDeleteConversation(
|
|
539
|
+
return await handleDeleteConversation(req);
|
|
349
540
|
}
|
|
350
541
|
|
|
351
542
|
if (endpoint === 'channels/inbound' && req.method === 'POST') {
|
|
352
|
-
return await handleChannelInbound(
|
|
543
|
+
return await handleChannelInbound(req, this.processMessage);
|
|
353
544
|
}
|
|
354
545
|
|
|
355
546
|
if (endpoint === 'channels/delivery-ack' && req.method === 'POST') {
|
|
356
|
-
return await handleChannelDeliveryAck(
|
|
547
|
+
return await handleChannelDeliveryAck(req);
|
|
357
548
|
}
|
|
358
549
|
|
|
359
550
|
if (endpoint === 'channels/dead-letters' && req.method === 'GET') {
|
|
360
|
-
return handleListDeadLetters(
|
|
551
|
+
return handleListDeadLetters();
|
|
361
552
|
}
|
|
362
553
|
|
|
363
554
|
if (endpoint === 'channels/replay' && req.method === 'POST') {
|
|
364
|
-
return await handleReplayDeadLetters(
|
|
555
|
+
return await handleReplayDeadLetters(req);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// ── Call API routes ───────────────────────────────────────────
|
|
559
|
+
if (endpoint === 'calls/start' && req.method === 'POST') {
|
|
560
|
+
return await handleStartCall(req);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Match calls/:callSessionId and calls/:callSessionId/cancel, calls/:callSessionId/answer
|
|
564
|
+
const callsMatch = endpoint.match(/^calls\/([^/]+?)(\/cancel|\/answer)?$/);
|
|
565
|
+
if (callsMatch) {
|
|
566
|
+
const callSessionId = callsMatch[1];
|
|
567
|
+
// Skip known sub-paths that are handled elsewhere (twilio, relay)
|
|
568
|
+
if (callSessionId !== 'twilio' && callSessionId !== 'relay' && callSessionId !== 'start') {
|
|
569
|
+
if (callsMatch[2] === '/cancel' && req.method === 'POST') {
|
|
570
|
+
return await handleCancelCall(req, callSessionId);
|
|
571
|
+
}
|
|
572
|
+
if (callsMatch[2] === '/answer' && req.method === 'POST') {
|
|
573
|
+
return await handleAnswerCall(req, callSessionId);
|
|
574
|
+
}
|
|
575
|
+
if (!callsMatch[2] && req.method === 'GET') {
|
|
576
|
+
return handleGetCallStatus(callSessionId);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// ── Internal Twilio forwarding endpoints (gateway → runtime) ──
|
|
582
|
+
// These accept JSON payloads from the gateway (which already validated
|
|
583
|
+
// the Twilio signature) and reconstruct requests for the existing
|
|
584
|
+
// Twilio route handlers.
|
|
585
|
+
if (endpoint === 'internal/twilio/voice-webhook' && req.method === 'POST') {
|
|
586
|
+
const json = await req.json() as { params: Record<string, string>; originalUrl?: string };
|
|
587
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
588
|
+
// Reconstruct request URL: keep the original URL query string (callSessionId)
|
|
589
|
+
const reconstructedUrl = json.originalUrl ?? req.url;
|
|
590
|
+
const fakeReq = new Request(reconstructedUrl, {
|
|
591
|
+
method: 'POST',
|
|
592
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
593
|
+
body: formBody,
|
|
594
|
+
});
|
|
595
|
+
return await handleVoiceWebhook(fakeReq);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (endpoint === 'internal/twilio/status' && req.method === 'POST') {
|
|
599
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
600
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
601
|
+
const fakeReq = new Request(req.url, {
|
|
602
|
+
method: 'POST',
|
|
603
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
604
|
+
body: formBody,
|
|
605
|
+
});
|
|
606
|
+
return await handleStatusCallback(fakeReq);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (endpoint === 'internal/twilio/connect-action' && req.method === 'POST') {
|
|
610
|
+
const json = await req.json() as { params: Record<string, string> };
|
|
611
|
+
const formBody = new URLSearchParams(json.params).toString();
|
|
612
|
+
const fakeReq = new Request(req.url, {
|
|
613
|
+
method: 'POST',
|
|
614
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
615
|
+
body: formBody,
|
|
616
|
+
});
|
|
617
|
+
return await handleConnectAction(fakeReq);
|
|
365
618
|
}
|
|
366
619
|
|
|
367
620
|
return Response.json({ error: 'Not found', source: 'runtime' }, { status: 404 });
|
|
368
621
|
} catch (err) {
|
|
369
622
|
if (err instanceof IngressBlockedError) {
|
|
370
|
-
log.warn({ endpoint,
|
|
623
|
+
log.warn({ endpoint, detectedTypes: err.detectedTypes }, 'Blocked HTTP request containing secrets');
|
|
371
624
|
return Response.json({ error: err.message, code: err.code }, { status: 422 });
|
|
372
625
|
}
|
|
373
626
|
if (err instanceof ConfigError) {
|
|
374
|
-
log.warn({ err, endpoint
|
|
627
|
+
log.warn({ err, endpoint }, 'Runtime HTTP config error');
|
|
375
628
|
return Response.json({ error: err.message, code: err.code }, { status: 422 });
|
|
376
629
|
}
|
|
377
|
-
log.error({ err, endpoint
|
|
630
|
+
log.error({ err, endpoint }, 'Runtime HTTP handler error');
|
|
378
631
|
const message = err instanceof Error ? err.message : 'Internal server error';
|
|
379
632
|
return Response.json({ error: message }, { status: 500 });
|
|
380
633
|
}
|
|
@@ -428,7 +681,6 @@ export class RuntimeHttpServer {
|
|
|
428
681
|
|
|
429
682
|
try {
|
|
430
683
|
const { messageId: userMessageId } = await this.processMessage(
|
|
431
|
-
event.assistantId,
|
|
432
684
|
event.conversationId,
|
|
433
685
|
content,
|
|
434
686
|
attachmentIds,
|
|
@@ -12,7 +12,6 @@ export interface RuntimeMessageSessionOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export type MessageProcessor = (
|
|
15
|
-
assistantId: string,
|
|
16
15
|
conversationId: string,
|
|
17
16
|
content: string,
|
|
18
17
|
attachmentIds?: string[],
|
|
@@ -26,7 +25,6 @@ export type MessageProcessor = (
|
|
|
26
25
|
* immediately.
|
|
27
26
|
*/
|
|
28
27
|
export type NonBlockingMessageProcessor = (
|
|
29
|
-
assistantId: string,
|
|
30
28
|
conversationId: string,
|
|
31
29
|
content: string,
|
|
32
30
|
attachmentIds?: string[],
|