import { ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Stream from "effect/Stream";
import { ProviderAuthService } from "../Services/ProviderAuthService.ts";
import { ProviderInstanceRegistry } from "../Services/ProviderInstanceRegistry.ts";
import { ProviderService } from "../Services/ProviderService.ts";
import { ProviderSessionDirectory } from "../Services/ProviderSessionDirectory.ts";
export const makeProviderAuthService = Effect.gen(function* () {
const registry = yield* ProviderInstanceRegistry;
const providers = yield* ProviderService;
const directory = yield* ProviderSessionDirectory;
const getController = Effect.fn("ProviderAuthService.getController")(function* (
instanceId: ProviderInstanceId,
operation: string,
) {
const instance = yield* registry.getInstance(instanceId);
if (!instance?.auth) {
return yield* new ProviderSetupError({
instanceId,
operation,
detail: instance
? "This provider does not support sign-in in T3 Code."
: "This provider instance is no longer available.",
});
}
return instance.auth;
});
const stopSessions = Effect.fn("ProviderAuthService.stopSessions")(function* (
instanceId: ProviderInstanceId,
) {
const bindings = yield* directory.listBindings().pipe(
Effect.mapError(
() =>
new ProviderSetupError({
instanceId,
operation: "stopSessions",
detail: "Could not read the provider's active sessions. Try again.",
}),
),
);
const sessions = yield* providers.listSessions();
const threadIds = new Set(
bindings
.filter(
(binding) => binding.providerInstanceId === instanceId && binding.status !== "stopped",
)
.map((binding) => binding.threadId),
);
for (const session of sessions) {
if (session.providerInstanceId === instanceId) {
threadIds.add(session.threadId);
}
}
yield* Effect.forEach(
threadIds,
(threadId) =>
providers.stopSession({ threadId }).pipe(
Effect.mapError(
() =>
new ProviderSetupError({
instanceId,
operation: "stopSessions",
detail: "Could not stop all sessions for this provider. Try again.",
}),
),
),
{ discard: true },
);
});
return ProviderAuthService.of({
start: Effect.fn("ProviderAuthService.start")(function* (input, ownerSessionId) {
const auth = yield* getController(input.instanceId, "start");
return yield* auth.start(ownerSessionId, stopSessions(input.instanceId));
}),
complete: Effect.fn("ProviderAuthService.complete")(function* (input, ownerSessionId) {
const auth = yield* getController(input.instanceId, "complete");
return yield* auth.complete(ownerSessionId, input);
}),
cancel: Effect.fn("ProviderAuthService.cancel")(function* (input, ownerSessionId) {
const auth = yield* getController(input.instanceId, "cancel");
return yield* auth.cancel(ownerSessionId, input.flowId);
}),
logout: Effect.fn("ProviderAuthService.logout")(function* (input) {
const auth = yield* getController(input.instanceId, "logout");
return yield* auth.logout(stopSessions(input.instanceId));
}),
subscribe: (input, ownerSessionId) =>
Effect.gen(function* () {
const changes = yield* registry.subscribeChanges;
const initial = yield* getController(input.instanceId, "subscribe");
return Stream.concat(
Stream.succeed(initial),
Stream.fromSubscription(changes).pipe(
Stream.mapEffect(() => getController(input.instanceId, "subscribe")),
),
).pipe(
Stream.changesWith((previous, next) => previous === next),
Stream.switchMap((auth) => auth.subscribe(ownerSessionId)),
);
}).pipe(Stream.unwrap),
tryHandlePromptCommand: Effect.fn("ProviderAuthService.tryHandlePromptCommand")(
function* (input) {
const instance = yield* registry.getInstance(input.instanceId);
if (!instance?.auth?.isLogoutPrompt?.(input.text, input.hasAttachments)) {
return false;
}
yield* instance.auth.logout(stopSessions(input.instanceId));
return true;
},
),
});
});
export const ProviderAuthServiceLive = Layer.effect(ProviderAuthService, makeProviderAuthService);