@narumitw/pi-codex-compact 0.52.0 → 0.53.1

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/README.md CHANGED
@@ -8,7 +8,7 @@ Pi still decides when compaction runs and keeps its normal `/compact`, threshold
8
8
 
9
9
  ## ✨ Features
10
10
 
11
- - Supports `openai-codex-responses`, `openai-responses`, and `azure-openai-responses`.
11
+ - Supports `openai-codex-responses`, `openai-responses`, and `azure-openai-responses`, plus explicitly profiled custom APIs.
12
12
  - Uses Remote V2 or same-origin unary `responses/compact` through the active provider and credentials.
13
13
  - Handles manual, threshold, and overflow compaction through Pi's existing lifecycle.
14
14
  - Validates and persists one bounded opaque checkpoint that survives compatible reloads, resumes, and forks.
@@ -49,36 +49,24 @@ Review third-party extension source before installing it.
49
49
 
50
50
  1. Sign in through Pi's built-in OpenAI, OpenAI Codex, or Azure OpenAI provider, or configure a compatible custom provider.
51
51
  2. Select a model using `openai-codex-responses`, `openai-responses`, or `azure-openai-responses`.
52
- 3. Work normally.
53
- Pi's automatic compaction and built-in `/compact` continue to operate.
54
- 4. Run `/codex-compact` to inspect the effective route or choose **Compact now**.
55
- 5. After compaction, continue the session normally; compatible requests replay the opaque checkpoint.
52
+ 3. Work normally; Pi's automatic compaction and built-in `/compact` continue to operate.
53
+
54
+ Run `/codex-compact` when you want to inspect the effective route or choose **Compact now**.
55
+ After compaction, compatible requests replay the opaque checkpoint automatically.
56
56
 
57
57
  With the default `auto` protocol, Codex Responses uses Remote V2 while OpenAI and Azure OpenAI Responses use unary `responses/compact`.
58
58
  When the active model uses another API, compaction remains entirely Pi-native.
59
+ To opt in a compatible custom API, add it to `apiProfiles` as described below.
59
60
 
60
61
  ## 💬 Commands
61
62
 
62
- ```text
63
- /codex-compact
64
- ```
63
+ Run `/codex-compact` to inspect the effective compaction path, change settings, or request manual compaction in TUI mode.
64
+ It accepts no arguments.
65
+ RPC reports the manual settings path without compacting; print and JSON modes reject the command.
66
+ Closing the menu with Escape or Ctrl+C does not compact the session.
65
67
 
66
- In TUI mode, the menu shows whether remote compaction is enabled, the protocol setting, the active model, and whether manual compaction will use **Responses Remote V2**, **Responses Compact API**, or **Pi native**.
67
- It contains:
68
-
69
- ```text
70
- Compact now
71
- Settings
72
- Close
73
- ```
74
-
75
- **Compact now** closes the menu before asking Pi to compact the active session.
76
- Escape or Ctrl+C closes without compacting, and an obsolete menu cannot trigger work after session replacement or shutdown.
77
- **Settings** opens the bounded settings editor.
78
- In RPC mode, the command reports the manual settings path instead of opening custom UI or compacting.
79
- Print and JSON modes reject the command explicitly because they cannot open the menu or safely replace their normal output with an interactive result.
80
-
81
- Pi's built-in `/compact` remains available and follows the same extension hook for all three supported Responses APIs.
68
+ Manual compaction uses **Responses Remote V2**, **Responses Compact API**, or **Pi native**, as described in [Settings](#-settings).
69
+ Pi's built-in `/compact` remains available and follows the same extension hook.
82
70
 
83
71
  ## ⚙️ Settings
84
72
 
@@ -95,6 +83,9 @@ There is no environment-variable or project-level override.
95
83
  {
96
84
  "enabled": true,
97
85
  "protocol": "auto",
86
+ "apiProfiles": {
87
+ "custom-responses": "codex-responses-v1"
88
+ },
98
89
  "requestTimeoutMs": 300000,
99
90
  "maxRetries": 2,
100
91
  "replacementTokenBudget": 64000,
@@ -106,6 +97,7 @@ There is no environment-variable or project-level override.
106
97
  | --- | ---: | --- | --- | --- |
107
98
  | `enabled` | `true` | Boolean | Attempt a supported remote compaction route. | Keep enabled unless diagnosing provider behavior. |
108
99
  | `protocol` | `"auto"` | `"auto"`, `"remote-v2"`, or `"responses-compact"` | Select by API or force one remote protocol. | Keep `auto`; force a route only when diagnosing a compatible backend. |
100
+ | `apiProfiles` | `{}` | Object mapping a custom API id to `"codex-responses-v1"` | Explicitly opts a non-built-in API into Codex-compatible request fields and replay. | Leave empty unless the provider documents Codex Responses compatibility. |
109
101
  | `requestTimeoutMs` | `300000` | Integer from 30,000 to 600,000 ms | Bound one extension-owned remote request. | Keep five minutes; increase only for a consistently slow connection. |
110
102
  | `maxRetries` | `2` | Integer from 0 to 2 | Retry transient provider transport failures before Pi fallback. | Keep two; use zero when diagnosing the first failure. |
111
103
  | `replacementTokenBudget` | `64000` | Integer from 8,000 to 128,000 tokens | Bound approximate retained user-message text beside the opaque item. | Keep 64K; lower it to reduce session size or raise it only when recent user context is being lost. |
@@ -135,7 +127,7 @@ This extension does **not** read `~/.codex/config.toml`.
135
127
  ## ✅ Requirements and compatibility
136
128
 
137
129
  - Pi APIs compatible with the package's declared peer dependencies.
138
- - One of `openai-codex-responses`, `openai-responses`, or `azure-openai-responses` on the active model.
130
+ - One of `openai-codex-responses`, `openai-responses`, or `azure-openai-responses` on the active model, or an explicit `apiProfiles` entry for a custom Codex-compatible API.
139
131
  - A provider whose HTTP Responses adapter honors Pi's public `onPayload` and injected `fetch` options.
140
132
  - A backend supporting the selected protocol and opaque `compaction` replay.
141
133
  - Working credentials and any required compaction entitlement.
@@ -148,10 +140,12 @@ This extension does **not** read `~/.codex/config.toml`.
148
140
 
149
141
  The installed built-in adapters are covered by deterministic transport tests.
150
142
  A custom provider or proxy is eligible when its model explicitly uses one of these APIs, but its backend still owns compatible routing, authentication, request transforms, and opaque replay.
143
+ For a custom API label, eligibility is opt-in: `apiProfiles` must map that exact non-built-in label to `"codex-responses-v1"`. The default is empty, and an unconfigured custom API remains Pi-native.
144
+ `codex-responses-v1` uses Remote V2 in `auto` mode. With `responses-compact` explicitly selected, it retains the Codex Compact request fields, including tools and reasoning. The provider must honor Pi's public `transport: "sse"`, `onPayload`, and injected `fetch` options, and its backend must support the selected compaction protocol and opaque item replay. The profile declares compatibility; it does not probe the backend.
151
145
  The extension does not send a separate capability probe or automatically retry a failed billable request through the other protocol.
152
146
  A failed remote attempt falls back to Pi native, but an ordinary request cannot transparently recover after an incompatible provider has already received an existing opaque checkpoint.
153
147
  Provider provenance is stored for diagnosis but is not a replay gate.
154
- Switching providers can replay a checkpoint only when the API label and exact model ID still match; switching APIs or model IDs leaves Pi's visible fallback marker plus retained recent messages in context.
148
+ Switching providers can replay a checkpoint only when the API label, compatibility profile, and exact model ID still match. Custom API checkpoints also require a current matching `apiProfiles` entry; removing or changing it leaves Pi's visible fallback marker plus retained recent messages in context.
155
149
 
156
150
  ## 🔄 How it works
157
151
 
@@ -160,11 +154,12 @@ Switching providers can replay a checkpoint only when the API label and exact mo
160
154
  3. Remote V2 projects that checkpoint into a normal Responses SSE request and appends exactly one final `compaction_trigger`.
161
155
  4. Unary compact captures the provider-built payload and authentication, rewrites only the same-origin `/responses` path to `/responses/compact`, and requests JSON without making a normal inference call.
162
156
  5. It requires one bounded non-empty opaque `compaction` item; unary output may precede it only with user-role retained messages.
163
- 6. It constructs bounded replacement history and stores it with Pi suffix fingerprints in versioned `CompactionEntry.details`.
157
+ 6. It constructs bounded replacement history and stores it with Pi suffix fingerprints, the real API label, and the resolved compatibility profile in versioned `CompactionEntry.details`.
164
158
  7. On later compatible requests, it replaces an exactly validated marker with the persisted replacement history immediately before provider dispatch.
165
159
 
166
160
  The persisted entry remains the summary identity source, so replay does not depend on the wording generated by the currently installed extension version.
167
161
  If persisted summary identity, fingerprints, model identity, payload shape, or marker count do not match exactly, the extension leaves Pi's visible fallback context unchanged instead of guessing.
162
+ Older v1 and v2 checkpoints are normalized to the current checkpoint format when read; their built-in API label determines the profile. They still replay only when the current route authorizes that API/profile/model combination.
168
163
 
169
164
  ## 🔒 Security and privacy
170
165
 
@@ -189,7 +184,7 @@ These hard byte ceilings are intentionally not configurable.
189
184
  ## 🚧 Limitations
190
185
 
191
186
  - Codex Remote V2 remains an undocumented hosted contract and can change independently of Pi or this package; keep backups of important sessions.
192
- - Full older history depends on this extension, the checkpoint API label, the exact checkpoint model ID, and a provider route whose backend accepts the opaque item.
187
+ - Full older history depends on this extension, the checkpoint API label and compatibility profile, the exact checkpoint model ID, and a provider route whose backend accepts the opaque item.
193
188
  Removing the extension exposes only the portability fallback marker and Pi-retained recent messages.
194
189
  - The package does not reproduce Codex core's context-window UUID/number lineage, previous-model compatibility fallback, exact pre-turn ordering, or exact mid-turn model-session ownership.
195
190
  - Pi's public `getAllTools()` metadata does not expose `constrainedSampling`; Remote V2 preserves active tool order, names, descriptions, and parameter schemas but cannot reproduce that optional field.
@@ -223,30 +218,23 @@ npm test
223
218
  npm run package:pack -- codex-compact
224
219
  ```
225
220
 
226
- See [`docs/implementation-notes/codex-compaction-mechanism.md`](../../docs/implementation-notes/codex-compaction-mechanism.md) for the underlying Codex mechanism research and the extension boundary.
221
+ See the [Codex compaction mechanism notes](https://github.com/narumiruna/pi-extensions/blob/main/docs/implementation-notes/codex-compaction-mechanism.md) for the underlying Codex mechanism research and the extension boundary.
227
222
 
228
223
  ## 🗂️ Package layout
229
224
 
230
225
  ```text
231
- src/index.ts Thin Pi entrypoint
232
- src/codex-compact.ts Pi lifecycle, command, provider projection, and fallback
233
- src/remote.ts Protocol dispatch
234
- src/remote-v2.ts Remote V2 provider stream and SSE inspection
235
- src/remote-compact.ts Unary Compact API bridge and usage normalization
236
- src/remote-shared.ts Shared provider completion handling
237
- src/remote-types.ts Protocol-neutral request and response types
238
- src/protocol.ts Bounded SSE/JSON parsing and payload/output validation
239
- src/checkpoint.ts Replacement history, fingerprints, persistence, and replay projection
240
- src/model-api.ts Responses API and protocol route selection
241
- src/settings.ts Global settings validation and atomic persistence
242
- src/settings-menu.ts First-use manual compaction and settings TUI
243
-
244
- dist/ Generated source-mapped Jiti runtime and lazy menu chunk
245
- scripts/ Runtime builder
246
- benchmark/ Three-arm compaction benchmark, self-test, and methodology
247
- test/ Protocol, checkpoint, lifecycle, remote, settings, menu, and builder coverage
226
+ packages/pi-codex-compact/
227
+ ├── src/ # Authoritative implementation and helpers
228
+ │ ├── index.ts # Thin Pi entrypoint
229
+ │ └── codex-compact.ts # Compaction routing, replay, and fallback
230
+ ├── dist/ # Generated Jiti runtime
231
+ ├── scripts/build-runtime.mjs # Runtime builder
232
+ ├── benchmark/ # Repository-only benchmark and methodology
233
+ └── test/ # Behavior and lifecycle coverage
248
234
  ```
249
235
 
236
+ The generated runtime is built from `src/index.ts` and does not import back into `src`.
237
+
250
238
  ## 🔎 Keywords
251
239
 
252
240
  Pi extension, Pi coding agent, OpenAI Codex, Azure OpenAI, custom provider, proxy, Remote Compaction V2, Responses Compact API, opaque checkpoint, Responses API, context compaction.
@@ -2,24 +2,20 @@
2
2
  // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
3
 
4
4
  // src/model-api.ts
5
- import { hasApi } from "@earendil-works/pi-ai";
6
- var RESPONSES_COMPACTION_APIS = [
7
- "openai-codex-responses",
8
- "openai-responses",
9
- "azure-openai-responses"
10
- ];
11
- function usesResponsesCompactionApi(model) {
12
- return model !== void 0 && RESPONSES_COMPACTION_APIS.some((api) => hasApi(model, api));
5
+ function resolveResponsesCompactionProfile(api, apiProfiles = {}) {
6
+ if (api === "openai-codex-responses") return "codex-responses-v1";
7
+ if (api === "openai-responses" || api === "azure-openai-responses") return "openai-responses-v1";
8
+ return api && Object.hasOwn(apiProfiles, api) && apiProfiles[api] === "codex-responses-v1" ? "codex-responses-v1" : void 0;
13
9
  }
14
10
  function resolveCompactionRouteForApi(api, options) {
15
11
  if (!options.enabled) return { kind: "native", reason: "remote compaction is disabled" };
16
12
  if (!api) return { kind: "native", reason: "no active model" };
17
- if (!RESPONSES_COMPACTION_APIS.includes(api)) {
13
+ const profile = resolveResponsesCompactionProfile(api, options.apiProfiles);
14
+ if (!profile) {
18
15
  return { kind: "native", reason: `API ${api} does not support Responses compaction` };
19
16
  }
20
- const supportedApi = api;
21
- const protocol = options.protocol === "auto" ? supportedApi === "openai-codex-responses" ? "remote-v2" : "responses-compact" : options.protocol;
22
- return { kind: "remote", protocol, api: supportedApi };
17
+ const protocol = options.protocol === "auto" ? profile === "codex-responses-v1" ? "remote-v2" : "responses-compact" : options.protocol;
18
+ return { kind: "remote", protocol, api, profile };
23
19
  }
24
20
  function resolveCompactionRoute(model, options) {
25
21
  return resolveCompactionRouteForApi(model?.api, options);
@@ -34,10 +30,8 @@ function terminalText(value) {
34
30
  }
35
31
 
36
32
  export {
37
- RESPONSES_COMPACTION_APIS,
38
- usesResponsesCompactionApi,
39
33
  resolveCompactionRouteForApi,
40
34
  resolveCompactionRoute,
41
35
  terminalText
42
36
  };
43
- //# sourceMappingURL=chunk-JYXZMCXD.js.map
37
+ //# sourceMappingURL=chunk-7H7JJ7ZV.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/model-api.ts", "../../src/terminal.ts"],
4
+ "sourcesContent": ["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport type { CodexCompactSettings } from \"./settings.js\";\n\nexport const RESPONSES_COMPACTION_APIS = [\n \"openai-codex-responses\",\n \"openai-responses\",\n \"azure-openai-responses\",\n] as const;\n\nexport type BuiltInResponsesCompactionApi = (typeof RESPONSES_COMPACTION_APIS)[number];\nexport type ResponsesCompactionApi = BuiltInResponsesCompactionApi;\nexport type ResponsesCompactionProfile = \"codex-responses-v1\" | \"openai-responses-v1\";\nexport type RemoteCompactionProtocol = \"remote-v2\" | \"responses-compact\";\nexport type RemoteCompactionProtocolSetting = \"auto\" | RemoteCompactionProtocol;\n\nexport type CompactionRoute =\n | { kind: \"remote\"; protocol: RemoteCompactionProtocol; api: Api; profile: ResponsesCompactionProfile }\n | { kind: \"native\"; reason: string };\n\nfunction resolveResponsesCompactionProfile(\n api: Api | undefined,\n apiProfiles: Readonly<Record<string, \"codex-responses-v1\">> = {},\n): ResponsesCompactionProfile | undefined {\n if (api === \"openai-codex-responses\") return \"codex-responses-v1\";\n if (api === \"openai-responses\" || api === \"azure-openai-responses\") return \"openai-responses-v1\";\n return api && Object.hasOwn(apiProfiles, api) && apiProfiles[api] === \"codex-responses-v1\"\n ? \"codex-responses-v1\"\n : undefined;\n}\n\nexport function resolveCompactionRouteForApi(\n api: Api | undefined,\n options: Pick<CodexCompactSettings, \"enabled\" | \"protocol\"> & {\n apiProfiles?: Readonly<Record<string, \"codex-responses-v1\">>;\n },\n): CompactionRoute {\n if (!options.enabled) return { kind: \"native\", reason: \"remote compaction is disabled\" };\n if (!api) return { kind: \"native\", reason: \"no active model\" };\n const profile = resolveResponsesCompactionProfile(api, options.apiProfiles);\n if (!profile) {\n return { kind: \"native\", reason: `API ${api} does not support Responses compaction` };\n }\n const protocol =\n options.protocol === \"auto\"\n ? profile === \"codex-responses-v1\"\n ? \"remote-v2\"\n : \"responses-compact\"\n : options.protocol;\n return { kind: \"remote\", protocol, api, profile };\n}\n\nexport function resolveCompactionRoute(\n model: Model<Api> | undefined,\n options: Pick<CodexCompactSettings, \"enabled\" | \"protocol\"> & {\n apiProfiles?: Readonly<Record<string, \"codex-responses-v1\">>;\n },\n): CompactionRoute {\n return resolveCompactionRouteForApi(model?.api, options);\n}\n", "export function terminalText(value: string): string {\n return Array.from(value, (character) => {\n const codePoint = character.codePointAt(0) ?? 0;\n return codePoint < 32 || (codePoint >= 127 && codePoint <= 159) ? \" \" : character;\n }).join(\"\");\n}\n"],
5
+ "mappings": ";;;;AAmBA,SAAS,kCACP,KACA,cAA8D,CAAC,GACvB;AACxC,MAAI,QAAQ,yBAA0B,QAAO;AAC7C,MAAI,QAAQ,sBAAsB,QAAQ,yBAA0B,QAAO;AAC3E,SAAO,OAAO,OAAO,OAAO,aAAa,GAAG,KAAK,YAAY,GAAG,MAAM,uBAClE,uBACA;AACN;AAEO,SAAS,6BACd,KACA,SAGiB;AACjB,MAAI,CAAC,QAAQ,QAAS,QAAO,EAAE,MAAM,UAAU,QAAQ,gCAAgC;AACvF,MAAI,CAAC,IAAK,QAAO,EAAE,MAAM,UAAU,QAAQ,kBAAkB;AAC7D,QAAM,UAAU,kCAAkC,KAAK,QAAQ,WAAW;AAC1E,MAAI,CAAC,SAAS;AACZ,WAAO,EAAE,MAAM,UAAU,QAAQ,OAAO,GAAG,yCAAyC;AAAA,EACtF;AACA,QAAM,WACJ,QAAQ,aAAa,SACjB,YAAY,uBACV,cACA,sBACF,QAAQ;AACd,SAAO,EAAE,MAAM,UAAU,UAAU,KAAK,QAAQ;AAClD;AAEO,SAAS,uBACd,OACA,SAGiB;AACjB,SAAO,6BAA6B,OAAO,KAAK,OAAO;AACzD;;;AC1DO,SAAS,aAAa,OAAuB;AAClD,SAAO,MAAM,KAAK,OAAO,CAAC,cAAc;AACtC,UAAM,YAAY,UAAU,YAAY,CAAC,KAAK;AAC9C,WAAO,YAAY,MAAO,aAAa,OAAO,aAAa,MAAO,MAAM;AAAA,EAC1E,CAAC,EAAE,KAAK,EAAE;AACZ;",
6
+ "names": []
7
+ }
@@ -3,9 +3,10 @@
3
3
  import {
4
4
  resolveCompactionRouteForApi,
5
5
  terminalText
6
- } from "./chunk-JYXZMCXD.js";
6
+ } from "./chunk-7H7JJ7ZV.js";
7
7
 
8
8
  // src/settings-menu.ts
9
+ import { runMenu } from "@narumitw/pi-tui-kit";
9
10
  function timeoutLabel(milliseconds) {
10
11
  return `${milliseconds / 6e4} min`;
11
12
  }
@@ -147,19 +148,9 @@ function createCodexCompactMenu(runtime, options = {}) {
147
148
  },
148
149
  signal
149
150
  ),
150
- "set-timeout": ({ ctx, value, signal }) => update(
151
- runtime,
152
- ctx,
153
- { requestTimeoutMs: Number.parseInt(value ?? "5", 10) * 6e4 },
154
- signal
155
- ),
151
+ "set-timeout": ({ ctx, value, signal }) => update(runtime, ctx, { requestTimeoutMs: Number.parseInt(value ?? "5", 10) * 6e4 }, signal),
156
152
  "set-retries": ({ ctx, value, signal }) => update(runtime, ctx, { maxRetries: Number.parseInt(value ?? "2", 10) }, signal),
157
- "set-retention": ({ ctx, value, signal }) => update(
158
- runtime,
159
- ctx,
160
- { replacementTokenBudget: Number.parseInt(value ?? "64", 10) * 1e3 },
161
- signal
162
- ),
153
+ "set-retention": ({ ctx, value, signal }) => update(runtime, ctx, { replacementTokenBudget: Number.parseInt(value ?? "64", 10) * 1e3 }, signal),
163
154
  "set-notify": ({ ctx, value, signal }) => update(runtime, ctx, { notifyOnFallback: value === "On" }, signal)
164
155
  }
165
156
  };
@@ -172,7 +163,6 @@ async function showCodexCompactMenu(runtime, ctx, owner) {
172
163
  if (ctx.mode !== "tui") {
173
164
  throw new Error("/codex-compact requires TUI or RPC UI support");
174
165
  }
175
- const { runMenu } = await import("@narumitw/pi-tui-kit");
176
166
  if (owner.signal.aborted || !owner.isCurrent()) return;
177
167
  let compactRequested = false;
178
168
  await runMenu(
@@ -215,4 +205,4 @@ export {
215
205
  createCodexCompactMenu,
216
206
  showCodexCompactMenu
217
207
  };
218
- //# sourceMappingURL=settings-menu-VYQ6ABCP.js.map
208
+ //# sourceMappingURL=settings-menu-OAWFW2PH.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/settings-menu.ts"],
4
+ "sourcesContent": ["import type { Api } from \"@earendil-works/pi-ai\";\nimport type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport { type MenuDefinition, runMenu } from \"@narumitw/pi-tui-kit\";\nimport { resolveCompactionRouteForApi } from \"./model-api.js\";\nimport type { CodexCompactSettings, CodexCompactSettingsRuntime, CodexCompactSettingsState } from \"./settings.js\";\nimport { terminalText as safeText } from \"./terminal.js\";\n\ntype Screen = \"main\" | \"settings\" | \"invalid\";\ntype Action =\n | \"compact-now\"\n | \"set-enabled\"\n | \"set-protocol\"\n | \"set-timeout\"\n | \"set-retries\"\n | \"set-retention\"\n | \"set-notify\";\n\nexport interface SettingsMenuOwner {\n signal: AbortSignal;\n isCurrent(): boolean;\n}\n\ninterface CompactMenuStatus {\n model: string;\n api?: Api;\n}\n\nfunction timeoutLabel(milliseconds: number): string {\n return `${milliseconds / 60_000} min`;\n}\n\nfunction retentionLabel(tokens: number): string {\n return `${tokens / 1000}K tokens`;\n}\n\nfunction protocolLabel(protocol: CodexCompactSettings[\"protocol\"]): string {\n switch (protocol) {\n case \"auto\":\n return \"Auto\";\n case \"remote-v2\":\n return \"Remote V2\";\n case \"responses-compact\":\n return \"Responses Compact\";\n }\n}\n\nasync function update(\n runtime: CodexCompactSettingsRuntime,\n ctx: ExtensionCommandContext,\n patch: Partial<CodexCompactSettings>,\n signal: AbortSignal,\n) {\n try {\n await runtime.update(patch, signal);\n if (signal.aborted) return { kind: \"rejected\" as const };\n ctx.ui.notify(\"Responses compaction settings saved.\", \"info\");\n return { kind: \"stay\" as const };\n } catch (error) {\n if (signal.aborted) return { kind: \"rejected\" as const };\n ctx.ui.notify(\n `Could not save pi-codex-compact.json: ${safeText(error instanceof Error ? error.message : String(error))}`,\n \"error\",\n );\n return { kind: \"rejected\" as const };\n }\n}\n\nexport function createCodexCompactMenu(\n runtime: CodexCompactSettingsRuntime,\n options: { onCompactRequested?: () => void; status?: CompactMenuStatus } = {},\n): MenuDefinition<CodexCompactSettingsState, Screen, Action, ExtensionCommandContext> {\n return {\n start: \"main\",\n screens: {\n main: ({ state }) => ({\n kind: \"actions\",\n title: \"Responses Compaction\",\n lines: [\n `Remote compaction: ${state.settings.enabled ? \"On\" : \"Off\"}`,\n `Protocol setting: ${protocolLabel(state.settings.protocol)}`,\n `Active model: ${safeText(options.status?.model ?? \"none\")}`,\n `Compact route: ${safeText(compactRoute(state, options.status))}`,\n ],\n items: [\n {\n id: \"compact-now\",\n label: \"Compact now\",\n description: \"Close this menu and compact the active session immediately.\",\n action: \"compact-now\",\n },\n state.kind === \"invalid\"\n ? {\n id: \"settings\",\n label: \"Settings\",\n description: \"Read-only until the invalid settings file is repaired.\",\n to: \"invalid\" as const,\n }\n : { id: \"settings\", label: \"Settings\", to: \"settings\" as const },\n { id: \"close\", label: \"Close\", close: true },\n ],\n hint: \"close\",\n }),\n settings: ({ state }) => ({\n kind: \"settings\",\n title: \"Responses Compaction Settings\",\n lines: [`User settings \u00B7 ${safeText(state.path)}`],\n items: [\n {\n id: \"enabled\",\n label: \"Remote compaction\",\n description: \"Use a supported Responses compaction protocol.\",\n currentValue: state.settings.enabled ? \"On\" : \"Off\",\n values: [\"On\", \"Off\"],\n action: \"set-enabled\",\n },\n {\n id: \"protocol\",\n label: \"Protocol\",\n description: \"Choose automatically or force one supported remote protocol.\",\n currentValue: protocolLabel(state.settings.protocol),\n values: [\"Auto\", \"Remote V2\", \"Responses Compact\"],\n action: \"set-protocol\",\n },\n {\n id: \"requestTimeoutMs\",\n label: \"Request timeout\",\n description: \"Maximum time for the extension-owned remote compaction request.\",\n currentValue: timeoutLabel(state.settings.requestTimeoutMs),\n values: [\"2 min\", \"5 min\", \"10 min\"],\n action: \"set-timeout\",\n },\n {\n id: \"maxRetries\",\n label: \"Transport retries\",\n description: \"Retry transient provider failures before falling back to Pi.\",\n currentValue: String(state.settings.maxRetries),\n values: [\"0\", \"1\", \"2\"],\n action: \"set-retries\",\n },\n {\n id: \"replacementTokenBudget\",\n label: \"Retained user history\",\n description: \"Approximate user-message budget kept beside the opaque checkpoint.\",\n currentValue: retentionLabel(state.settings.replacementTokenBudget),\n values: [\"32K tokens\", \"64K tokens\", \"96K tokens\", \"128K tokens\"],\n action: \"set-retention\",\n },\n {\n id: \"notifyOnFallback\",\n label: \"Fallback notifications\",\n description: \"Warn when remote compaction fails and Pi native takes over.\",\n currentValue: state.settings.notifyOnFallback ? \"On\" : \"Off\",\n values: [\"On\", \"Off\"],\n action: \"set-notify\",\n },\n ],\n }),\n invalid: ({ state }) => ({\n kind: \"detail\",\n title: \"Codex Compact Settings \u00B7 Read only\",\n lines: [\n `Invalid settings file: ${safeText(state.path)}`,\n `Issue: ${safeText(state.issue ?? \"unknown validation error\")}`,\n \"Built-in defaults are active. Repair the file and run /reload; it will not be overwritten.\",\n ],\n hint: \"back\",\n }),\n },\n actions: {\n \"compact-now\": async () => {\n options.onCompactRequested?.();\n return { kind: \"close\" };\n },\n \"set-enabled\": ({ ctx, value, signal }) => update(runtime, ctx, { enabled: value === \"On\" }, signal),\n \"set-protocol\": ({ ctx, value, signal }) =>\n update(\n runtime,\n ctx,\n {\n protocol:\n value === \"Remote V2\" ? \"remote-v2\" : value === \"Responses Compact\" ? \"responses-compact\" : \"auto\",\n },\n signal,\n ),\n \"set-timeout\": ({ ctx, value, signal }) =>\n update(runtime, ctx, { requestTimeoutMs: Number.parseInt(value ?? \"5\", 10) * 60_000 }, signal),\n \"set-retries\": ({ ctx, value, signal }) =>\n update(runtime, ctx, { maxRetries: Number.parseInt(value ?? \"2\", 10) }, signal),\n \"set-retention\": ({ ctx, value, signal }) =>\n update(runtime, ctx, { replacementTokenBudget: Number.parseInt(value ?? \"64\", 10) * 1000 }, signal),\n \"set-notify\": ({ ctx, value, signal }) => update(runtime, ctx, { notifyOnFallback: value === \"On\" }, signal),\n },\n };\n}\n\nexport async function showCodexCompactMenu(\n runtime: CodexCompactSettingsRuntime,\n ctx: ExtensionCommandContext,\n owner: SettingsMenuOwner,\n): Promise<void> {\n if (ctx.mode === \"rpc\" && ctx.hasUI) {\n ctx.ui.notify(`Edit Responses compaction settings at ${safeText(runtime.get().path)}.`, \"info\");\n return;\n }\n if (ctx.mode !== \"tui\") {\n throw new Error(\"/codex-compact requires TUI or RPC UI support\");\n }\n if (owner.signal.aborted || !owner.isCurrent()) return;\n let compactRequested = false;\n await runMenu(\n ctx,\n createCodexCompactMenu(runtime, {\n onCompactRequested: () => {\n compactRequested = true;\n },\n status: compactMenuStatus(ctx),\n }),\n {\n getState: () => runtime.get(),\n signal: owner.signal,\n isCurrent: owner.isCurrent,\n },\n );\n if (!compactRequested || owner.signal.aborted || !owner.isCurrent()) return;\n ctx.compact({\n onError: (error) => {\n if (!owner.signal.aborted && owner.isCurrent()) {\n ctx.ui.notify(`Compaction failed: ${safeText(error.message)}`, \"error\");\n }\n },\n });\n}\n\nexport function compactMenuStatus(ctx: ExtensionCommandContext): CompactMenuStatus {\n const model = ctx.model;\n return {\n model: model ? `${model.provider}/${model.id}` : \"none\",\n api: model?.api,\n };\n}\n\nfunction compactRoute(state: Readonly<CodexCompactSettingsState>, status: CompactMenuStatus | undefined): string {\n const route = resolveCompactionRouteForApi(status?.api, state.settings);\n if (route.kind === \"native\") return `Pi native (${route.reason})`;\n return route.protocol === \"remote-v2\" ? \"Responses Remote V2\" : \"Responses Compact API\";\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }