@llblab/pi-kit 0.31.1 → 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 +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +5 -3
- package/node_modules/@llblab/pi-codex-usage/AGENTS.md +2 -1
- package/node_modules/@llblab/pi-codex-usage/CHANGELOG.md +4 -0
- package/node_modules/@llblab/pi-codex-usage/README.md +11 -9
- package/node_modules/@llblab/pi-codex-usage/lib/extension.ts +13 -10
- package/node_modules/@llblab/pi-codex-usage/lib/fast.ts +6 -5
- package/node_modules/@llblab/pi-codex-usage/lib/query.ts +4 -4
- package/node_modules/@llblab/pi-codex-usage/lib/status.ts +10 -10
- package/node_modules/@llblab/pi-codex-usage/lib/telegram.ts +1 -1
- package/node_modules/@llblab/pi-codex-usage/lib/usage.ts +2 -1
- package/node_modules/@llblab/pi-codex-usage/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/CHANGELOG.md +10 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.d.ts +3 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/bindings.js +2 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/extension.js +8 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.d.ts +8 -1
- package/node_modules/@llblab/pi-telegram/dist/lib/routing.js +282 -67
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.d.ts +2 -0
- package/node_modules/@llblab/pi-telegram/dist/lib/threads.js +4 -2
- package/node_modules/@llblab/pi-telegram/dist/package.json +1 -1
- package/node_modules/@llblab/pi-telegram/docs/architecture.md +5 -5
- package/node_modules/@llblab/pi-telegram/docs/multi-instance-bus.md +2 -2
- package/node_modules/@llblab/pi-telegram/docs/ui-style.md +6 -4
- package/node_modules/@llblab/pi-telegram/lib/bindings.ts +4 -0
- package/node_modules/@llblab/pi-telegram/lib/extension.ts +8 -0
- package/node_modules/@llblab/pi-telegram/lib/routing.ts +357 -83
- package/node_modules/@llblab/pi-telegram/lib/threads.ts +10 -2
- package/node_modules/@llblab/pi-telegram/package.json +1 -1
- package/package.json +3 -3
package/BACKLOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Backlog
|
|
2
2
|
|
|
3
|
-
The 0.
|
|
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,14 @@
|
|
|
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
|
+
|
|
9
|
+
## 0.31.2: Telegram Restore Flow
|
|
10
|
+
|
|
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.
|
|
12
|
+
|
|
5
13
|
## 0.31.1: Telegram Mobile Routing Tab
|
|
6
14
|
|
|
7
15
|
- `Telegram Mobile Routing`: Advances the exact Telegram pin to `0.54.3`. Sending a command or prompt from `All` in mobile Telegram no longer leaves an extra tab named after the input next to the routing tab; the native tab the mobile client creates is adopted as the routing tab. Other pins, package membership, resources and load order are unchanged.
|
package/README.md
CHANGED
|
@@ -15,10 +15,10 @@ 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.
|
|
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
|
-
| [`@llblab/pi-telegram`](https://github.com/llblab/pi-telegram) | `0.54.
|
|
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 |
|
|
22
22
|
| [`@llblab/skills`](https://github.com/llblab/skills) | `1.15.0` | Portable workflows for engineering, review, design, context maintenance, and other focused tasks |
|
|
23
23
|
|
|
24
24
|
Versions are exact by design. An upstream release does not change an installed kit until this repository explicitly advances the dependency and publishes a new kit version. Runtime defects and package-specific feature requests belong in the linked repository; package selection and kit installation issues belong here.
|
|
@@ -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.
|
|
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
|
|
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
|
-
-
|
|
23
|
-
- When `pi-telegram` is available, the same compact value appears as `codex: <value>` in the `/start` menu status text for
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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: (
|
|
9
|
+
let releaseFast: Array<() => void> = [];
|
|
9
10
|
pi.on("session_start", (_event, ctx) => {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
12
|
-
return isModelOverrideValue(target(
|
|
12
|
+
export function isFastEnabled(model: FastModel, path?: string): boolean {
|
|
13
|
+
return isModelOverrideValue(target(model), path);
|
|
13
14
|
}
|
|
14
|
-
export function toggleFast(
|
|
15
|
-
return toggleModelOverrideValue(target(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
50
|
-
|
|
51
|
-
fastEnabled =
|
|
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 ===
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ===
|
|
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 };
|
|
@@ -4,6 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.54.4: Restored tabs take their Pi's name
|
|
8
|
+
|
|
9
|
+
- `Restore title`: Restoring a Pi into a routing tab ("move Pi into this tab") now renames the tab to that Pi's Thread display title, as Workspace Restore already did; previously a live-rebind Restore left the tab named `🚦 Routing`. A failed rename is recorded and never blocks the Restore.
|
|
10
|
+
- `Native routing tabs`: An `All` input now also adopts a generically named native tab (desktop `New Chat`) that its client created immediately before it, not only a tab named after the input (mobile). Desktop and Android clients, with user topic creation allowed or not, each end with exactly one routing tab; a client's own brief native name before the rename cannot be prevented.
|
|
11
|
+
- `Reclaimed leader tab`: When the leader's own tab is gone (for example after deleting every tab) and a new tab from `All` is reclaimed for it, the leader's Workspace binding now moves to that tab with its slot and name instead of gaining a second slot, and the tab is renamed to its display title instead of staying `New Chat`. Restore and labels no longer point at the deleted tab.
|
|
12
|
+
- `Display titles`: A Thread whose tab has no acknowledged title yet (fresh, moved or reclaimed) now shows its display-mode title, such as the directory title, in the Restore menu and after Restore, instead of falling back to its generated slot name.
|
|
13
|
+
- `One-shot routing menus`: Every final routing choice now deletes its menu. A live-rebind Restore left the chooser in the tab, a Workspace Restore kept it until old-Thread cleanup, which may never come, and Cancel replaced it with a notice. A restored tab stays as the Pi's Thread; a routing tab with nothing left to route is still removed. A delivered Workspace Restore answers `Restored`, not `Restore unconfirmed`, and the Restore submenu now reads `🔁 Restore a Pi into this tab:`.
|
|
14
|
+
- `Restore history`: An input sent from `All` now moves into the routing tab opened for it, on desktop and mobile alike: the tab starts with a silent forward of the input, its menu replies to it, and the original leaves `All` once the copy is confirmed. A Thread restored into that tab keeps the prompt it answers instead of losing it, since the Bot API files such inputs in `All` even when mobile first draws them in the tab.
|
|
15
|
+
- `Old tab cleanup`: After a live-rebind Restore, the old tab is deleted right after Pi finishes answering the moved input, instead of at the next paced retry, which could add up to about 30 seconds and later a full minute. Cleanup still waits for an idle session; a settled agent only wakes the waiting retry early.
|
|
16
|
+
|
|
7
17
|
## 0.54.3: One routing tab on mobile
|
|
8
18
|
|
|
9
19
|
- `Mobile routing tab`: Sending a command or prompt from `All` in mobile Telegram no longer leaves an extra tab named after the input. The mobile client creates that native tab itself before delivering the input to `All`; the bridge now adopts it as the routing tab and renames it, instead of creating a second tab beside it. Desktop behavior is unchanged.
|
|
@@ -291,6 +291,8 @@ interface TelegramLifecycleBindingDeps {
|
|
|
291
291
|
isTurnTransportActive?: (turn: Queue.PendingTelegramTurn) => boolean;
|
|
292
292
|
updateStatus: TelegramBridgeStatusUpdater;
|
|
293
293
|
recordRuntimeEvent: TelegramRuntimeEventRecorder;
|
|
294
|
+
/** Settled-session hint for owners that wait on agent idleness, such as old-tab cleanup after a live rebind. */
|
|
295
|
+
onSessionSettled?: (ctx: Pi.ExtensionContext) => void;
|
|
294
296
|
}
|
|
295
|
-
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, }: TelegramLifecycleBindingDeps): void;
|
|
297
|
+
export declare function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }: TelegramLifecycleBindingDeps): void;
|
|
296
298
|
export {};
|
|
@@ -626,7 +626,7 @@ export function registerTelegramCommandsAndTools({ pi, agentDir, configStore, pe
|
|
|
626
626
|
},
|
|
627
627
|
});
|
|
628
628
|
}
|
|
629
|
-
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, }) {
|
|
629
|
+
export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime, activityRuntime, activityVerbosityRuntime, diagnostics, assistantOutputRuntime, sessionLifecycleRuntime, configStore, abort, typing, lifecycle, activeTurnRuntime, telegramQueueStore, modelSwitchController, previewRuntime, promptDispatchRuntime, deferredQueueDispatchRuntime, modelContextAvailabilityRuntime, disconnectOnQuit, onSessionStarted, shutdownGenerativeAppLiveSurfaces, resolveAutomaticThreadCleanupEnabled, buttonActionStore, callMultipart, sendChatAction, sendRecordVoiceAction, sendMarkdownReply, sendTextReply, dispatchNextQueuedTelegramTurn, onPromptHandedOff, answerGuestQuery, deleteMessage, sendGuestReply, editGuestReply, stopGuestPlaceholder, preparePreviewDelivery, finalizeMarkdownPreview, proactivePushTargetGetter, getAssistantRenderingMode, recordMessageOwnership, canSendAgentActivity, isSessionContextActive = () => true, isTurnTransportActive, updateStatus, recordRuntimeEvent, onSessionSettled, }) {
|
|
630
630
|
const agentEndResetter = Runtime.createTelegramAgentEndResetter({
|
|
631
631
|
abort,
|
|
632
632
|
typing,
|
|
@@ -1077,6 +1077,7 @@ export function registerTelegramLifecycleRuntimeHooks({ pi, publicationRuntime,
|
|
|
1077
1077
|
agentWorkActive = false;
|
|
1078
1078
|
activityRuntime.onAgentSettled();
|
|
1079
1079
|
modelContextAvailabilityRuntime.reconcile();
|
|
1080
|
+
onSessionSettled?.(ctx);
|
|
1080
1081
|
},
|
|
1081
1082
|
onBeforeAgentStart: Prompts.createTelegramProactiveBeforeAgentStartHook({
|
|
1082
1083
|
reconcileAvailability: modelContextAvailabilityRuntime.reconcile,
|
|
@@ -478,6 +478,13 @@ export default function (pi) {
|
|
|
478
478
|
instanceId: telegramInstanceId,
|
|
479
479
|
listRecords: threadStore.list,
|
|
480
480
|
listWorkspaceBindings: threadStore.listWorkspaceBindings,
|
|
481
|
+
resolveAutomaticDisplayTitle(binding) {
|
|
482
|
+
return ThreadDisplay.resolveTelegramInitialWorkspaceDisplayName({
|
|
483
|
+
bindings: threadStore.listWorkspaceBindings(),
|
|
484
|
+
binding,
|
|
485
|
+
mode: Config.resolveTelegramThreadDisplayMode(configStore.get()),
|
|
486
|
+
});
|
|
487
|
+
},
|
|
481
488
|
getFollowerDisplayTitle: telegramBusFollowerRegistrationState.getDisplayTitle,
|
|
482
489
|
getActiveTurnTarget: activeTurnRuntime.getTarget,
|
|
483
490
|
getFollowerTarget: telegramBusFollowerRegistrationState.getTarget,
|
|
@@ -2440,5 +2447,6 @@ export default function (pi) {
|
|
|
2440
2447
|
},
|
|
2441
2448
|
updateStatus,
|
|
2442
2449
|
recordRuntimeEvent,
|
|
2450
|
+
onSessionSettled: inboundRouteRuntime.onSessionSettled,
|
|
2443
2451
|
});
|
|
2444
2452
|
}
|
|
@@ -57,7 +57,10 @@ export type TelegramLiveRebindCleanupIssue = {
|
|
|
57
57
|
cleanup: ThreadReconciler.TelegramLiveRebindCleanupOutcome;
|
|
58
58
|
recorded: boolean;
|
|
59
59
|
};
|
|
60
|
-
/**
|
|
60
|
+
/**
|
|
61
|
+
* Default live-rebind cleanup pacing: quick early attempts, then minutely, within a 15-minute window. The first
|
|
62
|
+
* delay is not cosmetic: a leader's detached command reply still needs the released, cleanup-free row.
|
|
63
|
+
*/
|
|
61
64
|
export declare const TELEGRAM_LIVE_REBIND_CLEANUP_SCHEDULE: Readonly<{
|
|
62
65
|
delaysMs: readonly number[];
|
|
63
66
|
intervalMs: 60000;
|
|
@@ -100,6 +103,8 @@ export declare function createTelegramLiveRebindCoordinator(input: {
|
|
|
100
103
|
/** Explicit staged singleton command branch; absence preserves ordinary live input behavior. */
|
|
101
104
|
selectedCommand?: Bus.TelegramBusSelectedCommandInput;
|
|
102
105
|
};
|
|
106
|
+
/** Restore moves the tab to its new owner: once applied, show that owner's title (best-effort, never blocking). */
|
|
107
|
+
retitle?: (isCurrent: () => boolean) => Promise<void>;
|
|
103
108
|
recordRuntimeEvent?: (category: string, error: unknown, details: Record<string, unknown>) => void;
|
|
104
109
|
}): {
|
|
105
110
|
/** Correlation only; a later cleanup attempt rereads and revalidates the canonical row itself. */
|
|
@@ -346,6 +351,8 @@ export declare function createTelegramInboundRouteRuntime<TUpdate extends Update
|
|
|
346
351
|
/** Commands-owned registry port for dormant scoped recipient assembly, not ordinary update replay. */
|
|
347
352
|
prepareHeldCommand: ReturnType<typeof Commands.createTelegramCommandHandlerTargetRuntime<TMessage, TContext>>["prepareHeldCommand"];
|
|
348
353
|
canPrepareHeldCommand: ReturnType<typeof Commands.createTelegramCommandHandlerTargetRuntime<TMessage, TContext>>["canPrepareHeldCommand"];
|
|
354
|
+
/** Agent-settled hint: wakes waiting leader cleanup retries early; every readiness gate is still sampled fresh. */
|
|
355
|
+
onSessionSettled(ctx: TContext): void;
|
|
349
356
|
/** Fresh body-free recipient observation only; never reconstructs an attempt or grants cleanup. */
|
|
350
357
|
observeLiveRebindLeaderWork(intent: Threads.TelegramWorkspaceLiveRebindIntent, ctx: TContext): Promise<Bus.TelegramBusLiveRebindWorkObservation | undefined>;
|
|
351
358
|
/** Fresh non-destructive candidate only; no retained idle, cleanup action or deletion grant. */
|