Open History to see the last commit
1import { resolveManagedCodexHomeLayout } from "./CodexManagedHome.ts";
2import { CodexSettings, ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts";
3import * as Effect from "effect/Effect";
4import * as FileSystem from "effect/FileSystem";
5import * as Path from "effect/Path";
6import * as Schema from "effect/Schema";
7import * as ServerConfig from "../config.ts";
8import * as CodexManagedTokenBroker from "./CodexManagedTokenBroker.ts";
9import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
10import { resolveSelfInvocation, selfInvocationArgs } from "@t3tools/shared/nodeRuntime";
11import * as CodexInstallation from "./CodexInstallation.ts";
12import { makeCodexChatGptAuth } from "./CodexChatGptAuth.ts";
13import { COMMAND_TIMEOUT_MS } from "./CodexManagedTokenProtocol.ts";
14import { materializeCodexShadowHome } from "./Drivers/CodexHomeLayout.ts";
15
16export interface CodexEffectiveRuntime {
17 readonly config: CodexSettings;
18 readonly environment: NodeJS.ProcessEnv;
19 readonly revision: string;
20}
21const decodeSettings = Schema.decodeSync(CodexSettings);
22const managedCodexLaunchArgs = (command: string, args: ReadonlyArray<string>) =>
23 [
24 'model_provider="openai_token_sharing"',
25 // Codex persists this provider ID in rollouts and needs it unchanged to resume.
26 `model_providers.openai_token_sharing={name="OpenAI Token Sharing",base_url="https://api.openai.com/v1",model_catalog_url="https://api.openai.com/v1/models",wire_api="responses",requires_openai_auth=false,supports_websockets=false,auth={command=${JSON.stringify(command)},args=${JSON.stringify(args)},timeout_ms=${COMMAND_TIMEOUT_MS},refresh_interval_ms=0}}`,
27 "features.api_key_model_discovery=true",
28 "features.multi_agent=false",
29 "features.multi_agent_v2=false",
30 ]
31 .map((value) => `-c ${JSON.stringify(value)}`)
32 .join(" ");
33
34export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(function* (options: {
35 readonly instanceId: ProviderInstanceId;
36 readonly enabled: boolean;
37 readonly environment: NodeJS.ProcessEnv;
38 readonly config: CodexSettings;
39}) {
40 const installation = yield* CodexInstallation.CodexInstallation;
41 const config = yield* ServerConfig.ServerConfig;
42 const fs = yield* FileSystem.FileSystem;
43 const path = yield* Path.Path;
44 const broker = yield* CodexManagedTokenBroker.CodexManagedTokenBroker;
45 const environmentIdentity = yield* ServerEnvironment.ServerEnvironmentIdentity;
46 const environmentId = yield* environmentIdentity.getEnvironmentId;
47 const invocation = yield* resolveSelfInvocation();
48 const auth = yield* makeCodexChatGptAuth({
49 instanceId: options.instanceId,
50 defaultReturnUrl: new URL(
51 "/welcome",
52 config.devUrl ?? `http://localhost:${config.port}`,
53 ).toString(),
54 });
55 const homeLayout = yield* resolveManagedCodexHomeLayout(
56 config.stateDir,
57 options.instanceId,
58 options.config,
59 );
60 const homePath = homeLayout.effectiveHomePath ?? homeLayout.sharedHomePath;
61 const resolve = Effect.gen(function* () {
62 // Establish renewal availability before auth.access can consume a refresh token.
63 const endpoint = yield* broker.endpoint.pipe(
64 Effect.mapError(
65 (cause) =>
66 new ProviderSetupError({
67 instanceId: options.instanceId,
68 operation: "runtime",
69 detail:
70 "Could not prepare the private managed Codex token listener. Restart T3 and retry.",
71 cause,
72 }),
73 ),
74 );
75 const executable = yield* installation.acquire().pipe(
76 Effect.mapError(
77 () =>
78 new ProviderSetupError({
79 instanceId: options.instanceId,
80 operation: "install",
81 detail: "Set up managed Codex before starting a session.",
82 }),
83 ),
84 );
85 const credentials = yield* auth.access;
86 const profileKey = yield* auth.profileKey(credentials);
87 const authArgs = selfInvocationArgs(invocation, [
88 "__managed-chatgpt-token",
89 endpoint,
90 options.instanceId,
91 environmentId,
92 profileKey,
93 ]);
94 yield* materializeCodexShadowHome(homeLayout).pipe(
95 Effect.provideService(FileSystem.FileSystem, fs),
96 Effect.provideService(Path.Path, path),
97 Effect.mapError(
98 (cause) =>
99 new ProviderSetupError({
100 instanceId: options.instanceId,
101 operation: "runtime",
102 detail: cause.message,
103 }),
104 ),
105 );
106 yield* fs.makeDirectory(homePath, { recursive: true }).pipe(
107 Effect.mapError(
108 () =>
109 new ProviderSetupError({
110 instanceId: options.instanceId,
111 operation: "runtime",
112 detail: "Could not prepare the managed Codex runtime.",
113 }),
114 ),
115 );
116 // Ambient CLI overrides cannot redirect a T3-owned token to a different provider.
117 const environment: NodeJS.ProcessEnv = {
118 ...options.environment,
119 CODEX_HOME: homePath,
120 };
121 delete environment.ACCESS_TOKEN;
122 delete environment.T3CODE_CODEX_LAUNCH_ARGS;
123 delete environment.OPENAI_API_KEY;
124 delete environment.OPENAI_BASE_URL;
125 return {
126 config: decodeSettings({
127 enabled: options.enabled,
128 setupMode: "managed",
129 binaryPath: executable.executablePath,
130 homePath,
131 launchArgs: managedCodexLaunchArgs(invocation.command, authArgs),
132 }),
133 environment,
134 revision: profileKey,
135 } satisfies CodexEffectiveRuntime;
136 });
137 return { auth, resolve, installation, homePath, homeLayout };
138});