@jskit-ai/assistant-core 0.1.200 → 0.1.202
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/examples/conversation/README.md +8 -1
- package/examples/conversation/package.json +5 -5
- package/fixtures/responsive-assistant/App.vue +4 -1
- package/package.json +99 -36
- package/src/client/conversation/AssistantConversationElement.vue +27 -4
- package/src/client/conversation/AssistantConversationStatus.vue +24 -0
- package/src/client/conversation/AssistantGoalControl.vue +8 -5
- package/src/client/conversation/AssistantTranscript.vue +10 -3
- package/src/client/conversation/index.js +2 -0
- package/src/client/conversation/messageDelivery.js +115 -32
- package/src/client/conversation/retainedConversation.js +32 -0
- package/src/client/conversation/useAssistantQuestions.js +76 -0
- package/src/client/lib/assistantApi.js +72 -2
- package/src/server/conversation/agentRun.js +222 -0
- package/src/server/conversation/attachments.js +93 -0
- package/src/server/conversation/claudeClient.js +221 -0
- package/src/server/conversation/claudeHistory.js +256 -0
- package/src/server/conversation/claudeProcess.js +327 -0
- package/src/server/conversation/claudeTools.js +42 -0
- package/src/server/conversation/claudeTurn.js +1082 -0
- package/src/server/conversation/codexConfiguration.js +1183 -0
- package/src/server/conversation/codexConversations.js +1082 -0
- package/src/server/conversation/codexDetachedTurn.js +332 -0
- package/src/server/conversation/codexEvents.js +194 -1
- package/src/server/conversation/codexGoalCommands.js +124 -0
- package/src/server/conversation/codexHelperExecution.js +306 -0
- package/src/server/conversation/codexHelperThreadLedger.js +804 -0
- package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
- package/src/server/conversation/codexHelperThreads.js +638 -0
- package/src/server/conversation/codexHistoryAdapter.js +369 -0
- package/src/server/conversation/codexMessageCommands.js +663 -0
- package/src/server/conversation/codexMessageDelivery.js +211 -0
- package/src/server/conversation/codexNativeHistoryExport.js +95 -0
- package/src/server/conversation/codexNotificationObserver.js +482 -0
- package/src/server/conversation/codexNotificationQueue.js +103 -0
- package/src/server/conversation/codexObservationControl.js +174 -0
- package/src/server/conversation/codexOutput.js +931 -0
- package/src/server/conversation/codexProcess.js +1559 -0
- package/src/server/conversation/codexProcessLeader.js +14 -0
- package/src/server/conversation/codexProvider.js +2710 -0
- package/src/server/conversation/codexProviderOwner.js +1058 -0
- package/src/server/conversation/codexProviderSelection.js +218 -0
- package/src/server/conversation/codexReasoning.js +314 -0
- package/src/server/conversation/codexRenewalCommands.js +290 -0
- package/src/server/conversation/codexResultSettlement.js +409 -0
- package/src/server/conversation/codexRunJournal.js +658 -0
- package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
- package/src/server/conversation/codexSessionCleanup.js +96 -0
- package/src/server/conversation/codexSessionReconciliation.js +284 -0
- package/src/server/conversation/codexThreadReadiness.js +397 -0
- package/src/server/conversation/codexThreadStatus.js +354 -0
- package/src/server/conversation/codexTurn.js +701 -255
- package/src/server/conversation/codexTurnControl.js +317 -0
- package/src/server/conversation/codexTurnRecovery.js +990 -0
- package/src/server/conversation/codexTurnState.js +549 -0
- package/src/server/conversation/commandHook.js +18 -0
- package/src/server/conversation/commandWrapper.js +37 -0
- package/src/server/conversation/configuration.js +43 -0
- package/src/server/conversation/continuity.js +289 -0
- package/src/server/conversation/fileStorage.js +55 -0
- package/src/server/conversation/hookBridge.js +19 -0
- package/src/server/conversation/index.js +13 -0
- package/src/server/conversation/localExecution.js +665 -0
- package/src/server/conversation/memoryStorage.js +7 -72
- package/src/server/conversation/nativeHistoryExport.js +90 -0
- package/src/server/conversation/nativeStorage.js +23 -0
- package/src/server/conversation/openCodeClient.js +209 -19
- package/src/server/conversation/openCodePlugin.js +84 -0
- package/src/server/conversation/openCodeProcess.js +476 -0
- package/src/server/conversation/openCodeRuntime.js +1578 -0
- package/src/server/conversation/openCodeTools.js +60 -0
- package/src/server/conversation/openCodeTurn.js +557 -0
- package/src/server/conversation/provider.js +30 -0
- package/src/server/conversation/providers/api.js +136 -0
- package/src/server/conversation/providers/claude.js +63 -0
- package/src/server/conversation/providers/claudeDriver.js +425 -0
- package/src/server/conversation/providers/codex.js +248 -0
- package/src/server/conversation/providers/codexDriver.js +1078 -0
- package/src/server/conversation/providers/opencode.js +49 -0
- package/src/server/conversation/providers/opencodeDriver.js +650 -0
- package/src/server/conversation/providers/scoped.js +312 -0
- package/src/server/conversation/runtime.js +2220 -0
- package/src/server/conversation/runtimeStateUpgrade.js +125 -0
- package/src/server/conversation/storage.js +184 -0
- package/src/server/conversation/streams.js +7 -2
- package/src/server/conversation/structuredOutput.js +240 -0
- package/src/server/conversation/systemPrompt.js +54 -0
- package/src/server/conversation/tools.js +84 -0
- package/src/server/conversation/transcript.js +5 -4
- package/src/server/hosts/claudeHistory.js +5 -0
- package/src/server/hosts/claudeProcess.js +11 -0
- package/src/server/hosts/claudeTurn.js +4 -0
- package/src/server/hosts/codexConfiguration.js +20 -0
- package/src/server/hosts/codexEvents.js +13 -0
- package/src/server/hosts/codexProcess.js +12 -0
- package/src/server/hosts/codexProvider.js +23 -0
- package/src/server/hosts/codexTurn.js +20 -0
- package/src/server/hosts/nativeHistory.js +3 -0
- package/src/server/hosts/openCodeClient.js +3 -0
- package/src/server/hosts/openCodeProcess.js +23 -0
- package/src/server/hosts/openCodeTurn.js +7 -0
- package/src/server/index.js +2 -0
- package/src/server/lib/aiConnectionClient.js +13 -6
- package/src/server/lib/assistantCompletion.js +190 -0
- package/src/server/lib/assistantToolLoop.js +314 -0
- package/src/server/lib/serviceToolCatalog.js +25 -10
- package/src/shared/assistantResponseText.js +29 -0
- package/src/shared/conversation/attachments.js +19 -0
- package/src/shared/conversation/index.js +3 -0
- package/src/shared/conversation/pagination.js +78 -0
- package/src/shared/conversation/patches.js +150 -0
- package/src/shared/conversation/questions.js +48 -0
- package/src/shared/conversation/streaming.js +7 -4
- package/src/shared/index.js +2 -0
- package/src/shared/nativeProviders.js +45 -0
- package/src/testing/conversationStorageContract.js +31 -1
- package/src/testing/nativeCodex.js +11 -0
- package/src/testing/nativeOpenCode.js +3 -0
- package/test/aiConnectionClient.test.js +2 -0
- package/test/apiConversationDriver.test.js +73 -0
- package/test/assistantApiSurfaceHeader.test.js +85 -0
- package/test/assistantBoundedToolLoop.test.js +285 -0
- package/test/assistantScroll.browser.test.js +16 -0
- package/test/claudeClient.test.js +189 -0
- package/test/claudeConversationSession.test.js +1173 -0
- package/test/claudeInstructionResume.test.js +82 -0
- package/test/claudeProcess.test.js +204 -0
- package/test/claudeTurn.test.js +253 -0
- package/test/codexConversationSession.test.js +2122 -0
- package/test/codexGoal.test.js +37 -0
- package/test/codexHelperDeletion.test.js +163 -0
- package/test/codexHelperThreadLedger.test.js +494 -0
- package/test/codexHistoryAdapter.test.js +541 -0
- package/test/codexInteractiveArguments.test.js +30 -0
- package/test/codexIsolation.test.js +756 -0
- package/test/codexModelCatalog.test.js +362 -0
- package/test/codexNativeHistoryExport.test.js +81 -0
- package/test/codexThreadSelection.test.js +136 -0
- package/test/codexTurn.test.js +2158 -0
- package/test/conversation.test.js +110 -0
- package/test/conversationAgentRun.test.js +326 -0
- package/test/conversationApiFacilities.test.js +90 -0
- package/test/conversationAttachments.test.js +238 -0
- package/test/conversationCommandWrapper.test.js +48 -0
- package/test/conversationConfiguration.test.js +174 -0
- package/test/conversationNativeStorage.test.js +25 -0
- package/test/conversationReplacement.test.js +172 -0
- package/test/conversationRuntime.test.js +241 -0
- package/test/conversationRuntimeStateUpgrade.test.js +144 -0
- package/test/conversationSession.test.js +1080 -0
- package/test/conversationStreaming.test.js +68 -0
- package/test/conversationSystemPrompt.test.js +84 -0
- package/test/conversationTools.test.js +416 -0
- package/test/fileConversationStorage.test.js +73 -0
- package/test/fixtures/codexHelperThreadLedger.js +39 -0
- package/test/localConversationExecution.test.js +90 -0
- package/test/messageDelivery.test.js +234 -1
- package/test/nativeConversationRetirement.test.js +215 -0
- package/test/openCodeClient.test.js +515 -0
- package/test/openCodeProcess.test.js +177 -0
- package/test/openCodeRuntime.test.js +322 -0
- package/test/openCodeTools.test.js +287 -0
- package/test/opencodeConversationSession.test.js +789 -0
- package/test/serviceToolCatalog.test.js +24 -0
|
@@ -0,0 +1,931 @@
|
|
|
1
|
+
import { createCodexAppServerReasoning } from "./codexReasoning.js";
|
|
2
|
+
import {
|
|
3
|
+
normalizeCodexRunText,
|
|
4
|
+
codexAppServerProcessedResultEvent,
|
|
5
|
+
codexAppServerTurnCanReceiveProviderCompletion,
|
|
6
|
+
codexAppServerTurnAwaitsProviderIdentity,
|
|
7
|
+
codexAppServerTurnCanReceiveProviderActivity,
|
|
8
|
+
codexAppServerTurnKey,
|
|
9
|
+
CODEX_APP_SERVER_AGENT_RUN_ID,
|
|
10
|
+
codexAppServerRunInputSource
|
|
11
|
+
} from "./codexTurnState.js";
|
|
12
|
+
import crypto from "node:crypto";
|
|
13
|
+
import { isPlainObject } from "./normalize.js";
|
|
14
|
+
import {
|
|
15
|
+
codexAppServerAssistantItemText,
|
|
16
|
+
classifyCodexAppServerEvent,
|
|
17
|
+
codexAppServerContentText,
|
|
18
|
+
codexAppServerNotificationEvent,
|
|
19
|
+
codexAppServerNotificationEventType,
|
|
20
|
+
codexAppServerNotificationEventPayload,
|
|
21
|
+
codexAppServerNotificationItem,
|
|
22
|
+
codexAppServerNotificationItemId,
|
|
23
|
+
codexAppServerNotificationTurnId,
|
|
24
|
+
codexAppServerProviderThreadAssistantSegments
|
|
25
|
+
} from "./codexEvents.js";
|
|
26
|
+
|
|
27
|
+
// Original native output, reasoning persistence and exact-result identity.
|
|
28
|
+
// One instance belongs to each retained run owner; its state is shared by reference.
|
|
29
|
+
export function createCodexAppServerOutput({
|
|
30
|
+
namespace,
|
|
31
|
+
createRuntime,
|
|
32
|
+
createStore,
|
|
33
|
+
publish,
|
|
34
|
+
acquireProvider,
|
|
35
|
+
turnState,
|
|
36
|
+
turnStateFromAgentRun,
|
|
37
|
+
debugLog,
|
|
38
|
+
debugError,
|
|
39
|
+
liveProgressMaxLength,
|
|
40
|
+
storeReadError,
|
|
41
|
+
messageMetadata,
|
|
42
|
+
snapshotRecoveryItemLimit
|
|
43
|
+
}) {
|
|
44
|
+
const codexAppServerFinalizedTurns = new Set();
|
|
45
|
+
const codexAppServerFinalAssistantResults = new Map();
|
|
46
|
+
const {
|
|
47
|
+
reasoningTurns: codexAppServerReasoningTurns,
|
|
48
|
+
reasoningPersistQueues: codexAppServerReasoningPersistQueues,
|
|
49
|
+
recordReasoningForSession: recordCodexAppServerReasoningForSession,
|
|
50
|
+
readReasoningText: readCodexAppServerReasoningText,
|
|
51
|
+
flushReasoningPersist: flushCodexAppServerReasoningPersist,
|
|
52
|
+
cleanupReasoningTurn: cleanupCodexAppServerReasoningTurn,
|
|
53
|
+
splitReasoningTurn: splitCodexAppServerReasoningTurn
|
|
54
|
+
} = createCodexAppServerReasoning({
|
|
55
|
+
createStore,
|
|
56
|
+
publish,
|
|
57
|
+
turnStateFromAgentRun,
|
|
58
|
+
debugLog,
|
|
59
|
+
readCodexAppServerAgentRunForSession
|
|
60
|
+
});
|
|
61
|
+
const codexAppServerLiveProgressItems = new Set();
|
|
62
|
+
const codexAppServerLiveProgressFingerprints = new Set();
|
|
63
|
+
const codexAppServerMirroredTerminalItems = new Set();
|
|
64
|
+
|
|
65
|
+
function codexAppServerResultFinalizationKey(sessionId = "", threadId = "", turnId = "") {
|
|
66
|
+
return [
|
|
67
|
+
namespace(sessionId),
|
|
68
|
+
codexAppServerTurnKey(threadId, turnId || "*")
|
|
69
|
+
].filter(Boolean).join(":");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function codexAppServerLiveProgressCandidate(notification = {}) {
|
|
73
|
+
const method = normalizeCodexRunText(notification.method);
|
|
74
|
+
const event = codexAppServerNotificationEvent(notification);
|
|
75
|
+
if (isPlainObject(event)) {
|
|
76
|
+
const eventType = codexAppServerNotificationEventType(notification, event);
|
|
77
|
+
const payload = codexAppServerNotificationEventPayload(notification, event);
|
|
78
|
+
const payloadType = normalizeCodexRunText(payload.type);
|
|
79
|
+
const phase = normalizeCodexRunText(payload.phase || event.phase);
|
|
80
|
+
if (eventType === "event_msg" && payloadType === "agent_message" && phase && phase !== "final_answer") {
|
|
81
|
+
return {
|
|
82
|
+
explicit: Boolean(phase),
|
|
83
|
+
phase,
|
|
84
|
+
source: "event",
|
|
85
|
+
text: normalizeCodexRunText(
|
|
86
|
+
codexAppServerContentText(payload.message) ||
|
|
87
|
+
codexAppServerContentText(payload.text) ||
|
|
88
|
+
codexAppServerContentText(payload.content)
|
|
89
|
+
)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (method !== "item/completed") {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const item = codexAppServerNotificationItem(notification);
|
|
98
|
+
const text = codexAppServerAssistantItemText(item);
|
|
99
|
+
if (!text) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
const phase = normalizeCodexRunText(item?.phase || item?.purpose || item?.category);
|
|
103
|
+
if (phase === "final_answer") {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
explicit: ["commentary", "progress", "status", "thinking"].includes(phase),
|
|
108
|
+
phase,
|
|
109
|
+
source: "item",
|
|
110
|
+
text
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function codexAppServerLiveProgressText(notification = {}) {
|
|
115
|
+
const candidate = codexAppServerLiveProgressCandidate(notification);
|
|
116
|
+
const text = normalizeCodexRunText(candidate?.text);
|
|
117
|
+
if (!text) {
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
if (candidate?.phase === "commentary") {
|
|
121
|
+
return text;
|
|
122
|
+
}
|
|
123
|
+
if (text.length > liveProgressMaxLength) {
|
|
124
|
+
return "";
|
|
125
|
+
}
|
|
126
|
+
if (text.includes("\n") || text.includes("\r") || text.includes("```")) {
|
|
127
|
+
return "";
|
|
128
|
+
}
|
|
129
|
+
return text;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function codexAppServerLiveProgressKey(sessionId = "", threadId = "", notification = {}) {
|
|
133
|
+
const itemId = codexAppServerNotificationItemId(notification);
|
|
134
|
+
if (!itemId) {
|
|
135
|
+
return "";
|
|
136
|
+
}
|
|
137
|
+
return [
|
|
138
|
+
normalizeCodexRunText(sessionId),
|
|
139
|
+
normalizeCodexRunText(threadId),
|
|
140
|
+
codexAppServerNotificationTurnId(notification) || "*",
|
|
141
|
+
"live-progress",
|
|
142
|
+
itemId
|
|
143
|
+
].join(":");
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function codexAppServerLiveProgressFingerprintKey(
|
|
147
|
+
sessionId = "",
|
|
148
|
+
threadId = "",
|
|
149
|
+
turnId = "",
|
|
150
|
+
text = ""
|
|
151
|
+
) {
|
|
152
|
+
const normalizedText = normalizeCodexRunText(text);
|
|
153
|
+
if (!normalizedText) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
return [
|
|
157
|
+
normalizeCodexRunText(sessionId),
|
|
158
|
+
normalizeCodexRunText(threadId),
|
|
159
|
+
normalizeCodexRunText(turnId) || "*",
|
|
160
|
+
"live-progress-text",
|
|
161
|
+
crypto.createHash("sha256").update(normalizedText).digest("hex")
|
|
162
|
+
].join(":");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function codexAppServerConversationMessageId(
|
|
166
|
+
threadId = "",
|
|
167
|
+
turnId = "",
|
|
168
|
+
role = "",
|
|
169
|
+
text = ""
|
|
170
|
+
) {
|
|
171
|
+
const normalizedText = normalizeCodexRunText(text);
|
|
172
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
173
|
+
if (!normalizedText || !normalizedThreadId) {
|
|
174
|
+
return "";
|
|
175
|
+
}
|
|
176
|
+
const digest = crypto.createHash("sha256")
|
|
177
|
+
.update([
|
|
178
|
+
normalizedThreadId,
|
|
179
|
+
normalizeCodexRunText(turnId) || "*",
|
|
180
|
+
normalizeCodexRunText(role),
|
|
181
|
+
normalizedText
|
|
182
|
+
].join("\u0000"))
|
|
183
|
+
.digest("hex");
|
|
184
|
+
return `codex-${digest}`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function readCodexAppServerAgentRunForSession(store, sessionId = "") {
|
|
188
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
189
|
+
if (!normalizedSessionId) {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (typeof store?.readAgentRun !== "function") {
|
|
193
|
+
throw new Error(storeReadError);
|
|
194
|
+
}
|
|
195
|
+
return store.readAgentRun(
|
|
196
|
+
normalizedSessionId,
|
|
197
|
+
CODEX_APP_SERVER_AGENT_RUN_ID
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function codexAppServerTerminalItemMirrorKey(sessionId = "", threadId = "", notification = {}, role = "") {
|
|
202
|
+
const itemId = codexAppServerNotificationItemId(notification);
|
|
203
|
+
if (!itemId) {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
return [
|
|
207
|
+
normalizeCodexRunText(sessionId),
|
|
208
|
+
normalizeCodexRunText(threadId),
|
|
209
|
+
codexAppServerNotificationTurnId(notification) || "*",
|
|
210
|
+
normalizeCodexRunText(role),
|
|
211
|
+
itemId
|
|
212
|
+
].join(":");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
async function writeMirroredCodexAppServerTerminalMessage({
|
|
216
|
+
notification = {},
|
|
217
|
+
role = "",
|
|
218
|
+
sessionId = "",
|
|
219
|
+
text = "",
|
|
220
|
+
threadId = ""
|
|
221
|
+
} = {}) {
|
|
222
|
+
const normalizedRole = normalizeCodexRunText(role);
|
|
223
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
224
|
+
const normalizedText = normalizeCodexRunText(text);
|
|
225
|
+
if (!normalizedSessionId || !normalizedText || !["assistant", "user"].includes(normalizedRole)) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
const key = codexAppServerTerminalItemMirrorKey(
|
|
229
|
+
normalizedSessionId,
|
|
230
|
+
threadId,
|
|
231
|
+
notification,
|
|
232
|
+
normalizedRole
|
|
233
|
+
);
|
|
234
|
+
if (!key) {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
if (codexAppServerMirroredTerminalItems.has(key)) {
|
|
238
|
+
return null;
|
|
239
|
+
}
|
|
240
|
+
codexAppServerMirroredTerminalItems.add(key);
|
|
241
|
+
let written = null;
|
|
242
|
+
try {
|
|
243
|
+
const store = await createStore(sessionId);
|
|
244
|
+
const writer = normalizedRole === "user"
|
|
245
|
+
? store?.writeConversationUserMessage
|
|
246
|
+
: store?.writeConversationAssistantMessage;
|
|
247
|
+
if (typeof writer !== "function") {
|
|
248
|
+
codexAppServerMirroredTerminalItems.delete(key);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
let turnMetadata = null;
|
|
252
|
+
if (normalizedRole === "user" && typeof store.readConversationLog === "function") {
|
|
253
|
+
turnMetadata = await messageMetadata.terminal?.(store, normalizedSessionId) ?? null;
|
|
254
|
+
}
|
|
255
|
+
written = await writer.call(store, normalizedSessionId, {
|
|
256
|
+
messageId: codexAppServerConversationMessageId(
|
|
257
|
+
threadId,
|
|
258
|
+
codexAppServerNotificationTurnId(notification),
|
|
259
|
+
normalizedRole,
|
|
260
|
+
normalizedText
|
|
261
|
+
),
|
|
262
|
+
text: normalizedText,
|
|
263
|
+
nativeIdentity: { threadId, turnId: codexAppServerNotificationTurnId(notification) },
|
|
264
|
+
...(turnMetadata ? { turnMetadata } : {})
|
|
265
|
+
});
|
|
266
|
+
if (!written) {
|
|
267
|
+
codexAppServerMirroredTerminalItems.delete(key);
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
const reason = normalizedRole === "user"
|
|
271
|
+
? "codex-app-server-terminal-user-message"
|
|
272
|
+
: "codex-app-server-terminal-assistant-message";
|
|
273
|
+
await publish(normalizedSessionId, {
|
|
274
|
+
payload: {
|
|
275
|
+
conversationLogPatch: {
|
|
276
|
+
turn: written,
|
|
277
|
+
type: "upsert-turn"
|
|
278
|
+
}
|
|
279
|
+
},
|
|
280
|
+
reason
|
|
281
|
+
});
|
|
282
|
+
debugLog(`appServerTerminal${normalizedRole === "user" ? "User" : "Assistant"}Message.mirrored`, {
|
|
283
|
+
itemId: normalizeCodexRunText(codexAppServerNotificationItem(notification)?.id),
|
|
284
|
+
sessionId: normalizedSessionId,
|
|
285
|
+
threadId: normalizeCodexRunText(threadId),
|
|
286
|
+
turnId: codexAppServerNotificationTurnId(notification)
|
|
287
|
+
});
|
|
288
|
+
return written;
|
|
289
|
+
} catch (error) {
|
|
290
|
+
if (!written) {
|
|
291
|
+
codexAppServerMirroredTerminalItems.delete(key);
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function mirrorCodexAppServerTerminalAssistantMessage(sessionId = "", threadId = "", notification = {}) {
|
|
298
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
299
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
300
|
+
if (codexAppServerLiveProgressCandidate(notification)?.explicit === true) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const classification = classifyCodexAppServerEvent(notification);
|
|
304
|
+
// Completed finals use the same native-item publication owner as UI chat.
|
|
305
|
+
if (classification.kind === "final_assistant_result") {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const text = normalizeCodexRunText(
|
|
309
|
+
codexAppServerAssistantItemText(codexAppServerNotificationItem(notification))
|
|
310
|
+
);
|
|
311
|
+
if (!normalizedSessionId || !text) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const store = await createStore(normalizedSessionId);
|
|
315
|
+
const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
|
|
316
|
+
if (normalizeCodexRunText(run?.inputSource) !== "terminal") {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
await writeMirroredCodexAppServerTerminalMessage({
|
|
320
|
+
notification,
|
|
321
|
+
role: "assistant",
|
|
322
|
+
sessionId: normalizedSessionId,
|
|
323
|
+
text,
|
|
324
|
+
threadId: normalizedThreadId
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function readCodexAppServerFinalAssistantResult(sessionId = "", threadId = "", turnId = "") {
|
|
329
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
330
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
331
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
332
|
+
if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return codexAppServerFinalAssistantResults.get(
|
|
336
|
+
codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId)
|
|
337
|
+
) || null;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function persistCodexAppServerAssistantReply(runtime, sessionId = "", record = {}, streamItemId = "", outputItemId = streamItemId || record.itemId) {
|
|
341
|
+
if (record.conversationTurn && !streamItemId) {
|
|
342
|
+
return record.conversationTurn;
|
|
343
|
+
}
|
|
344
|
+
const conversationText = normalizeCodexRunText(record.text);
|
|
345
|
+
if (!conversationText) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const messageId = codexAppServerConversationMessageId(
|
|
349
|
+
record.threadId, record.turnId, "assistant-item", record.itemId
|
|
350
|
+
);
|
|
351
|
+
let written = record.conversationTurn || await runtime.store.writeConversationAssistantMessage(sessionId, {
|
|
352
|
+
messageId,
|
|
353
|
+
outputId: codexAppServerConversationMessageId(
|
|
354
|
+
record.threadId, record.turnId, "assistant-item", outputItemId
|
|
355
|
+
),
|
|
356
|
+
nativeIdentity: { threadId: record.threadId, turnId: record.turnId },
|
|
357
|
+
text: conversationText
|
|
358
|
+
});
|
|
359
|
+
if (!written) {
|
|
360
|
+
// A replay or correction belongs to its original row, including after restart.
|
|
361
|
+
written = (await runtime.store.readConversationLog(sessionId))
|
|
362
|
+
.find((turn) => turn.assistant?.messageId === messageId);
|
|
363
|
+
if (!written) {
|
|
364
|
+
throw new Error("The saved Codex reply could not be found.");
|
|
365
|
+
}
|
|
366
|
+
if (written.assistant.text !== conversationText) {
|
|
367
|
+
written = await runtime.store.upsertConversationAssistantMessage(sessionId, {
|
|
368
|
+
text: conversationText,
|
|
369
|
+
nativeIdentity: { threadId: record.threadId, turnId: record.turnId },
|
|
370
|
+
turnId: written.turnId
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (streamItemId) {
|
|
375
|
+
runtime.store.completeConversationStreamMessage(sessionId, codexAppServerConversationMessageId(
|
|
376
|
+
record.threadId, record.turnId, "assistant-item", streamItemId
|
|
377
|
+
));
|
|
378
|
+
}
|
|
379
|
+
await publish(sessionId, {
|
|
380
|
+
payload: {
|
|
381
|
+
conversationStream: runtime.store.completeConversationStreamMessage(sessionId, messageId),
|
|
382
|
+
conversationLogPatch: {
|
|
383
|
+
turn: written,
|
|
384
|
+
type: "upsert-turn"
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
reason: "assistant-response-bundle"
|
|
388
|
+
});
|
|
389
|
+
record.conversationTurn = written;
|
|
390
|
+
debugLog("appServerAssistantResponseBundle.persisted", {
|
|
391
|
+
conversationTurnId: normalizeCodexRunText(written.turnId),
|
|
392
|
+
sessionId: normalizeCodexRunText(sessionId),
|
|
393
|
+
textLength: conversationText.length,
|
|
394
|
+
threadId: normalizeCodexRunText(record.threadId),
|
|
395
|
+
turnId: normalizeCodexRunText(record.turnId)
|
|
396
|
+
});
|
|
397
|
+
return written;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function recordCodexAppServerFinalAssistantResult({
|
|
401
|
+
itemId = "",
|
|
402
|
+
notification = {},
|
|
403
|
+
sessionId = "",
|
|
404
|
+
source = "",
|
|
405
|
+
streamItemId = "",
|
|
406
|
+
outputItemId,
|
|
407
|
+
text = "",
|
|
408
|
+
threadId = "",
|
|
409
|
+
turnId = ""
|
|
410
|
+
} = {}) {
|
|
411
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
412
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
413
|
+
const assistantText = normalizeCodexRunText(text);
|
|
414
|
+
const normalizedItemId = normalizeCodexRunText(itemId) || codexAppServerNotificationItemId(notification);
|
|
415
|
+
if (!normalizedSessionId || !normalizedThreadId || !assistantText || !normalizedItemId) {
|
|
416
|
+
return {
|
|
417
|
+
recorded: false,
|
|
418
|
+
reason: normalizedItemId ? "empty" : "missing_item_id"
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const runtime = await createRuntime();
|
|
423
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
424
|
+
const currentTurn = turnState(session);
|
|
425
|
+
const currentTurnId = normalizeCodexRunText(currentTurn.turnId);
|
|
426
|
+
const normalizedTurnId = normalizeCodexRunText(turnId) || codexAppServerNotificationTurnId(notification);
|
|
427
|
+
if (!normalizedTurnId) {
|
|
428
|
+
return { recorded: false, reason: "missing_turn_id" };
|
|
429
|
+
}
|
|
430
|
+
const key = codexAppServerResultFinalizationKey(normalizedSessionId, normalizedThreadId, normalizedTurnId);
|
|
431
|
+
const existing = codexAppServerFinalAssistantResults.get(key) || null;
|
|
432
|
+
if (
|
|
433
|
+
!existing &&
|
|
434
|
+
codexAppServerTurnAwaitsProviderIdentity(currentTurn, normalizedThreadId, normalizedTurnId)
|
|
435
|
+
) {
|
|
436
|
+
return {
|
|
437
|
+
recorded: false,
|
|
438
|
+
reason: "turn_identity_pending",
|
|
439
|
+
turnId: normalizedTurnId
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
const completedOwnedTurn = Boolean(codexAppServerProcessedResultEvent(session, normalizedThreadId, normalizedTurnId));
|
|
443
|
+
const stoppedRecovery = source === "provider-recovery" &&
|
|
444
|
+
currentTurn.status === "observation_lost" &&
|
|
445
|
+
currentTurn.threadId === normalizedThreadId &&
|
|
446
|
+
currentTurnId === normalizedTurnId &&
|
|
447
|
+
!currentTurn.active;
|
|
448
|
+
if (
|
|
449
|
+
!existing && !completedOwnedTurn && !stoppedRecovery &&
|
|
450
|
+
!codexAppServerTurnCanReceiveProviderCompletion(currentTurn, normalizedThreadId, normalizedTurnId)
|
|
451
|
+
) {
|
|
452
|
+
if (!codexAppServerFinalizedTurns.has(key)) {
|
|
453
|
+
codexAppServerFinalizedTurns.add(key);
|
|
454
|
+
debugLog("appServerAgentResult.stale", {
|
|
455
|
+
currentState: currentTurn.state,
|
|
456
|
+
currentStatus: currentTurn.status,
|
|
457
|
+
currentThreadId: currentTurn.threadId,
|
|
458
|
+
currentTurnId: currentTurn.turnId,
|
|
459
|
+
sessionId: normalizedSessionId,
|
|
460
|
+
source: normalizeCodexRunText(source),
|
|
461
|
+
threadId: normalizedThreadId,
|
|
462
|
+
turnId: normalizedTurnId
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
return {
|
|
466
|
+
recorded: false,
|
|
467
|
+
reason: "stale_turn_state",
|
|
468
|
+
turnId: normalizedTurnId
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
if (source !== "provider-recovery") {
|
|
473
|
+
// Live item IDs and saved-history IDs differ in native Codex threads.
|
|
474
|
+
// Only saved history supplies durable replies; a live completion triggers
|
|
475
|
+
// that read and retires its provisional stream in the same publication.
|
|
476
|
+
const segments = await recoverCodexAppServerAssistantSegmentsFromProvider(
|
|
477
|
+
normalizedSessionId, normalizedThreadId, normalizedTurnId
|
|
478
|
+
);
|
|
479
|
+
if (!segments.length) throw new Error("Codex's completed reply is missing from its saved turn.");
|
|
480
|
+
// A history read can already include a later native reply. Keep each exact
|
|
481
|
+
// native item identity separate from the original last-publication retirement.
|
|
482
|
+
const sameNativeItem = segments.some(segment => segment.itemId === normalizedItemId);
|
|
483
|
+
let result;
|
|
484
|
+
for (const segment of segments) {
|
|
485
|
+
result = await recordCodexAppServerFinalAssistantResult({
|
|
486
|
+
...segment,
|
|
487
|
+
sessionId: normalizedSessionId,
|
|
488
|
+
source: "provider-recovery",
|
|
489
|
+
streamItemId: segment === segments.at(-1) ? normalizedItemId : "",
|
|
490
|
+
outputItemId: sameNativeItem ? segment.itemId : undefined,
|
|
491
|
+
threadId: normalizedThreadId,
|
|
492
|
+
turnId: normalizedTurnId
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
}
|
|
497
|
+
if (existing?.itemId === normalizedItemId && existing.text === assistantText && existing.conversationTurn) {
|
|
498
|
+
if (streamItemId) await persistCodexAppServerAssistantReply(runtime, normalizedSessionId, existing, streamItemId, outputItemId);
|
|
499
|
+
return { ...existing, recorded: true, reason: "duplicate" };
|
|
500
|
+
}
|
|
501
|
+
const record = {
|
|
502
|
+
itemId: normalizedItemId,
|
|
503
|
+
source: normalizeCodexRunText(source),
|
|
504
|
+
text: assistantText,
|
|
505
|
+
threadId: normalizedThreadId,
|
|
506
|
+
turnId: normalizedTurnId
|
|
507
|
+
};
|
|
508
|
+
codexAppServerFinalAssistantResults.set(key, record);
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
await persistCodexAppServerAssistantReply(runtime, normalizedSessionId, record, streamItemId, outputItemId);
|
|
512
|
+
debugLog("appServerFinalAssistantResult.recorded", {
|
|
513
|
+
itemId: record.itemId,
|
|
514
|
+
sessionId: normalizedSessionId,
|
|
515
|
+
source: record.source,
|
|
516
|
+
threadId: normalizedThreadId,
|
|
517
|
+
turnId: normalizedTurnId
|
|
518
|
+
});
|
|
519
|
+
return {
|
|
520
|
+
...record,
|
|
521
|
+
recorded: true,
|
|
522
|
+
reason: existing ? "updated" : "recorded"
|
|
523
|
+
};
|
|
524
|
+
} catch (error) {
|
|
525
|
+
if (existing) {
|
|
526
|
+
codexAppServerFinalAssistantResults.set(key, existing);
|
|
527
|
+
} else {
|
|
528
|
+
codexAppServerFinalAssistantResults.delete(key);
|
|
529
|
+
}
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function cleanupCodexAppServerUntrackedTurn(threadId = "", turnId = "") {
|
|
535
|
+
cleanupCodexAppServerReasoningTurn(threadId, turnId);
|
|
536
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
537
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
538
|
+
if (!normalizedThreadId) {
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const markers = [
|
|
542
|
+
`:${normalizedThreadId}:*:`,
|
|
543
|
+
...(normalizedTurnId ? [`:${normalizedThreadId}:${normalizedTurnId}:`] : [])
|
|
544
|
+
];
|
|
545
|
+
for (const set of [
|
|
546
|
+
codexAppServerLiveProgressItems,
|
|
547
|
+
codexAppServerLiveProgressFingerprints,
|
|
548
|
+
codexAppServerMirroredTerminalItems
|
|
549
|
+
]) {
|
|
550
|
+
for (const key of set) {
|
|
551
|
+
if (markers.some((marker) => key.includes(marker))) {
|
|
552
|
+
set.delete(key);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function codexAppServerStreamMessage(classification) {
|
|
559
|
+
return {
|
|
560
|
+
nativeIdentity: { threadId: classification.threadId, turnId: classification.turnId },
|
|
561
|
+
turnId: `${classification.threadId}:${classification.turnId}`,
|
|
562
|
+
outputId: codexAppServerConversationMessageId(
|
|
563
|
+
classification.threadId, classification.turnId, "assistant-item", classification.itemId
|
|
564
|
+
),
|
|
565
|
+
messageId: codexAppServerConversationMessageId(
|
|
566
|
+
classification.threadId, classification.turnId, "assistant-item", classification.itemId
|
|
567
|
+
),
|
|
568
|
+
role: classification.role,
|
|
569
|
+
delta: classification.delta
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
async function writeCodexAppServerStream(sessionId, classification) {
|
|
574
|
+
if (!classification.itemId || !classification.turnId) return;
|
|
575
|
+
const store = await createStore(sessionId);
|
|
576
|
+
const conversationStream = await store.mutateSession(sessionId, async () => {
|
|
577
|
+
const run = await readCodexAppServerAgentRunForSession(store, sessionId);
|
|
578
|
+
const turn = turnStateFromAgentRun(run || {});
|
|
579
|
+
if (!codexAppServerTurnCanReceiveProviderActivity(turn, classification.threadId, classification.turnId)) return null;
|
|
580
|
+
return store.updateConversationStream(sessionId, codexAppServerStreamMessage(classification));
|
|
581
|
+
});
|
|
582
|
+
if (conversationStream) {
|
|
583
|
+
await publish(sessionId, {
|
|
584
|
+
payload: { conversationStream },
|
|
585
|
+
reason: "assistant-stream"
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async function writeCodexAppServerLiveProgress(sessionId = "", threadId = "", notification = {}) {
|
|
591
|
+
// Phase-less providers also send short updates as agent messages.
|
|
592
|
+
// Reasoning items are recorded separately from these user-facing updates.
|
|
593
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
594
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
595
|
+
const candidate = codexAppServerLiveProgressCandidate(notification);
|
|
596
|
+
const text = codexAppServerLiveProgressText(notification);
|
|
597
|
+
if (!normalizedSessionId || !normalizedThreadId || !text) {
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
const store = await createStore(normalizedSessionId);
|
|
601
|
+
const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
|
|
602
|
+
const turn = turnStateFromAgentRun(run || {});
|
|
603
|
+
if (!codexAppServerTurnCanReceiveProviderActivity(
|
|
604
|
+
turn,
|
|
605
|
+
normalizedThreadId,
|
|
606
|
+
codexAppServerNotificationTurnId(notification)
|
|
607
|
+
)) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
const key = codexAppServerLiveProgressKey(normalizedSessionId, normalizedThreadId, notification);
|
|
611
|
+
if (!key) {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
const fingerprintKey = codexAppServerLiveProgressFingerprintKey(
|
|
615
|
+
normalizedSessionId,
|
|
616
|
+
normalizedThreadId,
|
|
617
|
+
codexAppServerNotificationTurnId(notification) || turn.turnId,
|
|
618
|
+
text
|
|
619
|
+
);
|
|
620
|
+
const streamMessageId = codexAppServerConversationMessageId(
|
|
621
|
+
normalizedThreadId, codexAppServerNotificationTurnId(notification),
|
|
622
|
+
"assistant-item", codexAppServerNotificationItemId(notification)
|
|
623
|
+
);
|
|
624
|
+
if (
|
|
625
|
+
codexAppServerLiveProgressItems.has(key) ||
|
|
626
|
+
codexAppServerLiveProgressFingerprints.has(fingerprintKey)
|
|
627
|
+
) {
|
|
628
|
+
await publish(normalizedSessionId, {
|
|
629
|
+
payload: { conversationStream: store.completeConversationStreamMessage(normalizedSessionId, streamMessageId) },
|
|
630
|
+
reason: "assistant-stream"
|
|
631
|
+
});
|
|
632
|
+
return null;
|
|
633
|
+
}
|
|
634
|
+
codexAppServerLiveProgressItems.add(key);
|
|
635
|
+
codexAppServerLiveProgressFingerprints.add(fingerprintKey);
|
|
636
|
+
let written = null;
|
|
637
|
+
try {
|
|
638
|
+
const role = !candidate.phase || candidate.phase === "commentary" ? "commentary" : "thinking";
|
|
639
|
+
const writer = role === "commentary"
|
|
640
|
+
? store.writeConversationCommentaryMessage
|
|
641
|
+
: store.writeConversationThinkingMessage;
|
|
642
|
+
written = await writer.call(store, normalizedSessionId, {
|
|
643
|
+
...(streamMessageId ? { outputId: streamMessageId } : {}),
|
|
644
|
+
messageId: codexAppServerConversationMessageId(
|
|
645
|
+
normalizedThreadId,
|
|
646
|
+
codexAppServerNotificationTurnId(notification) || turn.turnId,
|
|
647
|
+
role,
|
|
648
|
+
text
|
|
649
|
+
),
|
|
650
|
+
requireOpenTurn: false,
|
|
651
|
+
nativeIdentity: { threadId: normalizedThreadId, turnId: codexAppServerNotificationTurnId(notification) || turn.turnId },
|
|
652
|
+
text
|
|
653
|
+
});
|
|
654
|
+
if (!written) {
|
|
655
|
+
codexAppServerLiveProgressItems.delete(key);
|
|
656
|
+
codexAppServerLiveProgressFingerprints.delete(fingerprintKey);
|
|
657
|
+
}
|
|
658
|
+
const payload = {
|
|
659
|
+
conversationStream: store.completeConversationStreamMessage(normalizedSessionId, streamMessageId)
|
|
660
|
+
};
|
|
661
|
+
if (written) {
|
|
662
|
+
payload.conversationLogPatch = { turn: written, type: "upsert-turn" };
|
|
663
|
+
}
|
|
664
|
+
await publish(normalizedSessionId, {
|
|
665
|
+
payload,
|
|
666
|
+
reason: role === "commentary"
|
|
667
|
+
? "codex-app-server-commentary"
|
|
668
|
+
: "codex-app-server-live-progress"
|
|
669
|
+
});
|
|
670
|
+
return written;
|
|
671
|
+
} catch (error) {
|
|
672
|
+
if (!written) {
|
|
673
|
+
codexAppServerLiveProgressItems.delete(key);
|
|
674
|
+
codexAppServerLiveProgressFingerprints.delete(fingerprintKey);
|
|
675
|
+
}
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function recoverCodexAppServerAssistantSegmentsFromProvider(sessionId = "", threadId = "", turnId = "") {
|
|
681
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
682
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
683
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
684
|
+
if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId) {
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
const runtime = await createRuntime();
|
|
688
|
+
const session = await runtime.getSession(normalizedSessionId);
|
|
689
|
+
const provider = await acquireProvider({ sessionId: normalizedSessionId, runtime, session });
|
|
690
|
+
if (!provider) {
|
|
691
|
+
return [];
|
|
692
|
+
}
|
|
693
|
+
// Page from the newest turn instead of hydrating the entire conversation.
|
|
694
|
+
// A goal may already have started its successor when completion arrives.
|
|
695
|
+
let cursor;
|
|
696
|
+
let assistantSegments = [];
|
|
697
|
+
do {
|
|
698
|
+
const page = await provider.listThreadTurns(normalizedThreadId, {
|
|
699
|
+
limit: 1, itemsView: "full", sortDirection: "desc", ...(cursor ? { cursor } : {})
|
|
700
|
+
});
|
|
701
|
+
const turn = page.data.find((candidate) => candidate.id === normalizedTurnId);
|
|
702
|
+
if (turn) {
|
|
703
|
+
assistantSegments = codexAppServerProviderThreadAssistantSegments({ turns: [turn] }, normalizedTurnId);
|
|
704
|
+
break;
|
|
705
|
+
}
|
|
706
|
+
cursor = page.nextCursor;
|
|
707
|
+
} while (cursor);
|
|
708
|
+
if (assistantSegments.length) {
|
|
709
|
+
debugLog("appServerAgentResult.recovered", {
|
|
710
|
+
assistantSegmentCount: assistantSegments.length,
|
|
711
|
+
sessionId: normalizedSessionId,
|
|
712
|
+
threadId: normalizedThreadId,
|
|
713
|
+
turnId: normalizedTurnId
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
return assistantSegments;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async function submitCodexAppServerAssistantResult(sessionId = "", threadId = "", turnId = "", {
|
|
720
|
+
recoverFromProvider = false
|
|
721
|
+
} = {}) {
|
|
722
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
723
|
+
let finalResult = readCodexAppServerFinalAssistantResult(normalizedSessionId, threadId, turnId);
|
|
724
|
+
let assistantText = normalizeCodexRunText(finalResult?.text);
|
|
725
|
+
const reasoningText = readCodexAppServerReasoningText(threadId, turnId);
|
|
726
|
+
try {
|
|
727
|
+
if (normalizedSessionId && recoverFromProvider) {
|
|
728
|
+
const recoveredSegments = await recoverCodexAppServerAssistantSegmentsFromProvider(
|
|
729
|
+
normalizedSessionId,
|
|
730
|
+
threadId,
|
|
731
|
+
turnId
|
|
732
|
+
);
|
|
733
|
+
for (const recoveredSegment of recoveredSegments) {
|
|
734
|
+
await recordCodexAppServerFinalAssistantResult({
|
|
735
|
+
itemId: recoveredSegment.itemId,
|
|
736
|
+
sessionId: normalizedSessionId,
|
|
737
|
+
source: "provider-recovery",
|
|
738
|
+
text: recoveredSegment.text,
|
|
739
|
+
threadId,
|
|
740
|
+
turnId
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (recoveredSegments.length) {
|
|
744
|
+
finalResult = readCodexAppServerFinalAssistantResult(normalizedSessionId, threadId, turnId);
|
|
745
|
+
assistantText = normalizeCodexRunText(finalResult?.text);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (!normalizedSessionId || !assistantText && !reasoningText) {
|
|
749
|
+
return {
|
|
750
|
+
ok: false,
|
|
751
|
+
processed: false,
|
|
752
|
+
reason: "empty"
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
const runtime = await createRuntime();
|
|
756
|
+
if (reasoningText) {
|
|
757
|
+
await flushCodexAppServerReasoningPersist(normalizedSessionId, threadId, turnId);
|
|
758
|
+
}
|
|
759
|
+
if (!assistantText) {
|
|
760
|
+
return {
|
|
761
|
+
ok: true,
|
|
762
|
+
processed: false,
|
|
763
|
+
reason: "missing_assistant_text"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
await persistCodexAppServerAssistantReply(runtime, normalizedSessionId, finalResult);
|
|
767
|
+
return {
|
|
768
|
+
ok: true,
|
|
769
|
+
processed: true,
|
|
770
|
+
reason: "assistant_response"
|
|
771
|
+
};
|
|
772
|
+
} catch (error) {
|
|
773
|
+
debugLog("appServerAgentResult.error", {
|
|
774
|
+
error: debugError(error),
|
|
775
|
+
sessionId: normalizedSessionId,
|
|
776
|
+
threadId: normalizeCodexRunText(threadId),
|
|
777
|
+
turnId: normalizeCodexRunText(turnId)
|
|
778
|
+
});
|
|
779
|
+
return {
|
|
780
|
+
error: normalizeCodexRunText(error?.error || error?.message || error) || "Codex app-server response could not be processed.",
|
|
781
|
+
ok: false,
|
|
782
|
+
processed: false,
|
|
783
|
+
reason: "error"
|
|
784
|
+
};
|
|
785
|
+
} finally {
|
|
786
|
+
cleanupCodexAppServerReasoningTurn(threadId, turnId);
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
function codexAppServerSnapshotCursor(run = {}) {
|
|
791
|
+
const cursor = run?.providerSnapshotCursor;
|
|
792
|
+
if (!isPlainObject(cursor)) {
|
|
793
|
+
return {
|
|
794
|
+
itemId: "",
|
|
795
|
+
turnId: ""
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
return {
|
|
799
|
+
itemId: normalizeCodexRunText(cursor.itemId),
|
|
800
|
+
turnId: normalizeCodexRunText(cursor.turnId)
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
async function recordCodexAppServerSnapshotCursor(store, sessionId = "", {
|
|
805
|
+
itemId = "",
|
|
806
|
+
turnId = ""
|
|
807
|
+
} = {}) {
|
|
808
|
+
const normalizedItemId = normalizeCodexRunText(itemId);
|
|
809
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
810
|
+
const normalizedTurnId = normalizeCodexRunText(turnId);
|
|
811
|
+
if (!normalizedItemId || !normalizedSessionId || !normalizedTurnId) {
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
|
|
815
|
+
const current = codexAppServerSnapshotCursor(run);
|
|
816
|
+
if (current.itemId === normalizedItemId && current.turnId === normalizedTurnId) {
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
await store.writeAgentRunEvent(normalizedSessionId, CODEX_APP_SERVER_AGENT_RUN_ID, {
|
|
820
|
+
event: {
|
|
821
|
+
kind: "codex-app-server-thread-snapshot-observed",
|
|
822
|
+
message: "",
|
|
823
|
+
providerThreadId: turnStateFromAgentRun(run || {}).threadId,
|
|
824
|
+
providerTurnId: normalizedTurnId
|
|
825
|
+
},
|
|
826
|
+
patch: {
|
|
827
|
+
providerSnapshotCursor: {
|
|
828
|
+
itemId: normalizedItemId,
|
|
829
|
+
turnId: normalizedTurnId
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function reconcileCodexAppServerObservedTurnItems(
|
|
836
|
+
sessionId = "",
|
|
837
|
+
threadId = "",
|
|
838
|
+
observedTurn = null,
|
|
839
|
+
provider = null
|
|
840
|
+
) {
|
|
841
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
842
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
843
|
+
const normalizedTurnId = normalizeCodexRunText(observedTurn?.id);
|
|
844
|
+
const items = (Array.isArray(observedTurn?.items) ? observedTurn.items : [])
|
|
845
|
+
.filter((item) => isPlainObject(item) && normalizeCodexRunText(item.id));
|
|
846
|
+
if (!normalizedSessionId || !normalizedThreadId || !normalizedTurnId || !items.length) {
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
try {
|
|
850
|
+
const store = await createStore(normalizedSessionId);
|
|
851
|
+
const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
|
|
852
|
+
for (const segment of codexAppServerProviderThreadAssistantSegments({ turns: [observedTurn] }, normalizedTurnId)) {
|
|
853
|
+
await recordCodexAppServerFinalAssistantResult({
|
|
854
|
+
...segment,
|
|
855
|
+
sessionId: normalizedSessionId,
|
|
856
|
+
source: "provider-recovery",
|
|
857
|
+
threadId: normalizedThreadId,
|
|
858
|
+
turnId: normalizedTurnId
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
if (codexAppServerRunInputSource(run) !== "terminal") {
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
const cursor = codexAppServerSnapshotCursor(run);
|
|
865
|
+
const cursorIndex = cursor.turnId === normalizedTurnId
|
|
866
|
+
? items.findIndex((item) => normalizeCodexRunText(item.id) === cursor.itemId)
|
|
867
|
+
: -1;
|
|
868
|
+
const unseenItems = cursorIndex >= 0
|
|
869
|
+
? items.slice(cursorIndex + 1)
|
|
870
|
+
: items.slice(-snapshotRecoveryItemLimit);
|
|
871
|
+
for (const item of unseenItems) {
|
|
872
|
+
if (!codexAppServerAssistantItemText(item)) {
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
const notification = {
|
|
876
|
+
method: "item/completed",
|
|
877
|
+
params: {
|
|
878
|
+
item,
|
|
879
|
+
threadId: normalizedThreadId,
|
|
880
|
+
turnId: normalizedTurnId
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
await writeCodexAppServerLiveProgress(
|
|
884
|
+
normalizedSessionId,
|
|
885
|
+
normalizedThreadId,
|
|
886
|
+
notification
|
|
887
|
+
);
|
|
888
|
+
await mirrorCodexAppServerTerminalAssistantMessage(
|
|
889
|
+
normalizedSessionId,
|
|
890
|
+
normalizedThreadId,
|
|
891
|
+
notification
|
|
892
|
+
);
|
|
893
|
+
}
|
|
894
|
+
await recordCodexAppServerSnapshotCursor(store, normalizedSessionId, {
|
|
895
|
+
itemId: normalizeCodexRunText(items.at(-1)?.id),
|
|
896
|
+
turnId: normalizedTurnId
|
|
897
|
+
});
|
|
898
|
+
} catch (error) {
|
|
899
|
+
await provider.failObservation(error);
|
|
900
|
+
throw error;
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return {
|
|
905
|
+
finalizedTurns: codexAppServerFinalizedTurns,
|
|
906
|
+
finalAssistantResults: codexAppServerFinalAssistantResults,
|
|
907
|
+
reasoningTurns: codexAppServerReasoningTurns,
|
|
908
|
+
reasoningPersistQueues: codexAppServerReasoningPersistQueues,
|
|
909
|
+
liveProgressItems: codexAppServerLiveProgressItems,
|
|
910
|
+
liveProgressFingerprints: codexAppServerLiveProgressFingerprints,
|
|
911
|
+
mirroredTerminalItems: codexAppServerMirroredTerminalItems,
|
|
912
|
+
resultFinalizationKey: codexAppServerResultFinalizationKey,
|
|
913
|
+
liveProgressCandidate: codexAppServerLiveProgressCandidate,
|
|
914
|
+
conversationMessageId: codexAppServerConversationMessageId,
|
|
915
|
+
readAgentRunForSession: readCodexAppServerAgentRunForSession,
|
|
916
|
+
readFinalAssistantResult: readCodexAppServerFinalAssistantResult,
|
|
917
|
+
persistAssistantReply: persistCodexAppServerAssistantReply,
|
|
918
|
+
recordFinalAssistantResult: recordCodexAppServerFinalAssistantResult,
|
|
919
|
+
recordReasoningForSession: recordCodexAppServerReasoningForSession,
|
|
920
|
+
flushReasoningPersist: flushCodexAppServerReasoningPersist,
|
|
921
|
+
splitReasoningTurn: splitCodexAppServerReasoningTurn,
|
|
922
|
+
cleanupUntrackedTurn: cleanupCodexAppServerUntrackedTurn,
|
|
923
|
+
streamMessage: codexAppServerStreamMessage,
|
|
924
|
+
writeStream: writeCodexAppServerStream,
|
|
925
|
+
writeLiveProgress: writeCodexAppServerLiveProgress,
|
|
926
|
+
submitAssistantResult: submitCodexAppServerAssistantResult,
|
|
927
|
+
writeMirroredTerminalMessage: writeMirroredCodexAppServerTerminalMessage,
|
|
928
|
+
mirrorTerminalAssistantMessage: mirrorCodexAppServerTerminalAssistantMessage,
|
|
929
|
+
reconcileObservedTurnItems: reconcileCodexAppServerObservedTurnItems
|
|
930
|
+
};
|
|
931
|
+
}
|