/** * CodexDriver — first concrete `ProviderDriver` in the new per-instance model. * * A driver is a plain value (not a Context.Service) whose `create()` returns * one `ProviderInstance` bundling: * - `snapshot` — the live `ServerProviderShape` for this instance; * - `adapter` — the Codex session/turn/approval runtime; * - `textGeneration` — commit/PR/branch/title generation via `codex exec`. * * Each call to `create()` captures the `codexConfig` argument in closures * owned by the returned instance. Two instances created with different * `homePath`s (e.g. `codex_personal` + `codex_work`) therefore run with * fully independent Codex app-server processes and `CODEX_HOME` * environments — no shared mutable state. * * Resource lifecycle: `create()` runs in a scope handed in by the registry. * Closing that scope releases the adapter's child processes, the managed * snapshot's refresh fibre, and the text-generation binaries' transient * scratch files. The registry uses this to tear down an instance when its * `providerInstances` entry disappears or its config changes. * * @module provider/Drivers/CodexDriver */import { CodexSettings, ProviderDriverKind } from "@t3tools/contracts";import * as Crypto from "effect/Crypto";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 { HttpClient } from "effect/unstable/http";import { ChildProcessSpawner } from "effect/unstable/process"; import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts";import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";import { ServerConfig } from "../../config.ts";import { expandHomePath } from "../../pathExpansion.ts";import { ServerSettingsService } from "../../serverSettings.ts";import { ProviderDriverError } from "../Errors.ts";import { makeCodexAdapter } from "../Layers/CodexAdapter.ts";import { CODEX_RESET_CREDIT_TIMEOUT, CodexResetCreditCoordinator,} from "../Layers/codexResetCredit.ts";import { checkCodexProviderStatus, makePendingCodexProvider, probeCodexSkillsForCwd, withCodexAppServerClient,} from "../Layers/CodexProvider.ts";import { resolveCodexLaunchArgs } from "../Layers/codexLaunchArgs.ts";import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";import * as ModelManifest from "../ModelManifest.ts";import type { ProviderDriver, ProviderInstance } from "../ProviderDriver.ts";import { withInstanceIdentity } from "./instanceIdentity.ts";import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";import { enrichProviderSnapshotWithVersionAdvisory, makeCachedProviderMaintenanceResolution, makePackageManagedProviderMaintenanceResolver, normalizeCommandPath, resolveProviderMaintenanceCapabilitiesEffect,} from "../providerMaintenance.ts";import { haveProviderSnapshotSettingsChanged, makeProviderSnapshotSettingsSource, type ProviderSnapshotSettings,} from "../providerUpdateSettings.ts";import { codexContinuationIdentity, materializeCodexShadowHome, resolveCodexHomeLayout,} from "./CodexHomeLayout.ts";const decodeCodexSettings = Schema.decodeSync(CodexSettings); const DRIVER_KIND = ProviderDriverKind.make("codex");// The standalone installer lays out `<CODEX_HOME>/packages/standalone/…`;// CODEX_HOME is not always `~/.codex`.function isCodexStandaloneCommandPath(commandPath: string): boolean { return normalizeCommandPath(commandPath).includes("/packages/standalone/");} /** * `codex update` replaces the standalone tree under `CODEX_HOME`. That tree * lives in the shared home even when an auth-overlay shadow home is in use * (the overlay only carries auth and a few local entries), so the updater * runs against `sharedHomePath` rather than the instance's effective home. */function makeCodexMaintenanceResolver(sharedHomePath: string) { return makePackageManagedProviderMaintenanceResolver({ provider: DRIVER_KIND, npmPackageName: "@openai/codex", nativeUpdate: { args: ["update"], isCommandPath: isCodexStandaloneCommandPath, env: { CODEX_HOME: sharedHomePath }, }, });} /** * Services the driver needs to materialize an instance. Surfaced as the * driver's `R` so the registry layer aggregates these across every * registered driver and the runtime satisfies them once. */export type CodexDriverEnv = | BackgroundPolicy.BackgroundPolicy | ChildProcessSpawner.ChildProcessSpawner | CodexResetCreditCoordinator | Crypto.Crypto | FileSystem.FileSystem | HttpClient.HttpClient | ModelManifest.ModelManifest | Path.Path | ProviderEventLoggers | ServerConfig | ServerSettingsService; export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = { driverKind: DRIVER_KIND, metadata: { displayName: "Codex", supportsMultipleInstances: true, }, configSchema: CodexSettings, defaultConfig: (): CodexSettings => decodeCodexSettings({}), create: ({ instanceId, displayName, accentColor, environment, enabled, config }) => Effect.gen(function* () { const spawner = yield* ChildProcessSpawner.ChildProcessSpawner; const resetCreditCoordinator = yield* CodexResetCreditCoordinator; const fileSystem = yield* FileSystem.FileSystem; const pathService = yield* Path.Path; const httpClient = yield* HttpClient.HttpClient; const serverSettings = yield* ServerSettingsService; const eventLoggers = yield* ProviderEventLoggers; const modelManifest = yield* ModelManifest.ModelManifest; const processEnv = mergeProviderInstanceEnvironment(environment); const homeLayout = yield* resolveCodexHomeLayout(config); const continuationIdentity = codexContinuationIdentity(homeLayout); const stampIdentity = withInstanceIdentity({ instanceId, driverKind: DRIVER_KIND, displayName, accentColor, continuationGroupKey: continuationIdentity.continuationKey, }); yield* materializeCodexShadowHome(homeLayout).pipe( Effect.mapError( (cause) => new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: cause.message, cause, }), ), ); const effectiveConfig = { ...config, enabled, binaryPath: expandHomePath(config.binaryPath), homePath: homeLayout.effectiveHomePath ?? "", } satisfies CodexSettings; const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution( resolveProviderMaintenanceCapabilitiesEffect( makeCodexMaintenanceResolver(homeLayout.sharedHomePath), { binaryPath: effectiveConfig.binaryPath, env: processEnv, }, ).pipe( Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.provideService(FileSystem.FileSystem, fileSystem), Effect.provideService(Path.Path, pathService), ), ); // `makeCodexAdapter` and `makeCodexTextGeneration` have `never` error // channels at construction time — their failure modes are all on the // per-operation closures they return. No `mapError` wrapper is needed // here; the registry only has to worry about snapshot-build and // spawner-availability failures surfaced from `checkCodexProviderStatus` // below. const adapter = yield* makeCodexAdapter(effectiveConfig, { instanceId, environment: processEnv, ...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}), }); const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv); // Build a managed snapshot whose settings never change — mutations come // in as instance rebuilds from the registry rather than in-place // updates. Pre-provide `ChildProcessSpawner` so the check fits // `makeManagedServerProvider.checkProvider`'s `R = never`. // Kick the TTL-gated manifest refresh in the background and classify // with the in-memory manifest, so a slow or hung fetch never delays the // provider check. A refresh that lands mid-probe applies on the next one. const checkProvider = modelManifest.refreshInBackground.pipe( Effect.andThen( Effect.zipWith( checkCodexProviderStatus(effectiveConfig, undefined, processEnv), modelManifest.current, (draft, manifest) => stampIdentity(ModelManifest.applyModelManifest(draft, manifest, DRIVER_KIND)), { concurrent: true }, ), ), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ); const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings); const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<CodexSettings>>({ resolveMaintenance, getSettings: snapshotSettings.getSettings, streamSettings: snapshotSettings.streamSettings, haveSettingsChanged: haveProviderSnapshotSettingsChanged, initialSnapshot: (settings) => Effect.zipWith( makePendingCodexProvider(settings.provider), modelManifest.current, (draft, manifest) => stampIdentity(ModelManifest.applyModelManifest(draft, manifest, DRIVER_KIND)), ), checkProvider, enrichSnapshot: ({ settings, snapshot, publishSnapshot }) => resolveMaintenance().pipe( Effect.flatMap((maintenanceCapabilities) => enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, { enableProviderUpdateChecks: settings.enableProviderUpdateChecks, }), ), Effect.provideService(HttpClient.HttpClient, httpClient), Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)), ), }).pipe( Effect.mapError( (cause) => new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: `Failed to build Codex snapshot: ${cause.message ?? String(cause)}`, cause, }), ), ); const snapshotForCwd = (cwd: string) => !effectiveConfig.enabled ? snapshot.getSnapshot : Effect.all([ snapshot.getSnapshot, probeCodexSkillsForCwd({ binaryPath: effectiveConfig.binaryPath, homePath: effectiveConfig.homePath, launchArgs: resolveCodexLaunchArgs(effectiveConfig.launchArgs, processEnv), cwd, environment: processEnv, }).pipe( Effect.scoped, Effect.timeout("20 seconds"), Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), ), ]).pipe( Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills })), Effect.mapError( (cause) => new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: `Failed to probe Codex skills for '${cwd}'`, cause, }), ), ); // Redemption spends something on the user's account. It serialises on // the account (instances sharing a Codex home share the credit), keeps // one idempotency key until Codex reports an outcome, and is bounded so // a hung app-server cannot hold the account lock. // Keyed on the directory holding auth.json: an auth-overlay instance has // its own account under `effectiveHomePath`, while plain instances share // the common home. The continuation key would conflate the two. const accountKey = homeLayout.effectiveHomePath ?? homeLayout.sharedHomePath; const consumeResetCredit: NonNullable<ProviderInstance["consumeResetCredit"]> = () => resetCreditCoordinator .redeem(accountKey, (idempotencyKey) => Effect.gen(function* () { const { client } = yield* withCodexAppServerClient({ binaryPath: effectiveConfig.binaryPath, homePath: effectiveConfig.homePath, launchArgs: resolveCodexLaunchArgs(effectiveConfig.launchArgs, processEnv), // Account-level request; any directory serves, same as the status probe. cwd: process.cwd(), environment: processEnv, }); const response = yield* client.request("account/rateLimitResetCredit/consume", { idempotencyKey, }); return response.outcome; }).pipe(Effect.scoped, Effect.timeout(CODEX_RESET_CREDIT_TIMEOUT)), ) .pipe( Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner), Effect.mapError( (cause) => new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: "Codex could not redeem the reset credit.", cause, }), ), // The windows just changed; re-probe so the snapshot says so. A // failed probe republishes the pre-redemption limits rather than // marking them failed, so "confirmed" means `checkedAt` moved // past what was published before the redemption started. Effect.tap(() => Effect.gen(function* () { const before = (yield* snapshot.getSnapshot).usageLimits?.checkedAt; const refreshed = yield* snapshot.refresh; const after = refreshed.usageLimits?.checkedAt; if ( after === undefined || after === before || refreshed.usageLimits?.unavailable?.reason === "probeFailed" ) { return yield* new ProviderDriverError({ driver: DRIVER_KIND, instanceId, detail: "The reset was applied, but Codex could not confirm the new limits. Refresh to check.", }); } }), ), ); return { instanceId, driverKind: DRIVER_KIND, continuationIdentity, displayName, accentColor, enabled, snapshot, snapshotForCwd, consumeResetCredit, adapter, textGeneration, } satisfies ProviderInstance; }),};
/**
* CodexDriver — first concrete `ProviderDriver` in the new per-instance model.
*
* A driver is a plain value (not a Context.Service) whose `create()` returns
* one `ProviderInstance` bundling:
* - `snapshot` — the live `ServerProviderShape` for this instance;
* - `adapter` — the Codex session/turn/approval runtime;
* - `textGeneration` — commit/PR/branch/title generation via `codex exec`.
*
* Each call to `create()` captures the `codexConfig` argument in closures
* owned by the returned instance. Two instances created with different
* `homePath`s (e.g. `codex_personal` + `codex_work`) therefore run with
* fully independent Codex app-server processes and `CODEX_HOME`
* environments — no shared mutable state.
*
* Resource lifecycle: `create()` runs in a scope handed in by the registry.
* Closing that scope releases the adapter's child processes, the managed
* snapshot's refresh fibre, and the text-generation binaries' transient
* scratch files. The registry uses this to tear down an instance when its
* `providerInstances` entry disappears or its config changes.
*
* @module provider/Drivers/CodexDriver
*/
import { CodexSettings, ProviderDriverKind } from "@t3tools/contracts";
import * as Crypto from "effect/Crypto";
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 { HttpClient } from "effect/unstable/http";
import { ChildProcessSpawner } from "effect/unstable/process";
import { makeCodexTextGeneration } from "../../textGeneration/CodexTextGeneration.ts";
import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
import { ServerConfig } from "../../config.ts";
import { expandHomePath } from "../../pathExpansion.ts";
import { ServerSettingsService } from "../../serverSettings.ts";
import { ProviderDriverError } from "../Errors.ts";
import { makeCodexAdapter } from "../Layers/CodexAdapter.ts";
import {
CODEX_RESET_CREDIT_TIMEOUT,
CodexResetCreditCoordinator,
} from "../Layers/codexResetCredit.ts";
import {
checkCodexProviderStatus,
makePendingCodexProvider,
probeCodexSkillsForCwd,
withCodexAppServerClient,
} from "../Layers/CodexProvider.ts";
import { resolveCodexLaunchArgs } from "../Layers/codexLaunchArgs.ts";
import { ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
import { makeManagedServerProvider } from "../makeManagedServerProvider.ts";
import * as ModelManifest from "../ModelManifest.ts";
import type { ProviderDriver, ProviderInstance } from "../ProviderDriver.ts";
import { withInstanceIdentity } from "./instanceIdentity.ts";
import { mergeProviderInstanceEnvironment } from "../ProviderInstanceEnvironment.ts";
import {
enrichProviderSnapshotWithVersionAdvisory,
makeCachedProviderMaintenanceResolution,
makePackageManagedProviderMaintenanceResolver,
normalizeCommandPath,
resolveProviderMaintenanceCapabilitiesEffect,
} from "../providerMaintenance.ts";
import {
haveProviderSnapshotSettingsChanged,
makeProviderSnapshotSettingsSource,
type ProviderSnapshotSettings,
} from "../providerUpdateSettings.ts";
import {
codexContinuationIdentity,
materializeCodexShadowHome,
resolveCodexHomeLayout,
} from "./CodexHomeLayout.ts";
const decodeCodexSettings = Schema.decodeSync(CodexSettings);
const DRIVER_KIND = ProviderDriverKind.make("codex");
// The standalone installer lays out `<CODEX_HOME>/packages/standalone/…`;
// CODEX_HOME is not always `~/.codex`.
function isCodexStandaloneCommandPath(commandPath: string): boolean {
return normalizeCommandPath(commandPath).includes("/packages/standalone/");
}
/**
* `codex update` replaces the standalone tree under `CODEX_HOME`. That tree
* lives in the shared home even when an auth-overlay shadow home is in use
* (the overlay only carries auth and a few local entries), so the updater
* runs against `sharedHomePath` rather than the instance's effective home.
*/
function makeCodexMaintenanceResolver(sharedHomePath: string) {
return makePackageManagedProviderMaintenanceResolver({
provider: DRIVER_KIND,
npmPackageName: "@openai/codex",
nativeUpdate: {
args: ["update"],
isCommandPath: isCodexStandaloneCommandPath,
env: { CODEX_HOME: sharedHomePath },
},
});
}
/**
* Services the driver needs to materialize an instance. Surfaced as the
* driver's `R` so the registry layer aggregates these across every
* registered driver and the runtime satisfies them once.
*/
export type CodexDriverEnv =
| BackgroundPolicy.BackgroundPolicy
| ChildProcessSpawner.ChildProcessSpawner
| CodexResetCreditCoordinator
| Crypto.Crypto
| FileSystem.FileSystem
| HttpClient.HttpClient
| ModelManifest.ModelManifest
| Path.Path
| ProviderEventLoggers
| ServerConfig
| ServerSettingsService;
export const CodexDriver: ProviderDriver<CodexSettings, CodexDriverEnv> = {
driverKind: DRIVER_KIND,
metadata: {
displayName: "Codex",
supportsMultipleInstances: true,
},
configSchema: CodexSettings,
defaultConfig: (): CodexSettings => decodeCodexSettings({}),
create: ({ instanceId, displayName, accentColor, environment, enabled, config }) =>
Effect.gen(function* () {
const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
const resetCreditCoordinator = yield* CodexResetCreditCoordinator;
const fileSystem = yield* FileSystem.FileSystem;
const pathService = yield* Path.Path;
const httpClient = yield* HttpClient.HttpClient;
const serverSettings = yield* ServerSettingsService;
const eventLoggers = yield* ProviderEventLoggers;
const modelManifest = yield* ModelManifest.ModelManifest;
const processEnv = mergeProviderInstanceEnvironment(environment);
const homeLayout = yield* resolveCodexHomeLayout(config);
const continuationIdentity = codexContinuationIdentity(homeLayout);
const stampIdentity = withInstanceIdentity({
instanceId,
driverKind: DRIVER_KIND,
displayName,
accentColor,
continuationGroupKey: continuationIdentity.continuationKey,
});
yield* materializeCodexShadowHome(homeLayout).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: cause.message,
cause,
}),
),
);
const effectiveConfig = {
...config,
enabled,
binaryPath: expandHomePath(config.binaryPath),
homePath: homeLayout.effectiveHomePath ?? "",
} satisfies CodexSettings;
const resolveMaintenance = yield* makeCachedProviderMaintenanceResolution(
resolveProviderMaintenanceCapabilitiesEffect(
makeCodexMaintenanceResolver(homeLayout.sharedHomePath),
{
binaryPath: effectiveConfig.binaryPath,
env: processEnv,
},
).pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.provideService(FileSystem.FileSystem, fileSystem),
Effect.provideService(Path.Path, pathService),
),
);
// `makeCodexAdapter` and `makeCodexTextGeneration` have `never` error
// channels at construction time — their failure modes are all on the
// per-operation closures they return. No `mapError` wrapper is needed
// here; the registry only has to worry about snapshot-build and
// spawner-availability failures surfaced from `checkCodexProviderStatus`
// below.
const adapter = yield* makeCodexAdapter(effectiveConfig, {
instanceId,
environment: processEnv,
...(eventLoggers.native ? { nativeEventLogger: eventLoggers.native } : {}),
});
const textGeneration = yield* makeCodexTextGeneration(effectiveConfig, processEnv);
// Build a managed snapshot whose settings never change — mutations come
// in as instance rebuilds from the registry rather than in-place
// updates. Pre-provide `ChildProcessSpawner` so the check fits
// `makeManagedServerProvider.checkProvider`'s `R = never`.
// Kick the TTL-gated manifest refresh in the background and classify
// with the in-memory manifest, so a slow or hung fetch never delays the
// provider check. A refresh that lands mid-probe applies on the next one.
const checkProvider = modelManifest.refreshInBackground.pipe(
Effect.andThen(
Effect.zipWith(
checkCodexProviderStatus(effectiveConfig, undefined, processEnv),
modelManifest.current,
(draft, manifest) =>
stampIdentity(ModelManifest.applyModelManifest(draft, manifest, DRIVER_KIND)),
{ concurrent: true },
),
),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
);
const snapshotSettings = makeProviderSnapshotSettingsSource(effectiveConfig, serverSettings);
const snapshot = yield* makeManagedServerProvider<ProviderSnapshotSettings<CodexSettings>>({
resolveMaintenance,
getSettings: snapshotSettings.getSettings,
streamSettings: snapshotSettings.streamSettings,
haveSettingsChanged: haveProviderSnapshotSettingsChanged,
initialSnapshot: (settings) =>
Effect.zipWith(
makePendingCodexProvider(settings.provider),
modelManifest.current,
(draft, manifest) =>
stampIdentity(ModelManifest.applyModelManifest(draft, manifest, DRIVER_KIND)),
),
checkProvider,
enrichSnapshot: ({ settings, snapshot, publishSnapshot }) =>
resolveMaintenance().pipe(
Effect.flatMap((maintenanceCapabilities) =>
enrichProviderSnapshotWithVersionAdvisory(snapshot, maintenanceCapabilities, {
enableProviderUpdateChecks: settings.enableProviderUpdateChecks,
}),
),
Effect.provideService(HttpClient.HttpClient, httpClient),
Effect.flatMap((enrichedSnapshot) => publishSnapshot(enrichedSnapshot)),
),
}).pipe(
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: `Failed to build Codex snapshot: ${cause.message ?? String(cause)}`,
cause,
}),
),
);
const snapshotForCwd = (cwd: string) =>
!effectiveConfig.enabled
? snapshot.getSnapshot
: Effect.all([
snapshot.getSnapshot,
probeCodexSkillsForCwd({
binaryPath: effectiveConfig.binaryPath,
homePath: effectiveConfig.homePath,
launchArgs: resolveCodexLaunchArgs(effectiveConfig.launchArgs, processEnv),
cwd,
environment: processEnv,
}).pipe(
Effect.scoped,
Effect.timeout("20 seconds"),
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
),
]).pipe(
Effect.map(([machineSnapshot, skills]) => ({ ...machineSnapshot, skills })),
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: `Failed to probe Codex skills for '${cwd}'`,
cause,
}),
),
);
// Redemption spends something on the user's account. It serialises on
// the account (instances sharing a Codex home share the credit), keeps
// one idempotency key until Codex reports an outcome, and is bounded so
// a hung app-server cannot hold the account lock.
// Keyed on the directory holding auth.json: an auth-overlay instance has
// its own account under `effectiveHomePath`, while plain instances share
// the common home. The continuation key would conflate the two.
const accountKey = homeLayout.effectiveHomePath ?? homeLayout.sharedHomePath;
const consumeResetCredit: NonNullable<ProviderInstance["consumeResetCredit"]> = () =>
resetCreditCoordinator
.redeem(accountKey, (idempotencyKey) =>
Effect.gen(function* () {
const { client } = yield* withCodexAppServerClient({
binaryPath: effectiveConfig.binaryPath,
homePath: effectiveConfig.homePath,
launchArgs: resolveCodexLaunchArgs(effectiveConfig.launchArgs, processEnv),
// Account-level request; any directory serves, same as the status probe.
cwd: process.cwd(),
environment: processEnv,
});
const response = yield* client.request("account/rateLimitResetCredit/consume", {
idempotencyKey,
});
return response.outcome;
}).pipe(Effect.scoped, Effect.timeout(CODEX_RESET_CREDIT_TIMEOUT)),
)
.pipe(
Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, spawner),
Effect.mapError(
(cause) =>
new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail: "Codex could not redeem the reset credit.",
cause,
}),
),
// The windows just changed; re-probe so the snapshot says so. A
// failed probe republishes the pre-redemption limits rather than
// marking them failed, so "confirmed" means `checkedAt` moved
// past what was published before the redemption started.
Effect.tap(() =>
Effect.gen(function* () {
const before = (yield* snapshot.getSnapshot).usageLimits?.checkedAt;
const refreshed = yield* snapshot.refresh;
const after = refreshed.usageLimits?.checkedAt;
if (
after === undefined ||
after === before ||
refreshed.usageLimits?.unavailable?.reason === "probeFailed"
) {
return yield* new ProviderDriverError({
driver: DRIVER_KIND,
instanceId,
detail:
"The reset was applied, but Codex could not confirm the new limits. Refresh to check.",
});
}
}),
),
);
return {
instanceId,
driverKind: DRIVER_KIND,
continuationIdentity,
displayName,
accentColor,
enabled,
snapshot,
snapshotForCwd,
consumeResetCredit,
adapter,
textGeneration,
} satisfies ProviderInstance;
}),
};