import { ProjectId, ThreadId } from "@t3tools/contracts"; import { assert, it } from "@effect/vitest"; import { Effect, Layer, Option } from "effect"; import * as SqlClient from "effect/unstable/sql/SqlClient"; import { SqlitePersistenceMemory } from "./Sqlite.ts"; import { ProjectionProjectRepositoryLive } from "./ProjectionProjects.ts"; import { ProjectionThreadRepositoryLive } from "./ProjectionThreads.ts"; import { ProjectionProjectRepository } from "../Services/ProjectionProjects.ts"; import { ProjectionThreadRepository } from "../Services/ProjectionThreads.ts"; const projectionRepositoriesLayer = it.layer( Layer.mergeAll( ProjectionProjectRepositoryLive.pipe(Layer.provideMerge(SqlitePersistenceMemory)), ProjectionThreadRepositoryLive.pipe(Layer.provideMerge(SqlitePersistenceMemory)), SqlitePersistenceMemory, ), ); projectionRepositoriesLayer("Projection repositories", (it) => { it.effect("stores SQL NULL for missing project model options", () => Effect.gen(function* () { const projects = yield* ProjectionProjectRepository; const sql = yield* SqlClient.SqlClient; yield* projects.upsert({ projectId: ProjectId.make("project-null-options"), title: "Null options project", workspaceRoot: "/tmp/project-null-options", defaultModelSelection: { provider: "codex", model: "gpt-5.4", }, scripts: [], createdAt: "2026-03-24T00:00:00.000Z", updatedAt: "2026-03-24T00:00:00.000Z", deletedAt: null, }); const rows = yield* sql<{ readonly defaultModelSelection: string | null; }>` SELECT default_model_selection_json AS "defaultModelSelection" FROM projection_projects WHERE project_id = 'project-null-options' `; const row = rows[0]; if (!row) { return yield* Effect.fail(new Error("Expected projection_projects row to exist.")); } assert.strictEqual( row.defaultModelSelection, JSON.stringify({ provider: "codex", model: "gpt-5.4", }), ); const persisted = yield* projects.getById({ projectId: ProjectId.make("project-null-options"), }); assert.deepStrictEqual(Option.getOrNull(persisted)?.defaultModelSelection, { provider: "codex", model: "gpt-5.4", }); }), ); it.effect("stores JSON for thread model options", () => Effect.gen(function* () { const threads = yield* ProjectionThreadRepository; const sql = yield* SqlClient.SqlClient; yield* threads.upsert({ threadId: ThreadId.make("thread-null-options"), projectId: ProjectId.make("project-null-options"), title: "Null options thread", modelSelection: { provider: "claudeAgent", model: "claude-opus-4-6", }, runtimeMode: "full-access", interactionMode: "default", branch: null, worktreePath: null, latestTurnId: null, createdAt: "2026-03-24T00:00:00.000Z", updatedAt: "2026-03-24T00:00:00.000Z", archivedAt: null, latestUserMessageAt: null, pendingApprovalCount: 0, pendingUserInputCount: 0, hasActionableProposedPlan: 0, deletedAt: null, }); const rows = yield* sql<{ readonly modelSelection: string | null; }>` SELECT model_selection_json AS "modelSelection" FROM projection_threads WHERE thread_id = 'thread-null-options' `; const row = rows[0]; if (!row) { return yield* Effect.fail(new Error("Expected projection_threads row to exist.")); } assert.strictEqual( row.modelSelection, JSON.stringify({ provider: "claudeAgent", model: "claude-opus-4-6", }), ); const persisted = yield* threads.getById({ threadId: ThreadId.make("thread-null-options"), }); assert.deepStrictEqual(Option.getOrNull(persisted)?.modelSelection, { provider: "claudeAgent", model: "claude-opus-4-6", }); }), ); });