@jrkropp/codex-js 0.1.2 → 0.1.3

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