Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/server/src/guide/topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ A Claude sign-in made with Claude's own sign-in (not an API key or a long-lived

When a Codex ChatGPT sign-in belongs to more than one workspace (a personal one and a company's, say), the Codex row shows the one in use, and its Account & Sign-in has Workspace: pick another and Switch. Switching first stops that Codex's running chats and holds new ones until it's done. A workspace behind company sign-on opens a sign-in page; Cancel keeps the current workspace. A chat whose data the old workspace keeps in another region may not continue after a switch; start a new thread for it. It needs Codex 0.167 or later.

When Codex's own config points it at a gateway or another model provider instead of OpenAI, the Codex row says Runs on and the provider's name, and Account & Sign-in has a Model provider line. Threadlines only shows it; change it in Codex's config. A ChatGPT sign-in beside it keeps its email, workspace and Sign out. It needs Codex 0.168 or later.
When Codex's own config points it at a gateway or another model provider instead of OpenAI, the Codex row says Runs on and the provider's name, and Account & Sign-in has a Model provider line. Threadlines only shows it; change it in Codex's config. A ChatGPT sign-in beside it keeps its email, workspace and Sign out. The model Codex's config names is in the model picker and is the default for new threads, and titles and commit messages written by Codex go through the gateway too. A Codex agent answering on the side in a room still runs on OpenAI. It needs Codex 0.168 or later.

When OpenAI asks a ChatGPT account to do something about its security, the Codex row says Security notice, and Account & Sign-in shows OpenAI's words with a button that opens the page on OpenAI's site. It needs Codex 0.169 or later.

Expand Down
49 changes: 48 additions & 1 deletion apps/server/src/provider/Drivers/CodexDriver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import { CodexSettings, ProviderDriverKind, type ServerProvider } from "@threadlines/contracts";
import * as Clock from "effect/Clock";
import * as DateTime from "effect/DateTime";
import * as Deferred from "effect/Deferred";
import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Option from "effect/Option";
import * as FileSystem from "effect/FileSystem";
import * as Path from "effect/Path";
import * as Schema from "effect/Schema";
Expand All @@ -45,6 +47,11 @@ import { makeCodexAdapter } from "../Layers/CodexAdapter.ts";
import { freshOrCurrentSnapshot } from "../freshProviderSnapshot.ts";
import {
checkCodexProviderStatus,
codexGatewayForCheck,
currentCodexModelProvider,
isCodexModelProviderKnown,
probeCodexGateway,
type CodexModelProviderMemory,
type CodexSecurityNoticeMemory,
type CodexWorkspaceMemory,
consumeCodexRateLimitResetCredit,
Expand Down Expand Up @@ -83,6 +90,14 @@ const decodeCodexSettings = Schema.decodeSync(CodexSettings);

const DRIVER_KIND = ProviderDriverKind.make("codex");
const SNAPSHOT_REFRESH_INTERVAL = Duration.minutes(5);
/**
* How long a title or commit message waits for the instance's first status
* check, which normally takes a second or two. Past it the run asks Codex
* itself which provider it is on, rather than hold the user's commit.
*/
const FIRST_CHECK_WAIT = Duration.seconds(5);
/** How long that question may take: two reads of Codex's own config. */
const GATEWAY_PROBE_TIMEOUT = Duration.seconds(10);
const WINDOWS_NATIVE_INSTALL = makeWindowsNativeInstaller({
url: "https://chatgpt.com/codex/install.ps1",
lockKey: "codex-native-win32",
Expand Down Expand Up @@ -193,7 +208,37 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
// here; the registry only has to worry about snapshot-build and
// spawner-availability failures surfaced from `checkCodexProviderStatus`
// below.
const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv);
// Filled in by each status check; titles and commit messages read it
// at the moment they run. Without an answer they would go to OpenAI,
// gateway or not, so a run that finds none waits a moment for the first
// check (it starts with the instance) and then asks Codex itself.
const modelProviderMemory: CodexModelProviderMemory = {};
const firstCheckEnded = yield* Deferred.make<void>();
const modelProviderLaunch = Effect.gen(function* () {
yield* Deferred.await(firstCheckEnded).pipe(Effect.timeoutOption(FIRST_CHECK_WAIT));
const nowMs = yield* Clock.currentTimeMillis;
if (!isCodexModelProviderKnown(modelProviderMemory, nowMs)) {
const reads = yield* probeCodexGateway({
binaryPath: effectiveConfig.binaryPath,
...(effectiveConfig.homePath ? { homePath: effectiveConfig.homePath } : {}),
cwd: process.cwd(),
environment: processEnv,
}).pipe(
Effect.scoped,
Effect.timeoutOption(GATEWAY_PROBE_TIMEOUT),
Effect.map(Option.getOrUndefined),
Effect.orElseSucceed(() => undefined),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
if (reads) {
codexGatewayForCheck(reads, modelProviderMemory, nowMs);
}
}
return currentCodexModelProvider(modelProviderMemory, nowMs)?.launch;
});
const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv, {
modelProviderLaunch,
});

// Build a managed snapshot whose settings never change — mutations come
// in as instance rebuilds from the registry rather than in-place
Expand All @@ -207,9 +252,11 @@ export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
processEnv,
workspaceMemory,
securityNoticeMemory,
modelProviderMemory,
).pipe(
Effect.map(stampIdentity),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.ensuring(Deferred.succeed(firstCheckEnded, undefined)),
);
const snapshot = yield* makeManagedServerProvider<CodexSettings>({
get maintenanceCapabilities() {
Expand Down
Loading
Loading