@wlv-zedd/dsh-chatgpt-web 1.0.0 → 1.0.2

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.
Files changed (81) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +140 -140
  3. package/cordis.patch.yml +4 -4
  4. package/lib/cli.js +13269 -11860
  5. package/lib/plugin.js +3 -50
  6. package/package.json +89 -88
  7. package/src/adapters/base.ts +16 -16
  8. package/src/adapters/chatgpt-web/adapter-error.ts +59 -59
  9. package/src/adapters/chatgpt-web/browser-helper-main.ts +513 -513
  10. package/src/adapters/chatgpt-web/browser-helper-prompt-selection.ts +27 -27
  11. package/src/adapters/chatgpt-web/browser-worker.ts +4944 -4944
  12. package/src/adapters/chatgpt-web/codex-rollout-environment.ts +628 -628
  13. package/src/adapters/chatgpt-web/compaction-handoff.ts +533 -533
  14. package/src/adapters/chatgpt-web/compaction-transaction.ts +142 -142
  15. package/src/adapters/chatgpt-web/concurrency.ts +6 -6
  16. package/src/adapters/chatgpt-web/conversation-key.ts +58 -58
  17. package/src/adapters/chatgpt-web/environment.ts +669 -669
  18. package/src/adapters/chatgpt-web/index.ts +1544 -1544
  19. package/src/adapters/chatgpt-web/input-tokens.ts +74 -74
  20. package/src/adapters/chatgpt-web/launcher-helper-client.ts +695 -695
  21. package/src/adapters/chatgpt-web/markdown.ts +418 -418
  22. package/src/adapters/chatgpt-web/mcp-main.ts +25 -25
  23. package/src/adapters/chatgpt-web/mcp-server.ts +933 -933
  24. package/src/adapters/chatgpt-web/model.ts +70 -70
  25. package/src/adapters/chatgpt-web/native-compaction-control.ts +74 -74
  26. package/src/adapters/chatgpt-web/output-validation.ts +62 -62
  27. package/src/adapters/chatgpt-web/process-line-writer.ts +46 -46
  28. package/src/adapters/chatgpt-web/prompt.ts +702 -702
  29. package/src/adapters/chatgpt-web/retry-policy.ts +73 -73
  30. package/src/adapters/chatgpt-web/rolling-checkpoint.ts +384 -384
  31. package/src/adapters/chatgpt-web/thread-environment.ts +238 -238
  32. package/src/adapters/chatgpt-web/tool-stream-parser.ts +1 -1
  33. package/src/adapters/chatgpt-web/turn-broker.ts +1481 -1481
  34. package/src/adapters/chatgpt-web/turn-execution.ts +816 -816
  35. package/src/adapters/chatgpt-web/turn-progress.ts +292 -292
  36. package/src/adapters/chatgpt-web/usage.ts +121 -121
  37. package/src/adapters/image.ts +9 -9
  38. package/src/bridge.ts +1083 -1083
  39. package/src/browser-login.ts +521 -521
  40. package/src/chatgpt-session.ts +240 -240
  41. package/src/chatgpt-web-models.ts +400 -400
  42. package/src/cli.ts +568 -568
  43. package/src/codex-integration-document.ts +824 -824
  44. package/src/codex-integration-journal.ts +212 -212
  45. package/src/codex-integration-route.ts +515 -515
  46. package/src/codex-integration-shared.ts +332 -332
  47. package/src/codex-integration.ts +529 -529
  48. package/src/codex-interrupt-hook.ts +158 -158
  49. package/src/config.ts +616 -616
  50. package/src/dev-chat/cli.ts +432 -432
  51. package/src/dev-chat/constants.ts +3 -3
  52. package/src/dev-chat/driver.ts +655 -655
  53. package/src/dev-chat/profile.ts +223 -223
  54. package/src/dev-chat/session.ts +287 -287
  55. package/src/dev-chat/transport.ts +54 -54
  56. package/src/doctor.ts +237 -237
  57. package/src/event-queue.ts +45 -45
  58. package/src/http-body.ts +30 -30
  59. package/src/launcher-browser-host.ts +695 -695
  60. package/src/lib/errors.ts +281 -281
  61. package/src/lib/token-estimate.ts +42 -42
  62. package/src/login-helper.cjs +1 -1
  63. package/src/model-catalog.ts +197 -197
  64. package/src/native-passthrough.ts +261 -261
  65. package/src/plugin.ts +191 -191
  66. package/src/process.ts +45 -45
  67. package/src/responses/compaction.ts +199 -199
  68. package/src/responses/parser.ts +633 -633
  69. package/src/responses/reasoning-envelope.ts +49 -49
  70. package/src/responses/schema.ts +172 -172
  71. package/src/responses/state.ts +230 -230
  72. package/src/server.ts +1111 -1111
  73. package/src/service.ts +315 -315
  74. package/src/setup.ts +671 -671
  75. package/src/stall-timeout.ts +23 -23
  76. package/src/tunnel-service.ts +160 -160
  77. package/src/tunnel.ts +417 -417
  78. package/src/turndown-plugin-gfm.d.ts +5 -5
  79. package/src/types.ts +307 -307
  80. package/src/usage/totals.ts +12 -12
  81. package/src/version.ts +1 -1
@@ -1,515 +1,515 @@
1
- import { existsSync } from "node:fs";
2
- import { resolve } from "node:path";
3
- import {
4
- CODEX_REALTIME_WEBRTC_CALL_BASE_URL,
5
- MANAGED_COMMENT,
6
- MANAGED_ROUTE_COMMENT,
7
- MANAGED_MULTI_AGENT_LINE,
8
- MANAGED_REMOTE_COMPACTION_LINE,
9
- managedAgentMaxDepthLine,
10
- } from "./codex-integration-shared";
11
- import type {
12
- CodexIntegrationJournal,
13
- LegacyCodexIntegrationJournal,
14
- LegacyCodexIntegrationJournalV4,
15
- LegacyCodexIntegrationJournalV5,
16
- LegacyCodexIntegrationJournalV6,
17
- LegacyCodexIntegrationJournalV7,
18
- LegacyCodexIntegrationJournalV8,
19
- LegacyCodexIntegrationJournalV9,
20
- ManagedAssignmentKey,
21
- ManagedRouteJournal,
22
- PreviousAssignment,
23
- PreviousFeatureAssignment,
24
- PreviousAgentAssignment,
25
- } from "./codex-integration-shared";
26
- import {
27
- restoreCodexInterruptHook,
28
- verifyCodexInterruptHook,
29
- verifyCodexInterruptHookRestored,
30
- } from "./codex-interrupt-hook";
31
- import {
32
- assignments,
33
- findFeatureAssignment,
34
- findAgentMaxDepthAssignment,
35
- findMultiAgentV2Assignment,
36
- findTopLevelAssignment,
37
- firstTableIndex,
38
- insertDocumentLine,
39
- managedMultiAgentV2AssignmentLine,
40
- parseDocument,
41
- removeDocumentLine,
42
- removeManagedComment,
43
- renderDocument,
44
- restoreBooleanFeature,
45
- restoreCompatibilityV1Features,
46
- restoreCompatibilityV1AgentDepth,
47
- restoreManagedFeatures,
48
- restoreMultiAgentV2Feature,
49
- splitLines,
50
- verifyInstalledFeatures,
51
- verifyCompatibilityV1Features,
52
- } from "./codex-integration-document";
53
-
54
- function compatibilityV1Evidence(
55
- journal: CodexIntegrationJournal | LegacyCodexIntegrationJournalV9 | LegacyCodexIntegrationJournalV8,
56
- ): {
57
- previousMultiAgent: PreviousFeatureAssignment;
58
- previousMultiAgentV2: PreviousFeatureAssignment;
59
- previousAgentMaxDepth: PreviousAgentAssignment;
60
- installedAgentMaxDepth: number;
61
- } | undefined {
62
- if (journal.installed.subagent_protocol !== "compatibility-v1") return undefined;
63
- if (!journal.previousMultiAgent || !journal.previousMultiAgentV2
64
- || !journal.previousAgentMaxDepth || journal.installed.agent_max_depth === undefined) {
65
- throw new Error("Codex integration journal is missing the Compatibility V1 feature baseline");
66
- }
67
- return {
68
- previousMultiAgent: journal.previousMultiAgent,
69
- previousMultiAgentV2: journal.previousMultiAgentV2,
70
- previousAgentMaxDepth: journal.previousAgentMaxDepth,
71
- installedAgentMaxDepth: journal.installed.agent_max_depth,
72
- };
73
- }
74
-
75
- function restoreOwnedManagedFeatures(text: string, journal: ManagedRouteJournal): string {
76
- let restored = text;
77
- if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
78
- const evidence = compatibilityV1Evidence(journal);
79
- if (evidence) {
80
- const depth = findAgentMaxDepthAssignment(splitLines(restored));
81
- if (depth.rawLine === managedAgentMaxDepthLine(evidence.installedAgentMaxDepth)
82
- && depth.value === String(evidence.installedAgentMaxDepth)) {
83
- restored = restoreCompatibilityV1AgentDepth(
84
- restored,
85
- evidence.previousAgentMaxDepth,
86
- evidence.installedAgentMaxDepth,
87
- );
88
- }
89
- const multiAgentV2 = findMultiAgentV2Assignment(splitLines(restored));
90
- const managedV2Line = managedMultiAgentV2AssignmentLine(evidence.previousMultiAgentV2);
91
- if (multiAgentV2.rawLine === managedV2Line && multiAgentV2.value === "false") {
92
- restored = restoreMultiAgentV2Feature(restored, evidence.previousMultiAgentV2);
93
- }
94
- const multiAgent = findFeatureAssignment(splitLines(restored), "multi_agent");
95
- if (multiAgent.rawLine === MANAGED_MULTI_AGENT_LINE && multiAgent.value === "true") {
96
- restored = restoreBooleanFeature(
97
- restored,
98
- "multi_agent",
99
- "true",
100
- MANAGED_MULTI_AGENT_LINE,
101
- evidence.previousMultiAgent,
102
- );
103
- }
104
- }
105
- }
106
- if (journal.version === 6) {
107
- const current = findMultiAgentV2Assignment(splitLines(restored));
108
- const managedLine = managedMultiAgentV2AssignmentLine(journal.previousMultiAgentV2);
109
- if (current.rawLine === managedLine && current.value === "false") {
110
- restored = restoreMultiAgentV2Feature(restored, journal.previousMultiAgentV2);
111
- }
112
- }
113
- if (journal.version === 5 || journal.version === 6) {
114
- const multiAgent = findFeatureAssignment(splitLines(restored), "multi_agent");
115
- if (multiAgent.rawLine === MANAGED_MULTI_AGENT_LINE && multiAgent.value === "true") {
116
- restored = restoreBooleanFeature(
117
- restored,
118
- "multi_agent",
119
- "true",
120
- MANAGED_MULTI_AGENT_LINE,
121
- journal.previousMultiAgent,
122
- );
123
- }
124
- const compaction = findFeatureAssignment(splitLines(restored), "remote_compaction_v2");
125
- if (compaction.rawLine === MANAGED_REMOTE_COMPACTION_LINE && compaction.value === "false") {
126
- restored = restoreBooleanFeature(
127
- restored,
128
- "remote_compaction_v2",
129
- "false",
130
- MANAGED_REMOTE_COMPACTION_LINE,
131
- journal.previousRemoteCompactionV2,
132
- );
133
- }
134
- }
135
- return restored;
136
- }
137
- function restoreStillManagedRouteAssignments(text: string, journal: ManagedRouteJournal): string {
138
- const document = parseDocument(text);
139
- removeManagedComment(document);
140
- const current = assignments(document.lines);
141
- const target = Object.fromEntries(
142
- (Object.keys(current) as ManagedAssignmentKey[]).map(key => {
143
- const stillManaged = key === "openai_base_url"
144
- ? current[key].present && current[key].value === journal.installed.openai_base_url
145
- : !current[key].present;
146
- return [key, stillManaged ? journal.previous[key] : current[key]];
147
- }),
148
- ) as Record<ManagedAssignmentKey, PreviousAssignment>;
149
- const currentIndices = Object.values(current)
150
- .flatMap(assignment => assignment.index === undefined ? [] : [assignment.index])
151
- .sort((left, right) => right - left);
152
- for (const index of currentIndices) removeDocumentLine(document, index);
153
-
154
- const previous = (Object.entries(target) as Array<[ManagedAssignmentKey, PreviousAssignment]>)
155
- .filter(([, assignment]) => assignment.present)
156
- .sort(([, left], [, right]) => (left.index ?? Number.MAX_SAFE_INTEGER) - (right.index ?? Number.MAX_SAFE_INTEGER));
157
- for (const [key, assignment] of previous) {
158
- if (!assignment.rawLine) throw new Error(`Codex integration journal is missing the prior ${key} line`);
159
- const index = Math.min(assignment.index ?? firstTableIndex(document.lines), firstTableIndex(document.lines));
160
- insertDocumentLine(document, index, assignment.rawLine);
161
- }
162
- return renderDocument(document);
163
- }
164
- export function managedJournalIsActive(journal: ManagedRouteJournal): boolean {
165
- return journal.version === 3 || journal.active;
166
- }
167
-
168
- export function verifyManagedJournalState(text: string, journal: ManagedRouteJournal): void {
169
- if (journal.version === 3 || journal.active) verifyInstalledRoute(text, journal);
170
- else verifyRestoredRoute(text, journal);
171
- }
172
-
173
- export function replacementBaseline(
174
- currentText: string,
175
- configExists: boolean,
176
- journal: ManagedRouteJournal,
177
- ): string {
178
- if (!configExists) return "";
179
- if (!managedJournalIsActive(journal)) return currentText;
180
-
181
- if (journal.version === 9 || journal.version === 10) {
182
- const withoutHook = journal.version === 10
183
- ? restoreCodexInterruptHook(currentText, journal.interruptHook)
184
- : currentText;
185
- const baseline = restoreOwnedManagedFeatures(withoutHook, journal);
186
- const document = parseDocument(baseline);
187
- removeManagedComment(document);
188
- for (const [key, installedValue, previous] of [
189
- ["openai_base_url", journal.installed.openai_base_url, journal.previous.openai_base_url],
190
- [
191
- "experimental_realtime_webrtc_call_base_url",
192
- journal.installed.experimental_realtime_webrtc_call_base_url,
193
- journal.previousRealtimeWebrtcCallBaseUrl,
194
- ],
195
- ] as const) {
196
- const current = findTopLevelAssignment(document.lines, key);
197
- if (current.value !== installedValue || current.index === undefined) continue;
198
- if (previous.present) {
199
- if (!previous.rawLine) throw new Error(`Codex integration journal is missing the prior ${key} line`);
200
- document.lines[current.index] = previous.rawLine;
201
- } else {
202
- removeDocumentLine(document, current.index);
203
- }
204
- }
205
- return renderDocument(document);
206
- }
207
-
208
- if (journal.version === 7 || journal.version === 8) {
209
- const baseline = restoreOwnedManagedFeatures(currentText, journal);
210
- const document = parseDocument(baseline);
211
- removeManagedComment(document);
212
- const current = findTopLevelAssignment(document.lines, "openai_base_url");
213
- if (current.value === journal.installed.openai_base_url && current.index !== undefined) {
214
- const previous = journal.previous.openai_base_url;
215
- if (previous.present) {
216
- if (!previous.rawLine) throw new Error("Codex integration journal is missing the prior openai_base_url line");
217
- document.lines[current.index] = previous.rawLine;
218
- } else {
219
- removeDocumentLine(document, current.index);
220
- }
221
- }
222
- return renderDocument(document);
223
- }
224
-
225
- let baseline = restoreOwnedManagedFeatures(currentText, journal);
226
- baseline = restoreStillManagedRouteAssignments(baseline, journal);
227
- return baseline;
228
- }
229
-
230
- export function installRoute(
231
- text: string,
232
- installedUrl: string,
233
- replaceExistingRoute: boolean,
234
- replaceExistingRealtimeRoute: boolean,
235
- ): {
236
- text: string;
237
- previous: CodexIntegrationJournal["previous"];
238
- previousRealtimeWebrtcCallBaseUrl: PreviousAssignment;
239
- } {
240
- const document = parseDocument(text);
241
- const previous = assignments(document.lines);
242
- if (previous.openai_base_url.present && !replaceExistingRoute) {
243
- throw new Error(
244
- `Codex already configures model routing (openai_base_url=${JSON.stringify(previous.openai_base_url.value)}). `
245
- + "Rerun with --replace-codex-route to replace it reversibly. "
246
- + "Check whether another Codex extension or wrapper (for example, OpenCodex or Headroom) is replacing the bridge port.",
247
- );
248
- }
249
- const previousRealtimeWebrtcCallBaseUrl = findTopLevelAssignment(
250
- document.lines,
251
- "experimental_realtime_webrtc_call_base_url",
252
- );
253
- if (previousRealtimeWebrtcCallBaseUrl.present
254
- && previousRealtimeWebrtcCallBaseUrl.value !== CODEX_REALTIME_WEBRTC_CALL_BASE_URL
255
- && !replaceExistingRealtimeRoute) {
256
- throw new Error(
257
- "Codex already configures its realtime WebRTC call route "
258
- + `(experimental_realtime_webrtc_call_base_url=${JSON.stringify(previousRealtimeWebrtcCallBaseUrl.value)}). `
259
- + "Rerun with --replace-codex-route to replace it reversibly.",
260
- );
261
- }
262
-
263
- const currentBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
264
- if (currentBaseUrl.index !== undefined) {
265
- document.lines[currentBaseUrl.index] = `openai_base_url = ${JSON.stringify(installedUrl)}`;
266
- } else {
267
- insertDocumentLine(document, firstTableIndex(document.lines), `openai_base_url = ${JSON.stringify(installedUrl)}`);
268
- }
269
- const currentRealtimeUrl = findTopLevelAssignment(document.lines, "experimental_realtime_webrtc_call_base_url");
270
- const realtimeLine = `experimental_realtime_webrtc_call_base_url = ${JSON.stringify(CODEX_REALTIME_WEBRTC_CALL_BASE_URL)}`;
271
- if (currentRealtimeUrl.index !== undefined) {
272
- document.lines[currentRealtimeUrl.index] = realtimeLine;
273
- } else {
274
- const installedBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
275
- insertDocumentLine(document, installedBaseUrl.index! + 1, realtimeLine);
276
- }
277
- removeManagedComment(document);
278
- const installedBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
279
- insertDocumentLine(document, installedBaseUrl.index!, MANAGED_ROUTE_COMMENT);
280
- return { text: renderDocument(document), previous, previousRealtimeWebrtcCallBaseUrl };
281
- }
282
-
283
- export function verifyInstalledRoute(text: string, journal: ManagedRouteJournal): void {
284
- const lines = splitLines(text);
285
- const current = assignments(lines);
286
- if (current.openai_base_url.value !== journal.installed.openai_base_url) {
287
- throw new Error("Codex openai_base_url changed after setup; refusing to overwrite the user's newer value");
288
- }
289
- const expectedMarker = journal.version === 9 || journal.version === 10
290
- ? MANAGED_ROUTE_COMMENT
291
- : MANAGED_COMMENT;
292
- if (!lines.includes(expectedMarker)) {
293
- throw new Error("Managed Codex route marker changed after setup; refusing to overwrite it");
294
- }
295
- if (journal.version === 9 || journal.version === 10) {
296
- const realtime = findTopLevelAssignment(lines, "experimental_realtime_webrtc_call_base_url");
297
- const expectedLine = `experimental_realtime_webrtc_call_base_url = ${JSON.stringify(journal.installed.experimental_realtime_webrtc_call_base_url)}`;
298
- if (realtime.value !== journal.installed.experimental_realtime_webrtc_call_base_url
299
- || realtime.rawLine !== expectedLine) {
300
- throw new Error("Codex realtime WebRTC call route changed after setup; refusing to overwrite the user's newer value");
301
- }
302
- }
303
- if (journal.version === 10) verifyCodexInterruptHook(text, journal.interruptHook);
304
- if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
305
- const evidence = compatibilityV1Evidence(journal);
306
- if (evidence) {
307
- verifyCompatibilityV1Features(
308
- text,
309
- evidence.previousMultiAgentV2,
310
- evidence.installedAgentMaxDepth,
311
- );
312
- }
313
- }
314
- if (journal.version !== 7 && journal.version !== 8 && journal.version !== 9 && journal.version !== 10) {
315
- if (current.model_provider.present || current.model_catalog_json.present) {
316
- throw new Error("Codex model_provider or model_catalog_json changed after setup; refusing to overwrite the user's newer value");
317
- }
318
- if (journal.version === 5 || journal.version === 6) verifyInstalledFeatures(text, journal);
319
- }
320
- }
321
-
322
- function previousAssignmentMatches(current: PreviousAssignment, previous: PreviousAssignment): boolean {
323
- return current.present === previous.present
324
- && (!current.present || current.value === previous.value);
325
- }
326
-
327
- function previousAssignmentMatchesExactly(current: PreviousAssignment, previous: PreviousAssignment): boolean {
328
- return current.present === previous.present
329
- && (!current.present || (current.value === previous.value && current.rawLine === previous.rawLine));
330
- }
331
-
332
- export function verifyRestoredRoute(
333
- text: string,
334
- journal: CodexIntegrationJournal | LegacyCodexIntegrationJournalV9 | LegacyCodexIntegrationJournalV8 | LegacyCodexIntegrationJournalV7 | LegacyCodexIntegrationJournalV6 | LegacyCodexIntegrationJournalV5 | LegacyCodexIntegrationJournalV4,
335
- ): void {
336
- const lines = splitLines(text);
337
- const current = assignments(lines);
338
- const keys = journal.version === 7 || journal.version === 8 || journal.version === 9 || journal.version === 10
339
- ? (["openai_base_url"] as const)
340
- : (["openai_base_url", "model_provider", "model_catalog_json"] as const);
341
- for (const key of keys) {
342
- if (!previousAssignmentMatches(current[key], journal.previous[key])) {
343
- throw new Error(`Codex ${key} changed while the bridge was disconnected; refusing to overwrite the user's newer value`);
344
- }
345
- }
346
- if (lines.includes(MANAGED_COMMENT) || lines.includes(MANAGED_ROUTE_COMMENT)) {
347
- throw new Error("Managed Codex route marker is present while the bridge is disconnected");
348
- }
349
- if (journal.version === 9 || journal.version === 10) {
350
- const realtime = findTopLevelAssignment(lines, "experimental_realtime_webrtc_call_base_url");
351
- if (!previousAssignmentMatchesExactly(realtime, journal.previousRealtimeWebrtcCallBaseUrl)) {
352
- throw new Error(
353
- "Codex realtime WebRTC call route changed while the bridge was disconnected; refusing to overwrite the user's newer value",
354
- );
355
- }
356
- }
357
- if (journal.version === 10) verifyCodexInterruptHookRestored(text);
358
- if (journal.version === 5 || journal.version === 6) {
359
- const previousFeatures: Array<readonly [string, PreviousFeatureAssignment]> = [
360
- ["remote_compaction_v2", journal.previousRemoteCompactionV2],
361
- ["multi_agent", journal.previousMultiAgent],
362
- ];
363
- if (journal.version === 6) {
364
- previousFeatures.push(["multi_agent_v2", journal.previousMultiAgentV2]);
365
- }
366
- for (const [key, previous] of previousFeatures) {
367
- const current = key === "multi_agent_v2"
368
- ? findMultiAgentV2Assignment(lines)
369
- : findFeatureAssignment(lines, key);
370
- const matches = current.present === previous.present
371
- && (current.tableName ?? "features") === (previous.tableName ?? "features")
372
- && (!current.present || current.rawLine === previous.rawLine);
373
- if (!matches) {
374
- throw new Error(
375
- `Codex [features].${key} changed while the bridge was disconnected; refusing to overwrite the user's newer value`,
376
- );
377
- }
378
- }
379
- }
380
- if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
381
- const evidence = compatibilityV1Evidence(journal);
382
- if (evidence) {
383
- for (const [key, previous] of [
384
- ["multi_agent", evidence.previousMultiAgent],
385
- ["multi_agent_v2", evidence.previousMultiAgentV2],
386
- ] as const) {
387
- const current = key === "multi_agent_v2"
388
- ? findMultiAgentV2Assignment(lines)
389
- : findFeatureAssignment(lines, key);
390
- const matches = current.present === previous.present
391
- && (!current.present || (
392
- (current.tableName ?? "features") === (previous.tableName ?? "features")
393
- && current.rawLine === previous.rawLine
394
- ));
395
- if (!matches) {
396
- throw new Error(
397
- `Codex [features].${key} changed while Compatibility V1 was disconnected; refusing to overwrite the user's newer value`,
398
- );
399
- }
400
- }
401
- const depth = findAgentMaxDepthAssignment(lines);
402
- const previousDepth = evidence.previousAgentMaxDepth;
403
- const depthMatches = depth.present === previousDepth.present
404
- && (!depth.present || depth.rawLine === previousDepth.rawLine);
405
- if (!depthMatches) {
406
- throw new Error(
407
- "Codex [agents].max_depth changed while Compatibility V1 was disconnected; refusing to overwrite the user's newer value",
408
- );
409
- }
410
- }
411
- }
412
- }
413
-
414
- export function assertPreservedPreviousAssignments(
415
- actual: CodexIntegrationJournal["previous"],
416
- expected: CodexIntegrationJournal["previous"],
417
- ): void {
418
- if (!previousAssignmentMatches(actual.openai_base_url, expected.openai_base_url)) {
419
- throw new Error("Codex openai_base_url changed while the bridge was disconnected; refusing to replace it");
420
- }
421
- }
422
-
423
- export function assertPreservedPreviousRealtimeAssignment(
424
- actual: PreviousAssignment,
425
- expected: PreviousAssignment,
426
- ): void {
427
- if (!previousAssignmentMatchesExactly(actual, expected)) {
428
- throw new Error("Codex realtime WebRTC call route changed while the bridge was disconnected; refusing to replace it");
429
- }
430
- }
431
-
432
- export function restoreManagedRoute(text: string, journal: ManagedRouteJournal): string {
433
- verifyInstalledRoute(text, journal);
434
- const withoutHook = journal.version === 10
435
- ? restoreCodexInterruptHook(text, journal.interruptHook)
436
- : text;
437
- const document = parseDocument(withoutHook);
438
- removeManagedComment(document);
439
- const currentBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
440
- if (currentBaseUrl.index === undefined) throw new Error("Managed Codex openai_base_url is missing");
441
- const previousBaseUrl = journal.previous.openai_base_url;
442
- if (previousBaseUrl.present) {
443
- if (!previousBaseUrl.rawLine) throw new Error("Codex integration journal is missing the prior openai_base_url line");
444
- document.lines[currentBaseUrl.index] = previousBaseUrl.rawLine;
445
- } else {
446
- removeDocumentLine(document, currentBaseUrl.index);
447
- }
448
- if (journal.version === 9 || journal.version === 10) {
449
- const currentRealtime = findTopLevelAssignment(document.lines, "experimental_realtime_webrtc_call_base_url");
450
- if (currentRealtime.index === undefined) throw new Error("Managed Codex realtime WebRTC call route is missing");
451
- const previousRealtime = journal.previousRealtimeWebrtcCallBaseUrl;
452
- if (previousRealtime.present) {
453
- if (!previousRealtime.rawLine) {
454
- throw new Error("Codex integration journal is missing the prior realtime WebRTC call route line");
455
- }
456
- document.lines[currentRealtime.index] = previousRealtime.rawLine;
457
- } else {
458
- removeDocumentLine(document, currentRealtime.index);
459
- }
460
- }
461
- if (journal.version !== 7 && journal.version !== 8 && journal.version !== 9 && journal.version !== 10) {
462
- const removedAssignments = (["model_provider", "model_catalog_json"] as const)
463
- .map(key => ({ key, previous: journal.previous[key] }))
464
- .filter(item => item.previous.present)
465
- .sort((left, right) => (left.previous.index ?? Number.MAX_SAFE_INTEGER) - (right.previous.index ?? Number.MAX_SAFE_INTEGER));
466
- for (const item of removedAssignments) {
467
- if (!item.previous.rawLine) throw new Error(`Codex integration journal is missing the prior ${item.key} line`);
468
- const index = Math.min(item.previous.index ?? firstTableIndex(document.lines), firstTableIndex(document.lines));
469
- insertDocumentLine(document, index, item.previous.rawLine);
470
- }
471
- }
472
- const restoredRoute = renderDocument(document);
473
- if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
474
- const evidence = compatibilityV1Evidence(journal);
475
- return evidence
476
- ? restoreCompatibilityV1Features(
477
- restoredRoute,
478
- evidence.previousMultiAgent,
479
- evidence.previousMultiAgentV2,
480
- evidence.previousAgentMaxDepth,
481
- evidence.installedAgentMaxDepth,
482
- )
483
- : restoredRoute;
484
- }
485
- return journal.version === 5 || journal.version === 6
486
- ? restoreManagedFeatures(restoredRoute, journal)
487
- : restoredRoute;
488
- }
489
-
490
- export function restoreLegacyV2(text: string, journal: LegacyCodexIntegrationJournal): string {
491
- if (!text.includes(journal.providerBlock)) {
492
- throw new Error("Managed legacy Codex provider block changed after setup; refusing migration");
493
- }
494
- const withoutProvider = text.replace(journal.providerBlock, "").replace(/\n{3,}/g, "\n\n");
495
- const document = parseDocument(withoutProvider);
496
- for (const key of ["model_provider", "model_catalog_json"] as const) {
497
- const current = findTopLevelAssignment(document.lines, key);
498
- if (current.value !== journal.installed[key] || current.index === undefined) {
499
- throw new Error(`Managed legacy Codex ${key} changed after setup; refusing migration`);
500
- }
501
- const previous = journal.previous[key];
502
- if (previous.present) {
503
- if (!previous.rawLine) throw new Error(`Legacy Codex integration journal is missing the prior ${key} line`);
504
- document.lines[current.index] = previous.rawLine;
505
- } else {
506
- removeDocumentLine(document, current.index);
507
- }
508
- }
509
- removeManagedComment(document);
510
- const restoredCatalog = findTopLevelAssignment(document.lines, "model_catalog_json");
511
- if (restoredCatalog.index !== undefined && restoredCatalog.value && !existsSync(resolve(restoredCatalog.value))) {
512
- removeDocumentLine(document, restoredCatalog.index);
513
- }
514
- return renderDocument(document);
515
- }
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import {
4
+ CODEX_REALTIME_WEBRTC_CALL_BASE_URL,
5
+ MANAGED_COMMENT,
6
+ MANAGED_ROUTE_COMMENT,
7
+ MANAGED_MULTI_AGENT_LINE,
8
+ MANAGED_REMOTE_COMPACTION_LINE,
9
+ managedAgentMaxDepthLine,
10
+ } from "./codex-integration-shared";
11
+ import type {
12
+ CodexIntegrationJournal,
13
+ LegacyCodexIntegrationJournal,
14
+ LegacyCodexIntegrationJournalV4,
15
+ LegacyCodexIntegrationJournalV5,
16
+ LegacyCodexIntegrationJournalV6,
17
+ LegacyCodexIntegrationJournalV7,
18
+ LegacyCodexIntegrationJournalV8,
19
+ LegacyCodexIntegrationJournalV9,
20
+ ManagedAssignmentKey,
21
+ ManagedRouteJournal,
22
+ PreviousAssignment,
23
+ PreviousFeatureAssignment,
24
+ PreviousAgentAssignment,
25
+ } from "./codex-integration-shared";
26
+ import {
27
+ restoreCodexInterruptHook,
28
+ verifyCodexInterruptHook,
29
+ verifyCodexInterruptHookRestored,
30
+ } from "./codex-interrupt-hook";
31
+ import {
32
+ assignments,
33
+ findFeatureAssignment,
34
+ findAgentMaxDepthAssignment,
35
+ findMultiAgentV2Assignment,
36
+ findTopLevelAssignment,
37
+ firstTableIndex,
38
+ insertDocumentLine,
39
+ managedMultiAgentV2AssignmentLine,
40
+ parseDocument,
41
+ removeDocumentLine,
42
+ removeManagedComment,
43
+ renderDocument,
44
+ restoreBooleanFeature,
45
+ restoreCompatibilityV1Features,
46
+ restoreCompatibilityV1AgentDepth,
47
+ restoreManagedFeatures,
48
+ restoreMultiAgentV2Feature,
49
+ splitLines,
50
+ verifyInstalledFeatures,
51
+ verifyCompatibilityV1Features,
52
+ } from "./codex-integration-document";
53
+
54
+ function compatibilityV1Evidence(
55
+ journal: CodexIntegrationJournal | LegacyCodexIntegrationJournalV9 | LegacyCodexIntegrationJournalV8,
56
+ ): {
57
+ previousMultiAgent: PreviousFeatureAssignment;
58
+ previousMultiAgentV2: PreviousFeatureAssignment;
59
+ previousAgentMaxDepth: PreviousAgentAssignment;
60
+ installedAgentMaxDepth: number;
61
+ } | undefined {
62
+ if (journal.installed.subagent_protocol !== "compatibility-v1") return undefined;
63
+ if (!journal.previousMultiAgent || !journal.previousMultiAgentV2
64
+ || !journal.previousAgentMaxDepth || journal.installed.agent_max_depth === undefined) {
65
+ throw new Error("Codex integration journal is missing the Compatibility V1 feature baseline");
66
+ }
67
+ return {
68
+ previousMultiAgent: journal.previousMultiAgent,
69
+ previousMultiAgentV2: journal.previousMultiAgentV2,
70
+ previousAgentMaxDepth: journal.previousAgentMaxDepth,
71
+ installedAgentMaxDepth: journal.installed.agent_max_depth,
72
+ };
73
+ }
74
+
75
+ function restoreOwnedManagedFeatures(text: string, journal: ManagedRouteJournal): string {
76
+ let restored = text;
77
+ if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
78
+ const evidence = compatibilityV1Evidence(journal);
79
+ if (evidence) {
80
+ const depth = findAgentMaxDepthAssignment(splitLines(restored));
81
+ if (depth.rawLine === managedAgentMaxDepthLine(evidence.installedAgentMaxDepth)
82
+ && depth.value === String(evidence.installedAgentMaxDepth)) {
83
+ restored = restoreCompatibilityV1AgentDepth(
84
+ restored,
85
+ evidence.previousAgentMaxDepth,
86
+ evidence.installedAgentMaxDepth,
87
+ );
88
+ }
89
+ const multiAgentV2 = findMultiAgentV2Assignment(splitLines(restored));
90
+ const managedV2Line = managedMultiAgentV2AssignmentLine(evidence.previousMultiAgentV2);
91
+ if (multiAgentV2.rawLine === managedV2Line && multiAgentV2.value === "false") {
92
+ restored = restoreMultiAgentV2Feature(restored, evidence.previousMultiAgentV2);
93
+ }
94
+ const multiAgent = findFeatureAssignment(splitLines(restored), "multi_agent");
95
+ if (multiAgent.rawLine === MANAGED_MULTI_AGENT_LINE && multiAgent.value === "true") {
96
+ restored = restoreBooleanFeature(
97
+ restored,
98
+ "multi_agent",
99
+ "true",
100
+ MANAGED_MULTI_AGENT_LINE,
101
+ evidence.previousMultiAgent,
102
+ );
103
+ }
104
+ }
105
+ }
106
+ if (journal.version === 6) {
107
+ const current = findMultiAgentV2Assignment(splitLines(restored));
108
+ const managedLine = managedMultiAgentV2AssignmentLine(journal.previousMultiAgentV2);
109
+ if (current.rawLine === managedLine && current.value === "false") {
110
+ restored = restoreMultiAgentV2Feature(restored, journal.previousMultiAgentV2);
111
+ }
112
+ }
113
+ if (journal.version === 5 || journal.version === 6) {
114
+ const multiAgent = findFeatureAssignment(splitLines(restored), "multi_agent");
115
+ if (multiAgent.rawLine === MANAGED_MULTI_AGENT_LINE && multiAgent.value === "true") {
116
+ restored = restoreBooleanFeature(
117
+ restored,
118
+ "multi_agent",
119
+ "true",
120
+ MANAGED_MULTI_AGENT_LINE,
121
+ journal.previousMultiAgent,
122
+ );
123
+ }
124
+ const compaction = findFeatureAssignment(splitLines(restored), "remote_compaction_v2");
125
+ if (compaction.rawLine === MANAGED_REMOTE_COMPACTION_LINE && compaction.value === "false") {
126
+ restored = restoreBooleanFeature(
127
+ restored,
128
+ "remote_compaction_v2",
129
+ "false",
130
+ MANAGED_REMOTE_COMPACTION_LINE,
131
+ journal.previousRemoteCompactionV2,
132
+ );
133
+ }
134
+ }
135
+ return restored;
136
+ }
137
+ function restoreStillManagedRouteAssignments(text: string, journal: ManagedRouteJournal): string {
138
+ const document = parseDocument(text);
139
+ removeManagedComment(document);
140
+ const current = assignments(document.lines);
141
+ const target = Object.fromEntries(
142
+ (Object.keys(current) as ManagedAssignmentKey[]).map(key => {
143
+ const stillManaged = key === "openai_base_url"
144
+ ? current[key].present && current[key].value === journal.installed.openai_base_url
145
+ : !current[key].present;
146
+ return [key, stillManaged ? journal.previous[key] : current[key]];
147
+ }),
148
+ ) as Record<ManagedAssignmentKey, PreviousAssignment>;
149
+ const currentIndices = Object.values(current)
150
+ .flatMap(assignment => assignment.index === undefined ? [] : [assignment.index])
151
+ .sort((left, right) => right - left);
152
+ for (const index of currentIndices) removeDocumentLine(document, index);
153
+
154
+ const previous = (Object.entries(target) as Array<[ManagedAssignmentKey, PreviousAssignment]>)
155
+ .filter(([, assignment]) => assignment.present)
156
+ .sort(([, left], [, right]) => (left.index ?? Number.MAX_SAFE_INTEGER) - (right.index ?? Number.MAX_SAFE_INTEGER));
157
+ for (const [key, assignment] of previous) {
158
+ if (!assignment.rawLine) throw new Error(`Codex integration journal is missing the prior ${key} line`);
159
+ const index = Math.min(assignment.index ?? firstTableIndex(document.lines), firstTableIndex(document.lines));
160
+ insertDocumentLine(document, index, assignment.rawLine);
161
+ }
162
+ return renderDocument(document);
163
+ }
164
+ export function managedJournalIsActive(journal: ManagedRouteJournal): boolean {
165
+ return journal.version === 3 || journal.active;
166
+ }
167
+
168
+ export function verifyManagedJournalState(text: string, journal: ManagedRouteJournal): void {
169
+ if (journal.version === 3 || journal.active) verifyInstalledRoute(text, journal);
170
+ else verifyRestoredRoute(text, journal);
171
+ }
172
+
173
+ export function replacementBaseline(
174
+ currentText: string,
175
+ configExists: boolean,
176
+ journal: ManagedRouteJournal,
177
+ ): string {
178
+ if (!configExists) return "";
179
+ if (!managedJournalIsActive(journal)) return currentText;
180
+
181
+ if (journal.version === 9 || journal.version === 10) {
182
+ const withoutHook = journal.version === 10
183
+ ? restoreCodexInterruptHook(currentText, journal.interruptHook)
184
+ : currentText;
185
+ const baseline = restoreOwnedManagedFeatures(withoutHook, journal);
186
+ const document = parseDocument(baseline);
187
+ removeManagedComment(document);
188
+ for (const [key, installedValue, previous] of [
189
+ ["openai_base_url", journal.installed.openai_base_url, journal.previous.openai_base_url],
190
+ [
191
+ "experimental_realtime_webrtc_call_base_url",
192
+ journal.installed.experimental_realtime_webrtc_call_base_url,
193
+ journal.previousRealtimeWebrtcCallBaseUrl,
194
+ ],
195
+ ] as const) {
196
+ const current = findTopLevelAssignment(document.lines, key);
197
+ if (current.value !== installedValue || current.index === undefined) continue;
198
+ if (previous.present) {
199
+ if (!previous.rawLine) throw new Error(`Codex integration journal is missing the prior ${key} line`);
200
+ document.lines[current.index] = previous.rawLine;
201
+ } else {
202
+ removeDocumentLine(document, current.index);
203
+ }
204
+ }
205
+ return renderDocument(document);
206
+ }
207
+
208
+ if (journal.version === 7 || journal.version === 8) {
209
+ const baseline = restoreOwnedManagedFeatures(currentText, journal);
210
+ const document = parseDocument(baseline);
211
+ removeManagedComment(document);
212
+ const current = findTopLevelAssignment(document.lines, "openai_base_url");
213
+ if (current.value === journal.installed.openai_base_url && current.index !== undefined) {
214
+ const previous = journal.previous.openai_base_url;
215
+ if (previous.present) {
216
+ if (!previous.rawLine) throw new Error("Codex integration journal is missing the prior openai_base_url line");
217
+ document.lines[current.index] = previous.rawLine;
218
+ } else {
219
+ removeDocumentLine(document, current.index);
220
+ }
221
+ }
222
+ return renderDocument(document);
223
+ }
224
+
225
+ let baseline = restoreOwnedManagedFeatures(currentText, journal);
226
+ baseline = restoreStillManagedRouteAssignments(baseline, journal);
227
+ return baseline;
228
+ }
229
+
230
+ export function installRoute(
231
+ text: string,
232
+ installedUrl: string,
233
+ replaceExistingRoute: boolean,
234
+ replaceExistingRealtimeRoute: boolean,
235
+ ): {
236
+ text: string;
237
+ previous: CodexIntegrationJournal["previous"];
238
+ previousRealtimeWebrtcCallBaseUrl: PreviousAssignment;
239
+ } {
240
+ const document = parseDocument(text);
241
+ const previous = assignments(document.lines);
242
+ if (previous.openai_base_url.present && !replaceExistingRoute) {
243
+ throw new Error(
244
+ `Codex already configures model routing (openai_base_url=${JSON.stringify(previous.openai_base_url.value)}). `
245
+ + "Rerun with --replace-codex-route to replace it reversibly. "
246
+ + "Check whether another Codex extension or wrapper (for example, OpenCodex or Headroom) is replacing the bridge port.",
247
+ );
248
+ }
249
+ const previousRealtimeWebrtcCallBaseUrl = findTopLevelAssignment(
250
+ document.lines,
251
+ "experimental_realtime_webrtc_call_base_url",
252
+ );
253
+ if (previousRealtimeWebrtcCallBaseUrl.present
254
+ && previousRealtimeWebrtcCallBaseUrl.value !== CODEX_REALTIME_WEBRTC_CALL_BASE_URL
255
+ && !replaceExistingRealtimeRoute) {
256
+ throw new Error(
257
+ "Codex already configures its realtime WebRTC call route "
258
+ + `(experimental_realtime_webrtc_call_base_url=${JSON.stringify(previousRealtimeWebrtcCallBaseUrl.value)}). `
259
+ + "Rerun with --replace-codex-route to replace it reversibly.",
260
+ );
261
+ }
262
+
263
+ const currentBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
264
+ if (currentBaseUrl.index !== undefined) {
265
+ document.lines[currentBaseUrl.index] = `openai_base_url = ${JSON.stringify(installedUrl)}`;
266
+ } else {
267
+ insertDocumentLine(document, firstTableIndex(document.lines), `openai_base_url = ${JSON.stringify(installedUrl)}`);
268
+ }
269
+ const currentRealtimeUrl = findTopLevelAssignment(document.lines, "experimental_realtime_webrtc_call_base_url");
270
+ const realtimeLine = `experimental_realtime_webrtc_call_base_url = ${JSON.stringify(CODEX_REALTIME_WEBRTC_CALL_BASE_URL)}`;
271
+ if (currentRealtimeUrl.index !== undefined) {
272
+ document.lines[currentRealtimeUrl.index] = realtimeLine;
273
+ } else {
274
+ const installedBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
275
+ insertDocumentLine(document, installedBaseUrl.index! + 1, realtimeLine);
276
+ }
277
+ removeManagedComment(document);
278
+ const installedBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
279
+ insertDocumentLine(document, installedBaseUrl.index!, MANAGED_ROUTE_COMMENT);
280
+ return { text: renderDocument(document), previous, previousRealtimeWebrtcCallBaseUrl };
281
+ }
282
+
283
+ export function verifyInstalledRoute(text: string, journal: ManagedRouteJournal): void {
284
+ const lines = splitLines(text);
285
+ const current = assignments(lines);
286
+ if (current.openai_base_url.value !== journal.installed.openai_base_url) {
287
+ throw new Error("Codex openai_base_url changed after setup; refusing to overwrite the user's newer value");
288
+ }
289
+ const expectedMarker = journal.version === 9 || journal.version === 10
290
+ ? MANAGED_ROUTE_COMMENT
291
+ : MANAGED_COMMENT;
292
+ if (!lines.includes(expectedMarker)) {
293
+ throw new Error("Managed Codex route marker changed after setup; refusing to overwrite it");
294
+ }
295
+ if (journal.version === 9 || journal.version === 10) {
296
+ const realtime = findTopLevelAssignment(lines, "experimental_realtime_webrtc_call_base_url");
297
+ const expectedLine = `experimental_realtime_webrtc_call_base_url = ${JSON.stringify(journal.installed.experimental_realtime_webrtc_call_base_url)}`;
298
+ if (realtime.value !== journal.installed.experimental_realtime_webrtc_call_base_url
299
+ || realtime.rawLine !== expectedLine) {
300
+ throw new Error("Codex realtime WebRTC call route changed after setup; refusing to overwrite the user's newer value");
301
+ }
302
+ }
303
+ if (journal.version === 10) verifyCodexInterruptHook(text, journal.interruptHook);
304
+ if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
305
+ const evidence = compatibilityV1Evidence(journal);
306
+ if (evidence) {
307
+ verifyCompatibilityV1Features(
308
+ text,
309
+ evidence.previousMultiAgentV2,
310
+ evidence.installedAgentMaxDepth,
311
+ );
312
+ }
313
+ }
314
+ if (journal.version !== 7 && journal.version !== 8 && journal.version !== 9 && journal.version !== 10) {
315
+ if (current.model_provider.present || current.model_catalog_json.present) {
316
+ throw new Error("Codex model_provider or model_catalog_json changed after setup; refusing to overwrite the user's newer value");
317
+ }
318
+ if (journal.version === 5 || journal.version === 6) verifyInstalledFeatures(text, journal);
319
+ }
320
+ }
321
+
322
+ function previousAssignmentMatches(current: PreviousAssignment, previous: PreviousAssignment): boolean {
323
+ return current.present === previous.present
324
+ && (!current.present || current.value === previous.value);
325
+ }
326
+
327
+ function previousAssignmentMatchesExactly(current: PreviousAssignment, previous: PreviousAssignment): boolean {
328
+ return current.present === previous.present
329
+ && (!current.present || (current.value === previous.value && current.rawLine === previous.rawLine));
330
+ }
331
+
332
+ export function verifyRestoredRoute(
333
+ text: string,
334
+ journal: CodexIntegrationJournal | LegacyCodexIntegrationJournalV9 | LegacyCodexIntegrationJournalV8 | LegacyCodexIntegrationJournalV7 | LegacyCodexIntegrationJournalV6 | LegacyCodexIntegrationJournalV5 | LegacyCodexIntegrationJournalV4,
335
+ ): void {
336
+ const lines = splitLines(text);
337
+ const current = assignments(lines);
338
+ const keys = journal.version === 7 || journal.version === 8 || journal.version === 9 || journal.version === 10
339
+ ? (["openai_base_url"] as const)
340
+ : (["openai_base_url", "model_provider", "model_catalog_json"] as const);
341
+ for (const key of keys) {
342
+ if (!previousAssignmentMatches(current[key], journal.previous[key])) {
343
+ throw new Error(`Codex ${key} changed while the bridge was disconnected; refusing to overwrite the user's newer value`);
344
+ }
345
+ }
346
+ if (lines.includes(MANAGED_COMMENT) || lines.includes(MANAGED_ROUTE_COMMENT)) {
347
+ throw new Error("Managed Codex route marker is present while the bridge is disconnected");
348
+ }
349
+ if (journal.version === 9 || journal.version === 10) {
350
+ const realtime = findTopLevelAssignment(lines, "experimental_realtime_webrtc_call_base_url");
351
+ if (!previousAssignmentMatchesExactly(realtime, journal.previousRealtimeWebrtcCallBaseUrl)) {
352
+ throw new Error(
353
+ "Codex realtime WebRTC call route changed while the bridge was disconnected; refusing to overwrite the user's newer value",
354
+ );
355
+ }
356
+ }
357
+ if (journal.version === 10) verifyCodexInterruptHookRestored(text);
358
+ if (journal.version === 5 || journal.version === 6) {
359
+ const previousFeatures: Array<readonly [string, PreviousFeatureAssignment]> = [
360
+ ["remote_compaction_v2", journal.previousRemoteCompactionV2],
361
+ ["multi_agent", journal.previousMultiAgent],
362
+ ];
363
+ if (journal.version === 6) {
364
+ previousFeatures.push(["multi_agent_v2", journal.previousMultiAgentV2]);
365
+ }
366
+ for (const [key, previous] of previousFeatures) {
367
+ const current = key === "multi_agent_v2"
368
+ ? findMultiAgentV2Assignment(lines)
369
+ : findFeatureAssignment(lines, key);
370
+ const matches = current.present === previous.present
371
+ && (current.tableName ?? "features") === (previous.tableName ?? "features")
372
+ && (!current.present || current.rawLine === previous.rawLine);
373
+ if (!matches) {
374
+ throw new Error(
375
+ `Codex [features].${key} changed while the bridge was disconnected; refusing to overwrite the user's newer value`,
376
+ );
377
+ }
378
+ }
379
+ }
380
+ if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
381
+ const evidence = compatibilityV1Evidence(journal);
382
+ if (evidence) {
383
+ for (const [key, previous] of [
384
+ ["multi_agent", evidence.previousMultiAgent],
385
+ ["multi_agent_v2", evidence.previousMultiAgentV2],
386
+ ] as const) {
387
+ const current = key === "multi_agent_v2"
388
+ ? findMultiAgentV2Assignment(lines)
389
+ : findFeatureAssignment(lines, key);
390
+ const matches = current.present === previous.present
391
+ && (!current.present || (
392
+ (current.tableName ?? "features") === (previous.tableName ?? "features")
393
+ && current.rawLine === previous.rawLine
394
+ ));
395
+ if (!matches) {
396
+ throw new Error(
397
+ `Codex [features].${key} changed while Compatibility V1 was disconnected; refusing to overwrite the user's newer value`,
398
+ );
399
+ }
400
+ }
401
+ const depth = findAgentMaxDepthAssignment(lines);
402
+ const previousDepth = evidence.previousAgentMaxDepth;
403
+ const depthMatches = depth.present === previousDepth.present
404
+ && (!depth.present || depth.rawLine === previousDepth.rawLine);
405
+ if (!depthMatches) {
406
+ throw new Error(
407
+ "Codex [agents].max_depth changed while Compatibility V1 was disconnected; refusing to overwrite the user's newer value",
408
+ );
409
+ }
410
+ }
411
+ }
412
+ }
413
+
414
+ export function assertPreservedPreviousAssignments(
415
+ actual: CodexIntegrationJournal["previous"],
416
+ expected: CodexIntegrationJournal["previous"],
417
+ ): void {
418
+ if (!previousAssignmentMatches(actual.openai_base_url, expected.openai_base_url)) {
419
+ throw new Error("Codex openai_base_url changed while the bridge was disconnected; refusing to replace it");
420
+ }
421
+ }
422
+
423
+ export function assertPreservedPreviousRealtimeAssignment(
424
+ actual: PreviousAssignment,
425
+ expected: PreviousAssignment,
426
+ ): void {
427
+ if (!previousAssignmentMatchesExactly(actual, expected)) {
428
+ throw new Error("Codex realtime WebRTC call route changed while the bridge was disconnected; refusing to replace it");
429
+ }
430
+ }
431
+
432
+ export function restoreManagedRoute(text: string, journal: ManagedRouteJournal): string {
433
+ verifyInstalledRoute(text, journal);
434
+ const withoutHook = journal.version === 10
435
+ ? restoreCodexInterruptHook(text, journal.interruptHook)
436
+ : text;
437
+ const document = parseDocument(withoutHook);
438
+ removeManagedComment(document);
439
+ const currentBaseUrl = findTopLevelAssignment(document.lines, "openai_base_url");
440
+ if (currentBaseUrl.index === undefined) throw new Error("Managed Codex openai_base_url is missing");
441
+ const previousBaseUrl = journal.previous.openai_base_url;
442
+ if (previousBaseUrl.present) {
443
+ if (!previousBaseUrl.rawLine) throw new Error("Codex integration journal is missing the prior openai_base_url line");
444
+ document.lines[currentBaseUrl.index] = previousBaseUrl.rawLine;
445
+ } else {
446
+ removeDocumentLine(document, currentBaseUrl.index);
447
+ }
448
+ if (journal.version === 9 || journal.version === 10) {
449
+ const currentRealtime = findTopLevelAssignment(document.lines, "experimental_realtime_webrtc_call_base_url");
450
+ if (currentRealtime.index === undefined) throw new Error("Managed Codex realtime WebRTC call route is missing");
451
+ const previousRealtime = journal.previousRealtimeWebrtcCallBaseUrl;
452
+ if (previousRealtime.present) {
453
+ if (!previousRealtime.rawLine) {
454
+ throw new Error("Codex integration journal is missing the prior realtime WebRTC call route line");
455
+ }
456
+ document.lines[currentRealtime.index] = previousRealtime.rawLine;
457
+ } else {
458
+ removeDocumentLine(document, currentRealtime.index);
459
+ }
460
+ }
461
+ if (journal.version !== 7 && journal.version !== 8 && journal.version !== 9 && journal.version !== 10) {
462
+ const removedAssignments = (["model_provider", "model_catalog_json"] as const)
463
+ .map(key => ({ key, previous: journal.previous[key] }))
464
+ .filter(item => item.previous.present)
465
+ .sort((left, right) => (left.previous.index ?? Number.MAX_SAFE_INTEGER) - (right.previous.index ?? Number.MAX_SAFE_INTEGER));
466
+ for (const item of removedAssignments) {
467
+ if (!item.previous.rawLine) throw new Error(`Codex integration journal is missing the prior ${item.key} line`);
468
+ const index = Math.min(item.previous.index ?? firstTableIndex(document.lines), firstTableIndex(document.lines));
469
+ insertDocumentLine(document, index, item.previous.rawLine);
470
+ }
471
+ }
472
+ const restoredRoute = renderDocument(document);
473
+ if (journal.version === 8 || journal.version === 9 || journal.version === 10) {
474
+ const evidence = compatibilityV1Evidence(journal);
475
+ return evidence
476
+ ? restoreCompatibilityV1Features(
477
+ restoredRoute,
478
+ evidence.previousMultiAgent,
479
+ evidence.previousMultiAgentV2,
480
+ evidence.previousAgentMaxDepth,
481
+ evidence.installedAgentMaxDepth,
482
+ )
483
+ : restoredRoute;
484
+ }
485
+ return journal.version === 5 || journal.version === 6
486
+ ? restoreManagedFeatures(restoredRoute, journal)
487
+ : restoredRoute;
488
+ }
489
+
490
+ export function restoreLegacyV2(text: string, journal: LegacyCodexIntegrationJournal): string {
491
+ if (!text.includes(journal.providerBlock)) {
492
+ throw new Error("Managed legacy Codex provider block changed after setup; refusing migration");
493
+ }
494
+ const withoutProvider = text.replace(journal.providerBlock, "").replace(/\n{3,}/g, "\n\n");
495
+ const document = parseDocument(withoutProvider);
496
+ for (const key of ["model_provider", "model_catalog_json"] as const) {
497
+ const current = findTopLevelAssignment(document.lines, key);
498
+ if (current.value !== journal.installed[key] || current.index === undefined) {
499
+ throw new Error(`Managed legacy Codex ${key} changed after setup; refusing migration`);
500
+ }
501
+ const previous = journal.previous[key];
502
+ if (previous.present) {
503
+ if (!previous.rawLine) throw new Error(`Legacy Codex integration journal is missing the prior ${key} line`);
504
+ document.lines[current.index] = previous.rawLine;
505
+ } else {
506
+ removeDocumentLine(document, current.index);
507
+ }
508
+ }
509
+ removeManagedComment(document);
510
+ const restoredCatalog = findTopLevelAssignment(document.lines, "model_catalog_json");
511
+ if (restoredCatalog.index !== undefined && restoredCatalog.value && !existsSync(resolve(restoredCatalog.value))) {
512
+ removeDocumentLine(document, restoredCatalog.index);
513
+ }
514
+ return renderDocument(document);
515
+ }