From 09a0a684accf0ca2f2d0a156f65da5e7f2496025 Mon Sep 17 00:00:00 2001 From: eleith Date: Thu, 5 Nov 2026 12:35:30 -0800 Subject: [PATCH] refactor(codex): remove unused auth-scope fingerprinting --- .changeset/codex-account-client-cleanup.md | 5 + extensions/codex/src/client.test.ts | 195 +++++++++++---------- extensions/codex/src/client.ts | 41 +---- extensions/codex/src/fast-sdk.test.ts | 1 - extensions/codex/src/fast.test.ts | 23 +-- extensions/codex/src/fast.ts | 2 +- extensions/codex/src/index.test.ts | 9 +- 7 files changed, 123 insertions(+), 153 deletions(-) create mode 100644 .changeset/codex-account-client-cleanup.md diff --git a/.changeset/codex-account-client-cleanup.md b/.changeset/codex-account-client-cleanup.md new file mode 100644 index 0000000..1b98d61 --- /dev/null +++ b/.changeset/codex-account-client-cleanup.md @@ -0,0 +1,5 @@ +--- +"@eleith-pi/codex": patch +--- + +Remove unused Codex auth-scope fingerprinting and the auth-only lookup path. Return catalog JSON directly without unused metadata wrappers, preserving account-request authentication, endpoint restrictions, cancellation, timeouts, response bounds, and safe errors. diff --git a/extensions/codex/src/client.test.ts b/extensions/codex/src/client.test.ts index aae57aa..8870259 100644 --- a/extensions/codex/src/client.test.ts +++ b/extensions/codex/src/client.test.ts @@ -1,12 +1,6 @@ -import { createHash } from "node:crypto"; import type { Api, AuthResult, Model } from "@earendil-works/pi-ai"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - fetchCodexModelCatalog, - fetchCodexUsage, - readCodexAuthScope, - type CodexAccountRegistry, -} from "./client.ts"; +import { fetchCodexModelCatalog, fetchCodexUsage, type CodexAccountRegistry } from "./client.ts"; const BASE_URL = "https://chatgpt.com/backend-api"; const model: Model = { @@ -72,67 +66,63 @@ it("uses Pi's existing Codex auth for the fixed account endpoint", async () => { }); }); -it("uses the same Pi credentials for the fixed versioned model catalog", async () => { +it("returns raw catalog JSON using only Pi's resolved routing credentials", async () => { const f = fixture(); + f.auth.auth.headers = { + "ChatGPT-Account-Id": "unused-account", + Authorization: "unused-token", + "X-Unrelated-Secret": "unused-secret", + }; const catalog = { models: [{ slug: "future-model", service_tiers: [{ id: "priority" }] }] }; - f.fetcher.mockResolvedValue(json(catalog)); + const response = json(catalog); + f.fetcher.mockResolvedValue(response); const result = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher); - expect(result.data).toEqual(catalog); + expect(result).toEqual(catalog); expect(f.registry.getProviderAuth).toHaveBeenCalledExactlyOnceWith("openai-codex"); + const requestOptions = expect.objectContaining({ method: "GET", redirect: "error" }); expect(f.fetcher).toHaveBeenCalledExactlyOnceWith( `${BASE_URL}/codex/models?client_version=0.162.0`, - expect.objectContaining({ method: "GET", redirect: "error" }), + requestOptions, ); const headers = new Headers(f.fetcher.mock.calls[0][1]?.headers); - expect(headers.get("authorization")).toBe(`Bearer ${f.auth.auth.apiKey}`); - expect(headers.get("chatgpt-account-id")).toBe("test-account"); - expect(result.authScope).toBe( - createHash("sha256") - .update(JSON.stringify([headers.get("authorization"), headers.get("chatgpt-account-id")])) - .digest("hex"), - ); - expect(Object.keys(result).sort()).toEqual(["authScope", "data"]); - expect(JSON.stringify(result)).not.toContain(f.auth.auth.apiKey); - expect(JSON.stringify(result)).not.toContain("test-account"); -}); - -it("reads a stable auth scope without fetching and ignores unused provider headers", async () => { - const f = fixture(); - const fetchSpy = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("must not fetch")); - try { - const scope = await readCodexAuthScope(f.registry); - f.auth.auth.headers = { - "ChatGPT-Account-Id": "unused-account", - Authorization: "unused-token", - "X-Unrelated-Secret": "unused-secret", - }; - expect(await readCodexAuthScope(f.registry)).toBe(scope); - expect(scope).toMatch(/^[a-f0-9]{64}$/); - expect(scope).not.toContain(f.auth.auth.apiKey); - expect(scope).not.toContain("test-account"); - expect(f.registry.getProviderAuth).toHaveBeenCalledTimes(2); - expect(fetchSpy).not.toHaveBeenCalled(); - const catalog = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher); - expect(catalog.authScope).toBe(scope); - } finally { - fetchSpy.mockRestore(); - } + const actualHeaders = Object.fromEntries(headers); + expect(actualHeaders).toEqual({ + accept: "application/json", + authorization: `Bearer ${f.auth.auth.apiKey}`, + "chatgpt-account-id": "test-account", + }); + const serialized = JSON.stringify(result); + expect(serialized).not.toContain(f.auth.auth.apiKey); + expect(serialized).not.toContain("test-account"); }); it.each(["token refresh", "account change"] as const)( - "changes the scope on %s and binds the catalog to its own resolved credentials", + "resolves fresh catalog credentials after %s", async (change) => { const f = fixture(); - const previous = await readCodexAuthScope(f.registry); - f.auth.auth.apiKey = - change === "token refresh" ? `${token()}-refreshed` : token("another-account"); - const catalog = await fetchCodexModelCatalog(f.registry, undefined, f.fetcher); - expect(catalog.authScope).not.toBe(previous); - expect(await readCodexAuthScope(f.registry)).toBe(catalog.authScope); + const originalToken = f.auth.auth.apiKey; + await fetchCodexModelCatalog(f.registry, undefined, f.fetcher); + const nextAccount = change === "token refresh" ? "test-account" : "another-account"; + const nextToken = + change === "token refresh" ? `${originalToken}-refreshed` : token(nextAccount); + f.auth.auth.apiKey = nextToken; + await fetchCodexModelCatalog(f.registry, undefined, f.fetcher); + const credentials = f.fetcher.mock.calls.map(([, init]) => { + const headers = new Headers(init?.headers); + return { + authorization: headers.get("authorization"), + account: headers.get("chatgpt-account-id"), + }; + }); + expect(credentials).toEqual([ + { authorization: `Bearer ${originalToken}`, account: "test-account" }, + { authorization: `Bearer ${nextToken}`, account: nextAccount }, + ]); + expect(f.registry.getProviderAuth).toHaveBeenCalledTimes(2); }, ); -describe("auth-only validation without HTTP", () => { +describe("catalog credential validation before HTTP", () => { const unexpectedFetch = vi.fn(async () => { throw new Error("must not fetch"); }); @@ -156,19 +146,22 @@ describe("auth-only validation without HTTP", () => { it("rejects an overridden provider endpoint", async () => { const account = fixture(); account.provider.baseUrl = "https://gateway.invalid"; - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unsupportedProvider); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unsupportedProvider); }); it("rejects a catalog model at a different endpoint", async () => { const account = fixture(); account.provider.getModels = () => [{ ...model, baseUrl: "https://gateway.invalid" }]; - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unsupportedProvider); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unsupportedProvider); }); it("rejects an endpoint override in resolved credentials", async () => { const account = fixture(); account.auth.auth.baseUrl = "https://gateway.invalid"; - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject({ + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject({ code: "unsupported", message: "Account requests do not support endpoint overrides.", }); @@ -177,25 +170,30 @@ describe("auth-only validation without HTTP", () => { it("requires an OAuth availability snapshot", async () => { const account = fixture(); account.registry.isUsingOAuth.mockReturnValue(false); - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unavailableAuth); }); it("rejects a non-OAuth resolution even when the snapshot says OAuth", async () => { const account = fixture(); account.auth.source = "models.json"; - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unavailableAuth); }); it("requires the token's account-routing claim", async () => { const account = fixture(); account.auth.auth.apiKey = "e30.e30.private-token"; - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unavailableAuth); }); it("does not repeat private Pi refresh errors", async () => { const account = fixture(); - account.registry.getProviderAuth.mockRejectedValue(new Error("private refresh details")); - await expect(readCodexAuthScope(account.registry)).rejects.toMatchObject(unavailableAuth); + const refreshError = new Error("private refresh details"); + account.registry.getProviderAuth.mockRejectedValue(refreshError); + const request = fetchCodexModelCatalog(account.registry); + await expect(request).rejects.toMatchObject(unavailableAuth); }); }); @@ -428,63 +426,66 @@ it("reports invalid JSON without repeating returned content", async () => { }); }); -it.each(["usage", "catalog", "auth"] as const)( - "cancels during the public %s wrapper handoff", - async (resource) => { - const f = fixture(); - const controller = new AbortController(); - const abort = AbortController.prototype.abort; - // The private flow aborts its deadline in finally, immediately before handing off. - const cleanup = vi.spyOn(AbortController.prototype, "abort").mockImplementation(function ( - this: AbortController, - reason?: unknown, - ) { - abort.call(this, reason); - if (this !== controller) abort.call(controller); - }); - try { - let request: Promise; - if (resource === "auth") request = readCodexAuthScope(f.registry, controller.signal); - else if (resource === "usage") - request = fetchCodexUsage(f.registry, controller.signal, f.fetcher); - else request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher); - await expect(request).rejects.toMatchObject({ code: "cancelled" }); - } finally { - cleanup.mockRestore(); - } - }, -); +it.each([ + ["usage", fetchCodexUsage], + ["catalog", fetchCodexModelCatalog], +] as const)("cancels during the public %s wrapper handoff", async (_resource, fetchData) => { + const f = fixture(); + const controller = new AbortController(); + const abort = AbortController.prototype.abort; + // The private flow aborts its deadline in finally, immediately before handing off. + const cleanup = vi.spyOn(AbortController.prototype, "abort").mockImplementation(function ( + this: AbortController, + reason?: unknown, + ) { + abort.call(this, reason); + if (this !== controller) abort.call(controller); + }); + try { + const request = fetchData(f.registry, controller.signal, f.fetcher); + await expect(request).rejects.toMatchObject({ code: "cancelled" }); + } finally { + cleanup.mockRestore(); + } +}); -it("does not resolve auth for an already-cancelled scope read", async () => { +it("does not resolve auth for an already-cancelled catalog request", async () => { const f = fixture(); const controller = new AbortController(); controller.abort(); - await expect(readCodexAuthScope(f.registry, controller.signal)).rejects.toMatchObject({ - code: "cancelled", - }); + const request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher); + await expect(request).rejects.toMatchObject({ code: "cancelled" }); expect(f.registry.getProviderAuth).not.toHaveBeenCalled(); + expect(f.fetcher).not.toHaveBeenCalled(); }); -it("ignores auth resolving after a scope read is cancelled", async () => { +it("ignores auth resolving after a catalog request is cancelled", async () => { const f = fixture(); const auth = deferred(); f.registry.getProviderAuth.mockReturnValue(auth.promise); const controller = new AbortController(); - const request = readCodexAuthScope(f.registry, controller.signal); + const request = fetchCodexModelCatalog(f.registry, controller.signal, f.fetcher); controller.abort(); await expect(request).rejects.toMatchObject({ code: "cancelled" }); auth.resolve(f.auth); await auth.promise; + expect(f.fetcher).not.toHaveBeenCalled(); }); -it("bounds auth-only resolution by the same ten-second deadline", async () => { +it("bounds the catalog's wait for Pi auth by the ten-second deadline", async () => { vi.useFakeTimers(); const f = fixture(); - f.registry.getProviderAuth.mockReturnValue(new Promise(() => {})); - const result = expect(readCodexAuthScope(f.registry)).rejects.toMatchObject({ code: "timeout" }); + const auth = deferred(); + f.registry.getProviderAuth.mockReturnValue(auth.promise); + const request = fetchCodexModelCatalog(f.registry, undefined, f.fetcher); + const failure = expect(request).rejects.toMatchObject({ code: "timeout" }); await vi.advanceTimersByTimeAsync(10_000); - await result; - expect(vi.getTimerCount()).toBe(0); + await failure; + auth.resolve(f.auth); + await auth.promise; + expect(f.fetcher).not.toHaveBeenCalled(); + const timerCount = vi.getTimerCount(); + expect(timerCount).toBe(0); }); it("cancels during handoff of an already-buffered response", async () => { diff --git a/extensions/codex/src/client.ts b/extensions/codex/src/client.ts index 5ce1f74..60b3a5e 100644 --- a/extensions/codex/src/client.ts +++ b/extensions/codex/src/client.ts @@ -1,4 +1,3 @@ -import { createHash } from "node:crypto"; import type { AuthResult, Provider } from "@earendil-works/pi-ai"; import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; @@ -8,8 +7,7 @@ const ACCOUNT_URLS = { usage: `${BASE_URL}/wham/usage`, catalog: `${BASE_URL}/codex/models?client_version=0.162.0`, } as const; -type NetworkResource = keyof typeof ACCOUNT_URLS; -type AccountResource = NetworkResource | "auth"; +type AccountResource = keyof typeof ACCOUNT_URLS; const TIMEOUT_MS = 10_000; const MAX_RESPONSE_BYTES = 1024 * 1024; @@ -20,13 +18,6 @@ export type CodexAccountRegistry = Pick< getProvider(id: string): Pick | undefined; }; -interface AccountData { - data: unknown; - authScope: string; -} - -export type CodexModelCatalog = AccountData; - type Failure = "auth" | "unsupported" | "cancelled" | "timeout" | "network" | "response" | "http"; export class CodexAccountError extends Error { @@ -46,28 +37,19 @@ export async function fetchCodexUsage( signal?: AbortSignal, fetcher: typeof fetch = fetch, ): Promise { - const result = await readAccountData("usage", registry, signal, fetcher); + const data = await readAccountData("usage", registry, signal, fetcher); if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled."); - return result.data; + return data; } export async function fetchCodexModelCatalog( registry: CodexAccountRegistry, signal?: AbortSignal, fetcher: typeof fetch = fetch, -): Promise { - const result = await readAccountData("catalog", registry, signal, fetcher); +): Promise { + const data = await readAccountData("catalog", registry, signal, fetcher); if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled."); - return result; -} - -export async function readCodexAuthScope( - registry: CodexAccountRegistry, - signal?: AbortSignal, -): Promise { - const result = await readAccountData("auth", registry, signal); - if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled."); - return result.authScope; + return data; } async function readAccountData( @@ -75,7 +57,7 @@ async function readAccountData( registry: CodexAccountRegistry, signal: AbortSignal | undefined, fetcher: typeof fetch = fetch, -): Promise { +): Promise { const deadline = new AbortController(); const timer = setTimeout(() => deadline.abort(), TIMEOUT_MS); const requestSignal = signal ? AbortSignal.any([signal, deadline.signal]) : deadline.signal; @@ -83,14 +65,9 @@ async function readAccountData( requestSignal.throwIfAborted(); const headers = await resolveAccountHeaders(registry, requestSignal); requestSignal.throwIfAborted(); - // Hash only the normalized credentials we actually send, never persist raw headers. - const authScope = createHash("sha256") - .update(JSON.stringify([headers.get("authorization"), headers.get("chatgpt-account-id")])) - .digest("hex"); - if (resource === "auth") return { data: undefined, authScope }; const data = await requestAccountJson(resource, headers, requestSignal, fetcher); requestSignal.throwIfAborted(); - return { data, authScope }; + return data; } catch (error) { if (signal?.aborted) throw new CodexAccountError("cancelled", "Codex account request cancelled."); @@ -194,7 +171,7 @@ function authError(): CodexAccountError { } async function requestAccountJson( - resource: NetworkResource, + resource: AccountResource, headers: Headers, requestSignal: AbortSignal, fetcher: typeof fetch, diff --git a/extensions/codex/src/fast-sdk.test.ts b/extensions/codex/src/fast-sdk.test.ts index 846bd01..3318487 100644 --- a/extensions/codex/src/fast-sdk.test.ts +++ b/extensions/codex/src/fast-sdk.test.ts @@ -554,7 +554,6 @@ function installResponseTransports(transport: Transport) { function createHttpResponses(transport: Transport, payloads: Record[]) { const catalog = vi.fn(async () => - // The client wraps this internally as { data, authScope }; the network JSON does not. Response.json({ models: [{ slug: "gpt-6-sol", service_tiers: [{ id: "priority" }] }] }), ); const sse = vi.fn(async (_url, init) => { diff --git a/extensions/codex/src/fast.test.ts b/extensions/codex/src/fast.test.ts index 5946772..b4a4a26 100644 --- a/extensions/codex/src/fast.test.ts +++ b/extensions/codex/src/fast.test.ts @@ -5,11 +5,7 @@ import type { SessionEntry, } from "@earendil-works/pi-coding-agent"; import { describe, expect, it, vi } from "vitest"; -import { - CodexAccountError, - type CodexModelCatalog, - type fetchCodexModelCatalog, -} from "./client.ts"; +import { CodexAccountError, type fetchCodexModelCatalog } from "./client.ts"; import { FastController, type FastHost } from "./fast.ts"; import { FAST_ENTRY, type FastPreference } from "./fast-state.ts"; import { CONTEXT_ENTRY } from "./state.ts"; @@ -27,9 +23,8 @@ const model: Model = { maxTokens: 128_000, }; const otherModel: Model = { ...model, id: "gpt-6-astra", name: "Astra" }; -const catalog: CodexModelCatalog = { - authScope: "scope-a", - data: { models: [{ slug: model.id, service_tiers: [{ id: "default" }, { id: "priority" }] }] }, +const catalog = { + models: [{ slug: model.id, service_tiers: [{ id: "default" }, { id: "priority" }] }], }; describe("commands and ownership", () => { @@ -113,7 +108,7 @@ describe("commands and ownership", () => { it("persists on only after cost confirmation and matching catalog discovery complete", async () => { const fast = createFastControllerFixture(); const confirmation = deferred(); - const response = deferred(); + const response = deferred(); const started = deferred(); fast.ctx.ui.confirm.mockReturnValue(confirmation.promise); fast.fetchCatalog.mockImplementationOnce(() => { @@ -134,7 +129,7 @@ describe("commands and ownership", () => { fast.ctx.modelRegistry, expect.any(AbortSignal), ); - // No separate auth-scope preflight; the account client resolves auth for its one request. + // The account client resolves auth for its one request; no separate preflight. expect(fast.ctx.modelRegistry.getProviderAuth).not.toHaveBeenCalled(); expect(fast.appendEntry).not.toHaveBeenCalled(); expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined(); @@ -166,7 +161,7 @@ describe("commands and ownership", () => { }, ])("does not enable without established model support: %j", async ({ data, reason }) => { const fast = createFastControllerFixture(); - fast.fetchCatalog.mockResolvedValue({ ...catalog, data }); + fast.fetchCatalog.mockResolvedValue(data); await fast.controller.handle("on", fast.ctx); expect(fast.appendEntry).not.toHaveBeenCalled(); expect(fast.controller.transform({ model: model.id }, fast.ctx)).toBeUndefined(); @@ -401,7 +396,7 @@ describe("asynchronous enable ownership", () => { "discards catalog discovery after the %s changes", async (changed) => { const fast = createFastControllerFixture(); - const response = deferred(); + const response = deferred(); const started = deferred(); fast.fetchCatalog.mockImplementationOnce(() => { started.resolve(); @@ -420,7 +415,7 @@ describe("asynchronous enable ownership", () => { it("aborts invalidated discovery without letting a late result clear a newer operation", async () => { const fast = createFastControllerFixture(); - const oldCatalog = deferred(); + const oldCatalog = deferred(); const oldStarted = deferred(); fast.fetchCatalog.mockImplementationOnce(() => { oldStarted.resolve(); @@ -458,7 +453,7 @@ describe("asynchronous enable ownership", () => { for (const outcome of ["resolve", "reject"]) { const fast = createFastControllerFixture(); const confirmation = deferred(); - const response = deferred(); + const response = deferred(); const started = deferred(); if (stage === "confirmation") fast.ctx.ui.confirm.mockReturnValue(confirmation.promise); fast.fetchCatalog.mockImplementationOnce(() => { diff --git a/extensions/codex/src/fast.ts b/extensions/codex/src/fast.ts index 22d877a..e2a4221 100644 --- a/extensions/codex/src/fast.ts +++ b/extensions/codex/src/fast.ts @@ -235,7 +235,7 @@ export class FastController { ): Promise { const catalog = await this.fetchCatalog(ctx.modelRegistry, preparation.abortController.signal); if (!this.canApplyPreparation(ctx, preparation) || !ctx.model) return false; - const eligibility = codexFastEligibility(catalog.data, ctx.model.id); + const eligibility = codexFastEligibility(catalog, ctx.model.id); if (eligibility === "supported") return true; const reason = eligibility === "unsupported" diff --git a/extensions/codex/src/index.test.ts b/extensions/codex/src/index.test.ts index 82bdda5..f6865f2 100644 --- a/extensions/codex/src/index.test.ts +++ b/extensions/codex/src/index.test.ts @@ -8,14 +8,13 @@ import { import type { Api, Model } from "@earendil-works/pi-ai"; import { afterAll, afterEach, expect, it, vi } from "vitest"; import codex from "./index.ts"; -import { fetchCodexModelCatalog, fetchCodexUsage, readCodexAuthScope } from "./client.ts"; +import { fetchCodexModelCatalog, fetchCodexUsage } from "./client.ts"; import { customInteractionUI } from "./custom-interaction.test-helper.ts"; vi.mock("./client.ts", async (importOriginal) => ({ ...(await importOriginal()), fetchCodexUsage: vi.fn(), fetchCodexModelCatalog: vi.fn(), - readCodexAuthScope: vi.fn(), })); const directory = await vi.hoisted(async () => { @@ -43,7 +42,6 @@ afterEach(() => { async function load(prefix = "eleith", mode: ExtensionCommandContext["mode"] = "print") { vi.mocked(fetchCodexUsage).mockReset(); vi.mocked(fetchCodexModelCatalog).mockReset(); - vi.mocked(readCodexAuthScope).mockReset(); writeFileSync(configPath, JSON.stringify({ commandPrefix: prefix })); type Command = Parameters[1]; type Listener = ( @@ -154,7 +152,6 @@ it("wires restore/reconcile hooks without provider calls for an unsupported mode expect(context.pi.on.mock.calls.map(([name]) => name)).not.toContain("before_agent_start"); expect(fetchCodexUsage).not.toHaveBeenCalled(); expect(fetchCodexModelCatalog).not.toHaveBeenCalled(); - expect(readCodexAuthScope).not.toHaveBeenCalled(); }); it("rejects removed commands and additional control arguments without side effects", async () => { @@ -176,7 +173,6 @@ it("rejects removed commands and additional control arguments without side effec expect(context.notify).toHaveBeenLastCalledWith(usage, "warning"); } expect(fetchCodexModelCatalog).not.toHaveBeenCalled(); - expect(readCodexAuthScope).not.toHaveBeenCalled(); expect(fetchCodexUsage).not.toHaveBeenCalled(); expect(context.pi.appendEntry).not.toHaveBeenCalled(); expect(context.pi.setModel).not.toHaveBeenCalled(); @@ -265,7 +261,6 @@ it("combines context and fast mode in a read-only dashboard with the configured expect(getProviderAuth).not.toHaveBeenCalled(); expect(fetchCodexUsage).not.toHaveBeenCalled(); expect(fetchCodexModelCatalog).not.toHaveBeenCalled(); - expect(readCodexAuthScope).not.toHaveBeenCalled(); expect(context.pi.appendEntry).not.toHaveBeenCalled(); expect(context.pi.setModel).not.toHaveBeenCalled(); const dashboard = context.notify.mock.calls[0][0]; @@ -277,7 +272,6 @@ it("combines context and fast mode in a read-only dashboard with the configured expect(getProviderAuth).not.toHaveBeenCalled(); expect(fetchCodexUsage).not.toHaveBeenCalled(); expect(fetchCodexModelCatalog).not.toHaveBeenCalled(); - expect(readCodexAuthScope).not.toHaveBeenCalled(); expect(context.pi.appendEntry).not.toHaveBeenCalled(); expect(context.pi.setModel).not.toHaveBeenCalled(); await context.emit("session_shutdown"); @@ -324,7 +318,6 @@ it("routes every TUI read-only alias to the shared overview inspector, not notif expect(context.notify).not.toHaveBeenCalled(); expect(fetchCodexUsage).not.toHaveBeenCalled(); expect(fetchCodexModelCatalog).not.toHaveBeenCalled(); - expect(readCodexAuthScope).not.toHaveBeenCalled(); expect(context.pi.setModel).not.toHaveBeenCalled(); expect(context.pi.appendEntry).not.toHaveBeenCalled(); await context.emit("session_shutdown"); -- 2.54.0