@navels/neal 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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +527 -0
  3. package/SECURITY.md +91 -0
  4. package/config.yml +104 -0
  5. package/dist/neal/activity-footer.js +177 -0
  6. package/dist/neal/activity-reporting.js +1 -0
  7. package/dist/neal/adjudicator/artifacts.js +58 -0
  8. package/dist/neal/adjudicator/blocked-adjudicator.js +223 -0
  9. package/dist/neal/adjudicator/contracts.js +139 -0
  10. package/dist/neal/adjudicator/execute.js +611 -0
  11. package/dist/neal/adjudicator/final-completion.js +104 -0
  12. package/dist/neal/adjudicator/planning.js +145 -0
  13. package/dist/neal/adjudicator/specs.js +453 -0
  14. package/dist/neal/agents/prompts.js +120 -0
  15. package/dist/neal/agents/rounds.js +706 -0
  16. package/dist/neal/agents/schemas.js +832 -0
  17. package/dist/neal/agents/structured-coder.js +82 -0
  18. package/dist/neal/agents/structured-json.js +528 -0
  19. package/dist/neal/agents.js +4 -0
  20. package/dist/neal/atomic-write.js +18 -0
  21. package/dist/neal/blocked-guidance.js +406 -0
  22. package/dist/neal/cli.js +471 -0
  23. package/dist/neal/commands/check.js +401 -0
  24. package/dist/neal/commands/compat.js +807 -0
  25. package/dist/neal/commands/interactive-activity.js +57 -0
  26. package/dist/neal/commands/new-run.js +79 -0
  27. package/dist/neal/commands/plan-and-execute.js +44 -0
  28. package/dist/neal/commands/recovery-guidance.js +217 -0
  29. package/dist/neal/commands/resume-run.js +395 -0
  30. package/dist/neal/commands/review.js +21 -0
  31. package/dist/neal/commands/runtime.js +557 -0
  32. package/dist/neal/commands/setup.js +596 -0
  33. package/dist/neal/commands/squash.js +113 -0
  34. package/dist/neal/commands/status.js +33 -0
  35. package/dist/neal/commands/writer-exit-codes.js +42 -0
  36. package/dist/neal/commit-message.js +17 -0
  37. package/dist/neal/config.js +432 -0
  38. package/dist/neal/context/artifacts.js +140 -0
  39. package/dist/neal/context/context.js +324 -0
  40. package/dist/neal/context/inline-review-context.js +131 -0
  41. package/dist/neal/context/reviewer-context.js +166 -0
  42. package/dist/neal/context/shared.js +117 -0
  43. package/dist/neal/context/types.js +1 -0
  44. package/dist/neal/diagnostic.js +208 -0
  45. package/dist/neal/execute-finalization.js +5 -0
  46. package/dist/neal/final-completion-review.js +188 -0
  47. package/dist/neal/final-completion.js +229 -0
  48. package/dist/neal/git.js +339 -0
  49. package/dist/neal/index.js +135 -0
  50. package/dist/neal/interactive-controls.js +85 -0
  51. package/dist/neal/logger.js +102 -0
  52. package/dist/neal/manual-gates.js +121 -0
  53. package/dist/neal/orchestrator/artifacts.js +70 -0
  54. package/dist/neal/orchestrator/completion.js +531 -0
  55. package/dist/neal/orchestrator/failures.js +31 -0
  56. package/dist/neal/orchestrator/notifications.js +175 -0
  57. package/dist/neal/orchestrator/phases/coder.js +516 -0
  58. package/dist/neal/orchestrator/phases/planning.js +540 -0
  59. package/dist/neal/orchestrator/phases/recovery.js +798 -0
  60. package/dist/neal/orchestrator/phases/review.js +136 -0
  61. package/dist/neal/orchestrator/phases/shared.js +279 -0
  62. package/dist/neal/orchestrator/run-loop.js +113 -0
  63. package/dist/neal/orchestrator/split-plan.js +235 -0
  64. package/dist/neal/orchestrator/transitions.js +309 -0
  65. package/dist/neal/orchestrator.js +215 -0
  66. package/dist/neal/phase-display.js +27 -0
  67. package/dist/neal/plan-doc.js +154 -0
  68. package/dist/neal/plan-queue.js +1092 -0
  69. package/dist/neal/plan-refinement.js +39 -0
  70. package/dist/neal/plan-validation.js +525 -0
  71. package/dist/neal/progress.js +237 -0
  72. package/dist/neal/prompts/assert-builder.js +13 -0
  73. package/dist/neal/prompts/execute.js +290 -0
  74. package/dist/neal/prompts/guidance.js +70 -0
  75. package/dist/neal/prompts/planning.js +313 -0
  76. package/dist/neal/prompts/review-doctrine.js +142 -0
  77. package/dist/neal/prompts/shared.js +101 -0
  78. package/dist/neal/prompts/specialized.js +212 -0
  79. package/dist/neal/prompts/specs.js +572 -0
  80. package/dist/neal/providers/anthropic-claude.js +1599 -0
  81. package/dist/neal/providers/detection.js +139 -0
  82. package/dist/neal/providers/generic-agentic-tools.js +586 -0
  83. package/dist/neal/providers/generic-agentic.js +1238 -0
  84. package/dist/neal/providers/liveness.js +151 -0
  85. package/dist/neal/providers/openai-codex.js +1014 -0
  86. package/dist/neal/providers/openai-compatible.js +654 -0
  87. package/dist/neal/providers/registry.js +389 -0
  88. package/dist/neal/providers/telemetry.js +208 -0
  89. package/dist/neal/providers/types.js +21 -0
  90. package/dist/neal/recovery-artifacts.js +50 -0
  91. package/dist/neal/resume-decision.js +220 -0
  92. package/dist/neal/resume-planner.js +265 -0
  93. package/dist/neal/retrospective.js +391 -0
  94. package/dist/neal/review-debt.js +18 -0
  95. package/dist/neal/review-findings/artifacts.js +173 -0
  96. package/dist/neal/review-findings/prompts.js +172 -0
  97. package/dist/neal/review-findings/provider.js +330 -0
  98. package/dist/neal/review-findings/run.js +373 -0
  99. package/dist/neal/review-findings/types.js +1 -0
  100. package/dist/neal/review-mode.js +67 -0
  101. package/dist/neal/review.js +137 -0
  102. package/dist/neal/run-lock.js +334 -0
  103. package/dist/neal/run-metrics.js +355 -0
  104. package/dist/neal/run-narrative-types.js +1 -0
  105. package/dist/neal/run-narrative.js +1374 -0
  106. package/dist/neal/run-registry.js +218 -0
  107. package/dist/neal/run-status.js +25 -0
  108. package/dist/neal/scopes.js +451 -0
  109. package/dist/neal/sensitive-text.js +8 -0
  110. package/dist/neal/squash-message.js +379 -0
  111. package/dist/neal/squash.js +591 -0
  112. package/dist/neal/state-invariants.js +496 -0
  113. package/dist/neal/state-views.js +344 -0
  114. package/dist/neal/state.js +887 -0
  115. package/dist/neal/status-footer.js +258 -0
  116. package/dist/neal/status.js +1260 -0
  117. package/dist/neal/storage-paths.js +57 -0
  118. package/dist/neal/support.js +58 -0
  119. package/dist/neal/terminal-narrator.js +435 -0
  120. package/dist/neal/types.js +1 -0
  121. package/dist/neal/verification-events.js +81 -0
  122. package/dist/neal/version.js +37 -0
  123. package/dist/neal/worktree-status.js +137 -0
  124. package/dist/notifier.js +44 -0
  125. package/docs/ADJUDICATOR_INVENTORY.md +310 -0
  126. package/docs/PROMPT_SPECS.md +266 -0
  127. package/docs/README.md +22 -0
  128. package/docs/architecture.md +113 -0
  129. package/docs/assets/neal-execution-flow.png +0 -0
  130. package/docs/automation.md +65 -0
  131. package/docs/comparison.md +105 -0
  132. package/docs/compat.md +269 -0
  133. package/docs/compatible-models.md +135 -0
  134. package/docs/demo.md +55 -0
  135. package/docs/maintenance.md +64 -0
  136. package/docs/plan-format.md +213 -0
  137. package/docs/providers.md +751 -0
  138. package/docs/release.md +147 -0
  139. package/docs/state-machine.md +266 -0
  140. package/docs/storage.md +207 -0
  141. package/docs/troubleshooting.md +152 -0
  142. package/examples/compat/add-edit-verify/PLAN.md +29 -0
  143. package/examples/compat/add-edit-verify/broken.diff +8 -0
  144. package/examples/compat/add-edit-verify/good.diff +8 -0
  145. package/examples/compat/add-edit-verify/package.json +5 -0
  146. package/examples/compat/add-edit-verify/src/add.js +2 -0
  147. package/examples/compat/add-edit-verify/test/add.test.js +9 -0
  148. package/examples/compat/is-even-add-test/PLAN.md +30 -0
  149. package/examples/compat/is-even-add-test/broken.diff +11 -0
  150. package/examples/compat/is-even-add-test/good.diff +11 -0
  151. package/examples/compat/is-even-add-test/package.json +5 -0
  152. package/examples/compat/is-even-add-test/src/is-even.js +3 -0
  153. package/examples/compat/is-even-add-test/test/is-even.test.js +9 -0
  154. package/examples/compat/manifest.json +60 -0
  155. package/examples/compat/plan-greeting/ISSUE.md +25 -0
  156. package/examples/compat/plan-greeting/package.json +5 -0
  157. package/examples/compat/plan-greeting/src/greet.js +2 -0
  158. package/examples/compat/plan-greeting/test/greet.test.js +8 -0
  159. package/examples/compat/reverse-grep-edit/PLAN.md +32 -0
  160. package/examples/compat/reverse-grep-edit/broken.diff +12 -0
  161. package/examples/compat/reverse-grep-edit/good.diff +12 -0
  162. package/examples/compat/reverse-grep-edit/package.json +5 -0
  163. package/examples/compat/reverse-grep-edit/src/strings.js +10 -0
  164. package/examples/compat/reverse-grep-edit/test/strings.test.js +15 -0
  165. package/examples/issue-triage-js/PLAN.md +83 -0
  166. package/examples/issue-triage-js/README.md +76 -0
  167. package/examples/issue-triage-js/package.json +9 -0
  168. package/examples/issue-triage-js/src/issue-triage.js +87 -0
  169. package/examples/issue-triage-js/test/issue-triage.test.js +107 -0
  170. package/package.json +70 -0
@@ -0,0 +1,1014 @@
1
+ import { Codex } from '@openai/codex-sdk';
2
+ import { runStructuredJsonProtocol } from '../agents/structured-json.js';
3
+ import { NealProviderError } from './types.js';
4
+ const OPENAI_CODEX_PROVIDER_ID = 'openai-codex';
5
+ class CodexInactivityTimeoutError extends Error {
6
+ constructor(timeoutMs) {
7
+ super(`Codex timed out after ${Math.round(timeoutMs / 1000)}s without progress`);
8
+ this.name = 'CodexInactivityTimeoutError';
9
+ }
10
+ }
11
+ // In-round transient (`api_error`) retries back off exponentially: 500 ms
12
+ // base, doubling per retry, capped at 5 s — the same schedule as the
13
+ // generic-agentic adapter. `sleep` is injectable through the adapter options
14
+ // so tests observe the delays without wall-clock waiting.
15
+ const API_RETRY_BASE_DELAY_MS = 500;
16
+ const API_RETRY_MAX_DELAY_MS = 5_000;
17
+ function defaultSleep(ms) {
18
+ return new Promise((resolve) => {
19
+ setTimeout(resolve, ms);
20
+ });
21
+ }
22
+ function getApiRetryDelayMs(retryCount) {
23
+ return Math.min(API_RETRY_BASE_DELAY_MS * 2 ** (retryCount - 1), API_RETRY_MAX_DELAY_MS);
24
+ }
25
+ async function nextWithTimeout(promise, timeoutMs, onTimeout) {
26
+ return await new Promise((resolvePromise, rejectPromise) => {
27
+ const timer = setTimeout(() => {
28
+ onTimeout?.();
29
+ rejectPromise(new CodexInactivityTimeoutError(timeoutMs));
30
+ }, timeoutMs);
31
+ promise.then((value) => {
32
+ clearTimeout(timer);
33
+ resolvePromise(value);
34
+ }, (error) => {
35
+ clearTimeout(timer);
36
+ rejectPromise(error);
37
+ });
38
+ });
39
+ }
40
+ // Propagates an external caller-owned abort signal into the adapter's
41
+ // internal per-turn AbortController so orchestration-level supervision can
42
+ // cancel an in-flight Codex turn. Repair threads use their own controllers
43
+ // and are intentionally not linked; repairs are short, prompt-only turns.
44
+ function linkExternalAbortSignal(signal, abortController) {
45
+ if (!signal) {
46
+ return;
47
+ }
48
+ if (signal.aborted) {
49
+ abortController.abort(signal.reason);
50
+ return;
51
+ }
52
+ signal.addEventListener('abort', () => abortController.abort(signal.reason), { once: true });
53
+ }
54
+ function isTransientCodexApiFailure(message) {
55
+ const text = message.toLowerCase();
56
+ return (text.includes('api_error') ||
57
+ text.includes('api error') ||
58
+ text.includes('internal server error') ||
59
+ text.includes('overloaded') ||
60
+ text.includes('at capacity') ||
61
+ text.includes('rate limit') ||
62
+ text.includes('temporar') ||
63
+ text.includes('try again'));
64
+ }
65
+ function inferCodexErrorKind(error, message, fallback) {
66
+ const text = message.toLowerCase();
67
+ if (error instanceof CodexInactivityTimeoutError || /\btimed out after\b/i.test(message)) {
68
+ return 'timeout';
69
+ }
70
+ if (text.includes('permission') || text.includes('denied') || text.includes('forbidden') || text.includes('not authorized')) {
71
+ return 'permission_denied';
72
+ }
73
+ if (text.includes('property_name_above_max_length') ||
74
+ text.includes('orphan function call output') ||
75
+ text.includes('failed to record rollout items') ||
76
+ text.includes('session unavailable') ||
77
+ text.includes('session not found') ||
78
+ text.includes('thread not found')) {
79
+ return 'session_unavailable';
80
+ }
81
+ if (isTransientCodexApiFailure(message)) {
82
+ return 'api_error';
83
+ }
84
+ return fallback;
85
+ }
86
+ function isRetryableCodexError(kind, message) {
87
+ return kind === 'timeout' || kind === 'session_unavailable' || (kind === 'api_error' && isTransientCodexApiFailure(message));
88
+ }
89
+ function isCodexReconnectProgressMessage(message) {
90
+ return /^reconnecting\.\.\.\s*\d+\/\d+\b/i.test(message.trim());
91
+ }
92
+ function createCodexProviderError(args) {
93
+ const kind = args.kind ?? inferCodexErrorKind(args.cause, args.message, 'provider_failed');
94
+ return new NealProviderError({
95
+ message: args.message,
96
+ provider: OPENAI_CODEX_PROVIDER_ID,
97
+ role: args.role,
98
+ sessionHandle: args.sessionHandle,
99
+ kind,
100
+ retryable: args.retryable ?? isRetryableCodexError(kind, args.message),
101
+ cause: args.cause,
102
+ });
103
+ }
104
+ function observeCodexFailure(args) {
105
+ return {
106
+ message: args.message,
107
+ kind: args.kind ?? inferCodexErrorKind(args.cause, args.message, 'provider_failed'),
108
+ source: args.source,
109
+ cause: args.cause,
110
+ };
111
+ }
112
+ function isGenericCodexProcessFailure(message) {
113
+ const text = message.toLowerCase();
114
+ return text.includes('codex exec exited') || text.includes('reading prompt from stdin') || text.includes('exited with code');
115
+ }
116
+ function shouldReplaceCodexFailure(current, next) {
117
+ if (!current) {
118
+ return true;
119
+ }
120
+ return isGenericCodexProcessFailure(current.message) || !isGenericCodexProcessFailure(next.message);
121
+ }
122
+ function createCodexProviderErrorFromObservedFailure(failure, args) {
123
+ const cause = args.cause === undefined
124
+ ? failure.cause
125
+ : {
126
+ source: failure.source,
127
+ providerCause: failure.cause,
128
+ iteratorCause: args.cause,
129
+ };
130
+ return createCodexProviderError({
131
+ message: failure.message,
132
+ role: args.role,
133
+ sessionHandle: args.sessionHandle,
134
+ kind: failure.kind,
135
+ cause,
136
+ });
137
+ }
138
+ function normalizeCodexProviderError(error, args) {
139
+ const message = error instanceof Error ? error.message : String(error);
140
+ if (error instanceof NealProviderError) {
141
+ if (error.sessionHandle || !args.sessionHandle) {
142
+ return error;
143
+ }
144
+ return createCodexProviderError({
145
+ message: error.message,
146
+ role: error.role,
147
+ sessionHandle: args.sessionHandle,
148
+ kind: error.kind,
149
+ retryable: error.retryable,
150
+ cause: error,
151
+ });
152
+ }
153
+ return createCodexProviderError({
154
+ message,
155
+ role: args.role,
156
+ sessionHandle: args.sessionHandle,
157
+ kind: args.kind,
158
+ cause: error,
159
+ });
160
+ }
161
+ function summarizeItem(item) {
162
+ switch (item.type) {
163
+ case 'command_execution':
164
+ return {
165
+ itemId: item.id,
166
+ itemType: item.type,
167
+ status: item.status,
168
+ command: item.command,
169
+ outputLength: item.aggregated_output.length,
170
+ exitCode: item.exit_code,
171
+ };
172
+ case 'file_change':
173
+ return {
174
+ itemId: item.id,
175
+ itemType: item.type,
176
+ status: item.status,
177
+ files: item.changes.map((change) => change.path),
178
+ };
179
+ case 'mcp_tool_call':
180
+ return {
181
+ itemId: item.id,
182
+ itemType: item.type,
183
+ status: item.status,
184
+ server: item.server,
185
+ tool: item.tool,
186
+ };
187
+ case 'web_search':
188
+ return {
189
+ itemId: item.id,
190
+ itemType: item.type,
191
+ query: item.query,
192
+ };
193
+ case 'todo_list':
194
+ return {
195
+ itemId: item.id,
196
+ itemType: item.type,
197
+ completed: item.items.filter((todo) => todo.completed).length,
198
+ total: item.items.length,
199
+ };
200
+ case 'reasoning':
201
+ return {
202
+ itemId: item.id,
203
+ itemType: item.type,
204
+ textLength: item.text.length,
205
+ };
206
+ case 'agent_message':
207
+ return {
208
+ itemId: item.id,
209
+ itemType: item.type,
210
+ textLength: item.text.length,
211
+ };
212
+ case 'error':
213
+ return {
214
+ itemId: item.id,
215
+ itemType: item.type,
216
+ message: item.message,
217
+ };
218
+ }
219
+ }
220
+ function buildCodexThreadOptions(args) {
221
+ return {
222
+ ...(args.model ? { model: args.model } : {}),
223
+ ...(args.effort ? { modelReasoningEffort: args.effort } : {}),
224
+ approvalPolicy: 'never',
225
+ sandboxMode: args.sandboxMode ?? 'danger-full-access',
226
+ workingDirectory: args.cwd,
227
+ skipGitRepoCheck: args.skipGitRepoCheck ?? false,
228
+ };
229
+ }
230
+ function createCodexThread(args) {
231
+ const codex = new Codex();
232
+ const threadOptions = buildCodexThreadOptions({
233
+ cwd: args.cwd,
234
+ model: args.model,
235
+ effort: args.effort,
236
+ skipGitRepoCheck: args.skipGitRepoCheck,
237
+ sandboxMode: args.sandboxMode,
238
+ });
239
+ return args.sessionHandle
240
+ ? codex.resumeThread(args.sessionHandle, threadOptions)
241
+ : codex.startThread(threadOptions);
242
+ }
243
+ async function emitProviderEvent(events, event) {
244
+ await events?.(event);
245
+ }
246
+ async function emitCodexStreamError(args) {
247
+ if (isCodexReconnectProgressMessage(args.message)) {
248
+ await emitProviderEvent(args.events, {
249
+ type: 'tool_progress',
250
+ provider: OPENAI_CODEX_PROVIDER_ID,
251
+ role: args.role,
252
+ label: args.label,
253
+ sessionHandle: args.sessionHandle,
254
+ toolName: 'codex_stream',
255
+ message: args.message,
256
+ isError: true,
257
+ providerData: { sdkEventType: 'error', nonFatal: true },
258
+ });
259
+ return false;
260
+ }
261
+ await emitProviderEvent(args.events, {
262
+ type: 'provider_error',
263
+ provider: OPENAI_CODEX_PROVIDER_ID,
264
+ role: args.role,
265
+ label: args.label,
266
+ sessionHandle: args.sessionHandle,
267
+ message: args.message,
268
+ providerData: { sdkEventType: 'error' },
269
+ });
270
+ return true;
271
+ }
272
+ async function consumeCodexTurn(turn, cwd, inactivityTimeoutMs, events, onSessionStarted, abortController) {
273
+ let finalResponse = '';
274
+ let observedFailure = null;
275
+ let sessionHandle = null;
276
+ const iterator = turn.events[Symbol.asyncIterator]();
277
+ while (true) {
278
+ let next;
279
+ try {
280
+ next = await nextWithTimeout(iterator.next(), inactivityTimeoutMs, () => abortController?.abort());
281
+ }
282
+ catch (error) {
283
+ if (observedFailure) {
284
+ throw createCodexProviderErrorFromObservedFailure(observedFailure, {
285
+ role: 'coder',
286
+ sessionHandle,
287
+ cause: error,
288
+ });
289
+ }
290
+ throw normalizeCodexProviderError(error, {
291
+ role: 'coder',
292
+ sessionHandle,
293
+ });
294
+ }
295
+ if (next.done) {
296
+ break;
297
+ }
298
+ const event = next.value;
299
+ switch (event.type) {
300
+ case 'thread.started':
301
+ sessionHandle = event.thread_id;
302
+ await emitProviderEvent(events, {
303
+ type: 'session_started',
304
+ provider: OPENAI_CODEX_PROVIDER_ID,
305
+ role: 'coder',
306
+ sessionHandle: event.thread_id,
307
+ providerData: { sdkEventType: event.type },
308
+ });
309
+ await onSessionStarted?.(event.thread_id);
310
+ break;
311
+ case 'turn.started':
312
+ await emitProviderEvent(events, {
313
+ type: 'turn_started',
314
+ provider: OPENAI_CODEX_PROVIDER_ID,
315
+ role: 'coder',
316
+ sessionHandle,
317
+ providerData: { sdkEventType: event.type },
318
+ });
319
+ break;
320
+ case 'item.started':
321
+ await emitProviderEvent(events, {
322
+ type: 'tool_started',
323
+ provider: OPENAI_CODEX_PROVIDER_ID,
324
+ role: 'coder',
325
+ sessionHandle,
326
+ itemId: summarizeItem(event.item).itemId,
327
+ toolName: event.item.type,
328
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
329
+ });
330
+ break;
331
+ case 'item.updated':
332
+ await emitProviderEvent(events, {
333
+ type: 'tool_progress',
334
+ provider: OPENAI_CODEX_PROVIDER_ID,
335
+ role: 'coder',
336
+ sessionHandle,
337
+ itemId: summarizeItem(event.item).itemId,
338
+ toolName: event.item.type,
339
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
340
+ });
341
+ break;
342
+ case 'item.completed':
343
+ if (event.item.type === 'command_execution') {
344
+ await emitProviderEvent(events, {
345
+ type: 'command_completed',
346
+ provider: OPENAI_CODEX_PROVIDER_ID,
347
+ role: 'coder',
348
+ sessionHandle,
349
+ itemId: event.item.id,
350
+ command: event.item.command,
351
+ status: event.item.status,
352
+ exitCode: event.item.exit_code ?? null,
353
+ output: event.item.aggregated_output,
354
+ outputLength: event.item.aggregated_output.length,
355
+ cwd,
356
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
357
+ });
358
+ }
359
+ else if (event.item.type === 'file_change' && event.item.changes.length > 0) {
360
+ const files = event.item.changes.map((change) => change.path);
361
+ await emitProviderEvent(events, {
362
+ type: 'file_changed',
363
+ provider: OPENAI_CODEX_PROVIDER_ID,
364
+ role: 'coder',
365
+ sessionHandle,
366
+ files,
367
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
368
+ });
369
+ }
370
+ else if (event.item.type === 'agent_message') {
371
+ finalResponse = event.item.text;
372
+ }
373
+ break;
374
+ case 'turn.completed':
375
+ await emitProviderEvent(events, {
376
+ type: 'turn_completed',
377
+ provider: OPENAI_CODEX_PROVIDER_ID,
378
+ role: 'coder',
379
+ sessionHandle,
380
+ usage: event.usage,
381
+ providerData: { sdkEventType: event.type },
382
+ });
383
+ if (event.usage !== undefined) {
384
+ await emitProviderEvent(events, {
385
+ type: 'usage_reported',
386
+ provider: OPENAI_CODEX_PROVIDER_ID,
387
+ role: 'coder',
388
+ sessionHandle,
389
+ usage: event.usage,
390
+ providerData: { sdkEventType: event.type },
391
+ });
392
+ }
393
+ break;
394
+ case 'turn.failed':
395
+ {
396
+ const nextFailure = observeCodexFailure({
397
+ message: event.error.message,
398
+ source: event.type,
399
+ cause: event.error,
400
+ });
401
+ if (shouldReplaceCodexFailure(observedFailure, nextFailure)) {
402
+ observedFailure = nextFailure;
403
+ await emitProviderEvent(events, {
404
+ type: 'provider_error',
405
+ provider: OPENAI_CODEX_PROVIDER_ID,
406
+ role: 'coder',
407
+ sessionHandle,
408
+ message: event.error.message,
409
+ providerData: { sdkEventType: event.type },
410
+ });
411
+ }
412
+ }
413
+ break;
414
+ case 'error':
415
+ {
416
+ const nextFailure = observeCodexFailure({
417
+ message: event.message,
418
+ source: event.type,
419
+ cause: event,
420
+ });
421
+ if (shouldReplaceCodexFailure(observedFailure, nextFailure)) {
422
+ if (await emitCodexStreamError({
423
+ events,
424
+ role: 'coder',
425
+ sessionHandle,
426
+ message: event.message,
427
+ })) {
428
+ observedFailure = nextFailure;
429
+ }
430
+ }
431
+ }
432
+ break;
433
+ default:
434
+ break;
435
+ }
436
+ }
437
+ if (observedFailure) {
438
+ throw createCodexProviderErrorFromObservedFailure(observedFailure, {
439
+ role: 'coder',
440
+ sessionHandle,
441
+ });
442
+ }
443
+ return { finalResponse, sessionHandle };
444
+ }
445
+ async function consumeCodexAdvisorTurn(turn, cwd, label, inactivityTimeoutMs, events, abortController) {
446
+ let finalResponse = '';
447
+ let observedFailure = null;
448
+ let sessionHandle = null;
449
+ const iterator = turn.events[Symbol.asyncIterator]();
450
+ while (true) {
451
+ let next;
452
+ try {
453
+ next = await nextWithTimeout(iterator.next(), inactivityTimeoutMs, () => abortController?.abort());
454
+ }
455
+ catch (error) {
456
+ if (observedFailure) {
457
+ throw createCodexProviderErrorFromObservedFailure(observedFailure, {
458
+ role: 'structured-advisor',
459
+ sessionHandle,
460
+ cause: error,
461
+ });
462
+ }
463
+ throw normalizeCodexProviderError(error, {
464
+ role: 'structured-advisor',
465
+ sessionHandle,
466
+ });
467
+ }
468
+ if (next.done) {
469
+ break;
470
+ }
471
+ const event = next.value;
472
+ switch (event.type) {
473
+ case 'thread.started':
474
+ sessionHandle = event.thread_id;
475
+ await emitProviderEvent(events, {
476
+ type: 'session_started',
477
+ provider: OPENAI_CODEX_PROVIDER_ID,
478
+ role: 'structured-advisor',
479
+ label,
480
+ sessionHandle: event.thread_id,
481
+ providerData: { sdkEventType: event.type },
482
+ });
483
+ break;
484
+ case 'turn.started':
485
+ await emitProviderEvent(events, {
486
+ type: 'turn_started',
487
+ provider: OPENAI_CODEX_PROVIDER_ID,
488
+ role: 'structured-advisor',
489
+ label,
490
+ sessionHandle,
491
+ providerData: { sdkEventType: event.type },
492
+ });
493
+ break;
494
+ case 'item.started':
495
+ await emitProviderEvent(events, {
496
+ type: 'tool_started',
497
+ provider: OPENAI_CODEX_PROVIDER_ID,
498
+ role: 'structured-advisor',
499
+ label,
500
+ sessionHandle,
501
+ itemId: summarizeItem(event.item).itemId,
502
+ toolName: event.item.type,
503
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
504
+ });
505
+ break;
506
+ case 'item.updated':
507
+ await emitProviderEvent(events, {
508
+ type: 'tool_progress',
509
+ provider: OPENAI_CODEX_PROVIDER_ID,
510
+ role: 'structured-advisor',
511
+ label,
512
+ sessionHandle,
513
+ itemId: summarizeItem(event.item).itemId,
514
+ toolName: event.item.type,
515
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
516
+ });
517
+ break;
518
+ case 'item.completed':
519
+ if (event.item.type === 'command_execution') {
520
+ await emitProviderEvent(events, {
521
+ type: 'command_completed',
522
+ provider: OPENAI_CODEX_PROVIDER_ID,
523
+ role: 'structured-advisor',
524
+ label,
525
+ sessionHandle,
526
+ itemId: event.item.id,
527
+ command: event.item.command,
528
+ status: event.item.status,
529
+ exitCode: event.item.exit_code ?? null,
530
+ output: event.item.aggregated_output,
531
+ outputLength: event.item.aggregated_output.length,
532
+ cwd,
533
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
534
+ });
535
+ }
536
+ else if (event.item.type === 'file_change' && event.item.changes.length > 0) {
537
+ const files = event.item.changes.map((change) => change.path);
538
+ await emitProviderEvent(events, {
539
+ type: 'file_changed',
540
+ provider: OPENAI_CODEX_PROVIDER_ID,
541
+ role: 'structured-advisor',
542
+ label,
543
+ sessionHandle,
544
+ files,
545
+ providerData: { sdkEventType: event.type, item: summarizeItem(event.item) },
546
+ });
547
+ }
548
+ else if (event.item.type === 'agent_message') {
549
+ finalResponse = event.item.text;
550
+ }
551
+ break;
552
+ case 'turn.completed':
553
+ await emitProviderEvent(events, {
554
+ type: 'turn_completed',
555
+ provider: OPENAI_CODEX_PROVIDER_ID,
556
+ role: 'structured-advisor',
557
+ label,
558
+ sessionHandle,
559
+ usage: event.usage,
560
+ providerData: { sdkEventType: event.type },
561
+ });
562
+ if (event.usage !== undefined) {
563
+ await emitProviderEvent(events, {
564
+ type: 'usage_reported',
565
+ provider: OPENAI_CODEX_PROVIDER_ID,
566
+ role: 'structured-advisor',
567
+ label,
568
+ sessionHandle,
569
+ usage: event.usage,
570
+ providerData: { sdkEventType: event.type },
571
+ });
572
+ }
573
+ break;
574
+ case 'turn.failed':
575
+ {
576
+ const nextFailure = observeCodexFailure({
577
+ message: event.error.message,
578
+ source: event.type,
579
+ cause: event.error,
580
+ });
581
+ if (shouldReplaceCodexFailure(observedFailure, nextFailure)) {
582
+ observedFailure = nextFailure;
583
+ await emitProviderEvent(events, {
584
+ type: 'provider_error',
585
+ provider: OPENAI_CODEX_PROVIDER_ID,
586
+ role: 'structured-advisor',
587
+ label,
588
+ sessionHandle,
589
+ message: event.error.message,
590
+ providerData: { sdkEventType: event.type },
591
+ });
592
+ }
593
+ }
594
+ break;
595
+ case 'error':
596
+ {
597
+ const nextFailure = observeCodexFailure({
598
+ message: event.message,
599
+ source: event.type,
600
+ cause: event,
601
+ });
602
+ if (shouldReplaceCodexFailure(observedFailure, nextFailure)) {
603
+ if (await emitCodexStreamError({
604
+ events,
605
+ role: 'structured-advisor',
606
+ label,
607
+ sessionHandle,
608
+ message: event.message,
609
+ })) {
610
+ observedFailure = nextFailure;
611
+ }
612
+ }
613
+ }
614
+ break;
615
+ default:
616
+ break;
617
+ }
618
+ }
619
+ if (observedFailure) {
620
+ throw createCodexProviderErrorFromObservedFailure(observedFailure, {
621
+ role: 'structured-advisor',
622
+ sessionHandle,
623
+ });
624
+ }
625
+ return { finalResponse, sessionHandle };
626
+ }
627
+ function parseCodexStructuredOutput(finalResponse, label, sessionHandle) {
628
+ try {
629
+ return JSON.parse(finalResponse);
630
+ }
631
+ catch (error) {
632
+ const message = error instanceof Error ? error.message : String(error);
633
+ throw createCodexProviderError({
634
+ message: `Codex ${label} returned invalid structured output: ${message}`,
635
+ role: 'structured-advisor',
636
+ sessionHandle,
637
+ kind: 'structured_output_invalid',
638
+ cause: error,
639
+ });
640
+ }
641
+ }
642
+ function buildCodexStructuredAdvisorRunOptions(args, signal) {
643
+ if (args.structuredJsonProtocol?.protocol === 'neal-json-block-v1') {
644
+ return { signal };
645
+ }
646
+ return {
647
+ outputSchema: args.schema,
648
+ signal,
649
+ };
650
+ }
651
+ // Best-effort `toolPolicy` enforcement for Codex coder turns. The Codex SDK
652
+ // has no per-tool-call hook, so `allowedWritePaths` cannot be enforced at
653
+ // path granularity; sandbox modes are the strongest mechanical lever it
654
+ // offers. A jailed plan-authoring turn (allowRun false + allowedWritePaths)
655
+ // runs under 'workspace-write' so writes are confined to the workspace
656
+ // sandbox. Residual gap: shell still runs inside that sandbox and can write
657
+ // any workspace path, not just the allowlisted plan document — documented in
658
+ // docs/providers.md. Policy-free coder rounds keep 'danger-full-access'
659
+ // because scope verification legitimately runs arbitrary commands.
660
+ function getCodexCoderSandboxMode(toolPolicy) {
661
+ return toolPolicy?.allowRun === false && toolPolicy.allowedWritePaths
662
+ ? 'workspace-write'
663
+ : 'danger-full-access';
664
+ }
665
+ class OpenAICodexCoderAdapter {
666
+ options;
667
+ constructor(options = {}) {
668
+ this.options = options;
669
+ }
670
+ async runPrompt(args) {
671
+ let thread = null;
672
+ try {
673
+ const createThread = this.options.createThread ?? createCodexThread;
674
+ thread = createThread({
675
+ cwd: args.cwd,
676
+ sessionHandle: args.resumeHandle,
677
+ model: this.options.model ?? undefined,
678
+ effort: this.options.effort ?? undefined,
679
+ skipGitRepoCheck: args.skipGitRepoCheck,
680
+ sandboxMode: getCodexCoderSandboxMode(args.toolPolicy),
681
+ });
682
+ const abortController = new AbortController();
683
+ linkExternalAbortSignal(args.signal, abortController);
684
+ const streamedTurn = await thread.runStreamed(args.prompt, {
685
+ ...(args.outputSchema ? { outputSchema: args.outputSchema } : {}),
686
+ signal: abortController.signal,
687
+ });
688
+ const result = await consumeCodexTurn(streamedTurn, args.cwd, args.inactivityTimeoutMs, args.events, args.onSessionStarted, abortController);
689
+ if (args.outputSchema) {
690
+ await emitProviderEvent(args.events, {
691
+ type: 'structured_output_received',
692
+ provider: OPENAI_CODEX_PROVIDER_ID,
693
+ role: 'coder',
694
+ sessionHandle: thread.id,
695
+ });
696
+ }
697
+ return {
698
+ sessionHandle: thread.id,
699
+ finalResponse: result.finalResponse,
700
+ };
701
+ }
702
+ catch (error) {
703
+ const providerError = normalizeCodexProviderError(error, {
704
+ role: 'coder',
705
+ sessionHandle: thread?.id ?? args.resumeHandle ?? null,
706
+ });
707
+ await emitProviderEvent(args.events, {
708
+ type: 'provider_error',
709
+ provider: OPENAI_CODEX_PROVIDER_ID,
710
+ role: 'coder',
711
+ sessionHandle: providerError.sessionHandle,
712
+ message: providerError.message,
713
+ errorKind: providerError.kind,
714
+ });
715
+ throw providerError;
716
+ }
717
+ }
718
+ async runStructuredPrompt(args) {
719
+ const sleep = this.options.sleep ?? defaultSleep;
720
+ let apiRetryCount = 0;
721
+ const apiRetryLimit = args.apiRetryLimit ?? 0;
722
+ while (true) {
723
+ let thread = null;
724
+ try {
725
+ const createThread = this.options.createThread ?? createCodexThread;
726
+ thread = createThread({
727
+ cwd: args.cwd,
728
+ sessionHandle: args.resumeHandle,
729
+ model: this.options.model ?? undefined,
730
+ effort: this.options.effort ?? undefined,
731
+ skipGitRepoCheck: args.skipGitRepoCheck,
732
+ sandboxMode: getCodexCoderSandboxMode(args.toolPolicy),
733
+ });
734
+ const abortController = new AbortController();
735
+ linkExternalAbortSignal(args.signal, abortController);
736
+ return await runStructuredJsonProtocol({
737
+ provider: OPENAI_CODEX_PROVIDER_ID,
738
+ role: 'coder',
739
+ label: args.label,
740
+ protocol: args.structuredJsonProtocol,
741
+ prompt: args.prompt,
742
+ events: args.events,
743
+ initialSessionHandle: args.resumeHandle ?? null,
744
+ runInitial: async (prompt) => {
745
+ const streamedTurn = await thread.runStreamed(prompt, {
746
+ signal: abortController.signal,
747
+ });
748
+ const result = await consumeCodexTurn(streamedTurn, args.cwd, args.inactivityTimeoutMs, args.events, args.onSessionStarted, abortController);
749
+ return {
750
+ assistantText: result.finalResponse,
751
+ sessionHandle: thread.id,
752
+ };
753
+ },
754
+ runRepair: async (prompt) => {
755
+ // Repair runs on a fresh prompt-only thread whose prompt forbids
756
+ // tool use entirely, so nothing in the repair flow needs write
757
+ // access; the read-only sandbox enforces that mechanically
758
+ // (mirroring the structured-advisor repair thread).
759
+ const repairThread = createThread({
760
+ cwd: args.cwd,
761
+ model: this.options.model ?? undefined,
762
+ effort: this.options.effort ?? undefined,
763
+ skipGitRepoCheck: true,
764
+ sandboxMode: 'read-only',
765
+ });
766
+ const repairAbortController = new AbortController();
767
+ const streamedTurn = await repairThread.runStreamed(prompt, {
768
+ signal: repairAbortController.signal,
769
+ });
770
+ const result = await consumeCodexTurn(streamedTurn, args.cwd, args.inactivityTimeoutMs, args.events, undefined, repairAbortController);
771
+ return {
772
+ assistantText: result.finalResponse,
773
+ sessionHandle: repairThread.id,
774
+ };
775
+ },
776
+ createProviderError: (errorArgs) => createCodexProviderError({
777
+ message: errorArgs.message,
778
+ role: 'coder',
779
+ sessionHandle: errorArgs.sessionHandle,
780
+ kind: errorArgs.kind,
781
+ retryable: false,
782
+ cause: errorArgs.cause,
783
+ }),
784
+ });
785
+ }
786
+ catch (error) {
787
+ const providerError = normalizeCodexProviderError(error, {
788
+ role: 'coder',
789
+ sessionHandle: thread?.id ?? args.resumeHandle ?? null,
790
+ });
791
+ // Bounded in-round retry for transient provider failures, mirroring
792
+ // the Claude structured-coder loop: only retryable `api_error` kinds
793
+ // burn budget and a caller abort never does. Each retry starts a
794
+ // fresh thread; per the adapter contract resumed sessions pass 0.
795
+ if (providerError.kind === 'api_error' &&
796
+ providerError.retryable &&
797
+ apiRetryCount < apiRetryLimit &&
798
+ !args.signal?.aborted) {
799
+ apiRetryCount += 1;
800
+ await emitProviderEvent(args.events, {
801
+ type: 'tool_progress',
802
+ provider: OPENAI_CODEX_PROVIDER_ID,
803
+ role: 'coder',
804
+ label: args.label,
805
+ sessionHandle: providerError.sessionHandle,
806
+ toolName: 'api_retry',
807
+ message: `transient Codex failure; retrying ${args.label} (${apiRetryCount}/${apiRetryLimit})`,
808
+ isError: true,
809
+ providerData: {
810
+ retryCount: apiRetryCount,
811
+ retryLimit: apiRetryLimit,
812
+ message: providerError.message,
813
+ },
814
+ });
815
+ await sleep(getApiRetryDelayMs(apiRetryCount));
816
+ continue;
817
+ }
818
+ await emitProviderEvent(args.events, {
819
+ type: 'provider_error',
820
+ provider: OPENAI_CODEX_PROVIDER_ID,
821
+ role: 'coder',
822
+ label: args.label,
823
+ sessionHandle: providerError.sessionHandle,
824
+ message: providerError.message,
825
+ errorKind: providerError.kind,
826
+ });
827
+ throw providerError;
828
+ }
829
+ }
830
+ }
831
+ }
832
+ class OpenAICodexStructuredAdvisorAdapter {
833
+ options;
834
+ constructor(options = {}) {
835
+ this.options = options;
836
+ }
837
+ async runStructuredRound(args) {
838
+ const createThread = this.options.createThread ?? createCodexThread;
839
+ const sleep = this.options.sleep ?? defaultSleep;
840
+ let apiRetryCount = 0;
841
+ const apiRetryLimit = args.apiRetryLimit;
842
+ while (true) {
843
+ let thread = null;
844
+ try {
845
+ thread = createThread({
846
+ cwd: args.cwd,
847
+ sessionHandle: args.resumeHandle,
848
+ model: this.options.model ?? undefined,
849
+ effort: this.options.effort ?? undefined,
850
+ skipGitRepoCheck: args.skipGitRepoCheck,
851
+ sandboxMode: 'read-only',
852
+ });
853
+ const abortController = new AbortController();
854
+ linkExternalAbortSignal(args.signal, abortController);
855
+ if (args.structuredJsonProtocol?.protocol === 'neal-json-block-v1') {
856
+ return await runStructuredJsonProtocol({
857
+ provider: OPENAI_CODEX_PROVIDER_ID,
858
+ role: 'structured-advisor',
859
+ label: args.label,
860
+ protocol: args.structuredJsonProtocol,
861
+ prompt: args.prompt,
862
+ events: args.events,
863
+ initialSessionHandle: args.resumeHandle ?? null,
864
+ runInitial: async (prompt) => {
865
+ const streamedTurn = await thread.runStreamed(prompt, {
866
+ signal: abortController.signal,
867
+ });
868
+ const result = await consumeCodexAdvisorTurn(streamedTurn, args.cwd, args.label, args.inactivityTimeoutMs, args.events, abortController);
869
+ return {
870
+ assistantText: result.finalResponse,
871
+ sessionHandle: thread.id ?? result.sessionHandle ?? null,
872
+ };
873
+ },
874
+ runRepair: async (prompt) => {
875
+ const repairThread = createThread({
876
+ cwd: args.cwd,
877
+ model: this.options.model ?? undefined,
878
+ effort: this.options.effort ?? undefined,
879
+ skipGitRepoCheck: true,
880
+ sandboxMode: 'read-only',
881
+ });
882
+ const repairAbortController = new AbortController();
883
+ const streamedTurn = await repairThread.runStreamed(prompt, {
884
+ signal: repairAbortController.signal,
885
+ });
886
+ const result = await consumeCodexAdvisorTurn(streamedTurn, args.cwd, args.label, args.inactivityTimeoutMs, args.events, repairAbortController);
887
+ return {
888
+ assistantText: result.finalResponse,
889
+ sessionHandle: repairThread.id ?? result.sessionHandle ?? null,
890
+ };
891
+ },
892
+ createProviderError: (errorArgs) => createCodexProviderError({
893
+ message: errorArgs.message,
894
+ role: 'structured-advisor',
895
+ sessionHandle: errorArgs.sessionHandle,
896
+ kind: errorArgs.kind,
897
+ retryable: false,
898
+ cause: errorArgs.cause,
899
+ }),
900
+ });
901
+ }
902
+ const streamedTurn = await thread.runStreamed(args.prompt, buildCodexStructuredAdvisorRunOptions(args, abortController.signal));
903
+ const result = await consumeCodexAdvisorTurn(streamedTurn, args.cwd, args.label, args.inactivityTimeoutMs, args.events, abortController);
904
+ const structured = parseCodexStructuredOutput(result.finalResponse, args.label, thread.id);
905
+ await emitProviderEvent(args.events, {
906
+ type: 'structured_output_received',
907
+ provider: OPENAI_CODEX_PROVIDER_ID,
908
+ role: 'structured-advisor',
909
+ label: args.label,
910
+ sessionHandle: thread.id,
911
+ });
912
+ return {
913
+ sessionHandle: thread.id,
914
+ structured,
915
+ };
916
+ }
917
+ catch (error) {
918
+ const providerError = normalizeCodexProviderError(error, {
919
+ role: 'structured-advisor',
920
+ sessionHandle: thread?.id ?? args.resumeHandle ?? null,
921
+ });
922
+ // Bounded in-round retry for transient provider failures, mirroring
923
+ // the Claude advisor rounds: only retryable `api_error` kinds burn
924
+ // budget and a caller abort never does. Each retry starts a fresh
925
+ // thread; per the adapter contract resumed sessions pass 0.
926
+ if (providerError.kind === 'api_error' &&
927
+ providerError.retryable &&
928
+ apiRetryCount < apiRetryLimit &&
929
+ !args.signal?.aborted) {
930
+ apiRetryCount += 1;
931
+ await emitProviderEvent(args.events, {
932
+ type: 'tool_progress',
933
+ provider: OPENAI_CODEX_PROVIDER_ID,
934
+ role: 'structured-advisor',
935
+ label: args.label,
936
+ sessionHandle: providerError.sessionHandle,
937
+ toolName: 'api_retry',
938
+ message: `transient Codex failure; retrying ${args.label} (${apiRetryCount}/${apiRetryLimit})`,
939
+ isError: true,
940
+ providerData: {
941
+ retryCount: apiRetryCount,
942
+ retryLimit: apiRetryLimit,
943
+ message: providerError.message,
944
+ },
945
+ });
946
+ await sleep(getApiRetryDelayMs(apiRetryCount));
947
+ continue;
948
+ }
949
+ await emitProviderEvent(args.events, {
950
+ type: 'provider_error',
951
+ provider: OPENAI_CODEX_PROVIDER_ID,
952
+ role: 'structured-advisor',
953
+ label: args.label,
954
+ sessionHandle: providerError.sessionHandle,
955
+ message: providerError.message,
956
+ errorKind: providerError.kind,
957
+ });
958
+ throw providerError;
959
+ }
960
+ }
961
+ }
962
+ }
963
+ export function createOpenAICodexCoderAdapter(options = {}) {
964
+ return new OpenAICodexCoderAdapter(options);
965
+ }
966
+ export function createOpenAICodexStructuredAdvisorAdapter(options = {}) {
967
+ return new OpenAICodexStructuredAdvisorAdapter(options);
968
+ }
969
+ export const openAICodexProviderDefinition = {
970
+ id: 'openai-codex',
971
+ displayName: 'OpenAI Codex',
972
+ capabilities: {
973
+ coder: {
974
+ supported: true,
975
+ toolAccess: {
976
+ read: true,
977
+ write: true,
978
+ shell: true,
979
+ },
980
+ supportsSessionResume: true,
981
+ supportsModelOverride: true,
982
+ supportsStructuredOutput: true,
983
+ usageReporting: 'opportunistic',
984
+ supportedEfforts: ['minimal', 'low', 'medium', 'high', 'xhigh'],
985
+ },
986
+ 'structured-advisor': {
987
+ supported: true,
988
+ // Reviewers run read-only: no write, no shell. The structured-advisor
989
+ // thread (primary and repair) runs under the SDK read-only sandbox so a
990
+ // reviewer can inspect the checkout but never mutate it or run commands.
991
+ toolAccess: {
992
+ read: true,
993
+ write: false,
994
+ shell: false,
995
+ },
996
+ supportsSessionResume: true,
997
+ supportsModelOverride: true,
998
+ supportsStructuredOutput: true,
999
+ usageReporting: 'opportunistic',
1000
+ supportedEfforts: ['minimal', 'low', 'medium', 'high', 'xhigh'],
1001
+ },
1002
+ },
1003
+ createCoderAdapter: createOpenAICodexCoderAdapter,
1004
+ createStructuredAdvisorAdapter: createOpenAICodexStructuredAdvisorAdapter,
1005
+ };
1006
+ export const openAICodexProviderTestHooks = {
1007
+ buildCodexStructuredAdvisorRunOptions,
1008
+ buildCodexThreadOptions,
1009
+ createCoderAdapterWithThreadFactory: (createThread, options) => new OpenAICodexCoderAdapter({ ...options, createThread }),
1010
+ createStructuredAdvisorAdapterWithThreadFactory: (createThread, options) => new OpenAICodexStructuredAdvisorAdapter({ ...options, createThread }),
1011
+ consumeCodexTurn,
1012
+ consumeCodexAdvisorTurn,
1013
+ parseCodexStructuredOutput,
1014
+ };