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