Skip to content
Closed
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
23 changes: 16 additions & 7 deletions apps/server/src/provider/CodexProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,11 @@ import {
formatCodexModelName,
} from "@t3tools/shared/model";
import { resolveSpawnCommand } from "@t3tools/shared/shell";
import { codexAppServerArgs, resolveCodexLaunchArgs } from "./codexLaunchArgs.ts";
import {
codexAppServerArgs,
codexUsageProbeDisabled,
resolveCodexLaunchArgs,
} from "./codexLaunchArgs.ts";
import {
buildServerProvider,
COMPACT_SLASH_COMMAND,
Expand Down Expand Up @@ -555,7 +559,7 @@ const probeCodexAppServerProvider = Effect.fn("probeCodexAppServerProvider")(fun
),
// Usage is an enrichment: a failure or a slow answer degrades to "no
// usage this probe" rather than costing the account and models.
input.skipNativeUsage
input.skipNativeUsage || !accountResponse.requiresOpenaiAuth
? // @effect-diagnostics-next-line effectSucceedWithVoid:off - This optional result requires undefined, not the wider void type.
Effect.succeed(undefined)
: withCodexDiscoveryDeadline(
Expand Down Expand Up @@ -703,6 +707,9 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
const resolvedEnvironment = environment ?? process.env;
const checkedAt = DateTime.formatIso(yield* DateTime.now);
const emptyModels = emptyCodexModelsFromSettings(codexSettings);
const launchArgs = resolveCodexLaunchArgs(codexSettings.launchArgs, resolvedEnvironment);
const skipNativeUsageBeforeProbe =
managedAuth !== undefined || codexUsageProbeDisabled(launchArgs);

if (!codexSettings.enabled) {
return buildServerProvider({
Expand All @@ -724,10 +731,10 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
const probeResult = yield* probe({
binaryPath: codexSettings.binaryPath,
homePath: codexSettings.homePath,
launchArgs: resolveCodexLaunchArgs(codexSettings.launchArgs, resolvedEnvironment),
launchArgs,
cwd: process.cwd(),
environment: resolvedEnvironment,
...(managedAuth ? { skipNativeUsage: true } : {}),
...(skipNativeUsageBeforeProbe ? { skipNativeUsage: true } : {}),
}).pipe(
// Process teardown can outlast the work budget without discarding a completed snapshot.
Effect.timeoutOption(Duration.millis(CODEX_AUTH_PROBE_TIMEOUT_MS)),
Expand Down Expand Up @@ -777,11 +784,13 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
}

const snapshot = probeResult.success.value;
const skipNativeUsage = skipNativeUsageBeforeProbe || !snapshot.account.requiresOpenaiAuth;
const accountStatus = managedAuth
? { status: "ready" as const, auth: managedAuth, message: undefined }
: accountProbeStatus(snapshot);
const usageLimits =
snapshot.account.account?.type === "apiKey"
const usageLimits = skipNativeUsage
? undefined
: snapshot.account.account?.type === "apiKey"
? makeUnavailableUsageLimits({ checkedAt, reason: "unsupported" })
: snapshot.rateLimits === undefined || "failure" in snapshot.rateLimits
? makeUnavailableUsageLimits({
Expand Down Expand Up @@ -831,7 +840,7 @@ export const checkCodexProviderStatus = Effect.fn("checkCodexProviderStatus")(fu
: snapshot.models === undefined
? { message: "Codex did not return its model catalog in time." }
: {}),
...(managedAuth ? {} : { usageLimits }),
...(usageLimits ? { usageLimits } : {}),
},
});
});
Expand Down
47 changes: 47 additions & 0 deletions apps/server/src/provider/ProviderRegistry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,53 @@ it.layer(
}),
);

it.effect("skips native usage probing for an external model provider", () =>
Effect.gen(function* () {
let skipNativeUsage: boolean | undefined;
const settings: CodexSettings = {
...defaultCodexSettings,
launchArgs:
"-c model_provider=local_pool -c model_providers.local_pool.requires_openai_auth=false",
};
const status = yield* checkCodexProviderStatus(settings, (input) => {
skipNativeUsage = input.skipNativeUsage;
return Effect.succeed(makeCodexProbeSnapshot());
});

assert.strictEqual(skipNativeUsage, true);
assert.isUndefined(status.usageLimits);
}),
);

it.effect("does not skip native usage for an unselected external provider", () =>
Effect.gen(function* () {
let skipNativeUsage: boolean | undefined;
const settings: CodexSettings = {
...defaultCodexSettings,
launchArgs:
"-c model_provider=openai -c model_providers.local_pool.requires_openai_auth=false",
};
const status = yield* checkCodexProviderStatus(settings, (input) => {
skipNativeUsage = input.skipNativeUsage;
return Effect.succeed(
makeCodexProbeSnapshot({
account: {
account: {
type: "chatgpt",
email: "test@example.com",
planType: "pro",
},
requiresOpenaiAuth: true,
},
}),
);
});

assert.notStrictEqual(skipNativeUsage, true);
assert.isDefined(status.usageLimits);
}),
);

it.effect("returns unauthenticated when app-server requires OpenAI auth", () =>
Effect.gen(function* () {
const status = yield* checkCodexProviderStatus(defaultCodexSettings, () =>
Expand Down
30 changes: 29 additions & 1 deletion apps/server/src/provider/codexLaunchArgs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import * as NodeAssert from "node:assert/strict";

import { describe, it } from "vite-plus/test";

import { codexAppServerArgs, codexExecLaunchArgs } from "./codexLaunchArgs.ts";
import {
codexAppServerArgs,
codexExecLaunchArgs,
codexUsageProbeDisabled,
} from "./codexLaunchArgs.ts";

describe("codexAppServerArgs", () => {
it("preserves escaped TOML quotes in config values", () => {
Expand All @@ -27,3 +31,27 @@ describe("codexExecLaunchArgs", () => {
]);
});
});

describe("codexUsageProbeDisabled", () => {
it("recognizes an external provider that does not use OpenAI auth", () => {
NodeAssert.equal(
codexUsageProbeDisabled(
"-c model_provider=local_pool -c model_providers.local_pool.requires_openai_auth=false --strict-config",
),
true,
);
});

it("ignores auth settings for providers that are not selected", () => {
NodeAssert.equal(
codexUsageProbeDisabled(
"-c model_provider=openai -c model_providers.local_pool.requires_openai_auth=false",
),
false,
);
});

it("keeps native usage probing enabled by default", () => {
NodeAssert.equal(codexUsageProbeDisabled('-c model="gpt-6.1-sol"'), false);
});
});
39 changes: 39 additions & 0 deletions apps/server/src/provider/codexLaunchArgs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,45 @@ export const codexAppServerArgs = (launchArgs?: string) => [
...codexLaunchArgv(launchArgs),
];

function configValue(
argument: string | undefined,
nextArgument: string | undefined,
): string | undefined {
if (argument === "-c" || argument === "--config") return nextArgument;
if (argument?.startsWith("-c=")) return argument.slice(3);
if (argument?.startsWith("--config=")) return argument.slice("--config=".length);
return undefined;
}

function configAssignment(value: string | undefined): readonly [string, string] | undefined {
if (!value) return undefined;
const separator = value.indexOf("=");
if (separator < 0) return undefined;
const key = value.slice(0, separator).trim();
const assignment = value.slice(separator + 1).trim();
if (!key || !assignment) return undefined;
return [key, assignment.replace(/^(['"])(.*)\1$/, "$2")];
}

/**
* External model providers do not implement Codex's OpenAI account quota RPC.
* A provider can opt out explicitly with `requires_openai_auth=false`.
*/
export const codexUsageProbeDisabled = (launchArgs?: string): boolean => {
const args = codexAppServerArgs(launchArgs);
const assignments = new Map<string, string>();
for (let index = 0; index < args.length; index++) {
const value = configValue(args[index], args[index + 1]);
const assignment = configAssignment(value);
if (assignment) assignments.set(assignment[0], assignment[1]);
}
const selectedProvider = assignments.get("model_provider") ?? "openai";
return (
assignments.get(`model_providers.${selectedProvider}.requires_openai_auth`)?.toLowerCase() ===
"false"
);
};

export const codexExecLaunchArgs = (launchArgs?: string) => {
const args = codexLaunchArgv(launchArgs);
const execArgs: Array<string> = [];
Expand Down
174 changes: 174 additions & 0 deletions packages/shared/src/usageLimits.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,180 @@ describe("pools", () => {
expect(accounts[0]?.limits.windows[0]?.usedPercent).toBe(55);
});

it("keeps duplicate hub credentials with one email as separate pool members", () => {
const accounts = ["first.json", "second.json"].map((id, index) => ({
id,
driver: claude,
email: "same@example.com",
usageLimits: {
checkedAt,
windows: [{ ...window, usedPercent: index === 0 ? 10 : 80 }],
},
}));
const input = new Map([
[
EnvironmentId.make("env-a"),
{
...laptop,
serverConfig: { providers: [], usageLimitSources: [{ ...source, accounts }] },
},
],
]);

const limitAccounts = collectLimitAccounts(input);
expect(limitAccounts.map((account) => account.key)).toEqual([
"hub:first.json",
"hub:second.json",
]);
expect(collectLimitPools(limitAccounts, now)[0]?.windows[0]).toMatchObject({
usedPercent: 45,
members: [{ account: { key: "hub:first.json" } }, { account: { key: "hub:second.json" } }],
});
});

it("keeps a workspace-less hub credential apart from a different workspace credential", () => {
const accounts = [
{
id: "workspace.json",
driver: claude,
email: "same@example.com",
workspaceId: "workspace-a",
usageLimits: {
checkedAt,
windows: [{ ...window, usedPercent: 20 }],
},
},
{
id: "personal.json",
driver: claude,
email: "same@example.com",
usageLimits: {
checkedAt,
windows: [{ ...window, usedPercent: 70 }],
},
},
];
const input = new Map([
[
EnvironmentId.make("env-a"),
{
...laptop,
serverConfig: { providers: [], usageLimitSources: [{ ...source, accounts }] },
},
],
]);

const limitAccounts = collectLimitAccounts(input);
expect(limitAccounts.map((account) => account.key)).toEqual([
"hub:workspace.json:workspace-a",
"hub:personal.json",
]);
expect(limitAccounts.map((account) => account.limits.windows[0]?.usedPercent)).toEqual([
20, 70,
]);
});

it("merges mixed workspace reports for one source credential across environments", () => {
const account = (
id: string,
workspaceId?: string,
usedPercent = 20,
reportCheckedAt = checkedAt,
) => ({
id,
driver: claude,
email: "same@example.com",
...(workspaceId ? { workspaceId } : {}),
usageLimits: { checkedAt: reportCheckedAt, windows: [{ ...window, usedPercent }] },
});
const input = new Map([
[
EnvironmentId.make("env-a"),
{
...laptop,
serverConfig: {
providers: [],
usageLimitSources: [
{
...source,
accounts: [
account("first.json", "workspace-a"),
account("second.json", "workspace-a", 80),
],
},
],
},
},
],
[
EnvironmentId.make("env-b"),
{
entry: { target: { label: "Desktop" } },
serverConfig: {
providers: [],
usageLimitSources: [
{
...source,
accounts: [account("first.json", undefined, 30, "2026-09-03T11:30:00.000Z")],
},
],
},
},
],
]);

const accounts = collectLimitAccounts(input);
expect(accounts.map((limitAccount) => limitAccount.key)).toEqual([
"hub:first.json:workspace-a",
"hub:second.json:workspace-a",
]);
expect(accounts[0]?.limits.windows[0]?.usedPercent).toBe(30);
expect(accounts[1]?.limits.windows[0]?.usedPercent).toBe(80);
});

it("deduplicates native reports without joining source credentials", () => {
const native = provider({
driver: claude,
instanceId: ProviderInstanceId.make("claude"),
auth: { status: "authenticated", email: "same@example.com" },
usageLimits: { checkedAt, windows: [{ ...window, usedPercent: 30 }] },
});
const accounts = ["first.json", "second.json"].map((id, index) => ({
id,
driver: claude,
email: "same@example.com",
usageLimits: { checkedAt, windows: [{ ...window, usedPercent: index === 0 ? 10 : 90 }] },
}));
const input = new Map([
[
EnvironmentId.make("env-a"),
{
...laptop,
serverConfig: { providers: [native], usageLimitSources: [{ ...source, accounts }] },
},
],
[
EnvironmentId.make("env-b"),
{
entry: { target: { label: "Desktop" } },
serverConfig: {
providers: [{ ...native, instanceId: ProviderInstanceId.make("codex-2") }],
},
},
],
]);

expect(collectLimitAccounts(input).map((account) => account.key)).toEqual([
"env-a:claude",
"hub:first.json",
"hub:second.json",
]);
expect(collectLimitAccounts(input)[0]?.environments).toEqual([
{ environmentId: "env-a", label: "Laptop" },
{ environmentId: "env-b", label: "Desktop" },
]);
});

it("merges OpenCode Go limits from machines with the same API key", () => {
const go = provider({
driver: ProviderDriverKind.make("opencode"),
Expand Down
Loading
Loading