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
151 changes: 151 additions & 0 deletions apps/server/src/provider/Drivers/CodexDriver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as Effect from "effect/Effect";
import * as FileSystem from "effect/FileSystem";
import * as Layer from "effect/Layer";
import * as Sink from "effect/Sink";
import * as Stream from "effect/Stream";
import { HttpClient } from "effect/unstable/http";
import * as ChildProcess from "effect/unstable/process/ChildProcess";
import * as ChildProcessSpawner from "effect/unstable/process/ChildProcessSpawner";

import * as BackgroundPolicy from "../../background/BackgroundPolicy.ts";
Expand All @@ -17,6 +20,11 @@ import { ServerSettingsService } from "../../serverSettings.ts";
import { layerTest as codexResetCreditLayerTest } from "../Layers/codexResetCredit.ts";
import { NoOpProviderEventLoggers, ProviderEventLoggers } from "../Layers/ProviderEventLoggers.ts";
import * as ModelManifest from "../ModelManifest.ts";
import {
createProviderVersionAdvisory,
ProviderVersionCache,
resolveLatestProviderVersion,
} from "../providerMaintenance.ts";
import { CodexDriver } from "./CodexDriver.ts";

const testLayer = ServerConfig.layerTest(process.cwd(), {
Expand Down Expand Up @@ -218,4 +226,147 @@ it.layer(testLayer)("CodexDriver", (it) => {
),
);
}

it.effect.each([
{
name: "conventional shim",
dataRoot: "mise",
commandName: "codex",
version: "0.153.4",
nodeFirst: false,
},
{
name: "custom data directory",
dataRoot: "custom-tool-data",
commandName: "codex",
version: "0.153.4",
nodeFirst: false,
},
{
name: "renamed configured command",
dataRoot: "mise",
commandName: "custom-codex",
version: "0.153.4",
nodeFirst: false,
},
{
name: "outdated provider",
dataRoot: "mise",
commandName: "codex",
version: "0.153.3",
nodeFirst: false,
},
{
name: "npm before shim",
dataRoot: "mise",
commandName: "codex",
version: "0.153.4",
nodeFirst: true,
},
])(
"does not mistake Homebrew mise for Codex's installer: $name",
(fixture) =>
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-codex-mise-shim-" });
const brewPrefix = NodePath.join(tempDir, "homebrew");
const brewPath = NodePath.join(brewPrefix, "bin", "brew");
const misePath = NodePath.join(brewPrefix, "Cellar", "mise", "2026.9.1", "bin", "mise");
const shimDir = NodePath.join(tempDir, fixture.dataRoot, "shims");
const npmPrefix = NodePath.join(tempDir, "mise", "installs", "node", "24.13.0");
const npmBin = NodePath.join(npmPrefix, "bin");
const npmEntry = NodePath.join(
npmPrefix,
"lib",
"node_modules",
"@openai",
"codex",
"bin",
"codex.js",
);
for (const file of [brewPath, misePath, npmEntry]) {
yield* fs.makeDirectory(NodePath.dirname(file), { recursive: true });
yield* fs.writeFileString(file, "#!/bin/sh\n");
yield* fs.chmod(file, 0o755);
}
yield* fs.makeDirectory(shimDir, { recursive: true });
yield* fs.makeDirectory(npmBin, { recursive: true });
yield* fs.symlink(misePath, NodePath.join(shimDir, fixture.commandName));
yield* fs.symlink(npmEntry, NodePath.join(npmBin, fixture.commandName));
const lookupPath = [
...(fixture.nodeFirst ? [npmBin, shimDir] : [shimDir, npmBin]),
NodePath.dirname(brewPath),
].join(NodePath.delimiter);
const probes: Array<ReadonlyArray<string>> = [];
const metadataSpawner = ChildProcessSpawner.make((command) => {
if (!ChildProcess.isStandardCommand(command) || command.command !== brewPath) {
return Effect.die("Provider resolution must not execute a provider or updater");
}
probes.push(command.args);
const stdout =
command.args[0] === "--prefix"
? brewPrefix
: JSON.stringify({ formulae: [{ versions: { stable: "2026.9.1" } }] });
return Effect.succeed(
ChildProcessSpawner.makeHandle({
pid: ChildProcessSpawner.ProcessId(1),
exitCode: Effect.succeed(ChildProcessSpawner.ExitCode(0)),
isRunning: Effect.succeed(false),
kill: () => Effect.void,
unref: Effect.succeed(Effect.void),
stdin: Sink.drain,
stdout: Stream.encodeText(Stream.make(stdout)),
stderr: Stream.empty,
all: Stream.empty,
getInputFd: () => Sink.drain,
getOutputFd: () => Stream.empty,
}),
);
});
const instance = yield* CodexDriver.create({
instanceId: ProviderInstanceId.make("codex-mise-shim"),
displayName: "Codex shim test",
enabled: false,
environment: [{ name: "PATH", value: lookupPath, sensitive: false }],
config: {
...CodexDriver.defaultConfig(),
binaryPath: fixture.commandName,
homePath: NodePath.join(tempDir, "codex-home"),
},
}).pipe(Effect.provideService(ChildProcessSpawner.ChildProcessSpawner, metadataSpawner));
const capabilities = yield* instance.snapshot.resolveMaintenance();
const latestVersion = yield* resolveLatestProviderVersion(capabilities).pipe(
Effect.provideService(
ProviderVersionCache,
new Map([
["@openai/codex", { expiresAt: Number.MAX_SAFE_INTEGER, version: "0.153.4" }],
]),
),
);
expect(probes).toEqual([]);
expect(latestVersion).toBe("0.153.4");
expect(
createProviderVersionAdvisory({
driver: CodexDriver.driverKind,
currentVersion: fixture.version,
latestVersion,
maintenanceCapabilities: capabilities,
}),
).toMatchObject({
status: fixture.version === "0.153.4" ? "current" : "behind_latest",
currentVersion: fixture.version,
latestVersion: "0.153.4",
canUpdate: fixture.nodeFirst,
});
if (fixture.nodeFirst) {
expect(capabilities.update).toMatchObject({
executable: "npm",
args: expect.arrayContaining(["--prefix", npmPrefix, "@openai/codex@latest"]),
});
} else {
expect(capabilities.update).toBeNull();
}
}).pipe(Effect.scoped),
{ skip: windowsHost },
);
});
43 changes: 29 additions & 14 deletions apps/server/src/provider/providerMaintenance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,25 +595,29 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
}),
);

it.effect.skipIf(!symlinksSupported)(
"upgrades the Homebrew cask that owns the binary and compares against its version",
() =>
it.effect.each([
{ directory: "Caskroom", name: "package-tool", kind: "cask" },
{ directory: "Cellar", name: "package-tool", kind: "formula" },
{ directory: "Cellar", name: "package-tool@latest", kind: "formula" },
] as const)(
"upgrades the owning Homebrew $kind $name through an executable alias",
(fixture) =>
Effect.gen(function* () {
const tempDir = yield* makeTempDir("t3-homebrew-capabilities");
const brewBinDir = NodePath.join(tempDir, "brew-bin");
const brewPath = NodePath.join(brewBinDir, "brew");
writeExecutable(brewPath);
const caskBinary = NodePath.join(
const ownedBinary = NodePath.join(
tempDir,
"Caskroom",
"package-tool",
fixture.directory,
fixture.name,
"0.148.0",
"package-tool",
"package-tool-0.148.0",
);
writeExecutable(caskBinary);
const link = NodePath.join(tempDir, "bin", "package-tool");
writeExecutable(ownedBinary);
const link = NodePath.join(tempDir, "bin", "custom-package-tool");
NodeFS.mkdirSync(NodePath.dirname(link), { recursive: true });
NodeFS.symlinkSync(caskBinary, link);
NodeFS.symlinkSync(ownedBinary, link);
const spawned: Array<ReadonlyArray<string>> = [];

const capabilities = yield* resolveProviderMaintenanceCapabilitiesEffect(
Expand All @@ -630,27 +634,38 @@ it.layer(NodeServices.layer)("providerMaintenance", (it) => {
spawned.push([command, ...args]);
return args[0] === "--prefix"
? `${tempDir}\n`
: JSON.stringify({ casks: [{ version: "0.148.0,42" }] });
: JSON.stringify(
fixture.kind === "cask"
? { casks: [{ version: "0.148.0,42" }] }
: { formulae: [{ versions: { stable: "0.148.0" } }] },
);
}),
),
);

expect(spawned).toEqual([
[brewPath, "--prefix"],
[brewPath, "info", "--json=v2", "package-tool"],
[brewPath, "info", "--json=v2", fixture.name],
]);
expect(capabilities).toEqual({
provider: driver("packageTool"),
packageName: "@example/package-tool",
latestVersion: "0.148.0",
update: {
command: "brew upgrade --cask package-tool",
command:
fixture.kind === "cask"
? `brew upgrade --cask ${fixture.name}`
: `brew upgrade ${fixture.name}`,
executable: brewPath,
args: ["upgrade", "--cask", "package-tool"],
args:
fixture.kind === "cask"
? ["upgrade", "--cask", fixture.name]
: ["upgrade", fixture.name],
lockKey: "homebrew",
},
});
}),
{ skip: !symlinksSupported },
);

it.effect.skipIf(windowsHost)(
Expand Down
4 changes: 4 additions & 0 deletions apps/server/src/provider/providerMaintenance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ export const resolvePackageManagedProviderMaintenance = Effect.fn(

const homebrew = homebrewOwnershipFromCommandPath(context.realCommandPath);
if (homebrew) {
// Mise shims resolve to the version manager, not the provider.
if (homebrew.kind === "formula" && homebrew.name.toLowerCase() === "mise") {
return manual;
}
const brewPath = yield* resolveCommandPath("brew", { env: context.env }).pipe(
Effect.catchTags({ CommandResolutionError: () => Effect.succeed(null) }),
);
Expand Down
Loading