Skip to content

Commit 0e76e5e

Browse files
rohitjavvadisteipete
authored andcommitted
fix(models): show oauth marker auth status
1 parent 5e94469 commit 0e76e5e

3 files changed

Lines changed: 95 additions & 2 deletions

File tree

src/commands/models/list.auth-overview.test.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ vi.mock("../../agents/model-auth.js", () => {
5858
provider: string;
5959
}) => {
6060
const apiKey = resolveConfigKey(params.cfg, params.provider);
61-
if (!apiKey || apiKey === "secretref-managed") {
61+
if (!apiKey || apiKey === "secretref-managed" || apiKey.startsWith("oauth:")) {
6262
return null;
6363
}
6464
if (apiKey === "OPENAI_API_KEY") {
@@ -190,6 +190,18 @@ describe("resolveProviderAuthOverview", () => {
190190
expect(overview.modelsJson?.value).toContain(`marker(${NON_ENV_SECRETREF_MARKER})`);
191191
});
192192

193+
it("treats OAuth delegation markers as effective models.json auth", () => {
194+
const overview = withEnv({ OPENAI_API_KEY: undefined }, () =>
195+
resolveOpenAiOverview("oauth:openai-codex"),
196+
);
197+
198+
expect(overview.effective).toEqual({
199+
kind: "models.json",
200+
detail: "marker(oauth:openai-codex)",
201+
});
202+
expect(overview.modelsJson?.value).toBe("marker(oauth:openai-codex)");
203+
});
204+
193205
it("keeps env-var-shaped models.json values masked to avoid accidental plaintext exposure", () => {
194206
const overview = withEnv({ OPENAI_API_KEY: undefined }, () =>
195207
resolveOpenAiOverview("OPENAI_API_KEY"),

src/commands/models/list.auth-overview.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { loadPersistedAuthProfileStore } from "../../agents/auth-profiles/persis
55
import { listProfilesForProvider } from "../../agents/auth-profiles/profiles.js";
66
import type { AuthProfileStore } from "../../agents/auth-profiles/types.js";
77
import { resolveProfileUnusableUntilForDisplay } from "../../agents/auth-profiles/usage.js";
8-
import { isNonSecretApiKeyMarker } from "../../agents/model-auth-markers.js";
8+
import { isNonSecretApiKeyMarker, isOAuthApiKeyMarker } from "../../agents/model-auth-markers.js";
99
import {
1010
getCustomProviderApiKey,
1111
resolveEnvApiKey,
@@ -177,6 +177,9 @@ export function resolveProviderAuthOverview(params: {
177177
if (params.syntheticAuth) {
178178
return { kind: "synthetic", detail: params.syntheticAuth.source };
179179
}
180+
if (customKey && isOAuthApiKeyMarker(customKey)) {
181+
return { kind: "models.json", detail: formatMarkerOrSecret(customKey) };
182+
}
180183
return { kind: "missing", detail: "missing" };
181184
})();
182185

src/commands/models/list.status.test.ts

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,84 @@ describe("modelsStatusCommand auth overview", () => {
516516
}
517517
});
518518

519+
it("keeps delegated OAuth marker display separate from runtime route usability", async () => {
520+
const localRuntime = createRuntime();
521+
const originalLoadConfig = mocks.loadConfig.getMockImplementation();
522+
const originalProfiles = { ...mocks.store.profiles };
523+
const originalEnvImpl = mocks.resolveEnvApiKey.getMockImplementation();
524+
const originalCustomKeyImpl = mocks.getCustomProviderApiKey.getMockImplementation();
525+
const originalUsableCustomKeyImpl =
526+
mocks.resolveUsableCustomProviderApiKey.getMockImplementation();
527+
mocks.loadConfig.mockReturnValue({
528+
agents: {
529+
defaults: {
530+
model: { primary: "openai/gpt-5.5", fallbacks: [] },
531+
models: { "openai/gpt-5.5": {} },
532+
},
533+
},
534+
models: {
535+
providers: {
536+
openai: {
537+
apiKey: "oauth:openai-codex",
538+
},
539+
},
540+
},
541+
env: { shellEnv: { enabled: false } },
542+
});
543+
mocks.store.profiles = {};
544+
mocks.resolveEnvApiKey.mockImplementation(() => null);
545+
mocks.getCustomProviderApiKey.mockImplementation((_cfg: unknown, provider: string) =>
546+
provider === "openai" ? "oauth:openai-codex" : undefined,
547+
);
548+
mocks.resolveUsableCustomProviderApiKey.mockImplementation(() => null);
549+
550+
try {
551+
await modelsStatusCommand({ json: true, check: true }, localRuntime as never);
552+
const payload = parseFirstJsonLog(localRuntime);
553+
const openai = requireProvider(payload.auth.providers, "openai");
554+
expect(openai.effective).toEqual({
555+
kind: "models.json",
556+
detail: "marker(oauth:openai-codex)",
557+
});
558+
expect(payload.auth.runtimeAuthRoutes).toEqual([
559+
{
560+
provider: "openai",
561+
runtime: "codex",
562+
authProvider: "openai-codex",
563+
status: "missing",
564+
effective: {
565+
kind: "missing",
566+
detail: "missing",
567+
},
568+
},
569+
]);
570+
expect(payload.auth.missingProvidersInUse).toStrictEqual(["openai"]);
571+
expect(localRuntime.exit).toHaveBeenCalledWith(1);
572+
} finally {
573+
mocks.store.profiles = originalProfiles;
574+
if (originalLoadConfig) {
575+
mocks.loadConfig.mockImplementation(originalLoadConfig);
576+
}
577+
if (originalEnvImpl) {
578+
mocks.resolveEnvApiKey.mockImplementation(originalEnvImpl);
579+
} else if (defaultResolveEnvApiKeyImpl) {
580+
mocks.resolveEnvApiKey.mockImplementation(defaultResolveEnvApiKeyImpl);
581+
} else {
582+
mocks.resolveEnvApiKey.mockImplementation(() => null);
583+
}
584+
if (originalCustomKeyImpl) {
585+
mocks.getCustomProviderApiKey.mockImplementation(originalCustomKeyImpl);
586+
} else {
587+
mocks.getCustomProviderApiKey.mockReturnValue(undefined);
588+
}
589+
if (originalUsableCustomKeyImpl) {
590+
mocks.resolveUsableCustomProviderApiKey.mockImplementation(originalUsableCustomKeyImpl);
591+
} else {
592+
mocks.resolveUsableCustomProviderApiKey.mockReturnValue(null);
593+
}
594+
}
595+
});
596+
519597
it("uses Codex synthetic auth for canonical OpenAI text routes", async () => {
520598
const localRuntime = createRuntime();
521599
const originalLoadConfig = mocks.loadConfig.getMockImplementation();

0 commit comments

Comments
 (0)