Skip to content
6 changes: 5 additions & 1 deletion apps/server/src/bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
* terminal-fallback agents run `t3 acp-mcp-call` per tool call, so their
* startup sits on first-message latency. Both dispatch here before the full
* CLI module graph (seconds of evaluation) loads; everything else defers to
* the real CLI in ./binCli.ts.
* the real CLI in ./binCli.ts. Managed Codex re-runs `t3 codex-managed-token`
* on a short interval to renew its bearer, so it dispatches here as well.
*/
import { isEntrypoint } from "./entrypoint.ts";

Expand All @@ -20,6 +21,9 @@ if (
if (command === "acp-mcp-bridge" || command === "acp-mcp-call") {
const { runAcpMcpCliFastPath } = await import("./mcp/AcpMcpStdioBridge.ts");
await runAcpMcpCliFastPath(command, process.argv.slice(3));
} else if (command === "codex-managed-token") {
const { runCodexManagedTokenCommand } = await import("./provider/codexManagedTokenCommand.ts");
await runCodexManagedTokenCommand();
} else {
const { runCli } = await import("./binCli.ts");
runCli();
Expand Down
2 changes: 2 additions & 0 deletions apps/server/src/binCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import * as CliError from "effect/cli/CliError";
import * as NetService from "@t3tools/shared/Net";
import packageJson from "../package.json" with { type: "json" };
import { acpMcpBridgeCommand, acpMcpCallCommand } from "./cli/acpMcpBridge.ts";
import { codexManagedTokenCommand } from "./cli/codexManagedToken.ts";
import { authCommand } from "./cli/auth.ts";
import { appCommand } from "./cli/app.ts";
import { browserCommand } from "./cli/browser.ts";
Expand Down Expand Up @@ -67,6 +68,7 @@ export const makeCli = ({ cloudEnabled = hasCloudPublicConfig } = {}) =>
),
acpMcpBridgeCommand,
acpMcpCallCommand,
codexManagedTokenCommand,
startCommand,
serveCommand,
appCommand,
Expand Down
21 changes: 21 additions & 0 deletions apps/server/src/cli/codexManagedToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as Effect from "effect/Effect";
import { Command } from "effect/cli";

import {
codexManagedTokenCommandName,
runCodexManagedTokenCommand,
} from "../provider/codexManagedTokenCommand.ts";

/**
* `t3 codex-managed-token` — internal credential command managed Codex runs to
* renew its bearer from the provider's loopback bridge.
*
* Real invocations dispatch through the bin.ts fast path before the CLI graph
* loads; this definition keeps the command wired for anything that drives the
* full CLI programmatically.
*/
export const codexManagedTokenCommand = Command.make(codexManagedTokenCommandName).pipe(
Command.withDescription("Print the current managed Codex credential for Codex."),
Command.unlisted,
Command.withHandler(() => Effect.promise(() => runCodexManagedTokenCommand())),
);
135 changes: 126 additions & 9 deletions apps/server/src/provider/CodexManagedRuntime.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
// @effect-diagnostics nodeBuiltinImport:off - checks the host's default Codex directory without writing to it.
// @effect-diagnostics nodeBuiltinImport:off globalFetchInEffect:off - checks the host's default Codex directory without writing to it.
import * as NodeOS from "node:os";
import * as NodeChildProcess from "node:child_process";
import * as NodeUtil from "node:util";
import * as NodeURL from "node:url";
import * as NodeServices from "@effect/platform-node/NodeServices";
import { assert, it } from "@effect/vitest";
import { CodexSettings, EnvironmentId, ProviderInstanceId } from "@t3tools/contracts";
Expand All @@ -11,17 +14,36 @@ import * as Layer from "effect/Layer";
import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import { HttpClient, HttpClientResponse } from "effect/http";
import * as HostProcess from "@t3tools/shared/HostProcess";
import * as ServerConfig from "../config.ts";
import * as ServerSecretStore from "../auth/ServerSecretStore.ts";
import * as ServerEnvironment from "../environment/ServerEnvironment.ts";
import * as CodexInstallation from "./CodexInstallation.ts";
import { makeCodexManagedRuntime } from "./CodexManagedRuntime.ts";
import { makeCodexManagedTokenSource } from "./CodexManagedTokenSource.ts";
import * as ProviderCredentialStore from "./ProviderCredentialStore.ts";
import { codexAppServerArgs } from "./codexLaunchArgs.ts";
import { resolveManagedCodexHomeLayout } from "./CodexManagedHome.ts";

const encodeJson = Schema.encodeEffect(Schema.fromJsonString(Schema.Unknown));
const decodeSettings = Schema.decodeSync(CodexSettings);
const serverEntrypoint = NodeURL.fileURLToPath(new URL("../bin.ts", import.meta.url));
it.effect("closes the managed credential bridge with its provider scope", () =>
Effect.gen(function* () {
const source = yield* makeCodexManagedTokenSource(
ProviderInstanceId.make("closed-codex"),
"dummy-account",
Effect.succeed({ accessToken: "dummy-token", clientId: "dummy-account" }),
).pipe(Effect.scoped);
const reached = yield* Effect.promise(() =>
fetch(source.url).then(
() => true,
() => false,
),
);
assert.isFalse(reached);
}),
);
it.effect("managed home defaults to the global Codex home and honors configured home paths", () =>
Effect.gen(function* () {
const path = yield* Path.Path;
Expand Down Expand Up @@ -79,6 +101,7 @@ it.effect.each(
getOrCreateRandom: () => Effect.die("unused"),
});
let leases = 0;
let renewals = 0;
const executable = {
executablePath:
source === "managed" ? "/isolated/tools/codex/0.156.1/bin/codex" : "/user/bin/codex",
Expand Down Expand Up @@ -149,7 +172,90 @@ it.effect.each(
);
assert.isFalse(yield* fs.exists(path.join(effective.config.homePath, "auth.json")));
}
assert.strictEqual(effective.environment.ACCESS_TOKEN, "dummy-owned-access");
// Emulate the same app-server making later requests, without resolving
// or restarting its runtime. Run its configured credential command.
const authSetting = (name: string) => {
const prefix = `model_providers.openai_token_sharing.auth.${name}=`;
const value = codexAppServerArgs(effective.config.launchArgs).find((arg) =>
arg.startsWith(prefix),
);
assert.isDefined(value);
return JSON.parse(value!.slice(prefix.length));
};
// The command is T3's own hidden subcommand, not a Node `-e` script the
// packaged executable cannot evaluate.
assert.equal(authSetting("command"), process.execPath);
assert.deepEqual(authSetting("args"), [serverEntrypoint, "codex-managed-token"]);
const requestToken = Effect.promise(async () => {
const { stdout } = await NodeUtil.promisify(NodeChildProcess.execFile)(
authSetting("command"),
authSetting("args"),
{ env: effective.environment, timeout: 25_000 },
);
return stdout;
});
assert.equal(yield* requestToken, "dummy-owned-access");
const expired = { ...JSON.parse(json), expiresAt: 0 };
yield* store.set(new TextEncoder().encode(JSON.stringify(expired)));
// The credential commands themselves must renew an expired token,
// even when no provider status read runs first.
assert.deepEqual(
yield* Effect.all([requestToken, requestToken], { concurrency: "unbounded" }),
["dummy-renewed-access", "dummy-renewed-access"],
);
assert.strictEqual(renewals, 1);
assert.isUndefined(effective.environment.ACCESS_TOKEN);
assert.notInclude(JSON.stringify(effective.environment), "dummy-owned-access");
const rejected = yield* Effect.promise(() =>
fetch(effective.environment.T3CODE_MANAGED_CODEX_AUTH_URL!),
);
assert.equal(rejected.status, 401);
yield* store.set(
new TextEncoder().encode(
JSON.stringify({
...expired,
clientId: "another-account",
accessToken: "dummy-other-account-access",
expiresAt: (yield* Clock.currentTimeMillis) + 3_600_000,
}),
),
);
const switched = yield* Effect.promise(() =>
fetch(effective.environment.T3CODE_MANAGED_CODEX_AUTH_URL!, {
headers: {
Authorization: `Bearer ${effective.environment.T3CODE_MANAGED_CODEX_AUTH_SECRET}`,
"X-T3-Codex-Account": effective.environment.T3CODE_MANAGED_CODEX_AUTH_ACCOUNT!,
},
}),
);
assert.equal(switched.status, 409);
assert.equal(yield* Effect.promise(() => switched.text()), "");
// A child holding the old bridge secret cannot expand its authority
// by naming the account that is now selected in T3.
const forged = yield* Effect.promise(() =>
fetch(effective.environment.T3CODE_MANAGED_CODEX_AUTH_URL!, {
headers: {
Authorization: `Bearer ${effective.environment.T3CODE_MANAGED_CODEX_AUTH_SECRET}`,
"X-T3-Codex-Account": "another-account",
},
}),
);
assert.equal(forged.status, 409);
assert.equal(yield* Effect.promise(() => forged.text()), "");
const replacement = yield* runtime.resolve;
assert.notEqual(
replacement.environment.T3CODE_MANAGED_CODEX_AUTH_SECRET,
effective.environment.T3CODE_MANAGED_CODEX_AUTH_SECRET,
);
const replacementToken = yield* Effect.promise(async () => {
const { stdout } = await NodeUtil.promisify(NodeChildProcess.execFile)(
authSetting("command"),
authSetting("args"),
{ env: replacement.environment, timeout: 25_000 },
);
return stdout;
});
assert.equal(replacementToken, "dummy-other-account-access");
assert.isUndefined(effective.environment.OPENAI_API_KEY);
assert.isUndefined(effective.environment.OPENAI_BASE_URL);
assert.isUndefined(effective.environment.T3CODE_CODEX_LAUNCH_ARGS);
Expand Down Expand Up @@ -194,26 +300,37 @@ it.effect.each(
[
"https://auth.openai.com/.well-known/openid-configuration",
"https://auth.openai.com/revoke",
"https://auth.openai.com/api/accounts/oauth/token",
].includes(request.url),
);
if (request.url.endsWith("/token")) renewals++;
return HttpClientResponse.fromWeb(
request,
request.url.endsWith("/revoke")
? new Response(null, { status: 200 })
: Response.json({
issuer: "https://auth.openai.com",
authorization_endpoint: "https://auth.openai.com/api/accounts/authorize",
token_endpoint: "https://auth.openai.com/api/accounts/oauth/token",
jwks_uri: "https://auth.openai.com/jwks",
revocation_endpoint: "https://auth.openai.com/revoke",
}),
: request.url.endsWith("/token")
? Response.json({
access_token: "dummy-renewed-access",
refresh_token: "dummy-rotated-refresh",
expires_in: 3600,
token_type: "Bearer",
scope: "chatgpt.tokens.use.direct",
})
: Response.json({
issuer: "https://auth.openai.com",
authorization_endpoint: "https://auth.openai.com/api/accounts/authorize",
token_endpoint: "https://auth.openai.com/api/accounts/oauth/token",
jwks_uri: "https://auth.openai.com/jwks",
revocation_endpoint: "https://auth.openai.com/revoke",
}),
);
}),
),
),
);
}).pipe(
Effect.scoped,
Effect.provideService(HostProcess.Arguments, [process.execPath, serverEntrypoint]),
Effect.provide(
ServerConfig.layerTest(process.cwd(), {
prefix: "t3-managed-runtime-",
Expand Down
63 changes: 46 additions & 17 deletions apps/server/src/provider/CodexManagedRuntime.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
import { resolveManagedCodexHomeLayout } from "./CodexManagedHome.ts";
import { CodexSettings, ProviderSetupError, type ProviderInstanceId } from "@t3tools/contracts";
import * as Cache from "effect/Cache";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";
import {
resolveSelfInvocation,
selfInvocationArgs,
type SelfInvocation,
} from "@t3tools/shared/nodeRuntime";
import * as ServerConfig from "../config.ts";
import * as CodexInstallation from "./CodexInstallation.ts";
import { makeCodexManagedTokenSource } from "./CodexManagedTokenSource.ts";
import { codexManagedTokenCommandName } from "./codexManagedTokenCommand.ts";
import { makeCodexChatGptAuth } from "./CodexChatGptAuth.ts";
import { materializeCodexShadowHome } from "./Drivers/CodexHomeLayout.ts";

Expand All @@ -16,21 +25,27 @@ export interface CodexEffectiveRuntime {
}
const decodeSettings = Schema.decodeSync(CodexSettings);
// Managed sign-in stores tokens in T3's credential store and never writes native auth.json.
const managedCodexLaunchArgs = [
'model_provider="openai_token_sharing"',
'model_providers.openai_token_sharing.name="OpenAI Token Sharing"',
'model_providers.openai_token_sharing.base_url="https://api.openai.com/v1"',
'model_providers.openai_token_sharing.model_catalog_url="https://api.openai.com/v1/models"',
"features.api_key_model_discovery=true",
"features.multi_agent=false",
"features.multi_agent_v2=false",
'model_providers.openai_token_sharing.env_key="ACCESS_TOKEN"',
'model_providers.openai_token_sharing.wire_api="responses"',
"model_providers.openai_token_sharing.requires_openai_auth=false",
"model_providers.openai_token_sharing.supports_websockets=false",
]
.map((value) => `-c '${value}'`)
.join(" ");
// The credential command is this T3 install's own hidden subcommand: the packaged
// executable has no Node CLI, so `node -e` is not available to every host.
const managedCodexLaunchArgs = (invocation: SelfInvocation) =>
[
'model_provider="openai_token_sharing"',
'model_providers.openai_token_sharing.name="OpenAI Token Sharing"',
'model_providers.openai_token_sharing.base_url="https://api.openai.com/v1"',
'model_providers.openai_token_sharing.model_catalog_url="https://api.openai.com/v1/models"',
"features.api_key_model_discovery=true",
"features.multi_agent=false",
"features.multi_agent_v2=false",
`model_providers.openai_token_sharing.auth.command=${JSON.stringify(invocation.command)}`,
`model_providers.openai_token_sharing.auth.args=${JSON.stringify(selfInvocationArgs(invocation, [codexManagedTokenCommandName]))}`,
"model_providers.openai_token_sharing.auth.timeout_ms=25000",
"model_providers.openai_token_sharing.auth.refresh_interval_ms=30000",
'model_providers.openai_token_sharing.wire_api="responses"',
"model_providers.openai_token_sharing.requires_openai_auth=false",
"model_providers.openai_token_sharing.supports_websockets=false",
]
.map((value) => `-c '${value.replaceAll("'", "'\"'\"'")}'`)
.join(" ");

export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(function* (options: {
readonly instanceId: ProviderInstanceId;
Expand All @@ -49,6 +64,15 @@ export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(func
config.devUrl ?? `http://localhost:${config.port}`,
).toString(),
});
const launchArgs = managedCodexLaunchArgs(yield* resolveSelfInvocation());
const scope = yield* Scope.Scope;
const tokenSources = yield* Cache.make({
capacity: Number.POSITIVE_INFINITY,
lookup: (clientId: string) =>
makeCodexManagedTokenSource(options.instanceId, clientId, auth.access).pipe(
Effect.provideService(Scope.Scope, scope),
),
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
const homeLayout = yield* resolveManagedCodexHomeLayout(
config.stateDir,
options.instanceId,
Expand All @@ -67,6 +91,7 @@ export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(func
),
);
const credentials = yield* auth.access;
const source = yield* Cache.get(tokenSources, credentials.clientId);
yield* materializeCodexShadowHome(homeLayout).pipe(
Effect.provideService(FileSystem.FileSystem, fs),
Effect.provideService(Path.Path, path),
Expand All @@ -92,9 +117,13 @@ export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(func
// Ambient CLI overrides cannot redirect a T3-owned token to a different provider.
const environment: NodeJS.ProcessEnv = {
...options.environment,
ACCESS_TOKEN: credentials.accessToken,
T3CODE_MANAGED_CODEX_AUTH_URL: source.url,
T3CODE_MANAGED_CODEX_AUTH_SECRET: source.secret,
T3CODE_MANAGED_CODEX_AUTH_ACCOUNT: credentials.clientId,
...(process.versions.electron ? { ELECTRON_RUN_AS_NODE: "1" } : {}),
CODEX_HOME: homePath,
};
delete environment.ACCESS_TOKEN;
Comment thread
luke2x marked this conversation as resolved.
Comment thread
luke2x marked this conversation as resolved.
delete environment.T3CODE_CODEX_LAUNCH_ARGS;
delete environment.OPENAI_API_KEY;
delete environment.OPENAI_BASE_URL;
Expand All @@ -104,7 +133,7 @@ export const makeCodexManagedRuntime = Effect.fn("makeCodexManagedRuntime")(func
setupMode: "managed",
binaryPath: executable.executablePath,
homePath,
launchArgs: managedCodexLaunchArgs,
launchArgs,
}),
environment,
revision: credentials.accessToken,
Expand Down
Loading
Loading