@jrkropp/codex-js 0.1.2 → 0.1.4

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 (134) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/README.md +2 -2
  3. package/dist/ClientNotification-B6-FhXQf.d.ts +5 -0
  4. package/dist/DynamicToolCallResponse-82DFjES2.d.ts +8 -0
  5. package/dist/DynamicToolSpec-CfnhqAYK.d.ts +29 -0
  6. package/dist/PermissionsRequestApprovalResponse-DxzPPDRb.d.ts +55 -0
  7. package/dist/ProviderStatusBanner-BlP6lzwE.d.ts +441 -0
  8. package/dist/ServerRequest-B5cKVJjr.d.ts +2181 -0
  9. package/dist/{session-DPhHN7RZ.d.ts → ThreadResumeResponse-DvmE1juU.d.ts} +3 -306
  10. package/dist/ToolRequestUserInputQuestion-CeZa5X1J.d.ts +23 -0
  11. package/dist/ToolRequestUserInputResponse-zcPLwbiK.d.ts +17 -0
  12. package/dist/TurnSteerResponse-0kBCfplh.d.ts +209 -0
  13. package/dist/WebSearchToolConfig-D3ep0625.d.ts +18 -0
  14. package/dist/chat-runtime-9RkXHC_w.d.ts +382 -0
  15. package/dist/chunk-2DZRMCI2.js +1258 -0
  16. package/dist/chunk-2DZRMCI2.js.map +1 -0
  17. package/dist/chunk-4DPLJPB5.js +396 -0
  18. package/dist/chunk-4DPLJPB5.js.map +1 -0
  19. package/dist/chunk-5JMJ6OI5.js +3 -0
  20. package/dist/chunk-5JMJ6OI5.js.map +1 -0
  21. package/dist/chunk-6ZMJ34KE.js +1153 -0
  22. package/dist/chunk-6ZMJ34KE.js.map +1 -0
  23. package/dist/chunk-CGBS37IU.js +128 -0
  24. package/dist/chunk-CGBS37IU.js.map +1 -0
  25. package/dist/chunk-DCMKA2A6.js +18 -0
  26. package/dist/chunk-DCMKA2A6.js.map +1 -0
  27. package/dist/chunk-DYLHN3HG.js +937 -0
  28. package/dist/chunk-DYLHN3HG.js.map +1 -0
  29. package/dist/{chunk-SVK6PLGO.js → chunk-LWQNX4LI.js} +12009 -18768
  30. package/dist/chunk-LWQNX4LI.js.map +1 -0
  31. package/dist/{chunk-JLDH4U5L.js → chunk-NCI4MAWZ.js} +317 -1967
  32. package/dist/chunk-NCI4MAWZ.js.map +1 -0
  33. package/dist/chunk-O44XP7LH.js +214 -0
  34. package/dist/chunk-O44XP7LH.js.map +1 -0
  35. package/dist/chunk-PST3ZWX2.js +555 -0
  36. package/dist/chunk-PST3ZWX2.js.map +1 -0
  37. package/dist/chunk-SYPHCDRD.js +1133 -0
  38. package/dist/chunk-SYPHCDRD.js.map +1 -0
  39. package/dist/chunk-V4BMZWBM.js +2401 -0
  40. package/dist/chunk-V4BMZWBM.js.map +1 -0
  41. package/dist/chunk-W7S6HFCQ.js +1983 -0
  42. package/dist/chunk-W7S6HFCQ.js.map +1 -0
  43. package/dist/chunk-YHVCFD2D.js +117 -0
  44. package/dist/chunk-YHVCFD2D.js.map +1 -0
  45. package/dist/chunk-Z63UPBS3.js +152 -0
  46. package/dist/chunk-Z63UPBS3.js.map +1 -0
  47. package/dist/client/index.d.ts +16 -4
  48. package/dist/client/index.js +13 -1
  49. package/dist/codex-rs/app-server/index.d.ts +161 -0
  50. package/dist/codex-rs/app-server/index.js +13 -0
  51. package/dist/codex-rs/app-server/index.js.map +1 -0
  52. package/dist/codex-rs/app-server-protocol/index.d.ts +1722 -0
  53. package/dist/codex-rs/app-server-protocol/index.js +6 -0
  54. package/dist/codex-rs/app-server-protocol/index.js.map +1 -0
  55. package/dist/codex-rs/app-server-protocol/protocol.d.ts +19 -0
  56. package/dist/codex-rs/app-server-protocol/protocol.js +4 -0
  57. package/dist/codex-rs/app-server-protocol/protocol.js.map +1 -0
  58. package/dist/codex-rs/codex-api/index.d.ts +104 -0
  59. package/dist/codex-rs/codex-api/index.js +11 -0
  60. package/dist/codex-rs/codex-api/index.js.map +1 -0
  61. package/dist/codex-rs/config/index.d.ts +88 -0
  62. package/dist/codex-rs/config/index.js +4 -0
  63. package/dist/codex-rs/config/index.js.map +1 -0
  64. package/dist/codex-rs/core/config/index.d.ts +61 -0
  65. package/dist/codex-rs/core/config/index.js +5 -0
  66. package/dist/codex-rs/core/config/index.js.map +1 -0
  67. package/dist/codex-rs/core/index.d.ts +1393 -0
  68. package/dist/codex-rs/core/index.js +11 -0
  69. package/dist/codex-rs/core/index.js.map +1 -0
  70. package/dist/codex-rs/model-provider/index.d.ts +2 -0
  71. package/dist/codex-rs/model-provider/index.js +4 -0
  72. package/dist/codex-rs/model-provider/index.js.map +1 -0
  73. package/dist/codex-rs/models-manager/index.d.ts +2 -0
  74. package/dist/codex-rs/models-manager/index.js +4 -0
  75. package/dist/codex-rs/models-manager/index.js.map +1 -0
  76. package/dist/codex-rs/parity.d.ts +26 -0
  77. package/dist/codex-rs/parity.js +3 -0
  78. package/dist/codex-rs/parity.js.map +1 -0
  79. package/dist/codex-rs/thread-store/index.d.ts +5 -0
  80. package/dist/codex-rs/thread-store/index.js +4 -0
  81. package/dist/codex-rs/thread-store/index.js.map +1 -0
  82. package/dist/codex-rs/unsupported.d.ts +15 -0
  83. package/dist/codex-rs/unsupported.js +22 -0
  84. package/dist/codex-rs/unsupported.js.map +1 -0
  85. package/dist/codex-rs/utils/output-truncation.d.ts +21 -0
  86. package/dist/codex-rs/utils/output-truncation.js +4 -0
  87. package/dist/codex-rs/utils/output-truncation.js.map +1 -0
  88. package/dist/codex-rs/utils/string.d.ts +7 -0
  89. package/dist/codex-rs/utils/string.js +3 -0
  90. package/dist/codex-rs/utils/string.js.map +1 -0
  91. package/dist/common-CTyph5x8.d.ts +40 -0
  92. package/dist/event-mapping-CbISdQ1D.d.ts +43 -0
  93. package/dist/history-CfM-4V7b.d.ts +1654 -0
  94. package/dist/index-77U_Oc-a.d.ts +63 -0
  95. package/dist/index-CoDZosq0.d.ts +261 -0
  96. package/dist/index.d.ts +18 -7
  97. package/dist/index.js +16 -2
  98. package/dist/lib-nXlaKiS-.d.ts +48 -0
  99. package/dist/live-thread-BMvlflzM.d.ts +30 -0
  100. package/dist/merge-B_AWVmnI.d.ts +24 -0
  101. package/dist/mod-DYVLSWO4.d.ts +91 -0
  102. package/dist/plan-mode-Cv6KWb_S.d.ts +14 -0
  103. package/dist/proposed-plan-DpN1ma0Y.d.ts +53 -0
  104. package/dist/protocol-mpBcYHrm.d.ts +1655 -0
  105. package/dist/react/index.d.ts +56 -48
  106. package/dist/react/index.js +16 -2
  107. package/dist/{remote-_6TDvg-g.d.ts → remote-ClZbq9KN.d.ts} +3 -1
  108. package/dist/rendered-thread-AOxw3V5b.d.ts +29 -0
  109. package/dist/responses_websocket-BhxSgCzK.d.ts +183 -0
  110. package/dist/runtime-Cm6ml53h.d.ts +528 -0
  111. package/dist/server/index.d.ts +29 -2416
  112. package/dist/server/index.js +13 -1
  113. package/dist/session-BRYzi8OT.d.ts +46 -0
  114. package/dist/shadcn/index.d.ts +1 -1
  115. package/dist/{sidebar-DT2XoitN.d.ts → sidebar-DMMij22z.d.ts} +1 -1
  116. package/dist/spec_plan_types-CmsJ-Tfn.d.ts +260 -0
  117. package/dist/{store-GYldc9EJ.d.ts → store-AGRxhgQ3.d.ts} +2 -1
  118. package/dist/t3code/apps/web/components/chat.d.ts +508 -0
  119. package/dist/t3code/apps/web/components/chat.js +12 -0
  120. package/dist/t3code/apps/web/components/chat.js.map +1 -0
  121. package/dist/t3code/apps/web/index.d.ts +12 -0
  122. package/dist/t3code/apps/web/index.js +13 -0
  123. package/dist/t3code/apps/web/index.js.map +1 -0
  124. package/dist/testing/index.d.ts +9 -91
  125. package/dist/testing/index.js +13 -1
  126. package/dist/thread-history-builder-zW0zeqcS.d.ts +58 -0
  127. package/dist/thread_event_store-C0zYzukG.d.ts +77 -0
  128. package/dist/types-BTeabLYr.d.ts +126 -0
  129. package/package.json +152 -88
  130. package/dist/chat-runtime-D7wu_KbX.d.ts +0 -747
  131. package/dist/chunk-JLDH4U5L.js.map +0 -1
  132. package/dist/chunk-SVK6PLGO.js.map +0 -1
  133. package/dist/index-CB9la6xE.d.ts +0 -112
  134. package/dist/thread_event_store-B9CoQUIA.d.ts +0 -3868
@@ -0,0 +1,1983 @@
1
+ import { buildOptimisticUserMessageTurnItem, deriveContextWindowSnapshotFromTokenUsage, proposedPlanTitle, normalizePendingUserInputQuestion, TooltipProvider, ChatView, ChatComposer, useComposerDraftStore, defaultCodexModel, defaultCodexReasoningEffort, resolvePlanFollowUpSubmission, buildRequestUserInputResponse, buildPlanImplementationPrompt, createComposerImageAttachments } from './chunk-NCI4MAWZ.js';
2
+ import { Sidebar, SidebarProvider, SidebarInset } from './chunk-FN3SWHRH.js';
3
+ import { ThreadEventStore, AppServerSession, threadEventSnapshotHasStarted } from './chunk-6ZMJ34KE.js';
4
+ import { thread_token_usage_updated_notification_from_rollout_items } from './chunk-V4BMZWBM.js';
5
+ import { asThreadId } from './chunk-LWQNX4LI.js';
6
+ import { createContext, useState, useCallback, useMemo, useRef, useEffect, createElement, Fragment as Fragment$1, useContext } from 'react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+
9
+ function createLocalDispatchSnapshot(threadState) {
10
+ return {
11
+ errorCount: threadState?.errors.length ?? 0,
12
+ itemIds: threadTurnItemIds(threadState),
13
+ runningTurnIds: [...threadState?.activeTurnIds ?? []],
14
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
15
+ };
16
+ }
17
+ function hasServerAcknowledgedLocalDispatch(input) {
18
+ const localDispatch = input.localDispatch;
19
+ if (!localDispatch) {
20
+ return false;
21
+ }
22
+ if (input.hasPendingRequest || Boolean(input.runtimeError)) {
23
+ return true;
24
+ }
25
+ const runningTurnIds = input.threadState?.activeTurnIds ?? [];
26
+ if (runningTurnIds.some(
27
+ (turnId) => !localDispatch.runningTurnIds.includes(turnId)
28
+ )) {
29
+ return true;
30
+ }
31
+ if (threadTurnItemIds(input.threadState).some(
32
+ (itemId) => !localDispatch.itemIds.includes(itemId)
33
+ )) {
34
+ return true;
35
+ }
36
+ return (input.threadState?.errors.length ?? 0) > localDispatch.errorCount;
37
+ }
38
+ function useLocalDispatchState(input) {
39
+ const [localDispatch, setLocalDispatch] = useState(null);
40
+ const beginLocalDispatch = useCallback(() => {
41
+ setLocalDispatch(
42
+ (current) => current ?? createLocalDispatchSnapshot(input.threadState)
43
+ );
44
+ }, [input.threadState]);
45
+ const resetLocalDispatch = useCallback(() => {
46
+ setLocalDispatch(null);
47
+ }, []);
48
+ const serverAcknowledgedLocalDispatch = useMemo(
49
+ () => hasServerAcknowledgedLocalDispatch({
50
+ hasPendingRequest: input.hasPendingRequest,
51
+ localDispatch,
52
+ runtimeError: input.runtimeError,
53
+ threadState: input.threadState
54
+ }),
55
+ [
56
+ input.hasPendingRequest,
57
+ input.runtimeError,
58
+ input.threadState,
59
+ localDispatch
60
+ ]
61
+ );
62
+ return {
63
+ beginLocalDispatch,
64
+ isSendBusy: localDispatch !== null && !serverAcknowledgedLocalDispatch,
65
+ localDispatch,
66
+ localDispatchStartedAt: localDispatch?.startedAt ?? null,
67
+ resetLocalDispatch,
68
+ serverAcknowledgedLocalDispatch
69
+ };
70
+ }
71
+ function deriveActiveWorkStartedAt(input) {
72
+ if (!input.isWorking) {
73
+ return null;
74
+ }
75
+ return input.sendStartedAt ?? input.runtimeStartedAt;
76
+ }
77
+ function deriveAssistantStreaming(threadState) {
78
+ if (!threadState || threadState.activeTurnIds.length === 0) {
79
+ return false;
80
+ }
81
+ const activeTurnIds = new Set(threadState.activeTurnIds);
82
+ return threadState.turns.some(
83
+ (turn) => activeTurnIds.has(turn.id) && turn.items.some(
84
+ (item) => item.type === "agentMessage" && item.text.length > 0
85
+ )
86
+ );
87
+ }
88
+ function deriveChatLifecycleWorkingState(input) {
89
+ return Boolean(input.threadState?.activeTurnIds.length) || input.isSendBusy || input.connectionStatus === "connecting" || input.connectionStatus === "reconnecting";
90
+ }
91
+ function threadHasStarted(threadState) {
92
+ return Boolean(
93
+ threadState && (threadState.turns.some((turn) => turn.items.length > 0) || threadState.activeTurnIds.length > 0 || threadState.errors.length > 0 || threadState.pendingRequests.length > 0)
94
+ );
95
+ }
96
+ function threadTurnItemIds(threadState) {
97
+ return threadState?.turns.flatMap((turn) => turn.items.map((item) => item.id)) ?? [];
98
+ }
99
+ function createDefaultThreadStartParams({
100
+ threadId
101
+ }) {
102
+ return { threadId };
103
+ }
104
+ function createDefaultTurnStartParams({
105
+ clientMessageId,
106
+ imageUrls,
107
+ interactionMode = "default",
108
+ runtimeMode,
109
+ sendContext,
110
+ threadId
111
+ }) {
112
+ const collaborationMode = interactionMode === "plan" ? {
113
+ mode: "plan",
114
+ settings: {
115
+ model: sendContext.model,
116
+ reasoning_effort: sendContext.effort ?? null,
117
+ developer_instructions: null
118
+ }
119
+ } : void 0;
120
+ return {
121
+ clientMessageId,
122
+ ...collaborationMode ? { collaborationMode } : {},
123
+ ...runtimeModeToTurnPolicy(runtimeMode),
124
+ effort: sendContext.effort,
125
+ input: [
126
+ ...sendContext.items.map(composerUserInputToProtocolUserInput),
127
+ ...imageUrls.map((url) => ({ type: "image", url }))
128
+ ],
129
+ model: sendContext.model,
130
+ threadId
131
+ };
132
+ }
133
+ function runtimeModeToTurnPolicy(runtimeMode) {
134
+ if (runtimeMode === "approval-required") {
135
+ return {
136
+ approvalPolicy: "on-request",
137
+ sandboxPolicy: {
138
+ type: "workspaceWrite",
139
+ writableRoots: [],
140
+ networkAccess: false,
141
+ excludeTmpdirEnvVar: false,
142
+ excludeSlashTmp: false
143
+ }
144
+ };
145
+ }
146
+ if (runtimeMode === "auto-accept-edits") {
147
+ return {
148
+ approvalPolicy: "on-failure",
149
+ sandboxPolicy: {
150
+ type: "workspaceWrite",
151
+ writableRoots: [],
152
+ networkAccess: false,
153
+ excludeTmpdirEnvVar: false,
154
+ excludeSlashTmp: false
155
+ }
156
+ };
157
+ }
158
+ if (runtimeMode === "full-access") {
159
+ return {
160
+ approvalPolicy: "never",
161
+ sandboxPolicy: { type: "dangerFullAccess" }
162
+ };
163
+ }
164
+ return {};
165
+ }
166
+ function useCodexChatLifecycle(options) {
167
+ const threadId = useMemo(() => normalizeThreadId(options.threadId), [options.threadId]);
168
+ const [threadSnapshot, setThreadSnapshot] = useState(
169
+ options.initialState?.thread?.id === threadId ? options.initialState : null
170
+ );
171
+ const [activeThread, setActiveThread] = useState(null);
172
+ const [runtimeError, setRuntimeError] = useState(null);
173
+ const [isSending, setIsSending] = useState(false);
174
+ const [reconnectToken, setReconnectToken] = useState(0);
175
+ const [optimisticUserMessages, setOptimisticUserMessages] = useState([]);
176
+ const [activeRuntimeStartedAt, setActiveRuntimeStartedAt] = useState(
177
+ null
178
+ );
179
+ const activeRuntimeStartedAtRef = useRef(null);
180
+ const localDispatchStartedAtRef = useRef(null);
181
+ const pendingComposerSendRef = useRef(null);
182
+ const sendInFlightRef = useRef(false);
183
+ const threadStoreRef = useRef(
184
+ options.initialState?.thread?.id === threadId ? ThreadEventStore.fromThread(options.initialState.thread) : null
185
+ );
186
+ const threadSnapshotRef = useRef(threadSnapshot);
187
+ const activeEventsIteratorRef = useRef(null);
188
+ const lifecycleGenerationRef = useRef(0);
189
+ const lifecycleKeyRef = useRef(null);
190
+ const subscriptionAbortRef = useRef(null);
191
+ const appServerSession = useMemo(
192
+ () => new AppServerSession(options.appServer),
193
+ [options.appServer]
194
+ );
195
+ const lifecycleKey = `${threadId}:${options.connectOnMount === false ? "draft" : "server"}`;
196
+ if (lifecycleKeyRef.current !== lifecycleKey) {
197
+ lifecycleKeyRef.current = lifecycleKey;
198
+ lifecycleGenerationRef.current += 1;
199
+ }
200
+ const visibleRuntimeError = runtimeError?.threadId === threadId ? runtimeError.message : null;
201
+ const closeActiveSubscription = useCallback(() => {
202
+ subscriptionAbortRef.current?.abort();
203
+ subscriptionAbortRef.current = null;
204
+ const iterator = activeEventsIteratorRef.current;
205
+ activeEventsIteratorRef.current = null;
206
+ void iterator?.return?.();
207
+ }, []);
208
+ const visibleOptimisticUserMessages = useMemo(
209
+ () => optimisticUserMessages.filter((message) => message.threadId === threadId).map((message) => message.item),
210
+ [optimisticUserMessages, threadId]
211
+ );
212
+ const setProtocolConnectionStatus = useCallback(
213
+ (status) => {
214
+ const store = threadStoreRef.current;
215
+ if (!store) {
216
+ return;
217
+ }
218
+ const snapshot = store.setConnectionStatus(status);
219
+ threadSnapshotRef.current = snapshot;
220
+ setThreadSnapshot(snapshot);
221
+ },
222
+ []
223
+ );
224
+ const hasPendingRequest = Boolean(threadSnapshot?.pendingRequests.length);
225
+ const {
226
+ beginLocalDispatch,
227
+ isSendBusy,
228
+ localDispatchStartedAt,
229
+ resetLocalDispatch,
230
+ serverAcknowledgedLocalDispatch
231
+ } = useLocalDispatchState({
232
+ hasPendingRequest,
233
+ runtimeError: visibleRuntimeError,
234
+ threadState: threadSnapshot
235
+ });
236
+ useEffect(() => {
237
+ threadSnapshotRef.current = threadSnapshot;
238
+ options.onState?.(threadSnapshot);
239
+ }, [options, threadSnapshot]);
240
+ useEffect(() => {
241
+ activeRuntimeStartedAtRef.current = activeRuntimeStartedAt;
242
+ }, [activeRuntimeStartedAt]);
243
+ useEffect(() => {
244
+ localDispatchStartedAtRef.current = localDispatchStartedAt;
245
+ }, [localDispatchStartedAt]);
246
+ const removeOptimisticUserMessage = useCallback((itemId) => {
247
+ setOptimisticUserMessages(
248
+ (current) => current.filter((message) => message.item.id !== itemId)
249
+ );
250
+ }, []);
251
+ const restorePendingComposerSend = useCallback(() => {
252
+ const pendingComposerSend = pendingComposerSendRef.current;
253
+ if (!pendingComposerSend) {
254
+ return;
255
+ }
256
+ removeOptimisticUserMessage(pendingComposerSend.optimisticItemId);
257
+ pendingComposerSend.restore?.();
258
+ }, [removeOptimisticUserMessage]);
259
+ const handleRuntimeError = useCallback(
260
+ (error) => {
261
+ options.onRuntimeError?.(error);
262
+ if (options.isRecoverableConnectionError?.(error) && !pendingComposerSendRef.current) {
263
+ resetLocalDispatch();
264
+ setActiveRuntimeStartedAt(null);
265
+ setIsSending(false);
266
+ setProtocolConnectionStatus("error");
267
+ return;
268
+ }
269
+ setRuntimeError({ message: error.message, threadId });
270
+ if (pendingComposerSendRef.current) {
271
+ if (!pendingComposerSendRef.current.serverUserMessageAcknowledged) {
272
+ restorePendingComposerSend();
273
+ }
274
+ pendingComposerSendRef.current = null;
275
+ sendInFlightRef.current = false;
276
+ setIsSending(false);
277
+ resetLocalDispatch();
278
+ setActiveRuntimeStartedAt(null);
279
+ }
280
+ setProtocolConnectionStatus("error");
281
+ },
282
+ [
283
+ options,
284
+ resetLocalDispatch,
285
+ restorePendingComposerSend,
286
+ setProtocolConnectionStatus,
287
+ threadId
288
+ ]
289
+ );
290
+ const handleSubmittedUserMessage = useCallback(
291
+ (state) => {
292
+ const pendingComposerSend = pendingComposerSendRef.current;
293
+ if (pendingComposerSend) {
294
+ pendingComposerSendRef.current = {
295
+ ...pendingComposerSend,
296
+ serverUserMessageAcknowledged: true
297
+ };
298
+ }
299
+ sendInFlightRef.current = false;
300
+ setIsSending(false);
301
+ options.onThreadListChanged?.();
302
+ options.onSubmittedUserMessage?.(state);
303
+ pendingComposerSendRef.current = null;
304
+ },
305
+ [options]
306
+ );
307
+ const handleThreadStarted = useCallback(
308
+ (state) => {
309
+ const runtimeStartedAt = activeRuntimeStartedAtRef.current ?? localDispatchStartedAtRef.current ?? (/* @__PURE__ */ new Date()).toISOString();
310
+ setActiveRuntimeStartedAt((current) => current ?? runtimeStartedAt);
311
+ if (pendingComposerSendRef.current) {
312
+ pendingComposerSendRef.current = null;
313
+ sendInFlightRef.current = false;
314
+ setIsSending(false);
315
+ options.onThreadListChanged?.();
316
+ }
317
+ options.onThreadStarted?.(state);
318
+ },
319
+ [options]
320
+ );
321
+ const applyServerNotification = useCallback(
322
+ (notification) => {
323
+ const store = threadStoreRef.current;
324
+ if (!store) {
325
+ return;
326
+ }
327
+ const next = store.applyNotification(notification);
328
+ threadSnapshotRef.current = next;
329
+ setThreadSnapshot(next);
330
+ if (notification.method === "item/completed" && notification.params.item.type === "userMessage") {
331
+ window.setTimeout(() => handleSubmittedUserMessage(next), 0);
332
+ }
333
+ if (threadEventSnapshotHasStarted(next)) {
334
+ window.setTimeout(() => handleThreadStarted(next), 0);
335
+ }
336
+ },
337
+ [handleSubmittedUserMessage, handleThreadStarted]
338
+ );
339
+ const applyServerRequest = useCallback(
340
+ (request) => {
341
+ const store = threadStoreRef.current;
342
+ if (!store) {
343
+ return;
344
+ }
345
+ const next = store.applyRequest(request);
346
+ threadSnapshotRef.current = next;
347
+ setThreadSnapshot(next);
348
+ },
349
+ []
350
+ );
351
+ const connectThread = useCallback(
352
+ async (input = {}) => {
353
+ const generation = lifecycleGenerationRef.current;
354
+ const isCurrentLifecycle = (abortController2) => !abortController2.signal.aborted && lifecycleGenerationRef.current === generation;
355
+ closeActiveSubscription();
356
+ const abortController = new AbortController();
357
+ subscriptionAbortRef.current = abortController;
358
+ try {
359
+ setRuntimeError(null);
360
+ let resumeThread = null;
361
+ if (options.buildThreadStartParams) {
362
+ const response = await appServerSession.threadStart(
363
+ options.buildThreadStartParams({ threadId })
364
+ );
365
+ resumeThread = response.thread;
366
+ } else {
367
+ const response = await appServerSession.threadResume({ threadId });
368
+ resumeThread = response.thread;
369
+ }
370
+ if (!isCurrentLifecycle(abortController)) {
371
+ return;
372
+ }
373
+ const currentSnapshot = threadSnapshotRef.current;
374
+ const handoffSnapshot = currentSnapshot?.thread?.id === threadId && threadEventSnapshotHasStarted(currentSnapshot) ? currentSnapshot : options.initialState?.thread?.id === threadId ? options.initialState : null;
375
+ const shouldDeferVisibleResumeSnapshot = Boolean(pendingComposerSendRef.current) && !handoffSnapshot && currentSnapshot === null;
376
+ const handoffWorkStartedAt = activeRuntimeStartedAtRef.current ?? localDispatchStartedAtRef.current;
377
+ if (handoffWorkStartedAt) {
378
+ setActiveRuntimeStartedAt((current) => current ?? handoffWorkStartedAt);
379
+ }
380
+ if (!handoffSnapshot) {
381
+ threadStoreRef.current = ThreadEventStore.fromThread(resumeThread);
382
+ }
383
+ const storedThread = options.threadReader ? await options.threadReader.readThread({
384
+ thread_id: threadId,
385
+ include_archived: false,
386
+ include_history: false
387
+ }) : storedThreadFromAppServerThread(resumeThread);
388
+ const tokenUsageReplay = options.threadReader ? await storedTokenUsageReplayNotification({
389
+ thread: resumeThread,
390
+ threadId,
391
+ threadReader: options.threadReader
392
+ }) : null;
393
+ if (!isCurrentLifecycle(abortController)) {
394
+ return;
395
+ }
396
+ setActiveThread(storedThread);
397
+ options.onActiveThread?.(storedThread);
398
+ const store = threadStoreRef.current ?? ThreadEventStore.fromThread(resumeThread);
399
+ threadStoreRef.current = store;
400
+ if (tokenUsageReplay) {
401
+ store.applyNotification(tokenUsageReplay);
402
+ }
403
+ const nextSnapshot = store.setConnectionStatus(
404
+ input.force ? "reconnecting" : "connecting"
405
+ );
406
+ if (!shouldDeferVisibleResumeSnapshot) {
407
+ threadSnapshotRef.current = nextSnapshot;
408
+ setThreadSnapshot(nextSnapshot);
409
+ if (threadEventSnapshotHasStarted(nextSnapshot)) {
410
+ window.setTimeout(() => handleThreadStarted(nextSnapshot), 0);
411
+ }
412
+ }
413
+ const events = appServerSession.events();
414
+ if (!events) {
415
+ setProtocolConnectionStatus("connected");
416
+ return;
417
+ }
418
+ if (shouldDeferVisibleResumeSnapshot) {
419
+ store.setConnectionStatus("connected");
420
+ } else {
421
+ setProtocolConnectionStatus("connected");
422
+ }
423
+ const iterator = events[Symbol.asyncIterator]();
424
+ activeEventsIteratorRef.current = iterator;
425
+ void (async () => {
426
+ try {
427
+ while (true) {
428
+ const result = await iterator.next();
429
+ if (!isCurrentLifecycle(abortController)) {
430
+ return;
431
+ }
432
+ if (result.done) {
433
+ break;
434
+ }
435
+ applyAppServerEvent(result.value, {
436
+ applyServerNotification,
437
+ applyServerRequest,
438
+ onServerRequest: options.onServerRequest
439
+ });
440
+ }
441
+ if (isCurrentLifecycle(abortController)) {
442
+ setProtocolConnectionStatus("closed");
443
+ }
444
+ } catch (error) {
445
+ if (isCurrentLifecycle(abortController)) {
446
+ handleRuntimeError(
447
+ error instanceof Error ? error : new Error("Codex chat connection failed.")
448
+ );
449
+ }
450
+ } finally {
451
+ if (activeEventsIteratorRef.current === iterator) {
452
+ activeEventsIteratorRef.current = null;
453
+ }
454
+ }
455
+ })();
456
+ } catch (error) {
457
+ if (isCurrentLifecycle(abortController)) {
458
+ handleRuntimeError(
459
+ error instanceof Error ? error : new Error("Codex chat could not connect.")
460
+ );
461
+ }
462
+ }
463
+ },
464
+ [
465
+ appServerSession,
466
+ applyServerNotification,
467
+ applyServerRequest,
468
+ closeActiveSubscription,
469
+ handleRuntimeError,
470
+ handleThreadStarted,
471
+ options,
472
+ setProtocolConnectionStatus,
473
+ threadId
474
+ ]
475
+ );
476
+ useEffect(() => {
477
+ if (options.connectOnMount === false) {
478
+ closeActiveSubscription();
479
+ setActiveThread(null);
480
+ options.onActiveThread?.(null);
481
+ setRuntimeError(null);
482
+ if (!pendingComposerSendRef.current) {
483
+ setActiveRuntimeStartedAt(null);
484
+ threadStoreRef.current = null;
485
+ threadSnapshotRef.current = null;
486
+ setThreadSnapshot(null);
487
+ setIsSending(false);
488
+ }
489
+ return;
490
+ }
491
+ void connectThread({ force: reconnectToken > 0 });
492
+ return () => {
493
+ closeActiveSubscription();
494
+ };
495
+ }, [closeActiveSubscription, connectThread, options, options.connectOnMount, reconnectToken]);
496
+ useEffect(() => {
497
+ if (!threadSnapshot?.turns.length || optimisticUserMessages.length === 0) {
498
+ return;
499
+ }
500
+ const serverUserMessages = threadSnapshot.turns.flatMap(
501
+ (turn) => turn.items.flatMap(
502
+ (item) => item.type === "userMessage" ? [
503
+ {
504
+ contentFingerprint: protocolUserInputFingerprint(item.content),
505
+ id: item.id
506
+ }
507
+ ] : []
508
+ )
509
+ );
510
+ const serverUserMessageIds = new Set(
511
+ serverUserMessages.map((message) => message.id)
512
+ );
513
+ const serverUserMessageFingerprints = new Set(
514
+ serverUserMessages.map((message) => message.contentFingerprint)
515
+ );
516
+ const acknowledgedOptimisticIds = optimisticUserMessages.flatMap((message) => {
517
+ if (message.threadId !== threadId) {
518
+ return [];
519
+ }
520
+ if (serverUserMessageIds.has(message.item.id)) {
521
+ return [message.item.id];
522
+ }
523
+ return serverUserMessageFingerprints.has(
524
+ coreUserInputFingerprint(message.item.content)
525
+ ) ? [message.item.id] : [];
526
+ });
527
+ if (acknowledgedOptimisticIds.length === 0) {
528
+ return;
529
+ }
530
+ const timerId = window.setTimeout(() => {
531
+ setOptimisticUserMessages(
532
+ (current) => current.filter(
533
+ (message) => !acknowledgedOptimisticIds.includes(message.item.id)
534
+ )
535
+ );
536
+ }, 0);
537
+ return () => window.clearTimeout(timerId);
538
+ }, [optimisticUserMessages, threadId, threadSnapshot?.turns]);
539
+ useEffect(() => {
540
+ const timerId = window.setTimeout(() => {
541
+ setOptimisticUserMessages((current) => {
542
+ const next = current.filter((message) => message.threadId === threadId);
543
+ return next.length === current.length ? current : next;
544
+ });
545
+ }, 0);
546
+ return () => window.clearTimeout(timerId);
547
+ }, [threadId]);
548
+ useEffect(() => {
549
+ if (!serverAcknowledgedLocalDispatch) {
550
+ return;
551
+ }
552
+ const timerId = window.setTimeout(() => {
553
+ resetLocalDispatch();
554
+ sendInFlightRef.current = false;
555
+ setIsSending(false);
556
+ }, 0);
557
+ return () => window.clearTimeout(timerId);
558
+ }, [resetLocalDispatch, serverAcknowledgedLocalDispatch]);
559
+ const connectionStatus = threadSnapshot?.connectionStatus ?? "idle";
560
+ const turnRunning = Boolean(threadSnapshot?.activeTurnIds.length);
561
+ const assistantStreaming = deriveAssistantStreaming(threadSnapshot);
562
+ const isWorking = deriveChatLifecycleWorkingState({
563
+ connectionStatus,
564
+ isSendBusy,
565
+ threadState: threadSnapshot
566
+ });
567
+ const activeWorkStartedAt = deriveActiveWorkStartedAt({
568
+ isWorking,
569
+ runtimeStartedAt: activeRuntimeStartedAt,
570
+ sendStartedAt: localDispatchStartedAt
571
+ });
572
+ useEffect(() => {
573
+ if (isWorking || activeRuntimeStartedAt === null) {
574
+ return;
575
+ }
576
+ const timerId = window.setTimeout(() => {
577
+ setActiveRuntimeStartedAt(null);
578
+ }, 0);
579
+ return () => window.clearTimeout(timerId);
580
+ }, [activeRuntimeStartedAt, isWorking]);
581
+ const resolveServerRequest = useCallback(
582
+ async (requestId, response) => {
583
+ try {
584
+ await appServerSession.resolveServerRequest(requestId, response);
585
+ return true;
586
+ } catch (error) {
587
+ setRuntimeError({
588
+ message: error instanceof Error ? error.message : "Codex server-request response failed.",
589
+ threadId
590
+ });
591
+ return false;
592
+ }
593
+ },
594
+ [appServerSession, threadId]
595
+ );
596
+ const rejectServerRequest = useCallback(
597
+ async (requestId, error) => {
598
+ try {
599
+ await appServerSession.rejectServerRequest(requestId, error);
600
+ return true;
601
+ } catch (rejectError) {
602
+ setRuntimeError({
603
+ message: rejectError instanceof Error ? rejectError.message : "Codex server-request rejection failed.",
604
+ threadId
605
+ });
606
+ return false;
607
+ }
608
+ },
609
+ [appServerSession, threadId]
610
+ );
611
+ const sendComposerMessage = useCallback(
612
+ async (sendContext, controls, sendOptions = {}) => {
613
+ if (sendInFlightRef.current || isSending || isSendBusy) {
614
+ return;
615
+ }
616
+ if (sendContext.items.length === 0 && sendContext.files.length === 0) {
617
+ return;
618
+ }
619
+ sendInFlightRef.current = true;
620
+ setIsSending(true);
621
+ beginLocalDispatch();
622
+ try {
623
+ const imageUrls = await Promise.all(sendContext.files.map(fileToDataUrl));
624
+ const clientMessageId = defaultId();
625
+ const turnStartParams = (options.buildTurnStartParams ?? createDefaultTurnStartParams)({
626
+ clientMessageId,
627
+ imageUrls,
628
+ interactionMode: sendOptions.interactionMode,
629
+ runtimeMode: sendOptions.runtimeMode,
630
+ sendContext,
631
+ threadId
632
+ });
633
+ const optimisticItemId = optimisticUserMessageIdForClientMessageId(
634
+ clientMessageId
635
+ );
636
+ const optimisticMessage = buildOptimisticUserMessageTurnItem({
637
+ id: optimisticItemId,
638
+ imageUrls,
639
+ items: sendContext.items
640
+ });
641
+ pendingComposerSendRef.current = {
642
+ optimisticItemId,
643
+ restore: () => controls.restoreComposer(sendContext),
644
+ serverUserMessageAcknowledged: false,
645
+ targetThreadId: threadId
646
+ };
647
+ await controls.prepareForOptimisticAppend();
648
+ setOptimisticUserMessages((current) => [
649
+ ...current.filter((message) => message.item.id !== optimisticItemId),
650
+ { item: optimisticMessage, threadId }
651
+ ]);
652
+ controls.clearComposer();
653
+ options.onThreadListChanged?.();
654
+ if (options.connectOnMount === false || !threadSnapshotRef.current) {
655
+ await connectThread({ force: true });
656
+ }
657
+ const activeTurnId = currentActiveTurnId(threadSnapshotRef.current);
658
+ if (activeTurnId) {
659
+ await appServerSession.turnSteer({
660
+ expectedTurnId: activeTurnId,
661
+ input: turnStartParams.input,
662
+ threadId
663
+ });
664
+ } else {
665
+ await appServerSession.turnStart(turnStartParams);
666
+ }
667
+ } catch (error) {
668
+ restorePendingComposerSend();
669
+ pendingComposerSendRef.current = null;
670
+ sendInFlightRef.current = false;
671
+ resetLocalDispatch();
672
+ setActiveRuntimeStartedAt(null);
673
+ setIsSending(false);
674
+ setRuntimeError({
675
+ message: error instanceof Error ? error.message : "Codex could not send the message.",
676
+ threadId
677
+ });
678
+ }
679
+ },
680
+ [
681
+ beginLocalDispatch,
682
+ appServerSession,
683
+ connectThread,
684
+ isSendBusy,
685
+ isSending,
686
+ options,
687
+ resetLocalDispatch,
688
+ restorePendingComposerSend,
689
+ threadId
690
+ ]
691
+ );
692
+ const interrupt = useCallback(async () => {
693
+ const turnId = currentActiveTurnId(threadSnapshotRef.current);
694
+ if (!turnId) {
695
+ setRuntimeError({
696
+ message: "Codex has no active response to stop.",
697
+ threadId
698
+ });
699
+ return false;
700
+ }
701
+ try {
702
+ await appServerSession.turnInterrupt({ threadId, turnId });
703
+ } catch (error) {
704
+ setRuntimeError({
705
+ message: error instanceof Error ? error.message : "Codex could not stop the active response.",
706
+ threadId
707
+ });
708
+ return false;
709
+ }
710
+ if (pendingComposerSendRef.current) {
711
+ removeOptimisticUserMessage(pendingComposerSendRef.current.optimisticItemId);
712
+ }
713
+ pendingComposerSendRef.current = null;
714
+ sendInFlightRef.current = false;
715
+ resetLocalDispatch();
716
+ setActiveRuntimeStartedAt(null);
717
+ setIsSending(false);
718
+ return true;
719
+ }, [appServerSession, removeOptimisticUserMessage, resetLocalDispatch, threadId]);
720
+ const compact = useCallback(async () => {
721
+ if (turnRunning || isSending || isSendBusy) {
722
+ setRuntimeError({
723
+ message: "Wait for the current response to finish before compacting.",
724
+ threadId
725
+ });
726
+ return false;
727
+ }
728
+ try {
729
+ await appServerSession.threadCompactStart({ threadId });
730
+ return true;
731
+ } catch (error) {
732
+ setRuntimeError({
733
+ message: error instanceof Error ? error.message : "Codex compact failed.",
734
+ threadId
735
+ });
736
+ return false;
737
+ }
738
+ }, [appServerSession, isSendBusy, isSending, threadId, turnRunning]);
739
+ const reconnect = useCallback(() => {
740
+ closeActiveSubscription();
741
+ if (pendingComposerSendRef.current) {
742
+ removeOptimisticUserMessage(pendingComposerSendRef.current.optimisticItemId);
743
+ }
744
+ pendingComposerSendRef.current = null;
745
+ sendInFlightRef.current = false;
746
+ setIsSending(false);
747
+ resetLocalDispatch();
748
+ setActiveRuntimeStartedAt(null);
749
+ setRuntimeError(null);
750
+ setProtocolConnectionStatus("reconnecting");
751
+ setReconnectToken((current) => current + 1);
752
+ }, [
753
+ closeActiveSubscription,
754
+ removeOptimisticUserMessage,
755
+ resetLocalDispatch,
756
+ setProtocolConnectionStatus
757
+ ]);
758
+ return {
759
+ activeThread,
760
+ activeWorkStartedAt,
761
+ assistantStreaming,
762
+ compact,
763
+ connectionStatus,
764
+ interrupt,
765
+ isSendBusy,
766
+ isSending,
767
+ isWorking,
768
+ pendingPermissionRequestActive: Boolean(
769
+ threadSnapshot?.pendingRequests.some(
770
+ (request) => request.method === "item/permissions/requestApproval"
771
+ )
772
+ ),
773
+ pendingUserInputActive: Boolean(
774
+ threadSnapshot?.pendingRequests.some(
775
+ (request) => request.method === "item/tool/requestUserInput"
776
+ )
777
+ ),
778
+ reconnect,
779
+ rejectServerRequest,
780
+ runtimeError: visibleRuntimeError,
781
+ sendComposerMessage,
782
+ resolveServerRequest,
783
+ threadSnapshot,
784
+ threadId,
785
+ turnRunning,
786
+ visibleOptimisticUserMessages
787
+ };
788
+ }
789
+ function normalizeThreadId(threadId) {
790
+ return typeof threadId === "string" ? asThreadId(threadId) : threadId;
791
+ }
792
+ function optimisticUserMessageIdForClientMessageId(clientMessageId) {
793
+ return `user-${clientMessageId}`;
794
+ }
795
+ function fileToDataUrl(file) {
796
+ const blob = new Blob([file], { type: file.type || "image/png" });
797
+ return new Promise((resolve, reject) => {
798
+ const reader = new FileReader();
799
+ reader.onload = () => resolve(String(reader.result));
800
+ reader.onerror = () => reject(reader.error ?? new Error("Could not read file."));
801
+ reader.readAsDataURL(blob);
802
+ });
803
+ }
804
+ function defaultId() {
805
+ return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
806
+ }
807
+ function currentActiveTurnId(snapshot) {
808
+ return snapshot?.activeTurnIds.at(-1) ?? null;
809
+ }
810
+ function protocolUserInputFingerprint(items) {
811
+ return JSON.stringify(items.map(protocolUserInputFingerprintPart));
812
+ }
813
+ function protocolUserInputFingerprintPart(item) {
814
+ switch (item.type) {
815
+ case "text":
816
+ return { text: item.text, type: "text" };
817
+ case "image":
818
+ return { type: "image", url: item.url };
819
+ case "localImage":
820
+ return { path: item.path, type: "localImage" };
821
+ case "skill":
822
+ return { name: item.name, path: item.path, type: "skill" };
823
+ case "mention":
824
+ return { name: item.name, path: item.path, type: "mention" };
825
+ }
826
+ }
827
+ function coreUserInputFingerprint(items) {
828
+ return JSON.stringify(items.map(coreUserInputFingerprintPart));
829
+ }
830
+ function coreUserInputFingerprintPart(item) {
831
+ switch (item.type) {
832
+ case "text":
833
+ return { text: item.text, type: "text" };
834
+ case "image":
835
+ return { type: "image", url: item.image_url };
836
+ case "local_image":
837
+ return { path: item.path, type: "localImage" };
838
+ case "skill":
839
+ return { name: item.name, path: item.path, type: "skill" };
840
+ case "mention":
841
+ return { name: item.name, path: item.path, type: "mention" };
842
+ }
843
+ }
844
+ function composerUserInputToProtocolUserInput(item) {
845
+ switch (item.type) {
846
+ case "text":
847
+ return {
848
+ text: item.text,
849
+ text_elements: (item.text_elements ?? []).map((element) => ({
850
+ byteRange: element.byte_range,
851
+ placeholder: element.placeholder ?? null
852
+ })),
853
+ type: "text"
854
+ };
855
+ case "image":
856
+ return { type: "image", url: item.image_url };
857
+ case "local_image":
858
+ return { path: item.path, type: "localImage" };
859
+ case "skill":
860
+ return { name: item.name, path: item.path, type: "skill" };
861
+ case "mention":
862
+ return { name: item.name, path: item.path, type: "mention" };
863
+ }
864
+ }
865
+ async function storedTokenUsageReplayNotification(input) {
866
+ try {
867
+ const history = await input.threadReader.loadHistory({
868
+ thread_id: input.threadId,
869
+ include_archived: false
870
+ });
871
+ return thread_token_usage_updated_notification_from_rollout_items({
872
+ rolloutItems: history.items,
873
+ thread: input.thread,
874
+ threadId: input.threadId
875
+ });
876
+ } catch {
877
+ return null;
878
+ }
879
+ }
880
+ function storedThreadFromAppServerThread(thread) {
881
+ return {
882
+ thread_id: asThreadId(thread.id),
883
+ rollout_path: thread.path ?? null,
884
+ forked_from_id: thread.forkedFromId ? asThreadId(thread.forkedFromId) : null,
885
+ preview: thread.preview,
886
+ name: thread.name,
887
+ model_provider: thread.modelProvider,
888
+ model: null,
889
+ reasoning_effort: null,
890
+ created_at: new Date(thread.createdAt * 1e3).toISOString(),
891
+ updated_at: new Date(thread.updatedAt * 1e3).toISOString(),
892
+ archived_at: null,
893
+ cwd: thread.cwd,
894
+ cli_version: thread.cliVersion,
895
+ source: typeof thread.source === "string" ? thread.source : "custom",
896
+ thread_source: typeof thread.threadSource === "string" ? thread.threadSource : null,
897
+ agent_nickname: thread.agentNickname,
898
+ agent_role: thread.agentRole,
899
+ git_info: thread.gitInfo,
900
+ history: null
901
+ };
902
+ }
903
+ function applyAppServerEvent(event, handlers) {
904
+ switch (event.type) {
905
+ case "server_notification":
906
+ handlers.applyServerNotification(event.notification);
907
+ return;
908
+ case "server_request":
909
+ handlers.applyServerRequest(event.request);
910
+ handlers.onServerRequest?.(event.request);
911
+ return;
912
+ case "disconnected":
913
+ throw new Error(event.message);
914
+ case "lagged":
915
+ return;
916
+ }
917
+ }
918
+
919
+ // src/components/codex-chat-render-state.ts
920
+ function createCodexChatRenderState({
921
+ interactionMode = "default",
922
+ lifecycle,
923
+ snapshot
924
+ }) {
925
+ const activeTurnIds = new Set(snapshot?.activeTurnIds ?? []);
926
+ const turns = (snapshot?.turns ?? []).map(
927
+ (turn) => t3TurnFromAppServerTurn(turn, activeTurnIds.has(turn.id))
928
+ );
929
+ const pendingRequests = (snapshot?.pendingRequests ?? []).map(codexChatPendingRequest);
930
+ const pendingDynamicToolCallRequests = pendingRequests.flatMap(
931
+ (request) => request.kind === "dynamicToolCall" ? [request.compatRequest] : []
932
+ );
933
+ const pendingPermissionRequest = pendingRequests.find((request) => request.kind === "permissions") ?? null;
934
+ const pendingUserInputRequest = pendingRequests.find((request) => request.kind === "userInput") ?? null;
935
+ const activeTurnStartedAt = lifecycle?.activeWorkStartedAt ?? null;
936
+ const errors = snapshot?.errors ?? [];
937
+ const isWorking = lifecycle?.isWorking ?? false;
938
+ const optimisticUserMessages = lifecycle?.visibleOptimisticUserMessages ?? [];
939
+ const runtimeError = lifecycle?.runtimeError ?? null;
940
+ const warnings = snapshot?.warnings ?? [];
941
+ const activeProposedPlan = findActiveProposedPlan(snapshot);
942
+ const contextWindow = deriveContextWindowSnapshotFromTokenUsage({
943
+ tokenUsage: snapshot?.tokenUsage?.tokenUsage,
944
+ updatedAt: snapshot?.tokenUsage?.updatedAt
945
+ });
946
+ const banners = defaultRenderBanners({
947
+ pendingRequests,
948
+ runtimeError
949
+ });
950
+ return {
951
+ activeProposedPlan,
952
+ activeTurnStartedAt,
953
+ banners,
954
+ composer: {
955
+ contextWindow,
956
+ pendingUserInput: pendingUserInputRequest?.pendingUserInput ?? null,
957
+ pendingUserInputAdapter: pendingUserInputRequest
958
+ },
959
+ errors,
960
+ interactionMode,
961
+ items: turns.flatMap((turn) => turn.items),
962
+ isWorking,
963
+ optimisticUserMessages,
964
+ pending_dynamic_tool_call_requests: pendingDynamicToolCallRequests,
965
+ pendingRequests,
966
+ pendingPermissionRequest,
967
+ pendingUserInputRequest,
968
+ runtimeError,
969
+ running_turn_ids: snapshot?.activeTurnIds ?? [],
970
+ showPlanFollowUpPrompt: interactionMode === "plan" && Boolean(activeProposedPlan) && !isWorking && !pendingUserInputRequest,
971
+ timeline: {
972
+ activeTurnStartedAt,
973
+ errors,
974
+ isWorking,
975
+ optimisticUserMessages,
976
+ runtimeError,
977
+ turns,
978
+ warnings
979
+ },
980
+ turns,
981
+ warnings
982
+ };
983
+ }
984
+ function findActiveProposedPlan(snapshot) {
985
+ if (!snapshot) {
986
+ return null;
987
+ }
988
+ for (const turn of [...snapshot.turns].reverse()) {
989
+ for (const item of [...turn.items].reverse()) {
990
+ if (item.type !== "plan" || item.text.trim().length === 0) {
991
+ continue;
992
+ }
993
+ return {
994
+ id: `${turn.id}:${item.id}`,
995
+ planMarkdown: item.text,
996
+ title: proposedPlanTitle(item.text),
997
+ turnId: turn.id
998
+ };
999
+ }
1000
+ }
1001
+ return null;
1002
+ }
1003
+ function codexChatPendingRequest(request) {
1004
+ switch (request.method) {
1005
+ case "item/tool/requestUserInput":
1006
+ return {
1007
+ kind: "userInput",
1008
+ itemId: request.params.itemId,
1009
+ pendingUserInput: pendingUserInputFromServerRequest(request),
1010
+ request,
1011
+ requestId: request.id,
1012
+ threadId: request.params.threadId,
1013
+ turnId: request.params.turnId
1014
+ };
1015
+ case "item/permissions/requestApproval":
1016
+ return {
1017
+ kind: "permissions",
1018
+ composerRequest: requestPermissionsFromServerRequest(request),
1019
+ request,
1020
+ requestId: request.id,
1021
+ threadId: request.params.threadId,
1022
+ turnId: request.params.turnId
1023
+ };
1024
+ case "item/tool/call":
1025
+ return {
1026
+ kind: "dynamicToolCall",
1027
+ compatRequest: {
1028
+ arguments: request.params.arguments,
1029
+ call_id: request.params.callId,
1030
+ namespace: request.params.namespace,
1031
+ tool: request.params.tool,
1032
+ turn_id: request.params.turnId
1033
+ },
1034
+ request,
1035
+ requestId: request.id,
1036
+ threadId: request.params.threadId,
1037
+ turnId: request.params.turnId
1038
+ };
1039
+ case "mcpServer/elicitation/request":
1040
+ return {
1041
+ kind: "mcpElicitation",
1042
+ request,
1043
+ requestId: request.id,
1044
+ threadId: request.params.threadId,
1045
+ turnId: request.params.turnId
1046
+ };
1047
+ case "item/commandExecution/requestApproval":
1048
+ return {
1049
+ kind: "commandApproval",
1050
+ request,
1051
+ requestId: request.id,
1052
+ threadId: request.params.threadId,
1053
+ turnId: request.params.turnId
1054
+ };
1055
+ case "item/fileChange/requestApproval":
1056
+ return {
1057
+ kind: "fileChangeApproval",
1058
+ request,
1059
+ requestId: request.id,
1060
+ threadId: request.params.threadId,
1061
+ turnId: request.params.turnId
1062
+ };
1063
+ case "account/chatgptAuthTokens/refresh":
1064
+ return {
1065
+ kind: "chatgptAuthTokensRefresh",
1066
+ request,
1067
+ requestId: request.id,
1068
+ threadId: null,
1069
+ turnId: null
1070
+ };
1071
+ case "applyPatchApproval":
1072
+ return {
1073
+ kind: "applyPatchApproval",
1074
+ request,
1075
+ requestId: request.id,
1076
+ threadId: request.params.conversationId,
1077
+ turnId: null
1078
+ };
1079
+ case "execCommandApproval":
1080
+ return {
1081
+ kind: "execCommandApproval",
1082
+ request,
1083
+ requestId: request.id,
1084
+ threadId: request.params.conversationId,
1085
+ turnId: null
1086
+ };
1087
+ }
1088
+ }
1089
+ function defaultRenderBanners({
1090
+ pendingRequests,
1091
+ runtimeError
1092
+ }) {
1093
+ const banners = [];
1094
+ if (runtimeError) {
1095
+ banners.push({
1096
+ id: "runtime-error",
1097
+ title: "Codex connection interrupted",
1098
+ description: runtimeError,
1099
+ variant: "error",
1100
+ tone: "destructive"
1101
+ });
1102
+ }
1103
+ for (const request of pendingRequests) {
1104
+ if (request.kind === "userInput" || request.kind === "dynamicToolCall") {
1105
+ continue;
1106
+ }
1107
+ if (request.kind === "permissions") {
1108
+ banners.push({
1109
+ id: `request-permissions:${request.requestId}`,
1110
+ request,
1111
+ title: "Permissions requested",
1112
+ description: "Codex requested extra permissions. Approval controls are not available yet, so the request was denied for this turn.",
1113
+ variant: "warning"
1114
+ });
1115
+ continue;
1116
+ }
1117
+ banners.push({
1118
+ id: `server-request:${request.requestId}`,
1119
+ request,
1120
+ title: "Codex needs input",
1121
+ description: `No default renderer is available for ${request.request.method}.`,
1122
+ variant: "warning"
1123
+ });
1124
+ }
1125
+ return banners;
1126
+ }
1127
+ function t3TurnFromAppServerTurn(turn, isActive) {
1128
+ return {
1129
+ completed_at: epochMillis(turn.completedAt),
1130
+ duration_ms: turn.durationMs,
1131
+ error: turn.error ? {
1132
+ additional_details: turn.error.additionalDetails,
1133
+ codex_error_info: turn.error.codexErrorInfo,
1134
+ message: turn.error.message
1135
+ } : null,
1136
+ id: turn.id,
1137
+ items: turn.items.flatMap((item) => t3TurnItemFromThreadItem(item, isActive)),
1138
+ items_view: turn.itemsView === "notLoaded" ? "not_loaded" : turn.itemsView,
1139
+ started_at: epochMillis(turn.startedAt),
1140
+ status: t3TurnStatusFromAppServer(turn.status)
1141
+ };
1142
+ }
1143
+ function t3TurnItemFromThreadItem(item, turnIsActive) {
1144
+ switch (item.type) {
1145
+ case "userMessage":
1146
+ return [
1147
+ {
1148
+ content: item.content.map(coreUserInputFromAppServer),
1149
+ id: item.id,
1150
+ type: "UserMessage"
1151
+ }
1152
+ ];
1153
+ case "agentMessage":
1154
+ return [
1155
+ {
1156
+ content: [{ text: item.text, type: "Text" }],
1157
+ id: item.id,
1158
+ memory_citation: item.memoryCitation ? {
1159
+ entries: item.memoryCitation.entries,
1160
+ rolloutIds: item.memoryCitation.threadIds
1161
+ } : null,
1162
+ phase: turnIsActive && item.text.trim().length > 0 ? "streaming" : item.phase,
1163
+ type: "AgentMessage"
1164
+ }
1165
+ ];
1166
+ case "plan":
1167
+ return [{ id: item.id, text: item.text, type: "Plan" }];
1168
+ case "reasoning":
1169
+ return [
1170
+ {
1171
+ id: item.id,
1172
+ raw_content: item.content,
1173
+ summary_text: item.summary,
1174
+ type: "Reasoning"
1175
+ }
1176
+ ];
1177
+ case "commandExecution":
1178
+ return [
1179
+ {
1180
+ command: [item.command],
1181
+ cwd: item.cwd,
1182
+ duration_ms: item.durationMs,
1183
+ exit_code: item.exitCode,
1184
+ id: item.id,
1185
+ status: item.status === "inProgress" ? "in_progress" : item.status === "declined" ? "cancelled" : item.status,
1186
+ stdout: item.aggregatedOutput ?? "",
1187
+ type: "CommandExecution"
1188
+ }
1189
+ ];
1190
+ case "fileChange":
1191
+ return [
1192
+ {
1193
+ auto_approved: false,
1194
+ changes: {},
1195
+ id: item.id,
1196
+ status: item.status === "completed" || item.status === "failed" || item.status === "declined" ? item.status : null,
1197
+ stderr: "",
1198
+ stdout: "",
1199
+ type: "FileChange"
1200
+ }
1201
+ ];
1202
+ case "dynamicToolCall":
1203
+ return [
1204
+ {
1205
+ arguments: item.arguments,
1206
+ content_items: item.contentItems,
1207
+ duration: item.durationMs === null ? null : String(item.durationMs),
1208
+ id: item.id,
1209
+ namespace: item.namespace,
1210
+ status: item.status,
1211
+ success: item.success,
1212
+ tool: item.tool,
1213
+ type: "DynamicToolCall"
1214
+ }
1215
+ ];
1216
+ case "contextCompaction":
1217
+ return [{ id: item.id, type: "ContextCompaction" }];
1218
+ default:
1219
+ return [];
1220
+ }
1221
+ }
1222
+ function pendingUserInputFromServerRequest(request) {
1223
+ return {
1224
+ itemId: request.params.itemId,
1225
+ questions: request.params.questions.map((question) => {
1226
+ const normalized = normalizePendingUserInputQuestion(question);
1227
+ return {
1228
+ ...normalized,
1229
+ options: normalized.options.map((option) => ({ ...option }))
1230
+ };
1231
+ }),
1232
+ requestId: request.id,
1233
+ threadId: request.params.threadId,
1234
+ turnId: request.params.turnId
1235
+ };
1236
+ }
1237
+ function requestPermissionsFromServerRequest(request) {
1238
+ return {
1239
+ call_id: String(request.id),
1240
+ cwd: request.params.cwd,
1241
+ permissions: request.params.permissions,
1242
+ reason: request.params.reason,
1243
+ turn_id: request.params.turnId
1244
+ };
1245
+ }
1246
+ function coreUserInputFromAppServer(input) {
1247
+ if (input.type === "image") {
1248
+ return { image_url: input.url, type: "image" };
1249
+ }
1250
+ if (input.type === "localImage") {
1251
+ return { path: input.path, type: "local_image" };
1252
+ }
1253
+ if (input.type === "text") {
1254
+ return {
1255
+ text: input.text,
1256
+ text_elements: input.text_elements.map((element) => ({
1257
+ byte_range: element.byteRange,
1258
+ placeholder: element.placeholder ?? void 0
1259
+ })),
1260
+ type: "text"
1261
+ };
1262
+ }
1263
+ return {
1264
+ text: input.name,
1265
+ text_elements: [],
1266
+ type: "text"
1267
+ };
1268
+ }
1269
+ function t3TurnStatusFromAppServer(status) {
1270
+ return status === "inProgress" ? "in_progress" : status;
1271
+ }
1272
+ function epochMillis(value) {
1273
+ return typeof value === "number" ? value * 1e3 : null;
1274
+ }
1275
+ function CodexChatView(props) {
1276
+ if (isLifecycleCodexChatViewProps(props)) {
1277
+ return createElement(CodexLifecycleChatView, props);
1278
+ }
1279
+ const { composer, ...chatViewProps } = props;
1280
+ return createElement(
1281
+ TooltipProvider,
1282
+ null,
1283
+ createElement(ChatView, {
1284
+ ...chatViewProps,
1285
+ composer: createElement(ChatComposer, composer)
1286
+ })
1287
+ );
1288
+ }
1289
+ function CodexLifecycleChatView({
1290
+ actions,
1291
+ bannerItems = [],
1292
+ className,
1293
+ composerCommands,
1294
+ composerSkills,
1295
+ composerDraftKey,
1296
+ composerRef: externalComposerRef,
1297
+ defaultInteractionMode = "default",
1298
+ defaultRuntimeMode = "full-access",
1299
+ editorAriaLabel,
1300
+ headerLeading,
1301
+ interactionMode,
1302
+ isEnvironmentUnavailable = false,
1303
+ listRef: externalListRef,
1304
+ lifecycle: lifecycleOptions,
1305
+ mentionRefs,
1306
+ modelOptions,
1307
+ placeholder,
1308
+ providerStatus = null,
1309
+ realtimeConversation,
1310
+ renderBannerItems,
1311
+ renderPendingRequest,
1312
+ renderPendingUserInput,
1313
+ renderTimelineExtras,
1314
+ runtimeMode,
1315
+ showInteractionModeToggle = true,
1316
+ showPlanToggle = false,
1317
+ planSidebarLabel = "Plan",
1318
+ planSidebarOpen = false,
1319
+ subtitle,
1320
+ threadKey,
1321
+ title,
1322
+ onCommand,
1323
+ onComposerError,
1324
+ onControlsChange,
1325
+ onInteractionModeChange,
1326
+ onRuntimeModeChange,
1327
+ onTogglePlanSidebar,
1328
+ onImplementProposedPlan,
1329
+ onIsAtEndChange
1330
+ }) {
1331
+ const lifecycle = useCodexChatLifecycle(lifecycleOptions);
1332
+ const [localInteractionMode, setLocalInteractionMode] = useState(defaultInteractionMode);
1333
+ const [localRuntimeMode, setLocalRuntimeMode] = useState(defaultRuntimeMode);
1334
+ const effectiveInteractionMode = interactionMode ?? localInteractionMode;
1335
+ const effectiveRuntimeMode = runtimeMode ?? localRuntimeMode;
1336
+ const setInteractionMode = useCallback(
1337
+ (nextMode) => {
1338
+ if (interactionMode === void 0) {
1339
+ setLocalInteractionMode(nextMode);
1340
+ }
1341
+ onInteractionModeChange?.(nextMode);
1342
+ },
1343
+ [interactionMode, onInteractionModeChange]
1344
+ );
1345
+ const setRuntimeMode = useCallback(
1346
+ (nextMode) => {
1347
+ if (runtimeMode === void 0) {
1348
+ setLocalRuntimeMode(nextMode);
1349
+ }
1350
+ onRuntimeModeChange?.(nextMode);
1351
+ },
1352
+ [runtimeMode, onRuntimeModeChange]
1353
+ );
1354
+ const effectiveThreadKey = threadKey ?? String(lifecycle.threadId);
1355
+ const effectiveComposerDraftKey = composerDraftKey ?? `codex:${String(lifecycle.threadId)}`;
1356
+ const effectiveEditorAriaLabel = editorAriaLabel ?? "Message Codex";
1357
+ const effectivePlaceholder = placeholder ?? "Ask anything, @tag files/folders, or use / to show available commands";
1358
+ const effectiveTitle = title ?? "Codex";
1359
+ const renderState = createCodexChatRenderState({
1360
+ interactionMode: effectiveInteractionMode,
1361
+ lifecycle,
1362
+ snapshot: lifecycle.threadSnapshot
1363
+ });
1364
+ const activeProposedPlan = renderState.activeProposedPlan;
1365
+ const showPlanFollowUpPrompt = renderState.showPlanFollowUpPrompt && Boolean(activeProposedPlan);
1366
+ const pendingUserInput = renderState.composer.pendingUserInputAdapter;
1367
+ const pendingRequestRenderItems = renderPendingRequest ? renderState.pendingRequests.flatMap((request) => {
1368
+ if (request.kind === "userInput") {
1369
+ return [];
1370
+ }
1371
+ const defaultNode = defaultPendingRequestNode(request);
1372
+ const node = renderPendingRequest({
1373
+ defaultNode,
1374
+ reject: (error) => rejectPendingRequest(request, error),
1375
+ request,
1376
+ resolve: (result) => lifecycle.resolveServerRequest(request.requestId, result),
1377
+ state: renderState
1378
+ });
1379
+ if (node === defaultNode) {
1380
+ return [];
1381
+ }
1382
+ return node ? [{ node, request }] : [];
1383
+ }) : [];
1384
+ const customPendingRequestIds = new Set(
1385
+ pendingRequestRenderItems.map(({ request }) => request.requestId)
1386
+ );
1387
+ const customUserInputNode = pendingUserInput ? renderPendingUserInput?.({
1388
+ defaultNode: null,
1389
+ reject: (error) => rejectPendingRequest(pendingUserInput, error),
1390
+ request: pendingUserInput,
1391
+ resolve: (result) => lifecycle.resolveServerRequest(pendingUserInput.requestId, result),
1392
+ state: renderState
1393
+ }) : null;
1394
+ const timelineExtras = renderTimelineExtras?.({ state: renderState });
1395
+ const internalComposerRef = useRef(null);
1396
+ const composerRef = externalComposerRef ?? internalComposerRef;
1397
+ const internalListRef = useRef(null);
1398
+ const listRef = externalListRef ?? internalListRef;
1399
+ const controlsRef = useRef(null);
1400
+ const shouldAutoScrollRef = useRef(true);
1401
+ const [composerNotice, setComposerNotice] = useState(null);
1402
+ const setComposerDraftPrompt = useComposerDraftStore((store) => store.setPrompt);
1403
+ const addComposerDraftImages = useComposerDraftStore((store) => store.addImages);
1404
+ const setNotice = useCallback(
1405
+ (message) => {
1406
+ setComposerNotice(message);
1407
+ onComposerError?.(message);
1408
+ },
1409
+ [onComposerError]
1410
+ );
1411
+ const scheduleStickToBottom = useCallback(() => {
1412
+ controlsRef.current?.scheduleStickToBottom();
1413
+ }, []);
1414
+ function restoreComposer(sendContext) {
1415
+ const snapshot = composerRef.current?.readSnapshot();
1416
+ const composerIsEmpty = !snapshot || snapshot.value.trim().length === 0;
1417
+ if (!composerIsEmpty) {
1418
+ return;
1419
+ }
1420
+ const restoredImages = createComposerImageAttachments(sendContext.files);
1421
+ setComposerDraftPrompt(effectiveComposerDraftKey, sendContext.text);
1422
+ addComposerDraftImages(effectiveComposerDraftKey, restoredImages);
1423
+ composerRef.current?.setDraft({
1424
+ mentionBindings: sendContext.mentionBindings,
1425
+ message: sendContext.text
1426
+ });
1427
+ }
1428
+ async function sendComposerMessage() {
1429
+ const sendContext = composerRef.current?.getSendContext();
1430
+ if (!sendContext) {
1431
+ setNotice("Composer is not ready. Try again.");
1432
+ return;
1433
+ }
1434
+ const activePlan = activeProposedPlan;
1435
+ const shouldSubmitPlanFollowUp = Boolean(activePlan) && showPlanFollowUpPrompt && sendContext.items.length === 0 && sendContext.files.length === 0;
1436
+ const effectiveSendContext = shouldSubmitPlanFollowUp && activePlan ? planImplementationSendContext(sendContext, activePlan) : sendContext;
1437
+ const submitInteractionMode = shouldSubmitPlanFollowUp && activePlan ? resolvePlanFollowUpSubmission({
1438
+ draftText: sendContext.text,
1439
+ planMarkdown: activePlan.planMarkdown
1440
+ }).interactionMode : effectiveInteractionMode;
1441
+ if (effectiveSendContext.items.length === 0 && effectiveSendContext.files.length === 0) {
1442
+ return;
1443
+ }
1444
+ await lifecycle.sendComposerMessage(effectiveSendContext, {
1445
+ clearComposer: () => {
1446
+ composerRef.current?.clear();
1447
+ setNotice(null);
1448
+ },
1449
+ prepareForOptimisticAppend: async () => {
1450
+ shouldAutoScrollRef.current = true;
1451
+ await (controlsRef.current?.prepareForOptimisticAppend?.() ?? listRef.current?.scrollToEnd?.({ animated: false }));
1452
+ },
1453
+ restoreComposer
1454
+ }, { interactionMode: submitInteractionMode, runtimeMode: effectiveRuntimeMode });
1455
+ if (submitInteractionMode !== effectiveInteractionMode) {
1456
+ setInteractionMode(submitInteractionMode);
1457
+ }
1458
+ }
1459
+ const submitPendingUserInput = (response) => {
1460
+ const request = pendingUserInput;
1461
+ if (!request) {
1462
+ setNotice("Codex thread is not connected. Reconnect and try again.");
1463
+ return false;
1464
+ }
1465
+ void lifecycle.resolveServerRequest(request.requestId, response);
1466
+ return true;
1467
+ };
1468
+ const dismissPendingUserInput = () => {
1469
+ const request = pendingUserInput;
1470
+ if (!request) {
1471
+ return;
1472
+ }
1473
+ submitPendingUserInput(buildRequestUserInputResponse(request.pendingUserInput, {}));
1474
+ };
1475
+ function rejectPendingRequest(request, error = "Codex server request was dismissed.") {
1476
+ const jsonRpcError = typeof error === "string" ? { code: -32e3, message: error } : error;
1477
+ return lifecycle.rejectServerRequest(request.requestId, jsonRpcError);
1478
+ }
1479
+ const interrupt = useCallback(() => {
1480
+ void lifecycle.interrupt().then((accepted) => {
1481
+ if (!accepted) {
1482
+ setNotice(
1483
+ "Codex could not stop the active response. Reconnect and try again."
1484
+ );
1485
+ }
1486
+ });
1487
+ }, [lifecycle, setNotice]);
1488
+ const handleCommand = useCallback(
1489
+ (command) => {
1490
+ if (command === "plan") {
1491
+ setInteractionMode("plan");
1492
+ return;
1493
+ }
1494
+ if (command === "default") {
1495
+ setInteractionMode("default");
1496
+ return;
1497
+ }
1498
+ if (command === "compact") {
1499
+ void lifecycle.compact().then((accepted) => {
1500
+ if (!accepted) {
1501
+ setNotice("Codex thread is not connected. Reconnect and try again.");
1502
+ }
1503
+ });
1504
+ return;
1505
+ }
1506
+ onCommand?.(command);
1507
+ },
1508
+ [lifecycle, onCommand, setInteractionMode, setNotice]
1509
+ );
1510
+ const implementProposedPlanInNewThread = useCallback(
1511
+ (plan) => {
1512
+ onImplementProposedPlan?.(plan);
1513
+ },
1514
+ [onImplementProposedPlan]
1515
+ );
1516
+ const lifecycleBannerItems = [...bannerItems];
1517
+ if (composerNotice) {
1518
+ lifecycleBannerItems.push({
1519
+ id: "composer-notice",
1520
+ title: "Composer needs attention",
1521
+ description: composerNotice,
1522
+ variant: "warning",
1523
+ onDismiss: () => setNotice(null)
1524
+ });
1525
+ }
1526
+ if (providerStatus && providerStatus.status !== "ready" && providerStatus.status !== "disabled") {
1527
+ const providerLabel = providerStatus.displayName?.trim() || (providerStatus.driver ? providerStatus.driver.split(/[-_\s]+/g).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ") : "Codex");
1528
+ const description = providerStatus.message ?? (providerStatus.status === "error" ? `${providerLabel} provider is unavailable.` : `${providerLabel} provider has limited availability.`);
1529
+ lifecycleBannerItems.push({
1530
+ description,
1531
+ id: "provider-status",
1532
+ title: `${providerLabel} provider status`,
1533
+ variant: providerStatus.status === "error" ? "error" : "warning"
1534
+ });
1535
+ }
1536
+ lifecycleBannerItems.push(
1537
+ ...renderState.banners.filter(
1538
+ (banner) => !banner.request || !customPendingRequestIds.has(banner.request.requestId)
1539
+ ).map((banner) => ({
1540
+ ...banner,
1541
+ action: banner.id === "runtime-error" ? { label: "Reconnect", onClick: lifecycle.reconnect } : void 0
1542
+ }))
1543
+ );
1544
+ lifecycleBannerItems.push(
1545
+ ...pendingRequestRenderItems.map(({ node, request }) => ({
1546
+ id: `custom-pending-request:${request.requestId}`,
1547
+ title: "Codex needs input",
1548
+ description: node,
1549
+ variant: "info"
1550
+ }))
1551
+ );
1552
+ if (customUserInputNode) {
1553
+ lifecycleBannerItems.push({
1554
+ id: `custom-pending-request:${pendingUserInput?.requestId ?? "user-input"}`,
1555
+ title: "Codex needs input",
1556
+ description: customUserInputNode,
1557
+ variant: "info"
1558
+ });
1559
+ }
1560
+ lifecycleBannerItems.push(...renderBannerItems?.(renderState) ?? []);
1561
+ const effectiveActions = showInteractionModeToggle ? /* @__PURE__ */ jsxs(Fragment, { children: [
1562
+ /* @__PURE__ */ jsx(
1563
+ CodexInteractionModeToggle,
1564
+ {
1565
+ mode: effectiveInteractionMode,
1566
+ onChange: setInteractionMode
1567
+ }
1568
+ ),
1569
+ actions
1570
+ ] }) : actions;
1571
+ const chatView = /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsx(
1572
+ ChatView,
1573
+ {
1574
+ actions: effectiveActions,
1575
+ bannerItems: lifecycleBannerItems,
1576
+ className,
1577
+ headerLeading,
1578
+ listRef,
1579
+ subtitle,
1580
+ threadKey: effectiveThreadKey,
1581
+ timeline: renderState.timeline,
1582
+ title: effectiveTitle,
1583
+ onControlsChange: (controls) => {
1584
+ controlsRef.current = controls;
1585
+ onControlsChange?.(controls);
1586
+ },
1587
+ onIsAtEndChange: (isAtEnd) => {
1588
+ shouldAutoScrollRef.current = isAtEnd;
1589
+ onIsAtEndChange?.(isAtEnd);
1590
+ },
1591
+ composer: /* @__PURE__ */ jsx(
1592
+ ChatComposer,
1593
+ {
1594
+ ref: composerRef,
1595
+ contextWindow: renderState.composer.contextWindow,
1596
+ disabled: Boolean(lifecycle.runtimeError) || lifecycle.connectionStatus === "connecting" || lifecycle.connectionStatus === "reconnecting",
1597
+ draftKey: effectiveComposerDraftKey,
1598
+ editorAriaLabel: effectiveEditorAriaLabel,
1599
+ interactionMode: effectiveInteractionMode,
1600
+ isConnecting: lifecycle.connectionStatus === "connecting" || lifecycle.connectionStatus === "reconnecting",
1601
+ isEnvironmentUnavailable,
1602
+ isRunning: lifecycle.turnRunning,
1603
+ isSending: lifecycle.isSending || lifecycle.isSendBusy,
1604
+ mentionRefs: mentionRefs ?? [],
1605
+ composerCommands,
1606
+ composerSkills,
1607
+ modelOptions,
1608
+ pendingUserInput: customUserInputNode ? null : renderState.composer.pendingUserInput,
1609
+ pendingRequestDisabled: lifecycle.connectionStatus !== "connected",
1610
+ placeholder: effectivePlaceholder,
1611
+ planFollowUpTitle: activeProposedPlan?.title ?? null,
1612
+ planSidebarLabel,
1613
+ planSidebarOpen,
1614
+ realtimeConversation,
1615
+ runtimeMode: effectiveRuntimeMode,
1616
+ showInteractionModeToggle,
1617
+ showPlanFollowUpPrompt,
1618
+ showPlanToggle,
1619
+ onCommand: handleCommand,
1620
+ onComposerError: setNotice,
1621
+ onImplementPlanInNewThread: activeProposedPlan && onImplementProposedPlan ? () => implementProposedPlanInNewThread(activeProposedPlan) : void 0,
1622
+ onToggleInteractionMode: () => setInteractionMode(
1623
+ effectiveInteractionMode === "plan" ? "default" : "plan"
1624
+ ),
1625
+ onRuntimeModeChange: setRuntimeMode,
1626
+ onTogglePlanSidebar,
1627
+ onPendingRequestDismiss: dismissPendingUserInput,
1628
+ onPendingRequestSubmit: submitPendingUserInput,
1629
+ onInterrupt: interrupt,
1630
+ scheduleStickToBottom,
1631
+ shouldAutoScrollRef,
1632
+ onSend: () => void sendComposerMessage()
1633
+ }
1634
+ )
1635
+ }
1636
+ ) });
1637
+ if (!timelineExtras) {
1638
+ return chatView;
1639
+ }
1640
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1641
+ chatView,
1642
+ timelineExtras
1643
+ ] });
1644
+ }
1645
+ function isLifecycleCodexChatViewProps(props) {
1646
+ return "lifecycle" in props && Boolean(props.lifecycle);
1647
+ }
1648
+ function defaultPendingRequestNode(request) {
1649
+ if (request.kind === "permissions") {
1650
+ return null;
1651
+ }
1652
+ return `No default renderer is available for ${request.request.method}.`;
1653
+ }
1654
+ function CodexInteractionModeToggle({
1655
+ mode,
1656
+ onChange
1657
+ }) {
1658
+ return /* @__PURE__ */ jsxs(
1659
+ "div",
1660
+ {
1661
+ "aria-label": "Interaction mode",
1662
+ className: "flex shrink-0 items-center rounded-md border bg-background p-0.5",
1663
+ role: "group",
1664
+ children: [
1665
+ /* @__PURE__ */ jsx(
1666
+ "button",
1667
+ {
1668
+ "aria-pressed": mode === "default",
1669
+ className: mode === "default" ? "rounded px-2 py-1 font-medium text-foreground text-xs shadow-xs" : "rounded px-2 py-1 text-muted-foreground text-xs hover:text-foreground",
1670
+ type: "button",
1671
+ onClick: () => onChange("default"),
1672
+ children: "Build"
1673
+ }
1674
+ ),
1675
+ /* @__PURE__ */ jsx(
1676
+ "button",
1677
+ {
1678
+ "aria-pressed": mode === "plan",
1679
+ className: mode === "plan" ? "rounded px-2 py-1 font-medium text-foreground text-xs shadow-xs" : "rounded px-2 py-1 text-muted-foreground text-xs hover:text-foreground",
1680
+ type: "button",
1681
+ onClick: () => onChange("plan"),
1682
+ children: "Plan"
1683
+ }
1684
+ )
1685
+ ]
1686
+ }
1687
+ );
1688
+ }
1689
+ function planImplementationSendContext(sendContext, plan) {
1690
+ const text = buildPlanImplementationPrompt(plan.planMarkdown);
1691
+ return {
1692
+ ...sendContext,
1693
+ files: [],
1694
+ items: [{ text, text_elements: [], type: "text" }],
1695
+ text
1696
+ };
1697
+ }
1698
+
1699
+ // src/components/codex-chat.tsx
1700
+ function CodexChat({
1701
+ appServer,
1702
+ buildThreadStartParams,
1703
+ buildTurnStartParams,
1704
+ connectOnMount,
1705
+ initialState,
1706
+ isRecoverableConnectionError,
1707
+ onActiveThread,
1708
+ onRuntimeError,
1709
+ onServerRequest,
1710
+ onState,
1711
+ onSubmittedUserMessage,
1712
+ onThreadListChanged,
1713
+ onThreadStarted,
1714
+ threadId,
1715
+ threadReader,
1716
+ ...viewProps
1717
+ }) {
1718
+ const lifecycle = {
1719
+ appServer,
1720
+ buildThreadStartParams,
1721
+ buildTurnStartParams: buildTurnStartParams ?? createDefaultTurnStartParams,
1722
+ connectOnMount,
1723
+ initialState,
1724
+ isRecoverableConnectionError,
1725
+ onActiveThread,
1726
+ onRuntimeError,
1727
+ onServerRequest,
1728
+ onState,
1729
+ onSubmittedUserMessage,
1730
+ onThreadListChanged,
1731
+ onThreadStarted,
1732
+ threadId,
1733
+ threadReader
1734
+ };
1735
+ return createElement(CodexChatView, {
1736
+ ...viewProps,
1737
+ lifecycle
1738
+ });
1739
+ }
1740
+ function CodexChatSidebar(props) {
1741
+ return createElement(Sidebar, props);
1742
+ }
1743
+ function CodexChatLayout({
1744
+ children,
1745
+ className,
1746
+ collapsible,
1747
+ defaultOpen,
1748
+ onOpenChange,
1749
+ open,
1750
+ side,
1751
+ sidebar
1752
+ }) {
1753
+ if (!sidebar) {
1754
+ return createElement(Fragment$1, null, children);
1755
+ }
1756
+ return createElement(
1757
+ SidebarProvider,
1758
+ {
1759
+ className,
1760
+ defaultOpen,
1761
+ onOpenChange,
1762
+ open
1763
+ },
1764
+ createElement(CodexChatSidebar, { collapsible, side }, sidebar),
1765
+ createElement(SidebarInset, null, children)
1766
+ );
1767
+ }
1768
+ var CodexChatContext = createContext(null);
1769
+ function CodexChatProvider({
1770
+ appServer,
1771
+ children,
1772
+ threadReader,
1773
+ threadId
1774
+ }) {
1775
+ const runtime = useCodexChat({ appServer, threadReader, threadId });
1776
+ return createElement(
1777
+ CodexChatContext.Provider,
1778
+ { value: runtime },
1779
+ children
1780
+ );
1781
+ }
1782
+ function useCodexChatRuntime() {
1783
+ const runtime = useContext(CodexChatContext);
1784
+ if (!runtime) {
1785
+ throw new Error("useCodexChatRuntime must be used inside CodexChatProvider.");
1786
+ }
1787
+ return runtime;
1788
+ }
1789
+ function useCodexChat(options) {
1790
+ const [activeThreadId, setActiveThreadId] = useState(options.threadId ?? null);
1791
+ const normalizedThreadId = useMemo(
1792
+ () => activeThreadId ? normalizeThreadId2(activeThreadId) : asThreadId("00000000-0000-4000-8000-000000000000"),
1793
+ [activeThreadId]
1794
+ );
1795
+ const lifecycle = useCodexChatLifecycle({
1796
+ appServer: options.appServer,
1797
+ connectOnMount: activeThreadId !== null,
1798
+ threadId: normalizedThreadId,
1799
+ threadReader: options.threadReader
1800
+ });
1801
+ const threadState = useMemo(
1802
+ () => createCodexChatRenderState({
1803
+ lifecycle,
1804
+ snapshot: lifecycle.threadSnapshot
1805
+ }),
1806
+ [lifecycle]
1807
+ );
1808
+ const error = lifecycle.runtimeError ? new Error(lifecycle.runtimeError) : null;
1809
+ return {
1810
+ error,
1811
+ messages: messagesFromRenderState(threadState),
1812
+ async sendMessage(input) {
1813
+ if (!activeThreadId) {
1814
+ throw new Error("Cannot send a Codex chat message without a thread.");
1815
+ }
1816
+ const sendContext = sendContextFromMessageInput(input);
1817
+ await lifecycle.sendComposerMessage(sendContext, {
1818
+ clearComposer: () => {
1819
+ },
1820
+ prepareForOptimisticAppend: async () => {
1821
+ },
1822
+ restoreComposer: () => {
1823
+ }
1824
+ });
1825
+ },
1826
+ setThread(nextThreadId) {
1827
+ setActiveThreadId(nextThreadId);
1828
+ },
1829
+ status: chatRuntimeStatus(lifecycle, activeThreadId !== null),
1830
+ async stop() {
1831
+ await lifecycle.interrupt();
1832
+ },
1833
+ threadId: activeThreadId,
1834
+ threadSnapshot: lifecycle.threadSnapshot,
1835
+ threadState
1836
+ };
1837
+ }
1838
+ function chatRuntimeStatus(lifecycle, hasThread) {
1839
+ if (!hasThread) {
1840
+ return "idle";
1841
+ }
1842
+ if (lifecycle.runtimeError) {
1843
+ return "error";
1844
+ }
1845
+ if (lifecycle.connectionStatus === "connecting" || lifecycle.connectionStatus === "reconnecting") {
1846
+ return "loading";
1847
+ }
1848
+ if (lifecycle.isSending || lifecycle.isSendBusy) {
1849
+ return "submitting";
1850
+ }
1851
+ if (lifecycle.turnRunning || lifecycle.assistantStreaming) {
1852
+ return "streaming";
1853
+ }
1854
+ return "ready";
1855
+ }
1856
+ function sendContextFromMessageInput(input) {
1857
+ const text = input.text?.trim() ?? "";
1858
+ const files = messageInputFiles(input.files);
1859
+ const fileParts = input.files && !isFileList(input.files) && !isFileArray(input.files) ? input.files : [];
1860
+ const items = [
1861
+ ...text ? [{
1862
+ text,
1863
+ text_elements: [],
1864
+ type: "text"
1865
+ }] : [],
1866
+ ...fileParts.flatMap(messagePartToComposerItem)
1867
+ ];
1868
+ return {
1869
+ effort: defaultCodexReasoningEffort,
1870
+ files,
1871
+ items,
1872
+ mentionBindings: [],
1873
+ model: defaultCodexModel,
1874
+ text
1875
+ };
1876
+ }
1877
+ function messageInputFiles(files) {
1878
+ if (!files) {
1879
+ return [];
1880
+ }
1881
+ if (isFileList(files)) {
1882
+ return Array.from(files);
1883
+ }
1884
+ if (isFileArray(files)) {
1885
+ return files;
1886
+ }
1887
+ return [];
1888
+ }
1889
+ function messagePartToComposerItem(part) {
1890
+ if (part.type === "image") {
1891
+ return [{ image_url: part.url, type: "image" }];
1892
+ }
1893
+ if (part.type === "file") {
1894
+ return [{ path: part.url, type: "local_image" }];
1895
+ }
1896
+ return [];
1897
+ }
1898
+ function isFileList(files) {
1899
+ return typeof FileList !== "undefined" && files instanceof FileList;
1900
+ }
1901
+ function isFileArray(files) {
1902
+ return typeof File !== "undefined" && Array.isArray(files) && files.every((file) => file instanceof File);
1903
+ }
1904
+ function messagesFromRenderState(threadState) {
1905
+ return threadState.items.flatMap((item) => {
1906
+ if (item.type === "UserMessage") {
1907
+ return [
1908
+ {
1909
+ id: item.id,
1910
+ parts: item.content.flatMap(userInputToMessageParts),
1911
+ role: "user"
1912
+ }
1913
+ ];
1914
+ }
1915
+ if (item.type === "AgentMessage") {
1916
+ return [
1917
+ {
1918
+ id: item.id,
1919
+ parts: item.content.map((part) => ({
1920
+ type: "text",
1921
+ text: part.text
1922
+ })),
1923
+ role: "assistant"
1924
+ }
1925
+ ];
1926
+ }
1927
+ return [];
1928
+ });
1929
+ }
1930
+ function userInputToMessageParts(input) {
1931
+ if (input.type === "text") {
1932
+ return [{ type: "text", text: input.text }];
1933
+ }
1934
+ if (input.type === "image") {
1935
+ return [{ type: "image", url: input.image_url }];
1936
+ }
1937
+ if (input.type === "local_image") {
1938
+ return [{ type: "file", url: input.path }];
1939
+ }
1940
+ return [];
1941
+ }
1942
+ function normalizeThreadId2(threadId) {
1943
+ return typeof threadId === "string" ? asThreadId(threadId) : threadId;
1944
+ }
1945
+ function CodexThread({ children }) {
1946
+ return createElement("div", { "data-codex-thread": "" }, children);
1947
+ }
1948
+ function CodexMessages() {
1949
+ const runtime = useCodexChatRuntime();
1950
+ return createElement(
1951
+ "div",
1952
+ { "data-codex-messages": "" },
1953
+ runtime.messages.map(
1954
+ (message) => createElement(
1955
+ "div",
1956
+ { "data-role": message.role, key: message.id },
1957
+ message.parts.filter((part) => part.type === "text").map((part) => part.text).join("\n")
1958
+ )
1959
+ )
1960
+ );
1961
+ }
1962
+ function CodexComposer() {
1963
+ const runtime = useCodexChatRuntime();
1964
+ const sendMessage = useCallback(
1965
+ async (formData) => {
1966
+ const text = String(formData.get("message") ?? "");
1967
+ if (text.trim()) {
1968
+ await runtime.sendMessage({ text });
1969
+ }
1970
+ },
1971
+ [runtime]
1972
+ );
1973
+ return createElement(
1974
+ "form",
1975
+ { action: sendMessage, "data-codex-composer": "" },
1976
+ createElement("textarea", { name: "message" }),
1977
+ createElement("button", { type: "submit" }, "Send")
1978
+ );
1979
+ }
1980
+
1981
+ export { CodexChat, CodexChatLayout, CodexChatProvider, CodexChatSidebar, CodexChatView, CodexComposer, CodexMessages, CodexThread, createCodexChatRenderState, createDefaultThreadStartParams, createDefaultTurnStartParams, createLocalDispatchSnapshot, deriveActiveWorkStartedAt, deriveAssistantStreaming, deriveChatLifecycleWorkingState, hasServerAcknowledgedLocalDispatch, threadHasStarted, useCodexChat, useCodexChatLifecycle, useCodexChatRuntime, useLocalDispatchState };
1982
+ //# sourceMappingURL=chunk-W7S6HFCQ.js.map
1983
+ //# sourceMappingURL=chunk-W7S6HFCQ.js.map