@vellumai/assistant 0.5.1 → 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__/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-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__/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 +32 -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__/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__/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 +8 -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 +48 -7
- 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/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 +71 -8
- 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 +2 -7
- 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
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { mkdtempSync, realpathSync, rmSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
5
|
+
|
|
6
|
+
const testDir = realpathSync(
|
|
7
|
+
mkdtempSync(join(tmpdir(), "turn-boundary-resolution-test-")),
|
|
8
|
+
);
|
|
9
|
+
const workspaceDir = join(testDir, ".vellum", "workspace");
|
|
10
|
+
const conversationsDir = join(workspaceDir, "conversations");
|
|
11
|
+
|
|
12
|
+
mock.module("../util/platform.js", () => ({
|
|
13
|
+
getRootDir: () => join(testDir, ".vellum"),
|
|
14
|
+
getDataDir: () => join(workspaceDir, "data"),
|
|
15
|
+
getWorkspaceDir: () => workspaceDir,
|
|
16
|
+
getConversationsDir: () => conversationsDir,
|
|
17
|
+
isMacOS: () => process.platform === "darwin",
|
|
18
|
+
isLinux: () => process.platform === "linux",
|
|
19
|
+
isWindows: () => process.platform === "win32",
|
|
20
|
+
getPidPath: () => join(testDir, "test.pid"),
|
|
21
|
+
getDbPath: () => join(testDir, "test.db"),
|
|
22
|
+
getLogPath: () => join(testDir, "test.log"),
|
|
23
|
+
ensureDataDir: () => {},
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
mock.module("../util/logger.js", () => ({
|
|
27
|
+
getLogger: () =>
|
|
28
|
+
new Proxy({} as Record<string, unknown>, {
|
|
29
|
+
get: () => () => {},
|
|
30
|
+
}),
|
|
31
|
+
}));
|
|
32
|
+
|
|
33
|
+
mock.module("../config/loader.js", () => ({
|
|
34
|
+
getConfig: () => ({
|
|
35
|
+
ui: {},
|
|
36
|
+
model: "test",
|
|
37
|
+
provider: "test",
|
|
38
|
+
memory: { enabled: false },
|
|
39
|
+
rateLimit: { maxRequestsPerMinute: 0 },
|
|
40
|
+
secretDetection: { enabled: false },
|
|
41
|
+
}),
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
import {
|
|
45
|
+
addMessage,
|
|
46
|
+
createConversation,
|
|
47
|
+
getAssistantMessageIdsInTurn,
|
|
48
|
+
} from "../memory/conversation-crud.js";
|
|
49
|
+
import { getDb, initializeDb, resetDb } from "../memory/db.js";
|
|
50
|
+
import { llmRequestLogs, toolInvocations } from "../memory/schema.js";
|
|
51
|
+
|
|
52
|
+
initializeDb();
|
|
53
|
+
|
|
54
|
+
function resetTables(): void {
|
|
55
|
+
const db = getDb();
|
|
56
|
+
db.delete(llmRequestLogs).run();
|
|
57
|
+
db.delete(toolInvocations).run();
|
|
58
|
+
db.run("DELETE FROM message_attachments");
|
|
59
|
+
db.run("DELETE FROM attachments");
|
|
60
|
+
db.run("DELETE FROM messages");
|
|
61
|
+
db.run("DELETE FROM conversations");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function toolResultContent(toolUseIds: string[]): string {
|
|
65
|
+
return JSON.stringify(
|
|
66
|
+
toolUseIds.map((id) => ({
|
|
67
|
+
type: "tool_result",
|
|
68
|
+
tool_use_id: id,
|
|
69
|
+
content: "ok",
|
|
70
|
+
is_error: false,
|
|
71
|
+
})),
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
afterAll(() => {
|
|
76
|
+
resetDb();
|
|
77
|
+
try {
|
|
78
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
79
|
+
} catch {
|
|
80
|
+
/* best effort */
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("getAssistantMessageIdsInTurn", () => {
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
resetTables();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("single-step turn: returns only the one assistant message", async () => {
|
|
90
|
+
const conv = createConversation("single-step");
|
|
91
|
+
await addMessage(conv.id, "user", "Hello", undefined, {
|
|
92
|
+
skipIndexing: true,
|
|
93
|
+
});
|
|
94
|
+
const a1 = await addMessage(conv.id, "assistant", "Hi there!", undefined, {
|
|
95
|
+
skipIndexing: true,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = getAssistantMessageIdsInTurn(a1.id);
|
|
99
|
+
expect(result).toEqual([a1.id]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("multi-step turn: user → A1 → tool_result → A2 → query A2 → returns [A1, A2]", async () => {
|
|
103
|
+
const conv = createConversation("multi-step");
|
|
104
|
+
await addMessage(conv.id, "user", "Do the thing", undefined, {
|
|
105
|
+
skipIndexing: true,
|
|
106
|
+
});
|
|
107
|
+
const a1 = await addMessage(
|
|
108
|
+
conv.id,
|
|
109
|
+
"assistant",
|
|
110
|
+
"Using tool...",
|
|
111
|
+
undefined,
|
|
112
|
+
{ skipIndexing: true },
|
|
113
|
+
);
|
|
114
|
+
await addMessage(
|
|
115
|
+
conv.id,
|
|
116
|
+
"user",
|
|
117
|
+
toolResultContent(["tool-1"]),
|
|
118
|
+
undefined,
|
|
119
|
+
{ skipIndexing: true },
|
|
120
|
+
);
|
|
121
|
+
const a2 = await addMessage(conv.id, "assistant", "Done!", undefined, {
|
|
122
|
+
skipIndexing: true,
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const result = getAssistantMessageIdsInTurn(a2.id);
|
|
126
|
+
expect(result).toEqual([a1.id, a2.id]);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("3-step turn: user → A1 → tool_result → A2 → tool_result → A3 → query A3 → returns [A1, A2, A3]", async () => {
|
|
130
|
+
const conv = createConversation("three-step");
|
|
131
|
+
await addMessage(conv.id, "user", "Complex task", undefined, {
|
|
132
|
+
skipIndexing: true,
|
|
133
|
+
});
|
|
134
|
+
const a1 = await addMessage(conv.id, "assistant", "Step 1...", undefined, {
|
|
135
|
+
skipIndexing: true,
|
|
136
|
+
});
|
|
137
|
+
await addMessage(
|
|
138
|
+
conv.id,
|
|
139
|
+
"user",
|
|
140
|
+
toolResultContent(["tool-1"]),
|
|
141
|
+
undefined,
|
|
142
|
+
{ skipIndexing: true },
|
|
143
|
+
);
|
|
144
|
+
const a2 = await addMessage(conv.id, "assistant", "Step 2...", undefined, {
|
|
145
|
+
skipIndexing: true,
|
|
146
|
+
});
|
|
147
|
+
await addMessage(
|
|
148
|
+
conv.id,
|
|
149
|
+
"user",
|
|
150
|
+
toolResultContent(["tool-2"]),
|
|
151
|
+
undefined,
|
|
152
|
+
{ skipIndexing: true },
|
|
153
|
+
);
|
|
154
|
+
const a3 = await addMessage(conv.id, "assistant", "All done!", undefined, {
|
|
155
|
+
skipIndexing: true,
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const result = getAssistantMessageIdsInTurn(a3.id);
|
|
159
|
+
expect(result).toEqual([a1.id, a2.id, a3.id]);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
test("query intermediate message: query A1 in a 2-step turn → returns [A1, A2]", async () => {
|
|
163
|
+
const conv = createConversation("intermediate");
|
|
164
|
+
await addMessage(conv.id, "user", "Start task", undefined, {
|
|
165
|
+
skipIndexing: true,
|
|
166
|
+
});
|
|
167
|
+
const a1 = await addMessage(
|
|
168
|
+
conv.id,
|
|
169
|
+
"assistant",
|
|
170
|
+
"Using tool...",
|
|
171
|
+
undefined,
|
|
172
|
+
{ skipIndexing: true },
|
|
173
|
+
);
|
|
174
|
+
await addMessage(
|
|
175
|
+
conv.id,
|
|
176
|
+
"user",
|
|
177
|
+
toolResultContent(["tool-1"]),
|
|
178
|
+
undefined,
|
|
179
|
+
{ skipIndexing: true },
|
|
180
|
+
);
|
|
181
|
+
const a2 = await addMessage(conv.id, "assistant", "Done!", undefined, {
|
|
182
|
+
skipIndexing: true,
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const result = getAssistantMessageIdsInTurn(a1.id);
|
|
186
|
+
expect(result).toEqual([a1.id, a2.id]);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
test("message not found: returns [messageId]", () => {
|
|
190
|
+
const result = getAssistantMessageIdsInTurn("nonexistent-id");
|
|
191
|
+
expect(result).toEqual(["nonexistent-id"]);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test("consecutive turns: query message in second turn → returns only that turn's messages", async () => {
|
|
195
|
+
const conv = createConversation("consecutive");
|
|
196
|
+
|
|
197
|
+
// First turn
|
|
198
|
+
await addMessage(conv.id, "user", "First question", undefined, {
|
|
199
|
+
skipIndexing: true,
|
|
200
|
+
});
|
|
201
|
+
const a1 = await addMessage(
|
|
202
|
+
conv.id,
|
|
203
|
+
"assistant",
|
|
204
|
+
"First answer",
|
|
205
|
+
undefined,
|
|
206
|
+
{ skipIndexing: true },
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
// Second turn
|
|
210
|
+
await addMessage(conv.id, "user", "Second question", undefined, {
|
|
211
|
+
skipIndexing: true,
|
|
212
|
+
});
|
|
213
|
+
const a2 = await addMessage(
|
|
214
|
+
conv.id,
|
|
215
|
+
"assistant",
|
|
216
|
+
"Using tool...",
|
|
217
|
+
undefined,
|
|
218
|
+
{ skipIndexing: true },
|
|
219
|
+
);
|
|
220
|
+
await addMessage(
|
|
221
|
+
conv.id,
|
|
222
|
+
"user",
|
|
223
|
+
toolResultContent(["tool-1"]),
|
|
224
|
+
undefined,
|
|
225
|
+
{ skipIndexing: true },
|
|
226
|
+
);
|
|
227
|
+
const a3 = await addMessage(
|
|
228
|
+
conv.id,
|
|
229
|
+
"assistant",
|
|
230
|
+
"Done with second",
|
|
231
|
+
undefined,
|
|
232
|
+
{ skipIndexing: true },
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
// Query second turn → should NOT include a1
|
|
236
|
+
const result = getAssistantMessageIdsInTurn(a3.id);
|
|
237
|
+
expect(result).toEqual([a2.id, a3.id]);
|
|
238
|
+
|
|
239
|
+
// Query first turn → should only include a1
|
|
240
|
+
const firstTurnResult = getAssistantMessageIdsInTurn(a1.id);
|
|
241
|
+
expect(firstTurnResult).toEqual([a1.id]);
|
|
242
|
+
});
|
|
243
|
+
});
|
|
@@ -1641,7 +1641,9 @@ describe("web_fetch tool", () => {
|
|
|
1641
1641
|
);
|
|
1642
1642
|
|
|
1643
1643
|
expect(result.isError).toBe(false);
|
|
1644
|
-
expect(result.content).not.toContain(
|
|
1644
|
+
expect(result.content).not.toContain(
|
|
1645
|
+
"Extracted text content is very short",
|
|
1646
|
+
);
|
|
1645
1647
|
});
|
|
1646
1648
|
|
|
1647
1649
|
test("does not suggest JS rendering notice in raw mode even for sparse HTML", async () => {
|
|
@@ -1660,6 +1662,8 @@ describe("web_fetch tool", () => {
|
|
|
1660
1662
|
);
|
|
1661
1663
|
|
|
1662
1664
|
expect(result.isError).toBe(false);
|
|
1663
|
-
expect(result.content).not.toContain(
|
|
1665
|
+
expect(result.content).not.toContain(
|
|
1666
|
+
"Extracted text content is very short",
|
|
1667
|
+
);
|
|
1664
1668
|
});
|
|
1665
1669
|
});
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Mocks — must precede the migration import
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
mock.module("../security/secure-keys.js", () => ({
|
|
17
|
+
getProviderKeyAsync: async () => null,
|
|
18
|
+
getSecureKeyAsync: async () => null,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
import { servicesConfigMigration } from "../workspace/migrations/006-services-config.js";
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Helpers
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
let workspaceDir: string;
|
|
28
|
+
|
|
29
|
+
function freshWorkspace(): void {
|
|
30
|
+
workspaceDir = join(
|
|
31
|
+
tmpdir(),
|
|
32
|
+
`vellum-migration-006-test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
33
|
+
);
|
|
34
|
+
mkdirSync(workspaceDir, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function writeConfig(data: Record<string, unknown>): void {
|
|
38
|
+
writeFileSync(
|
|
39
|
+
join(workspaceDir, "config.json"),
|
|
40
|
+
JSON.stringify(data, null, 2) + "\n",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function readConfig(): Record<string, unknown> {
|
|
45
|
+
return JSON.parse(readFileSync(join(workspaceDir, "config.json"), "utf-8"));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Setup / teardown
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
freshWorkspace();
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(() => {
|
|
57
|
+
if (existsSync(workspaceDir)) {
|
|
58
|
+
rmSync(workspaceDir, { recursive: true, force: true });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// Tests
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
describe("006-services-config migration", () => {
|
|
67
|
+
test("migrates all legacy fields into services object", async () => {
|
|
68
|
+
writeConfig({
|
|
69
|
+
provider: "openai",
|
|
70
|
+
model: "gpt-4o",
|
|
71
|
+
imageGenModel: "dall-e-3",
|
|
72
|
+
webSearchProvider: "brave",
|
|
73
|
+
otherSetting: true,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
77
|
+
|
|
78
|
+
const config = readConfig();
|
|
79
|
+
|
|
80
|
+
// Legacy fields removed
|
|
81
|
+
expect(config.provider).toBeUndefined();
|
|
82
|
+
expect(config.model).toBeUndefined();
|
|
83
|
+
expect(config.imageGenModel).toBeUndefined();
|
|
84
|
+
expect(config.webSearchProvider).toBeUndefined();
|
|
85
|
+
|
|
86
|
+
// Non-legacy fields preserved
|
|
87
|
+
expect(config.otherSetting).toBe(true);
|
|
88
|
+
|
|
89
|
+
// Services populated correctly
|
|
90
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
91
|
+
expect(services.inference).toEqual({
|
|
92
|
+
mode: "your-own",
|
|
93
|
+
provider: "openai",
|
|
94
|
+
model: "gpt-4o",
|
|
95
|
+
});
|
|
96
|
+
expect(services["image-generation"]).toEqual({
|
|
97
|
+
mode: "your-own",
|
|
98
|
+
provider: "openai",
|
|
99
|
+
model: "dall-e-3",
|
|
100
|
+
});
|
|
101
|
+
expect(services["web-search"]).toEqual({
|
|
102
|
+
mode: "your-own",
|
|
103
|
+
provider: "brave",
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("uses anthropic defaults when legacy fields are non-string", async () => {
|
|
108
|
+
// Legacy fields present but not strings (e.g. null or number) — fall
|
|
109
|
+
// through to defaults
|
|
110
|
+
writeConfig({
|
|
111
|
+
provider: null,
|
|
112
|
+
model: 123,
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
116
|
+
|
|
117
|
+
const config = readConfig();
|
|
118
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
119
|
+
expect(services.inference.provider).toBe("anthropic");
|
|
120
|
+
expect(services.inference.model).toBe("claude-opus-4-6");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("no-op when config.json does not exist", async () => {
|
|
124
|
+
// No config file — should return without error
|
|
125
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
126
|
+
expect(existsSync(join(workspaceDir, "config.json"))).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("no-op when no legacy fields are present (fresh install)", async () => {
|
|
130
|
+
const original = {
|
|
131
|
+
services: {
|
|
132
|
+
inference: {
|
|
133
|
+
mode: "your-own",
|
|
134
|
+
provider: "anthropic",
|
|
135
|
+
model: "claude-opus-4-6",
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
writeConfig(original);
|
|
140
|
+
|
|
141
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
142
|
+
|
|
143
|
+
// Config should be unchanged — migration skipped entirely
|
|
144
|
+
const config = readConfig();
|
|
145
|
+
expect(config).toEqual(original);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test("no-op when no legacy fields are present (already migrated)", async () => {
|
|
149
|
+
const alreadyMigrated = {
|
|
150
|
+
services: {
|
|
151
|
+
inference: { mode: "your-own", provider: "openai", model: "gpt-4o" },
|
|
152
|
+
"image-generation": {
|
|
153
|
+
mode: "your-own",
|
|
154
|
+
provider: "gemini",
|
|
155
|
+
model: "gemini-2.5-flash-image",
|
|
156
|
+
},
|
|
157
|
+
"web-search": { mode: "your-own", provider: "anthropic-native" },
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
writeConfig(alreadyMigrated);
|
|
161
|
+
|
|
162
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
163
|
+
|
|
164
|
+
const config = readConfig();
|
|
165
|
+
expect(config).toEqual(alreadyMigrated);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("idempotency: running migration twice produces same result", async () => {
|
|
169
|
+
writeConfig({
|
|
170
|
+
provider: "openai",
|
|
171
|
+
model: "gpt-4o",
|
|
172
|
+
imageGenModel: "dall-e-3",
|
|
173
|
+
webSearchProvider: "brave",
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
177
|
+
const afterFirst = readConfig();
|
|
178
|
+
|
|
179
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
180
|
+
const afterSecond = readConfig();
|
|
181
|
+
|
|
182
|
+
expect(afterSecond).toEqual(afterFirst);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
test("merges with existing backfilled services object", async () => {
|
|
186
|
+
// Simulates the scenario where backfillConfigDefaults wrote a default
|
|
187
|
+
// services object before migrations run, and legacy fields coexist.
|
|
188
|
+
writeConfig({
|
|
189
|
+
provider: "openai",
|
|
190
|
+
model: "gpt-4o",
|
|
191
|
+
services: {
|
|
192
|
+
inference: {
|
|
193
|
+
mode: "your-own",
|
|
194
|
+
provider: "anthropic",
|
|
195
|
+
model: "claude-opus-4-6",
|
|
196
|
+
},
|
|
197
|
+
"image-generation": {
|
|
198
|
+
mode: "your-own",
|
|
199
|
+
provider: "gemini",
|
|
200
|
+
model: "gemini-2.5-flash-image",
|
|
201
|
+
},
|
|
202
|
+
"web-search": {
|
|
203
|
+
mode: "your-own",
|
|
204
|
+
provider: "anthropic-native",
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
210
|
+
|
|
211
|
+
const config = readConfig();
|
|
212
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
213
|
+
|
|
214
|
+
// Legacy fields should win over backfilled defaults
|
|
215
|
+
expect(services.inference.provider).toBe("openai");
|
|
216
|
+
expect(services.inference.model).toBe("gpt-4o");
|
|
217
|
+
|
|
218
|
+
// Legacy fields removed
|
|
219
|
+
expect(config.provider).toBeUndefined();
|
|
220
|
+
expect(config.model).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
test("preserves extra keys from existing services during merge", async () => {
|
|
224
|
+
// If backfill or future code added extra keys in services, the spread
|
|
225
|
+
// operator should preserve them.
|
|
226
|
+
writeConfig({
|
|
227
|
+
provider: "openai",
|
|
228
|
+
services: {
|
|
229
|
+
inference: {
|
|
230
|
+
mode: "your-own",
|
|
231
|
+
provider: "anthropic",
|
|
232
|
+
model: "claude-opus-4-6",
|
|
233
|
+
extraKey: "should-survive",
|
|
234
|
+
},
|
|
235
|
+
"custom-service": {
|
|
236
|
+
foo: "bar",
|
|
237
|
+
},
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
242
|
+
|
|
243
|
+
const config = readConfig();
|
|
244
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
245
|
+
|
|
246
|
+
// Extra key preserved from spread
|
|
247
|
+
expect(services.inference.extraKey).toBe("should-survive");
|
|
248
|
+
|
|
249
|
+
// Custom service section preserved from top-level spread
|
|
250
|
+
expect(services["custom-service"]).toEqual({ foo: "bar" });
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("gemini image model sets provider to gemini", async () => {
|
|
254
|
+
writeConfig({
|
|
255
|
+
imageGenModel: "gemini-2.5-flash-image",
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
259
|
+
|
|
260
|
+
const config = readConfig();
|
|
261
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
262
|
+
expect(services["image-generation"].provider).toBe("gemini");
|
|
263
|
+
expect(services["image-generation"].model).toBe("gemini-2.5-flash-image");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("dall-e image model sets provider to openai", async () => {
|
|
267
|
+
writeConfig({
|
|
268
|
+
imageGenModel: "dall-e-3",
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
272
|
+
|
|
273
|
+
const config = readConfig();
|
|
274
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
275
|
+
expect(services["image-generation"].provider).toBe("openai");
|
|
276
|
+
expect(services["image-generation"].model).toBe("dall-e-3");
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
test("gracefully handles invalid JSON in config file", async () => {
|
|
280
|
+
writeFileSync(join(workspaceDir, "config.json"), "not-valid-json");
|
|
281
|
+
|
|
282
|
+
// Should return without error
|
|
283
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
284
|
+
|
|
285
|
+
// File should be unchanged
|
|
286
|
+
expect(readFileSync(join(workspaceDir, "config.json"), "utf-8")).toBe(
|
|
287
|
+
"not-valid-json",
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("gracefully handles array config", async () => {
|
|
292
|
+
writeFileSync(join(workspaceDir, "config.json"), JSON.stringify([1, 2, 3]));
|
|
293
|
+
|
|
294
|
+
// Should return without error
|
|
295
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
296
|
+
|
|
297
|
+
// File should be unchanged
|
|
298
|
+
const raw = JSON.parse(
|
|
299
|
+
readFileSync(join(workspaceDir, "config.json"), "utf-8"),
|
|
300
|
+
);
|
|
301
|
+
expect(raw).toEqual([1, 2, 3]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("falls back to existing services values when legacy fields are absent for that service", async () => {
|
|
305
|
+
// Only provider legacy field present; imageGenModel and webSearchProvider
|
|
306
|
+
// are missing. Existing services should be used for image-generation and
|
|
307
|
+
// web-search.
|
|
308
|
+
writeConfig({
|
|
309
|
+
provider: "openai",
|
|
310
|
+
services: {
|
|
311
|
+
"image-generation": {
|
|
312
|
+
mode: "your-own",
|
|
313
|
+
provider: "openai",
|
|
314
|
+
model: "dall-e-2",
|
|
315
|
+
},
|
|
316
|
+
"web-search": {
|
|
317
|
+
mode: "your-own",
|
|
318
|
+
provider: "brave",
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
await servicesConfigMigration.run(workspaceDir);
|
|
324
|
+
|
|
325
|
+
const config = readConfig();
|
|
326
|
+
const services = config.services as Record<string, Record<string, unknown>>;
|
|
327
|
+
|
|
328
|
+
// image-generation: falls back to existing services model value
|
|
329
|
+
expect(services["image-generation"].model).toBe("dall-e-2");
|
|
330
|
+
expect(services["image-generation"].provider).toBe("openai");
|
|
331
|
+
|
|
332
|
+
// web-search: falls back to existing services provider value
|
|
333
|
+
expect(services["web-search"].provider).toBe("brave");
|
|
334
|
+
});
|
|
335
|
+
});
|