@mzwing/pi-codex-enhancer 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Lockinwize Lolite
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is furnished
10
+ to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @mzwing/pi-codex-enhancer
2
+
3
+ [![npm](https://img.shields.io/npm/v/@mzwing/pi-codex-enhancer)](https://www.npmjs.com/package/@mzwing/pi-codex-enhancer)
4
+
5
+ Keeps a valid Codex turn state on every [Pi](https://pi.dev) request, so ChatGPT accounts stay off
6
+ the degraded scheduling path.
7
+
8
+ The ChatGPT Codex backend mints an opaque blob into the `x-codex-turn-state` response header. A
9
+ non-degraded one is exactly 292 characters long; carrying it on your next request is what keeps the
10
+ answers full-strength instead of the broken-logic, `overloaded`, 429 version. Pi never sends that
11
+ header, so every turn starts from nothing. This mints one, holds it for its hour, puts it on each
12
+ Codex request, and drops it the moment the server hands back a degraded one:
13
+
14
+ ```
15
+ ✓ codex+ 292 · 47m left a valid state is going out on every request
16
+ … codex+ minting a state is being minted right now
17
+ ⚠ codex+ degraded the server sent a short state; it was dropped and will be re-minted
18
+ ? codex+ no state nothing could be minted, and the request went out bare
19
+ · codex+ not gated this model does not use a turn state
20
+ ```
21
+
22
+ Requests are never blocked: no state just means no header.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pi install npm:@mzwing/pi-codex-enhancer
28
+ ```
29
+
30
+ Then set `"transport": "sse"` in pi's `settings.json` — see [Limits](#limits).
31
+
32
+ ## Configuration
33
+
34
+ Optional. Both scopes are merged, project over global.
35
+
36
+ | Scope | Path |
37
+ | ------- | ------------------------------------------------------ |
38
+ | Global | `~/.pi/agent/extensions/pi-codex-enhancer/config.json` |
39
+ | Project | `.pi/extensions/pi-codex-enhancer/config.json` |
40
+
41
+ | Field | Default | Description |
42
+ | -------------------- | ------------------ | ---------------------------------------------------------------------------------- |
43
+ | `enabled` | `true` | Turn the whole thing off. |
44
+ | `providers` | `["openai-codex"]` | Provider ids to act on. `[]` acts on every provider that speaks the Codex api. |
45
+ | `probeTimeoutMs` | `8000` | How long one request may be held while a state is minted. 1000-30000. |
46
+ | `minProbeIntervalMs` | `60000` | Floor between mints, so a failing account cannot tax every request. 10000-3600000. |
47
+ | `probeProxyUrl` | `""` | Egress for the mint only. `http`, `https`, `socks5` or `socks5h`; host, no path. |
48
+ | `notify` | `true` | Raise a notification on a degraded state, a failed mint, or the wrong transport. |
49
+
50
+ Whether a 292 is issued at all seems to depend on the egress IP. `probeProxyUrl` exists for that:
51
+ point it at a residential or native-IPv6 exit and only the mint goes through it, while your normal
52
+ requests keep pi's usual network path. Tickets are stored at
53
+ `~/.pi/agent/extensions/pi-codex-enhancer/tickets.json`, owner-readable only, and shared across
54
+ concurrent pi sessions so they mint once between them.
55
+
56
+ ## /codex-enhancer
57
+
58
+ `/codex-enhancer` prints the held state, the resolved config and where everything was read from.
59
+ `/codex-enhancer refresh` mints a new state now, ignoring the interval. `/codex-enhancer forget`
60
+ drops the stored one, which is what you want after logging in as a different account.
61
+
62
+ ## Limits
63
+
64
+ **Set `"transport": "sse"`.** Pi's default `auto` prefers WebSocket, and it pools sockets — the
65
+ injected header only reaches the backend on a fresh handshake, so most requests on that transport go
66
+ out without it. The extension says so once per session rather than pretending to protect you.
67
+
68
+ Each mint is a real one-token Codex request and costs a little quota — roughly one an hour. On the
69
+ SSE transport the state is usually captured off a real response instead, for free.
70
+
71
+ Nothing here can make the backend issue a 292. Egress IP, account tier and upstream load all appear
72
+ to matter and none of them are ours to set; `no state` means it did not happen, not that something
73
+ broke. And the whole mechanism is undocumented: OpenAI can change or drop it at any time.
74
+
75
+ The state is never printed, only described by length.
76
+
77
+ ## Credits
78
+
79
+ <https://blog.caowo.de/posts/chatgpt-codex-292-state-anti-degradation-2026/> for writing up the
80
+ mechanism, and [sub2api@49a39b6](https://github.com/Wei-Shaw/sub2api/tree/49a39b6dc1abed30fd227611e8af1108bc427610)
81
+ for the implementation that pinned down what a good state actually looks like.
82
+
83
+ ## License
84
+
85
+ [MIT](LICENSE)
@@ -0,0 +1,9 @@
1
+ {
2
+ "$schema": "../schemas/config.schema.json",
3
+ "enabled": true,
4
+ "providers": ["openai-codex"],
5
+ "probeTimeoutMs": 8000,
6
+ "minProbeIntervalMs": 60000,
7
+ "probeProxyUrl": "socks5h://user:pass@127.0.0.1:7890",
8
+ "notify": true
9
+ }
@@ -0,0 +1,5 @@
1
+ import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ //#region src/index.d.ts
3
+ export default function codexEnhancerExtension(pi: ExtensionAPI): void;
4
+ //#endregion
5
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
Binary file
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["request","httpsRequest","httpRequest","DefaultTicketStore"],"sources":["../src/config.ts","../src/state.ts","../src/render.ts","../src/command.ts","../src/harvest.ts","../src/probe.ts","../src/store.ts","../src/extension.ts","../src/index.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\nimport process from 'node:process'\nimport { z } from 'zod'\n\nexport const EXTENSION_ID = 'pi-codex-enhancer'\nexport const COMMAND_NAME = 'codex-enhancer'\nconst DEFAULT_PROVIDERS: string[] = ['openai-codex']\nconst PROXY_SCHEMES = ['http:', 'https:', 'socks5:', 'socks5h:']\nconst CONFIG_SCHEMA_URL =\n 'https://raw.githubusercontent.com/mzwing/pi-packages/master/packages/pi-codex-enhancer/schemas/config.schema.json'\n\n/** Syntax only, and never a network call. Ported from sub2api's harvest-proxy validation. */\nexport function proxyUrlProblem(raw: string): string | undefined {\n const value = raw.trim()\n if (value.length === 0) {\n return undefined\n }\n\n let parsed: URL\n try {\n parsed = new URL(value)\n } catch {\n return 'must be a URL'\n }\n if (!PROXY_SCHEMES.includes(parsed.protocol)) {\n return 'scheme must be http, https, socks5 or socks5h'\n }\n if (parsed.hostname.length === 0) {\n return 'must name a host'\n }\n if ((parsed.pathname !== '' && parsed.pathname !== '/') || parsed.search !== '' || parsed.hash !== '') {\n return 'must not carry a path, query or fragment'\n }\n if (parsed.port !== '' && (Number(parsed.port) < 1 || Number(parsed.port) > 65535)) {\n return 'port must be between 1 and 65535'\n }\n\n return undefined\n}\n\n/** So a password never reaches the terminal, a log or a bug report. */\nexport function maskProxyUrl(raw: string): string {\n const value = raw.trim()\n if (value.length === 0 || proxyUrlProblem(value) !== undefined) {\n return ''\n }\n const parsed = new URL(value)\n if (parsed.password !== '') {\n parsed.password = '***'\n }\n\n return parsed.toString()\n}\n\nconst proxyUrlSchema = z\n .string()\n .refine(\n value => proxyUrlProblem(value) === undefined,\n 'must be an http, https, socks5 or socks5h URL naming a host, with no path, query or fragment',\n )\n\nconst configFileShape = {\n $schema: z.string().min(1).optional(),\n enabled: z.boolean().optional(),\n providers: z.array(z.string().trim().min(1)).optional(),\n probeTimeoutMs: z.number().int().min(1000).max(30_000).optional(),\n minProbeIntervalMs: z.number().int().min(10_000).max(3_600_000).optional(),\n probeProxyUrl: proxyUrlSchema.optional(),\n notify: z.boolean().optional(),\n}\n\nconst configFileSchema = z.strictObject(configFileShape)\n\nconst enhancerConfigSchema = z.strictObject({\n ...configFileShape,\n enabled: z.boolean().default(true),\n providers: z.array(z.string().trim().min(1)).default(DEFAULT_PROVIDERS),\n probeTimeoutMs: z.number().int().min(1000).max(30_000).default(8000),\n minProbeIntervalMs: z.number().int().min(10_000).max(3_600_000).default(60_000),\n probeProxyUrl: proxyUrlSchema.default(''),\n notify: z.boolean().default(true),\n})\n\n/**\n * Hand-written because `isolatedDeclarations` cannot emit a `z.infer` of a module-private schema.\n * `DEFAULT_CONFIG` below is the assignability check that keeps the two in step.\n */\nexport interface EnhancerConfig {\n $schema?: string | undefined\n enabled: boolean\n /** Provider ids to act on. An empty list acts on every provider that speaks the Codex api. */\n providers: string[]\n /** How long one provider request may be held while a ticket is minted. */\n probeTimeoutMs: number\n minProbeIntervalMs: number\n /** Egress for the probe only. Business requests keep Pi's own network path. */\n probeProxyUrl: string\n notify: boolean\n}\n\nexport const DEFAULT_CONFIG: EnhancerConfig = enhancerConfigSchema.parse({})\n\ninterface EnhancerConfigFile {\n $schema?: string | undefined\n enabled?: boolean | undefined\n providers?: string[] | undefined\n probeTimeoutMs?: number | undefined\n minProbeIntervalMs?: number | undefined\n probeProxyUrl?: string | undefined\n notify?: boolean | undefined\n}\n\ninterface ConfigIssue {\n sourcePath: string\n message: string\n}\n\nexport interface EnhancerConfigPaths {\n globalPath: string\n projectPath: string\n}\n\nexport interface LoadConfigResult {\n config: EnhancerConfig | undefined\n issues: ConfigIssue[]\n globalPath: string\n projectPath: string\n}\n\nexport interface LoadConfigOptions {\n cwd: string\n agentDir?: string\n readFile?: (path: string) => string | undefined\n}\n\nexport function defaultEnhancerAgentDir(): string {\n return process.env['PI_CODING_AGENT_DIR'] ?? join(homedir(), '.pi', 'agent')\n}\n\nexport function getEnhancerConfigPaths(cwd: string, agentDir: string = defaultEnhancerAgentDir()): EnhancerConfigPaths {\n return {\n globalPath: join(agentDir, 'extensions', EXTENSION_ID, 'config.json'),\n projectPath: join(cwd, '.pi', 'extensions', EXTENSION_ID, 'config.json'),\n }\n}\n\nexport function getTicketStorePath(agentDir: string = defaultEnhancerAgentDir()): string {\n return join(agentDir, 'extensions', EXTENSION_ID, 'tickets.json')\n}\n\n/** Reads a config file, reporting a missing one as `undefined` rather than an error. */\nfunction readConfigFile(path: string): string | undefined {\n try {\n return readFileSync(path, 'utf8')\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n return undefined\n }\n throw error\n }\n}\n\nfunction formatZodIssue(error: z.ZodError): string {\n return error.issues\n .map(issue => `${issue.path.length > 0 ? issue.path.join('.') : '(root)'}: ${issue.message}`)\n .join('; ')\n}\n\n/** Returns `undefined` on failure, having recorded why; an absent file reads as an empty scope. */\nfunction readScope(\n path: string,\n readFile: (path: string) => string | undefined,\n issues: ConfigIssue[],\n): EnhancerConfigFile | undefined {\n let source: string | undefined\n try {\n source = readFile(path)\n } catch (error) {\n issues.push({ sourcePath: path, message: error instanceof Error ? error.message : String(error) })\n\n return undefined\n }\n if (source === undefined) {\n return {}\n }\n\n let value: unknown\n try {\n value = JSON.parse(source)\n } catch (error) {\n issues.push({\n sourcePath: path,\n message: `invalid JSON: ${error instanceof Error ? error.message : String(error)}`,\n })\n\n return undefined\n }\n\n const parsed = configFileSchema.safeParse(value)\n if (!parsed.success) {\n issues.push({ sourcePath: path, message: formatZodIssue(parsed.error) })\n\n return undefined\n }\n\n return parsed.data\n}\n\nexport function loadEnhancerConfig(options: LoadConfigOptions): LoadConfigResult {\n const { globalPath, projectPath } = getEnhancerConfigPaths(options.cwd, options.agentDir)\n const readFile = options.readFile ?? readConfigFile\n const issues: ConfigIssue[] = []\n const globalConfig = readScope(globalPath, readFile, issues)\n const projectConfig = readScope(projectPath, readFile, issues)\n\n if (globalConfig === undefined || projectConfig === undefined) {\n return { config: undefined, issues, globalPath, projectPath }\n }\n\n const merged = enhancerConfigSchema.safeParse({ ...globalConfig, ...projectConfig })\n if (!merged.success) {\n issues.push({ sourcePath: projectPath, message: formatZodIssue(merged.error) })\n\n return { config: undefined, issues, globalPath, projectPath }\n }\n\n return { config: merged.data, issues, globalPath, projectPath }\n}\n\nexport type PiTransport = 'sse' | 'websocket' | 'websocket-cached' | 'auto'\n\nconst TRANSPORTS: PiTransport[] = ['sse', 'websocket', 'websocket-cached', 'auto']\n\nfunction readTransportScope(path: string, readFile: (path: string) => string | undefined): PiTransport | undefined {\n let source: string | undefined\n try {\n source = readFile(path)\n } catch {\n return undefined\n }\n if (source === undefined) {\n return undefined\n }\n\n try {\n const value: unknown = JSON.parse(source)\n const record = typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {}\n\n return TRANSPORTS.find(candidate => candidate === record['transport'])\n } catch {\n return undefined\n }\n}\n\n/**\n * Pi's own transport, read from its settings rather than `ExtensionContext`, which does not carry\n * it. The injected header only reaches the backend on a fresh handshake, so anything but `sse`\n * leaves most requests untouched and the user has to be told.\n */\nexport function readPiTransport(options: LoadConfigOptions): PiTransport {\n const agentDir = options.agentDir ?? defaultEnhancerAgentDir()\n const readFile = options.readFile ?? readConfigFile\n\n return (\n readTransportScope(join(options.cwd, '.pi', 'settings.json'), readFile) ??\n readTransportScope(join(agentDir, 'settings.json'), readFile) ??\n 'auto'\n )\n}\n\nexport function buildEnhancerJsonSchema(): Record<string, unknown> {\n const { $schema, ...schema } = z.toJSONSchema(enhancerConfigSchema, { target: 'draft-2020-12', io: 'input' })\n\n return { $schema, $id: CONFIG_SCHEMA_URL, ...schema }\n}\n","/** The header the Codex backend mints its turn state into, and that a client echoes back. */\nexport const TURN_STATE_HEADER = 'x-codex-turn-state'\n\n/**\n * A non-degraded state is exactly this long and is a Fernet token. Both conditions hold together:\n * the community's \"312\" is the same blob shape at the degraded length.\n */\nconst GOOD_LENGTH = 292\nconst STATE_PREFIX = 'gAAAAA'\n\n/** The backend never states the real lifetime; one hour is what the tooling converged on. */\nexport const TICKET_TTL_MS = 3_600_000\nconst TICKET_REFRESH_BEFORE_MS = 600_000\n\nexport type TurnStateVerdict = 'good' | 'degraded' | 'absent'\ntype TicketSource = 'probe' | 'response'\n\nexport interface Ticket {\n accountId: string\n model: string\n value: string\n capturedAt: number\n source: TicketSource\n}\n\nexport interface MintTicketOptions {\n accountId: string\n model: string\n value: string | undefined\n capturedAt: number\n source: TicketSource\n}\n\nexport function ticketKey(accountId: string, model: string): string {\n return `${accountId}\u0000${model}`\n}\n\nexport function classifyTurnState(value: string | undefined): TurnStateVerdict {\n const state = value?.trim() ?? ''\n if (state.length === 0) {\n return 'absent'\n }\n\n return state.length === GOOD_LENGTH && state.startsWith(STATE_PREFIX) ? 'good' : 'degraded'\n}\n\n/** For reports and logs. Never returns the blob itself — it is bearer material. */\nexport function describeTurnState(value: string | undefined): string {\n const verdict = classifyTurnState(value)\n if (verdict === 'absent') {\n return 'absent'\n }\n\n return `${value?.trim().length ?? 0} chars, ${verdict}`\n}\n\n/** The single choke point that keeps a degraded blob out of the store. */\nexport function mintTicket(options: MintTicketOptions): Ticket | undefined {\n if (classifyTurnState(options.value) !== 'good') {\n return undefined\n }\n\n return {\n accountId: options.accountId,\n model: options.model,\n value: options.value?.trim() ?? '',\n capturedAt: options.capturedAt,\n source: options.source,\n }\n}\n\nexport function ticketAgeMs(ticket: Ticket, now: number): number {\n return Math.max(0, now - ticket.capturedAt)\n}\n\nexport function isUsable(ticket: Ticket | undefined, now: number): boolean {\n return ticket !== undefined && classifyTurnState(ticket.value) === 'good' && ticketAgeMs(ticket, now) < TICKET_TTL_MS\n}\n\nexport function needsRefresh(ticket: Ticket | undefined, now: number): boolean {\n return ticket === undefined || ticketAgeMs(ticket, now) >= TICKET_TTL_MS - TICKET_REFRESH_BEFORE_MS\n}\n\nexport function newerTicket(left: Ticket | undefined, right: Ticket | undefined): Ticket | undefined {\n if (left === undefined) {\n return right\n }\n if (right === undefined) {\n return left\n }\n\n return right.capturedAt > left.capturedAt ? right : left\n}\n","import type { EnhancerConfig, EnhancerConfigPaths, PiTransport } from './config.js'\nimport type { Ticket } from './state.js'\nimport type { ThemeColor } from '@earendil-works/pi-coding-agent'\nimport { maskProxyUrl } from './config.js'\nimport { describeTurnState, TICKET_TTL_MS, ticketAgeMs } from './state.js'\n\n/** The slice of Pi's `Theme` the footer needs; `ctx.ui.theme` satisfies it. */\nexport interface StatusTheme {\n fg: (color: ThemeColor, text: string) => string\n}\n\nexport type StatusKind = 'good' | 'minting' | 'degraded' | 'missing' | 'unsupported'\nexport type AlertKind = 'degraded' | 'transport' | 'unreachable'\n\nexport interface StatusView {\n kind: StatusKind\n remainingMs?: number | undefined\n}\n\nexport interface ReportView {\n config: EnhancerConfig\n paths: EnhancerConfigPaths | undefined\n storePath: string\n transport: PiTransport\n ticket: Ticket | undefined\n now: number\n lastOutcome: string | undefined\n nextProbeAt: number | undefined\n}\n\nconst LABEL = 'codex+'\nconst KIND_COLORS: Record<StatusKind, ThemeColor> = {\n good: 'success',\n minting: 'dim',\n degraded: 'error',\n missing: 'warning',\n unsupported: 'dim',\n}\nconst KIND_GLYPHS: Record<StatusKind, string> = {\n good: '✓',\n minting: '…',\n degraded: '⚠',\n missing: '?',\n unsupported: '·',\n}\n\nfunction formatDuration(ms: number): string {\n const minutes = Math.floor(ms / 60_000)\n\n return minutes < 1 ? '<1m' : `${minutes}m`\n}\n\nfunction kindText(view: StatusView): string {\n if (view.kind === 'good') {\n return view.remainingMs === undefined ? '292' : `292 · ${formatDuration(view.remainingMs)} left`\n }\n if (view.kind === 'minting') {\n return 'minting'\n }\n if (view.kind === 'degraded') {\n return 'degraded'\n }\n\n return view.kind === 'missing' ? 'no state' : 'not gated'\n}\n\n/** The footer line. Always present, so \"no state\" is stated rather than implied to be fine. */\nexport function renderStatus(view: StatusView, theme: StatusTheme): string {\n const color = KIND_COLORS[view.kind]\n\n return `${theme.fg(color, KIND_GLYPHS[view.kind])} ${theme.fg('dim', LABEL)} ${theme.fg(color, kindText(view))}`\n}\n\nexport function renderAlert(kind: AlertKind, detail: string): string {\n if (kind === 'transport') {\n return `${LABEL}: pi's transport is ${detail}, so the turn state only reaches the backend on a new connection — set \"transport\": \"sse\" in pi's settings`\n }\n if (kind === 'unreachable') {\n return `${LABEL}: could not mint a turn state — ${detail}`\n }\n\n return `${LABEL}: the backend returned a degraded turn state (${detail}); it has been dropped and will be re-minted`\n}\n\n/** The `/codex-enhancer` body. Prints what the state is, never the state itself. */\nexport function renderReport(view: ReportView): string {\n const { config, ticket } = view\n const lines = [\n `enabled : ${config.enabled}`,\n `providers : ${config.providers.length > 0 ? config.providers.join(', ') : '(every codex provider)'}`,\n `transport : ${view.transport}${view.transport === 'sse' ? '' : ' (only a new connection carries the header)'}`,\n `ticket : ${\n ticket === undefined\n ? '(none)'\n : `${describeTurnState(ticket.value)}, ${formatDuration(TICKET_TTL_MS - ticketAgeMs(ticket, view.now))} left, from ${ticket.source}, minted for ${ticket.model}`\n }`,\n `last probe : ${view.lastOutcome ?? '(none yet)'}`,\n `next probe : ${\n view.nextProbeAt === undefined || view.nextProbeAt <= view.now\n ? 'allowed now'\n : `in ${formatDuration(view.nextProbeAt - view.now)}`\n }`,\n '',\n `timeout : ${config.probeTimeoutMs}ms`,\n `interval : ${config.minProbeIntervalMs}ms`,\n `proxy : ${maskProxyUrl(config.probeProxyUrl) || '(none)'}`,\n `notify : ${config.notify}`,\n '',\n `tickets : ${view.storePath}`,\n `global : ${view.paths?.globalPath ?? '(unknown until the session starts)'}`,\n `project : ${view.paths?.projectPath ?? '(unknown until the session starts)'}`,\n ]\n\n return lines.join('\\n')\n}\n","import type { ReportView } from './render.js'\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'\nimport { COMMAND_NAME, EXTENSION_ID } from './config.js'\nimport { renderReport } from './render.js'\n\ninterface CompletionItem {\n value: string\n label: string\n description: string\n}\n\nconst USAGE = `Usage: /${COMMAND_NAME} [refresh|forget]`\nconst SUBCOMMANDS: CompletionItem[] = [\n { value: 'refresh', label: 'refresh', description: 'Mint a new turn state now, ignoring the probe interval' },\n { value: 'forget', label: 'forget', description: 'Drop the stored turn state, for instance after switching account' },\n]\n\nexport interface EnhancerCommandController {\n getReport: () => ReportView\n refresh: (context: ExtensionContext) => Promise<string>\n forget: (context: ExtensionContext) => string\n}\n\nexport function registerEnhancerCommand(pi: ExtensionAPI, controller: EnhancerCommandController): void {\n try {\n pi.registerCommand(COMMAND_NAME, {\n description: 'Report and control the Codex turn state this session puts on its requests',\n getArgumentCompletions(prefix) {\n const matches = SUBCOMMANDS.filter(item => item.value.startsWith(prefix.trim()))\n\n return matches.length > 0 ? matches : null\n },\n handler: async (args, ctx) => {\n const argument = args.trim().toLowerCase()\n if (argument.length === 0) {\n ctx.ui.notify(renderReport(controller.getReport()), 'info')\n\n return\n }\n if (argument === 'refresh') {\n ctx.ui.notify(await controller.refresh(ctx), 'info')\n\n return\n }\n if (argument === 'forget') {\n ctx.ui.notify(controller.forget(ctx), 'info')\n\n return\n }\n ctx.ui.notify(USAGE, 'warning')\n },\n })\n } catch (error) {\n // `codex-enhancer` is a plausible name for someone else's command; a clash must not take the\n // extension down, because the header injection is the part that matters.\n console.warn(\n `[${EXTENSION_ID}] could not register /${COMMAND_NAME}: ${error instanceof Error ? error.message : String(error)}`,\n )\n }\n}\n","import type { ProbeReply, ProbeTransport } from './probe.js'\nimport type { Ticket } from './state.js'\nimport { Buffer } from 'node:buffer'\nimport { randomUUID } from 'node:crypto'\nimport { arch, platform, release } from 'node:os'\nimport { mintTicket, ticketKey, TURN_STATE_HEADER } from './state.js'\n\n/** Where the ChatGPT account id sits in the access token, as Pi's own Codex api reads it. */\nconst JWT_CLAIM = 'https://api.openai.com/auth'\nconst DEFAULT_BASE_URL = 'https://chatgpt.com/backend-api'\nconst FIRST_BACKOFF_MS = 30_000\nconst MAX_BACKOFF_MS = 900_000\nconst TRAILING_SLASHES = /\\/+$/\n\ntype HarvestFailure = 'throttled' | 'unsupported' | 'degraded' | 'http-error' | 'network'\n\nexport type HarvestOutcome = { ok: true; ticket: Ticket } | { ok: false; reason: HarvestFailure; message: string }\n\nexport interface ProbeIdentity {\n accountId: string\n token: string\n baseUrl: string | undefined\n /** Headers Pi resolved for the model, kept so a relay's own headers still reach it. */\n headers: Record<string, string>\n}\n\ninterface HarvestRequest {\n identity: ProbeIdentity\n model: string\n timeoutMs: number\n proxyUrl: string\n signal?: AbortSignal | undefined\n /** `/codex-enhancer refresh`: skips the interval and the backoff, never a probe already running. */\n force?: boolean | undefined\n}\n\ninterface HarvesterDependencies {\n transport: ProbeTransport\n now?: (() => number) | undefined\n newSessionId?: (() => string) | undefined\n userAgent?: (() => string) | undefined\n}\n\nexport interface HarvesterOptions {\n minIntervalMs: number\n dependencies: HarvesterDependencies\n}\n\nexport interface Harvester {\n probe: (request: HarvestRequest) => Promise<HarvestOutcome>\n canProbe: (accountId: string, model: string) => boolean\n nextProbeAt: (accountId: string, model: string) => number | undefined\n isRunning: (accountId: string, model: string) => boolean\n}\n\ninterface Throttle {\n nextAllowedAt: number\n backoffMs: number\n /** Set once a 200 comes back with no state header at all: this model does not mint one. */\n unsupported: boolean\n}\n\n/** Pi's `getPiUserAgent` is internal, so the probe recomputes the same string. */\nexport function defaultUserAgent(): string {\n return `pi (${platform()} ${release()}; ${arch()})`\n}\n\n/** Mirrors Pi's `resolveCodexUrl`, so a relay base URL is honoured instead of hard-coding OpenAI. */\nexport function resolveCodexProbeUrl(baseUrl: string | undefined): string {\n const raw = baseUrl !== undefined && baseUrl.trim().length > 0 ? baseUrl.trim() : DEFAULT_BASE_URL\n const normalized = raw.replace(TRAILING_SLASHES, '')\n if (normalized.endsWith('/codex/responses')) {\n return normalized\n }\n\n return normalized.endsWith('/codex') ? `${normalized}/responses` : `${normalized}/codex/responses`\n}\n\n/** Unlike Pi's `extractAccountId`, an opaque key is \"no account\" rather than a throw. */\nexport function extractAccountId(token: string): string | undefined {\n const payload = token.split('.')[1]\n if (payload === undefined) {\n return undefined\n }\n\n try {\n const claims = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')) as Record<string, unknown>\n const auth = claims[JWT_CLAIM]\n const accountId =\n typeof auth === 'object' && auth !== null ? (auth as Record<string, unknown>)['chatgpt_account_id'] : undefined\n\n return typeof accountId === 'string' && accountId.length > 0 ? accountId : undefined\n } catch {\n return undefined\n }\n}\n\nexport function buildProbeBody(model: string): string {\n return JSON.stringify({\n model,\n store: false,\n stream: true,\n instructions: 'Reply with exactly: pong',\n input: [{ role: 'user', content: [{ type: 'input_text', text: 'ping' }] }],\n })\n}\n\n/**\n * The probe presents Pi's identity, not Codex CLI's. Pi hard-sets `originator` and `User-Agent`\n * after an extension's headers, so a state minted under any other identity would be replayed under\n * Pi's — the self-contradictory signal this extension exists to avoid.\n */\nexport function buildProbeHeaders(\n identity: ProbeIdentity,\n sessionId: string,\n userAgent: string,\n): Record<string, string> {\n return {\n ...identity.headers,\n authorization: `Bearer ${identity.token}`,\n 'chatgpt-account-id': identity.accountId,\n originator: 'pi',\n 'user-agent': userAgent,\n 'openai-beta': 'responses=experimental',\n accept: 'text/event-stream',\n 'content-type': 'application/json',\n 'session-id': sessionId,\n 'x-client-request-id': sessionId,\n }\n}\n\n/**\n * Owns \"may a probe run right now\": single flight per key, a floor between probes, and a backoff that\n * decays a failing account instead of taxing every request. Callers cannot bypass it by accident.\n */\nexport function createHarvester(options: HarvesterOptions): Harvester {\n const { transport } = options.dependencies\n const now = options.dependencies.now ?? Date.now\n const newSessionId = options.dependencies.newSessionId ?? randomUUID\n const userAgent = options.dependencies.userAgent ?? defaultUserAgent\n const flights = new Map<string, Promise<HarvestOutcome>>()\n const throttles = new Map<string, Throttle>()\n\n function throttle(key: string): Throttle {\n const existing = throttles.get(key)\n if (existing !== undefined) {\n return existing\n }\n const created: Throttle = { nextAllowedAt: 0, backoffMs: 0, unsupported: false }\n throttles.set(key, created)\n\n return created\n }\n\n function succeed(key: string): void {\n const state = throttle(key)\n state.nextAllowedAt = now() + options.minIntervalMs\n state.backoffMs = 0\n }\n\n function fail(key: string): void {\n const state = throttle(key)\n state.backoffMs = Math.min(state.backoffMs === 0 ? FIRST_BACKOFF_MS : state.backoffMs * 2, MAX_BACKOFF_MS)\n state.nextAllowedAt = now() + state.backoffMs\n }\n\n async function run(request: HarvestRequest): Promise<HarvestOutcome> {\n const key = ticketKey(request.identity.accountId, request.model)\n let reply: ProbeReply\n try {\n reply = await transport({\n url: resolveCodexProbeUrl(request.identity.baseUrl),\n headers: buildProbeHeaders(request.identity, newSessionId(), userAgent()),\n body: buildProbeBody(request.model),\n timeoutMs: request.timeoutMs,\n proxyUrl: request.proxyUrl,\n signal: request.signal,\n })\n } catch (error) {\n fail(key)\n\n return { ok: false, reason: 'network', message: error instanceof Error ? error.message : String(error) }\n }\n\n const state = reply.headers[TURN_STATE_HEADER]\n if (reply.status !== 200) {\n fail(key)\n\n return { ok: false, reason: 'http-error', message: `probe answered HTTP ${reply.status}` }\n }\n if (state === undefined || state.trim().length === 0) {\n // Nothing to retry: this model simply does not mint a turn state.\n throttle(key).unsupported = true\n\n return { ok: false, reason: 'unsupported', message: 'the backend minted no turn state for this model' }\n }\n\n const ticket = mintTicket({\n accountId: request.identity.accountId,\n model: request.model,\n value: state,\n capturedAt: now(),\n source: 'probe',\n })\n if (ticket === undefined) {\n fail(key)\n\n return { ok: false, reason: 'degraded', message: `probe minted ${state.trim().length} chars, not 292` }\n }\n succeed(key)\n\n return { ok: true, ticket }\n }\n\n return {\n async probe(request) {\n const key = ticketKey(request.identity.accountId, request.model)\n const running = flights.get(key)\n if (running !== undefined) {\n return running\n }\n const state = throttle(key)\n if (state.unsupported) {\n return { ok: false, reason: 'unsupported', message: 'the backend minted no turn state for this model' }\n }\n if (request.force !== true && now() < state.nextAllowedAt) {\n const seconds = Math.ceil((state.nextAllowedAt - now()) / 1000)\n\n return { ok: false, reason: 'throttled', message: `next probe allowed in ${seconds}s` }\n }\n\n const flight = run(request)\n flights.set(key, flight)\n void flight.finally(() => flights.delete(key))\n\n return flight\n },\n canProbe(accountId, model) {\n const state = throttle(ticketKey(accountId, model))\n\n return !state.unsupported && now() >= state.nextAllowedAt\n },\n nextProbeAt(accountId, model) {\n const state = throttles.get(ticketKey(accountId, model))\n\n return state === undefined || state.nextAllowedAt === 0 ? undefined : state.nextAllowedAt\n },\n isRunning(accountId, model) {\n return flights.has(ticketKey(accountId, model))\n },\n }\n}\n","import type { Agent, IncomingHttpHeaders } from 'node:http'\nimport { request as httpRequest } from 'node:http'\nimport { request as httpsRequest } from 'node:https'\nimport { proxyUrlProblem } from './config.js'\n\nexport interface ProbeCall {\n url: string\n headers: Record<string, string>\n body: string\n timeoutMs: number\n proxyUrl: string\n signal?: AbortSignal | undefined\n}\n\nexport interface ProbeReply {\n status: number\n headers: Record<string, string>\n}\n\nexport type ProbeTransport = (call: ProbeCall) => Promise<ProbeReply>\n\nconst agents = new Map<string, Agent>()\n\nexport function normalizeHeaders(raw: IncomingHttpHeaders): Record<string, string> {\n const headers: Record<string, string> = {}\n for (const [name, value] of Object.entries(raw)) {\n if (typeof value === 'string') {\n headers[name.toLowerCase()] = value\n } else if (Array.isArray(value)) {\n headers[name.toLowerCase()] = value.join(', ')\n }\n }\n\n return headers\n}\n\n/**\n * Loaded on demand, so a session that configures no proxy never pays for the agent packages. The\n * scheme of the *target* picks the tunnel for http and https; a socks proxy serves both.\n */\nexport async function createProxyAgent(proxyUrl: string, targetUrl: string): Promise<Agent | undefined> {\n const url = proxyUrl.trim()\n if (url.length === 0) {\n return undefined\n }\n if (proxyUrlProblem(url) !== undefined) {\n throw new Error('probe proxy URL is unusable')\n }\n const secureTarget = new URL(targetUrl).protocol === 'https:'\n const cacheKey = `${secureTarget ? 'https' : 'http'} ${url}`\n const cached = agents.get(cacheKey)\n if (cached !== undefined) {\n return cached\n }\n\n let agent: Agent\n if (new URL(url).protocol.startsWith('socks')) {\n agent = new (await import('socks-proxy-agent')).SocksProxyAgent(url)\n } else if (secureTarget) {\n agent = new (await import('https-proxy-agent')).HttpsProxyAgent(url)\n } else {\n agent = new (await import('http-proxy-agent')).HttpProxyAgent(url)\n }\n agents.set(cacheKey, agent)\n\n return agent\n}\n\nasync function send(call: ProbeCall, agent: Agent | undefined): Promise<ProbeReply> {\n const request = new URL(call.url).protocol === 'https:' ? httpsRequest : httpRequest\n\n return new Promise<ProbeReply>((resolve, reject) => {\n let timer: ReturnType<typeof setTimeout> | undefined\n // `agent: false` gives this request a throwaway socket, so destroying the response below cannot\n // poison a pooled connection Pi would go on to use.\n const client = request(\n call.url,\n { agent: agent ?? false, headers: call.headers, method: 'POST', signal: call.signal },\n response => {\n clearTimeout(timer)\n const headers = normalizeHeaders(response.headers)\n // Only the headers carry the state; reading the event stream would spend the turn for nothing.\n response.destroy()\n resolve({ status: response.statusCode ?? 0, headers })\n },\n )\n timer = setTimeout(() => client.destroy(new Error(`probe timed out after ${call.timeoutMs}ms`)), call.timeoutMs)\n client.on('error', error => {\n clearTimeout(timer)\n reject(error)\n })\n client.end(call.body)\n })\n}\n\nexport function createProbeTransport(): ProbeTransport {\n return async call => send(call, await createProxyAgent(call.proxyUrl, call.url))\n}\n","import type { Ticket } from './state.js'\nimport { mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { getTicketStorePath } from './config.js'\nimport { isUsable, newerTicket, ticketKey } from './state.js'\n\nexport const STORE_VERSION = 1\n\ninterface StoreEnvelope {\n version: number\n tickets: Ticket[]\n}\n\nexport interface TicketFileSystem {\n readFile: (path: string) => string | undefined\n writeFile: (path: string, data: string) => void\n rename: (from: string, to: string) => void\n mkdir: (path: string) => void\n unlink: (path: string) => void\n}\n\nexport interface TicketStoreOptions {\n path?: string | undefined\n agentDir?: string | undefined\n fileSystem?: TicketFileSystem | undefined\n}\n\nconst defaultFileSystem: TicketFileSystem = {\n readFile(path) {\n try {\n return readFileSync(path, 'utf8')\n } catch (error) {\n if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {\n return undefined\n }\n throw error\n }\n },\n writeFile(path, data) {\n // A ticket is bearer material, so the file is owner-only from the moment it exists.\n writeFileSync(path, data, { encoding: 'utf8', mode: 0o600 })\n },\n rename(from, to) {\n renameSync(from, to)\n },\n mkdir(path) {\n mkdirSync(path, { recursive: true, mode: 0o700 })\n },\n unlink(path) {\n unlinkSync(path)\n },\n}\n\nfunction isTicket(value: unknown): value is Ticket {\n if (typeof value !== 'object' || value === null) {\n return false\n }\n const candidate = value as Partial<Ticket>\n\n return (\n typeof candidate.accountId === 'string' &&\n typeof candidate.model === 'string' &&\n typeof candidate.value === 'string' &&\n typeof candidate.capturedAt === 'number' &&\n (candidate.source === 'probe' || candidate.source === 'response')\n )\n}\n\n/**\n * Shared across concurrent Pi sessions: the hour-long lifetime and the real cost of a probe make one\n * mint per window worth persisting, and a session that starts cold reads a sibling's ticket instead\n * of buying its own.\n */\nexport class TicketStore {\n private readonly filePath: string\n private readonly fileSystem: TicketFileSystem\n\n constructor(options: TicketStoreOptions = {}) {\n this.filePath = options.path ?? getTicketStorePath(options.agentDir)\n this.fileSystem = options.fileSystem ?? defaultFileSystem\n }\n\n path(): string {\n return this.filePath\n }\n\n read(accountId: string, model: string, now: number): Ticket | undefined {\n const key = ticketKey(accountId, model)\n\n return this.all(now).find(ticket => ticketKey(ticket.accountId, ticket.model) === key)\n }\n\n /** Re-reads first, so another session's fresher record wins and other keys survive the write. */\n write(ticket: Ticket): void {\n const key = ticketKey(ticket.accountId, ticket.model)\n const existing = this.all(ticket.capturedAt)\n const current = existing.find(candidate => ticketKey(candidate.accountId, candidate.model) === key)\n const kept = existing.filter(candidate => ticketKey(candidate.accountId, candidate.model) !== key)\n kept.push(newerTicket(current, ticket) ?? ticket)\n this.persist(kept)\n }\n\n forget(accountId: string, model: string, now: number): void {\n const key = ticketKey(accountId, model)\n this.persist(this.all(now).filter(ticket => ticketKey(ticket.accountId, ticket.model) !== key))\n }\n\n /**\n * A corrupt or foreign-version file reads as empty and is left on disk: the next write replaces\n * it, and destroying a copy another session may still read is not an improvement.\n */\n private all(now: number): Ticket[] {\n let raw: string | undefined\n try {\n raw = this.fileSystem.readFile(this.filePath)\n } catch {\n return []\n }\n if (raw === undefined) {\n return []\n }\n\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch {\n return []\n }\n if (typeof parsed !== 'object' || parsed === null) {\n return []\n }\n\n const envelope = parsed as Partial<StoreEnvelope>\n if (envelope.version !== STORE_VERSION || !Array.isArray(envelope.tickets)) {\n return []\n }\n\n return envelope.tickets.filter(ticket => isTicket(ticket) && isUsable(ticket, now))\n }\n\n private persist(tickets: Ticket[]): void {\n const envelope: StoreEnvelope = { version: STORE_VERSION, tickets }\n const temporary = `${this.filePath}.tmp`\n try {\n this.fileSystem.mkdir(dirname(this.filePath))\n this.fileSystem.writeFile(temporary, `${JSON.stringify(envelope)}\\n`)\n this.fileSystem.rename(temporary, this.filePath)\n } catch (error) {\n try {\n this.fileSystem.unlink(temporary)\n } catch {\n // The write error is the actionable one; a failed cleanup is not.\n }\n throw error\n }\n }\n}\n","import type { EnhancerConfig, EnhancerConfigPaths, LoadConfigResult, PiTransport } from './config.js'\nimport type { Harvester, HarvestOutcome, ProbeIdentity } from './harvest.js'\nimport type { StatusKind } from './render.js'\nimport type { Ticket } from './state.js'\nimport type { TicketStore } from './store.js'\nimport type { Api, Model, ProviderHeaders } from '@earendil-works/pi-ai'\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent'\nimport { registerEnhancerCommand } from './command.js'\nimport {\n DEFAULT_CONFIG,\n defaultEnhancerAgentDir,\n EXTENSION_ID,\n getEnhancerConfigPaths,\n getTicketStorePath,\n loadEnhancerConfig,\n readPiTransport,\n} from './config.js'\nimport { createHarvester, extractAccountId } from './harvest.js'\nimport { createProbeTransport } from './probe.js'\nimport { renderAlert, renderStatus } from './render.js'\nimport {\n classifyTurnState,\n isUsable,\n mintTicket,\n needsRefresh,\n newerTicket,\n TICKET_TTL_MS,\n ticketAgeMs,\n TURN_STATE_HEADER,\n} from './state.js'\nimport { TicketStore as DefaultTicketStore } from './store.js'\n\n/** Pi refreshes the token itself, so re-reading the credential every request buys nothing. */\nconst AUTH_CACHE_MS = 60_000\nconst CODEX_API = 'openai-codex-responses'\n\nexport interface EnhancerExtensionDependencies {\n loadConfig?: ((cwd: string, agentDir: string) => LoadConfigResult) | undefined\n readTransport?: ((cwd: string, agentDir: string) => PiTransport) | undefined\n createStore?: ((agentDir: string) => TicketStore) | undefined\n createHarvesterFor?: ((config: EnhancerConfig) => Harvester) | undefined\n agentDir?: string | undefined\n now?: (() => number) | undefined\n warn?: ((message: string) => void) | undefined\n}\n\nfunction defaultWarn(message: string): void {\n console.warn(`[${EXTENSION_ID}] ${message}`)\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction readHeader(headers: Record<string, string>, name: string): string | undefined {\n const match = Object.entries(headers).find(([key]) => key.toLowerCase() === name)\n\n return match?.[1]\n}\n\n/** `ProviderHeaders` can delete with `null`; the probe only sends what is actually set. */\nfunction plainHeaders(...sources: (Record<string, string> | ProviderHeaders | undefined)[]): Record<string, string> {\n const headers: Record<string, string> = {}\n for (const source of sources) {\n for (const [name, value] of Object.entries(source ?? {})) {\n if (typeof value === 'string') {\n headers[name] = value\n }\n }\n }\n\n return headers\n}\n\nexport function createEnhancerExtension(pi: ExtensionAPI, dependencies: EnhancerExtensionDependencies = {}): void {\n const warn = dependencies.warn ?? defaultWarn\n const now = dependencies.now ?? Date.now\n const agentDir = dependencies.agentDir ?? defaultEnhancerAgentDir()\n const loadConfig = dependencies.loadConfig ?? ((cwd, dir) => loadEnhancerConfig({ cwd, agentDir: dir }))\n const readTransport = dependencies.readTransport ?? ((cwd, dir) => readPiTransport({ cwd, agentDir: dir }))\n const createStore = dependencies.createStore ?? (dir => new DefaultTicketStore({ agentDir: dir }))\n const createHarvesterFor =\n dependencies.createHarvesterFor ??\n (settings =>\n createHarvester({\n minIntervalMs: settings.minProbeIntervalMs,\n dependencies: { transport: createProbeTransport(), now },\n }))\n\n let config: EnhancerConfig = DEFAULT_CONFIG\n let paths: EnhancerConfigPaths | undefined\n let transport: PiTransport = 'auto'\n let store: TicketStore | undefined\n let harvester: Harvester | undefined\n let ticket: Ticket | undefined\n let identity: ProbeIdentity | undefined\n let identityKey = ''\n let identityAt = 0\n let kind: StatusKind = 'missing'\n let lastOutcome: string | undefined\n let nextProbe: number | undefined\n const notified = new Set<string>()\n\n function reset(): void {\n ticket = undefined\n identity = undefined\n identityKey = ''\n identityAt = 0\n kind = 'missing'\n lastOutcome = undefined\n nextProbe = undefined\n notified.clear()\n }\n\n function watches(model: Model<Api> | undefined): model is Model<Api> {\n return (\n model !== undefined &&\n model.api === CODEX_API &&\n (config.providers.length === 0 || config.providers.includes(model.provider))\n )\n }\n\n function announce(context: ExtensionContext, key: string, message: string): void {\n if (!config.notify || notified.has(key)) {\n return\n }\n notified.add(key)\n context.ui.notify(message, 'warning')\n }\n\n function paint(context: ExtensionContext): void {\n const remaining = ticket === undefined ? undefined : TICKET_TTL_MS - ticketAgeMs(ticket, now())\n context.ui.setStatus(\n EXTENSION_ID,\n renderStatus(kind === 'good' ? { kind, remainingMs: remaining } : { kind }, context.ui.theme),\n )\n }\n\n async function resolveIdentity(context: ExtensionContext, model: Model<Api>): Promise<ProbeIdentity | undefined> {\n const key = `${model.provider} ${model.id}`\n if (identity !== undefined && identityKey === key && now() - identityAt < AUTH_CACHE_MS) {\n return identity\n }\n\n const resolved = await context.modelRegistry.getApiKeyAndHeaders(model)\n if (!resolved.ok || resolved.apiKey === undefined) {\n return undefined\n }\n const accountId = extractAccountId(resolved.apiKey)\n if (accountId === undefined) {\n return undefined\n }\n identity = {\n accountId,\n token: resolved.apiKey,\n baseUrl: resolved.baseUrl ?? model.baseUrl,\n headers: plainHeaders(model.headers, resolved.headers),\n }\n identityKey = key\n identityAt = now()\n\n return identity\n }\n\n function absorb(context: ExtensionContext, outcome: HarvestOutcome): void {\n if (outcome.ok) {\n ticket = outcome.ticket\n kind = 'good'\n lastOutcome = 'minted a 292-char state'\n try {\n store?.write(outcome.ticket)\n } catch (error) {\n warn(`could not persist the turn state: ${describeError(error)}`)\n }\n\n return\n }\n\n lastOutcome = outcome.message\n if (outcome.reason === 'unsupported') {\n kind = 'unsupported'\n\n return\n }\n if (outcome.reason === 'throttled') {\n return\n }\n kind = outcome.reason === 'degraded' ? 'degraded' : 'missing'\n announce(context, `probe:${outcome.reason}`, renderAlert('unreachable', outcome.message))\n }\n\n async function mint(context: ExtensionContext, model: string, force: boolean): Promise<void> {\n if (harvester === undefined) {\n return\n }\n const current = identity\n if (current === undefined) {\n return\n }\n if (!force && !harvester.canProbe(current.accountId, model)) {\n return\n }\n kind = 'minting'\n paint(context)\n absorb(\n context,\n await harvester.probe({\n identity: current,\n model,\n timeoutMs: config.probeTimeoutMs,\n proxyUrl: config.probeProxyUrl,\n signal: context.signal,\n force,\n }),\n )\n nextProbe = harvester.nextProbeAt(current.accountId, model)\n }\n\n pi.on('session_start', (_event, context) => {\n reset()\n paths = getEnhancerConfigPaths(context.cwd, agentDir)\n\n const loaded = loadConfig(context.cwd, agentDir)\n for (const issue of loaded.issues) {\n warn(`config issue at ${issue.sourcePath}: ${issue.message}`)\n }\n config = loaded.config ?? DEFAULT_CONFIG\n transport = readTransport(context.cwd, agentDir)\n store = createStore(agentDir)\n harvester = createHarvesterFor(config)\n context.ui.setStatus(EXTENSION_ID, undefined)\n\n if (config.enabled && transport !== 'sse') {\n announce(context, 'transport', renderAlert('transport', transport))\n }\n })\n\n pi.on('before_provider_headers', async (event, context) => {\n const model = context.model\n if (!config.enabled || !watches(model)) {\n return\n }\n\n try {\n const resolved = await resolveIdentity(context, model)\n if (resolved === undefined) {\n return\n }\n if (ticket !== undefined && (ticket.accountId !== resolved.accountId || ticket.model !== model.id)) {\n // A state minted for another account or model is exactly the contradictory signal to avoid.\n ticket = undefined\n }\n ticket = newerTicket(ticket, store?.read(resolved.accountId, model.id, now()))\n\n if (ticket !== undefined && isUsable(ticket, now())) {\n event.headers[TURN_STATE_HEADER] = ticket.value\n kind = 'good'\n paint(context)\n // The hour is nearly up: keep this request on the state we hold and replace it out of band.\n if (needsRefresh(ticket, now())) {\n void mint(context, model.id, false).catch(error => warn(`background mint failed: ${describeError(error)}`))\n }\n\n return\n }\n\n await mint(context, model.id, false)\n if (ticket !== undefined && isUsable(ticket, now())) {\n event.headers[TURN_STATE_HEADER] = ticket.value\n }\n paint(context)\n } catch (error) {\n warn(`could not attach a turn state: ${describeError(error)}`)\n }\n })\n\n pi.on('after_provider_response', (event, context) => {\n const model = context.model\n if (!config.enabled || !watches(model)) {\n return\n }\n const seen = readHeader(event.headers, TURN_STATE_HEADER)\n const verdict = classifyTurnState(seen)\n if (verdict === 'absent') {\n return\n }\n\n if (verdict === 'degraded') {\n ticket = undefined\n kind = 'degraded'\n lastOutcome = `response carried ${seen?.trim().length ?? 0} chars, not 292`\n announce(context, `degraded:${seen?.trim().length ?? 0}`, renderAlert('degraded', lastOutcome))\n paint(context)\n\n return\n }\n\n // A real response mints the same state a probe would, so an SSE session pays for at most one.\n const accountId = identity?.accountId\n if (accountId === undefined) {\n return\n }\n const captured = mintTicket({ accountId, model: model.id, value: seen, capturedAt: now(), source: 'response' })\n if (captured === undefined) {\n return\n }\n ticket = captured\n kind = 'good'\n try {\n store?.write(captured)\n } catch (error) {\n warn(`could not persist the turn state: ${describeError(error)}`)\n }\n paint(context)\n })\n\n pi.on('model_select', (_event, context) => {\n if (!config.enabled || !watches(context.model)) {\n context.ui.setStatus(EXTENSION_ID, undefined)\n\n return\n }\n kind = 'missing'\n paint(context)\n })\n\n pi.on('session_shutdown', (_event, context) => {\n context.ui.setStatus(EXTENSION_ID, undefined)\n reset()\n })\n\n registerEnhancerCommand(pi, {\n getReport: () => ({\n config,\n paths,\n storePath: store?.path() ?? getTicketStorePath(agentDir),\n transport,\n ticket,\n now: now(),\n lastOutcome,\n nextProbeAt: nextProbe,\n }),\n refresh: async context => {\n const model = context.model\n if (!watches(model)) {\n return 'The selected model does not speak the Codex api, so there is no turn state to mint.'\n }\n const resolved = await resolveIdentity(context, model)\n if (resolved === undefined) {\n return 'Could not resolve a ChatGPT account from the stored credential.'\n }\n await mint(context, model.id, true)\n paint(context)\n\n return lastOutcome ?? 'Nothing to report.'\n },\n forget: context => {\n const accountId = identity?.accountId\n const model = ticket?.model ?? context.model?.id\n ticket = undefined\n kind = 'missing'\n if (accountId === undefined || model === undefined) {\n return 'No turn state was held.'\n }\n try {\n store?.forget(accountId, model, now())\n } catch (error) {\n return `Could not clear the stored turn state: ${describeError(error)}`\n }\n\n return 'Dropped the stored turn state; the next request mints a new one.'\n },\n })\n}\n","import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'\nimport { createEnhancerExtension } from './extension.js'\n\nexport default function codexEnhancerExtension(pi: ExtensionAPI): void {\n createEnhancerExtension(pi)\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,83 @@
1
+ {
2
+ "name": "@mzwing/pi-codex-enhancer",
3
+ "version": "0.1.0",
4
+ "description": "Keeps a valid 292-length x-codex-turn-state on every Pi Codex request",
5
+ "keywords": [
6
+ "codex",
7
+ "degradation",
8
+ "downgrade",
9
+ "pi",
10
+ "pi-coding-agent",
11
+ "pi-extension",
12
+ "pi-package",
13
+ "turn-state"
14
+ ],
15
+ "homepage": "https://github.com/mzwing/pi-packages/tree/master/packages/pi-codex-enhancer#readme",
16
+ "bugs": {
17
+ "url": "https://github.com/mzwing/pi-packages/issues"
18
+ },
19
+ "license": "MIT",
20
+ "author": {
21
+ "name": "mzwing",
22
+ "email": "mzwing@mzwing.eu.org"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/mzwing/pi-packages.git",
27
+ "directory": "packages/pi-codex-enhancer"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "schemas",
32
+ "config/config.example.json",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "type": "module",
37
+ "sideEffects": false,
38
+ "main": "./dist/index.js",
39
+ "types": "./dist/index.d.ts",
40
+ "exports": {
41
+ ".": {
42
+ "types": "./dist/index.d.ts",
43
+ "import": "./dist/index.js"
44
+ }
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "registry": "https://registry.npmjs.org/"
49
+ },
50
+ "dependencies": {
51
+ "http-proxy-agent": "9.1.0",
52
+ "https-proxy-agent": "9.1.0",
53
+ "socks-proxy-agent": "10.1.0",
54
+ "zod": "4.6.5"
55
+ },
56
+ "devDependencies": {
57
+ "@earendil-works/pi-ai": "0.85.1",
58
+ "@earendil-works/pi-coding-agent": "0.85.1",
59
+ "@types/node": "26.6.1",
60
+ "tsdown": "0.23.0",
61
+ "typescript": "7.0.2",
62
+ "vitest": "5.0.1"
63
+ },
64
+ "peerDependencies": {
65
+ "@earendil-works/pi-ai": "^0.84.2 || ^0.85.0",
66
+ "@earendil-works/pi-coding-agent": "^0.84.2 || ^0.85.0"
67
+ },
68
+ "engines": {
69
+ "node": ">=24"
70
+ },
71
+ "pi": {
72
+ "extensions": [
73
+ "./dist/index.js"
74
+ ]
75
+ },
76
+ "scripts": {
77
+ "build": "tsdown",
78
+ "typecheck": "tsc --noEmit",
79
+ "test": "vitest run",
80
+ "test:watch": "vitest",
81
+ "gen:schema": "node --experimental-strip-types scripts/generate-schema.ts"
82
+ }
83
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://raw.githubusercontent.com/mzwing/pi-packages/master/packages/pi-codex-enhancer/schemas/config.schema.json",
4
+ "type": "object",
5
+ "properties": {
6
+ "$schema": {
7
+ "type": "string",
8
+ "minLength": 1
9
+ },
10
+ "enabled": {
11
+ "default": true,
12
+ "type": "boolean"
13
+ },
14
+ "providers": {
15
+ "default": ["openai-codex"],
16
+ "type": "array",
17
+ "items": {
18
+ "type": "string",
19
+ "minLength": 1
20
+ }
21
+ },
22
+ "probeTimeoutMs": {
23
+ "default": 8000,
24
+ "type": "integer",
25
+ "minimum": 1000,
26
+ "maximum": 30000
27
+ },
28
+ "minProbeIntervalMs": {
29
+ "default": 60000,
30
+ "type": "integer",
31
+ "minimum": 10000,
32
+ "maximum": 3600000
33
+ },
34
+ "probeProxyUrl": {
35
+ "default": "",
36
+ "type": "string"
37
+ },
38
+ "notify": {
39
+ "default": true,
40
+ "type": "boolean"
41
+ }
42
+ },
43
+ "additionalProperties": false
44
+ }