271 lines · 9.3 KB · TypeScript
RawDownload
// @effect-diagnostics nodeBuiltinImport:offimport * as NodeFS from "node:fs";import * as NodeOS from "node:os";import * as NodePath from "node:path"; import * as NodeServices from "@effect/platform-node/NodeServices";import { ProviderDriverKind, ThreadId } from "@t3tools/contracts";import { it, assert } from "@effect/vitest";import { assertSome } from "@effect/vitest/utils";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";import * as Option from "effect/Option";import * as SqlClient from "effect/unstable/sql/SqlClient"; import {  makeSqlitePersistenceLive,  SqlitePersistenceMemory,} from "../../persistence/Layers/Sqlite.ts";import * as ProviderSessionRuntime from "../../persistence/ProviderSessionRuntime.ts";import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";import { ProviderSessionDirectoryLive } from "./ProviderSessionDirectory.ts"; function makeDirectoryLayer<E, R>(persistenceLayer: Layer.Layer<SqlClient.SqlClient, E, R>) {  const runtimeRepositoryLayer = ProviderSessionRuntime.layer.pipe(Layer.provide(persistenceLayer));  return Layer.mergeAll(    runtimeRepositoryLayer,    ProviderSessionDirectoryLive.pipe(Layer.provide(runtimeRepositoryLayer)),    NodeServices.layer,  );} it.layer(makeDirectoryLayer(SqlitePersistenceMemory))("ProviderSessionDirectoryLive", (it) => {  it("upserts and reads thread bindings", () =>    Effect.gen(function* () {      const directory = yield* ProviderSessionDirectory;      const runtimeRepository = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository;       const initialThreadId = ThreadId.make("thread-1");       yield* directory.upsert({        provider: ProviderDriverKind.make("codex"),        threadId: initialThreadId,      });       const provider = yield* directory.getProvider(initialThreadId);      assert.equal(provider, "codex");      const resolvedBinding = yield* directory.getBinding(initialThreadId);      assertSome(resolvedBinding, {        threadId: initialThreadId,        provider: ProviderDriverKind.make("codex"),      });      if (Option.isSome(resolvedBinding)) {        assert.equal(resolvedBinding.value.threadId, initialThreadId);      }       const nextThreadId = ThreadId.make("thread-2");       yield* directory.upsert({        provider: ProviderDriverKind.make("codex"),        threadId: nextThreadId,      });      const updatedBinding = yield* directory.getBinding(nextThreadId);      assert.equal(Option.isSome(updatedBinding), true);      if (Option.isSome(updatedBinding)) {        assert.equal(updatedBinding.value.threadId, nextThreadId);      }       const runtime = yield* runtimeRepository.getByThreadId({ threadId: nextThreadId });      assert.equal(Option.isSome(runtime), true);      if (Option.isSome(runtime)) {        assert.equal(runtime.value.threadId, nextThreadId);        assert.equal(runtime.value.status, "running");        assert.equal(runtime.value.providerName, "codex");      }       const threadIds = yield* directory.listThreadIds();      assert.deepEqual(threadIds, [nextThreadId]);    }));   it("persists runtime fields and merges payload updates", () =>    Effect.gen(function* () {      const directory = yield* ProviderSessionDirectory;      const runtimeRepository = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository;       const threadId = ThreadId.make("thread-runtime");       yield* directory.upsert({        provider: ProviderDriverKind.make("codex"),        threadId,        status: "starting",        resumeCursor: {          threadId: "provider-thread-runtime",        },        runtimePayload: {          cwd: "/tmp/project",          model: "gpt-5-codex",        },      });       yield* directory.upsert({        provider: ProviderDriverKind.make("codex"),        threadId,        status: "running",        runtimePayload: {          activeTurnId: "turn-1",        },      });       const runtime = yield* runtimeRepository.getByThreadId({ threadId });      assert.equal(Option.isSome(runtime), true);      if (Option.isSome(runtime)) {        assert.equal(runtime.value.threadId, threadId);        assert.equal(runtime.value.status, "running");        assert.deepEqual(runtime.value.resumeCursor, {          threadId: "provider-thread-runtime",        });        assert.deepEqual(runtime.value.runtimePayload, {          cwd: "/tmp/project",          model: "gpt-5-codex",          activeTurnId: "turn-1",        });      }    }));   it("lists persisted bindings with metadata in oldest-first order", () =>    Effect.gen(function* () {      const directory = yield* ProviderSessionDirectory;      const runtimeRepository = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository;       const olderThreadId = ThreadId.make("thread-runtime-older");      const newerThreadId = ThreadId.make("thread-runtime-newer");       yield* runtimeRepository.upsert({        threadId: newerThreadId,        providerName: "codex",        providerInstanceId: null,        adapterKey: "codex",        runtimeMode: "full-access",        status: "running",        lastSeenAt: "2026-04-14T12:05:00.000Z",        resumeCursor: {          opaque: "resume-newer",        },        runtimePayload: {          cwd: "/tmp/newer",        },      });       yield* runtimeRepository.upsert({        threadId: olderThreadId,        providerName: "claudeAgent",        providerInstanceId: null,        adapterKey: "claudeAgent",        runtimeMode: "approval-required",        status: "starting",        lastSeenAt: "2026-04-14T12:00:00.000Z",        resumeCursor: {          opaque: "resume-older",        },        runtimePayload: {          cwd: "/tmp/older",        },      });       const bindings = yield* directory.listBindings();       assert.deepEqual(bindings, [        {          threadId: olderThreadId,          provider: ProviderDriverKind.make("claudeAgent"),          adapterKey: "claudeAgent",          runtimeMode: "approval-required",          status: "starting",          lastSeenAt: "2026-04-14T12:00:00.000Z",          resumeCursor: {            opaque: "resume-older",          },          runtimePayload: {            cwd: "/tmp/older",          },        },        {          threadId: newerThreadId,          provider: ProviderDriverKind.make("codex"),          adapterKey: "codex",          runtimeMode: "full-access",          status: "running",          lastSeenAt: "2026-04-14T12:05:00.000Z",          resumeCursor: {            opaque: "resume-newer",          },          runtimePayload: {            cwd: "/tmp/newer",          },        },      ]);    }));   it("resets adapterKey to the new provider when provider changes without an explicit adapter key", () =>    Effect.gen(function* () {      const directory = yield* ProviderSessionDirectory;      const runtimeRepository = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository;      const threadId = ThreadId.make("thread-provider-change");       yield* runtimeRepository.upsert({        threadId,        providerName: "claudeAgent",        providerInstanceId: null,        adapterKey: "claudeAgent",        runtimeMode: "full-access",        status: "running",        lastSeenAt: "2026-01-01T00:00:00.000Z",        resumeCursor: null,        runtimePayload: null,      });       yield* directory.upsert({        provider: ProviderDriverKind.make("codex"),        threadId,      });       const runtime = yield* runtimeRepository.getByThreadId({ threadId });      assert.equal(Option.isSome(runtime), true);      if (Option.isSome(runtime)) {        assert.equal(runtime.value.providerName, "codex");        assert.equal(runtime.value.adapterKey, "codex");      }    }));   it("rehydrates persisted mappings across layer restart", () =>    Effect.gen(function* () {      const tempDir = NodeFS.mkdtempSync(NodePath.join(NodeOS.tmpdir(), "t3-provider-directory-"));      const dbPath = NodePath.join(tempDir, "orchestration.sqlite");      const directoryLayer = makeDirectoryLayer(makeSqlitePersistenceLive(dbPath));       const threadId = ThreadId.make("thread-restart");       yield* Effect.gen(function* () {        const directory = yield* ProviderSessionDirectory;        yield* directory.upsert({          provider: ProviderDriverKind.make("codex"),          threadId,        });      }).pipe(Effect.provide(directoryLayer));       yield* Effect.gen(function* () {        const directory = yield* ProviderSessionDirectory;        const sql = yield* SqlClient.SqlClient;        const provider = yield* directory.getProvider(threadId);        assert.equal(provider, "codex");         const resolvedBinding = yield* directory.getBinding(threadId);        assertSome(resolvedBinding, {          threadId,          provider: ProviderDriverKind.make("codex"),        });        if (Option.isSome(resolvedBinding)) {          assert.equal(resolvedBinding.value.threadId, threadId);        }         const legacyTableRows = yield* sql<{ readonly name: string }>`          SELECT name          FROM sqlite_master          WHERE type = 'table' AND name = 'provider_sessions'        `;        assert.equal(legacyTableRows.length, 0);      }).pipe(Effect.provide(directoryLayer));       NodeFS.rmSync(tempDir, { recursive: true, force: true });    }));});