@iam-brain/opencode-codex-auth 1.7.1 → 1.8.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.
Files changed (48) hide show
  1. package/dist/index.d.ts.map +1 -1
  2. package/dist/index.js +2 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/lib/codex-native/acquire-auth.d.ts +2 -0
  5. package/dist/lib/codex-native/acquire-auth.d.ts.map +1 -1
  6. package/dist/lib/codex-native/acquire-auth.js +52 -0
  7. package/dist/lib/codex-native/acquire-auth.js.map +1 -1
  8. package/dist/lib/codex-native/openai-loader-fetch.d.ts +2 -0
  9. package/dist/lib/codex-native/openai-loader-fetch.d.ts.map +1 -1
  10. package/dist/lib/codex-native/openai-loader-fetch.js +87 -1
  11. package/dist/lib/codex-native/openai-loader-fetch.js.map +1 -1
  12. package/dist/lib/codex-native/request-transform-model-service-tier.js +1 -1
  13. package/dist/lib/codex-native/request-transform-model-service-tier.js.map +1 -1
  14. package/dist/lib/codex-native/request-transform-model.js +1 -1
  15. package/dist/lib/codex-native/request-transform-model.js.map +1 -1
  16. package/dist/lib/codex-native.d.ts +1 -0
  17. package/dist/lib/codex-native.d.ts.map +1 -1
  18. package/dist/lib/codex-native.js +17 -3
  19. package/dist/lib/codex-native.js.map +1 -1
  20. package/dist/lib/config/file.d.ts.map +1 -1
  21. package/dist/lib/config/file.js +3 -0
  22. package/dist/lib/config/file.js.map +1 -1
  23. package/dist/lib/config/resolve.d.ts +1 -0
  24. package/dist/lib/config/resolve.d.ts.map +1 -1
  25. package/dist/lib/config/resolve.js +5 -0
  26. package/dist/lib/config/resolve.js.map +1 -1
  27. package/dist/lib/config/types.d.ts +3 -2
  28. package/dist/lib/config/types.d.ts.map +1 -1
  29. package/dist/lib/config/types.js +11 -5
  30. package/dist/lib/config/types.js.map +1 -1
  31. package/dist/lib/config.d.ts +1 -1
  32. package/dist/lib/config.d.ts.map +1 -1
  33. package/dist/lib/config.js +1 -1
  34. package/dist/lib/config.js.map +1 -1
  35. package/dist/lib/model-catalog/shared.d.ts +2 -2
  36. package/dist/lib/model-catalog/shared.d.ts.map +1 -1
  37. package/dist/lib/model-catalog/shared.js +4 -8
  38. package/dist/lib/model-catalog/shared.js.map +1 -1
  39. package/dist/lib/paths.d.ts +2 -0
  40. package/dist/lib/paths.d.ts.map +1 -1
  41. package/dist/lib/paths.js +4 -0
  42. package/dist/lib/paths.js.map +1 -1
  43. package/dist/lib/shareable-debug.d.ts +105 -0
  44. package/dist/lib/shareable-debug.d.ts.map +1 -0
  45. package/dist/lib/shareable-debug.js +1071 -0
  46. package/dist/lib/shareable-debug.js.map +1 -0
  47. package/package.json +2 -2
  48. package/schemas/codex-config.schema.json +3 -0
@@ -0,0 +1,1071 @@
1
+ import { createHmac, randomBytes, randomUUID } from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import fsSync from "node:fs";
4
+ import path from "node:path";
5
+ import { enforceOwnerOnlyPermissions, isFsErrorCode, readJsonFileBestEffort, writeJsonFileAtomic, writeJsonFileAtomicBestEffort } from "./cache-io.js";
6
+ import { defaultShareableDebugLogPath } from "./paths.js";
7
+ const PROCESS_SECRET = randomBytes(32);
8
+ const DEFAULT_SUMMARY_MAX_BYTES = 256 * 1024;
9
+ const DEFAULT_SUMMARY_MAX_FILES = 2;
10
+ const DEFAULT_SEGMENT_MAX_BYTES = 16 * 1024;
11
+ const DEFAULT_ROLLING_BUFFER_MAX_BYTES = 256 * 1024;
12
+ const DEFAULT_PRE_TRIGGER_EVENT_COUNT = 40;
13
+ const DEFAULT_POST_TRIGGER_EVENT_COUNT = 20;
14
+ const DEFAULT_MAX_INCIDENT_FILES = 8;
15
+ const DEFAULT_MAX_INCIDENT_BYTES = 512 * 1024;
16
+ function pseudonym(prefix, raw) {
17
+ const normalized = raw?.trim();
18
+ if (!normalized)
19
+ return undefined;
20
+ const digest = createHmac("sha256", PROCESS_SECRET).update(normalized).digest("hex").slice(0, 8);
21
+ return `${prefix}_${digest}`;
22
+ }
23
+ function normalizeEndpoint(input) {
24
+ if (!input)
25
+ return undefined;
26
+ try {
27
+ return new URL(input).pathname || undefined;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ async function extractPromptCacheKey(request) {
34
+ try {
35
+ const raw = await request.clone().text();
36
+ if (!raw)
37
+ return undefined;
38
+ try {
39
+ const parsed = JSON.parse(raw);
40
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
41
+ const candidate = parsed.prompt_cache_key;
42
+ return typeof candidate === "string" && candidate.trim().length > 0 ? candidate : undefined;
43
+ }
44
+ return undefined;
45
+ }
46
+ catch {
47
+ const params = new URLSearchParams(raw);
48
+ const candidate = params.get("prompt_cache_key");
49
+ return candidate && candidate.trim().length > 0 ? candidate : undefined;
50
+ }
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ function createNoopShareableDebugLogger() {
57
+ const noop = async () => { };
58
+ return {
59
+ enabled: false,
60
+ emitRotationBegin: noop,
61
+ emitRotationDecision: noop,
62
+ emitRotationCandidateSelected: noop,
63
+ emitFetchAttemptRequest: noop,
64
+ emitFetchAttemptResponse: noop,
65
+ emitRetryAfter429: noop,
66
+ emitAuthFailure: noop,
67
+ emitSyntheticFatalError: noop
68
+ };
69
+ }
70
+ function defaultStateDirForLogPath(filePath) {
71
+ const parsed = path.parse(filePath);
72
+ return path.join(parsed.dir, `${parsed.name}-state`);
73
+ }
74
+ function parsePositiveInteger(value, fallback) {
75
+ if (typeof value !== "number" || !Number.isFinite(value))
76
+ return fallback;
77
+ return Math.max(1, Math.floor(value));
78
+ }
79
+ function segmentFileName(startSeq) {
80
+ return `segment-${startSeq.toString().padStart(16, "0")}.jsonl`;
81
+ }
82
+ function incidentFileName(incidentId, timestamp) {
83
+ const stamp = timestamp.replaceAll(/[:.]/g, "-");
84
+ return `incident-${stamp}-${incidentId}.jsonl`;
85
+ }
86
+ function sortLexicallyAscending(values) {
87
+ return [...values].sort((left, right) => left.localeCompare(right));
88
+ }
89
+ function incidentFileReference(filePath) {
90
+ return path.basename(filePath);
91
+ }
92
+ function contiguousWindow(rows, startSeq, maxCount) {
93
+ const deduped = Array.from(new Map(rows.map((row) => [row.seq, row])).values()).sort((left, right) => left.seq - right.seq);
94
+ const window = [];
95
+ let expectedSeq = startSeq;
96
+ for (const row of deduped) {
97
+ if (window.length >= maxCount)
98
+ break;
99
+ if (row.seq < expectedSeq)
100
+ continue;
101
+ if (row.seq > expectedSeq) {
102
+ return {
103
+ rows: window,
104
+ hasGapAfterPrefix: true
105
+ };
106
+ }
107
+ window.push(row);
108
+ expectedSeq += 1;
109
+ }
110
+ return {
111
+ rows: window,
112
+ hasGapAfterPrefix: false
113
+ };
114
+ }
115
+ function parseSegmentStartSeq(filePath) {
116
+ const match = /^segment-(\d+)\.jsonl$/u.exec(path.basename(filePath));
117
+ if (!match)
118
+ return undefined;
119
+ const value = Number.parseInt(match[1] ?? "", 10);
120
+ return Number.isFinite(value) && value >= 1 ? value : undefined;
121
+ }
122
+ async function readJsonlFile(filePath) {
123
+ try {
124
+ const raw = await fs.readFile(filePath, "utf8");
125
+ const lines = raw
126
+ .split("\n")
127
+ .map((line) => line.trim())
128
+ .filter(Boolean);
129
+ const records = [];
130
+ let hadTruncatedTail = false;
131
+ for (const [index, line] of lines.entries()) {
132
+ try {
133
+ records.push(JSON.parse(line));
134
+ }
135
+ catch (error) {
136
+ if (index === lines.length - 1) {
137
+ hadTruncatedTail = true;
138
+ break;
139
+ }
140
+ throw error;
141
+ }
142
+ }
143
+ return {
144
+ records,
145
+ hadTruncatedTail
146
+ };
147
+ }
148
+ catch (error) {
149
+ if (isFsErrorCode(error, "ENOENT")) {
150
+ return {
151
+ records: [],
152
+ hadTruncatedTail: false
153
+ };
154
+ }
155
+ throw error;
156
+ }
157
+ }
158
+ function readJsonlFileSync(filePath) {
159
+ try {
160
+ const raw = fsSync.readFileSync(filePath, "utf8");
161
+ const lines = raw
162
+ .split("\n")
163
+ .map((line) => line.trim())
164
+ .filter(Boolean);
165
+ const records = [];
166
+ let hadTruncatedTail = false;
167
+ for (const [index, line] of lines.entries()) {
168
+ try {
169
+ records.push(JSON.parse(line));
170
+ }
171
+ catch (error) {
172
+ if (index === lines.length - 1) {
173
+ hadTruncatedTail = true;
174
+ break;
175
+ }
176
+ throw error;
177
+ }
178
+ }
179
+ return {
180
+ records,
181
+ hadTruncatedTail
182
+ };
183
+ }
184
+ catch (error) {
185
+ if (isFsErrorCode(error, "ENOENT")) {
186
+ return {
187
+ records: [],
188
+ hadTruncatedTail: false
189
+ };
190
+ }
191
+ throw error;
192
+ }
193
+ }
194
+ async function readJsonlRecords(filePath) {
195
+ return (await readJsonlFile(filePath)).records;
196
+ }
197
+ function readJsonlRecordsSync(filePath) {
198
+ return readJsonlFileSync(filePath).records;
199
+ }
200
+ async function lastSeqFromSegment(filePath) {
201
+ const rows = await readJsonlRecords(filePath);
202
+ const last = rows.at(-1);
203
+ return typeof last?.seq === "number" ? last.seq : 0;
204
+ }
205
+ async function appendJsonlLine(filePath, line) {
206
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
207
+ await fs.appendFile(filePath, line, { mode: 0o600 });
208
+ await enforceOwnerOnlyPermissions(filePath);
209
+ }
210
+ async function writeJsonlLines(filePath, lines) {
211
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
212
+ await fs.writeFile(filePath, lines.join(""), { mode: 0o600 });
213
+ await enforceOwnerOnlyPermissions(filePath);
214
+ }
215
+ function appendJsonlLineSync(filePath, line) {
216
+ fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
217
+ fsSync.appendFileSync(filePath, line, { mode: 0o600 });
218
+ try {
219
+ fsSync.chmodSync(filePath, 0o600);
220
+ }
221
+ catch {
222
+ // best-effort only in crash path
223
+ }
224
+ }
225
+ function writeJsonlLinesSync(filePath, lines) {
226
+ fsSync.mkdirSync(path.dirname(filePath), { recursive: true });
227
+ fsSync.writeFileSync(filePath, lines.join(""), { mode: 0o600 });
228
+ try {
229
+ fsSync.chmodSync(filePath, 0o600);
230
+ }
231
+ catch {
232
+ // best-effort only in crash path
233
+ }
234
+ }
235
+ async function rotateLogFileIfNeeded(filePath, nextLineBytes, maxBytes, maxFiles) {
236
+ const keepFiles = Math.max(1, maxFiles);
237
+ let currentSize = 0;
238
+ try {
239
+ currentSize = (await fs.stat(filePath)).size;
240
+ }
241
+ catch (error) {
242
+ if (!isFsErrorCode(error, "ENOENT"))
243
+ throw error;
244
+ }
245
+ if (currentSize === 0 || currentSize + nextLineBytes <= maxBytes)
246
+ return;
247
+ for (let index = keepFiles - 1; index >= 1; index -= 1) {
248
+ const source = index === 1 ? filePath : `${filePath}.${index - 1}`;
249
+ const dest = `${filePath}.${index}`;
250
+ try {
251
+ await deleteFileIfExists(dest);
252
+ await fs.rename(source, dest);
253
+ }
254
+ catch (error) {
255
+ if (!isFsErrorCode(error, "ENOENT"))
256
+ throw error;
257
+ }
258
+ }
259
+ }
260
+ async function deleteFileIfExists(filePath) {
261
+ try {
262
+ await fs.unlink(filePath);
263
+ }
264
+ catch (error) {
265
+ if (!isFsErrorCode(error, "ENOENT"))
266
+ throw error;
267
+ }
268
+ }
269
+ function isIncidentManifest(value) {
270
+ return (typeof value === "object" &&
271
+ value !== null &&
272
+ "version" in value &&
273
+ value.version === 1 &&
274
+ "incidentId" in value &&
275
+ typeof value.incidentId === "string" &&
276
+ "outputFilePath" in value &&
277
+ typeof value.outputFilePath === "string" &&
278
+ "triggerSeq" in value &&
279
+ typeof value.triggerSeq === "number" &&
280
+ "triggerEvent" in value &&
281
+ typeof value.triggerEvent === "string" &&
282
+ "triggerReason" in value &&
283
+ typeof value.triggerReason === "string" &&
284
+ "preTriggerStartSeq" in value &&
285
+ typeof value.preTriggerStartSeq === "number" &&
286
+ "preTriggerEndSeq" in value &&
287
+ typeof value.preTriggerEndSeq === "number" &&
288
+ "postWindowCount" in value &&
289
+ typeof value.postWindowCount === "number" &&
290
+ "postRemaining" in value &&
291
+ typeof value.postRemaining === "number" &&
292
+ "status" in value &&
293
+ value.status === "open" &&
294
+ "createdAt" in value &&
295
+ typeof value.createdAt === "string" &&
296
+ "updatedAt" in value &&
297
+ typeof value.updatedAt === "string");
298
+ }
299
+ function isManifestlessRecoveryMarker(value) {
300
+ return (typeof value === "object" &&
301
+ value !== null &&
302
+ "version" in value &&
303
+ value.version === 1 &&
304
+ "triggerSeq" in value &&
305
+ typeof value.triggerSeq === "number");
306
+ }
307
+ export function createShareableDebugLogger(input) {
308
+ if (!input.enabled)
309
+ return createNoopShareableDebugLogger();
310
+ const filePath = input.filePath ?? defaultShareableDebugLogPath(input.env);
311
+ const stateDir = input.stateDir ?? defaultStateDirForLogPath(filePath);
312
+ const segmentsDir = path.join(stateDir, "segments");
313
+ const incidentsDir = path.join(stateDir, "incidents");
314
+ const manifestPath = path.join(stateDir, "incident-state.json");
315
+ const manifestlessRecoveryPath = path.join(stateDir, "manifestless-recovery.json");
316
+ const summaryMaxBytes = parsePositiveInteger(input.incidentConfig?.summaryMaxBytes, DEFAULT_SUMMARY_MAX_BYTES);
317
+ const summaryMaxFiles = parsePositiveInteger(input.incidentConfig?.summaryMaxFiles, DEFAULT_SUMMARY_MAX_FILES);
318
+ const segmentMaxBytes = parsePositiveInteger(input.incidentConfig?.segmentMaxBytes, DEFAULT_SEGMENT_MAX_BYTES);
319
+ const rollingBufferMaxBytes = parsePositiveInteger(input.incidentConfig?.rollingBufferMaxBytes, DEFAULT_ROLLING_BUFFER_MAX_BYTES);
320
+ const preTriggerEventCount = parsePositiveInteger(input.incidentConfig?.preTriggerEventCount, DEFAULT_PRE_TRIGGER_EVENT_COUNT);
321
+ const postTriggerEventCount = parsePositiveInteger(input.incidentConfig?.postTriggerEventCount, DEFAULT_POST_TRIGGER_EVENT_COUNT);
322
+ const maxIncidentFiles = parsePositiveInteger(input.incidentConfig?.maxIncidentFiles, DEFAULT_MAX_INCIDENT_FILES);
323
+ const maxIncidentBytes = parsePositiveInteger(input.incidentConfig?.maxIncidentBytes, DEFAULT_MAX_INCIDENT_BYTES);
324
+ let nextSeq = 1;
325
+ let currentSegmentPath;
326
+ let currentSegmentBytes = 0;
327
+ let openIncident;
328
+ let pendingWrite = Promise.resolve();
329
+ let initPromise = Promise.resolve();
330
+ let handlersRegistered = false;
331
+ let signalInFlight = false;
332
+ const sanitizeJsonlTail = async (targetPath) => {
333
+ const result = await readJsonlFile(targetPath);
334
+ if (result.hadTruncatedTail) {
335
+ await writeJsonlLines(targetPath, result.records.map((record) => `${JSON.stringify(record)}\n`));
336
+ }
337
+ return result.records;
338
+ };
339
+ const appendSummaryLifecycleSync = (event, payload) => {
340
+ appendSummaryLineSync(`${JSON.stringify({ timestamp: new Date().toISOString(), event, ...payload })}\n`);
341
+ };
342
+ const reconcileIncidentFile = async (incident) => {
343
+ let incidentRecords;
344
+ try {
345
+ incidentRecords = await sanitizeJsonlTail(incident.outputFilePath);
346
+ }
347
+ catch (error) {
348
+ if (isFsErrorCode(error, "ENOENT")) {
349
+ incidentRecords = [];
350
+ }
351
+ else {
352
+ await sealIncidentIncomplete({
353
+ incident,
354
+ reason: "interrupted"
355
+ });
356
+ return undefined;
357
+ }
358
+ }
359
+ const baseRowsFromSegments = await readEventsInRange(incident.preTriggerStartSeq, incident.preTriggerEndSeq);
360
+ const baseRowsFromIncident = incidentRecords.filter((row) => typeof row.seq === "number" && row.seq >= incident.preTriggerStartSeq && row.seq <= incident.triggerSeq);
361
+ const expectedBaseCount = incident.triggerSeq - incident.preTriggerStartSeq + 1;
362
+ const baseWindow = contiguousWindow([...baseRowsFromIncident, ...baseRowsFromSegments], incident.preTriggerStartSeq, expectedBaseCount);
363
+ if (baseWindow.hasGapAfterPrefix ||
364
+ baseWindow.rows.length !== expectedBaseCount ||
365
+ !baseWindow.rows.some((row) => row.seq === incident.triggerSeq && row.event === incident.triggerEvent)) {
366
+ await sealIncidentIncomplete({
367
+ incident,
368
+ reason: "interrupted"
369
+ });
370
+ return undefined;
371
+ }
372
+ const closedRow = incidentRecords.find((row) => row.event === "incident_closed" && row.incidentId === incident.incidentId);
373
+ if (closedRow) {
374
+ await deleteFileIfExists(manifestPath);
375
+ return undefined;
376
+ }
377
+ const postRowsFromSegments = await readEventsInRange(incident.triggerSeq + 1, incident.triggerSeq + incident.postWindowCount);
378
+ const recoveredPostWindow = contiguousWindow([
379
+ ...incidentRecords.filter((row) => typeof row.seq === "number" && row.seq > incident.triggerSeq && row.event !== "incident_closed"),
380
+ ...postRowsFromSegments
381
+ ], incident.triggerSeq + 1, incident.postWindowCount);
382
+ if (recoveredPostWindow.hasGapAfterPrefix) {
383
+ await sealIncidentIncomplete({
384
+ incident,
385
+ reason: "interrupted"
386
+ });
387
+ return undefined;
388
+ }
389
+ const recoveredPostRows = recoveredPostWindow.rows;
390
+ await writeJsonlLines(incident.outputFilePath, [...baseWindow.rows, ...recoveredPostRows].map((row) => `${JSON.stringify(row)}\n`));
391
+ const postRemaining = Math.max(0, incident.postWindowCount - recoveredPostRows.length);
392
+ const recoveredIncident = {
393
+ ...incident,
394
+ postRemaining,
395
+ updatedAt: new Date().toISOString()
396
+ };
397
+ if (recoveredIncident.postRemaining <= 0) {
398
+ await closeIncident(recoveredIncident, "recovered");
399
+ return undefined;
400
+ }
401
+ await writeManifest(recoveredIncident);
402
+ await appendSummaryLifecycle("incident_recovered", {
403
+ incidentId: recoveredIncident.incidentId,
404
+ incidentFile: incidentFileReference(recoveredIncident.outputFilePath),
405
+ triggerSeq: recoveredIncident.triggerSeq,
406
+ triggerEvent: recoveredIncident.triggerEvent,
407
+ postRemaining: recoveredIncident.postRemaining
408
+ });
409
+ return recoveredIncident;
410
+ };
411
+ const initializeState = async () => {
412
+ await fs.mkdir(segmentsDir, { recursive: true });
413
+ await fs.mkdir(incidentsDir, { recursive: true });
414
+ const segmentFiles = sortLexicallyAscending(await fs.readdir(segmentsDir).catch(() => []));
415
+ const latestSegment = segmentFiles.at(-1);
416
+ let latestSegmentRecord;
417
+ if (latestSegment) {
418
+ currentSegmentPath = path.join(segmentsDir, latestSegment);
419
+ const latestSegmentRows = await sanitizeJsonlTail(currentSegmentPath);
420
+ currentSegmentBytes = (await fs.stat(currentSegmentPath)).size;
421
+ const segmentStartSeq = parseSegmentStartSeq(currentSegmentPath) ?? 1;
422
+ nextSeq = Math.max(segmentStartSeq, (await lastSeqFromSegment(currentSegmentPath)) + 1);
423
+ latestSegmentRecord = latestSegmentRows.at(-1);
424
+ }
425
+ const manifest = await readJsonFileBestEffort(manifestPath);
426
+ const manifestlessRecovery = await readJsonFileBestEffort(manifestlessRecoveryPath);
427
+ if (isManifestlessRecoveryMarker(manifestlessRecovery) &&
428
+ manifestlessRecovery.triggerSeq !== latestSegmentRecord?.seq) {
429
+ await deleteFileIfExists(manifestlessRecoveryPath);
430
+ }
431
+ if (!isIncidentManifest(manifest)) {
432
+ await deleteFileIfExists(manifestPath);
433
+ const triggerReason = latestSegmentRecord ? triggerReasonFor(latestSegmentRecord) : undefined;
434
+ if (latestSegmentRecord &&
435
+ triggerReason &&
436
+ (!isManifestlessRecoveryMarker(manifestlessRecovery) ||
437
+ manifestlessRecovery.triggerSeq !== latestSegmentRecord.seq)) {
438
+ const incident = createIncidentManifest(latestSegmentRecord, triggerReason);
439
+ const preTriggerEvents = await readEventsInRange(incident.preTriggerStartSeq, incident.preTriggerEndSeq);
440
+ const expectedBaseCount = incident.triggerSeq - incident.preTriggerStartSeq + 1;
441
+ const baseWindow = contiguousWindow(preTriggerEvents, incident.preTriggerStartSeq, expectedBaseCount);
442
+ await writeJsonlLines(incident.outputFilePath, baseWindow.rows.map((entry) => `${JSON.stringify(entry)}\n`));
443
+ if (baseWindow.hasGapAfterPrefix || baseWindow.rows.length !== expectedBaseCount) {
444
+ await writeJsonFileAtomic(manifestlessRecoveryPath, {
445
+ version: 1,
446
+ triggerSeq: latestSegmentRecord.seq
447
+ });
448
+ await sealIncidentIncomplete({
449
+ incident,
450
+ reason: "interrupted"
451
+ });
452
+ return;
453
+ }
454
+ await writeManifest(incident);
455
+ await deleteFileIfExists(manifestlessRecoveryPath);
456
+ await appendSummaryLifecycle("incident_recovered", {
457
+ incidentId: incident.incidentId,
458
+ incidentFile: incidentFileReference(incident.outputFilePath),
459
+ triggerSeq: incident.triggerSeq,
460
+ triggerEvent: incident.triggerEvent,
461
+ postRemaining: incident.postRemaining
462
+ });
463
+ openIncident = incident;
464
+ await pruneIncidents();
465
+ }
466
+ return;
467
+ }
468
+ openIncident = await reconcileIncidentFile(manifest);
469
+ };
470
+ const runQueued = (work) => {
471
+ pendingWrite = pendingWrite
472
+ .then(() => initPromise)
473
+ .then(work)
474
+ .catch((error) => {
475
+ input.log?.warn("shareable debug write failed", {
476
+ error: error instanceof Error ? error.message : String(error)
477
+ });
478
+ });
479
+ return pendingWrite;
480
+ };
481
+ const writeManifest = async (manifest) => {
482
+ await writeJsonFileAtomic(manifestPath, manifest);
483
+ };
484
+ const writeManifestBestEffort = async (manifest) => {
485
+ await writeJsonFileAtomicBestEffort(manifestPath, manifest);
486
+ };
487
+ const appendSummaryLine = async (line) => {
488
+ await rotateLogFileIfNeeded(filePath, Buffer.byteLength(line), summaryMaxBytes, summaryMaxFiles);
489
+ await appendJsonlLine(filePath, line);
490
+ };
491
+ const appendSummaryLifecycle = async (event, payload) => {
492
+ const line = `${JSON.stringify({ timestamp: new Date().toISOString(), event, ...payload })}\n`;
493
+ await appendSummaryLine(line);
494
+ };
495
+ const appendSummaryLineSync = (line) => {
496
+ try {
497
+ const existingSize = fsSync.existsSync(filePath) ? fsSync.statSync(filePath).size : 0;
498
+ if (existingSize > 0 && existingSize + Buffer.byteLength(line) > summaryMaxBytes) {
499
+ for (let index = summaryMaxFiles - 1; index >= 1; index -= 1) {
500
+ const source = index === 1 ? filePath : `${filePath}.${index - 1}`;
501
+ const dest = `${filePath}.${index}`;
502
+ if (fsSync.existsSync(source)) {
503
+ if (fsSync.existsSync(dest)) {
504
+ fsSync.unlinkSync(dest);
505
+ }
506
+ fsSync.renameSync(source, dest);
507
+ }
508
+ }
509
+ }
510
+ appendJsonlLineSync(filePath, line);
511
+ }
512
+ catch {
513
+ // best-effort only in crash path
514
+ }
515
+ };
516
+ const ensureSegmentForLine = async (lineBytes, seq) => {
517
+ if (!currentSegmentPath || currentSegmentBytes + lineBytes > segmentMaxBytes) {
518
+ currentSegmentPath = path.join(segmentsDir, segmentFileName(seq));
519
+ currentSegmentBytes = 0;
520
+ }
521
+ };
522
+ const pruneSegments = async () => {
523
+ const files = sortLexicallyAscending(await fs.readdir(segmentsDir));
524
+ let totalBytes = 0;
525
+ const entries = [];
526
+ for (const name of files) {
527
+ const filePath = path.join(segmentsDir, name);
528
+ const stat = await fs.stat(filePath);
529
+ entries.push({ name, filePath, size: stat.size });
530
+ totalBytes += stat.size;
531
+ }
532
+ for (const entry of entries) {
533
+ if (totalBytes <= rollingBufferMaxBytes)
534
+ break;
535
+ if (entry.filePath === currentSegmentPath)
536
+ continue;
537
+ await deleteFileIfExists(entry.filePath);
538
+ totalBytes -= entry.size;
539
+ }
540
+ };
541
+ const pruneIncidents = async () => {
542
+ const incidentFiles = sortLexicallyAscending(await fs.readdir(incidentsDir).catch(() => []));
543
+ const entries = [];
544
+ let totalBytes = 0;
545
+ for (const name of incidentFiles) {
546
+ const filePath = path.join(incidentsDir, name);
547
+ const stat = await fs.stat(filePath);
548
+ entries.push({ filePath, name, size: stat.size });
549
+ totalBytes += stat.size;
550
+ }
551
+ for (const entry of entries) {
552
+ if (entries.length <= maxIncidentFiles && totalBytes <= maxIncidentBytes)
553
+ break;
554
+ if (entry.filePath === openIncident?.outputFilePath)
555
+ continue;
556
+ await deleteFileIfExists(entry.filePath);
557
+ totalBytes -= entry.size;
558
+ const index = entries.findIndex((candidate) => candidate.filePath === entry.filePath);
559
+ if (index >= 0) {
560
+ entries.splice(index, 1);
561
+ }
562
+ }
563
+ };
564
+ const appendToSegment = async (record) => {
565
+ const line = `${JSON.stringify(record)}\n`;
566
+ const lineBytes = Buffer.byteLength(line);
567
+ await ensureSegmentForLine(lineBytes, record.seq);
568
+ if (!currentSegmentPath) {
569
+ throw new Error("shareable_debug_missing_segment_path");
570
+ }
571
+ await appendJsonlLine(currentSegmentPath, line);
572
+ currentSegmentBytes += lineBytes;
573
+ await pruneSegments();
574
+ };
575
+ const appendToSegmentSync = (record) => {
576
+ const line = `${JSON.stringify(record)}\n`;
577
+ const lineBytes = Buffer.byteLength(line);
578
+ if (!currentSegmentPath || currentSegmentBytes + lineBytes > segmentMaxBytes) {
579
+ currentSegmentPath = path.join(segmentsDir, segmentFileName(record.seq));
580
+ currentSegmentBytes = 0;
581
+ }
582
+ appendJsonlLineSync(currentSegmentPath, line);
583
+ currentSegmentBytes += lineBytes;
584
+ };
585
+ const readEventsInRange = async (startSeq, endSeq) => {
586
+ const files = sortLexicallyAscending(await fs.readdir(segmentsDir));
587
+ const events = [];
588
+ for (const name of files) {
589
+ const rows = await readJsonlRecords(path.join(segmentsDir, name));
590
+ for (const row of rows) {
591
+ if (typeof row.seq !== "number")
592
+ continue;
593
+ if (row.seq < startSeq || row.seq > endSeq)
594
+ continue;
595
+ events.push(row);
596
+ }
597
+ }
598
+ return events.sort((left, right) => left.seq - right.seq);
599
+ };
600
+ const readEventsInRangeSync = (startSeq, endSeq) => {
601
+ const files = sortLexicallyAscending(fsSync.readdirSync(segmentsDir, "utf8"));
602
+ const events = [];
603
+ for (const name of files) {
604
+ const rows = readJsonlRecordsSync(path.join(segmentsDir, name));
605
+ for (const row of rows) {
606
+ if (typeof row.seq !== "number")
607
+ continue;
608
+ if (row.seq < startSeq || row.seq > endSeq)
609
+ continue;
610
+ events.push(row);
611
+ }
612
+ }
613
+ return events.sort((left, right) => left.seq - right.seq);
614
+ };
615
+ const createIncidentManifest = (record, triggerReason) => {
616
+ const incidentId = randomUUID();
617
+ const timestamp = new Date().toISOString();
618
+ return {
619
+ version: 1,
620
+ incidentId,
621
+ outputFilePath: path.join(incidentsDir, incidentFileName(incidentId, timestamp)),
622
+ triggerSeq: record.seq,
623
+ triggerEvent: record.event,
624
+ triggerReason,
625
+ preTriggerStartSeq: Math.max(1, record.seq - preTriggerEventCount),
626
+ preTriggerEndSeq: record.seq,
627
+ postWindowCount: postTriggerEventCount,
628
+ postRemaining: postTriggerEventCount,
629
+ status: "open",
630
+ createdAt: timestamp,
631
+ updatedAt: timestamp
632
+ };
633
+ };
634
+ const appendIncidentLine = async (incident, record) => {
635
+ await appendJsonlLine(incident.outputFilePath, `${JSON.stringify(record)}\n`);
636
+ };
637
+ const appendIncidentLifecycle = async (incident, event, payload) => {
638
+ await appendJsonlLine(incident.outputFilePath, `${JSON.stringify({
639
+ seq: nextSeq++,
640
+ timestamp: new Date().toISOString(),
641
+ event,
642
+ incidentId: incident.incidentId,
643
+ ...payload
644
+ })}\n`);
645
+ };
646
+ const appendIncidentLifecycleSync = (incident, event, payload) => {
647
+ appendJsonlLineSync(incident.outputFilePath, `${JSON.stringify({
648
+ seq: nextSeq++,
649
+ timestamp: new Date().toISOString(),
650
+ event,
651
+ incidentId: incident.incidentId,
652
+ ...payload
653
+ })}\n`);
654
+ };
655
+ const closeIncident = async (incident, reason) => {
656
+ await appendIncidentLifecycle(incident, "incident_closed", {
657
+ reason,
658
+ triggerSeq: incident.triggerSeq,
659
+ triggerEvent: incident.triggerEvent,
660
+ incomplete: false
661
+ });
662
+ await appendSummaryLifecycle("incident_closed", {
663
+ incidentId: incident.incidentId,
664
+ incidentFile: incidentFileReference(incident.outputFilePath),
665
+ triggerSeq: incident.triggerSeq,
666
+ triggerEvent: incident.triggerEvent,
667
+ reason
668
+ });
669
+ await deleteFileIfExists(manifestPath);
670
+ openIncident = undefined;
671
+ await pruneIncidents();
672
+ };
673
+ const sealIncidentIncomplete = async (inputState) => {
674
+ try {
675
+ await sanitizeJsonlTail(inputState.incident.outputFilePath);
676
+ }
677
+ catch (error) {
678
+ if (isFsErrorCode(error, "ENOENT")) {
679
+ await writeJsonlLines(inputState.incident.outputFilePath, []);
680
+ }
681
+ }
682
+ await writeJsonFileAtomic(manifestlessRecoveryPath, {
683
+ version: 1,
684
+ triggerSeq: inputState.incident.triggerSeq
685
+ });
686
+ await appendIncidentLifecycle(inputState.incident, "incident_closed", {
687
+ reason: inputState.reason,
688
+ triggerSeq: inputState.incident.triggerSeq,
689
+ triggerEvent: inputState.incident.triggerEvent,
690
+ incomplete: true
691
+ });
692
+ await appendSummaryLifecycle("incident_closed", {
693
+ incidentId: inputState.incident.incidentId,
694
+ incidentFile: incidentFileReference(inputState.incident.outputFilePath),
695
+ triggerSeq: inputState.incident.triggerSeq,
696
+ triggerEvent: inputState.incident.triggerEvent,
697
+ reason: inputState.reason,
698
+ incomplete: true
699
+ });
700
+ await deleteFileIfExists(manifestPath);
701
+ };
702
+ const writeManifestSync = (incident) => {
703
+ fsSync.mkdirSync(path.dirname(manifestPath), { recursive: true });
704
+ fsSync.writeFileSync(manifestPath, `${JSON.stringify(incident, null, 2)}\n`, { mode: 0o600 });
705
+ try {
706
+ fsSync.chmodSync(manifestPath, 0o600);
707
+ }
708
+ catch {
709
+ // best-effort only in crash path
710
+ }
711
+ };
712
+ const sealIncidentIncompleteSync = (incident, reason) => {
713
+ try {
714
+ fsSync.mkdirSync(path.dirname(manifestlessRecoveryPath), { recursive: true });
715
+ fsSync.writeFileSync(manifestlessRecoveryPath, `${JSON.stringify({ version: 1, triggerSeq: incident.triggerSeq }, null, 2)}\n`, { mode: 0o600 });
716
+ fsSync.chmodSync(manifestlessRecoveryPath, 0o600);
717
+ }
718
+ catch {
719
+ // best-effort only in crash path
720
+ }
721
+ appendIncidentLifecycleSync(incident, "incident_closed", {
722
+ reason,
723
+ triggerSeq: incident.triggerSeq,
724
+ triggerEvent: incident.triggerEvent,
725
+ incomplete: true
726
+ });
727
+ appendSummaryLifecycleSync("incident_closed", {
728
+ incidentId: incident.incidentId,
729
+ incidentFile: incidentFileReference(incident.outputFilePath),
730
+ triggerSeq: incident.triggerSeq,
731
+ triggerEvent: incident.triggerEvent,
732
+ reason,
733
+ incomplete: true
734
+ });
735
+ try {
736
+ fsSync.unlinkSync(manifestPath);
737
+ }
738
+ catch (error) {
739
+ if (!isFsErrorCode(error, "ENOENT"))
740
+ throw error;
741
+ }
742
+ };
743
+ const openIncidentCaptureSync = (record, triggerReason) => {
744
+ const incident = createIncidentManifest(record, triggerReason);
745
+ writeManifestSync(incident);
746
+ const preTriggerEvents = readEventsInRangeSync(incident.preTriggerStartSeq, record.seq);
747
+ const expectedBaseCount = incident.triggerSeq - incident.preTriggerStartSeq + 1;
748
+ const baseWindow = contiguousWindow(preTriggerEvents, incident.preTriggerStartSeq, expectedBaseCount);
749
+ writeJsonlLinesSync(incident.outputFilePath, baseWindow.rows.map((entry) => `${JSON.stringify(entry)}\n`));
750
+ if (baseWindow.hasGapAfterPrefix || baseWindow.rows.length !== expectedBaseCount) {
751
+ sealIncidentIncompleteSync(incident, "interrupted");
752
+ return incident;
753
+ }
754
+ try {
755
+ fsSync.unlinkSync(manifestlessRecoveryPath);
756
+ }
757
+ catch (error) {
758
+ if (!isFsErrorCode(error, "ENOENT"))
759
+ throw error;
760
+ }
761
+ appendSummaryLifecycleSync("incident_opened", {
762
+ incidentId: incident.incidentId,
763
+ incidentFile: incidentFileReference(incident.outputFilePath),
764
+ triggerSeq: record.seq,
765
+ triggerEvent: record.event,
766
+ triggerReason
767
+ });
768
+ return incident;
769
+ };
770
+ initPromise = initializeState().catch((error) => {
771
+ input.log?.warn("shareable debug initialization failed", {
772
+ error: error instanceof Error ? error.message : String(error)
773
+ });
774
+ });
775
+ const openIncidentCapture = async (record, triggerReason) => {
776
+ if (openIncident)
777
+ return;
778
+ const incident = createIncidentManifest(record, triggerReason);
779
+ await writeManifest(incident);
780
+ const preTriggerEvents = await readEventsInRange(incident.preTriggerStartSeq, record.seq);
781
+ const expectedBaseCount = incident.triggerSeq - incident.preTriggerStartSeq + 1;
782
+ const baseWindow = contiguousWindow(preTriggerEvents, incident.preTriggerStartSeq, expectedBaseCount);
783
+ await writeJsonlLines(incident.outputFilePath, baseWindow.rows.map((entry) => `${JSON.stringify(entry)}\n`));
784
+ if (baseWindow.hasGapAfterPrefix || baseWindow.rows.length !== expectedBaseCount) {
785
+ await sealIncidentIncomplete({
786
+ incident,
787
+ reason: "interrupted"
788
+ });
789
+ return;
790
+ }
791
+ await deleteFileIfExists(manifestlessRecoveryPath);
792
+ await appendSummaryLifecycle("incident_opened", {
793
+ incidentId: incident.incidentId,
794
+ incidentFile: incidentFileReference(incident.outputFilePath),
795
+ triggerSeq: record.seq,
796
+ triggerEvent: record.event,
797
+ triggerReason
798
+ });
799
+ if (incident.postRemaining <= 0) {
800
+ await closeIncident(incident, "trigger");
801
+ return;
802
+ }
803
+ openIncident = incident;
804
+ await pruneIncidents();
805
+ };
806
+ const appendToOpenIncident = async (record) => {
807
+ if (!openIncident || record.seq <= openIncident.triggerSeq)
808
+ return;
809
+ await appendIncidentLine(openIncident, record);
810
+ openIncident = {
811
+ ...openIncident,
812
+ postRemaining: Math.max(0, openIncident.postRemaining - 1),
813
+ updatedAt: new Date().toISOString()
814
+ };
815
+ if (openIncident.postRemaining <= 0) {
816
+ await closeIncident(openIncident, "trigger");
817
+ return;
818
+ }
819
+ await writeManifestBestEffort(openIncident);
820
+ };
821
+ const triggerReasonFor = (record) => {
822
+ if (record.event === "auth_failure")
823
+ return "auth_failure";
824
+ if (record.event === "retry_after_429")
825
+ return "retry_after_429";
826
+ if (record.event === "synthetic_fatal_error")
827
+ return "synthetic_fatal_error";
828
+ if (record.event === "process_failure")
829
+ return "process_failure";
830
+ if (record.event === "fetch_attempt_response") {
831
+ const status = typeof record.status === "number" ? record.status : undefined;
832
+ if (status === 401 || status === 403 || status === 429) {
833
+ return "http_status";
834
+ }
835
+ }
836
+ return undefined;
837
+ };
838
+ const appendRecord = async (record) => {
839
+ await appendToSegment(record);
840
+ const triggerReason = triggerReasonFor(record);
841
+ if (triggerReason && !openIncident) {
842
+ await openIncidentCapture(record, triggerReason);
843
+ }
844
+ else {
845
+ await appendToOpenIncident(record);
846
+ }
847
+ await appendSummaryLine(`${JSON.stringify(record)}\n`);
848
+ };
849
+ const buildRecord = (event, payload) => ({
850
+ seq: nextSeq++,
851
+ timestamp: new Date().toISOString(),
852
+ event,
853
+ ...payload
854
+ });
855
+ const emitEvent = async (event, payload) => {
856
+ await runQueued(async () => {
857
+ const record = buildRecord(event, payload);
858
+ await appendRecord(record);
859
+ });
860
+ };
861
+ const captureProcessFailureSync = (event, payload) => {
862
+ try {
863
+ const record = buildRecord(event, payload);
864
+ appendToSegmentSync(record);
865
+ const triggerReason = triggerReasonFor(record);
866
+ if (triggerReason && !openIncident) {
867
+ openIncidentCaptureSync(record, triggerReason);
868
+ }
869
+ else if (openIncident && record.seq > openIncident.triggerSeq) {
870
+ appendJsonlLineSync(openIncident.outputFilePath, `${JSON.stringify(record)}\n`);
871
+ openIncident = {
872
+ ...openIncident,
873
+ postRemaining: Math.max(0, openIncident.postRemaining - 1),
874
+ updatedAt: new Date().toISOString()
875
+ };
876
+ if (openIncident.postRemaining <= 0) {
877
+ appendIncidentLifecycleSync(openIncident, "incident_closed", {
878
+ reason: "trigger",
879
+ triggerSeq: openIncident.triggerSeq,
880
+ triggerEvent: openIncident.triggerEvent,
881
+ incomplete: false
882
+ });
883
+ try {
884
+ fsSync.unlinkSync(manifestPath);
885
+ }
886
+ catch (error) {
887
+ if (!isFsErrorCode(error, "ENOENT"))
888
+ throw error;
889
+ }
890
+ appendSummaryLifecycleSync("incident_closed", {
891
+ incidentId: openIncident.incidentId,
892
+ incidentFile: incidentFileReference(openIncident.outputFilePath),
893
+ triggerSeq: openIncident.triggerSeq,
894
+ triggerEvent: openIncident.triggerEvent,
895
+ reason: "trigger"
896
+ });
897
+ openIncident = undefined;
898
+ }
899
+ else {
900
+ writeManifestSync(openIncident);
901
+ }
902
+ }
903
+ appendSummaryLineSync(`${JSON.stringify(record)}\n`);
904
+ }
905
+ catch {
906
+ // best-effort only in crash path
907
+ }
908
+ };
909
+ const flushPending = async () => {
910
+ await pendingWrite;
911
+ };
912
+ const installProcessHandlers = () => {
913
+ if (input.registerProcessHandlers === false || handlersRegistered)
914
+ return;
915
+ handlersRegistered = true;
916
+ const handleSignal = (signal) => {
917
+ if (signalInFlight)
918
+ return;
919
+ signalInFlight = true;
920
+ captureProcessFailureSync("process_failure", {
921
+ authMode: "codex",
922
+ outcome: signal.toLowerCase(),
923
+ status: 499
924
+ });
925
+ const signalHandler = signalHandlers[signal];
926
+ if (signalHandler) {
927
+ process.removeListener(signal, signalHandler);
928
+ }
929
+ try {
930
+ process.kill(process.pid, signal);
931
+ }
932
+ catch {
933
+ // best-effort only
934
+ }
935
+ };
936
+ const beforeExitHandler = () => {
937
+ void flushPending();
938
+ };
939
+ const uncaughtExceptionMonitorHandler = (error) => {
940
+ captureProcessFailureSync("process_failure", {
941
+ authMode: "codex",
942
+ outcome: "uncaught_exception",
943
+ status: 500,
944
+ errorName: error.name
945
+ });
946
+ };
947
+ const signalHandlers = {
948
+ SIGINT: () => handleSignal("SIGINT"),
949
+ SIGTERM: () => handleSignal("SIGTERM"),
950
+ SIGHUP: () => handleSignal("SIGHUP"),
951
+ SIGBREAK: () => handleSignal("SIGBREAK")
952
+ };
953
+ process.on("beforeExit", beforeExitHandler);
954
+ process.on("uncaughtExceptionMonitor", uncaughtExceptionMonitorHandler);
955
+ process.on("SIGINT", signalHandlers.SIGINT ?? (() => { }));
956
+ process.on("SIGTERM", signalHandlers.SIGTERM ?? (() => { }));
957
+ if (process.platform === "win32") {
958
+ process.on("SIGBREAK", signalHandlers.SIGBREAK ?? (() => { }));
959
+ }
960
+ else {
961
+ process.on("SIGHUP", signalHandlers.SIGHUP ?? (() => { }));
962
+ }
963
+ };
964
+ installProcessHandlers();
965
+ return {
966
+ enabled: true,
967
+ async emitRotationBegin(event) {
968
+ await emitEvent("rotation_begin", {
969
+ authMode: event.authMode,
970
+ rotationStrategy: event.rotationStrategy,
971
+ totalAccounts: event.totalAccounts,
972
+ enabledAccounts: event.enabledAccounts,
973
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
974
+ session: pseudonym("sess", event.sessionKey)
975
+ });
976
+ },
977
+ async emitRotationDecision(event) {
978
+ await emitEvent("rotation_decision", {
979
+ authMode: event.authMode,
980
+ rotationStrategy: event.rotationStrategy,
981
+ decision: event.decision,
982
+ totalCount: event.totalCount,
983
+ disabledCount: event.disabledCount,
984
+ cooldownCount: event.cooldownCount,
985
+ refreshLeaseCount: event.refreshLeaseCount,
986
+ eligibleCount: event.eligibleCount,
987
+ attemptedCount: event.attemptedCount,
988
+ selectedIndex: event.selectedIndex,
989
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
990
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
991
+ session: pseudonym("sess", event.sessionKey),
992
+ attempt: pseudonym("attempt", event.attemptKey)
993
+ });
994
+ },
995
+ async emitRotationCandidateSelected(event) {
996
+ await emitEvent("rotation_candidate_selected", {
997
+ authMode: event.authMode,
998
+ selectedIndex: event.selectedIndex,
999
+ selectedEnabled: event.selectedEnabled,
1000
+ selectedCooldownUntil: event.selectedCooldownUntil,
1001
+ selectedExpires: event.selectedExpires,
1002
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1003
+ attempt: pseudonym("attempt", event.attemptKey)
1004
+ });
1005
+ },
1006
+ async emitFetchAttemptRequest(event) {
1007
+ await emitEvent("fetch_attempt_request", {
1008
+ authMode: event.authMode,
1009
+ rotationStrategy: event.rotationStrategy,
1010
+ attempt: event.attempt,
1011
+ maxAttempts: event.maxAttempts,
1012
+ attemptReasonCode: event.attemptReasonCode,
1013
+ method: event.request.method.toUpperCase(),
1014
+ endpoint: normalizeEndpoint(event.request.url),
1015
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1016
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
1017
+ session: pseudonym("sess", event.sessionKey),
1018
+ promptCacheKey: pseudonym("pck", await extractPromptCacheKey(event.request))
1019
+ });
1020
+ },
1021
+ async emitFetchAttemptResponse(event) {
1022
+ await emitEvent("fetch_attempt_response", {
1023
+ authMode: event.authMode,
1024
+ rotationStrategy: event.rotationStrategy,
1025
+ attempt: event.attempt,
1026
+ maxAttempts: event.maxAttempts,
1027
+ attemptReasonCode: event.attemptReasonCode,
1028
+ endpoint: normalizeEndpoint(event.endpoint),
1029
+ status: event.status,
1030
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1031
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
1032
+ session: pseudonym("sess", event.sessionKey)
1033
+ });
1034
+ },
1035
+ async emitRetryAfter429(event) {
1036
+ await emitEvent("retry_after_429", {
1037
+ authMode: event.authMode,
1038
+ rotationStrategy: event.rotationStrategy,
1039
+ attempt: event.attempt,
1040
+ maxAttempts: event.maxAttempts,
1041
+ attemptReasonCode: event.attemptReasonCode,
1042
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1043
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
1044
+ session: pseudonym("sess", event.sessionKey)
1045
+ });
1046
+ },
1047
+ async emitAuthFailure(event) {
1048
+ await emitEvent("auth_failure", {
1049
+ authMode: event.authMode,
1050
+ outcome: event.outcome,
1051
+ status: event.status,
1052
+ waitMs: event.waitMs,
1053
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1054
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
1055
+ session: pseudonym("sess", event.sessionKey)
1056
+ });
1057
+ },
1058
+ async emitSyntheticFatalError(event) {
1059
+ await emitEvent("synthetic_fatal_error", {
1060
+ authMode: event.authMode,
1061
+ outcome: event.outcome,
1062
+ status: event.status,
1063
+ endpoint: normalizeEndpoint(event.endpoint),
1064
+ selectedIdentity: pseudonym("ident", event.selectedIdentityKey),
1065
+ activeIdentity: pseudonym("ident", event.activeIdentityKey),
1066
+ session: pseudonym("sess", event.sessionKey)
1067
+ });
1068
+ }
1069
+ };
1070
+ }
1071
+ //# sourceMappingURL=shareable-debug.js.map