@vellumai/assistant 0.6.0 → 0.6.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/AGENTS.md +4 -0
- package/ARCHITECTURE.md +68 -15
- package/Dockerfile +2 -2
- package/bun.lock +6 -2
- package/docker-entrypoint.sh +42 -1
- package/docs/architecture/integrations.md +1 -1
- package/docs/architecture/memory.md +21 -24
- package/node_modules/@vellumai/ces-contracts/src/handles.ts +7 -9
- package/openapi.yaml +539 -4
- package/package.json +5 -1
- package/src/__tests__/anthropic-provider.test.ts +160 -95
- package/src/__tests__/app-dir-path-guard.test.ts +1 -0
- package/src/__tests__/app-executors.test.ts +47 -1
- package/src/__tests__/app-source-watcher.test.ts +159 -0
- package/src/__tests__/assistant-event-hub.test.ts +30 -0
- package/src/__tests__/checker.test.ts +138 -172
- package/src/__tests__/cli-command-risk-guard.test.ts +1 -1
- package/src/__tests__/config-schema.test.ts +5 -0
- package/src/__tests__/context-overflow-approval.test.ts +5 -5
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +4 -6
- package/src/__tests__/conversation-agent-loop.test.ts +4 -51
- package/src/__tests__/conversation-analysis-routes.test.ts +169 -0
- package/src/__tests__/conversation-directories-parse.test.ts +105 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-runtime-assembly.test.ts +653 -832
- package/src/__tests__/conversation-runtime-workspace.test.ts +1 -93
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +17 -4
- package/src/__tests__/conversation-wipe.test.ts +2 -6
- package/src/__tests__/conversation-workspace-cache-state.test.ts +6 -12
- package/src/__tests__/conversation-workspace-injection.test.ts +25 -26
- package/src/__tests__/conversation-workspace-tool-tracking.test.ts +1 -1
- package/src/__tests__/copy-composer-tc-templates.test.ts +335 -0
- package/src/__tests__/credential-execution-approval-bridge.test.ts +0 -2
- package/src/__tests__/date-context.test.ts +76 -210
- package/src/__tests__/db-schedule-syntax-migration.test.ts +16 -1
- package/src/__tests__/file-list-tool.test.ts +219 -0
- package/src/__tests__/first-greeting.test.ts +1 -1
- package/src/__tests__/heartbeat-service.test.ts +180 -3
- package/src/__tests__/identity-routes.test.ts +328 -0
- package/src/__tests__/init-feature-flag-overrides.test.ts +167 -0
- package/src/__tests__/injection-block.test.ts +24 -0
- package/src/__tests__/inline-command-runner.test.ts +7 -5
- package/src/__tests__/install-skill-routing.test.ts +7 -6
- package/src/__tests__/jobs-store-qdrant-breaker.test.ts +15 -14
- package/src/__tests__/list-messages-tool-merge.test.ts +300 -0
- package/src/__tests__/llm-context-normalization.test.ts +18 -18
- package/src/__tests__/llm-context-route-provider.test.ts +101 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +162 -0
- package/src/__tests__/log-export-workspace.test.ts +257 -100
- package/src/__tests__/managed-credential-catalog-cli.test.ts +12 -14
- package/src/__tests__/mcp-abort-signal.test.ts +5 -0
- package/src/__tests__/mcp-client-auth.test.ts +5 -0
- package/src/__tests__/memory-recall-log-store.test.ts +132 -0
- package/src/__tests__/migration-export-streaming.test.ts +304 -0
- package/src/__tests__/migration-import-commit-http.test.ts +11 -10
- package/src/__tests__/mock-fetch.ts +87 -0
- package/src/__tests__/navigate-settings-tab.test.ts +14 -1
- package/src/__tests__/notification-broadcaster.test.ts +65 -0
- package/src/__tests__/notification-decision-recipient-context.test.ts +282 -0
- package/src/__tests__/onboarding-template-contract.test.ts +63 -14
- package/src/__tests__/parser.test.ts +32 -0
- package/src/__tests__/permission-checker-host-gate.test.ts +452 -0
- package/src/__tests__/permission-controls-v2-flag.test.ts +55 -0
- package/src/__tests__/permission-mode-sse.test.ts +418 -0
- package/src/__tests__/permission-mode-store.test.ts +277 -0
- package/src/__tests__/permission-mode.test.ts +101 -0
- package/src/__tests__/pkb-autoinject.test.ts +96 -0
- package/src/__tests__/platform-bash-auto-approve.test.ts +359 -0
- package/src/__tests__/profiler-routes.test.ts +502 -0
- package/src/__tests__/profiler-run-store.test.ts +441 -0
- package/src/__tests__/proxy-approval-callback.test.ts +4 -75
- package/src/__tests__/registry.test.ts +1 -1
- package/src/__tests__/require-fresh-approval.test.ts +0 -2
- package/src/__tests__/sandbox-diagnostics.test.ts +1 -32
- package/src/__tests__/sandbox-host-parity.test.ts +5 -4
- package/src/__tests__/scheduler-reuse-conversation.test.ts +368 -0
- package/src/__tests__/scrub-corrupted-image-attachments.test.ts +278 -0
- package/src/__tests__/search-skills-unified.test.ts +4 -3
- package/src/__tests__/send-endpoint-busy.test.ts +42 -3
- package/src/__tests__/set-permission-mode.test.ts +274 -0
- package/src/__tests__/skill-load-feature-flag.test.ts +12 -0
- package/src/__tests__/skill-memory.test.ts +2 -783
- package/src/__tests__/strip-memory-injections.test.ts +187 -0
- package/src/__tests__/subagent-detail.test.ts +84 -0
- package/src/__tests__/subagent-disposal.test.ts +308 -0
- package/src/__tests__/subagent-manager-notify.test.ts +19 -10
- package/src/__tests__/subagent-notify-parent.test.ts +390 -0
- package/src/__tests__/subagent-role-registry.test.ts +108 -0
- package/src/__tests__/subagent-tool-filtering.test.ts +71 -0
- package/src/__tests__/subagent-tools.test.ts +464 -4
- package/src/__tests__/system-prompt-ask-mode.test.ts +139 -0
- package/src/__tests__/task-memory-cleanup.test.ts +12 -12
- package/src/__tests__/terminal-sandbox.test.ts +1 -1
- package/src/__tests__/terminal-tools.test.ts +16 -29
- package/src/__tests__/test-preload.ts +18 -0
- package/src/__tests__/tool-domain-event-publisher.test.ts +0 -1
- package/src/__tests__/tool-executor-lifecycle-events.test.ts +1 -8
- package/src/__tests__/tool-executor.test.ts +4 -27
- package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
- package/src/__tests__/top-level-renderer.test.ts +10 -13
- package/src/__tests__/transport-hints-queue.test.ts +77 -0
- package/src/__tests__/trust-store.test.ts +4 -4
- package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +116 -2
- package/src/__tests__/workspace-migration-028-recover-conversations-from-disk-view.test.ts +387 -0
- package/src/__tests__/workspace-migration-030-seed-pkb-autoinject.test.ts +168 -0
- package/src/__tests__/workspace-policy.test.ts +2 -7
- package/src/agent/loop.ts +6 -29
- package/src/approvals/guardian-request-resolvers.ts +24 -0
- package/src/avatar/traits-png-sync.ts +3 -3
- package/src/channels/types.ts +5 -0
- package/src/cli/__tests__/run-assistant-command.ts +56 -0
- package/src/cli/__tests__/unknown-command.test.ts +33 -0
- package/src/cli/commands/__tests__/email-download.test.ts +245 -0
- package/src/cli/commands/__tests__/email-list.test.ts +192 -0
- package/src/cli/commands/__tests__/email-register.test.ts +186 -0
- package/src/cli/commands/__tests__/email-send.test.ts +291 -0
- package/src/cli/commands/__tests__/email-status.test.ts +181 -0
- package/src/cli/commands/__tests__/email-unregister.test.ts +139 -0
- package/src/cli/commands/__tests__/routes.test.ts +562 -0
- package/src/cli/commands/conversations.ts +1 -8
- package/src/cli/commands/default-action.ts +68 -1
- package/src/cli/commands/email.ts +584 -835
- package/src/cli/commands/memory.ts +1 -34
- package/src/cli/commands/notifications.ts +7 -2
- package/src/cli/commands/oauth/__tests__/connect.test.ts +27 -0
- package/src/cli/commands/oauth/connect.ts +25 -5
- package/src/cli/commands/platform/__tests__/connect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/disconnect.test.ts +1 -1
- package/src/cli/commands/platform/__tests__/status.test.ts +1 -1
- package/src/cli/commands/routes.ts +396 -0
- package/src/cli/commands/skills.ts +130 -20
- package/src/cli/program.ts +11 -2
- package/src/cli.ts +1 -120
- package/src/config/assistant-feature-flags.ts +59 -55
- package/src/config/bundled-skills/app-builder/SKILL.md +91 -5
- package/src/config/bundled-skills/gmail/SKILL.md +13 -8
- package/src/config/bundled-skills/gmail/TOOLS.json +1 -1
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -1
- package/src/config/bundled-skills/messaging/SKILL.md +7 -0
- package/src/config/bundled-skills/schedule/SKILL.md +22 -2
- package/src/config/bundled-skills/schedule/TOOLS.json +8 -0
- package/src/config/bundled-skills/settings/TOOLS.json +1 -1
- package/src/config/bundled-skills/settings/tools/avatar-get.ts +3 -13
- package/src/config/bundled-skills/settings/tools/avatar-remove.ts +2 -4
- package/src/config/bundled-skills/settings/tools/avatar-update.ts +5 -2
- package/src/config/bundled-skills/settings/tools/navigate-settings-tab.ts +8 -3
- package/src/config/bundled-skills/slack/SKILL.md +2 -0
- package/src/config/bundled-skills/subagent/SKILL.md +43 -3
- package/src/config/bundled-skills/subagent/TOOLS.json +29 -4
- package/src/config/env-registry.ts +63 -0
- package/src/config/feature-flag-registry.json +17 -1
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/filing.ts +51 -0
- package/src/config/schemas/heartbeat.ts +15 -12
- package/src/config/schemas/memory-lifecycle.ts +12 -0
- package/src/config/schemas/security.ts +14 -0
- package/src/config/schemas/services.ts +8 -0
- package/src/credential-execution/approval-bridge.ts +0 -1
- package/src/credential-execution/managed-catalog.ts +3 -7
- package/src/daemon/app-source-watcher.ts +93 -0
- package/src/daemon/config-watcher.ts +85 -3
- package/src/daemon/context-overflow-approval.ts +0 -1
- package/src/daemon/conversation-agent-loop-handlers.ts +20 -0
- package/src/daemon/conversation-agent-loop.ts +179 -65
- package/src/daemon/conversation-attachments.ts +0 -1
- package/src/daemon/conversation-history.ts +4 -19
- package/src/daemon/conversation-lifecycle.ts +8 -14
- package/src/daemon/conversation-messaging.ts +3 -0
- package/src/daemon/conversation-process.ts +30 -8
- package/src/daemon/conversation-queue-manager.ts +8 -0
- package/src/daemon/conversation-runtime-assembly.ts +359 -308
- package/src/daemon/conversation-surfaces.ts +65 -0
- package/src/daemon/conversation-tool-setup.ts +44 -17
- package/src/daemon/conversation-workspace.ts +1 -2
- package/src/daemon/conversation.ts +19 -3
- package/src/daemon/date-context.ts +26 -53
- package/src/daemon/first-greeting.ts +1 -1
- package/src/daemon/handlers/conversations.ts +5 -7
- package/src/daemon/handlers/shared.test.ts +143 -0
- package/src/daemon/handlers/shared.ts +70 -5
- package/src/daemon/handlers/skills.ts +11 -18
- package/src/daemon/lifecycle.ts +220 -158
- package/src/daemon/message-types/conversations.ts +29 -6
- package/src/daemon/message-types/messages.ts +9 -2
- package/src/daemon/message-types/notifications.ts +12 -0
- package/src/daemon/message-types/schedules.ts +1 -0
- package/src/daemon/message-types/settings.ts +18 -0
- package/src/daemon/profiler-run-store.ts +557 -0
- package/src/daemon/server.ts +87 -10
- package/src/daemon/shutdown-handlers.ts +5 -0
- package/src/daemon/tool-side-effects.ts +23 -3
- package/src/daemon/transport-hints.ts +33 -0
- package/src/export/transcript-formatter.ts +148 -0
- package/src/filing/filing-service.ts +228 -0
- package/src/heartbeat/heartbeat-service.ts +96 -7
- package/src/index.ts +1 -1
- package/src/mcp/client.ts +6 -0
- package/src/mcp/mcp-oauth-provider.ts +149 -27
- package/src/memory/admin.ts +33 -32
- package/src/memory/app-store.ts +69 -0
- package/src/memory/conversation-bootstrap.ts +1 -1
- package/src/memory/conversation-crud.ts +151 -117
- package/src/memory/conversation-directories.ts +39 -0
- package/src/memory/conversation-group-migration.ts +66 -6
- package/src/memory/conversation-queries.ts +58 -12
- package/src/memory/conversation-title-service.ts +1 -0
- package/src/memory/db-init.ts +182 -376
- package/src/memory/embedding-local.ts +1 -1
- package/src/memory/graph/bootstrap.ts +75 -66
- package/src/memory/graph/capability-seed.ts +167 -17
- package/src/memory/graph/consolidation.ts +38 -4
- package/src/memory/graph/conversation-graph-memory.ts +133 -104
- package/src/memory/graph/extraction-job.ts +9 -4
- package/src/memory/graph/extraction.ts +66 -23
- package/src/memory/graph/graph-memory-state-store.ts +37 -0
- package/src/memory/graph/graph-search.ts +29 -15
- package/src/memory/graph/injection.ts +38 -8
- package/src/memory/graph/inspect.ts +12 -3
- package/src/memory/graph/retriever.ts +365 -262
- package/src/memory/graph/store.test.ts +48 -0
- package/src/memory/graph/store.ts +150 -11
- package/src/memory/graph/tool-handlers.ts +84 -209
- package/src/memory/graph/tools.ts +8 -52
- package/src/memory/graph/types.ts +24 -0
- package/src/memory/group-crud.ts +25 -9
- package/src/memory/job-handlers/cleanup.ts +44 -1
- package/src/memory/jobs-store.ts +70 -60
- package/src/memory/jobs-worker.ts +44 -28
- package/src/memory/llm-request-log-store.ts +96 -12
- package/src/memory/memory-recall-log-store.ts +49 -5
- package/src/memory/migrations/203-drop-memory-items-tables.ts +33 -1
- package/src/memory/migrations/206-memory-graph-node-edits.ts +19 -0
- package/src/memory/migrations/206-scrub-corrupted-image-attachments.ts +131 -0
- package/src/memory/migrations/207-conversation-graph-memory-state.ts +20 -0
- package/src/memory/migrations/208-conversations-last-message-at.ts +35 -0
- package/src/memory/migrations/209-strip-thinking-from-consolidated.ts +85 -0
- package/src/memory/migrations/210-schedule-reuse-conversation.ts +13 -0
- package/src/memory/migrations/211-memory-recall-logs-query-context.ts +21 -0
- package/src/memory/migrations/212-llm-request-logs-created-at-index.ts +19 -0
- package/src/memory/migrations/index.ts +8 -0
- package/src/memory/migrations/registry.ts +8 -0
- package/src/memory/schema/conversations.ts +14 -0
- package/src/memory/schema/infrastructure.ts +8 -1
- package/src/memory/schema/memory-core.ts +0 -51
- package/src/memory/schema/memory-graph.ts +15 -0
- package/src/memory/task-memory-cleanup.ts +30 -11
- package/src/messaging/provider.ts +1 -1
- package/src/notifications/broadcaster.ts +6 -0
- package/src/notifications/conversation-pairing.ts +12 -4
- package/src/notifications/copy-composer.ts +86 -0
- package/src/notifications/decision-engine.ts +35 -0
- package/src/notifications/emit-signal.ts +14 -0
- package/src/notifications/signal.ts +11 -0
- package/src/oauth/platform-connection.test.ts +2 -2
- package/src/oauth/seed-providers.ts +1 -0
- package/src/permissions/checker.ts +15 -4
- package/src/permissions/defaults.ts +7 -8
- package/src/permissions/permission-mode-store.ts +180 -0
- package/src/permissions/permission-mode.ts +31 -0
- package/src/permissions/prompter.ts +0 -2
- package/src/permissions/workspace-policy.ts +9 -0
- package/src/platform/client.ts +1 -1
- package/src/prompts/system-prompt.ts +59 -7
- package/src/prompts/templates/BOOTSTRAP-REFERENCE.md +100 -0
- package/src/prompts/templates/BOOTSTRAP.md +76 -162
- package/src/prompts/templates/HEARTBEAT.md +3 -1
- package/src/prompts/templates/SOUL.md +30 -9
- package/src/prompts/templates/UPDATES.md +8 -0
- package/src/providers/anthropic/client.ts +107 -219
- package/src/runtime/assistant-event-hub.ts +22 -0
- package/src/runtime/auth/route-policy.ts +23 -0
- package/src/runtime/auth/token-service.ts +8 -0
- package/src/runtime/http-server.ts +32 -2
- package/src/runtime/http-types.ts +12 -1
- package/src/runtime/migrations/vbundle-builder.ts +389 -3
- package/src/runtime/migrations/vbundle-importer.ts +8 -6
- package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +378 -0
- package/src/runtime/routes/app-management-routes.ts +1 -11
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +26 -0
- package/src/runtime/routes/archive-utils.ts +29 -0
- package/src/runtime/routes/avatar-routes.ts +2 -9
- package/src/runtime/routes/btw-routes.ts +14 -1
- package/src/runtime/routes/conversation-analysis-routes.ts +185 -0
- package/src/runtime/routes/conversation-management-routes.ts +1 -14
- package/src/runtime/routes/conversation-query-routes.ts +49 -3
- package/src/runtime/routes/conversation-routes.ts +270 -44
- package/src/runtime/routes/group-routes.ts +22 -8
- package/src/runtime/routes/heartbeat-routes.ts +4 -10
- package/src/runtime/routes/identity-routes.ts +53 -18
- package/src/runtime/routes/llm-context-normalization.ts +14 -10
- package/src/runtime/routes/log-export/AGENTS.md +104 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist-error-contract.test.ts +103 -0
- package/src/runtime/routes/log-export/__tests__/workspace-allowlist.test.ts +716 -0
- package/src/runtime/routes/log-export/workspace-allowlist.ts +458 -0
- package/src/runtime/routes/log-export-routes.ts +41 -278
- package/src/runtime/routes/memory-item-routes.test.ts +168 -233
- package/src/runtime/routes/migration-routes.ts +18 -7
- package/src/runtime/routes/profiler-routes.ts +350 -0
- package/src/runtime/routes/schedule-routes.ts +27 -12
- package/src/runtime/routes/settings-routes.ts +95 -8
- package/src/runtime/routes/subagents-routes.ts +28 -7
- package/src/runtime/routes/user-route-dispatcher.ts +223 -0
- package/src/runtime/routes/user-routes.ts +41 -0
- package/src/runtime/routes/workspace-routes.ts +0 -1
- package/src/schedule/schedule-store.ts +30 -0
- package/src/schedule/scheduler.ts +45 -18
- package/src/skills/catalog-install.ts +10 -2
- package/src/skills/inline-command-runner.ts +12 -14
- package/src/skills/managed-store.ts +2 -2
- package/src/skills/skill-memory.ts +1 -293
- package/src/subagent/index.ts +13 -3
- package/src/subagent/manager.ts +308 -29
- package/src/subagent/types.ts +68 -0
- package/src/tasks/task-runner.ts +4 -4
- package/src/tools/apps/executors.ts +29 -4
- package/src/tools/filesystem/list.ts +93 -0
- package/src/tools/permission-checker.ts +78 -18
- package/src/tools/registry.ts +4 -0
- package/src/tools/schedule/create.ts +3 -0
- package/src/tools/schedule/list.ts +1 -0
- package/src/tools/schedule/update.ts +6 -0
- package/src/tools/secret-detection-handler.ts +0 -1
- package/src/tools/shared/filesystem/errors.ts +5 -0
- package/src/tools/shared/filesystem/file-ops-service.ts +90 -2
- package/src/tools/shared/filesystem/types.ts +17 -0
- package/src/tools/shared/shell-output.ts +31 -2
- package/src/tools/skills/sandbox-runner.ts +3 -6
- package/src/tools/subagent/abort.ts +12 -2
- package/src/tools/subagent/message.ts +9 -2
- package/src/tools/subagent/notify-parent.ts +79 -0
- package/src/tools/subagent/read.ts +29 -8
- package/src/tools/subagent/resolve.ts +21 -0
- package/src/tools/subagent/spawn.ts +2 -0
- package/src/tools/subagent/status.ts +11 -1
- package/src/tools/system/avatar-generator.ts +3 -3
- package/src/tools/system/register.ts +23 -0
- package/src/tools/system/set-permission-mode.ts +103 -0
- package/src/tools/terminal/parser.ts +30 -5
- package/src/tools/terminal/safe-env.ts +16 -1
- package/src/tools/terminal/sandbox-diagnostics.ts +4 -4
- package/src/tools/terminal/sandbox.ts +4 -1
- package/src/tools/terminal/shell.ts +3 -5
- package/src/tools/tool-manifest.ts +6 -0
- package/src/tools/types.ts +2 -3
- package/src/util/logger.ts +1 -1
- package/src/util/platform.ts +50 -17
- package/src/watcher/provider-types.ts +1 -1
- package/src/workspace/migrations/023-move-config-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/024-move-runtime-files-to-workspace.ts +2 -2
- package/src/workspace/migrations/028-recover-conversations-from-disk-view.ts +270 -0
- package/src/workspace/migrations/029-seed-pkb.ts +85 -0
- package/src/workspace/migrations/030-seed-pkb-autoinject.ts +73 -0
- package/src/workspace/migrations/registry.ts +6 -0
- package/src/workspace/top-level-renderer.ts +5 -9
- package/src/__tests__/cli-memory.test.ts +0 -377
- package/src/__tests__/clipboard.test.ts +0 -88
- package/src/cli/cli-memory.ts +0 -179
- package/src/util/clipboard.ts +0 -34
|
@@ -37,15 +37,15 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
37
37
|
|
|
38
38
|
test("claims embed jobs when circuit breaker is closed (healthy)", () => {
|
|
39
39
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
40
|
-
enqueueMemoryJob("
|
|
41
|
-
enqueueMemoryJob("
|
|
40
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
|
|
41
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
42
42
|
|
|
43
43
|
const claimed = claimMemoryJobs(10);
|
|
44
44
|
const types = claimed.map((j) => j.type);
|
|
45
45
|
|
|
46
46
|
expect(types).toContain("embed_segment");
|
|
47
|
-
expect(types).toContain("
|
|
48
|
-
expect(types).toContain("
|
|
47
|
+
expect(types).toContain("embed_graph_node");
|
|
48
|
+
expect(types).toContain("graph_extract");
|
|
49
49
|
expect(claimed).toHaveLength(3);
|
|
50
50
|
});
|
|
51
51
|
|
|
@@ -62,9 +62,9 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
65
|
-
enqueueMemoryJob("
|
|
65
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-1" });
|
|
66
66
|
enqueueMemoryJob("embed_summary", { summaryId: "sum-1" });
|
|
67
|
-
enqueueMemoryJob("
|
|
67
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
68
68
|
enqueueMemoryJob("build_conversation_summary", {
|
|
69
69
|
conversationId: "conv-1",
|
|
70
70
|
});
|
|
@@ -73,10 +73,10 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
73
73
|
const types = claimed.map((j) => j.type);
|
|
74
74
|
|
|
75
75
|
// Only non-embed jobs should be claimed
|
|
76
|
-
expect(types).toContain("
|
|
76
|
+
expect(types).toContain("graph_extract");
|
|
77
77
|
expect(types).toContain("build_conversation_summary");
|
|
78
78
|
expect(types).not.toContain("embed_segment");
|
|
79
|
-
expect(types).not.toContain("
|
|
79
|
+
expect(types).not.toContain("embed_graph_node");
|
|
80
80
|
expect(types).not.toContain("embed_summary");
|
|
81
81
|
expect(claimed).toHaveLength(2);
|
|
82
82
|
});
|
|
@@ -95,7 +95,7 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
95
95
|
|
|
96
96
|
// Verify embed jobs are skipped while open
|
|
97
97
|
enqueueMemoryJob("embed_segment", { segmentId: "seg-1" });
|
|
98
|
-
enqueueMemoryJob("
|
|
98
|
+
enqueueMemoryJob("graph_extract", { conversationId: "conv-1" });
|
|
99
99
|
|
|
100
100
|
const claimedWhileOpen = claimMemoryJobs(10);
|
|
101
101
|
expect(claimedWhileOpen.map((j) => j.type)).not.toContain("embed_segment");
|
|
@@ -104,21 +104,22 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
104
104
|
_resetQdrantBreaker();
|
|
105
105
|
|
|
106
106
|
// Re-enqueue an embed job (the previous one is now "running")
|
|
107
|
-
enqueueMemoryJob("
|
|
107
|
+
enqueueMemoryJob("embed_graph_node", { nodeId: "node-2" });
|
|
108
108
|
|
|
109
109
|
const claimedAfterClose = claimMemoryJobs(10);
|
|
110
110
|
const types = claimedAfterClose.map((j) => j.type);
|
|
111
111
|
|
|
112
|
-
expect(types).toContain("
|
|
112
|
+
expect(types).toContain("embed_graph_node");
|
|
113
113
|
});
|
|
114
114
|
|
|
115
115
|
test("all embed job types are skipped when breaker is open", async () => {
|
|
116
116
|
const embedTypes: MemoryJobType[] = [
|
|
117
117
|
"embed_segment",
|
|
118
|
-
"embed_item",
|
|
119
118
|
"embed_summary",
|
|
120
119
|
"embed_media",
|
|
121
120
|
"embed_attachment",
|
|
121
|
+
"embed_graph_node",
|
|
122
|
+
"graph_trigger_embed",
|
|
122
123
|
];
|
|
123
124
|
|
|
124
125
|
// Trip the circuit breaker
|
|
@@ -137,13 +138,13 @@ describe("claimMemoryJobs with Qdrant circuit breaker", () => {
|
|
|
137
138
|
enqueueMemoryJob(type, { id: `test-${type}` });
|
|
138
139
|
}
|
|
139
140
|
// Also enqueue a non-embed job
|
|
140
|
-
enqueueMemoryJob("
|
|
141
|
+
enqueueMemoryJob("graph_consolidate", { conversationId: "conv-1" });
|
|
141
142
|
|
|
142
143
|
const claimed = claimMemoryJobs(20);
|
|
143
144
|
const types = claimed.map((j) => j.type);
|
|
144
145
|
|
|
145
146
|
// Only the non-embed job should be claimed
|
|
146
147
|
expect(claimed).toHaveLength(1);
|
|
147
|
-
expect(types).toEqual(["
|
|
148
|
+
expect(types).toEqual(["graph_consolidate"]);
|
|
148
149
|
});
|
|
149
150
|
});
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for handleListMessages tool_result merging.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that tool_result blocks from user messages are merged into the
|
|
5
|
+
* preceding assistant message so they render with proper tool names instead
|
|
6
|
+
* of "Unknown" after a conversation reload.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { beforeEach, describe, expect, mock, test } from "bun:test";
|
|
10
|
+
|
|
11
|
+
mock.module("../util/logger.js", () => ({
|
|
12
|
+
getLogger: () =>
|
|
13
|
+
new Proxy({} as Record<string, unknown>, {
|
|
14
|
+
get: () => () => {},
|
|
15
|
+
}),
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
mock.module("../config/loader.js", () => ({
|
|
19
|
+
getConfig: () => ({
|
|
20
|
+
ui: {},
|
|
21
|
+
model: "test",
|
|
22
|
+
provider: "test",
|
|
23
|
+
memory: { enabled: false },
|
|
24
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
25
|
+
}),
|
|
26
|
+
}));
|
|
27
|
+
|
|
28
|
+
import { addMessage, createConversation } from "../memory/conversation-crud.js";
|
|
29
|
+
import { getDb, initializeDb } from "../memory/db.js";
|
|
30
|
+
import { handleListMessages } from "../runtime/routes/conversation-routes.js";
|
|
31
|
+
|
|
32
|
+
initializeDb();
|
|
33
|
+
|
|
34
|
+
function resetTables() {
|
|
35
|
+
const db = getDb();
|
|
36
|
+
db.run("DELETE FROM message_attachments");
|
|
37
|
+
db.run("DELETE FROM attachments");
|
|
38
|
+
db.run("DELETE FROM messages");
|
|
39
|
+
db.run("DELETE FROM conversations");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createTestUrl(conversationId: string): URL {
|
|
43
|
+
return new URL(
|
|
44
|
+
`http://localhost/v1/messages?conversationId=${conversationId}`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ToolCallPayload {
|
|
49
|
+
name: string;
|
|
50
|
+
input: Record<string, unknown>;
|
|
51
|
+
result?: string;
|
|
52
|
+
isError?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface MessagePayload {
|
|
56
|
+
role: string;
|
|
57
|
+
content: string;
|
|
58
|
+
toolCalls?: ToolCallPayload[];
|
|
59
|
+
textSegments?: string[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
describe("handleListMessages tool_result merging", () => {
|
|
63
|
+
beforeEach(resetTables);
|
|
64
|
+
|
|
65
|
+
test("merges tool_result from user message into preceding assistant", async () => {
|
|
66
|
+
const conv = createConversation();
|
|
67
|
+
// User prompt
|
|
68
|
+
await addMessage(
|
|
69
|
+
conv.id,
|
|
70
|
+
"user",
|
|
71
|
+
JSON.stringify([{ type: "text", text: "run ls" }]),
|
|
72
|
+
);
|
|
73
|
+
// Assistant with tool_use
|
|
74
|
+
await addMessage(
|
|
75
|
+
conv.id,
|
|
76
|
+
"assistant",
|
|
77
|
+
JSON.stringify([
|
|
78
|
+
{ type: "text", text: "Running command." },
|
|
79
|
+
{ type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
// Tool result (separate user message)
|
|
83
|
+
await addMessage(
|
|
84
|
+
conv.id,
|
|
85
|
+
"user",
|
|
86
|
+
JSON.stringify([
|
|
87
|
+
{ type: "tool_result", tool_use_id: "tu1", content: "file1.txt\nfile2.txt" },
|
|
88
|
+
]),
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
92
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
93
|
+
|
|
94
|
+
// Should be 2 messages: user prompt + assistant (tool_result user msg suppressed)
|
|
95
|
+
expect(body.messages).toHaveLength(2);
|
|
96
|
+
expect(body.messages[0].role).toBe("user");
|
|
97
|
+
expect(body.messages[1].role).toBe("assistant");
|
|
98
|
+
|
|
99
|
+
// Assistant tool call should have proper name AND result
|
|
100
|
+
const toolCalls = body.messages[1].toolCalls;
|
|
101
|
+
expect(toolCalls).toBeDefined();
|
|
102
|
+
expect(toolCalls).toHaveLength(1);
|
|
103
|
+
expect(toolCalls![0].name).toBe("bash");
|
|
104
|
+
expect(toolCalls![0].result).toBe("file1.txt\nfile2.txt");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("merges multiple tool_results into matching tool_uses", async () => {
|
|
108
|
+
const conv = createConversation();
|
|
109
|
+
await addMessage(
|
|
110
|
+
conv.id,
|
|
111
|
+
"user",
|
|
112
|
+
JSON.stringify([{ type: "text", text: "do stuff" }]),
|
|
113
|
+
);
|
|
114
|
+
await addMessage(
|
|
115
|
+
conv.id,
|
|
116
|
+
"assistant",
|
|
117
|
+
JSON.stringify([
|
|
118
|
+
{ type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
|
|
119
|
+
{ type: "text", text: "and also" },
|
|
120
|
+
{ type: "tool_use", id: "tu2", name: "file_read", input: { path: "/tmp/a" } },
|
|
121
|
+
]),
|
|
122
|
+
);
|
|
123
|
+
await addMessage(
|
|
124
|
+
conv.id,
|
|
125
|
+
"user",
|
|
126
|
+
JSON.stringify([
|
|
127
|
+
{ type: "tool_result", tool_use_id: "tu1", content: "dir listing" },
|
|
128
|
+
{ type: "tool_result", tool_use_id: "tu2", content: "file contents" },
|
|
129
|
+
]),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
133
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
134
|
+
|
|
135
|
+
expect(body.messages).toHaveLength(2);
|
|
136
|
+
const toolCalls = body.messages[1].toolCalls!;
|
|
137
|
+
expect(toolCalls).toHaveLength(2);
|
|
138
|
+
expect(toolCalls[0].name).toBe("bash");
|
|
139
|
+
expect(toolCalls[0].result).toBe("dir listing");
|
|
140
|
+
expect(toolCalls[1].name).toBe("file_read");
|
|
141
|
+
expect(toolCalls[1].result).toBe("file contents");
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("plain user message passes through unchanged", async () => {
|
|
145
|
+
const conv = createConversation();
|
|
146
|
+
await addMessage(
|
|
147
|
+
conv.id,
|
|
148
|
+
"user",
|
|
149
|
+
JSON.stringify([{ type: "text", text: "hello" }]),
|
|
150
|
+
);
|
|
151
|
+
await addMessage(
|
|
152
|
+
conv.id,
|
|
153
|
+
"assistant",
|
|
154
|
+
JSON.stringify([{ type: "text", text: "hi there" }]),
|
|
155
|
+
);
|
|
156
|
+
await addMessage(
|
|
157
|
+
conv.id,
|
|
158
|
+
"user",
|
|
159
|
+
JSON.stringify([{ type: "text", text: "how are you?" }]),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
163
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
164
|
+
|
|
165
|
+
expect(body.messages).toHaveLength(3);
|
|
166
|
+
expect(body.messages[2].role).toBe("user");
|
|
167
|
+
expect(body.messages[2].content).toBe("how are you?");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("tool_result at start of array (no preceding assistant) is preserved", async () => {
|
|
171
|
+
const conv = createConversation();
|
|
172
|
+
// Orphan tool_result with no preceding assistant (pagination boundary).
|
|
173
|
+
// The preceding assistant tool_use lives in the previous page — dropping
|
|
174
|
+
// the result would be unrecoverable, so it is kept as-is.
|
|
175
|
+
await addMessage(
|
|
176
|
+
conv.id,
|
|
177
|
+
"user",
|
|
178
|
+
JSON.stringify([
|
|
179
|
+
{ type: "tool_result", tool_use_id: "tu_orphan", content: "stale result" },
|
|
180
|
+
]),
|
|
181
|
+
);
|
|
182
|
+
await addMessage(
|
|
183
|
+
conv.id,
|
|
184
|
+
"assistant",
|
|
185
|
+
JSON.stringify([{ type: "text", text: "response" }]),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
189
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
190
|
+
|
|
191
|
+
// Orphan tool_result is preserved (not suppressed) to avoid data loss
|
|
192
|
+
expect(body.messages).toHaveLength(2);
|
|
193
|
+
expect(body.messages[0].role).toBe("user");
|
|
194
|
+
// The preserved message must retain the actual tool_result payload
|
|
195
|
+
const orphanToolCalls = body.messages[0].toolCalls;
|
|
196
|
+
expect(orphanToolCalls).toBeDefined();
|
|
197
|
+
expect(orphanToolCalls).toHaveLength(1);
|
|
198
|
+
expect(orphanToolCalls![0].result).toBe("stale result");
|
|
199
|
+
expect(body.messages[1].role).toBe("assistant");
|
|
200
|
+
expect(body.messages[1].content).toBe("response");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("multi-turn: each tool_result merges into correct assistant", async () => {
|
|
204
|
+
const conv = createConversation();
|
|
205
|
+
// Turn 1
|
|
206
|
+
await addMessage(
|
|
207
|
+
conv.id,
|
|
208
|
+
"user",
|
|
209
|
+
JSON.stringify([{ type: "text", text: "list files" }]),
|
|
210
|
+
);
|
|
211
|
+
await addMessage(
|
|
212
|
+
conv.id,
|
|
213
|
+
"assistant",
|
|
214
|
+
JSON.stringify([
|
|
215
|
+
{ type: "tool_use", id: "tu1", name: "bash", input: { command: "ls" } },
|
|
216
|
+
]),
|
|
217
|
+
);
|
|
218
|
+
await addMessage(
|
|
219
|
+
conv.id,
|
|
220
|
+
"user",
|
|
221
|
+
JSON.stringify([
|
|
222
|
+
{ type: "tool_result", tool_use_id: "tu1", content: "files" },
|
|
223
|
+
]),
|
|
224
|
+
);
|
|
225
|
+
// Turn 2
|
|
226
|
+
await addMessage(
|
|
227
|
+
conv.id,
|
|
228
|
+
"assistant",
|
|
229
|
+
JSON.stringify([
|
|
230
|
+
{ type: "text", text: "Now reading:" },
|
|
231
|
+
{ type: "tool_use", id: "tu2", name: "file_read", input: { path: "/x" } },
|
|
232
|
+
]),
|
|
233
|
+
);
|
|
234
|
+
await addMessage(
|
|
235
|
+
conv.id,
|
|
236
|
+
"user",
|
|
237
|
+
JSON.stringify([
|
|
238
|
+
{ type: "tool_result", tool_use_id: "tu2", content: "file data" },
|
|
239
|
+
]),
|
|
240
|
+
);
|
|
241
|
+
// Turn 3: real user message
|
|
242
|
+
await addMessage(
|
|
243
|
+
conv.id,
|
|
244
|
+
"user",
|
|
245
|
+
JSON.stringify([{ type: "text", text: "thanks" }]),
|
|
246
|
+
);
|
|
247
|
+
|
|
248
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
249
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
250
|
+
|
|
251
|
+
// user("list files"), assistant(bash), assistant(file_read), user("thanks")
|
|
252
|
+
expect(body.messages).toHaveLength(4);
|
|
253
|
+
expect(body.messages[0].role).toBe("user");
|
|
254
|
+
expect(body.messages[1].role).toBe("assistant");
|
|
255
|
+
expect(body.messages[1].toolCalls![0].name).toBe("bash");
|
|
256
|
+
expect(body.messages[1].toolCalls![0].result).toBe("files");
|
|
257
|
+
expect(body.messages[2].role).toBe("assistant");
|
|
258
|
+
expect(body.messages[2].toolCalls![0].name).toBe("file_read");
|
|
259
|
+
expect(body.messages[2].toolCalls![0].result).toBe("file data");
|
|
260
|
+
expect(body.messages[3].role).toBe("user");
|
|
261
|
+
expect(body.messages[3].content).toBe("thanks");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("tool_result with is_error propagates error status", async () => {
|
|
265
|
+
const conv = createConversation();
|
|
266
|
+
await addMessage(
|
|
267
|
+
conv.id,
|
|
268
|
+
"user",
|
|
269
|
+
JSON.stringify([{ type: "text", text: "do it" }]),
|
|
270
|
+
);
|
|
271
|
+
await addMessage(
|
|
272
|
+
conv.id,
|
|
273
|
+
"assistant",
|
|
274
|
+
JSON.stringify([
|
|
275
|
+
{ type: "tool_use", id: "tu1", name: "bash", input: { command: "fail" } },
|
|
276
|
+
]),
|
|
277
|
+
);
|
|
278
|
+
await addMessage(
|
|
279
|
+
conv.id,
|
|
280
|
+
"user",
|
|
281
|
+
JSON.stringify([
|
|
282
|
+
{
|
|
283
|
+
type: "tool_result",
|
|
284
|
+
tool_use_id: "tu1",
|
|
285
|
+
content: "command not found",
|
|
286
|
+
is_error: true,
|
|
287
|
+
},
|
|
288
|
+
]),
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
const response = handleListMessages(createTestUrl(conv.id), null);
|
|
292
|
+
const body = (await response.json()) as { messages: MessagePayload[] };
|
|
293
|
+
|
|
294
|
+
expect(body.messages).toHaveLength(2);
|
|
295
|
+
const tc = body.messages[1].toolCalls![0];
|
|
296
|
+
expect(tc.name).toBe("bash");
|
|
297
|
+
expect(tc.result).toBe("command not found");
|
|
298
|
+
expect(tc.isError).toBe(true);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
@@ -278,12 +278,6 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
278
278
|
role: "user",
|
|
279
279
|
text: "Find the latest changelog.",
|
|
280
280
|
},
|
|
281
|
-
{
|
|
282
|
-
kind: "message",
|
|
283
|
-
label: "Assistant message 2",
|
|
284
|
-
role: "assistant",
|
|
285
|
-
text: "Checking sources.",
|
|
286
|
-
},
|
|
287
281
|
{
|
|
288
282
|
kind: "reasoning",
|
|
289
283
|
label: "Assistant message 2 reasoning",
|
|
@@ -296,6 +290,12 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
296
290
|
role: "assistant",
|
|
297
291
|
text: "[redacted thinking]",
|
|
298
292
|
},
|
|
293
|
+
{
|
|
294
|
+
kind: "message",
|
|
295
|
+
label: "Assistant message 2",
|
|
296
|
+
role: "assistant",
|
|
297
|
+
text: "Checking sources.",
|
|
298
|
+
},
|
|
299
299
|
{
|
|
300
300
|
kind: "tool_use",
|
|
301
301
|
label: "Assistant message 2 tool use",
|
|
@@ -331,12 +331,6 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
331
331
|
},
|
|
332
332
|
]);
|
|
333
333
|
expect(normalized.responseSections).toEqual([
|
|
334
|
-
{
|
|
335
|
-
kind: "message",
|
|
336
|
-
label: "Assistant response",
|
|
337
|
-
role: "assistant",
|
|
338
|
-
text: "I found the changelog.",
|
|
339
|
-
},
|
|
340
334
|
{
|
|
341
335
|
kind: "reasoning",
|
|
342
336
|
label: "Assistant response reasoning",
|
|
@@ -349,6 +343,12 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
349
343
|
role: "assistant",
|
|
350
344
|
text: "[redacted thinking]",
|
|
351
345
|
},
|
|
346
|
+
{
|
|
347
|
+
kind: "message",
|
|
348
|
+
label: "Assistant response",
|
|
349
|
+
role: "assistant",
|
|
350
|
+
text: "I found the changelog.",
|
|
351
|
+
},
|
|
352
352
|
{
|
|
353
353
|
kind: "tool_use",
|
|
354
354
|
label: "Assistant response tool use",
|
|
@@ -412,12 +412,6 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
412
412
|
toolCallNames: undefined,
|
|
413
413
|
});
|
|
414
414
|
expect(normalized.responseSections).toEqual([
|
|
415
|
-
{
|
|
416
|
-
kind: "message",
|
|
417
|
-
label: "Assistant response",
|
|
418
|
-
role: "assistant",
|
|
419
|
-
text: "The answer is 42.",
|
|
420
|
-
},
|
|
421
415
|
{
|
|
422
416
|
kind: "reasoning",
|
|
423
417
|
label: "Assistant response reasoning",
|
|
@@ -430,6 +424,12 @@ describe("normalizeLlmContextPayloads", () => {
|
|
|
430
424
|
role: "assistant",
|
|
431
425
|
text: "[redacted thinking]",
|
|
432
426
|
},
|
|
427
|
+
{
|
|
428
|
+
kind: "message",
|
|
429
|
+
label: "Assistant response",
|
|
430
|
+
role: "assistant",
|
|
431
|
+
text: "The answer is 42.",
|
|
432
|
+
},
|
|
433
433
|
]);
|
|
434
434
|
});
|
|
435
435
|
|
|
@@ -33,6 +33,27 @@ function dispatchLlmContext(messageId: string): Promise<Response> | Response {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
function dispatchLogPayload(logId: string): Promise<Response> | Response {
|
|
37
|
+
const url = new URL(
|
|
38
|
+
`http://localhost/v1/llm-request-logs/${logId}/payload`,
|
|
39
|
+
);
|
|
40
|
+
const route = routes.find(
|
|
41
|
+
(r) =>
|
|
42
|
+
r.method === "GET" && r.endpoint === "llm-request-logs/:id/payload",
|
|
43
|
+
);
|
|
44
|
+
if (!route) {
|
|
45
|
+
throw new Error("No llm-request-logs payload route found");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return route.handler({
|
|
49
|
+
req: new Request(url.toString(), { method: "GET" }),
|
|
50
|
+
url,
|
|
51
|
+
server: null as never,
|
|
52
|
+
authContext: {} as never,
|
|
53
|
+
params: { id: logId },
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
function clearRequestLogs(): void {
|
|
37
58
|
getDb().delete(llmRequestLogs).run();
|
|
38
59
|
}
|
|
@@ -189,4 +210,84 @@ describe("GET /v1/messages/:id/llm-context provider preference", () => {
|
|
|
189
210
|
expect(body.logs).toHaveLength(1);
|
|
190
211
|
expect(body.logs[0]?.summary).toEqual({ provider: "ollama" });
|
|
191
212
|
});
|
|
213
|
+
|
|
214
|
+
test("returns null payloads to keep the initial response lightweight", async () => {
|
|
215
|
+
seedRequestLog({
|
|
216
|
+
id: "log-null-payload",
|
|
217
|
+
messageId: "msg-null-payload",
|
|
218
|
+
provider: "openrouter",
|
|
219
|
+
requestPayload: openAiRequestPayload,
|
|
220
|
+
responsePayload: openAiResponsePayload,
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
const response = await dispatchLlmContext("msg-null-payload");
|
|
224
|
+
expect(response.status).toBe(200);
|
|
225
|
+
|
|
226
|
+
const body = (await response.json()) as {
|
|
227
|
+
logs: Array<{
|
|
228
|
+
requestPayload: unknown;
|
|
229
|
+
responsePayload: unknown;
|
|
230
|
+
}>;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
expect(body.logs).toHaveLength(1);
|
|
234
|
+
expect(body.logs[0]?.requestPayload).toBeNull();
|
|
235
|
+
expect(body.logs[0]?.responsePayload).toBeNull();
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
describe("GET /v1/llm-request-logs/:id/payload", () => {
|
|
240
|
+
test("returns parsed payloads for a valid log", async () => {
|
|
241
|
+
const reqPayload = JSON.stringify({ model: "gpt-4.1", messages: [] });
|
|
242
|
+
const resPayload = JSON.stringify({
|
|
243
|
+
choices: [{ message: { content: "hi" } }],
|
|
244
|
+
});
|
|
245
|
+
seedRequestLog({
|
|
246
|
+
id: "log-payload-ok",
|
|
247
|
+
messageId: "msg-payload-ok",
|
|
248
|
+
provider: "openai",
|
|
249
|
+
requestPayload: reqPayload,
|
|
250
|
+
responsePayload: resPayload,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const response = await dispatchLogPayload("log-payload-ok");
|
|
254
|
+
expect(response.status).toBe(200);
|
|
255
|
+
|
|
256
|
+
const body = (await response.json()) as {
|
|
257
|
+
id: string;
|
|
258
|
+
requestPayload: unknown;
|
|
259
|
+
responsePayload: unknown;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
expect(body.id).toBe("log-payload-ok");
|
|
263
|
+
expect(body.requestPayload).toEqual(JSON.parse(reqPayload));
|
|
264
|
+
expect(body.responsePayload).toEqual(JSON.parse(resPayload));
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
test("returns 404 for a nonexistent log", async () => {
|
|
268
|
+
const response = await dispatchLogPayload("does-not-exist");
|
|
269
|
+
expect(response.status).toBe(404);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
test("falls back to string values for non-JSON payloads", async () => {
|
|
273
|
+
seedRequestLog({
|
|
274
|
+
id: "log-raw-strings",
|
|
275
|
+
messageId: "msg-raw-strings",
|
|
276
|
+
provider: null,
|
|
277
|
+
requestPayload: "raw-request-text",
|
|
278
|
+
responsePayload: "raw-response-text",
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
const response = await dispatchLogPayload("log-raw-strings");
|
|
282
|
+
expect(response.status).toBe(200);
|
|
283
|
+
|
|
284
|
+
const body = (await response.json()) as {
|
|
285
|
+
id: string;
|
|
286
|
+
requestPayload: unknown;
|
|
287
|
+
responsePayload: unknown;
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
expect(body.requestPayload).toBe("raw-request-text");
|
|
291
|
+
expect(body.responsePayload).toBe("raw-response-text");
|
|
292
|
+
});
|
|
192
293
|
});
|