@gakr-gakr/codex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/autobot.plugin.json +374 -0
  2. package/doctor-contract-api.ts +68 -0
  3. package/harness.ts +72 -0
  4. package/index.ts +124 -0
  5. package/media-understanding-provider.ts +521 -0
  6. package/package.json +40 -0
  7. package/prompt-overlay.ts +21 -0
  8. package/provider-catalog.ts +83 -0
  9. package/provider-discovery.ts +45 -0
  10. package/provider.ts +243 -0
  11. package/src/app-server/app-inventory-cache.ts +324 -0
  12. package/src/app-server/approval-bridge.ts +1211 -0
  13. package/src/app-server/auth-bridge.ts +614 -0
  14. package/src/app-server/capabilities.ts +27 -0
  15. package/src/app-server/client-factory.ts +24 -0
  16. package/src/app-server/client.ts +715 -0
  17. package/src/app-server/compact.ts +512 -0
  18. package/src/app-server/computer-use.ts +683 -0
  19. package/src/app-server/config.ts +1038 -0
  20. package/src/app-server/context-engine-projection.ts +403 -0
  21. package/src/app-server/dynamic-tool-diagnostics.ts +73 -0
  22. package/src/app-server/dynamic-tool-profile.ts +70 -0
  23. package/src/app-server/dynamic-tools.ts +623 -0
  24. package/src/app-server/elicitation-bridge.ts +783 -0
  25. package/src/app-server/event-projector.ts +2065 -0
  26. package/src/app-server/image-payload-sanitizer.ts +167 -0
  27. package/src/app-server/local-runtime-attribution.ts +39 -0
  28. package/src/app-server/managed-binary.ts +193 -0
  29. package/src/app-server/models.ts +172 -0
  30. package/src/app-server/native-hook-relay.ts +150 -0
  31. package/src/app-server/native-subagent-task-mirror.ts +497 -0
  32. package/src/app-server/plugin-activation.ts +283 -0
  33. package/src/app-server/plugin-app-cache-key.ts +74 -0
  34. package/src/app-server/plugin-approval-roundtrip.ts +122 -0
  35. package/src/app-server/plugin-inventory.ts +357 -0
  36. package/src/app-server/plugin-thread-config.ts +455 -0
  37. package/src/app-server/protocol-generated/json/DynamicToolCallParams.json +33 -0
  38. package/src/app-server/protocol-generated/json/v2/ErrorNotification.json +199 -0
  39. package/src/app-server/protocol-generated/json/v2/GetAccountResponse.json +102 -0
  40. package/src/app-server/protocol-generated/json/v2/ModelListResponse.json +227 -0
  41. package/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +2630 -0
  42. package/src/app-server/protocol-generated/json/v2/ThreadStartResponse.json +2630 -0
  43. package/src/app-server/protocol-generated/json/v2/TurnCompletedNotification.json +1659 -0
  44. package/src/app-server/protocol-generated/json/v2/TurnStartResponse.json +1655 -0
  45. package/src/app-server/protocol-validators.ts +203 -0
  46. package/src/app-server/protocol.ts +520 -0
  47. package/src/app-server/rate-limit-cache.ts +48 -0
  48. package/src/app-server/rate-limits.ts +583 -0
  49. package/src/app-server/request.ts +73 -0
  50. package/src/app-server/run-attempt.ts +4862 -0
  51. package/src/app-server/session-binding.ts +398 -0
  52. package/src/app-server/session-history.ts +44 -0
  53. package/src/app-server/shared-client.ts +289 -0
  54. package/src/app-server/side-question.ts +1009 -0
  55. package/src/app-server/test-support.ts +48 -0
  56. package/src/app-server/thread-lifecycle.ts +959 -0
  57. package/src/app-server/timeout.ts +9 -0
  58. package/src/app-server/tool-progress-normalization.ts +77 -0
  59. package/src/app-server/trajectory.ts +368 -0
  60. package/src/app-server/transcript-mirror.ts +208 -0
  61. package/src/app-server/transport-stdio.ts +107 -0
  62. package/src/app-server/transport-websocket.ts +90 -0
  63. package/src/app-server/transport.ts +117 -0
  64. package/src/app-server/user-input-bridge.ts +316 -0
  65. package/src/app-server/version.ts +4 -0
  66. package/src/app-server/vision-tools.ts +12 -0
  67. package/src/command-account.ts +544 -0
  68. package/src/command-formatters.ts +426 -0
  69. package/src/command-handlers.ts +2021 -0
  70. package/src/command-plugins-management.ts +137 -0
  71. package/src/command-rpc.ts +142 -0
  72. package/src/commands.ts +65 -0
  73. package/src/conversation-binding-data.ts +124 -0
  74. package/src/conversation-binding.ts +561 -0
  75. package/src/conversation-control.ts +303 -0
  76. package/src/conversation-turn-collector.ts +186 -0
  77. package/src/conversation-turn-input.ts +106 -0
  78. package/src/migration/apply.ts +501 -0
  79. package/src/migration/helpers.ts +55 -0
  80. package/src/migration/plan.ts +461 -0
  81. package/src/migration/provider.ts +41 -0
  82. package/src/migration/source.ts +643 -0
  83. package/src/migration/targets.ts +25 -0
  84. package/src/node-cli-sessions.ts +711 -0
  85. package/test-api.ts +95 -0
  86. package/tsconfig.json +16 -0
@@ -0,0 +1,512 @@
1
+ import {
2
+ compactContextEngineWithSafetyTimeout,
3
+ embeddedAgentLog,
4
+ formatErrorMessage,
5
+ isActiveHarnessContextEngine,
6
+ resolveCompactionTimeoutMs,
7
+ resolveContextEngineOwnerPluginId,
8
+ runHarnessContextEngineMaintenance,
9
+ type CompactEmbeddedPiSessionParams,
10
+ type EmbeddedPiCompactResult,
11
+ } from "autobot/plugin-sdk/agent-harness-runtime";
12
+ import {
13
+ defaultCodexAppServerClientFactory,
14
+ type CodexAppServerClientFactory,
15
+ } from "./client-factory.js";
16
+ import type { CodexAppServerClient, CodexServerNotificationHandler } from "./client.js";
17
+ import { resolveCodexAppServerRuntimeOptions } from "./config.js";
18
+ import { isJsonObject, type CodexServerNotification, type JsonObject } from "./protocol.js";
19
+ import { clearCodexAppServerBinding, readCodexAppServerBinding } from "./session-binding.js";
20
+ type CodexNativeCompactionCompletion = {
21
+ signal: "thread/compacted" | "item/completed";
22
+ turnId?: string;
23
+ itemId?: string;
24
+ };
25
+ type CodexNativeCompactionWaiter = {
26
+ promise: Promise<CodexNativeCompactionCompletion>;
27
+ startTimeout: () => void;
28
+ cancel: () => void;
29
+ };
30
+
31
+ const DEFAULT_CODEX_COMPACTION_WAIT_TIMEOUT_MS = 5 * 60 * 1000;
32
+ const warnedIgnoredCompactionOverrides = new Set<string>();
33
+
34
+ export async function maybeCompactCodexAppServerSession(
35
+ params: CompactEmbeddedPiSessionParams,
36
+ options: { pluginConfig?: unknown; clientFactory?: CodexAppServerClientFactory } = {},
37
+ ): Promise<EmbeddedPiCompactResult | undefined> {
38
+ const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine)
39
+ ? params.contextEngine
40
+ : undefined;
41
+ if (activeContextEngine?.info.ownsCompaction) {
42
+ return await compactOwningContextEngine(params, activeContextEngine);
43
+ }
44
+ warnIfIgnoringAutoBotCompactionOverrides(params);
45
+ const nativeResult = await compactCodexNativeThread(params, options);
46
+ if (activeContextEngine && nativeResult?.ok && nativeResult.compacted) {
47
+ try {
48
+ await runHarnessContextEngineMaintenance({
49
+ contextEngine: activeContextEngine,
50
+ sessionId: params.sessionId,
51
+ sessionKey: params.sessionKey,
52
+ sessionFile: params.sessionFile,
53
+ reason: "compaction",
54
+ runtimeContext: params.contextEngineRuntimeContext,
55
+ config: params.config,
56
+ });
57
+ } catch (error) {
58
+ embeddedAgentLog.warn("context engine compaction maintenance failed after Codex compaction", {
59
+ sessionId: params.sessionId,
60
+ engineId: activeContextEngine.info.id,
61
+ error: formatErrorMessage(error),
62
+ });
63
+ }
64
+ }
65
+ return nativeResult;
66
+ }
67
+
68
+ async function compactOwningContextEngine(
69
+ params: CompactEmbeddedPiSessionParams,
70
+ contextEngine: NonNullable<CompactEmbeddedPiSessionParams["contextEngine"]>,
71
+ ): Promise<EmbeddedPiCompactResult> {
72
+ embeddedAgentLog.info("starting context-engine-owned Codex app-server compaction", {
73
+ sessionId: params.sessionId,
74
+ sessionKey: params.sessionKey,
75
+ engineId: contextEngine.info.id,
76
+ tokenBudget: params.contextTokenBudget,
77
+ currentTokenCount: params.currentTokenCount,
78
+ trigger: params.trigger,
79
+ compactionTarget: params.trigger === "manual" ? "threshold" : "budget",
80
+ force: params.trigger === "manual",
81
+ });
82
+ let result: Awaited<ReturnType<typeof contextEngine.compact>>;
83
+ try {
84
+ // Bound the plugin-owned compaction with the same finite safety timeout
85
+ // that protects native runtime compaction, and thread the caller's abort
86
+ // signal through, so a slow/hung plugin compact() cannot hang the Codex
87
+ // compaction lane indefinitely. A timeout/abort (or any thrown error) is
88
+ // converted to a clean { ok: false } result by the catch below.
89
+ result = await compactContextEngineWithSafetyTimeout(
90
+ contextEngine,
91
+ {
92
+ sessionId: params.sessionId,
93
+ sessionKey: params.sessionKey,
94
+ sessionFile: params.sessionFile,
95
+ tokenBudget: params.contextTokenBudget,
96
+ currentTokenCount: params.currentTokenCount,
97
+ compactionTarget: params.trigger === "manual" ? "threshold" : "budget",
98
+ customInstructions: params.customInstructions,
99
+ force: params.trigger === "manual",
100
+ runtimeContext: params.contextEngineRuntimeContext,
101
+ },
102
+ resolveCompactionTimeoutMs(params.config),
103
+ params.abortSignal,
104
+ );
105
+ } catch (error) {
106
+ embeddedAgentLog.warn("context-engine-owned Codex app-server compaction failed", {
107
+ sessionId: params.sessionId,
108
+ sessionKey: params.sessionKey,
109
+ engineId: contextEngine.info.id,
110
+ error: formatErrorMessage(error),
111
+ });
112
+ return {
113
+ ok: false,
114
+ compacted: false,
115
+ reason: `context engine compaction failed: ${formatErrorMessage(error)}`,
116
+ };
117
+ }
118
+
119
+ if (result.ok && result.compacted) {
120
+ const compactedSessionId = result.result?.sessionId ?? params.sessionId;
121
+ const compactedSessionFile = result.result?.sessionFile ?? params.sessionFile;
122
+ try {
123
+ await runHarnessContextEngineMaintenance({
124
+ contextEngine,
125
+ sessionId: compactedSessionId,
126
+ sessionKey: params.sessionKey,
127
+ sessionFile: compactedSessionFile,
128
+ reason: "compaction",
129
+ runtimeContext: params.contextEngineRuntimeContext,
130
+ config: params.config,
131
+ });
132
+ } catch (error) {
133
+ embeddedAgentLog.warn("context engine compaction maintenance failed", {
134
+ sessionId: compactedSessionId,
135
+ engineId: contextEngine.info.id,
136
+ error: formatErrorMessage(error),
137
+ });
138
+ }
139
+ await clearCodexAppServerBinding(params.sessionFile);
140
+ if (compactedSessionFile !== params.sessionFile) {
141
+ await clearCodexAppServerBinding(compactedSessionFile);
142
+ }
143
+ }
144
+
145
+ embeddedAgentLog.info("completed context-engine-owned Codex app-server compaction", {
146
+ sessionId: params.sessionId,
147
+ sessionKey: params.sessionKey,
148
+ engineId: contextEngine.info.id,
149
+ ok: result.ok,
150
+ compacted: result.compacted,
151
+ reason: result.reason,
152
+ codexThreadBindingInvalidated: result.ok && result.compacted,
153
+ });
154
+ return {
155
+ ok: result.ok,
156
+ compacted: result.compacted,
157
+ reason: result.reason,
158
+ result: result.result
159
+ ? {
160
+ ...result.result,
161
+ summary: result.result.summary ?? "",
162
+ firstKeptEntryId: result.result.firstKeptEntryId ?? "",
163
+ details: mergeContextEngineCompactionDetails(result.result.details, {
164
+ codexThreadBindingInvalidated: result.ok && result.compacted,
165
+ }),
166
+ }
167
+ : result.ok && result.compacted
168
+ ? {
169
+ summary: "",
170
+ firstKeptEntryId: "",
171
+ tokensBefore: params.currentTokenCount ?? 0,
172
+ details: { codexThreadBindingInvalidated: true },
173
+ }
174
+ : undefined,
175
+ };
176
+ }
177
+
178
+ function mergeContextEngineCompactionDetails(
179
+ details: unknown,
180
+ extra: Record<string, unknown>,
181
+ ): unknown {
182
+ if (details && typeof details === "object" && !Array.isArray(details)) {
183
+ return {
184
+ ...(details as Record<string, unknown>),
185
+ ...extra,
186
+ };
187
+ }
188
+ return extra;
189
+ }
190
+
191
+ function warnIfIgnoringAutoBotCompactionOverrides(params: CompactEmbeddedPiSessionParams): void {
192
+ const activeContextEngine = isActiveHarnessContextEngine(params.contextEngine)
193
+ ? params.contextEngine
194
+ : undefined;
195
+ const ignoredConfig = readIgnoredCompactionOverridePaths(params, activeContextEngine);
196
+ if (ignoredConfig.length === 0) {
197
+ return;
198
+ }
199
+ const warningKey = ignoredConfig.join("\0");
200
+ if (warnedIgnoredCompactionOverrides.has(warningKey)) {
201
+ return;
202
+ }
203
+ warnedIgnoredCompactionOverrides.add(warningKey);
204
+ embeddedAgentLog.warn(
205
+ "ignoring AutoBot compaction overrides for Codex app-server compaction; Codex uses native server-side compaction",
206
+ {
207
+ sessionId: params.sessionId,
208
+ sessionKey: params.sessionKey,
209
+ ignoredConfig,
210
+ },
211
+ );
212
+ }
213
+
214
+ function readIgnoredCompactionOverridePaths(
215
+ params: CompactEmbeddedPiSessionParams,
216
+ activeContextEngine?: CompactEmbeddedPiSessionParams["contextEngine"],
217
+ ): string[] {
218
+ const ignored = new Set<string>();
219
+ const configuredContextEngine = readStringPath(params.config, [
220
+ "plugins",
221
+ "slots",
222
+ "contextEngine",
223
+ ]);
224
+ const runtimeContextEnginePlugin =
225
+ typeof params.contextEngineRuntimeContext?.contextEnginePluginId === "string"
226
+ ? params.contextEngineRuntimeContext.contextEnginePluginId.trim()
227
+ : "";
228
+ const activeContextEnginePlugin = resolveContextEngineOwnerPluginId(activeContextEngine);
229
+ for (const entry of readCompactionOverrideEntries(params)) {
230
+ const localProvider =
231
+ typeof entry.record.provider === "string" ? entry.record.provider.trim() : "";
232
+ const inheritedProvider =
233
+ !localProvider && typeof entry.inheritedRecord?.provider === "string"
234
+ ? entry.inheritedRecord.provider.trim()
235
+ : "";
236
+ const provider = localProvider || inheritedProvider;
237
+ const providerPath = localProvider
238
+ ? `${entry.path}.compaction.provider`
239
+ : inheritedProvider && entry.inheritedPath
240
+ ? `${entry.inheritedPath}.compaction.provider`
241
+ : undefined;
242
+ const activeLosslessContextEngine =
243
+ provider.toLowerCase() === "lossless-claw" &&
244
+ (activeContextEnginePlugin === "lossless-claw" ||
245
+ runtimeContextEnginePlugin.toLowerCase() === "lossless-claw" ||
246
+ configuredContextEngine?.toLowerCase() === "lossless-claw");
247
+ if (activeLosslessContextEngine) {
248
+ continue;
249
+ }
250
+ if (typeof entry.record.model === "string" && entry.record.model.trim()) {
251
+ ignored.add(`${entry.path}.compaction.model`);
252
+ }
253
+ if (providerPath) {
254
+ ignored.add(providerPath);
255
+ }
256
+ }
257
+ return [...ignored];
258
+ }
259
+
260
+ function readCompactionOverrideEntries(params: CompactEmbeddedPiSessionParams): Array<{
261
+ path: string;
262
+ record: Record<string, unknown>;
263
+ inheritedRecord?: Record<string, unknown>;
264
+ inheritedPath?: string;
265
+ }> {
266
+ const entries: Array<{
267
+ path: string;
268
+ record: Record<string, unknown>;
269
+ inheritedRecord?: Record<string, unknown>;
270
+ inheritedPath?: string;
271
+ }> = [];
272
+ const defaultCompaction = readRecord(readRecord(params.config?.agents)?.defaults)?.compaction;
273
+ const defaultRecord = readRecord(defaultCompaction);
274
+ if (defaultRecord) {
275
+ entries.push({ path: "agents.defaults", record: defaultRecord });
276
+ }
277
+ const agentId = readAgentIdFromSessionKey(params.sessionKey ?? params.sandboxSessionKey);
278
+ if (!agentId) {
279
+ return entries;
280
+ }
281
+ const agents = Array.isArray(params.config?.agents?.list) ? params.config.agents.list : [];
282
+ const activeAgent = agents.find((agent) => {
283
+ const id = typeof agent?.id === "string" ? agent.id.trim().toLowerCase() : "";
284
+ return id === agentId;
285
+ });
286
+ const agentCompaction = readRecord(activeAgent)?.compaction;
287
+ const agentRecord = readRecord(agentCompaction);
288
+ if (agentRecord) {
289
+ entries.push({
290
+ path: `agents.list.${agentId}`,
291
+ record: agentRecord,
292
+ inheritedRecord: defaultRecord,
293
+ inheritedPath: "agents.defaults",
294
+ });
295
+ }
296
+ return entries;
297
+ }
298
+
299
+ function readAgentIdFromSessionKey(sessionKey: string | undefined): string | undefined {
300
+ const parts = sessionKey?.trim().toLowerCase().split(":").filter(Boolean) ?? [];
301
+ if (parts.length < 3 || parts[0] !== "agent") {
302
+ return undefined;
303
+ }
304
+ return parts[1]?.trim() || undefined;
305
+ }
306
+
307
+ function readRecord(value: unknown): Record<string, unknown> | undefined {
308
+ return value && typeof value === "object" && !Array.isArray(value)
309
+ ? (value as Record<string, unknown>)
310
+ : undefined;
311
+ }
312
+
313
+ function readStringPath(value: unknown, path: readonly string[]): string | undefined {
314
+ let current = value;
315
+ for (const segment of path) {
316
+ current = readRecord(current)?.[segment];
317
+ }
318
+ return typeof current === "string" && current.trim() ? current.trim() : undefined;
319
+ }
320
+
321
+ async function compactCodexNativeThread(
322
+ params: CompactEmbeddedPiSessionParams,
323
+ options: { pluginConfig?: unknown; clientFactory?: CodexAppServerClientFactory } = {},
324
+ ): Promise<EmbeddedPiCompactResult | undefined> {
325
+ const appServer = resolveCodexAppServerRuntimeOptions({ pluginConfig: options.pluginConfig });
326
+ const binding = await readCodexAppServerBinding(params.sessionFile, { config: params.config });
327
+ if (!binding?.threadId) {
328
+ return { ok: false, compacted: false, reason: "no codex app-server thread binding" };
329
+ }
330
+ const requestedAuthProfileId = params.authProfileId?.trim() || undefined;
331
+ if (
332
+ requestedAuthProfileId &&
333
+ binding.authProfileId &&
334
+ binding.authProfileId !== requestedAuthProfileId
335
+ ) {
336
+ return { ok: false, compacted: false, reason: "auth profile mismatch for session binding" };
337
+ }
338
+
339
+ const clientFactory = options.clientFactory ?? defaultCodexAppServerClientFactory;
340
+ const client = await clientFactory(
341
+ appServer.start,
342
+ requestedAuthProfileId ?? binding.authProfileId,
343
+ params.agentDir,
344
+ params.config,
345
+ );
346
+ const waiter = createCodexNativeCompactionWaiter(client, binding.threadId);
347
+ let completion: CodexNativeCompactionCompletion;
348
+ try {
349
+ await client.request("thread/compact/start", {
350
+ threadId: binding.threadId,
351
+ });
352
+ embeddedAgentLog.info("started codex app-server compaction", {
353
+ sessionId: params.sessionId,
354
+ threadId: binding.threadId,
355
+ });
356
+ waiter.startTimeout();
357
+ completion = await waiter.promise;
358
+ } catch (error) {
359
+ waiter.cancel();
360
+ return {
361
+ ok: false,
362
+ compacted: false,
363
+ reason: formatCompactionError(error),
364
+ };
365
+ }
366
+ embeddedAgentLog.info("completed codex app-server compaction", {
367
+ sessionId: params.sessionId,
368
+ threadId: binding.threadId,
369
+ signal: completion.signal,
370
+ turnId: completion.turnId,
371
+ itemId: completion.itemId,
372
+ });
373
+ return {
374
+ ok: true,
375
+ compacted: true,
376
+ result: {
377
+ summary: "",
378
+ firstKeptEntryId: "",
379
+ tokensBefore: params.currentTokenCount ?? 0,
380
+ details: {
381
+ backend: "codex-app-server",
382
+ threadId: binding.threadId,
383
+ signal: completion.signal,
384
+ turnId: completion.turnId,
385
+ itemId: completion.itemId,
386
+ },
387
+ },
388
+ };
389
+ }
390
+
391
+ function createCodexNativeCompactionWaiter(
392
+ client: CodexAppServerClient,
393
+ threadId: string,
394
+ ): CodexNativeCompactionWaiter {
395
+ let settled = false;
396
+ let removeHandler: () => void = () => {};
397
+ let timeout: ReturnType<typeof setTimeout> | undefined;
398
+ let failWaiter: (error: Error) => void = () => {};
399
+
400
+ const promise = new Promise<CodexNativeCompactionCompletion>((resolve, reject) => {
401
+ const cleanup = (): void => {
402
+ removeHandler();
403
+ if (timeout) {
404
+ clearTimeout(timeout);
405
+ }
406
+ };
407
+ const complete = (completion: CodexNativeCompactionCompletion): void => {
408
+ if (settled) {
409
+ return;
410
+ }
411
+ settled = true;
412
+ cleanup();
413
+ resolve(completion);
414
+ };
415
+ const fail = (error: Error): void => {
416
+ if (settled) {
417
+ return;
418
+ }
419
+ settled = true;
420
+ cleanup();
421
+ reject(error);
422
+ };
423
+ failWaiter = fail;
424
+ const handler: CodexServerNotificationHandler = (notification) => {
425
+ const completion = readNativeCompactionCompletion(notification, threadId);
426
+ if (completion) {
427
+ complete(completion);
428
+ }
429
+ };
430
+ removeHandler = client.addNotificationHandler(handler);
431
+ });
432
+
433
+ return {
434
+ promise,
435
+ startTimeout(): void {
436
+ if (settled || timeout) {
437
+ return;
438
+ }
439
+ timeout = setTimeout(() => {
440
+ failWaiter(new Error(`timed out waiting for codex app-server compaction for ${threadId}`));
441
+ }, resolveCompactionWaitTimeoutMs());
442
+ timeout.unref?.();
443
+ },
444
+ cancel(): void {
445
+ if (settled) {
446
+ return;
447
+ }
448
+ settled = true;
449
+ removeHandler();
450
+ if (timeout) {
451
+ clearTimeout(timeout);
452
+ }
453
+ },
454
+ };
455
+ }
456
+
457
+ function readNativeCompactionCompletion(
458
+ notification: CodexServerNotification,
459
+ threadId: string,
460
+ ): CodexNativeCompactionCompletion | undefined {
461
+ const params = notification.params;
462
+ if (!isJsonObject(params) || readString(params, "threadId", "thread_id") !== threadId) {
463
+ return undefined;
464
+ }
465
+ if (notification.method === "thread/compacted") {
466
+ return {
467
+ signal: "thread/compacted",
468
+ turnId: readString(params, "turnId", "turn_id"),
469
+ };
470
+ }
471
+ if (notification.method !== "item/completed") {
472
+ return undefined;
473
+ }
474
+ const item = isJsonObject(params.item) ? params.item : undefined;
475
+ if (readString(item, "type") !== "contextCompaction") {
476
+ return undefined;
477
+ }
478
+ return {
479
+ signal: "item/completed",
480
+ turnId: readString(params, "turnId", "turn_id"),
481
+ itemId: readString(item, "id") ?? readString(params, "itemId", "item_id", "id"),
482
+ };
483
+ }
484
+
485
+ function resolveCompactionWaitTimeoutMs(): number {
486
+ const raw = process.env.AUTOBOT_CODEX_COMPACTION_WAIT_TIMEOUT_MS?.trim();
487
+ const parsed = raw ? Number.parseInt(raw, 10) : Number.NaN;
488
+ if (Number.isFinite(parsed) && parsed > 0) {
489
+ return parsed;
490
+ }
491
+ return DEFAULT_CODEX_COMPACTION_WAIT_TIMEOUT_MS;
492
+ }
493
+
494
+ function readString(params: JsonObject | undefined, ...keys: string[]): string | undefined {
495
+ if (!params) {
496
+ return undefined;
497
+ }
498
+ for (const key of keys) {
499
+ const value = params[key];
500
+ if (typeof value === "string") {
501
+ return value;
502
+ }
503
+ }
504
+ return undefined;
505
+ }
506
+
507
+ function formatCompactionError(error: unknown): string {
508
+ if (error instanceof Error) {
509
+ return error.message;
510
+ }
511
+ return String(error);
512
+ }