@llblab/pi-kit 0.31.2 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/BACKLOG.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Backlog
2
2
 
3
- The 0.31.2 composition advances Telegram to 0.54.4; package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
3
+ The 0.32.0 composition advances Codex Usage to 0.13.0; package pins, resource order and bundled runtime ownership remain authoritative in `package.json`. Release outcomes belong in [CHANGELOG.md](./CHANGELOG.md).
4
4
 
5
5
  ## Carried checks
6
6
 
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  All notable changes to `@llblab/pi-kit` are documented here.
4
4
 
5
+ ## 0.32.0: OpenAI Subscription Quota Support
6
+
7
+ - `OpenAI Subscription Support`: Advances Codex Usage to `0.13.0`. ChatGPT subscription OAuth under `openai` now fetches Codex quotas without the legacy `openai-codex` provider; ordinary API-key auth is skipped. Both providers support terminal and Telegram status plus `/fast`, with preferences isolated by provider/model. Other pins, package membership, resources and load order are unchanged.
8
+
5
9
  ## 0.31.2: Telegram Restore Flow
6
10
 
7
11
  - `Telegram Restore Flow`: Advances the exact Telegram pin to `0.54.4`. Restoring a Pi into a routing tab renames the tab to that Pi's Thread title and keeps the prompt it answers, since an input sent from `All` now moves into its routing tab on desktop and mobile alike. Routing menus disappear after every final choice, every client ends with one routing tab per `All` input, and the old tab is removed right after Pi answers. Other pins, package membership, resources and load order are unchanged.
package/README.md CHANGED
@@ -15,7 +15,7 @@ Package links lead to the owning repositories for usage, documentation, issues,
15
15
  | [`@llblab/pi-actors`](https://github.com/llblab/pi-actors) | `0.54.0` | Inspectable local Runs, reusable Recipes, persistent tools, and delegation Skills |
16
16
  | [`@llblab/pi-claude-usage`](https://github.com/llblab/pi-claude-usage) | `0.2.1` | Claude subscription quota status and shared per-model Fast toggle for Opus, mirrored in Telegram |
17
17
  | [`@llblab/pi-clean-room`](https://github.com/llblab/pi-clean-room) | `0.3.0` | Isolated nested Pi TUI with named npm extensions and compatible model selection |
18
- | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.12.1` | Shared Codex quota/Business credit status and persistent priority Fast toggle, mirrored in Telegram |
18
+ | [`@llblab/pi-codex-usage`](https://github.com/llblab/pi-codex-usage) | `0.13.0` | OpenAI/legacy Codex subscription quota and Business credit status, with persistent priority Fast toggle mirrored in Telegram |
19
19
  | [`@llblab/pi-grow-loop`](https://github.com/llblab/pi-grow-loop) | `0.9.0` | Visible continuation scheduling and bounded worker Skills through compiled, manifest-owned resources |
20
20
  | [`@llblab/pi-state-flow`](https://github.com/llblab/pi-state-flow) | `0.26.4` | Scoped context/memory compiler with step-independent current memory, stable Passive/Active modes and visible blocked-state status |
21
21
  | [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.54.4` | Telegram companion with live Thread rebinding, temporary routing tabs, Workspace Restore, follower Threads, Windows support, files, voice, and controls |
@@ -45,7 +45,9 @@ Prefer the kit instead of separately loading the same packages. If you already u
45
45
 
46
46
  ## Development
47
47
 
48
- The `0.31.2` composition carries Telegram `0.54.4`, which moves an `All` input into its routing tab, renames a restored tab to its Pi's Thread title, keeps routing menus one-shot and removes the old tab right after Pi answers, and `0.54.3`, which keeps mobile Telegram to one routing tab per `All` input, on top of `0.54.2`: an idle connected session no longer writes to disk periodically (the update journal has its own lock instead of the `telegram.json` guard, the leader proves liveness over the bus instead of a two-second `state.json` heartbeat), followers go offline instead of looping when Threaded Mode is switched off, unverifiable ownership checks are recorded and tolerated, and cancelled Generative App methods cannot leave processes behind. Telegram 0.54 uses bus protocol v3: update every Pi process that runs pi-telegram together, because a 0.53.x follower cannot register with a 0.54 leader. State Flow stays at `0.26.4`; all other pins, resources and load order remain unchanged. Telegram runtime storage stays in `tmp/pi-telegram`. Drain the prompt queue or accept losing waiting work before reload/restart; damaged-state reset discards all profiles' runtime continuity. `npm run validate` checks exact installed pins, declared resources, dependency audit and bundled inventory. It does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
48
+ The `0.32.0` composition advances Codex Usage to `0.13.0`: subscription OAuth under `openai` can fetch the existing Codex quota without the legacy `openai-codex` provider, while ordinary API-key credentials are skipped. Terminal status, Telegram quota/Fast status and `/fast` support both providers, with Fast preferences isolated by provider/model. Other pins, resource paths and load order are unchanged.
49
+
50
+ Telegram `0.54.4` moves an `All` input into its routing tab, renames a restored tab to its Pi's Thread title, keeps routing menus one-shot and removes the old tab right after Pi answers, and `0.54.3`, which keeps mobile Telegram to one routing tab per `All` input, on top of `0.54.2`: an idle connected session no longer writes to disk periodically (the update journal has its own lock instead of the `telegram.json` guard, the leader proves liveness over the bus instead of a two-second `state.json` heartbeat), followers go offline instead of looping when Threaded Mode is switched off, unverifiable ownership checks are recorded and tolerated, and cancelled Generative App methods cannot leave processes behind. Telegram 0.54 uses bus protocol v3: update every Pi process that runs pi-telegram together, because a 0.53.x follower cannot register with a 0.54 leader. State Flow stays at `0.26.4`; all other pins, resources and load order remain unchanged. Telegram runtime storage stays in `tmp/pi-telegram`. Drain the prompt queue or accept losing waiting work before reload/restart; damaged-state reset discards all profiles' runtime continuity. `npm run validate` checks exact installed pins, declared resources, dependency audit and bundled inventory. It does not certify installed-client rendering; carried checks remain in [Backlog](./BACKLOG.md).
49
51
 
50
52
  ```bash
51
53
  npm install
@@ -5,7 +5,8 @@
5
5
  - `Domain DAG`: `index.ts` only re-exports the extension and public contracts; `lib/extension.ts` composes the Pi extension. `lib/usage-store.ts` owns persistence/leadership, `lib/query.ts` owns quota I/O, `lib/usage.ts` owns normalized reports, `lib/status.ts` and `lib/status-format.ts` own local UI orchestration and formatting, `lib/telegram.ts` owns optional registration, and `lib/fast.ts` owns Codex Fast semantics/request adaptation. `@llblab/pi-command-fast` owns command arbitration and generic JSONC override editing. Keep dependency direction acyclic; do not import `index.ts` from `lib/` or mix Fast persistence into quota coordination. Place domain tests in `tests/<domain>.test.ts` (or a domain-prefixed focused integration test); use independent names such as `invariants.test.ts` only for cross-domain invariants.
6
6
  - `Statusline-first scope`: Own usage state + usage mode: keep quota reporting zero-configuration and the optional Fast toggle focused on the existing terminal status.
7
7
  - Trigger: Considering new commands, menus, persisted settings, or notification output.
8
- - Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, the optional `pi-telegram` `/start` status-line mirror, or the existing argument-free `/fast` command. Register the `openai-codex` provider handler through `registerFastProvider` on session_start and release on session_shutdown; never register `/fast` directly or filter Fast by model ID/auth. Fast persists solely as `serviceTier: "priority"` in the current model override; OFF deletes only that property. Preserve JSONC/unrelated settings, existing wire tiers, and usage refresh/Telegram. Apply lowercase ` fast` through the final terminal boundary; the optional Telegram row reads the active model's Fast preference at render time and appends plain-text ` fast` (or shows `fast` alone without quota). Toggle redraws without quota fetch or success notification. The library owns session WeakMap/reload arbitration, not Fast state. A Fast config read failure must not block usage status.
8
+ - Action: Prefer deleting the surface unless it is required for the optimistic TUI status widget, the optional `pi-telegram` `/start` status-line mirror, or the existing argument-free `/fast` command. Register both `openai-codex` and `openai` provider handlers through `registerFastProvider` on session_start and release both on session_shutdown; never register `/fast` directly or filter Fast by model ID/auth. Fast persists solely as `serviceTier: "priority"` in the current provider/model override (never share preferences across providers with the same model ID); OFF deletes only that property. Preserve JSONC/unrelated settings, existing wire tiers, and usage refresh/Telegram. Apply lowercase ` fast` through the final terminal boundary; the optional Telegram row reads the active model's Fast preference at render time and appends plain-text ` fast` (or shows `fast` alone without quota). Toggle redraws without quota fetch or success notification. The library owns session WeakMap/reload arbitration, not Fast state. A Fast config read failure must not block usage status.
9
+ - `OpenAI provider support`: Show the same Codex subscription quota for active `openai-codex` and `openai` models. Quota transport accepts `openai` subscription OAuth (guarded by `modelRegistry.isUsingOAuth`) and legacy `openai-codex` auth, with the Codex CLI fallback; never send `openai` API-key credentials to the ChatGPT quota endpoint or label Codex quotas as API billing limits.
9
10
  - `Optimistic refresh`: Preserve the last good statusline bar during refresh and transient failures.
10
11
  - Trigger: Updating quota polling or error handling.
11
12
  - Action: Do not collapse the bar while a request is in flight; only show `n/a` or `error` after repeated failures or no usable quota.
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.13.0: OpenAI Subscription Provider Support
4
+
5
+ - Added `openai` alongside `openai-codex` for `/fast`, request priority adaptation, terminal status and the optional Telegram row. Fast preferences remain isolated by provider/model, including identical model IDs. Both providers share the existing Codex subscription quota stream. Pi subscription OAuth under `openai` now fetches quota directly without requiring the legacy provider; `openai` API-key auth is skipped and the Codex CLI fallback is preserved.
6
+
3
7
  ## 0.12.1: Telegram Fast Status
4
8
 
5
9
  - The optional Telegram status row now appends lowercase ` fast` for the active Codex model, or shows `fast` alone when quota is unavailable. It rereads the per-model preference at menu render time, so toggles, model changes and manual edits do not wait for quota refresh. Quota polling, auth, request adaptation and terminal behavior are unchanged; isolated tests cover enabled/disabled state, model/provider changes and unreadable configuration.
@@ -19,16 +19,16 @@ This repository is a minimal fork of [`narumiruna/pi-extensions/extensions/pi-co
19
19
  - Shows two counter-moving half-height markers in the statusline bar while Codex usage is loading, then keeps the bar fresh (the countdown ticks locally)
20
20
  - Keeps the last usable bar visible during ordinary refreshes instead of replacing known quota with a loading state
21
21
  - Statusline output adapts to the response: weekly-only limits show an explicit remaining percentage and reset countdown, while dual-window limits keep the compact themed bar
22
- - All Codex subscription models share the primary `codex` quota and status label
23
- - When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for active OpenAI Codex subscription models
22
+ - Models under either `openai-codex` or `openai` show the same primary Codex subscription quota and `codex` status label (not OpenAI API billing limits)
23
+ - When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for either supported provider
24
24
  - Additional returned quota buckets are ignored
25
- - Pi OpenAI Codex provider auth is used first
25
+ - Pi subscription auth from `openai` or legacy `openai-codex` is used first
26
26
  - Codex CLI app-server remains available as a fallback
27
27
  - Missing auth, subscription, plan, or quota windows are shown as `n/a`, not as an error
28
28
  - Successful updates briefly redraw the bar only when a 5% segment changes
29
29
  - Network/provider failures keep the last good bar (up to an hour), then show `error`
30
30
  - Any number of Pi instances share one request stream, see [Shared Refresh](#shared-refresh)
31
- - Quota display requires no configuration; `/fast` optionally toggles priority service for the active native Codex model
31
+ - Quota display requires no configuration; `/fast` optionally toggles priority service for the active `openai-codex` or `openai` model
32
32
 
33
33
  ## Install
34
34
 
@@ -59,7 +59,7 @@ For explicitly local library experiments, a temporary folder link reads the libr
59
59
 
60
60
  ## Fast mode
61
61
 
62
- `/fast` takes no arguments and dispatches by the current **provider**, not model names or OAuth eligibility. For any `openai-codex` model, it stores only `serviceTier: "priority"` in Pi's canonical `models.json` (honoring `PI_CODING_AGENT_DIR`):
62
+ `/fast` takes no arguments and dispatches by the current **provider**, not model names or OAuth eligibility. For any `openai-codex` or `openai` model, it stores only `serviceTier: "priority"` in Pi's canonical `models.json` (honoring `PI_CODING_AGENT_DIR`):
63
63
 
64
64
  ```json
65
65
  {
@@ -73,6 +73,8 @@ For explicitly local library experiments, a temporary folder link reads the libr
73
73
  }
74
74
  ```
75
75
 
76
+ For an `openai` model, the same override goes under `providers.openai.modelOverrides`. Preferences are isolated by provider and model ID, so identical IDs in the two providers do not share Fast state.
77
+
76
78
  OFF removes only `serviceTier`; it never writes `"default"`. JSONC comments and unrelated configuration survive. There is no separate Fast config or model allowlist. State follows provider/model selection and survives restart; manual edits are read on lifecycle refresh and each request.
77
79
 
78
80
  When this and Claude Usage are loaded, their shared library registers **one** `/fast`, in either load order, even with separate physical dependency copies. A session-scoped WeakMap avoids cross-session dispatch; shutdown releases registrations for reload (Pi retains the session manager). Unrelated providers receive a concise unsupported-provider message. Invalid arguments show `Usage: /fast`; success is silent.
@@ -155,22 +157,22 @@ Coordination assumes a local filesystem and cooperating instances on the same ma
155
157
 
156
158
  ## Telegram Status Menu
157
159
 
158
- If `@llblab/pi-telegram` is loaded with the public status-line provider API, this extension registers an optional `/start` menu status row. The row is shown only while the active model uses the OpenAI Codex subscription provider:
160
+ If `@llblab/pi-telegram` is loaded with the public status-line provider API, this extension registers an optional `/start` menu status row. The row is shown only while the active model uses `openai-codex` or `openai`:
159
161
 
160
162
  ```text
161
163
  codex: ██████▀▀▀▀ 6d
162
164
  ```
163
165
 
164
- The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, always with the `codex` label. When Fast is enabled for the active model, it appends plain-text ` fast`; without a usable quota report it shows `codex: fast`. The preference is reread when the menu is rendered, so toggles and model changes do not depend on a quota refresh. If `pi-telegram` is absent, older, or the active model is not a Codex subscription model, no Telegram row is added.
166
+ The value is the same compact quota bar plus weekly reset countdown used by the terminal statusline, always with the `codex` label. When Fast is enabled for the active model, it appends plain-text ` fast`; without a usable quota report it shows `codex: fast`. The preference is reread when the menu is rendered, so toggles and model changes do not depend on a quota refresh. If `pi-telegram` is absent, older, or the active model uses another provider, no Telegram row is added.
165
167
 
166
168
  ## Auth
167
169
 
168
170
  The extension tries usage sources in this order:
169
171
 
170
- 1. Pi's `openai-codex` provider auth
172
+ 1. Pi's subscription auth from `openai` or legacy `openai-codex` (active model first)
171
173
  2. `codex app-server --listen stdio://`
172
174
 
173
- OpenAI API keys are not ChatGPT Codex subscription auth and do not expose these quotas.
175
+ Selecting an `openai` model enables the status and Fast toggle, and its ChatGPT subscription OAuth credentials can fetch the quota directly; a legacy `openai-codex` provider is not required. The extension uses Pi's resolved auth headers/token, checks `modelRegistry.isUsingOAuth` before using `openai` credentials, and keeps the Codex CLI fallback. OpenAI API keys are not ChatGPT subscription auth and do not expose these quotas; `openai` API-key credentials are skipped, not sent to the ChatGPT quota endpoint. Without subscription auth/quota, the quota status is `n/a` when both sources report an unavailable state. Fast works independently of subscription auth.
174
176
 
175
177
  ## License
176
178
 
@@ -3,23 +3,26 @@ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
  import { registerFastProvider } from "@llblab/pi-command-fast";
4
4
  import { applyFastToRequest, isFastEnabled, isFastEligibleModel, toggleFast } from "./fast.ts";
5
5
  import { createCodexUsageStatus } from "./status.ts";
6
+ import { OPENAI_USAGE_PROVIDERS } from "./usage.ts";
6
7
 
7
8
  export default function codexUsage(pi: ExtensionAPI) {
8
- let releaseFast: (() => void) | undefined;
9
+ let releaseFast: Array<() => void> = [];
9
10
  pi.on("session_start", (_event, ctx) => {
10
- releaseFast = registerFastProvider(pi, ctx, {
11
- provider: "openai-codex",
12
- toggle(commandCtx) {
13
- toggleFast(commandCtx.model!.id);
14
- status.fastChanged(commandCtx, commandCtx.model!);
15
- },
16
- });
11
+ for (const provider of OPENAI_USAGE_PROVIDERS) {
12
+ releaseFast.push(registerFastProvider(pi, ctx, {
13
+ provider,
14
+ toggle(commandCtx) {
15
+ toggleFast(commandCtx.model!);
16
+ status.fastChanged(commandCtx, commandCtx.model!);
17
+ },
18
+ }));
19
+ }
17
20
  });
18
- pi.on("session_shutdown", () => { releaseFast?.(); releaseFast = undefined; });
21
+ pi.on("session_shutdown", () => { releaseFast.forEach((release) => release()); releaseFast = []; });
19
22
  const status = createCodexUsageStatus(pi);
20
23
  pi.on("before_provider_request", (event, ctx) => {
21
24
  const model = ctx.model;
22
25
  if (!isFastEligibleModel(model)) return;
23
- return applyFastToRequest(event.payload, model.id, isFastEnabled(model.id));
26
+ return applyFastToRequest(event.payload, model.id, isFastEnabled(model));
24
27
  });
25
28
  }
@@ -3,16 +3,17 @@ import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
3
3
  import { isModelOverrideValue, toggleModelOverrideValue } from "@llblab/pi-command-fast";
4
4
  import { isOpenAICodexModel } from "./usage.ts";
5
5
 
6
- const target = (id: string) => ({ provider: "openai-codex", id, property: "serviceTier", enabledValue: "priority" });
6
+ type FastModel = Pick<NonNullable<ExtensionContext["model"]>, "provider" | "id">;
7
+ const target = (model: FastModel) => ({ provider: model.provider, id: model.id, property: "serviceTier", enabledValue: "priority" });
7
8
 
8
9
  export function isFastEligibleModel(model: ExtensionContext["model"]): model is NonNullable<ExtensionContext["model"]> {
9
10
  return !!model && isOpenAICodexModel(model);
10
11
  }
11
- export function isFastEnabled(modelId: string, path?: string): boolean {
12
- return isModelOverrideValue(target(modelId), path);
12
+ export function isFastEnabled(model: FastModel, path?: string): boolean {
13
+ return isModelOverrideValue(target(model), path);
13
14
  }
14
- export function toggleFast(modelId: string, path?: string): boolean {
15
- return toggleModelOverrideValue(target(modelId), path);
15
+ export function toggleFast(model: FastModel, path?: string): boolean {
16
+ return toggleModelOverrideValue(target(model), path);
16
17
  }
17
18
 
18
19
  export function applyFastToRequest(payload: unknown, modelId: string, enabled: boolean): unknown {
@@ -2,8 +2,7 @@
2
2
  import { type ChildProcessWithoutNullStreams, spawn } from "node:child_process";
3
3
  import { createInterface } from "node:readline";
4
4
  import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
5
- import { assertObject, errorMessage, normalizeAppServerResponse, normalizeBackendPayload, parseJsonObject, selectUsageSnapshot, type AppServerRateLimitResponse, type CodexUsageModel, type CodexUsageReport, type RateLimitStatusPayload, type UsageQueryError } from "./usage.ts";
6
- const CODEX_PROVIDER_ID = "openai-codex";
5
+ import { assertObject, errorMessage, isOpenAICodexModel, normalizeAppServerResponse, normalizeBackendPayload, parseJsonObject, selectUsageSnapshot, type AppServerRateLimitResponse, type CodexUsageModel, type CodexUsageReport, type RateLimitStatusPayload, type UsageQueryError } from "./usage.ts";
7
6
  const CODEX_USAGE_LIMIT_ID = "codex";
8
7
  const CODEX_USAGE_URL = "https://chatgpt.com/backend-api/wham/usage";
9
8
  const MAX_ERROR_BODY_CHARS = 600;
@@ -64,7 +63,7 @@ async function queryViaPiAuth(
64
63
  const auth = await resolvePiCodexAuth(ctx);
65
64
  if (!auth) {
66
65
  throw new Error(
67
- "No Pi OpenAI Codex subscription auth was available. Use a Pi OpenAI Codex model or run /login for OpenAI ChatGPT Plus/Pro (Codex).",
66
+ "No Pi OpenAI Codex subscription auth was available. Use an openai or openai-codex subscription model or run /login for OpenAI ChatGPT.",
68
67
  );
69
68
  }
70
69
 
@@ -96,6 +95,7 @@ async function resolvePiCodexAuth(
96
95
  const errors: string[] = [];
97
96
 
98
97
  for (const model of models) {
98
+ if (model.provider === "openai" && !ctx.modelRegistry.isUsingOAuth(model)) continue;
99
99
  const auth = await ctx.modelRegistry.getApiKeyAndHeaders(model);
100
100
  if (!auth.ok) {
101
101
  errors.push(auth.error);
@@ -127,7 +127,7 @@ function codexAuthCandidateModels(ctx: ExtensionContext): PiModel[] {
127
127
  const candidates: PiModel[] = [];
128
128
  const seen = new Set<string>();
129
129
  const add = (model: PiModel | undefined) => {
130
- if (!model || model.provider !== CODEX_PROVIDER_ID) return;
130
+ if (!model || !isOpenAICodexModel(model)) return;
131
131
  const key = `${model.provider}/${model.id}`;
132
132
  if (seen.has(key)) return;
133
133
  seen.add(key);
@@ -41,20 +41,20 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
41
41
  let statuslineRequestId = 0;
42
42
  let unregisterTelegramStatusLine: (() => void) | undefined;
43
43
  let telegramStatusLineRegistration: Promise<void> | undefined;
44
- let fastModelId: string | undefined;
44
+ let fastModel: PiModel | undefined;
45
45
  let fastEnabled = false;
46
46
  let lastStatusText: string | undefined;
47
47
  let loadingModel: CodexUsageModel | undefined;
48
48
 
49
- const syncFast = (model: PiModel | undefined, ctx: ExtensionContext) => {
50
- fastModelId = isFastEligibleModel(model) ? model.id : undefined;
51
- fastEnabled = fastModelId !== undefined && isFastEnabled(fastModelId);
49
+ const syncFast = (model: PiModel | undefined) => {
50
+ fastModel = isFastEligibleModel(model) ? model : undefined;
51
+ fastEnabled = fastModel !== undefined && isFastEnabled(fastModel);
52
52
  };
53
53
  const setCodexStatus = (ctx: ExtensionContext, text: string | undefined, model?: CodexUsageModel) => {
54
54
  lastStatusText = text;
55
55
  ctx.ui.setStatus(STATUS_KEY, text === undefined ? undefined : appendFastStatus(
56
56
  ctx, text,
57
- fastEnabled && model?.id === fastModelId,
57
+ fastEnabled && model?.provider === fastModel?.provider && model?.id === fastModel?.id,
58
58
  ));
59
59
  };
60
60
 
@@ -95,7 +95,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
95
95
  ctx: ExtensionContext,
96
96
  model: CodexUsageModel | undefined,
97
97
  ) => {
98
- const modelChanged = loadingModel?.id !== model?.id;
98
+ const modelChanged = loadingModel?.provider !== model?.provider || loadingModel?.id !== model?.id;
99
99
  loadingModel = model;
100
100
  if (statuslineLoadingTimer) {
101
101
  if (modelChanged && lastStatusText !== undefined) setCodexStatus(ctx, lastStatusText, model);
@@ -331,7 +331,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
331
331
  try {
332
332
  const activeModel = model ?? ctx.model;
333
333
  const previousFast = fastEnabled;
334
- syncFast(activeModel, ctx);
334
+ syncFast(activeModel);
335
335
  forceRender ||= previousFast !== fastEnabled;
336
336
  if (!isOpenAICodexModel(activeModel)) {
337
337
  clearUsageStatusline(ctx);
@@ -370,7 +370,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
370
370
  ensureTelegramStatusLineRegistered();
371
371
 
372
372
  pi.on("session_start", (_event, ctx) => {
373
- syncFast(ctx.model, ctx);
373
+ syncFast(ctx.model);
374
374
  ensureTelegramStatusLineRegistered();
375
375
  if (isOpenAICodexModel(ctx.model))
376
376
  void refreshCurrentCodexUsageStatusline(ctx, true).catch(
@@ -388,7 +388,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
388
388
  });
389
389
 
390
390
  pi.on("model_select", (event, ctx) => {
391
- syncFast(event.model, ctx);
391
+ syncFast(event.model);
392
392
  if (isOpenAICodexModel(event.model)) {
393
393
  void refreshCurrentCodexUsageStatusline(ctx, true, event.model).catch(
394
394
  handleAsyncTimerError,
@@ -405,7 +405,7 @@ export function createCodexUsageStatus(pi: ExtensionAPI) {
405
405
  });
406
406
  return {
407
407
  fastChanged(ctx: ExtensionContext, model: PiModel) {
408
- syncFast(model, ctx);
408
+ syncFast(model);
409
409
  if (lastStatusText !== undefined) setCodexStatus(ctx, lastStatusText, model);
410
410
  renderState(ctx, loadState(), model, true);
411
411
  },
@@ -29,7 +29,7 @@ export function codexUsageTelegramStatusLine(
29
29
  ): TelegramStatusLineProviderResult {
30
30
  if (!activeModel || !isOpenAICodexModel(activeModel)) return undefined;
31
31
  const value = report ? formatCodexUsageStatusValue(report, activeModel) : undefined;
32
- const fast = isFastEnabled(activeModel.id);
32
+ const fast = isFastEnabled(activeModel);
33
33
  if (!value && !fast) return undefined;
34
34
  return { label: "codex", value: value ? `${value}${fast ? " fast" : ""}` : "fast" };
35
35
  }
@@ -4,8 +4,9 @@ const SECOND_MS = 1000;
4
4
  const CODEX_USAGE_LIMIT_ID = "codex";
5
5
  type PiModel = NonNullable<ExtensionContext["model"]>;
6
6
  export type CodexUsageModel = Pick<PiModel, "id" | "name" | "provider">;
7
+ export const OPENAI_USAGE_PROVIDERS = ["openai-codex", "openai"] as const;
7
8
  export function isOpenAICodexModel(model: Pick<PiModel, "provider"> | undefined): boolean {
8
- return model?.provider === "openai-codex";
9
+ return OPENAI_USAGE_PROVIDERS.some((provider) => model?.provider === provider);
9
10
  }
10
11
  export type UsageSource = "pi-auth" | "codex-app-server";
11
12
  export type UsageQueryError = { source: UsageSource; message: string; cause?: unknown };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-codex-usage",
3
- "version": "0.12.1",
3
+ "version": "0.13.0",
4
4
  "private": false,
5
5
  "description": "Pi extension for Codex subscription usage and an optional Fast toggle",
6
6
  "keywords": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llblab/pi-kit",
3
- "version": "0.31.2",
3
+ "version": "0.32.0",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -44,7 +44,7 @@
44
44
  "@llblab/pi-actors": "0.54.0",
45
45
  "@llblab/pi-claude-usage": "0.2.1",
46
46
  "@llblab/pi-clean-room": "0.3.0",
47
- "@llblab/pi-codex-usage": "0.12.1",
47
+ "@llblab/pi-codex-usage": "0.13.0",
48
48
  "@llblab/pi-grow-loop": "0.9.0",
49
49
  "@llblab/pi-state-flow": "0.26.4",
50
50
  "@llblab/pi-telegram": "0.54.4",