@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.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/dist/ClientNotification-B6-FhXQf.d.ts +5 -0
- package/dist/DynamicToolCallResponse-82DFjES2.d.ts +8 -0
- package/dist/DynamicToolSpec-CfnhqAYK.d.ts +29 -0
- package/dist/PermissionsRequestApprovalResponse-DxzPPDRb.d.ts +55 -0
- package/dist/ProviderStatusBanner-BlP6lzwE.d.ts +441 -0
- package/dist/ServerRequest-B5cKVJjr.d.ts +2181 -0
- package/dist/{session-DPhHN7RZ.d.ts → ThreadResumeResponse-DvmE1juU.d.ts} +3 -306
- package/dist/ToolRequestUserInputQuestion-CeZa5X1J.d.ts +23 -0
- package/dist/ToolRequestUserInputResponse-zcPLwbiK.d.ts +17 -0
- package/dist/TurnSteerResponse-0kBCfplh.d.ts +209 -0
- package/dist/WebSearchToolConfig-D3ep0625.d.ts +18 -0
- package/dist/chat-runtime-9RkXHC_w.d.ts +382 -0
- package/dist/chunk-2DZRMCI2.js +1258 -0
- package/dist/chunk-2DZRMCI2.js.map +1 -0
- package/dist/chunk-4DPLJPB5.js +396 -0
- package/dist/chunk-4DPLJPB5.js.map +1 -0
- package/dist/chunk-5JMJ6OI5.js +3 -0
- package/dist/chunk-5JMJ6OI5.js.map +1 -0
- package/dist/chunk-6ZMJ34KE.js +1153 -0
- package/dist/chunk-6ZMJ34KE.js.map +1 -0
- package/dist/chunk-CGBS37IU.js +128 -0
- package/dist/chunk-CGBS37IU.js.map +1 -0
- package/dist/chunk-DCMKA2A6.js +18 -0
- package/dist/chunk-DCMKA2A6.js.map +1 -0
- package/dist/chunk-DYLHN3HG.js +937 -0
- package/dist/chunk-DYLHN3HG.js.map +1 -0
- package/dist/{chunk-SVK6PLGO.js → chunk-LWQNX4LI.js} +12009 -18768
- package/dist/chunk-LWQNX4LI.js.map +1 -0
- package/dist/{chunk-JLDH4U5L.js → chunk-NCI4MAWZ.js} +317 -1967
- package/dist/chunk-NCI4MAWZ.js.map +1 -0
- package/dist/chunk-O44XP7LH.js +214 -0
- package/dist/chunk-O44XP7LH.js.map +1 -0
- package/dist/chunk-PST3ZWX2.js +555 -0
- package/dist/chunk-PST3ZWX2.js.map +1 -0
- package/dist/chunk-SYPHCDRD.js +1133 -0
- package/dist/chunk-SYPHCDRD.js.map +1 -0
- package/dist/chunk-V4BMZWBM.js +2401 -0
- package/dist/chunk-V4BMZWBM.js.map +1 -0
- package/dist/chunk-W7S6HFCQ.js +1983 -0
- package/dist/chunk-W7S6HFCQ.js.map +1 -0
- package/dist/chunk-YHVCFD2D.js +117 -0
- package/dist/chunk-YHVCFD2D.js.map +1 -0
- package/dist/chunk-Z63UPBS3.js +152 -0
- package/dist/chunk-Z63UPBS3.js.map +1 -0
- package/dist/client/index.d.ts +16 -4
- package/dist/client/index.js +13 -1
- package/dist/codex-rs/app-server/index.d.ts +161 -0
- package/dist/codex-rs/app-server/index.js +13 -0
- package/dist/codex-rs/app-server/index.js.map +1 -0
- package/dist/codex-rs/app-server-protocol/index.d.ts +1722 -0
- package/dist/codex-rs/app-server-protocol/index.js +6 -0
- package/dist/codex-rs/app-server-protocol/index.js.map +1 -0
- package/dist/codex-rs/app-server-protocol/protocol.d.ts +19 -0
- package/dist/codex-rs/app-server-protocol/protocol.js +4 -0
- package/dist/codex-rs/app-server-protocol/protocol.js.map +1 -0
- package/dist/codex-rs/codex-api/index.d.ts +104 -0
- package/dist/codex-rs/codex-api/index.js +11 -0
- package/dist/codex-rs/codex-api/index.js.map +1 -0
- package/dist/codex-rs/config/index.d.ts +88 -0
- package/dist/codex-rs/config/index.js +4 -0
- package/dist/codex-rs/config/index.js.map +1 -0
- package/dist/codex-rs/core/config/index.d.ts +61 -0
- package/dist/codex-rs/core/config/index.js +5 -0
- package/dist/codex-rs/core/config/index.js.map +1 -0
- package/dist/codex-rs/core/index.d.ts +1393 -0
- package/dist/codex-rs/core/index.js +11 -0
- package/dist/codex-rs/core/index.js.map +1 -0
- package/dist/codex-rs/model-provider/index.d.ts +2 -0
- package/dist/codex-rs/model-provider/index.js +4 -0
- package/dist/codex-rs/model-provider/index.js.map +1 -0
- package/dist/codex-rs/models-manager/index.d.ts +2 -0
- package/dist/codex-rs/models-manager/index.js +4 -0
- package/dist/codex-rs/models-manager/index.js.map +1 -0
- package/dist/codex-rs/parity.d.ts +26 -0
- package/dist/codex-rs/parity.js +3 -0
- package/dist/codex-rs/parity.js.map +1 -0
- package/dist/codex-rs/thread-store/index.d.ts +5 -0
- package/dist/codex-rs/thread-store/index.js +4 -0
- package/dist/codex-rs/thread-store/index.js.map +1 -0
- package/dist/codex-rs/unsupported.d.ts +15 -0
- package/dist/codex-rs/unsupported.js +22 -0
- package/dist/codex-rs/unsupported.js.map +1 -0
- package/dist/codex-rs/utils/output-truncation.d.ts +21 -0
- package/dist/codex-rs/utils/output-truncation.js +4 -0
- package/dist/codex-rs/utils/output-truncation.js.map +1 -0
- package/dist/codex-rs/utils/string.d.ts +7 -0
- package/dist/codex-rs/utils/string.js +3 -0
- package/dist/codex-rs/utils/string.js.map +1 -0
- package/dist/common-CTyph5x8.d.ts +40 -0
- package/dist/event-mapping-CbISdQ1D.d.ts +43 -0
- package/dist/history-CfM-4V7b.d.ts +1654 -0
- package/dist/index-77U_Oc-a.d.ts +63 -0
- package/dist/index-CoDZosq0.d.ts +261 -0
- package/dist/index.d.ts +18 -7
- package/dist/index.js +16 -2
- package/dist/lib-nXlaKiS-.d.ts +48 -0
- package/dist/live-thread-BMvlflzM.d.ts +30 -0
- package/dist/merge-B_AWVmnI.d.ts +24 -0
- package/dist/mod-DYVLSWO4.d.ts +91 -0
- package/dist/plan-mode-Cv6KWb_S.d.ts +14 -0
- package/dist/proposed-plan-DpN1ma0Y.d.ts +53 -0
- package/dist/protocol-mpBcYHrm.d.ts +1655 -0
- package/dist/react/index.d.ts +56 -48
- package/dist/react/index.js +16 -2
- package/dist/{remote-_6TDvg-g.d.ts → remote-ClZbq9KN.d.ts} +3 -1
- package/dist/rendered-thread-AOxw3V5b.d.ts +29 -0
- package/dist/responses_websocket-BhxSgCzK.d.ts +183 -0
- package/dist/runtime-Cm6ml53h.d.ts +528 -0
- package/dist/server/index.d.ts +29 -2416
- package/dist/server/index.js +13 -1
- package/dist/session-BRYzi8OT.d.ts +46 -0
- package/dist/shadcn/index.d.ts +1 -1
- package/dist/{sidebar-DT2XoitN.d.ts → sidebar-DMMij22z.d.ts} +1 -1
- package/dist/spec_plan_types-CmsJ-Tfn.d.ts +260 -0
- package/dist/{store-GYldc9EJ.d.ts → store-AGRxhgQ3.d.ts} +2 -1
- package/dist/t3code/apps/web/components/chat.d.ts +508 -0
- package/dist/t3code/apps/web/components/chat.js +12 -0
- package/dist/t3code/apps/web/components/chat.js.map +1 -0
- package/dist/t3code/apps/web/index.d.ts +12 -0
- package/dist/t3code/apps/web/index.js +13 -0
- package/dist/t3code/apps/web/index.js.map +1 -0
- package/dist/testing/index.d.ts +9 -91
- package/dist/testing/index.js +13 -1
- package/dist/thread-history-builder-zW0zeqcS.d.ts +58 -0
- package/dist/thread_event_store-C0zYzukG.d.ts +77 -0
- package/dist/types-BTeabLYr.d.ts +126 -0
- package/package.json +152 -88
- package/dist/chat-runtime-D7wu_KbX.d.ts +0 -747
- package/dist/chunk-JLDH4U5L.js.map +0 -1
- package/dist/chunk-SVK6PLGO.js.map +0 -1
- package/dist/index-CB9la6xE.d.ts +0 -112
- 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
|