Skip to content
Source
Sign inSign up

CodexChatGptHandoff.ts

70 lines2.9 KB

1import type { ChatGptHandoffInput, ChatGptHandoffState } from "@t3tools/contracts";
2import { ProviderSetupError } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as Option from "effect/Option";
5import * as Stream from "effect/Stream";
6import { FetchHttpClient } from "effect/http";
7import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
8import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
9import { makeCodexChatGptAuth } from "./CodexChatGptAuth.ts";
10​
11export function subscribeChatGptHandoff(
12 input: ChatGptHandoffInput,
13 owner: string,
14 endpoints: { discoveryUrl: string; resource: string } | undefined = undefined,
15) {
16 return Stream.unwrap(
17 Effect.gen(function* () {
18 const bytes = new Map<string, Uint8Array>();
19 yield* Effect.addFinalizer(() => Effect.sync(() => bytes.clear()));
20 const store = ServerSecretStore.ServerSecretStore.of({
21 get: Effect.fn("ServerSecretStore.get")(function (key) {
22 return Effect.sync(() => Option.fromUndefinedOr(bytes.get(key)));
23 }),
24 set: Effect.fn("ServerSecretStore.set")(function (key, value) {
25 return Effect.sync(() => {
26 bytes.set(key, value);
27 });
28 }),
29 remove: Effect.fn("ServerSecretStore.remove")(function (key) {
30 return Effect.sync(() => {
31 bytes.delete(key);
32 });
33 }),
34 create: Effect.fn("ServerSecretStore.create")(function () {
35 return Effect.die("Handoff does not create persistent secrets.");
36 }),
37 getOrCreateRandom: Effect.fn("ServerSecretStore.getOrCreateRandom")(function () {
38 return Effect.die("Handoff uses the destination environment identity.");
39 }),
40 });
41 const auth = yield* makeCodexChatGptAuth({
42 ...endpoints,
43 instanceId: input.instanceId,
44 reconnectProfile: input.profile,
45 telemetryFlow: "primary_handoff",
46 defaultReturnUrl: input.returnUrl,
47 }).pipe(
48 Effect.provideService(ServerSecretStore.ServerSecretStore, store),
49 Effect.provideService(
50 ServerEnvironment.ServerEnvironmentIdentity,
51 ServerEnvironment.ServerEnvironmentIdentity.of({
52 getEnvironmentId: Effect.succeed(input.environmentId),
53 }),
54 ),
55 Effect.provide(FetchHttpClient.layer),
56 );
57 yield* auth.controller.start(owner, Effect.void, "chatgpt", input.returnUrl, "server");
58 return auth.controller.subscribe(owner).pipe(
59 Stream.takeUntil((state) => ["succeeded", "failed", "cancelled"].includes(state.phase)),
60 Stream.mapEffect((state): Effect.Effect<ChatGptHandoffState, ProviderSetupError> =>
61 state.phase === "succeeded"
62 ? auth.exportProfile.pipe(
63 Effect.map((profile) => ({ phase: "finished" as const, profile })),
64 )
65 : Effect.succeed({ phase: "auth" as const, state }),
66 ),
67 );
68 }),
69 );