@narumitw/pi-codex-compact 0.51.3 → 0.53.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/src/settings.ts CHANGED
@@ -3,225 +3,277 @@ import { constants } from "node:fs";
3
3
  import { mkdir, open, rename, rm, writeFile } from "node:fs/promises";
4
4
  import { basename, dirname, join } from "node:path";
5
5
  import { getAgentDir } from "@earendil-works/pi-coding-agent";
6
+ import type { RemoteCompactionProtocolSetting } from "./model-api.js";
6
7
 
7
8
  export const CODEX_COMPACT_SETTINGS_FILE = "pi-codex-compact.json";
8
9
  export const MAX_SETTINGS_BYTES = 64 * 1024;
9
10
 
10
11
  export interface CodexCompactSettings {
11
- enabled: boolean;
12
- requestTimeoutMs: number;
13
- maxRetries: number;
14
- replacementTokenBudget: number;
15
- notifyOnFallback: boolean;
12
+ enabled: boolean;
13
+ protocol: RemoteCompactionProtocolSetting;
14
+ apiProfiles: Record<string, "codex-responses-v1">;
15
+ requestTimeoutMs: number;
16
+ maxRetries: number;
17
+ replacementTokenBudget: number;
18
+ notifyOnFallback: boolean;
16
19
  }
17
20
 
18
21
  export const DEFAULT_CODEX_COMPACT_SETTINGS: Readonly<CodexCompactSettings> = Object.freeze({
19
- enabled: true,
20
- requestTimeoutMs: 300_000,
21
- maxRetries: 2,
22
- replacementTokenBudget: 64_000,
23
- notifyOnFallback: true,
22
+ enabled: true,
23
+ protocol: "auto",
24
+ apiProfiles: {},
25
+ requestTimeoutMs: 300_000,
26
+ maxRetries: 2,
27
+ replacementTokenBudget: 64_000,
28
+ notifyOnFallback: true,
24
29
  });
25
30
 
26
31
  const LIMITS = Object.freeze({
27
- requestTimeoutMs: { minimum: 30_000, maximum: 600_000 },
28
- maxRetries: { minimum: 0, maximum: 2 },
29
- replacementTokenBudget: { minimum: 8_000, maximum: 128_000 },
32
+ requestTimeoutMs: { minimum: 30_000, maximum: 600_000 },
33
+ maxRetries: { minimum: 0, maximum: 2 },
34
+ replacementTokenBudget: { minimum: 8_000, maximum: 128_000 },
30
35
  });
31
36
 
37
+ const BUILT_IN_APIS = new Set([
38
+ "openai-completions",
39
+ "mistral-conversations",
40
+ "openai-codex-responses",
41
+ "openai-responses",
42
+ "azure-openai-responses",
43
+ "anthropic-messages",
44
+ "bedrock-converse-stream",
45
+ "google-generative-ai",
46
+ "google-vertex",
47
+ "pi-messages",
48
+ ]);
49
+ const MAX_API_PROFILE_ID_LENGTH = 256;
50
+
32
51
  export interface CodexCompactSettingsState {
33
- kind: "missing" | "loaded" | "invalid";
34
- path: string;
35
- settings: CodexCompactSettings;
36
- document?: Record<string, unknown>;
37
- issue?: string;
52
+ kind: "missing" | "loaded" | "invalid";
53
+ path: string;
54
+ settings: CodexCompactSettings;
55
+ document?: Record<string, unknown>;
56
+ issue?: string;
38
57
  }
39
58
 
40
59
  export interface CodexCompactSettingsRuntime {
41
- get(): Readonly<CodexCompactSettingsState>;
42
- reload(signal?: AbortSignal): Promise<Readonly<CodexCompactSettingsState>>;
43
- update(
44
- patch: Partial<CodexCompactSettings>,
45
- signal?: AbortSignal,
46
- ): Promise<Readonly<CodexCompactSettingsState>>;
47
- flush(): Promise<void>;
60
+ get(): Readonly<CodexCompactSettingsState>;
61
+ reload(signal?: AbortSignal): Promise<Readonly<CodexCompactSettingsState>>;
62
+ update(patch: Partial<CodexCompactSettings>, signal?: AbortSignal): Promise<Readonly<CodexCompactSettingsState>>;
63
+ flush(): Promise<void>;
48
64
  }
49
65
 
50
66
  function isRecord(value: unknown): value is Record<string, unknown> {
51
- return typeof value === "object" && value !== null && !Array.isArray(value);
67
+ return typeof value === "object" && value !== null && !Array.isArray(value);
52
68
  }
53
69
 
54
70
  function validInteger(value: unknown, minimum: number, maximum: number): value is number {
55
- return (
56
- typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum
57
- );
71
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= minimum && value <= maximum;
72
+ }
73
+
74
+ function hasWhitespaceOrControl(value: string): boolean {
75
+ return [...value].some((character) => {
76
+ const code = character.codePointAt(0) ?? 0;
77
+ return code <= 0x20 || code === 0x7f;
78
+ });
79
+ }
80
+
81
+ function normalizeApiProfiles(value: unknown): Record<string, "codex-responses-v1"> | undefined {
82
+ if (
83
+ !isRecord(value) ||
84
+ (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null)
85
+ ) {
86
+ return undefined;
87
+ }
88
+ const profiles: Record<string, "codex-responses-v1"> = {};
89
+ for (const [api, profile] of Object.entries(value)) {
90
+ if (
91
+ api.length === 0 ||
92
+ api.length > MAX_API_PROFILE_ID_LENGTH ||
93
+ api.trim() !== api ||
94
+ hasWhitespaceOrControl(api) ||
95
+ BUILT_IN_APIS.has(api) ||
96
+ api === "__proto__" ||
97
+ api === "constructor" ||
98
+ api === "prototype" ||
99
+ profile !== "codex-responses-v1"
100
+ ) {
101
+ return undefined;
102
+ }
103
+ profiles[api] = profile;
104
+ }
105
+ return profiles;
58
106
  }
59
107
 
60
108
  export function normalizeCodexCompactSettings(value: unknown): CodexCompactSettings | undefined {
61
- if (!isRecord(value)) return undefined;
62
- if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return undefined;
63
- if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
64
- return undefined;
65
- }
66
- for (const [field, limits] of Object.entries(LIMITS) as Array<
67
- [keyof typeof LIMITS, { minimum: number; maximum: number }]
68
- >) {
69
- if (
70
- Object.hasOwn(value, field) &&
71
- !validInteger(value[field], limits.minimum, limits.maximum)
72
- ) {
73
- return undefined;
74
- }
75
- }
76
- return {
77
- enabled:
78
- typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
79
- requestTimeoutMs:
80
- typeof value.requestTimeoutMs === "number"
81
- ? value.requestTimeoutMs
82
- : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
83
- maxRetries:
84
- typeof value.maxRetries === "number"
85
- ? value.maxRetries
86
- : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
87
- replacementTokenBudget:
88
- typeof value.replacementTokenBudget === "number"
89
- ? value.replacementTokenBudget
90
- : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
91
- notifyOnFallback:
92
- typeof value.notifyOnFallback === "boolean"
93
- ? value.notifyOnFallback
94
- : DEFAULT_CODEX_COMPACT_SETTINGS.notifyOnFallback,
95
- };
109
+ if (!isRecord(value)) return undefined;
110
+ if (Object.hasOwn(value, "enabled") && typeof value.enabled !== "boolean") return undefined;
111
+ if (
112
+ Object.hasOwn(value, "protocol") &&
113
+ value.protocol !== "auto" &&
114
+ value.protocol !== "remote-v2" &&
115
+ value.protocol !== "responses-compact"
116
+ ) {
117
+ return undefined;
118
+ }
119
+ if (Object.hasOwn(value, "notifyOnFallback") && typeof value.notifyOnFallback !== "boolean") {
120
+ return undefined;
121
+ }
122
+ if (Object.hasOwn(value, "apiProfiles") && normalizeApiProfiles(value.apiProfiles) === undefined) return undefined;
123
+ for (const [field, limits] of Object.entries(LIMITS) as [
124
+ keyof typeof LIMITS,
125
+ { minimum: number; maximum: number },
126
+ ][]) {
127
+ if (Object.hasOwn(value, field) && !validInteger(value[field], limits.minimum, limits.maximum)) {
128
+ return undefined;
129
+ }
130
+ }
131
+ return {
132
+ enabled: typeof value.enabled === "boolean" ? value.enabled : DEFAULT_CODEX_COMPACT_SETTINGS.enabled,
133
+ protocol:
134
+ value.protocol === "remote-v2" || value.protocol === "responses-compact"
135
+ ? value.protocol
136
+ : DEFAULT_CODEX_COMPACT_SETTINGS.protocol,
137
+ apiProfiles: normalizeApiProfiles(value.apiProfiles) ?? structuredClone(DEFAULT_CODEX_COMPACT_SETTINGS.apiProfiles),
138
+ requestTimeoutMs:
139
+ typeof value.requestTimeoutMs === "number"
140
+ ? value.requestTimeoutMs
141
+ : DEFAULT_CODEX_COMPACT_SETTINGS.requestTimeoutMs,
142
+ maxRetries: typeof value.maxRetries === "number" ? value.maxRetries : DEFAULT_CODEX_COMPACT_SETTINGS.maxRetries,
143
+ replacementTokenBudget:
144
+ typeof value.replacementTokenBudget === "number"
145
+ ? value.replacementTokenBudget
146
+ : DEFAULT_CODEX_COMPACT_SETTINGS.replacementTokenBudget,
147
+ notifyOnFallback:
148
+ typeof value.notifyOnFallback === "boolean"
149
+ ? value.notifyOnFallback
150
+ : DEFAULT_CODEX_COMPACT_SETTINGS.notifyOnFallback,
151
+ };
96
152
  }
97
153
 
98
154
  export function codexCompactSettingsPath(): string {
99
- return join(getAgentDir(), CODEX_COMPACT_SETTINGS_FILE);
155
+ return join(getAgentDir(), CODEX_COMPACT_SETTINGS_FILE);
100
156
  }
101
157
 
102
158
  function aborted(signal?: AbortSignal): void {
103
- if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
159
+ if (signal?.aborted) throw new DOMException("Settings operation aborted", "AbortError");
104
160
  }
105
161
 
106
162
  export async function loadCodexCompactSettings(
107
- path = codexCompactSettingsPath(),
108
- signal?: AbortSignal,
163
+ path = codexCompactSettingsPath(),
164
+ signal?: AbortSignal,
109
165
  ): Promise<CodexCompactSettingsState> {
110
- aborted(signal);
111
- try {
112
- const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
113
- let text: string;
114
- try {
115
- const stats = await handle.stat();
116
- aborted(signal);
117
- if (!stats.isFile()) throw new Error("settings path is not a regular file");
118
- if (stats.size > MAX_SETTINGS_BYTES) throw new Error("settings file exceeds 64 KiB");
119
- text = await handle.readFile("utf8");
120
- } finally {
121
- await handle.close();
122
- }
123
- aborted(signal);
124
- const document = JSON.parse(text) as unknown;
125
- const settings = normalizeCodexCompactSettings(document);
126
- if (!settings || !isRecord(document)) throw new Error("invalid settings shape or bounds");
127
- return { kind: "loaded", path, settings, document };
128
- } catch (error) {
129
- if (signal?.aborted) throw error;
130
- if (isNodeError(error) && error.code === "ENOENT") {
131
- return {
132
- kind: "missing",
133
- path,
134
- settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
135
- document: {},
136
- };
137
- }
138
- return {
139
- kind: "invalid",
140
- path,
141
- settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
142
- issue:
143
- isNodeError(error) && error.code === "ELOOP"
144
- ? "symbolic links are not accepted"
145
- : error instanceof Error
146
- ? error.message
147
- : String(error),
148
- };
149
- }
166
+ aborted(signal);
167
+ try {
168
+ const handle = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW);
169
+ let text: string;
170
+ try {
171
+ const stats = await handle.stat();
172
+ aborted(signal);
173
+ if (!stats.isFile()) throw new Error("settings path is not a regular file");
174
+ if (stats.size > MAX_SETTINGS_BYTES) throw new Error("settings file exceeds 64 KiB");
175
+ text = await handle.readFile("utf8");
176
+ } finally {
177
+ await handle.close();
178
+ }
179
+ aborted(signal);
180
+ const document = JSON.parse(text) as unknown;
181
+ const settings = normalizeCodexCompactSettings(document);
182
+ if (!settings || !isRecord(document)) throw new Error("invalid settings shape or bounds");
183
+ return { kind: "loaded", path, settings, document };
184
+ } catch (error) {
185
+ if (signal?.aborted) throw error;
186
+ if (isNodeError(error) && error.code === "ENOENT") {
187
+ return {
188
+ kind: "missing",
189
+ path,
190
+ settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
191
+ document: {},
192
+ };
193
+ }
194
+ return {
195
+ kind: "invalid",
196
+ path,
197
+ settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
198
+ issue:
199
+ isNodeError(error) && error.code === "ELOOP"
200
+ ? "symbolic links are not accepted"
201
+ : error instanceof Error
202
+ ? error.message
203
+ : String(error),
204
+ };
205
+ }
150
206
  }
151
207
 
152
208
  async function savePatch(
153
- path: string,
154
- patch: Partial<CodexCompactSettings>,
155
- signal?: AbortSignal,
209
+ path: string,
210
+ patch: Partial<CodexCompactSettings>,
211
+ signal?: AbortSignal,
156
212
  ): Promise<CodexCompactSettingsState> {
157
- const latest = await loadCodexCompactSettings(path, signal);
158
- if (latest.kind === "invalid") {
159
- throw new Error(
160
- "Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first",
161
- );
162
- }
163
- const document = { ...latest.document, ...patch };
164
- const settings = normalizeCodexCompactSettings(document);
165
- if (!settings) throw new Error("Refusing to save invalid Codex compaction settings");
166
- const temporaryPath = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
167
- await mkdir(dirname(path), { recursive: true });
168
- aborted(signal);
169
- try {
170
- await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`, {
171
- encoding: "utf8",
172
- flag: "wx",
173
- mode: 0o600,
174
- });
175
- aborted(signal);
176
- const current = await loadCodexCompactSettings(path, signal);
177
- if (
178
- current.kind === "invalid" ||
179
- current.kind !== latest.kind ||
180
- JSON.stringify(current.document) !== JSON.stringify(latest.document)
181
- ) {
182
- throw new Error("pi-codex-compact.json changed while saving; reopen settings and retry");
183
- }
184
- await rename(temporaryPath, path);
185
- } finally {
186
- await rm(temporaryPath, { force: true }).catch(() => undefined);
187
- }
188
- return { kind: "loaded", path, settings, document };
213
+ const latest = await loadCodexCompactSettings(path, signal);
214
+ if (latest.kind === "invalid") {
215
+ throw new Error("Cannot overwrite an invalid pi-codex-compact.json; repair it and reload first");
216
+ }
217
+ const document = { ...latest.document, ...patch };
218
+ const settings = normalizeCodexCompactSettings(document);
219
+ if (!settings) throw new Error("Refusing to save invalid Codex compaction settings");
220
+ const temporaryPath = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
221
+ await mkdir(dirname(path), { recursive: true });
222
+ aborted(signal);
223
+ try {
224
+ await writeFile(temporaryPath, `${JSON.stringify(document, null, 2)}\n`, {
225
+ encoding: "utf8",
226
+ flag: "wx",
227
+ mode: 0o600,
228
+ });
229
+ aborted(signal);
230
+ const current = await loadCodexCompactSettings(path, signal);
231
+ if (
232
+ current.kind === "invalid" ||
233
+ current.kind !== latest.kind ||
234
+ JSON.stringify(current.document) !== JSON.stringify(latest.document)
235
+ ) {
236
+ throw new Error("pi-codex-compact.json changed while saving; reopen settings and retry");
237
+ }
238
+ await rename(temporaryPath, path);
239
+ } finally {
240
+ await rm(temporaryPath, { force: true }).catch(() => undefined);
241
+ }
242
+ return { kind: "loaded", path, settings, document };
189
243
  }
190
244
 
191
- export function createCodexCompactSettingsRuntime(
192
- path = codexCompactSettingsPath(),
193
- ): CodexCompactSettingsRuntime {
194
- let state: CodexCompactSettingsState = {
195
- kind: "missing",
196
- path,
197
- settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
198
- document: {},
199
- };
200
- let queue = Promise.resolve();
201
- const enqueue = <T>(operation: () => Promise<T>): Promise<T> => {
202
- const result = queue.then(operation, operation);
203
- queue = result.then(
204
- () => undefined,
205
- () => undefined,
206
- );
207
- return result;
208
- };
209
- return {
210
- get: () => structuredClone(state),
211
- reload: (signal) =>
212
- enqueue(async () => {
213
- state = await loadCodexCompactSettings(path, signal);
214
- return structuredClone(state);
215
- }),
216
- update: (patch, signal) =>
217
- enqueue(async () => {
218
- state = await savePatch(path, patch, signal);
219
- return structuredClone(state);
220
- }),
221
- flush: () => queue,
222
- };
245
+ export function createCodexCompactSettingsRuntime(path = codexCompactSettingsPath()): CodexCompactSettingsRuntime {
246
+ let state: CodexCompactSettingsState = {
247
+ kind: "missing",
248
+ path,
249
+ settings: { ...DEFAULT_CODEX_COMPACT_SETTINGS },
250
+ document: {},
251
+ };
252
+ let queue = Promise.resolve();
253
+ const enqueue = <T>(operation: () => Promise<T>): Promise<T> => {
254
+ const result = queue.then(operation, operation);
255
+ queue = result.then(
256
+ () => undefined,
257
+ () => undefined,
258
+ );
259
+ return result;
260
+ };
261
+ return {
262
+ get: () => structuredClone(state),
263
+ reload: (signal) =>
264
+ enqueue(async () => {
265
+ state = await loadCodexCompactSettings(path, signal);
266
+ return structuredClone(state);
267
+ }),
268
+ update: (patch, signal) =>
269
+ enqueue(async () => {
270
+ state = await savePatch(path, patch, signal);
271
+ return structuredClone(state);
272
+ }),
273
+ flush: () => queue,
274
+ };
223
275
  }
224
276
 
225
277
  function isNodeError(error: unknown): error is NodeJS.ErrnoException {
226
- return error instanceof Error && "code" in error;
278
+ return error instanceof Error && "code" in error;
227
279
  }
@@ -0,0 +1,6 @@
1
+ export function terminalText(value: string): string {
2
+ return Array.from(value, (character) => {
3
+ const codePoint = character.codePointAt(0) ?? 0;
4
+ return codePoint < 32 || (codePoint >= 127 && codePoint <= 159) ? " " : character;
5
+ }).join("");
6
+ }
@@ -1,13 +0,0 @@
1
- // @generated by scripts/build-runtime.mjs; do not edit.
2
- // @ts-nocheck -- the generated entry uses a .ts extension for Pi's Jiti loader.
3
-
4
- // src/model-api.ts
5
- import { hasApi } from "@earendil-works/pi-ai";
6
- function usesCodexResponsesApi(model) {
7
- return model !== void 0 && hasApi(model, "openai-codex-responses");
8
- }
9
-
10
- export {
11
- usesCodexResponsesApi
12
- };
13
- //# sourceMappingURL=chunk-6TZ2L2BM.js.map
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/model-api.ts"],
4
- "sourcesContent": ["import type { Api, Model } from \"@earendil-works/pi-ai\";\nimport { hasApi } from \"@earendil-works/pi-ai\";\n\nexport function usesCodexResponsesApi(\n\tmodel: Model<Api> | undefined,\n): model is Model<\"openai-codex-responses\"> {\n\treturn model !== undefined && hasApi(model, \"openai-codex-responses\");\n}\n"],
5
- "mappings": ";;;;AACA,SAAS,cAAc;AAEhB,SAAS,sBACf,OAC2C;AAC3C,SAAO,UAAU,UAAa,OAAO,OAAO,wBAAwB;AACrE;",
6
- "names": []
7
- }
@@ -1,7 +0,0 @@
1
- {
2
- "version": 3,
3
- "sources": ["../../src/settings-menu.ts"],
4
- "sourcesContent": ["import type { ExtensionCommandContext } from \"@earendil-works/pi-coding-agent\";\nimport type { MenuDefinition } from \"@narumitw/pi-tui-kit\";\nimport { usesCodexResponsesApi } from \"./model-api.js\";\nimport type {\n\tCodexCompactSettings,\n\tCodexCompactSettingsRuntime,\n\tCodexCompactSettingsState,\n} from \"./settings.js\";\n\ntype Screen = \"main\" | \"settings\" | \"invalid\";\ntype Action =\n\t| \"compact-now\"\n\t| \"set-enabled\"\n\t| \"set-timeout\"\n\t| \"set-retries\"\n\t| \"set-retention\"\n\t| \"set-notify\";\n\nexport interface SettingsMenuOwner {\n\tsignal: AbortSignal;\n\tisCurrent(): boolean;\n}\n\ninterface CompactMenuStatus {\n\tmodel: string;\n\tremoteEligible: boolean;\n}\n\nfunction safeText(value: string): string {\n\treturn Array.from(value, (character) => {\n\t\tconst codePoint = character.codePointAt(0) ?? 0;\n\t\treturn codePoint < 32 || (codePoint >= 127 && codePoint <= 159) ? \" \" : character;\n\t}).join(\"\");\n}\n\nfunction timeoutLabel(milliseconds: number): string {\n\treturn `${milliseconds / 60_000} min`;\n}\n\nfunction retentionLabel(tokens: number): string {\n\treturn `${tokens / 1000}K tokens`;\n}\n\nasync function update(\n\truntime: CodexCompactSettingsRuntime,\n\tctx: ExtensionCommandContext,\n\tpatch: Partial<CodexCompactSettings>,\n\tsignal: AbortSignal,\n) {\n\ttry {\n\t\tawait runtime.update(patch, signal);\n\t\tctx.ui.notify(\"Codex compaction settings saved.\", \"info\");\n\t\treturn { kind: \"stay\" as const };\n\t} catch (error) {\n\t\tif (signal.aborted) return { kind: \"rejected\" as const };\n\t\tctx.ui.notify(\n\t\t\t`Could not save pi-codex-compact.json: ${safeText(error instanceof Error ? error.message : String(error))}`,\n\t\t\t\"error\",\n\t\t);\n\t\treturn { kind: \"rejected\" as const };\n\t}\n}\n\nexport function createCodexCompactMenu(\n\truntime: CodexCompactSettingsRuntime,\n\toptions: { onCompactRequested?: () => void; status?: CompactMenuStatus } = {},\n): MenuDefinition<CodexCompactSettingsState, Screen, Action, ExtensionCommandContext> {\n\treturn {\n\t\tstart: \"main\",\n\t\tscreens: {\n\t\t\tmain: ({ state }) => ({\n\t\t\t\tkind: \"actions\",\n\t\t\t\ttitle: \"Codex Remote Compaction\",\n\t\t\t\tlines: [\n\t\t\t\t\t`Remote V2: ${state.settings.enabled ? \"On\" : \"Off\"}`,\n\t\t\t\t\t`Active model: ${safeText(options.status?.model ?? \"none\")}`,\n\t\t\t\t\t`Compact route: ${safeText(compactRoute(state, options.status))}`,\n\t\t\t\t],\n\t\t\t\titems: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"compact-now\",\n\t\t\t\t\t\tlabel: \"Compact now\",\n\t\t\t\t\t\tdescription: \"Close this menu and compact the active session immediately.\",\n\t\t\t\t\t\taction: \"compact-now\",\n\t\t\t\t\t},\n\t\t\t\t\tstate.kind === \"invalid\"\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tid: \"settings\",\n\t\t\t\t\t\t\t\tlabel: \"Settings\",\n\t\t\t\t\t\t\t\tdescription: \"Read-only until the invalid settings file is repaired.\",\n\t\t\t\t\t\t\t\tto: \"invalid\" as const,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: { id: \"settings\", label: \"Settings\", to: \"settings\" as const },\n\t\t\t\t\t{ id: \"close\", label: \"Close\", close: true },\n\t\t\t\t],\n\t\t\t\thint: \"close\",\n\t\t\t}),\n\t\t\tsettings: ({ state }) => ({\n\t\t\t\tkind: \"settings\",\n\t\t\t\ttitle: \"Codex Remote Compaction Settings\",\n\t\t\t\tlines: [`User settings \u00B7 ${safeText(state.path)}`],\n\t\t\t\titems: [\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"enabled\",\n\t\t\t\t\t\tlabel: \"Remote compaction\",\n\t\t\t\t\t\tdescription: \"Use Codex Remote V2 when the model uses openai-codex-responses.\",\n\t\t\t\t\t\tcurrentValue: state.settings.enabled ? \"On\" : \"Off\",\n\t\t\t\t\t\tvalues: [\"On\", \"Off\"],\n\t\t\t\t\t\taction: \"set-enabled\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"requestTimeoutMs\",\n\t\t\t\t\t\tlabel: \"Request timeout\",\n\t\t\t\t\t\tdescription: \"Maximum time for the extension-owned remote compaction request.\",\n\t\t\t\t\t\tcurrentValue: timeoutLabel(state.settings.requestTimeoutMs),\n\t\t\t\t\t\tvalues: [\"2 min\", \"5 min\", \"10 min\"],\n\t\t\t\t\t\taction: \"set-timeout\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"maxRetries\",\n\t\t\t\t\t\tlabel: \"Transport retries\",\n\t\t\t\t\t\tdescription: \"Retry transient provider failures before falling back to Pi.\",\n\t\t\t\t\t\tcurrentValue: String(state.settings.maxRetries),\n\t\t\t\t\t\tvalues: [\"0\", \"1\", \"2\"],\n\t\t\t\t\t\taction: \"set-retries\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"replacementTokenBudget\",\n\t\t\t\t\t\tlabel: \"Retained user history\",\n\t\t\t\t\t\tdescription: \"Approximate user-message budget kept beside the opaque checkpoint.\",\n\t\t\t\t\t\tcurrentValue: retentionLabel(state.settings.replacementTokenBudget),\n\t\t\t\t\t\tvalues: [\"32K tokens\", \"64K tokens\", \"96K tokens\", \"128K tokens\"],\n\t\t\t\t\t\taction: \"set-retention\",\n\t\t\t\t\t},\n\t\t\t\t\t{\n\t\t\t\t\t\tid: \"notifyOnFallback\",\n\t\t\t\t\t\tlabel: \"Fallback notifications\",\n\t\t\t\t\t\tdescription: \"Warn when Remote V2 fails and native Pi compaction takes over.\",\n\t\t\t\t\t\tcurrentValue: state.settings.notifyOnFallback ? \"On\" : \"Off\",\n\t\t\t\t\t\tvalues: [\"On\", \"Off\"],\n\t\t\t\t\t\taction: \"set-notify\",\n\t\t\t\t\t},\n\t\t\t\t],\n\t\t\t}),\n\t\t\tinvalid: ({ state }) => ({\n\t\t\t\tkind: \"detail\",\n\t\t\t\ttitle: \"Codex Compact Settings \u00B7 Read only\",\n\t\t\t\tlines: [\n\t\t\t\t\t`Invalid settings file: ${safeText(state.path)}`,\n\t\t\t\t\t`Issue: ${safeText(state.issue ?? \"unknown validation error\")}`,\n\t\t\t\t\t\"Built-in defaults are active. Repair the file and run /reload; it will not be overwritten.\",\n\t\t\t\t],\n\t\t\t\thint: \"back\",\n\t\t\t}),\n\t\t},\n\t\tactions: {\n\t\t\t\"compact-now\": async () => {\n\t\t\t\toptions.onCompactRequested?.();\n\t\t\t\treturn { kind: \"close\" };\n\t\t\t},\n\t\t\t\"set-enabled\": ({ ctx, value, signal }) =>\n\t\t\t\tupdate(runtime, ctx, { enabled: value === \"On\" }, signal),\n\t\t\t\"set-timeout\": ({ ctx, value, signal }) =>\n\t\t\t\tupdate(\n\t\t\t\t\truntime,\n\t\t\t\t\tctx,\n\t\t\t\t\t{ requestTimeoutMs: Number.parseInt(value ?? \"5\", 10) * 60_000 },\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t\"set-retries\": ({ ctx, value, signal }) =>\n\t\t\t\tupdate(runtime, ctx, { maxRetries: Number.parseInt(value ?? \"2\", 10) }, signal),\n\t\t\t\"set-retention\": ({ ctx, value, signal }) =>\n\t\t\t\tupdate(\n\t\t\t\t\truntime,\n\t\t\t\t\tctx,\n\t\t\t\t\t{ replacementTokenBudget: Number.parseInt(value ?? \"64\", 10) * 1000 },\n\t\t\t\t\tsignal,\n\t\t\t\t),\n\t\t\t\"set-notify\": ({ ctx, value, signal }) =>\n\t\t\t\tupdate(runtime, ctx, { notifyOnFallback: value === \"On\" }, signal),\n\t\t},\n\t};\n}\n\nexport async function showCodexCompactMenu(\n\truntime: CodexCompactSettingsRuntime,\n\tctx: ExtensionCommandContext,\n\towner: SettingsMenuOwner,\n): Promise<void> {\n\tif (ctx.mode !== \"tui\") {\n\t\tctx.ui.notify(`Edit Codex compaction settings at ${safeText(runtime.get().path)}.`, \"info\");\n\t\treturn;\n\t}\n\tconst { runMenu } = await import(\"@narumitw/pi-tui-kit\");\n\tif (owner.signal.aborted || !owner.isCurrent()) return;\n\tlet compactRequested = false;\n\tawait runMenu(\n\t\tctx,\n\t\tcreateCodexCompactMenu(runtime, {\n\t\t\tonCompactRequested: () => {\n\t\t\t\tcompactRequested = true;\n\t\t\t},\n\t\t\tstatus: compactMenuStatus(ctx),\n\t\t}),\n\t\t{\n\t\t\tgetState: () => runtime.get(),\n\t\t\tsignal: owner.signal,\n\t\t\tisCurrent: owner.isCurrent,\n\t\t},\n\t);\n\tif (!compactRequested || owner.signal.aborted || !owner.isCurrent()) return;\n\tctx.compact({\n\t\tonError: (error) => {\n\t\t\tif (!owner.signal.aborted && owner.isCurrent()) {\n\t\t\t\tctx.ui.notify(`Compaction failed: ${safeText(error.message)}`, \"error\");\n\t\t\t}\n\t\t},\n\t});\n}\n\nexport function compactMenuStatus(ctx: ExtensionCommandContext): CompactMenuStatus {\n\tconst model = ctx.model;\n\treturn {\n\t\tmodel: model ? `${model.provider}/${model.id}` : \"none\",\n\t\tremoteEligible: usesCodexResponsesApi(model),\n\t};\n}\n\nfunction compactRoute(\n\tstate: Readonly<CodexCompactSettingsState>,\n\tstatus: CompactMenuStatus | undefined,\n): string {\n\tif (!state.settings.enabled) return \"Pi native (Remote V2 off)\";\n\treturn status?.remoteEligible ? \"Codex Remote V2\" : \"Pi native (requires openai-codex-responses)\";\n}\n"],
5
- "mappings": "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",
6
- "names": []
7
- }