import { defaultInstanceIdForDriver, ProviderDriverKind, type ThreadId } from "@t3tools/contracts"; import * as DateTime from "effect/DateTime"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Schema from "effect/Schema"; import * as ProviderSessionRuntime from "../../persistence/ProviderSessionRuntime.ts"; import { ProviderSessionDirectoryPersistenceError, ProviderValidationError } from "../Errors.ts"; import { ProviderSessionDirectory, type ProviderRuntimeBinding, type ProviderRuntimeBindingWithMetadata, type ProviderSessionDirectoryShape, } from "../Services/ProviderSessionDirectory.ts"; const decodeProviderDriverKindValue = Schema.decodeUnknownEffect(ProviderDriverKind); function toPersistenceError(operation: string) { return (cause: unknown) => new ProviderSessionDirectoryPersistenceError({ operation, detail: `Failed to execute ${operation}.`, cause, }); } function decodeProviderDriverKind( providerName: string, operation: string, ): Effect.Effect { return decodeProviderDriverKindValue(providerName).pipe( Effect.mapError( (cause) => new ProviderSessionDirectoryPersistenceError({ operation, detail: `Unknown persisted provider '${providerName}'.`, cause, }), ), ); } function isRecord(value: unknown): value is Record { return value !== null && typeof value === "object" && !Array.isArray(value); } function mergeRuntimePayload( existing: unknown | null, next: unknown | null | undefined, ): unknown | null { if (next === undefined) { return existing ?? null; } if (isRecord(existing) && isRecord(next)) { return { ...existing, ...next }; } return next; } function toRuntimeBinding( runtime: ProviderSessionRuntime.ProviderSessionRuntime, operation: string, ): Effect.Effect { return decodeProviderDriverKind(runtime.providerName, operation).pipe( Effect.map( (provider) => ({ threadId: runtime.threadId, provider, // Migration boundary only: rows written before the instance split // have a null provider_instance_id. Promote them as they leave // persistence so hot routing code never has to infer an instance // from a driver kind. providerInstanceId: runtime.providerInstanceId ?? defaultInstanceIdForDriver(provider), adapterKey: runtime.adapterKey, runtimeMode: runtime.runtimeMode, status: runtime.status, resumeCursor: runtime.resumeCursor, runtimePayload: runtime.runtimePayload, lastSeenAt: runtime.lastSeenAt, }) satisfies ProviderRuntimeBindingWithMetadata, ), ); } const makeProviderSessionDirectory = Effect.gen(function* () { const repository = yield* ProviderSessionRuntime.ProviderSessionRuntimeRepository; const getBinding = (threadId: ThreadId) => repository.getByThreadId({ threadId }).pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.getBinding:getByThreadId")), Effect.flatMap((runtime) => Option.match(runtime, { onNone: () => Effect.succeed(Option.none()), onSome: (value) => toRuntimeBinding(value, "ProviderSessionDirectory.getBinding").pipe( Effect.map((binding) => Option.some(binding)), ), }), ), ); const upsert: ProviderSessionDirectoryShape["upsert"] = Effect.fn(function* (binding) { const existing = yield* repository .getByThreadId({ threadId: binding.threadId }) .pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:getByThreadId"))); const existingRuntime = Option.getOrUndefined(existing); const resolvedThreadId = binding.threadId ?? existingRuntime?.threadId; if (!resolvedThreadId) { return yield* new ProviderValidationError({ operation: "ProviderSessionDirectory.upsert", issue: "threadId must be a non-empty string.", }); } const now = DateTime.formatIso(yield* DateTime.now); const providerChanged = existingRuntime !== undefined && existingRuntime.providerName !== binding.provider; const providerInstanceId = binding.providerInstanceId ?? (!providerChanged ? existingRuntime?.providerInstanceId : null); if (providerInstanceId === null || providerInstanceId === undefined) { return yield* new ProviderValidationError({ operation: "ProviderSessionDirectory.upsert", issue: "providerInstanceId is required for provider session runtime bindings.", }); } yield* repository .upsert({ threadId: resolvedThreadId, providerName: binding.provider, providerInstanceId, adapterKey: binding.adapterKey ?? (providerChanged ? binding.provider : (existingRuntime?.adapterKey ?? binding.provider)), runtimeMode: binding.runtimeMode ?? existingRuntime?.runtimeMode ?? "full-access", status: binding.status ?? existingRuntime?.status ?? "running", lastSeenAt: now, resumeCursor: binding.resumeCursor !== undefined ? binding.resumeCursor : (existingRuntime?.resumeCursor ?? null), runtimePayload: mergeRuntimePayload( existingRuntime?.runtimePayload ?? null, binding.runtimePayload, ), }) .pipe(Effect.mapError(toPersistenceError("ProviderSessionDirectory.upsert:upsert"))); }); const getProvider: ProviderSessionDirectoryShape["getProvider"] = (threadId) => getBinding(threadId).pipe( Effect.flatMap((binding) => Option.match(binding, { onSome: (value) => Effect.succeed(value.provider), onNone: () => Effect.fail( new ProviderSessionDirectoryPersistenceError({ operation: "ProviderSessionDirectory.getProvider", detail: `No persisted provider binding found for thread '${threadId}'.`, }), ), }), ), ); const listThreadIds: ProviderSessionDirectoryShape["listThreadIds"] = () => repository.list().pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.listThreadIds:list")), Effect.map((rows) => rows.map((row) => row.threadId)), ); const listBindings: ProviderSessionDirectoryShape["listBindings"] = () => repository.list().pipe( Effect.mapError(toPersistenceError("ProviderSessionDirectory.listBindings:list")), Effect.flatMap((rows) => Effect.forEach( rows, (row) => toRuntimeBinding(row, "ProviderSessionDirectory.listBindings"), { concurrency: "unbounded" }, ), ), ); return { upsert, getProvider, getBinding, listThreadIds, listBindings, } satisfies ProviderSessionDirectoryShape; }); export const ProviderSessionDirectoryLive = Layer.effect( ProviderSessionDirectory, makeProviderSessionDirectory, ); export function makeProviderSessionDirectoryLive() { return Layer.effect(ProviderSessionDirectory, makeProviderSessionDirectory); }