feat: show Codex usage in provider usage center

This commit is contained in:
Josh Lambert
2026-08-27 12:17:01 -04:00
parent 24b1fa1fc7
commit e87bb5b6a8
27 changed files with 1023 additions and 2 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@kilocode/cli": minor
"kilo-code": minor
---
Show ChatGPT Codex quota alongside other provider usage in the CLI and VS Code.
+81 -2
View File
@@ -9,14 +9,22 @@ import { Integration } from "../integration"
import { PluginV2 } from "../plugin"
import { ProviderV2 } from "../provider"
import * as Cloud from "./provider-usage/cloud"
import * as Codex from "./provider-usage/codex"
import { bindings, direct, type Candidate } from "./provider-usage/minimax/usage"
const successTtl = 60_000
const errorTtl = 10_000
const readyPlugin = PluginV2.ID.make("config-provider")
type Input =
| { status: "absent" }
| { status: "failed"; connection: string }
| { status: "ready"; connection: string; identity: string; candidate: Codex.Candidate }
interface AdapterContext {
candidates: readonly Candidate[]
codex: Input
scope: string | undefined
failedCandidates: readonly Candidate["providerID"][]
cloud: (() => Promise<Cloud.CloudState>) | undefined
token: string | undefined
@@ -25,6 +33,7 @@ interface AdapterContext {
fetch: typeof fetch
usage: typeof Cloud.fetchCodingPlanUsage
identityCurrent(identity: string): boolean
current(identity: string): boolean
source(id: string, load: () => Promise<Contract.UsageSnapshot>, identity?: string): Promise<Contract.UsageSnapshot>
preserve(prefix: string, identity?: string): Contract.UsageSnapshot[]
prune(prefix: string, keep: string[]): void
@@ -80,7 +89,21 @@ const minimax: Adapter = {
},
}
const registry: readonly Adapter[] = [managed, minimax]
const codex: Adapter = {
cachePrefixes: ["codex-chatgpt"],
async run(ctx) {
if (ctx.codex.status === "absent") return { items: [] }
if (ctx.codex.status === "failed") {
return { items: ctx.scope ? ctx.preserve("codex-chatgpt", ctx.scope) : [] }
}
const current = ctx.codex
if (!ctx.current(current.identity)) return { items: [] }
const item = await ctx.source("codex-chatgpt", () => Codex.load(current.candidate, ctx.fetch), current.identity)
return { items: ctx.current(current.identity) ? [item] : [] }
},
}
const registry: readonly Adapter[] = [managed, minimax, codex]
export class ServiceError extends Schema.TaggedErrorClass<ServiceError>()("ProviderUsageServiceError", {
message: Schema.String,
@@ -105,6 +128,7 @@ interface State {
sources: Map<string, SourceCell>
cloud: CloudCell
cloudIdentity?: string
codex?: { connection: string; identity: string }
}
function fingerprint(value: string) {
@@ -122,6 +146,7 @@ function scopeCloudCache(state: State, token: string | undefined) {
function stale(next: Contract.UsageSnapshot, previous: Contract.UsageSnapshot | undefined) {
if (next.fetchState !== "unavailable" && next.fetchState !== "error") return next
if (next.error?.code === "codex_auth_unavailable" && !next.error.retryable) return next
if (!previous || (previous.fetchState !== "ready" && previous.fetchState !== "stale")) return next
return {
...previous,
@@ -279,12 +304,57 @@ function nonempty(value: unknown) {
return text || undefined
}
const openai = Effect.fn("ProviderUsage.openai")(function* (
provider: ProviderV2.Info | undefined,
integrations: Integration.Interface,
) {
if (!provider || provider.disabled) return { status: "absent" as const }
const connection = yield* integrations.connection.active(provider.integrationID ?? Integration.ID.make(provider.id))
if (!connection) return { status: "absent" as const }
const marker = fingerprint(`${connection.type}:${connection.type === "credential" ? connection.id : connection.name}`)
const resolved = yield* integrations.connection.resolve(connection).pipe(
Effect.map((value) => ({ ok: true as const, value })),
Effect.catch(() => Effect.succeed({ ok: false as const })),
)
if (!resolved.ok) return { status: "failed" as const, connection: marker }
if (resolved.value?.type !== "oauth" || !resolved.value.access) return { status: "absent" as const }
const raw = resolved.value.metadata?.accountID
const account = typeof raw === "string" && /^[A-Za-z0-9._-]{1,256}$/.test(raw) ? raw : undefined
return {
status: "ready" as const,
connection: marker,
identity: fingerprint(
JSON.stringify([marker, typeof raw === "string" ? raw : "", resolved.value.access, resolved.value.refresh]),
),
candidate: {
label: provider.name,
access: resolved.value.access,
...(account ? { account } : {}),
},
}
})
function scoped(state: State, current: Input) {
if (current.status === "failed" && state.codex?.connection === current.connection) return state.codex.identity
if (
current.status === "ready" &&
state.codex?.connection === current.connection &&
state.codex.identity === current.identity
) {
return current.identity
}
state.codex = current.status === "ready" ? { connection: current.connection, identity: current.identity } : undefined
prune(state, "codex-chatgpt", [])
return state.codex?.identity
}
const inputs = Effect.fn("ProviderUsage.inputs")(function* (
catalog: Catalog.Interface,
integrations: Integration.Interface,
) {
const providers = yield* catalog.provider.all()
const byID = new Map(providers.map((provider) => [provider.id, provider]))
const codex = yield* openai(byID.get(ProviderV2.ID.openai), integrations)
const failedCandidates: Candidate["providerID"][] = []
const candidates = yield* Effect.forEach(Object.keys(bindings) as (keyof typeof bindings)[], (providerID) =>
Effect.gen(function* () {
@@ -312,6 +382,7 @@ const inputs = Effect.fn("ProviderUsage.inputs")(function* (
kilo.ok && kilo.value?.type === "oauth" && !organization && kilo.value.access ? kilo.value.access : undefined
return {
candidates: candidates.filter((item): item is Candidate => item !== undefined),
codex,
failedCandidates,
token,
cloudReliable,
@@ -330,8 +401,11 @@ function makeService(
yield* ready
const current = yield* inputs(catalog, integrations)
const cloudIdentity = current.cloudReliable ? scopeCloudCache(state, current.token) : state.cloudIdentity
const identity = scoped(state, current.codex)
const ctx: AdapterContext = {
candidates: current.candidates,
codex: current.codex,
scope: identity,
failedCandidates: current.failedCandidates,
cloud:
current.token && cloudIdentity
@@ -343,6 +417,7 @@ function makeService(
fetch: transport.fetch,
usage: transport.usage,
identityCurrent: (identity) => state.cloudIdentity === identity,
current: (identity) => state.codex?.identity === identity,
source: (id, load, identity) => source(state, id, force, load, identity),
preserve: (prefix, identity) => preserve(state, prefix, identity),
prune: (prefix, keep) => prune(state, prefix, keep),
@@ -367,7 +442,11 @@ function makeService(
(value): value is string => value !== undefined,
)
return {
items: results.flatMap((result) => result.items),
items: results
.flatMap((result) => result.items)
.filter(
(item) => item.id !== "codex-chatgpt" || (identity !== undefined && state.codex?.identity === identity),
),
generatedAt: stamps.toSorted().at(-1) ?? new Date().toISOString(),
} satisfies Contract.Info
})
@@ -0,0 +1,288 @@
import type { ProviderUsage } from "@opencode-ai/schema/kilocode/provider-usage"
const url = "https://chatgpt.com/backend-api/wham/usage"
const manage = "https://chatgpt.com/codex/settings/usage"
const limit = 64 * 1024
const timeout = 5_000
const maximum = 8_640_000_000_000_000
const plans: Record<string, string> = {
plus: "ChatGPT Plus",
pro: "ChatGPT Pro",
prolite: "ChatGPT Pro Lite",
business: "ChatGPT Business",
enterprise: "ChatGPT Enterprise",
edu: "ChatGPT Edu",
education: "ChatGPT Education",
team: "ChatGPT Team",
free: "ChatGPT Free",
go: "ChatGPT Go",
}
export interface Candidate {
label: string
access: string
account?: string
}
interface Window {
used: number
duration?: number
reset?: number
after?: number
}
interface Rate {
allowed?: boolean
reached?: boolean
primary?: Window
secondary?: Window
}
interface Native {
plan?: string
rate?: Rate
additional: { name: string; rate: Rate }[]
}
class Failure extends Error {
constructor(readonly code: "network" | "auth" | "http" | "size" | "invalid") {
super(code === "auth" ? "ChatGPT authentication is unavailable." : "Codex usage is unavailable.")
}
}
function object(input: unknown): input is Record<string, unknown> {
return typeof input === "object" && input !== null && !Array.isArray(input)
}
function number(input: unknown) {
return typeof input === "number" && Number.isFinite(input) ? input : undefined
}
function window(input: unknown): Window | undefined {
if (!object(input)) return undefined
const used = number(input.used_percent)
if (used === undefined) return undefined
return {
used,
duration: number(input.limit_window_seconds),
reset: number(input.reset_at),
after: number(input.reset_after_seconds),
}
}
function rate(input: unknown): Rate | undefined {
if (!object(input)) return undefined
return {
allowed: typeof input.allowed === "boolean" ? input.allowed : undefined,
reached: typeof input.limit_reached === "boolean" ? input.limit_reached : undefined,
primary: window(input.primary_window),
secondary: window(input.secondary_window),
}
}
export function decode(input: unknown): Native {
if (!object(input)) throw new Failure("invalid")
const additional = Array.isArray(input.additional_rate_limits)
? input.additional_rate_limits.flatMap((item) => {
if (!object(item)) return []
const limit = rate(item.rate_limit)
if (!limit) return []
const name =
typeof item.limit_name === "string" && item.limit_name.trim()
? item.limit_name.trim()
: typeof item.metered_feature === "string" && item.metered_feature.trim()
? item.metered_feature.trim()
: "Additional quota"
return [{ name, rate: limit }]
})
: []
return {
plan: typeof input.plan_type === "string" && input.plan_type ? input.plan_type : undefined,
rate: rate(input.rate_limit),
additional,
}
}
async function body(response: Response) {
const declared = Number(response.headers.get("content-length"))
if (Number.isFinite(declared) && declared > limit) {
response.body?.cancel().catch(() => undefined)
throw new Failure("size")
}
if (!response.body) {
const value = await response.arrayBuffer()
if (value.byteLength > limit) throw new Failure("size")
return new TextDecoder().decode(value)
}
const reader = response.body.getReader()
const chunks: Uint8Array[] = []
let size = 0
while (true) {
const chunk = await reader.read()
if (chunk.done) break
if (!chunk.value) continue
size += chunk.value.byteLength
if (size > limit) {
await reader.cancel().catch(() => undefined)
throw new Failure("size")
}
chunks.push(chunk.value)
}
const value = new Uint8Array(size)
let offset = 0
for (const chunk of chunks) {
value.set(chunk, offset)
offset += chunk.byteLength
}
return new TextDecoder().decode(value)
}
export async function query(candidate: Candidate, fetcher: typeof fetch = fetch): Promise<Native> {
const headers: Record<string, string> = {
Accept: "application/json",
Authorization: `Bearer ${candidate.access}`,
}
if (candidate.account) headers["ChatGPT-Account-Id"] = candidate.account
const response = await fetcher(url, {
method: "GET",
headers,
cache: "no-store",
redirect: "error",
signal: AbortSignal.timeout(timeout),
}).catch(() => {
throw new Failure("network")
})
if (!response.ok) {
response.body?.cancel().catch(() => undefined)
throw new Failure(response.status === 401 || response.status === 403 ? "auth" : "http")
}
const text = await body(response)
try {
return decode(JSON.parse(text))
} catch {
throw new Failure("invalid")
}
}
function reset(value: Window, now: number) {
const direct = value.reset === undefined ? undefined : value.reset * 1000
if (direct !== undefined && direct > 0 && direct <= maximum) return new Date(direct).toISOString()
const offset = value.after === undefined ? undefined : value.after * 1000
const relative = offset === undefined ? undefined : now + offset
if (
relative !== undefined &&
offset !== undefined &&
offset > 0 &&
relative <= maximum &&
Number.isSafeInteger(relative)
) {
return new Date(relative).toISOString()
}
return undefined
}
function period(duration: number): ProviderUsage.UsagePeriod | undefined {
for (const [unit, seconds] of [
["week", 604_800],
["day", 86_400],
["hour", 3_600],
] as const) {
if (duration % seconds === 0) return { unit, value: duration / seconds }
}
return undefined
}
function windows(name: string, rate: Rate | undefined, now: number, used: Map<string, number>) {
if (!rate) return []
const clean =
name
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-+|-+$/g, "") || "quota"
let count = (used.get(clean) ?? 0) + 1
let slug = count === 1 ? clean : `${clean}-${count}`
while (used.has(slug)) {
count++
slug = `${clean}-${count}`
}
used.set(clean, count)
used.set(slug, 1)
return (
[
["primary", rate.primary],
["secondary", rate.secondary],
] as const
).flatMap(([slot, value]) => {
if (!value) return []
const exhausted = rate.allowed === false || rate.reached === true || value.used >= 100
const percent = exhausted ? 100 : Math.min(100, Math.max(0, value.used))
const duration =
value.duration !== undefined && value.duration > 0 && Number.isSafeInteger(value.duration * 1000)
? value.duration
: undefined
return [
{
id: `${slug}-${slot}`,
resource: name,
unit: "percent",
orientation: "used_percent",
used: percent,
remaining: 100 - percent,
limit: 100,
durationMs: duration === undefined ? undefined : duration * 1000,
period: duration === undefined ? undefined : period(duration),
resetAt: reset(value, now),
state: exhausted ? "exhausted" : "active",
} satisfies ProviderUsage.UsageWindow,
]
})
}
export function normalize(native: Native, label = "OpenAI"): ProviderUsage.UsageSnapshot {
const now = Date.now()
const seen = new Map<string, number>()
const main = windows("Codex", native.rate, now, seen)
const additional = native.additional.flatMap((item) => windows(item.name, item.rate, now, seen))
return {
id: "codex-chatgpt",
providerID: "openai",
sourceKind: "direct",
providerLabel: label,
planLabel: native.plan ? (plans[native.plan.toLowerCase()] ?? `ChatGPT ${native.plan}`) : "ChatGPT Codex",
sourceLabel: "ChatGPT OAuth",
fetchState: "ready",
planState: "active",
routingState: "not_applicable",
fetchedAt: new Date(now).toISOString(),
managementUrl: manage,
windows: [...main, ...additional],
}
}
function unavailable(label: string, auth: boolean): ProviderUsage.UsageSnapshot {
return {
id: "codex-chatgpt",
providerID: "openai",
sourceKind: "direct",
providerLabel: label,
planLabel: "ChatGPT Codex",
sourceLabel: "ChatGPT OAuth",
fetchState: "unavailable",
planState: "unknown",
routingState: "not_applicable",
managementUrl: manage,
windows: [],
error: {
code: auth ? "codex_auth_unavailable" : "codex_usage_unavailable",
message: auth ? "Reconnect ChatGPT to view Codex usage." : "Usage unavailable.",
retryable: !auth,
},
}
}
export function load(candidate: Candidate, fetcher: typeof fetch = fetch) {
return query(candidate, fetcher)
.then((native) => normalize(native, candidate.label))
.catch((error) => unavailable(candidate.label, error instanceof Failure && error.code === "auth"))
}
@@ -0,0 +1,620 @@
import { describe, expect, test } from "bun:test"
import { Deferred, Effect, Fiber, Layer } from "effect"
import path from "node:path"
import { Catalog } from "../src/catalog"
import { Credential } from "../src/credential"
import { Database } from "../src/database/database"
import { AppNodeBuilder } from "../src/effect/app-node-builder"
import { LayerNode } from "../src/effect/layer-node"
import { Global } from "../src/global"
import { Integration } from "../src/integration"
import { ProviderUsage } from "../src/kilocode/provider-usage"
import { decode, load, normalize } from "../src/kilocode/provider-usage/codex"
import { Location } from "../src/location"
import { PluginV2 } from "../src/plugin"
import { ProviderV2 } from "../src/provider"
import { AbsolutePath } from "../src/schema"
import { location } from "./fixture/location"
import { tmpdir } from "./fixture/tmpdir"
import { testEffect } from "./lib/effect"
const openai = Integration.ID.make("openai")
const minimax = Integration.ID.make("minimax-coding-plan")
const method = Integration.MethodID.make("chatgpt-browser")
const it = testEffect(Layer.empty)
type RequestHandler = (input: string | URL | Request, init?: RequestInit) => Promise<Response>
type Fixture = {
usage: ProviderUsage.Interface
catalog: Catalog.Interface
integrations: Integration.Interface
credentials: Credential.Interface
requests: Array<{ url: string; init: RequestInit }>
refreshes: { count: number }
}
const window = (used: number, seconds = 18_000) => ({
used_percent: used,
limit_window_seconds: seconds,
reset_after_seconds: seconds,
reset_at: Math.floor(Date.now() / 1000) + seconds,
})
const payload = (overrides: Record<string, unknown> = {}) => ({
plan_type: "plus",
rate_limit: {
allowed: true,
limit_reached: false,
primary_window: window(20),
secondary_window: window(35, 604_800),
},
additional_rate_limits: [],
...overrides,
})
const native = (remaining: number) =>
Response.json({
base_resp: { status_code: 0 },
model_remains: [
{
model_name: "general",
current_interval_remaining_percent: remaining,
current_interval_status: 1,
},
],
})
const fixture = <A, E, R>(
handler: RequestHandler,
body: (value: Fixture) => Effect.Effect<A, E, R>,
opts: { minimax?: boolean; failure?: () => boolean } = {},
) =>
Effect.acquireRelease(
Effect.promise(() => tmpdir()),
(dir) => Effect.promise(() => dir[Symbol.asyncDispose]()),
).pipe(
Effect.flatMap((dir) => {
const requests: Fixture["requests"] = []
const refreshes = { count: 0 }
const transport = Layer.succeed(ProviderUsage.Transport, {
fetch: Object.assign(
(input: string | URL | Request, init?: RequestInit) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
requests.push({ url, init: init ?? {} })
return handler(input, init)
},
{ preconnect: fetch.preconnect },
),
plans: async () => [],
byok: async () => [],
usage: async () => {
throw new Error("Unexpected managed usage request")
},
})
const layer = AppNodeBuilder.build(
LayerNode.group([ProviderUsage.node, Catalog.node, Integration.node, Credential.node, PluginV2.node]),
[
[
Global.node,
Global.layerWith({
home: dir.path,
data: dir.path,
cache: dir.path,
config: dir.path,
state: dir.path,
tmp: dir.path,
bin: dir.path,
log: dir.path,
repos: dir.path,
}),
],
[Database.node, Database.layerFromPath(path.join(dir.path, "provider-usage.sqlite"))],
[
Location.node,
Layer.succeed(
Location.Service,
Location.Service.of(location(Location.Ref.make({ directory: AbsolutePath.make(dir.path) }))),
),
],
[ProviderUsage.transportNode, transport],
],
)
return Effect.gen(function* () {
const plugins = yield* PluginV2.Service
const catalog = yield* Catalog.Service
const integrations = yield* Integration.Service
const credentials = yield* Credential.Service
const usage = yield* ProviderUsage.Service
yield* plugins.add(PluginV2.ID.make("config-provider"), (host) =>
Effect.gen(function* () {
yield* host.integration.transform((draft) => {
draft.method.update({
integrationID: openai,
method: { id: method, type: "oauth", label: "ChatGPT" },
authorize: () => Effect.die("Unexpected OAuth authorization"),
refresh: (value) =>
Effect.suspend(() => {
refreshes.count++
if (opts.failure?.()) return Effect.fail(new Error("private OAuth refresh failure"))
return Effect.succeed(
Credential.OAuth.make({
...value,
methodID: method,
access: "refreshed-access-token",
refresh: "rotated-refresh-token",
expires: Date.now() + 3_600_000,
}),
)
}),
})
draft.method.update({
integrationID: openai,
method: { type: "key", label: "API key" },
})
if (opts.minimax)
draft.method.update({
integrationID: minimax,
method: { type: "key", label: "MiniMax API key" },
})
})
yield* host.catalog.transform((draft) => {
draft.provider.update(openai, (provider) => {
provider.name = "OpenAI"
provider.integrationID = openai
})
if (opts.minimax)
draft.provider.update(minimax, (provider) => {
provider.name = "MiniMax Global"
provider.integrationID = minimax
})
})
}),
)
return yield* body({ usage, catalog, integrations, credentials, requests, refreshes })
}).pipe(Effect.provide(layer))
}),
)
const connect = Effect.fn("CodexProviderUsageTest.connect")(function* (
credentials: Credential.Interface,
input: { access?: string; account?: string; expires?: number } = {},
) {
return yield* credentials.create({
integrationID: openai,
label: input.account ?? "Personal",
value: Credential.OAuth.make({
type: "oauth",
methodID: method,
access: input.access ?? "codex-access-token",
refresh: "codex-refresh-token",
expires: input.expires ?? Date.now() + 3_600_000,
metadata: input.account ? { accountID: input.account } : undefined,
}),
})
})
describe("Codex provider usage service", () => {
it.live("uses OAuth bearer and account headers with bounded direct transport settings", () =>
fixture(
async () => Response.json(payload()),
({ usage, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials, { account: "acct-personal" })
const result = yield* usage.get()
const headers = new Headers(requests[0]?.init.headers)
expect(requests).toHaveLength(1)
expect(requests[0]?.url).toBe("https://chatgpt.com/backend-api/wham/usage")
expect(requests[0]?.init).toMatchObject({ method: "GET", cache: "no-store", redirect: "error" })
expect(requests[0]?.init.signal).toBeInstanceOf(AbortSignal)
expect(headers.get("authorization")).toBe("Bearer codex-access-token")
expect(headers.get("chatgpt-account-id")).toBe("acct-personal")
expect(result.items).toHaveLength(1)
expect(result.items[0]).toMatchObject({
id: "codex-chatgpt",
providerID: "openai",
sourceKind: "direct",
fetchState: "ready",
planState: "active",
routingState: "not_applicable",
windows: [
{
orientation: "used_percent",
unit: "percent",
used: 20,
remaining: 80,
limit: 100,
durationMs: 18_000_000,
period: { unit: "hour", value: 5 },
state: "active",
},
{
orientation: "used_percent",
used: 35,
remaining: 65,
durationMs: 604_800_000,
period: { unit: "week", value: 1 },
},
],
})
expect(JSON.stringify(result)).not.toContain("codex-access-token")
expect(JSON.stringify(result)).not.toContain("acct-personal")
}),
),
)
it.live("omits the account header and accepts a successful response without windows", () =>
fixture(
async () => Response.json(payload({ rate_limit: null })),
({ usage, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials)
const result = yield* usage.get()
expect(new Headers(requests[0]?.init.headers).has("chatgpt-account-id")).toBe(false)
expect(result.items[0]).toMatchObject({ id: "codex-chatgpt", fetchState: "ready", windows: [] })
}),
),
)
it.live("refreshes expired OAuth credentials through the registered implementation and persists them", () =>
fixture(
async () => Response.json(payload()),
({ usage, credentials, requests, refreshes }) =>
Effect.gen(function* () {
const saved = yield* connect(credentials, { account: "acct-refresh", expires: Date.now() - 1 })
const result = yield* usage.get()
const stored = yield* credentials.get(saved.id)
const headers = new Headers(requests[0]?.init.headers)
expect(result.items[0]?.fetchState).toBe("ready")
expect(refreshes.count).toBe(1)
expect(headers.get("authorization")).toBe("Bearer refreshed-access-token")
expect(headers.get("chatgpt-account-id")).toBe("acct-refresh")
expect(stored?.value).toMatchObject({
type: "oauth",
access: "refreshed-access-token",
refresh: "rotated-refresh-token",
metadata: { accountID: "acct-refresh" },
})
expect((yield* usage.get()).items[0]?.fetchState).toBe("ready")
expect(refreshes.count).toBe(1)
expect(requests).toHaveLength(1)
}),
),
)
it.live("preserves transient refresh failures only for the same active credential", () => {
const failure = { current: false }
return fixture(
async () => Response.json(payload()),
({ usage, credentials, requests, refreshes }) =>
Effect.gen(function* () {
const saved = yield* connect(credentials, { account: "acct-original" })
const ready = (yield* usage.get()).items[0]
expect(ready?.fetchState).toBe("ready")
expect(ready?.windows[0]?.used).toBe(20)
yield* credentials.update(saved.id, {
value: Credential.OAuth.make({
type: "oauth",
methodID: method,
access: "codex-access-token",
refresh: "codex-refresh-token",
expires: Date.now() - 1,
metadata: { accountID: "acct-original" },
}),
})
failure.current = true
const stale = yield* usage.get()
expect(stale.items[0]?.fetchState).toBe("stale")
expect(stale.items[0]?.windows[0]?.used).toBe(20)
expect(JSON.stringify(stale)).not.toContain("private OAuth refresh failure")
expect(requests).toHaveLength(1)
expect(refreshes.count).toBe(1)
yield* connect(credentials, { account: "acct-replacement", expires: Date.now() - 1 })
expect((yield* usage.get()).items).toEqual([])
expect(requests).toHaveLength(1)
expect(refreshes.count).toBe(2)
}),
{ failure: () => failure.current },
)
})
it.live("invalidates cached usage when an account changes despite reusing its access token", () =>
fixture(
async (_input, init) =>
Response.json(
payload({
rate_limit: {
allowed: true,
limit_reached: false,
primary_window: window(new Headers(init?.headers).get("chatgpt-account-id") === "acct-first" ? 15 : 75),
},
}),
),
({ usage, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials, { access: "shared-access-token", account: "acct-first" })
expect((yield* usage.get()).items[0]?.windows[0]).toMatchObject({ used: 15 })
yield* connect(credentials, { access: "shared-access-token", account: "acct-second" })
expect((yield* usage.get()).items[0]?.windows[0]).toMatchObject({ used: 75 })
expect(requests).toHaveLength(2)
expect(new Headers(requests[1]?.init.headers).get("chatgpt-account-id")).toBe("acct-second")
}),
),
)
it.live("prevents an old in-flight account request from overwriting its replacement", () =>
Effect.gen(function* () {
const started = yield* Deferred.make<void>()
const pending: { release?: (response: Response) => void } = {}
return yield* fixture(
(input, init) => {
if (new Headers(init?.headers).get("chatgpt-account-id") === "acct-old") {
Effect.runSync(Deferred.succeed(started, undefined))
return new Promise((resolve) => {
pending.release = resolve
})
}
return Promise.resolve(Response.json(payload({ rate_limit: { allowed: true, primary_window: window(70) } })))
},
({ usage, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials, { access: "shared-access-token", account: "acct-old" })
const first = yield* usage.get().pipe(Effect.forkChild)
yield* Deferred.await(started).pipe(Effect.timeout("2 seconds"))
yield* connect(credentials, { access: "shared-access-token", account: "acct-new" })
const second = yield* usage.get()
expect(second.items[0]?.windows[0]).toMatchObject({ used: 70 })
expect(requests).toHaveLength(2)
pending.release?.(Response.json(payload({ rate_limit: { allowed: true, primary_window: window(10) } })))
expect((yield* Fiber.join(first)).items).toEqual([])
expect((yield* usage.get()).items[0]?.windows[0]).toMatchObject({ used: 70 })
expect(requests).toHaveLength(2)
}),
)
}),
)
for (const state of ["disabled", "removed"]) {
it.live(`prunes cached usage when the OpenAI provider is ${state}`, () =>
fixture(
async () => Response.json(payload()),
({ usage, catalog, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials)
expect((yield* usage.get()).items).toHaveLength(1)
yield* catalog.transform((draft) => {
if (state === "removed") return draft.provider.remove(ProviderV2.ID.openai)
draft.provider.update(ProviderV2.ID.openai, (provider) => {
provider.disabled = true
})
})
expect((yield* usage.get()).items).toEqual([])
expect(requests).toHaveLength(1)
}),
),
)
}
it.live("prunes OAuth usage after API-key takeover and logout", () =>
fixture(
async () => Response.json(payload()),
({ usage, integrations, credentials, requests }) =>
Effect.gen(function* () {
yield* connect(credentials, { account: "acct-key" })
expect((yield* usage.get()).items).toHaveLength(1)
yield* integrations.connection.key({ integrationID: openai, key: "private-api-key" })
expect((yield* usage.get()).items).toEqual([])
const restored = yield* connect(credentials, { account: "acct-logout" })
expect((yield* usage.get()).items).toHaveLength(1)
yield* integrations.connection.remove(restored.id)
expect((yield* usage.get()).items).toEqual([])
expect(requests).toHaveLength(2)
}),
),
)
it.live("keeps Codex and MiniMax independent when either upstream fails", () =>
fixture(
(() => {
const calls = { codex: 0, minimax: 0 }
return async (input: string | URL | Request) => {
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
if (url.includes("chatgpt.com")) {
calls.codex++
return calls.codex === 1 ? new Response("private codex failure", { status: 503 }) : Response.json(payload())
}
calls.minimax++
return calls.minimax === 1 ? native(80) : new Response("private minimax failure", { status: 503 })
}
})(),
({ usage, credentials }) =>
Effect.gen(function* () {
yield* connect(credentials, { account: "acct-both" })
yield* credentials.create({
integrationID: minimax,
value: Credential.Key.make({ type: "key", key: "sk-cp-minimax-secret" }),
})
const first = yield* usage.get()
expect(first.items.find((item) => item.id === "codex-chatgpt")).toMatchObject({
fetchState: "unavailable",
windows: [],
})
expect(first.items.find((item) => item.id === "minimax-direct-global")).toMatchObject({
fetchState: "ready",
windows: [{ remaining: 80 }],
})
const second = yield* usage.refresh()
expect(second.items.find((item) => item.id === "codex-chatgpt")).toMatchObject({ fetchState: "ready" })
expect(second.items.find((item) => item.id === "minimax-direct-global")).toMatchObject({
fetchState: "stale",
windows: [{ remaining: 80 }],
})
expect(JSON.stringify([first, second])).not.toContain("private")
expect(JSON.stringify([first, second])).not.toContain("sk-cp-minimax-secret")
}),
{ minimax: true },
),
)
for (const status of [401, 403, 503]) {
it.live(`handles HTTP ${status} without exposing private upstream errors or invalid quota`, () => {
const responses = [Response.json(payload()), new Response("private upstream secret", { status })]
return fixture(
async () => responses.shift()!,
({ usage, credentials }) =>
Effect.gen(function* () {
yield* connect(credentials, { account: "acct-errors" })
const ready = yield* usage.get()
const failed = yield* usage.refresh()
const item = failed.items[0]
expect(ready.items[0]).toMatchObject({ fetchState: "ready", windows: [{ used: 20 }, { used: 35 }] })
expect(item).toMatchObject({
id: "codex-chatgpt",
fetchState: status === 503 ? "stale" : "unavailable",
error: { retryable: status === 503 },
})
expect(item?.windows).toHaveLength(status === 503 ? 2 : 0)
expect(JSON.stringify(failed)).not.toContain("private upstream secret")
expect(JSON.stringify(failed)).not.toContain("codex-access-token")
}),
)
})
}
})
describe("Codex usage normalization", () => {
test("preserves valid sibling windows when adjacent native windows are malformed", () => {
const value = decode(
payload({
rate_limit: {
allowed: true,
primary_window: { ...window(20), used_percent: "invalid private usage" },
secondary_window: window(45, 86_400),
},
additional_rate_limits: [
{ limit_name: "Malformed", metered_feature: "bad", rate_limit: "invalid" },
{
limit_name: "Spark",
metered_feature: "spark",
rate_limit: { allowed: true, primary_window: window(30, 3_600) },
},
],
}),
)
const item = normalize(value)
expect(item.fetchState).toBe("ready")
expect(item.windows).toHaveLength(2)
expect(item.windows.map((entry) => entry.used)).toEqual([45, 30])
expect(item.windows[0]).toMatchObject({ durationMs: 86_400_000, period: { unit: "day", value: 1 } })
expect(JSON.stringify(item)).not.toContain("invalid private usage")
})
test("clamps percentages, marks exhausted limits, and produces stable unique duplicate slugs", () => {
const value = decode(
payload({
rate_limit: {
allowed: true,
limit_reached: false,
primary_window: window(-10),
secondary_window: window(140, 604_800),
},
additional_rate_limits: [
{
limit_name: "Spark Fast",
metered_feature: "spark",
rate_limit: { allowed: false, limit_reached: true, primary_window: window(25) },
},
{
limit_name: "Spark-Fast",
metered_feature: "spark",
rate_limit: { allowed: true, limit_reached: false, primary_window: window(60) },
},
],
}),
)
const first = normalize(value)
const second = normalize(value)
const ids = first.windows.map((entry) => entry.id)
expect(first.fetchState).toBe("ready")
expect(first.windows).toHaveLength(4)
expect(first.windows[0]).toMatchObject({ used: 0, remaining: 100, state: "active" })
expect(first.windows[1]).toMatchObject({ used: 100, remaining: 0, state: "exhausted" })
expect(first.windows[2]).toMatchObject({ used: 100, remaining: 0, state: "exhausted" })
expect(first.windows[3]).toMatchObject({ used: 60, remaining: 40, state: "active" })
expect(new Set(ids).size).toBe(ids.length)
expect(second.windows.map((entry) => entry.id)).toEqual(ids)
})
test("retains non-round durations without fabricating a named period", () => {
const value = decode(
payload({
rate_limit: {
allowed: true,
primary_window: window(20, 5_400),
secondary_window: window(40, 172_800),
},
}),
)
const item = normalize(value)
expect(item.windows[0]).toMatchObject({ durationMs: 5_400_000 })
expect(item.windows[0]?.period).toBeUndefined()
expect(item.windows[1]).toMatchObject({ durationMs: 172_800_000, period: { unit: "day", value: 2 } })
})
test("falls back from overflowing timestamps and rejects overflowing fallback durations", () => {
const value = decode(
payload({
rate_limit: {
allowed: true,
primary_window: { ...window(20), reset_at: Number.MAX_SAFE_INTEGER, reset_after_seconds: 3_600 },
secondary_window: { ...window(30), reset_at: -1, reset_after_seconds: Number.MAX_SAFE_INTEGER },
},
}),
)
const item = normalize(value)
expect(item.fetchState).toBe("ready")
expect(item.windows[0]?.resetAt).toBe(new Date(Date.parse(item.fetchedAt!) + 3_600_000).toISOString())
expect(item.windows[1]?.resetAt).toBeUndefined()
})
})
describe("Codex usage transport", () => {
for (const mode of ["declared", "streamed"]) {
test(`rejects oversized ${mode} bodies without exposing their contents`, async () => {
const response = Response.json(
{ private: mode === "declared" ? "secret" : "secret".padEnd(64 * 1024, "x") },
{ headers: mode === "declared" ? { "content-length": String(64 * 1024 + 1) } : undefined },
)
const item = await load(
{ label: "OpenAI", access: "codex-access-token" },
Object.assign(async () => response, { preconnect: fetch.preconnect }),
)
expect(item).toMatchObject({ id: "codex-chatgpt", fetchState: "unavailable", windows: [] })
expect(JSON.stringify(item)).not.toContain("secret")
})
}
})
@@ -109,6 +109,10 @@ const UsageCard: Component<{
</p>
</Show>
<Show when={props.item.fetchState === "ready" && props.item.windows.length === 0}>
<CardDescription>{props.language.t("profile.usage.state.empty")}</CardDescription>
</Show>
<div class="provider-usage-resources">
<For each={props.item.windows}>
{(window) => {
+1
View File
@@ -603,6 +603,7 @@ export const dict = {
"profile.usage.source.direct": "مباشر",
"profile.usage.state.stale": "يتم عرض بيانات الاستخدام في آخر تحديث.",
"profile.usage.state.unavailable": "بيانات الاستخدام غير متوفرة.",
"profile.usage.state.empty": "لم يتم الإبلاغ عن أي حدود للاستخدام.",
"profile.usage.plan.pastDue": "الخطة: الدفع متأخر",
"profile.usage.plan.canceling": "الخطة: تُلغى في نهاية الفترة",
"profile.usage.plan.unknown": "الخطة: الحالة غير معروفة",
+1
View File
@@ -618,6 +618,7 @@ export const dict = {
"profile.usage.source.direct": "Direto",
"profile.usage.state.stale": "Exibindo os dados de uso da última atualização.",
"profile.usage.state.unavailable": "Dados de uso indisponíveis.",
"profile.usage.state.empty": "Nenhum limite de uso informado.",
"profile.usage.plan.pastDue": "Plano: Pagamento em atraso",
"profile.usage.plan.canceling": "Plano: Cancela no fim do período",
"profile.usage.plan.unknown": "Plano: Status desconhecido",
+1
View File
@@ -658,6 +658,7 @@ export const dict = {
"profile.usage.source.direct": "Direktno",
"profile.usage.state.stale": "Prikazuju se posljednji ažurirani podaci o korištenju.",
"profile.usage.state.unavailable": "Podaci o korištenju nisu dostupni.",
"profile.usage.state.empty": "Nisu prijavljena ograničenja korištenja.",
"profile.usage.plan.pastDue": "Plan: Plaćanje kasni",
"profile.usage.plan.canceling": "Plan: Otkazuje se na kraju perioda",
"profile.usage.plan.unknown": "Plan: Status nepoznat",
+1
View File
@@ -656,6 +656,7 @@ export const dict = {
"profile.usage.source.direct": "Direkte",
"profile.usage.state.stale": "Viser de senest opdaterede forbrugsdata.",
"profile.usage.state.unavailable": "Forbrugsdata er ikke tilgængelige.",
"profile.usage.state.empty": "Ingen forbrugsgrænser rapporteret.",
"profile.usage.plan.pastDue": "Abonnement: Betaling forfalden",
"profile.usage.plan.canceling": "Abonnement: Opsiges ved periodens udgang",
"profile.usage.plan.unknown": "Abonnement: Status ukendt",
@@ -668,6 +668,7 @@ export const dict = {
"profile.usage.source.direct": "Direkt",
"profile.usage.state.stale": "Zuletzt aktualisierte Nutzungsdaten werden angezeigt.",
"profile.usage.state.unavailable": "Nutzungsdaten nicht verfügbar.",
"profile.usage.state.empty": "Keine Nutzungslimits gemeldet.",
"profile.usage.plan.pastDue": "Tarif: Zahlung überfällig",
"profile.usage.plan.canceling": "Tarif: Kündigung zum Ende des Abrechnungszeitraums",
"profile.usage.plan.unknown": "Tarif: Status unbekannt",
@@ -567,6 +567,7 @@ export const dict = {
"profile.usage.source.direct": "Direct",
"profile.usage.state.stale": "Showing last updated usage.",
"profile.usage.state.unavailable": "Usage unavailable.",
"profile.usage.state.empty": "No usage limits reported.",
"profile.usage.plan.pastDue": "Plan: Past due",
"profile.usage.plan.canceling": "Plan: Cancels at period end",
"profile.usage.plan.unknown": "Plan: Status unknown",
+1
View File
@@ -662,6 +662,7 @@ export const dict = {
"profile.usage.source.direct": "Directo",
"profile.usage.state.stale": "Se muestran los últimos datos de uso actualizados.",
"profile.usage.state.unavailable": "Datos de uso no disponibles.",
"profile.usage.state.empty": "No se informaron límites de uso.",
"profile.usage.plan.pastDue": "Plan: Pago atrasado",
"profile.usage.plan.canceling": "Plan: Se cancela al final del período",
"profile.usage.plan.unknown": "Plan: Estado desconocido",
+1
View File
@@ -570,6 +570,7 @@ export const dict = {
"profile.usage.source.direct": "مستقیم",
"profile.usage.state.stale": "آخرین میزان استفاده به‌روزشده نمایش داده می‌شود.",
"profile.usage.state.unavailable": "میزان استفاده در دسترس نیست.",
"profile.usage.state.empty": "هیچ محدودیتی برای استفاده گزارش نشده است.",
"profile.usage.plan.pastDue": "طرح: سررسید گذشته",
"profile.usage.plan.canceling": "طرح: در پایان دوره لغو می‌شود",
"profile.usage.plan.unknown": "طرح: وضعیت نامشخص",
+1
View File
@@ -669,6 +669,7 @@ export const dict = {
"profile.usage.source.direct": "Direct",
"profile.usage.state.stale": "Affichage des dernières données d'utilisation mises à jour.",
"profile.usage.state.unavailable": "Données d'utilisation indisponibles.",
"profile.usage.state.empty": "Aucune limite d'utilisation signalée.",
"profile.usage.plan.pastDue": "Forfait : paiement en retard",
"profile.usage.plan.canceling": "Forfait : résiliation à la fin de la période",
"profile.usage.plan.unknown": "Forfait : statut inconnu",
+1
View File
@@ -497,6 +497,7 @@ export const dict = {
"profile.usage.source.direct": "Diretto",
"profile.usage.state.stale": "Vengono mostrati i dati di utilizzo dell'ultimo aggiornamento.",
"profile.usage.state.unavailable": "Dati di utilizzo non disponibili.",
"profile.usage.state.empty": "Nessun limite di utilizzo segnalato.",
"profile.usage.plan.pastDue": "Piano: Pagamento scaduto",
"profile.usage.plan.canceling": "Piano: Si annulla al termine del periodo",
"profile.usage.plan.unknown": "Piano: Stato sconosciuto",
+1
View File
@@ -651,6 +651,7 @@ export const dict = {
"profile.usage.source.direct": "直接",
"profile.usage.state.stale": "最後に更新された使用状況を表示しています。",
"profile.usage.state.unavailable": "使用状況を取得できません。",
"profile.usage.state.empty": "使用量の上限は報告されていません。",
"profile.usage.plan.pastDue": "プラン:支払い期限切れ",
"profile.usage.plan.canceling": "プラン:期間終了時に解約",
"profile.usage.plan.unknown": "プラン:ステータス不明",
+1
View File
@@ -611,6 +611,7 @@ export const dict = {
"profile.usage.source.direct": "직접",
"profile.usage.state.stale": "마지막으로 업데이트된 사용량을 표시합니다.",
"profile.usage.state.unavailable": "사용량을 확인할 수 없습니다.",
"profile.usage.state.empty": "보고된 사용량 한도가 없습니다.",
"profile.usage.plan.pastDue": "요금제: 결제 기한 지남",
"profile.usage.plan.canceling": "요금제: 기간 종료 시 취소",
"profile.usage.plan.unknown": "요금제: 상태 알 수 없음",
+1
View File
@@ -610,6 +610,7 @@ export const dict = {
"profile.usage.source.direct": "Direct",
"profile.usage.state.stale": "De laatst bijgewerkte gebruiksgegevens worden weergegeven.",
"profile.usage.state.unavailable": "Gebruiksgegevens niet beschikbaar.",
"profile.usage.state.empty": "Geen gebruikslimieten gemeld.",
"profile.usage.plan.pastDue": "Abonnement: Betaling achterstallig",
"profile.usage.plan.canceling": "Abonnement: Wordt aan het einde van de periode opgezegd",
"profile.usage.plan.unknown": "Abonnement: Status onbekend",
+1
View File
@@ -618,6 +618,7 @@ export const dict = {
"profile.usage.source.direct": "Direkte",
"profile.usage.state.stale": "Viser sist oppdaterte forbruksdata.",
"profile.usage.state.unavailable": "Forbruksdata er utilgjengelige.",
"profile.usage.state.empty": "Ingen forbruksgrenser rapportert.",
"profile.usage.plan.pastDue": "Abonnement: Betaling forfalt",
"profile.usage.plan.canceling": "Abonnement: Avsluttes ved periodens slutt",
"profile.usage.plan.unknown": "Abonnement: Status ukjent",
+1
View File
@@ -614,6 +614,7 @@ export const dict = {
"profile.usage.source.direct": "Bezpośrednio",
"profile.usage.state.stale": "Wyświetlane są ostatnio zaktualizowane dane o wykorzystaniu.",
"profile.usage.state.unavailable": "Dane o wykorzystaniu są niedostępne.",
"profile.usage.state.empty": "Nie zgłoszono limitów wykorzystania.",
"profile.usage.plan.pastDue": "Plan: Zaległa płatność",
"profile.usage.plan.canceling": "Plan: Zostanie anulowany z końcem okresu",
"profile.usage.plan.unknown": "Plan: Status nieznany",
+1
View File
@@ -655,6 +655,7 @@ export const dict = {
"profile.usage.source.direct": "Напрямую",
"profile.usage.state.stale": "Показаны последние обновлённые данные об использовании.",
"profile.usage.state.unavailable": "Данные об использовании недоступны.",
"profile.usage.state.empty": "Лимиты использования не указаны.",
"profile.usage.plan.pastDue": "Тариф: Платёж просрочен",
"profile.usage.plan.canceling": "Тариф: Отмена в конце периода",
"profile.usage.plan.unknown": "Тариф: Статус неизвестен",
+1
View File
@@ -648,6 +648,7 @@ export const dict = {
"profile.usage.source.direct": "โดยตรง",
"profile.usage.state.stale": "กำลังแสดงข้อมูลการใช้งานที่อัปเดตล่าสุด",
"profile.usage.state.unavailable": "ไม่มีข้อมูลการใช้งาน",
"profile.usage.state.empty": "ไม่มีการรายงานขีดจำกัดการใช้งาน",
"profile.usage.plan.pastDue": "แผน: ค้างชำระ",
"profile.usage.plan.canceling": "แผน: ยกเลิกเมื่อสิ้นสุดรอบ",
"profile.usage.plan.unknown": "แผน: ไม่ทราบสถานะ",
+1
View File
@@ -607,6 +607,7 @@ export const dict = {
"profile.usage.source.direct": "Doğrudan",
"profile.usage.state.stale": "Son güncellenen kullanım verileri gösteriliyor.",
"profile.usage.state.unavailable": "Kullanım verileri kullanılamıyor.",
"profile.usage.state.empty": "Herhangi bir kullanım sınırı bildirilmedi.",
"profile.usage.plan.pastDue": "Plan: Ödeme gecikmiş",
"profile.usage.plan.canceling": "Plan: Dönem sonunda iptal edilecek",
"profile.usage.plan.unknown": "Plan: Durum bilinmiyor",
+1
View File
@@ -608,6 +608,7 @@ export const dict = {
"profile.usage.source.direct": "Напряму",
"profile.usage.state.stale": "Показано останні оновлені дані про використання.",
"profile.usage.state.unavailable": "Дані про використання недоступні.",
"profile.usage.state.empty": "Про обмеження використання не повідомлено.",
"profile.usage.plan.pastDue": "План: Платіж прострочено",
"profile.usage.plan.canceling": "План: Скасування наприкінці періоду",
"profile.usage.plan.unknown": "План: Статус невідомий",
+1
View File
@@ -632,6 +632,7 @@ export const dict = {
"profile.usage.source.direct": "直接",
"profile.usage.state.stale": "正在显示上次更新的用量。",
"profile.usage.state.unavailable": "用量数据不可用。",
"profile.usage.state.empty": "未报告任何用量限制。",
"profile.usage.plan.pastDue": "套餐:付款逾期",
"profile.usage.plan.canceling": "套餐:将在周期结束时取消",
"profile.usage.plan.unknown": "套餐:状态未知",
+1
View File
@@ -592,6 +592,7 @@ export const dict = {
"profile.usage.source.direct": "直接",
"profile.usage.state.stale": "正在顯示上次更新的用量。",
"profile.usage.state.unavailable": "無法取得用量資料。",
"profile.usage.state.empty": "未回報任何用量限制。",
"profile.usage.plan.pastDue": "方案:付款逾期",
"profile.usage.plan.canceling": "方案:將於週期結束時取消",
"profile.usage.plan.unknown": "方案:狀態未知",
@@ -22,6 +22,9 @@ function Item(props: { item: ProviderUsageSnapshot }) {
<text fg={props.item.fetchState === "ready" ? theme.textMuted : theme.warning}>
{props.item.fetchState === "ready" ? props.item.planState : props.item.fetchState}
</text>
<Show when={props.item.fetchState === "ready" && props.item.windows.length === 0}>
<text fg={theme.textMuted}>No usage limits reported.</text>
</Show>
<For each={props.item.windows}>
{(window) => (
<box>