1// @effect-diagnostics nodeBuiltinImport:off - checks the host's default Codex directory without writing to it.
2import * as NodeOS from "node:os";
3import * as NodeServices from "@effect/platform-node/NodeServices";
4import { assert, it } from "@effect/vitest";
5import { CodexSettings, EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
6import * as Clock from "effect/Clock";
7import * as Effect from "effect/Effect";
8import * as FileSystem from "effect/FileSystem";
9import * as Path from "effect/Path";
10import * as Layer from "effect/Layer";
11import * as Option from "effect/Option";
12import * as Schema from "effect/Schema";
13import { HttpClient, HttpClientResponse } from "effect/http";
14import * as ServerConfig from "../config.ts";
15import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
16import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
17import * as CodexInstallation from "./CodexInstallation.ts";
18import { makeCodexManagedRuntime } from "./CodexManagedRuntime.ts";
19import * as ProviderCredentialStore from "./ProviderCredentialStore.ts";
20import { codexAppServerArgs } from "./codexLaunchArgs.ts";
21import { resolveManagedCodexHomeLayout } from "./CodexManagedHome.ts";
22
23const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
24const decodeSettings = Schema.decodeSync(CodexSettings);
25it.effect("managed home defaults to the global Codex home and honors configured home paths", () =>
26 Effect.gen(function* () {
27 const path = yield* Path.Path;
28 const primary = yield* resolveManagedCodexHomeLayout(
29 "/t3-state",
30 ProviderInstanceId.make("codex"),
31 decodeSettings({}),
32 );
33 assert.equal(primary.sharedHomePath, path.join(NodeOS.homedir(), ".codex"));
34 assert.equal(primary.mode, "direct");
35 const additional = yield* resolveManagedCodexHomeLayout(
36 "/t3-state",
37 ProviderInstanceId.make("codex-work"),
38 decodeSettings({}),
39 );
40 assert.equal(additional.sharedHomePath, primary.sharedHomePath);
41 assert.equal(additional.mode, "authOverlay");
42 const configured = yield* resolveManagedCodexHomeLayout(
43 "/t3-state",
44 ProviderInstanceId.make("codex-work"),
45 decodeSettings({ homePath: "/custom/shared", shadowHomePath: "/custom/shadow" }),
46 );
47 assert.equal(configured.sharedHomePath, "/custom/shared");
48 assert.equal(configured.effectiveHomePath, "/custom/shadow");
49 }).pipe(Effect.provide(NodeServices.layer)),
50);
51it.effect.each(
52 (["managed", "local"] as const).flatMap((source) =>
53 (["primary", "additional"] as const).map((account) => ({ source, account })),
54 ),
55)(
56 "$source Codex $account account shares home state without routing owned tokens through ambient CLI overrides",
57 ({ source, account }) =>
58 Effect.gen(function* () {
59 const instanceId = ProviderInstanceId.make(
60 account === "primary" ? "codex" : "codex-personal",
61 );
62 const fs = yield* FileSystem.FileSystem;
63 const path = yield* Path.Path;
64 const sharedHome = yield* fs.makeTempDirectoryScoped({ prefix: "codex-shared-home-" });
65 yield* fs.writeFileString(path.join(sharedHome, "auth.json"), "native-auth-unchanged");
66 yield* fs.writeFileString(path.join(sharedHome, "config.toml"), "# shared config\n");
67 const data = new Map<string, Uint8Array>();
68 const secrets = ServerSecretStore.ServerSecretStore.of({
69 get: (key) => Effect.sync(() => Option.fromUndefinedOr(data.get(key))),
70 set: (key, value) =>
71 Effect.sync(() => {
72 data.set(key, value);
73 }),
74 remove: (key) =>
75 Effect.sync(() => {
76 data.delete(key);
77 }),
78 create: () => Effect.die("unused"),
79 getOrCreateRandom: () => Effect.die("unused"),
80 });
81 let leases = 0;
82 const executable = {
83 executablePath:
84 source === "managed" ? "/isolated/tools/codex/0.156.1/bin/codex" : "/user/bin/codex",
85 managedVersionDirectory: source === "managed" ? "/isolated/tools/codex/0.156.1" : null,
86 source,
87 version: "0.156.1",
88 };
89 const layerInstaller = Layer.mock(CodexInstallation.CodexInstallation)({
90 managedDirectory: "/isolated/tools/codex",
91 resolve: () => Effect.succeed(executable),
92 acquire: () =>
93 Effect.gen(function* () {
94 leases++;
95 yield* Effect.addFinalizer(() =>
96 Effect.sync(() => {
97 leases--;
98 }),
99 );
100 return executable;
101 }),
102 });
103 yield* Effect.gen(function* () {
104 const store = yield* ProviderCredentialStore.make("codex-chatgpt", instanceId);
105 const json = yield* encodeJson({
106 clientId: "oaiapp_test",
107 accessToken: "dummy-owned-access",
108 refreshToken: "dummy-refresh",
109 expiresAt: (yield* Clock.currentTimeMillis) + 3_600_000,
110 earliestRefreshAt: null,
111 scopes: ["chatgpt.tokens.use.direct"],
112 subject: "test-user",
113 email: null,
114 });
115 yield* store.set(new TextEncoder().encode(json));
116 const ambient = {
117 CODEX_HOME: "/user/.codex",
118 OPENAI_API_KEY: "dummy-global-key",
119 OPENAI_BASE_URL: "https://user-proxy.test",
120 T3CODE_CODEX_LAUNCH_ARGS: "--config model_provider=global-proxy",
121 PATH: "/usr/bin",
122 };
123 const runtime = yield* makeCodexManagedRuntime({
124 instanceId,
125 enabled: true,
126 config: decodeSettings({ setupMode: "managed", homePath: sharedHome }),
127 environment: ambient,
128 });
129 yield* Effect.gen(function* () {
130 const effective = yield* runtime.resolve;
131 assert.strictEqual(leases, 1);
132 assert.strictEqual(effective.config.binaryPath, executable.executablePath);
133 assert.notStrictEqual(effective.config.homePath, ambient.CODEX_HOME);
134 assert.equal(runtime.homeLayout.sharedHomePath, sharedHome);
135 if (account === "primary") {
136 assert.equal(effective.config.homePath, sharedHome);
137 assert.equal(runtime.homeLayout.mode, "direct");
138 } else {
139 assert.include(effective.config.homePath, instanceId);
140 assert.include(effective.config.homePath, "userdata/providers/codex");
141 assert.equal(runtime.homeLayout.mode, "authOverlay");
142 assert.equal(
143 yield* fs.readLink(path.join(effective.config.homePath, "sessions")),
144 path.join(sharedHome, "sessions"),
145 );
146 assert.equal(
147 yield* fs.readLink(path.join(effective.config.homePath, "config.toml")),
148 path.join(sharedHome, "config.toml"),
149 );
150 assert.isFalse(yield* fs.exists(path.join(effective.config.homePath, "auth.json")));
151 }
152 assert.strictEqual(effective.environment.ACCESS_TOKEN, "dummy-owned-access");
153 assert.isUndefined(effective.environment.OPENAI_API_KEY);
154 assert.isUndefined(effective.environment.OPENAI_BASE_URL);
155 assert.isUndefined(effective.environment.T3CODE_CODEX_LAUNCH_ARGS);
156 const args = codexAppServerArgs(effective.config.launchArgs);
157 assert.include(
158 args,
159 'model_providers.openai_token_sharing.base_url="https://api.openai.com/v1"',
160 );
161 assert.include(
162 args,
163 'model_providers.openai_token_sharing.model_catalog_url="https://api.openai.com/v1/models"',
164 );
165 assert.include(args, "features.api_key_model_discovery=true");
166 assert.include(args, "features.multi_agent=false");
167 assert.include(args, "features.multi_agent_v2=false");
168 assert.notInclude(effective.config.launchArgs, "model_catalog_json");
169 assert.notInclude(effective.config.launchArgs, "x-openai-chatpass-test");
170 assert.include(args, "model_providers.openai_token_sharing.supports_websockets=false");
171 assert.include(args, "model_providers.openai_token_sharing.requires_openai_auth=false");
172 assert.notInclude(effective.config.launchArgs, "dummy-owned-access");
173 assert.strictEqual(ambient.CODEX_HOME, "/user/.codex");
174 }).pipe(Effect.scoped);
175 assert.strictEqual(leases, 0);
176 yield* runtime.auth.controller.logout(Effect.void);
177 assert.equal(
178 yield* fs.readFileString(path.join(sharedHome, "auth.json")),
179 "native-auth-unchanged",
180 );
181 }).pipe(
182 Effect.provideService(ServerSecretStore.ServerSecretStore, secrets),
183 Effect.provideService(ServerEnvironment.ServerEnvironmentIdentity, {
184 getEnvironmentId: Effect.succeed(
185 EnvironmentId.make("00000000-0000-4000-8000-000000000001"),
186 ),
187 }),
188 Effect.provide(layerInstaller),
189 Effect.provideService(
190 HttpClient.HttpClient,
191 HttpClient.make((request) =>
192 Effect.sync(() => {
193 assert.isTrue(
194 [
195 "https://auth.openai.com/.well-known/openid-configuration",
196 "https://auth.openai.com/revoke",
197 ].includes(request.url),
198 );
199 return HttpClientResponse.fromWeb(
200 request,
201 request.url.endsWith("/revoke")
202 ? new Response(null, { status: 200 })
203 : Response.json({
204 issuer: "https://auth.openai.com",
205 authorization_endpoint: "https://auth.openai.com/api/accounts/authorize",
206 token_endpoint: "https://auth.openai.com/api/accounts/oauth/token",
207 jwks_uri: "https://auth.openai.com/jwks",
208 revocation_endpoint: "https://auth.openai.com/revoke",
209 }),
210 );
211 }),
212 ),
213 ),
214 );
215 }).pipe(
216 Effect.scoped,
217 Effect.provide(
218 ServerConfig.layerTest(process.cwd(), {
219 prefix: "t3-managed-runtime-",
220 }).pipe(Layer.provideMerge(NodeServices.layer)),
221 ),
222 ),
223);