@vellumai/assistant 0.5.0 → 0.5.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/ARCHITECTURE.md +54 -54
- package/docs/architecture/integrations.md +62 -67
- package/docs/credential-execution-service.md +3 -3
- package/package.json +1 -1
- package/src/__tests__/agent-loop.test.ts +111 -0
- package/src/__tests__/always-loaded-tools-guard.test.ts +3 -4
- package/src/__tests__/app-builder-tool-scripts.test.ts +13 -151
- package/src/__tests__/app-dir-path-guard.test.ts +78 -0
- package/src/__tests__/app-executors.test.ts +1 -291
- package/src/__tests__/app-git-history.test.ts +4 -4
- package/src/__tests__/app-routes-csp.test.ts +1 -0
- package/src/__tests__/app-store-dir-names.test.ts +426 -0
- package/src/__tests__/assistant-feature-flags-integration.test.ts +7 -9
- package/src/__tests__/attachments-store.test.ts +169 -21
- package/src/__tests__/attachments.test.ts +115 -1
- package/src/__tests__/btw-routes.test.ts +1 -0
- package/src/__tests__/canonical-guardian-store.test.ts +38 -0
- package/src/__tests__/channel-reply-delivery.test.ts +55 -0
- package/src/__tests__/checker.test.ts +54 -0
- package/src/__tests__/claude-code-skill-regression.test.ts +2 -0
- package/src/__tests__/claude-code-tool-profiles.test.ts +2 -0
- package/src/__tests__/compaction.benchmark.test.ts +2 -1
- package/src/__tests__/config-schema-cmd.test.ts +68 -21
- package/src/__tests__/config-schema.test.ts +1 -1
- package/src/__tests__/conversation-agent-loop-overflow.test.ts +149 -5
- package/src/__tests__/conversation-agent-loop.test.ts +290 -2
- package/src/__tests__/conversation-attachments.test.ts +17 -19
- package/src/__tests__/conversation-disk-view-integration.test.ts +277 -0
- package/src/__tests__/conversation-disk-view.test.ts +810 -0
- package/src/__tests__/conversation-error.test.ts +1 -1
- package/src/__tests__/conversation-fork-crud.test.ts +551 -0
- package/src/__tests__/conversation-fork-route.test.ts +386 -0
- package/src/__tests__/conversation-history-web-search.test.ts +1 -1
- package/src/__tests__/conversation-key-store-disk-view.test.ts +130 -0
- package/src/__tests__/conversation-media-retry.test.ts +8 -2
- package/src/__tests__/conversation-queue.test.ts +36 -1
- package/src/__tests__/conversation-routes-disk-view.test.ts +439 -0
- package/src/__tests__/conversation-routes-guardian-reply.test.ts +2 -2
- package/src/__tests__/conversation-routes-slash-commands.test.ts +2 -7
- package/src/__tests__/conversation-runtime-assembly.test.ts +17 -2
- package/src/__tests__/conversation-skill-tools.test.ts +4 -9
- package/src/__tests__/conversation-slash-commands.test.ts +149 -0
- package/src/__tests__/conversation-store.test.ts +24 -21
- package/src/__tests__/conversation-surfaces-state-update.test.ts +246 -0
- package/src/__tests__/conversation-surfaces-task-progress.test.ts +1 -0
- package/src/__tests__/conversation-title-service.test.ts +137 -0
- package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +25 -315
- package/src/__tests__/conversation-tool-setup-memory-scope.test.ts +1 -0
- package/src/__tests__/conversation-tool-setup-side-effect-flag.test.ts +1 -0
- package/src/__tests__/conversation-workspace-cache-state.test.ts +44 -2
- package/src/__tests__/conversation-workspace-injection.test.ts +11 -0
- package/src/__tests__/credential-execution-feature-gates.test.ts +3 -3
- package/src/__tests__/credential-security-invariants.test.ts +3 -0
- package/src/__tests__/credential-vault-unit.test.ts +5 -10
- package/src/__tests__/cu-unified-flow.test.ts +1 -0
- package/src/__tests__/db-conversation-fork-lineage-migration.test.ts +241 -0
- package/src/__tests__/db-llm-request-log-provider-migration.test.ts +214 -0
- package/src/__tests__/diagnostics-export.test.ts +70 -1
- package/src/__tests__/filesystem-tools.test.ts +4 -2
- package/src/__tests__/first-greeting.test.ts +80 -0
- package/src/__tests__/gateway-only-guard.test.ts +1 -0
- package/src/__tests__/handlers-user-message-approval-consumption.test.ts +3 -7
- package/src/__tests__/history-repair.test.ts +103 -10
- package/src/__tests__/http-conversation-lineage.test.ts +251 -0
- package/src/__tests__/image-source-path-reinject.test.ts +136 -0
- package/src/__tests__/llm-context-normalization.test.ts +1116 -0
- package/src/__tests__/llm-context-route-provider.test.ts +217 -0
- package/src/__tests__/llm-request-log-turn-query.test.ts +270 -0
- package/src/__tests__/media-generate-image.test.ts +47 -94
- package/src/__tests__/memory-lifecycle-e2e.test.ts +3 -1
- package/src/__tests__/memory-recall-quality.test.ts +5 -5
- package/src/__tests__/migration-cross-version-compatibility.test.ts +4 -1
- package/src/__tests__/migration-export-http.test.ts +3 -1
- package/src/__tests__/migration-import-commit-http.test.ts +18 -4
- package/src/__tests__/migration-import-preflight-http.test.ts +1 -3
- package/src/__tests__/mime-builder.test.ts +3 -2
- package/src/__tests__/non-member-access-request.test.ts +12 -1
- package/src/__tests__/notification-decision-identity.test.ts +52 -0
- package/src/__tests__/oauth-apps-routes.test.ts +103 -0
- package/src/__tests__/oauth-store.test.ts +115 -0
- package/src/__tests__/provider-error-scenarios.test.ts +1 -3
- package/src/__tests__/provider-failover-actual-provider.test.ts +66 -0
- package/src/__tests__/recording-handler.test.ts +17 -0
- package/src/__tests__/registry.test.ts +3 -8
- package/src/__tests__/relay-server.test.ts +1 -1
- package/src/__tests__/runtime-attachment-metadata.test.ts +7 -3
- package/src/__tests__/schema-transforms.test.ts +165 -5
- package/src/__tests__/server-history-render.test.ts +2 -2
- package/src/__tests__/skill-feature-flags-integration.test.ts +18 -17
- package/src/__tests__/skill-feature-flags.test.ts +13 -13
- package/src/__tests__/skill-load-feature-flag.test.ts +4 -4
- package/src/__tests__/slack-app-setup-skill-regression.test.ts +3 -1
- package/src/__tests__/slack-inbound-verification.test.ts +2 -2
- package/src/__tests__/starter-task-flow.test.ts +1 -0
- package/src/__tests__/suggestion-routes.test.ts +443 -0
- package/src/__tests__/swarm-conversation-integration.test.ts +1 -0
- package/src/__tests__/swarm-recursion.test.ts +1 -0
- package/src/__tests__/swarm-tool.test.ts +1 -0
- package/src/__tests__/system-prompt.test.ts +8 -0
- package/src/__tests__/tool-execution-abort-cleanup.test.ts +1 -0
- package/src/__tests__/tool-preview-lifecycle.test.ts +32 -5
- package/src/__tests__/top-level-renderer.test.ts +22 -0
- package/src/__tests__/turn-boundary-resolution.test.ts +243 -0
- package/src/__tests__/web-fetch.test.ts +6 -2
- package/src/__tests__/workspace-migration-006-services-config.test.ts +335 -0
- package/src/__tests__/workspace-migration-007-web-search-provider-rename.test.ts +312 -0
- package/src/__tests__/workspace-migration-009-backfill-conversation-disk-view.test.ts +278 -0
- package/src/__tests__/workspace-migration-010-app-dir-rename.test.ts +275 -0
- package/src/__tests__/workspace-migration-012-rename-conversation-disk-view-dirs.test.ts +77 -0
- package/src/__tests__/workspace-migration-013-repair-conversation-disk-view.test.ts +401 -0
- package/src/__tests__/workspace-migration-backfill-installation-id.test.ts +328 -0
- package/src/__tests__/workspace-migration-seed-device-id.test.ts +6 -10
- package/src/agent/attachments.ts +27 -1
- package/src/agent/loop.ts +29 -1
- package/src/avatar/traits-png-sync.ts +80 -25
- package/src/bundler/app-bundler.ts +4 -4
- package/src/calls/call-domain.ts +1 -0
- package/src/calls/voice-session-bridge.ts +1 -0
- package/src/cli/commands/auth.ts +92 -0
- package/src/cli/commands/avatar.ts +7 -6
- package/src/cli/commands/config.ts +2 -0
- package/src/cli/commands/oauth/providers.ts +29 -0
- package/src/cli/program.ts +12 -0
- package/src/cli.ts +15 -48
- package/src/config/bundled-skills/app-builder/SKILL.md +103 -28
- package/src/config/bundled-skills/app-builder/TOOLS.json +5 -199
- package/src/config/bundled-skills/app-builder/tools/{app-query.ts → app-refresh.ts} +2 -2
- package/src/config/bundled-skills/contacts/tools/google-contacts.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-archive.ts +6 -9
- package/src/config/bundled-skills/gmail/tools/gmail-attachments.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-filters.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-follow-up.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-forward.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-label.ts +4 -6
- package/src/config/bundled-skills/gmail/tools/gmail-outreach-scan.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-send-draft.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-sender-digest.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-trash.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-unsubscribe.ts +2 -3
- package/src/config/bundled-skills/gmail/tools/gmail-vacation.ts +2 -3
- package/src/config/bundled-skills/google-calendar/tools/shared.ts +1 -1
- package/src/config/bundled-skills/image-studio/SKILL.md +2 -2
- package/src/config/bundled-skills/image-studio/TOOLS.json +2 -2
- package/src/config/bundled-skills/image-studio/tools/media-generate-image.ts +45 -72
- package/src/config/bundled-skills/media-processing/tools/extract-keyframes.ts +2 -2
- package/src/config/bundled-skills/messaging/tools/shared.ts +1 -1
- package/src/config/bundled-skills/settings/tools/voice-config-update.ts +19 -3
- package/src/config/bundled-skills/skill-management/TOOLS.json +2 -2
- package/src/config/bundled-skills/slack/tools/shared.ts +19 -4
- package/src/config/bundled-skills/slack/tools/slack-scan-digest.ts +2 -3
- package/src/config/bundled-skills/transcribe/SKILL.md +1 -1
- package/src/config/bundled-skills/transcribe/TOOLS.json +2 -6
- package/src/config/bundled-skills/transcribe/tools/transcribe-media.ts +19 -83
- package/src/config/bundled-tool-registry.ts +2 -14
- package/src/config/feature-flag-registry.json +16 -0
- package/src/config/loader.ts +64 -0
- package/src/config/raw-config-utils.ts +30 -0
- package/src/config/schema-utils.ts +28 -7
- package/src/config/schema.ts +8 -0
- package/src/config/schemas/elevenlabs.ts +18 -0
- package/src/config/schemas/memory-lifecycle.ts +4 -2
- package/src/config/schemas/memory-storage.ts +1 -1
- package/src/config/schemas/services.ts +8 -6
- package/src/contacts/contact-store.ts +13 -6
- package/src/contacts/contacts-write.ts +0 -1
- package/src/context/window-manager.ts +13 -2
- package/src/daemon/conversation-agent-loop-handlers.ts +46 -42
- package/src/daemon/conversation-agent-loop.ts +56 -19
- package/src/daemon/conversation-attachments.ts +18 -36
- package/src/daemon/conversation-error.ts +2 -1
- package/src/daemon/conversation-history.ts +18 -4
- package/src/daemon/conversation-lifecycle.ts +39 -15
- package/src/daemon/conversation-messaging.ts +70 -26
- package/src/daemon/conversation-process.ts +58 -34
- package/src/daemon/conversation-runtime-assembly.ts +21 -38
- package/src/daemon/conversation-slash.ts +121 -256
- package/src/daemon/conversation-surfaces.ts +143 -20
- package/src/daemon/conversation-tool-setup.ts +0 -6
- package/src/daemon/conversation-workspace.ts +21 -1
- package/src/daemon/conversation.ts +51 -29
- package/src/daemon/first-greeting.ts +35 -0
- package/src/daemon/handlers/config-embeddings.ts +148 -0
- package/src/daemon/handlers/config-model.ts +71 -26
- package/src/daemon/handlers/conversations.ts +0 -23
- package/src/daemon/handlers/recording.ts +26 -21
- package/src/daemon/history-repair.ts +28 -8
- package/src/daemon/host-cu-proxy.ts +2 -2
- package/src/daemon/lifecycle.ts +106 -64
- package/src/daemon/message-protocol.ts +3 -0
- package/src/daemon/message-types/conversations.ts +19 -0
- package/src/daemon/message-types/messages.ts +1 -0
- package/src/daemon/message-types/shared.ts +2 -0
- package/src/daemon/message-types/surfaces.ts +2 -0
- package/src/daemon/message-types/upgrades.ts +23 -0
- package/src/daemon/server.ts +83 -12
- package/src/daemon/shutdown-handlers.ts +8 -5
- package/src/daemon/startup-error.ts +9 -0
- package/src/daemon/tool-side-effects.ts +11 -28
- package/src/events/tool-permission-telemetry-listener.ts +1 -3
- package/src/instrument.ts +0 -4
- package/src/media/app-icon-generator.ts +2 -2
- package/src/memory/app-git-service.ts +28 -16
- package/src/memory/app-store.ts +230 -41
- package/src/memory/attachments-store.ts +558 -130
- package/src/memory/conversation-attention-store.ts +70 -0
- package/src/memory/conversation-crud.ts +442 -3
- package/src/memory/conversation-directories.ts +125 -0
- package/src/memory/conversation-disk-view.ts +390 -0
- package/src/memory/conversation-key-store.ts +17 -5
- package/src/memory/conversation-queries.ts +5 -1
- package/src/memory/conversation-title-service.ts +21 -49
- package/src/memory/db-init.ts +28 -0
- package/src/memory/embedding-backend.ts +42 -53
- package/src/memory/embedding-gemini.test.ts +4 -4
- package/src/memory/embedding-local.ts +1 -3
- package/src/memory/embedding-ollama.ts +1 -3
- package/src/memory/embedding-openai.ts +1 -3
- package/src/memory/indexer.ts +9 -7
- package/src/memory/items-extractor.ts +42 -13
- package/src/memory/job-handlers/conversation-starters.ts +6 -1
- package/src/memory/job-handlers/embedding.test.ts +1 -4
- package/src/memory/llm-request-log-store.ts +100 -1
- package/src/memory/migrations/102-alter-table-columns.ts +5 -0
- package/src/memory/migrations/146-schedule-oneshot-routing.ts +3 -3
- package/src/memory/migrations/147-migrate-reminders-to-schedules.ts +66 -70
- package/src/memory/migrations/148-drop-reminders-table.ts +5 -9
- package/src/memory/migrations/160-drop-loopback-port-column.ts +1 -3
- package/src/memory/migrations/174-rename-thread-starters-table.ts +0 -7
- package/src/memory/migrations/178-oauth-providers-managed-service-config-key.ts +15 -0
- package/src/memory/migrations/179-llm-request-log-message-id.ts +16 -0
- package/src/memory/migrations/180-backfill-inline-attachments-to-disk.ts +66 -0
- package/src/memory/migrations/181-rename-thread-starters-checkpoints.ts +46 -0
- package/src/memory/migrations/182-oauth-providers-display-metadata.ts +20 -0
- package/src/memory/migrations/183-add-conversation-fork-lineage.ts +22 -0
- package/src/memory/migrations/184-llm-request-log-provider.ts +12 -0
- package/src/memory/migrations/index.ts +7 -0
- package/src/memory/migrations/registry.ts +13 -0
- package/src/memory/retriever.test.ts +601 -2
- package/src/memory/retriever.ts +85 -9
- package/src/memory/schema/conversations.ts +6 -0
- package/src/memory/schema/infrastructure.ts +13 -7
- package/src/memory/schema/oauth.ts +6 -0
- package/src/messaging/providers/gmail/mime-builder.ts +3 -1
- package/src/notifications/copy-composer.ts +26 -0
- package/src/notifications/decision-engine.ts +14 -1
- package/src/notifications/emit-signal.ts +1 -1
- package/src/notifications/signal.ts +36 -0
- package/src/oauth/byo-connection.test.ts +1 -45
- package/src/oauth/byo-connection.ts +2 -8
- package/src/oauth/connect-orchestrator.ts +15 -11
- package/src/oauth/connection-resolver.test.ts +191 -0
- package/src/oauth/connection-resolver.ts +66 -38
- package/src/oauth/connection.ts +0 -1
- package/src/oauth/oauth-store.ts +97 -47
- package/src/oauth/platform-connection.test.ts +0 -1
- package/src/oauth/platform-connection.ts +11 -3
- package/src/oauth/seed-providers.ts +78 -3
- package/src/oauth/token-persistence.ts +16 -10
- package/src/permissions/checker.ts +62 -19
- package/src/prompts/system-prompt.ts +2 -0
- package/src/prompts/templates/BOOTSTRAP.md +2 -0
- package/src/providers/anthropic/client.ts +8 -1
- package/src/providers/failover.ts +4 -1
- package/src/providers/gemini/client.ts +50 -0
- package/src/providers/model-catalog.ts +92 -0
- package/src/providers/model-intents.ts +29 -20
- package/src/providers/openai/client.ts +49 -0
- package/src/providers/types.ts +2 -0
- package/src/runtime/access-request-helper.ts +16 -7
- package/src/runtime/auth/credential-service.ts +3 -1
- package/src/runtime/auth/route-policy.ts +14 -1
- package/src/runtime/btw-sidechain.ts +101 -0
- package/src/runtime/channel-reply-delivery.ts +17 -1
- package/src/runtime/http-router.ts +3 -1
- package/src/runtime/http-server.ts +196 -141
- package/src/runtime/http-types.ts +1 -0
- package/src/runtime/migrations/vbundle-builder.ts +5 -1
- package/src/runtime/routes/access-request-decision.ts +41 -0
- package/src/runtime/routes/app-management-routes.ts +6 -3
- package/src/runtime/routes/app-routes.ts +7 -3
- package/src/runtime/routes/approval-routes.ts +1 -0
- package/src/runtime/routes/approval-strategies/guardian-callback-strategy.ts +34 -2
- package/src/runtime/routes/attachment-routes.ts +45 -15
- package/src/runtime/routes/btw-routes.ts +21 -61
- package/src/runtime/routes/conversation-management-routes.ts +68 -0
- package/src/runtime/routes/conversation-query-routes.ts +180 -10
- package/src/runtime/routes/conversation-routes.ts +222 -28
- package/src/runtime/routes/conversation-starter-routes.ts +9 -11
- package/src/runtime/routes/diagnostics-routes.ts +1 -0
- package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -2
- package/src/runtime/routes/llm-context-normalization.ts +1199 -0
- package/src/runtime/routes/log-export-routes.ts +3 -0
- package/src/runtime/routes/memory-item-routes.test.ts +34 -0
- package/src/runtime/routes/memory-item-routes.ts +4 -0
- package/src/runtime/routes/migration-routes.ts +4 -1
- package/src/runtime/routes/oauth-apps.ts +291 -0
- package/src/runtime/routes/secret-routes.ts +28 -1
- package/src/runtime/routes/settings-routes.ts +14 -0
- package/src/runtime/routes/trace-event-routes.ts +4 -1
- package/src/schedule/schedule-store.ts +9 -21
- package/src/security/secure-keys.ts +21 -0
- package/src/signals/bash.ts +1 -1
- package/src/swarm/backend-claude-code.ts +3 -6
- package/src/telemetry/usage-telemetry-reporter.test.ts +3 -2
- package/src/telemetry/usage-telemetry-reporter.ts +3 -1
- package/src/tools/AGENTS.md +6 -10
- package/src/tools/apps/executors.ts +17 -232
- package/src/tools/claude-code/claude-code.ts +2 -3
- package/src/tools/credentials/vault.ts +7 -12
- package/src/tools/host-filesystem/read.ts +13 -10
- package/src/tools/network/__tests__/web-search.test.ts +4 -2
- package/src/tools/schedule/list.ts +2 -7
- package/src/tools/schema-transforms.ts +5 -0
- package/src/tools/shared/filesystem/format-diff.ts +4 -21
- package/src/tools/skills/execute.ts +1 -1
- package/src/tools/tool-manifest.ts +0 -6
- package/src/tools/ui-surface/definitions.ts +2 -2
- package/src/util/device-id.ts +28 -5
- package/src/util/platform.ts +6 -0
- package/src/util/pricing.ts +1 -0
- package/src/util/retry.ts +1 -3
- package/src/workspace/migrations/002-backfill-installation-id.ts +23 -12
- package/src/workspace/migrations/003-seed-device-id.ts +3 -4
- package/src/workspace/migrations/006-services-config.ts +5 -0
- package/src/workspace/migrations/008-voice-timeout-and-max-steps.ts +12 -0
- package/src/workspace/migrations/009-backfill-conversation-disk-view.ts +10 -0
- package/src/workspace/migrations/010-app-dir-rename.ts +223 -0
- package/src/workspace/migrations/012-rename-conversation-disk-view-dirs.ts +64 -0
- package/src/workspace/migrations/013-repair-conversation-disk-view.ts +11 -0
- package/src/workspace/migrations/rebuild-conversation-disk-view.ts +186 -0
- package/src/workspace/migrations/registry.ts +10 -0
- package/src/workspace/top-level-renderer.ts +12 -0
- package/src/__tests__/asset-materialize-tool.test.ts +0 -523
- package/src/__tests__/asset-search-tool.test.ts +0 -536
- package/src/__tests__/fixtures/media-reuse-fixtures.ts +0 -56
- package/src/__tests__/media-reuse-story.e2e.test.ts +0 -762
- package/src/__tests__/media-visibility-policy.test.ts +0 -190
- package/src/config/bundled-skills/app-builder/tools/app-file-edit.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-file-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-file-read.ts +0 -21
- package/src/config/bundled-skills/app-builder/tools/app-file-write.ts +0 -14
- package/src/config/bundled-skills/app-builder/tools/app-list.ts +0 -13
- package/src/config/bundled-skills/app-builder/tools/app-update.ts +0 -23
- package/src/daemon/media-visibility-policy.ts +0 -59
- package/src/tools/assets/materialize.ts +0 -248
- package/src/tools/assets/search.ts +0 -400
|
@@ -117,32 +117,60 @@ describe("getSchemaAtPath", () => {
|
|
|
117
117
|
"memory.segmentation",
|
|
118
118
|
);
|
|
119
119
|
expect(result).not.toBeNull();
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
expect(
|
|
128
|
-
expect(
|
|
129
|
-
expect(schema.shape.overlapTokens).toBeDefined();
|
|
120
|
+
// Verify we can produce JSON Schema with the expected properties
|
|
121
|
+
const jsonSchema = z.toJSONSchema(result!, {
|
|
122
|
+
unrepresentable: "any",
|
|
123
|
+
io: "input",
|
|
124
|
+
}) as Record<string, unknown>;
|
|
125
|
+
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
126
|
+
expect(properties).toBeDefined();
|
|
127
|
+
expect(properties.targetTokens).toBeDefined();
|
|
128
|
+
expect(properties.overlapTokens).toBeDefined();
|
|
130
129
|
});
|
|
131
130
|
|
|
132
131
|
test("navigates through .default() wrappers (calls → object schema)", () => {
|
|
133
132
|
const result = getSchemaAtPath(AssistantConfigSchema, "calls");
|
|
134
133
|
expect(result).not.toBeNull();
|
|
135
|
-
//
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
expect(
|
|
143
|
-
expect(
|
|
144
|
-
expect(
|
|
145
|
-
|
|
134
|
+
// Verify we can produce JSON Schema with the expected properties
|
|
135
|
+
const jsonSchema = z.toJSONSchema(result!, {
|
|
136
|
+
unrepresentable: "any",
|
|
137
|
+
io: "input",
|
|
138
|
+
}) as Record<string, unknown>;
|
|
139
|
+
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
140
|
+
expect(properties).toBeDefined();
|
|
141
|
+
expect(properties.enabled).toBeDefined();
|
|
142
|
+
expect(properties.voice).toBeDefined();
|
|
143
|
+
expect(properties.safety).toBeDefined();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("navigates through .transform() wrappers (ingress → object schema)", () => {
|
|
147
|
+
const result = getSchemaAtPath(AssistantConfigSchema, "ingress");
|
|
148
|
+
expect(result).not.toBeNull();
|
|
149
|
+
// ingress uses .transform() which creates a pipe — getSchemaAtPath
|
|
150
|
+
// must unwrap through the pipe to reach the input object shape
|
|
151
|
+
const jsonSchema = z.toJSONSchema(result!, {
|
|
152
|
+
unrepresentable: "any",
|
|
153
|
+
io: "input",
|
|
154
|
+
}) as Record<string, unknown>;
|
|
155
|
+
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
156
|
+
expect(properties).toBeDefined();
|
|
157
|
+
expect(properties.enabled).toBeDefined();
|
|
158
|
+
expect(properties.webhook).toBeDefined();
|
|
159
|
+
expect(properties.rateLimit).toBeDefined();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("navigates nested path through .transform() wrapper (ingress.webhook)", () => {
|
|
163
|
+
const result = getSchemaAtPath(AssistantConfigSchema, "ingress.webhook");
|
|
164
|
+
expect(result).not.toBeNull();
|
|
165
|
+
const jsonSchema = z.toJSONSchema(result!, {
|
|
166
|
+
unrepresentable: "any",
|
|
167
|
+
io: "input",
|
|
168
|
+
}) as Record<string, unknown>;
|
|
169
|
+
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
170
|
+
expect(properties).toBeDefined();
|
|
171
|
+
expect(properties.secret).toBeDefined();
|
|
172
|
+
expect(properties.timeoutMs).toBeDefined();
|
|
173
|
+
expect(properties.maxRetries).toBeDefined();
|
|
146
174
|
});
|
|
147
175
|
|
|
148
176
|
test("returns null for non-existent top-level path", () => {
|
|
@@ -170,6 +198,7 @@ describe("z.toJSONSchema integration", () => {
|
|
|
170
198
|
test("full schema produces valid JSON Schema with type object and properties", () => {
|
|
171
199
|
const jsonSchema = z.toJSONSchema(AssistantConfigSchema, {
|
|
172
200
|
unrepresentable: "any",
|
|
201
|
+
io: "input",
|
|
173
202
|
}) as Record<string, unknown>;
|
|
174
203
|
expect(jsonSchema.type).toBe("object");
|
|
175
204
|
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
@@ -184,11 +213,27 @@ describe("z.toJSONSchema integration", () => {
|
|
|
184
213
|
expect(properties.sandbox).toBeDefined();
|
|
185
214
|
});
|
|
186
215
|
|
|
216
|
+
test("full schema emits real properties for transformed fields (ingress)", () => {
|
|
217
|
+
const jsonSchema = z.toJSONSchema(AssistantConfigSchema, {
|
|
218
|
+
unrepresentable: "any",
|
|
219
|
+
io: "input",
|
|
220
|
+
}) as Record<string, unknown>;
|
|
221
|
+
const properties = jsonSchema.properties as Record<string, unknown>;
|
|
222
|
+
const ingress = properties.ingress as Record<string, unknown>;
|
|
223
|
+
// Without io: "input", transforms produce empty {} — verify we get real content
|
|
224
|
+
expect(ingress.properties).toBeDefined();
|
|
225
|
+
const ingressProps = ingress.properties as Record<string, unknown>;
|
|
226
|
+
expect(ingressProps.enabled).toBeDefined();
|
|
227
|
+
expect(ingressProps.webhook).toBeDefined();
|
|
228
|
+
expect(ingressProps.rateLimit).toBeDefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
187
231
|
test("sub-schema at calls produces JSON Schema with expected properties", () => {
|
|
188
232
|
const callsSchema = getSchemaAtPath(AssistantConfigSchema, "calls");
|
|
189
233
|
expect(callsSchema).not.toBeNull();
|
|
190
234
|
const jsonSchema = z.toJSONSchema(callsSchema!, {
|
|
191
235
|
unrepresentable: "any",
|
|
236
|
+
io: "input",
|
|
192
237
|
}) as Record<string, unknown>;
|
|
193
238
|
const properties = jsonSchema.properties as
|
|
194
239
|
| Record<string, unknown>
|
|
@@ -204,6 +249,7 @@ describe("z.toJSONSchema integration", () => {
|
|
|
204
249
|
expect(maxTokensSchema).not.toBeNull();
|
|
205
250
|
const jsonSchema = z.toJSONSchema(maxTokensSchema!, {
|
|
206
251
|
unrepresentable: "any",
|
|
252
|
+
io: "input",
|
|
207
253
|
}) as Record<string, unknown>;
|
|
208
254
|
expect(jsonSchema.type).toBe("integer");
|
|
209
255
|
});
|
|
@@ -216,6 +262,7 @@ describe("z.toJSONSchema integration", () => {
|
|
|
216
262
|
expect(segSchema).not.toBeNull();
|
|
217
263
|
const jsonSchema = z.toJSONSchema(segSchema!, {
|
|
218
264
|
unrepresentable: "any",
|
|
265
|
+
io: "input",
|
|
219
266
|
}) as Record<string, unknown>;
|
|
220
267
|
expect(jsonSchema.type).toBe("object");
|
|
221
268
|
const properties = jsonSchema.properties as
|
|
@@ -62,12 +62,13 @@ mock.module("../config/loader.js", () => ({
|
|
|
62
62
|
// ── Overflow recovery mocks ──────────────────────────────────────────
|
|
63
63
|
|
|
64
64
|
// Token estimator — controllable per-test via mockEstimateTokens.
|
|
65
|
-
// Can be a number (constant) or a function
|
|
66
|
-
|
|
65
|
+
// Can be a number (constant), a no-arg function, or a function that
|
|
66
|
+
// receives the messages array for dynamic behavior based on content.
|
|
67
|
+
let mockEstimateTokens: number | ((msgs?: Message[]) => number) = 1000;
|
|
67
68
|
mock.module("../context/token-estimator.js", () => ({
|
|
68
|
-
estimatePromptTokens: () =>
|
|
69
|
+
estimatePromptTokens: (msgs: Message[]) =>
|
|
69
70
|
typeof mockEstimateTokens === "function"
|
|
70
|
-
? mockEstimateTokens()
|
|
71
|
+
? mockEstimateTokens(msgs)
|
|
71
72
|
: mockEstimateTokens,
|
|
72
73
|
}));
|
|
73
74
|
|
|
@@ -208,8 +209,9 @@ mock.module("../daemon/conversation-memory.js", () => ({
|
|
|
208
209
|
}),
|
|
209
210
|
}));
|
|
210
211
|
|
|
212
|
+
let mockApplyRuntimeInjections: (msgs: Message[]) => Message[] = (msgs) => msgs;
|
|
211
213
|
mock.module("../daemon/conversation-runtime-assembly.js", () => ({
|
|
212
|
-
applyRuntimeInjections: (msgs: Message[]) => msgs,
|
|
214
|
+
applyRuntimeInjections: (msgs: Message[]) => mockApplyRuntimeInjections(msgs),
|
|
213
215
|
stripInjectedContext: (msgs: Message[]) => msgs,
|
|
214
216
|
}));
|
|
215
217
|
|
|
@@ -327,6 +329,7 @@ mock.module("../agent/message-types.js", () => ({
|
|
|
327
329
|
|
|
328
330
|
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
329
331
|
recordRequestLog: () => {},
|
|
332
|
+
backfillMessageIdOnLogs: () => {},
|
|
330
333
|
}));
|
|
331
334
|
|
|
332
335
|
// ── Imports (after mocks) ────────────────────────────────────────────
|
|
@@ -520,6 +523,7 @@ beforeEach(() => {
|
|
|
520
523
|
mockReducerStepFn = null;
|
|
521
524
|
mockOverflowAction = "fail_gracefully";
|
|
522
525
|
mockApprovalResult = { approved: false };
|
|
526
|
+
mockApplyRuntimeInjections = (msgs) => msgs;
|
|
523
527
|
recordUsageMock.mockClear();
|
|
524
528
|
});
|
|
525
529
|
|
|
@@ -1929,4 +1933,144 @@ describe("session-agent-loop overflow recovery (JARVIS-110)", () => {
|
|
|
1929
1933
|
// Agent loop: 1 initial + 3 mid-loop re-entries + 2 convergence re-runs = 6 calls
|
|
1930
1934
|
expect(agentLoopCallCount).toBe(6);
|
|
1931
1935
|
});
|
|
1936
|
+
|
|
1937
|
+
// ── Test 8 ────────────────────────────────────────────────────────
|
|
1938
|
+
// BUG: The preflight overflow reducer's budget check uses
|
|
1939
|
+
// step.estimatedTokens (computed on bare ctx.messages) without
|
|
1940
|
+
// accounting for tokens added by applyRuntimeInjections(). This
|
|
1941
|
+
// causes the reducer to stop early when the bare estimate is under
|
|
1942
|
+
// budget, even though post-injection tokens exceed it — leading to
|
|
1943
|
+
// a wasted provider round-trip that gets rejected.
|
|
1944
|
+
//
|
|
1945
|
+
// After fix: the budget check re-estimates on runMessages (with
|
|
1946
|
+
// injections) so the reducer continues to the next tier.
|
|
1947
|
+
test("preflight reducer continues when post-injection tokens exceed budget", async () => {
|
|
1948
|
+
const events: ServerMessage[] = [];
|
|
1949
|
+
|
|
1950
|
+
// Injections add an extra message, bumping the token count.
|
|
1951
|
+
const injectionMessage: Message = {
|
|
1952
|
+
role: "user" as const,
|
|
1953
|
+
content: [
|
|
1954
|
+
{
|
|
1955
|
+
type: "text" as const,
|
|
1956
|
+
text: "injected context " + "x".repeat(500),
|
|
1957
|
+
},
|
|
1958
|
+
],
|
|
1959
|
+
};
|
|
1960
|
+
mockApplyRuntimeInjections = (msgs) => [...msgs, injectionMessage];
|
|
1961
|
+
|
|
1962
|
+
// Budget = 200_000 * 0.95 = 190_000
|
|
1963
|
+
// The estimator returns different values based on whether the
|
|
1964
|
+
// injection message is present:
|
|
1965
|
+
// - bare history (no injection msg) → 195_000 (triggers preflight)
|
|
1966
|
+
// - after tier 1 bare → 185_000 (under budget, would stop early without fix)
|
|
1967
|
+
// - after tier 1 with injection → 195_000 (still over budget)
|
|
1968
|
+
// - after tier 2 bare → 170_000
|
|
1969
|
+
// - after tier 2 with injection → 175_000 (under budget, reducer stops)
|
|
1970
|
+
let reducerCallCount = 0;
|
|
1971
|
+
mockEstimateTokens = (msgs?: Message[]) => {
|
|
1972
|
+
const hasInjection = msgs?.some(
|
|
1973
|
+
(m) =>
|
|
1974
|
+
m.role === "user" &&
|
|
1975
|
+
Array.isArray(m.content) &&
|
|
1976
|
+
m.content.some(
|
|
1977
|
+
(b: { type: string; text?: string }) =>
|
|
1978
|
+
b.type === "text" &&
|
|
1979
|
+
typeof b.text === "string" &&
|
|
1980
|
+
b.text.startsWith("injected context"),
|
|
1981
|
+
),
|
|
1982
|
+
);
|
|
1983
|
+
if (reducerCallCount === 0) {
|
|
1984
|
+
// Before any reduction: preflight check on runMessages (with injection)
|
|
1985
|
+
return 195_000;
|
|
1986
|
+
}
|
|
1987
|
+
if (reducerCallCount === 1) {
|
|
1988
|
+
// After tier 1
|
|
1989
|
+
return hasInjection ? 195_000 : 185_000;
|
|
1990
|
+
}
|
|
1991
|
+
// After tier 2
|
|
1992
|
+
return hasInjection ? 175_000 : 170_000;
|
|
1993
|
+
};
|
|
1994
|
+
|
|
1995
|
+
mockReducerStepFn = (msgs: Message[]) => {
|
|
1996
|
+
reducerCallCount++;
|
|
1997
|
+
const tier =
|
|
1998
|
+
reducerCallCount === 1 ? "forced_compaction" : "tool_result_truncation";
|
|
1999
|
+
return {
|
|
2000
|
+
messages: msgs,
|
|
2001
|
+
tier,
|
|
2002
|
+
state: {
|
|
2003
|
+
appliedTiers:
|
|
2004
|
+
reducerCallCount === 1
|
|
2005
|
+
? ["forced_compaction"]
|
|
2006
|
+
: ["forced_compaction", "tool_result_truncation"],
|
|
2007
|
+
injectionMode: "full" as const,
|
|
2008
|
+
exhausted: reducerCallCount >= 2,
|
|
2009
|
+
},
|
|
2010
|
+
// Bare-history estimate (what the reducer sees on ctx.messages)
|
|
2011
|
+
estimatedTokens: reducerCallCount === 1 ? 185_000 : 170_000,
|
|
2012
|
+
compactionResult: {
|
|
2013
|
+
compacted: true,
|
|
2014
|
+
messages: msgs,
|
|
2015
|
+
compactedPersistedMessages: 5,
|
|
2016
|
+
summaryText: "Summary",
|
|
2017
|
+
previousEstimatedInputTokens: 195_000,
|
|
2018
|
+
estimatedInputTokens: reducerCallCount === 1 ? 185_000 : 170_000,
|
|
2019
|
+
maxInputTokens: 200_000,
|
|
2020
|
+
thresholdTokens: 160_000,
|
|
2021
|
+
compactedMessages: 10,
|
|
2022
|
+
summaryCalls: 1,
|
|
2023
|
+
summaryInputTokens: 500,
|
|
2024
|
+
summaryOutputTokens: 200,
|
|
2025
|
+
summaryModel: "mock-model",
|
|
2026
|
+
},
|
|
2027
|
+
};
|
|
2028
|
+
};
|
|
2029
|
+
|
|
2030
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
2031
|
+
onEvent({
|
|
2032
|
+
type: "message_complete",
|
|
2033
|
+
message: {
|
|
2034
|
+
role: "assistant",
|
|
2035
|
+
content: [{ type: "text", text: "done" }],
|
|
2036
|
+
},
|
|
2037
|
+
});
|
|
2038
|
+
onEvent({
|
|
2039
|
+
type: "usage",
|
|
2040
|
+
inputTokens: 170_000,
|
|
2041
|
+
outputTokens: 200,
|
|
2042
|
+
model: "test-model",
|
|
2043
|
+
providerDurationMs: 500,
|
|
2044
|
+
});
|
|
2045
|
+
return [
|
|
2046
|
+
...messages,
|
|
2047
|
+
{
|
|
2048
|
+
role: "assistant" as const,
|
|
2049
|
+
content: [{ type: "text", text: "done" }] as ContentBlock[],
|
|
2050
|
+
},
|
|
2051
|
+
];
|
|
2052
|
+
};
|
|
2053
|
+
|
|
2054
|
+
const ctx = makeCtx({
|
|
2055
|
+
agentLoopRun,
|
|
2056
|
+
contextWindowManager: {
|
|
2057
|
+
shouldCompact: () => ({ needed: false, estimatedTokens: 0 }),
|
|
2058
|
+
maybeCompact: async () => ({ compacted: false }),
|
|
2059
|
+
} as unknown as AgentLoopConversationContext["contextWindowManager"],
|
|
2060
|
+
});
|
|
2061
|
+
|
|
2062
|
+
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
2063
|
+
|
|
2064
|
+
// The reducer must be called twice — the first tier's bare estimate
|
|
2065
|
+
// (185k) is under budget (190k), but post-injection tokens (195k)
|
|
2066
|
+
// still exceed it. Without the fix, the reducer would stop after
|
|
2067
|
+
// tier 1 and the provider call would likely fail.
|
|
2068
|
+
expect(reducerCallCount).toBe(2);
|
|
2069
|
+
|
|
2070
|
+
// Should succeed without errors
|
|
2071
|
+
const conversationError = events.find(
|
|
2072
|
+
(e) => e.type === "conversation_error",
|
|
2073
|
+
);
|
|
2074
|
+
expect(conversationError).toBeUndefined();
|
|
2075
|
+
});
|
|
1932
2076
|
});
|
|
@@ -143,6 +143,14 @@ mock.module("../memory/conversation-crud.js", () => ({
|
|
|
143
143
|
getConversationOriginChannel: () => null,
|
|
144
144
|
}));
|
|
145
145
|
|
|
146
|
+
const syncMessageToDiskMock = mock(() => {});
|
|
147
|
+
const rebuildConversationDiskViewFromDbStateMock = mock(() => {});
|
|
148
|
+
mock.module("../memory/conversation-disk-view.js", () => ({
|
|
149
|
+
syncMessageToDisk: syncMessageToDiskMock,
|
|
150
|
+
rebuildConversationDiskViewFromDbState:
|
|
151
|
+
rebuildConversationDiskViewFromDbStateMock,
|
|
152
|
+
}));
|
|
153
|
+
|
|
146
154
|
mock.module("../memory/retriever.js", () => ({
|
|
147
155
|
buildMemoryRecall: async () => ({
|
|
148
156
|
enabled: false,
|
|
@@ -213,11 +221,13 @@ mock.module("../daemon/history-repair.js", () => ({
|
|
|
213
221
|
deepRepairHistory: (msgs: Message[]) => ({ messages: msgs, stats: {} }),
|
|
214
222
|
}));
|
|
215
223
|
|
|
224
|
+
const consolidateAssistantMessagesMock = mock(() => false);
|
|
216
225
|
mock.module("../daemon/conversation-history.js", () => ({
|
|
217
|
-
consolidateAssistantMessages:
|
|
226
|
+
consolidateAssistantMessages: consolidateAssistantMessagesMock,
|
|
218
227
|
}));
|
|
219
228
|
|
|
220
229
|
const recordUsageMock = mock(() => {});
|
|
230
|
+
const recordRequestLogMock = mock(() => {});
|
|
221
231
|
mock.module("../daemon/conversation-usage.js", () => ({
|
|
222
232
|
recordUsage: recordUsageMock,
|
|
223
233
|
}));
|
|
@@ -306,7 +316,8 @@ mock.module("../agent/message-types.js", () => ({
|
|
|
306
316
|
}));
|
|
307
317
|
|
|
308
318
|
mock.module("../memory/llm-request-log-store.js", () => ({
|
|
309
|
-
recordRequestLog:
|
|
319
|
+
recordRequestLog: recordRequestLogMock,
|
|
320
|
+
backfillMessageIdOnLogs: () => {},
|
|
310
321
|
}));
|
|
311
322
|
|
|
312
323
|
// ── Imports (after mocks) ────────────────────────────────────────────
|
|
@@ -447,6 +458,11 @@ beforeEach(() => {
|
|
|
447
458
|
mockOverflowAction = "fail_gracefully";
|
|
448
459
|
mockApprovalResult = { approved: false };
|
|
449
460
|
recordUsageMock.mockClear();
|
|
461
|
+
recordRequestLogMock.mockClear();
|
|
462
|
+
syncMessageToDiskMock.mockClear();
|
|
463
|
+
rebuildConversationDiskViewFromDbStateMock.mockClear();
|
|
464
|
+
consolidateAssistantMessagesMock.mockReset();
|
|
465
|
+
consolidateAssistantMessagesMock.mockImplementation(() => false);
|
|
450
466
|
});
|
|
451
467
|
|
|
452
468
|
describe("session-agent-loop", () => {
|
|
@@ -590,6 +606,235 @@ describe("session-agent-loop", () => {
|
|
|
590
606
|
});
|
|
591
607
|
});
|
|
592
608
|
|
|
609
|
+
describe("LLM request log persistence", () => {
|
|
610
|
+
test("record request log prefers the actual provider from failover", async () => {
|
|
611
|
+
const events: ServerMessage[] = [];
|
|
612
|
+
const rawRequest = {
|
|
613
|
+
model: "gpt-4.1",
|
|
614
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
615
|
+
};
|
|
616
|
+
const rawResponse = {
|
|
617
|
+
model: "gpt-4.1-2026-03-01",
|
|
618
|
+
choices: [
|
|
619
|
+
{
|
|
620
|
+
finish_reason: "stop",
|
|
621
|
+
message: {
|
|
622
|
+
role: "assistant",
|
|
623
|
+
content: "Hi there.",
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
],
|
|
627
|
+
usage: {
|
|
628
|
+
prompt_tokens: 12,
|
|
629
|
+
completion_tokens: 3,
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
634
|
+
onEvent({
|
|
635
|
+
type: "message_complete",
|
|
636
|
+
message: {
|
|
637
|
+
role: "assistant",
|
|
638
|
+
content: [{ type: "text", text: "Hi there." }],
|
|
639
|
+
},
|
|
640
|
+
});
|
|
641
|
+
onEvent({
|
|
642
|
+
type: "usage",
|
|
643
|
+
inputTokens: 12,
|
|
644
|
+
outputTokens: 3,
|
|
645
|
+
model: "gpt-4.1-2026-03-01",
|
|
646
|
+
actualProvider: "fireworks",
|
|
647
|
+
providerDurationMs: 45,
|
|
648
|
+
rawRequest,
|
|
649
|
+
rawResponse,
|
|
650
|
+
});
|
|
651
|
+
return [
|
|
652
|
+
...messages,
|
|
653
|
+
{
|
|
654
|
+
role: "assistant" as const,
|
|
655
|
+
content: [{ type: "text", text: "Hi there." }] as ContentBlock[],
|
|
656
|
+
},
|
|
657
|
+
];
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
const ctx = makeCtx({
|
|
661
|
+
agentLoopRun,
|
|
662
|
+
provider: {
|
|
663
|
+
name: "openrouter",
|
|
664
|
+
sendMessage: async () => ({
|
|
665
|
+
content: [{ type: "text", text: "title" }],
|
|
666
|
+
model: "mock",
|
|
667
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
668
|
+
stopReason: "end_turn",
|
|
669
|
+
}),
|
|
670
|
+
} as unknown as AgentLoopConversationContext["provider"],
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
674
|
+
|
|
675
|
+
expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
|
|
676
|
+
const call = recordRequestLogMock.mock.calls[0] as unknown as [
|
|
677
|
+
string,
|
|
678
|
+
string,
|
|
679
|
+
string,
|
|
680
|
+
undefined,
|
|
681
|
+
string,
|
|
682
|
+
];
|
|
683
|
+
expect(call).toEqual([
|
|
684
|
+
"test-conv",
|
|
685
|
+
JSON.stringify(rawRequest),
|
|
686
|
+
JSON.stringify(rawResponse),
|
|
687
|
+
undefined,
|
|
688
|
+
"fireworks",
|
|
689
|
+
]);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
test("record request log falls back to the runtime provider when no actual provider is supplied", async () => {
|
|
693
|
+
const rawRequest = {
|
|
694
|
+
model: "gpt-4.1",
|
|
695
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
696
|
+
};
|
|
697
|
+
const rawResponse = {
|
|
698
|
+
model: "gpt-4.1-2026-03-01",
|
|
699
|
+
choices: [
|
|
700
|
+
{
|
|
701
|
+
finish_reason: "stop",
|
|
702
|
+
message: {
|
|
703
|
+
role: "assistant",
|
|
704
|
+
content: "Hi there.",
|
|
705
|
+
},
|
|
706
|
+
},
|
|
707
|
+
],
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
711
|
+
onEvent({
|
|
712
|
+
type: "message_complete",
|
|
713
|
+
message: {
|
|
714
|
+
role: "assistant",
|
|
715
|
+
content: [{ type: "text", text: "Hi there." }],
|
|
716
|
+
},
|
|
717
|
+
});
|
|
718
|
+
onEvent({
|
|
719
|
+
type: "usage",
|
|
720
|
+
inputTokens: 12,
|
|
721
|
+
outputTokens: 3,
|
|
722
|
+
model: "gpt-4.1-2026-03-01",
|
|
723
|
+
providerDurationMs: 45,
|
|
724
|
+
rawRequest,
|
|
725
|
+
rawResponse,
|
|
726
|
+
});
|
|
727
|
+
return [
|
|
728
|
+
...messages,
|
|
729
|
+
{
|
|
730
|
+
role: "assistant" as const,
|
|
731
|
+
content: [{ type: "text", text: "Hi there." }] as ContentBlock[],
|
|
732
|
+
},
|
|
733
|
+
];
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const ctx = makeCtx({
|
|
737
|
+
agentLoopRun,
|
|
738
|
+
provider: {
|
|
739
|
+
name: "openrouter",
|
|
740
|
+
sendMessage: async () => ({
|
|
741
|
+
content: [{ type: "text", text: "title" }],
|
|
742
|
+
model: "mock",
|
|
743
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
744
|
+
stopReason: "end_turn",
|
|
745
|
+
}),
|
|
746
|
+
} as unknown as AgentLoopConversationContext["provider"],
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
await runAgentLoopImpl(ctx, "hello", "msg-1", () => {});
|
|
750
|
+
|
|
751
|
+
expect(recordRequestLogMock).toHaveBeenCalledTimes(1);
|
|
752
|
+
const call = recordRequestLogMock.mock.calls[0] as unknown as [
|
|
753
|
+
string,
|
|
754
|
+
string,
|
|
755
|
+
string,
|
|
756
|
+
undefined,
|
|
757
|
+
string,
|
|
758
|
+
];
|
|
759
|
+
expect(call[4]).toBe("openrouter");
|
|
760
|
+
});
|
|
761
|
+
});
|
|
762
|
+
|
|
763
|
+
describe("usage accounting", () => {
|
|
764
|
+
test("records the actual provider for failover-served usage", async () => {
|
|
765
|
+
const events: ServerMessage[] = [];
|
|
766
|
+
|
|
767
|
+
const agentLoopRun: AgentLoopRun = async (messages, onEvent) => {
|
|
768
|
+
onEvent({
|
|
769
|
+
type: "message_complete",
|
|
770
|
+
message: {
|
|
771
|
+
role: "assistant",
|
|
772
|
+
content: [{ type: "text", text: "Hi there." }],
|
|
773
|
+
},
|
|
774
|
+
});
|
|
775
|
+
onEvent({
|
|
776
|
+
type: "usage",
|
|
777
|
+
inputTokens: 12,
|
|
778
|
+
outputTokens: 3,
|
|
779
|
+
model: "gpt-4.1-2026-03-01",
|
|
780
|
+
actualProvider: "fireworks",
|
|
781
|
+
providerDurationMs: 45,
|
|
782
|
+
rawRequest: {
|
|
783
|
+
model: "gpt-4.1",
|
|
784
|
+
messages: [{ role: "user", content: "Hello" }],
|
|
785
|
+
},
|
|
786
|
+
rawResponse: {
|
|
787
|
+
model: "gpt-4.1-2026-03-01",
|
|
788
|
+
choices: [
|
|
789
|
+
{
|
|
790
|
+
finish_reason: "stop",
|
|
791
|
+
message: {
|
|
792
|
+
role: "assistant",
|
|
793
|
+
content: "Hi there.",
|
|
794
|
+
},
|
|
795
|
+
},
|
|
796
|
+
],
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
return [
|
|
800
|
+
...messages,
|
|
801
|
+
{
|
|
802
|
+
role: "assistant" as const,
|
|
803
|
+
content: [{ type: "text", text: "Hi there." }] as ContentBlock[],
|
|
804
|
+
},
|
|
805
|
+
];
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const ctx = makeCtx({
|
|
809
|
+
agentLoopRun,
|
|
810
|
+
provider: {
|
|
811
|
+
name: "openrouter",
|
|
812
|
+
sendMessage: async () => ({
|
|
813
|
+
content: [{ type: "text", text: "title" }],
|
|
814
|
+
model: "mock",
|
|
815
|
+
usage: { inputTokens: 0, outputTokens: 0 },
|
|
816
|
+
stopReason: "end_turn",
|
|
817
|
+
}),
|
|
818
|
+
} as unknown as AgentLoopConversationContext["provider"],
|
|
819
|
+
});
|
|
820
|
+
|
|
821
|
+
await runAgentLoopImpl(ctx, "hello", "msg-1", (msg) => events.push(msg));
|
|
822
|
+
|
|
823
|
+
const mainAgentCall = recordUsageMock.mock.calls.find(
|
|
824
|
+
(call) => (call as unknown[])[5] === "main_agent",
|
|
825
|
+
) as unknown[] | undefined;
|
|
826
|
+
|
|
827
|
+
expect(mainAgentCall).toBeDefined();
|
|
828
|
+
expect(mainAgentCall?.[0]).toMatchObject({
|
|
829
|
+
conversationId: "test-conv",
|
|
830
|
+
providerName: "fireworks",
|
|
831
|
+
});
|
|
832
|
+
expect(mainAgentCall?.[1]).toBe(12);
|
|
833
|
+
expect(mainAgentCall?.[2]).toBe(3);
|
|
834
|
+
expect(mainAgentCall?.[3]).toBe("gpt-4.1-2026-03-01");
|
|
835
|
+
});
|
|
836
|
+
});
|
|
837
|
+
|
|
593
838
|
describe("context window exhaustion (context-too-large recovery)", () => {
|
|
594
839
|
test("forwards cache-aware compaction usage to recordUsage", async () => {
|
|
595
840
|
const events: ServerMessage[] = [];
|
|
@@ -1688,6 +1933,49 @@ describe("session-agent-loop", () => {
|
|
|
1688
1933
|
|
|
1689
1934
|
expect(drainReason).toBe("loop_complete");
|
|
1690
1935
|
});
|
|
1936
|
+
|
|
1937
|
+
test("rebuilds disk view after consolidation mutates persisted history", async () => {
|
|
1938
|
+
consolidateAssistantMessagesMock.mockReturnValue(true);
|
|
1939
|
+
|
|
1940
|
+
const ctx = makeCtx({
|
|
1941
|
+
agentLoopRun: async (
|
|
1942
|
+
messages: Message[],
|
|
1943
|
+
onEvent: (event: AgentEvent) => void,
|
|
1944
|
+
) => {
|
|
1945
|
+
onEvent({
|
|
1946
|
+
type: "message_complete",
|
|
1947
|
+
message: {
|
|
1948
|
+
role: "assistant",
|
|
1949
|
+
content: [{ type: "text", text: "done" }],
|
|
1950
|
+
},
|
|
1951
|
+
});
|
|
1952
|
+
onEvent({
|
|
1953
|
+
type: "usage",
|
|
1954
|
+
inputTokens: 10,
|
|
1955
|
+
outputTokens: 5,
|
|
1956
|
+
model: "test",
|
|
1957
|
+
providerDurationMs: 50,
|
|
1958
|
+
});
|
|
1959
|
+
return [
|
|
1960
|
+
...messages,
|
|
1961
|
+
{
|
|
1962
|
+
role: "assistant" as const,
|
|
1963
|
+
content: [{ type: "text", text: "done" }] as ContentBlock[],
|
|
1964
|
+
},
|
|
1965
|
+
];
|
|
1966
|
+
},
|
|
1967
|
+
});
|
|
1968
|
+
|
|
1969
|
+
await runAgentLoopImpl(ctx, "hi", "msg-consolidate", () => {});
|
|
1970
|
+
|
|
1971
|
+
expect(consolidateAssistantMessagesMock).toHaveBeenCalledWith(
|
|
1972
|
+
"test-conv",
|
|
1973
|
+
"msg-consolidate",
|
|
1974
|
+
);
|
|
1975
|
+
expect(rebuildConversationDiskViewFromDbStateMock).toHaveBeenCalledWith(
|
|
1976
|
+
"test-conv",
|
|
1977
|
+
);
|
|
1978
|
+
});
|
|
1691
1979
|
});
|
|
1692
1980
|
|
|
1693
1981
|
describe("stale pending surface cleanup", () => {
|