@jskit-ai/assistant-core 0.1.200 → 0.1.201
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,211 @@
|
|
|
1
|
+
import { normalizeCodexRunText, codexAppServerPendingUserMessageOwnership } from "./codexTurnState.js";
|
|
2
|
+
import {
|
|
3
|
+
codexAppServerNotificationItem,
|
|
4
|
+
codexAppServerUserMessageText,
|
|
5
|
+
codexAppServerNotificationTurnId
|
|
6
|
+
} from "./codexEvents.js";
|
|
7
|
+
|
|
8
|
+
// Original request deduplication and authored receipt persistence.
|
|
9
|
+
// One instance and its two Maps belong to the containing retained run owner.
|
|
10
|
+
export function createCodexAppServerMessageDelivery({
|
|
11
|
+
namespace,
|
|
12
|
+
publish,
|
|
13
|
+
messageMetadata,
|
|
14
|
+
createStore,
|
|
15
|
+
turnStateFromAgentRun,
|
|
16
|
+
deliveryStateMetadataKey,
|
|
17
|
+
output,
|
|
18
|
+
journal,
|
|
19
|
+
recovery
|
|
20
|
+
}) {
|
|
21
|
+
const {
|
|
22
|
+
readAgentRunForSession: readCodexAppServerAgentRunForSession,
|
|
23
|
+
writeMirroredTerminalMessage: writeMirroredCodexAppServerTerminalMessage
|
|
24
|
+
} = output;
|
|
25
|
+
const { writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership } = journal;
|
|
26
|
+
const { markProviderTurnActive: markCodexAppServerProviderTurnActive } = recovery;
|
|
27
|
+
const codexAppServerMessageDeliveries = new Map();
|
|
28
|
+
const codexAppServerPendingUserMessages = new Map();
|
|
29
|
+
|
|
30
|
+
async function withCodexAppServerMessageDelivery(sessionId, messageId, pendingMessage, operation) {
|
|
31
|
+
const normalizedMessageId = normalizeCodexRunText(messageId);
|
|
32
|
+
const deliveryKey = normalizedMessageId ? `${namespace(sessionId)}\0${normalizedMessageId}` : "";
|
|
33
|
+
const existing = deliveryKey ? codexAppServerMessageDeliveries.get(deliveryKey) : null;
|
|
34
|
+
if (existing) {
|
|
35
|
+
return existing;
|
|
36
|
+
}
|
|
37
|
+
if (deliveryKey) {
|
|
38
|
+
codexAppServerPendingUserMessages.set(deliveryKey, {
|
|
39
|
+
...pendingMessage,
|
|
40
|
+
receipt: Promise.withResolvers()
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
const delivery = operation();
|
|
44
|
+
if (deliveryKey) {
|
|
45
|
+
codexAppServerMessageDeliveries.set(deliveryKey, delivery);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
return await delivery;
|
|
49
|
+
} finally {
|
|
50
|
+
if (deliveryKey && codexAppServerMessageDeliveries.get(deliveryKey) === delivery) {
|
|
51
|
+
codexAppServerMessageDeliveries.delete(deliveryKey);
|
|
52
|
+
codexAppServerPendingUserMessages.delete(deliveryKey);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function writeCodexAppServerDeliveredUserMessage(
|
|
58
|
+
runtime,
|
|
59
|
+
sessionId = "",
|
|
60
|
+
text = "",
|
|
61
|
+
messageId = "",
|
|
62
|
+
turnMetadata = null,
|
|
63
|
+
attachments = [],
|
|
64
|
+
nativeIdentity = null
|
|
65
|
+
) {
|
|
66
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
67
|
+
const message = normalizeCodexRunText(text);
|
|
68
|
+
if (
|
|
69
|
+
!normalizedSessionId ||
|
|
70
|
+
(!message && !(Array.isArray(attachments) && attachments.length)) ||
|
|
71
|
+
typeof runtime?.store?.writeConversationUserMessage !== "function"
|
|
72
|
+
) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const pendingMessage = codexAppServerPendingUserMessages.get(
|
|
76
|
+
`${namespace(normalizedSessionId)}\0${normalizeCodexRunText(messageId)}`
|
|
77
|
+
);
|
|
78
|
+
if (pendingMessage?.recording) {
|
|
79
|
+
return pendingMessage.recording;
|
|
80
|
+
}
|
|
81
|
+
const recording = (async () => {
|
|
82
|
+
const written = await runtime.store.writeConversationUserMessage(normalizedSessionId, {
|
|
83
|
+
attachments,
|
|
84
|
+
nativeIdentity,
|
|
85
|
+
messageId: normalizeCodexRunText(messageId),
|
|
86
|
+
text: message,
|
|
87
|
+
turnMetadata: messageMetadata.delivered
|
|
88
|
+
? await messageMetadata.delivered(runtime.store, normalizedSessionId, turnMetadata)
|
|
89
|
+
: turnMetadata
|
|
90
|
+
});
|
|
91
|
+
if (!written) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
await publish(normalizedSessionId, {
|
|
95
|
+
payload: {
|
|
96
|
+
conversationLogPatch: {
|
|
97
|
+
turn: written,
|
|
98
|
+
type: "upsert-turn"
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
reason: "codex-app-server-message-delivered"
|
|
102
|
+
});
|
|
103
|
+
return written;
|
|
104
|
+
})();
|
|
105
|
+
if (pendingMessage) {
|
|
106
|
+
pendingMessage.recording = recording;
|
|
107
|
+
}
|
|
108
|
+
return recording;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function mirrorCodexAppServerTerminalUserMessage(sessionId = "", threadId = "", notification = {}) {
|
|
112
|
+
const normalizedSessionId = normalizeCodexRunText(sessionId);
|
|
113
|
+
const normalizedThreadId = normalizeCodexRunText(threadId);
|
|
114
|
+
const item = codexAppServerNotificationItem(notification);
|
|
115
|
+
const text = codexAppServerUserMessageText(item);
|
|
116
|
+
const clientId = normalizeCodexRunText(item?.clientId);
|
|
117
|
+
if (!normalizedSessionId || (!text && (!clientId || normalizeCodexRunText(item?.type) !== "userMessage"))) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const store = await createStore(normalizedSessionId);
|
|
121
|
+
const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
|
|
122
|
+
let ownership = codexAppServerPendingUserMessageOwnership(run, clientId);
|
|
123
|
+
if (!ownership && clientId && await store.conversationMessageIdExists(normalizedSessionId, clientId)) return;
|
|
124
|
+
const receiptId = ownership?.clientId || clientId;
|
|
125
|
+
const pendingMessage = codexAppServerPendingUserMessages.get(
|
|
126
|
+
`${namespace(normalizedSessionId)}\0${receiptId}`
|
|
127
|
+
);
|
|
128
|
+
let recoveredMessage = null;
|
|
129
|
+
if (receiptId && !pendingMessage) {
|
|
130
|
+
const saved = JSON.parse(await store.readMetadataValue(normalizedSessionId, deliveryStateMetadataKey) || "null");
|
|
131
|
+
const pending = Object.values(saved?.engines || {}).map((binding) => binding.pending)
|
|
132
|
+
.find((candidate) => candidate?.messageId === receiptId && candidate.threadId === normalizedThreadId);
|
|
133
|
+
if (pending) {
|
|
134
|
+
recoveredMessage = pending;
|
|
135
|
+
ownership ||= { clientId: receiptId, inputSource: "chat" };
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
if (!text && (!ownership || !(
|
|
139
|
+
Array.isArray(pendingMessage?.attachments) && pendingMessage.attachments.length ||
|
|
140
|
+
Array.isArray(recoveredMessage?.displayAttachments) && recoveredMessage.displayAttachments.length
|
|
141
|
+
))) return;
|
|
142
|
+
if (ownership) {
|
|
143
|
+
// The provider's user-message receipt precedes its answer notifications.
|
|
144
|
+
// Persist the authored message here so answers cannot overtake it while
|
|
145
|
+
// the original HTTP request is still finishing startup bookkeeping.
|
|
146
|
+
if (pendingMessage) {
|
|
147
|
+
await writeCodexAppServerDeliveredUserMessage(
|
|
148
|
+
{ store },
|
|
149
|
+
normalizedSessionId,
|
|
150
|
+
pendingMessage.text,
|
|
151
|
+
ownership.clientId,
|
|
152
|
+
{ ...await messageMetadata.actor?.(pendingMessage.actorContext), ...pendingMessage.turnMetadata },
|
|
153
|
+
pendingMessage.attachments,
|
|
154
|
+
{ threadId: normalizedThreadId, turnId: codexAppServerNotificationTurnId(notification) }
|
|
155
|
+
);
|
|
156
|
+
} else if (recoveredMessage) {
|
|
157
|
+
await writeCodexAppServerDeliveredUserMessage(
|
|
158
|
+
{ store }, normalizedSessionId, recoveredMessage.displayMessage, receiptId,
|
|
159
|
+
recoveredMessage.turnMetadata, recoveredMessage.displayAttachments,
|
|
160
|
+
{ threadId: normalizedThreadId, turnId: codexAppServerNotificationTurnId(notification) }
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
const turn = turnStateFromAgentRun(run || {});
|
|
164
|
+
const providerTurnId = codexAppServerNotificationTurnId(notification);
|
|
165
|
+
const providerTurnAlreadyTracked = normalizeCodexRunText(turn.state) === "active" &&
|
|
166
|
+
(!normalizedThreadId || normalizeCodexRunText(turn.threadId) === normalizedThreadId) &&
|
|
167
|
+
(!providerTurnId || normalizeCodexRunText(turn.turnId) === providerTurnId);
|
|
168
|
+
if (!providerTurnAlreadyTracked) {
|
|
169
|
+
await markCodexAppServerProviderTurnActive(normalizedSessionId, {
|
|
170
|
+
inputSource: ownership.inputSource,
|
|
171
|
+
status: "inProgress",
|
|
172
|
+
threadId: normalizedThreadId,
|
|
173
|
+
turnId: providerTurnId
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
await writeCodexAppServerUserMessageOwnership(
|
|
177
|
+
store,
|
|
178
|
+
normalizedSessionId,
|
|
179
|
+
ownership.clientId,
|
|
180
|
+
{
|
|
181
|
+
eventKind: "codex-app-server-user-message-consumed",
|
|
182
|
+
owned: false
|
|
183
|
+
}
|
|
184
|
+
);
|
|
185
|
+
if (providerTurnId) {
|
|
186
|
+
pendingMessage?.receipt.resolve({ id: providerTurnId });
|
|
187
|
+
}
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
await markCodexAppServerProviderTurnActive(normalizedSessionId, {
|
|
191
|
+
status: "inProgress",
|
|
192
|
+
threadId: normalizedThreadId,
|
|
193
|
+
turnId: codexAppServerNotificationTurnId(notification)
|
|
194
|
+
});
|
|
195
|
+
await writeMirroredCodexAppServerTerminalMessage({
|
|
196
|
+
notification,
|
|
197
|
+
role: "user",
|
|
198
|
+
sessionId: normalizedSessionId,
|
|
199
|
+
text,
|
|
200
|
+
threadId: normalizedThreadId
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
messageDeliveries: codexAppServerMessageDeliveries,
|
|
206
|
+
pendingUserMessages: codexAppServerPendingUserMessages,
|
|
207
|
+
withMessageDelivery: withCodexAppServerMessageDelivery,
|
|
208
|
+
writeDeliveredUserMessage: writeCodexAppServerDeliveredUserMessage,
|
|
209
|
+
mirrorTerminalUserMessage: mirrorCodexAppServerTerminalUserMessage
|
|
210
|
+
};
|
|
211
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codexAppServerAssistantItemText,
|
|
3
|
+
codexAppServerUserMessageText
|
|
4
|
+
} from "./codexEvents.js";
|
|
5
|
+
import { canonicalNativeHistoryJson as canonicalJson, createNativeHistoryExport } from "./nativeHistoryExport.js";
|
|
6
|
+
|
|
7
|
+
function threadHistoryMetadata(thread) {
|
|
8
|
+
const stored = { ...thread };
|
|
9
|
+
// These describe a currently loaded process, not persisted conversation
|
|
10
|
+
// content. Native deletion can unload a thread before refusing an external
|
|
11
|
+
// fork, so they must not invalidate an otherwise identical preservation.
|
|
12
|
+
delete stored.status;
|
|
13
|
+
delete stored.canAcceptDirectInput;
|
|
14
|
+
delete stored.environments;
|
|
15
|
+
return stored;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The caller owns a bounded, read-only connection. This exports API-visible
|
|
19
|
+
// content, not an importable database or a native-resume snapshot.
|
|
20
|
+
export async function exportCodexNativeHistory(client, threadId, onRecord, {
|
|
21
|
+
signal, maxPages = 20_000, maxBytes = 2 * 1024 ** 3, errorPrefix = ""
|
|
22
|
+
} = {}) {
|
|
23
|
+
if (!threadId || typeof onRecord !== "function") throw new TypeError("Native export requires a thread and record callback.");
|
|
24
|
+
const output = createNativeHistoryExport(onRecord, { signal, maxBytes });
|
|
25
|
+
const emit = output.emit;
|
|
26
|
+
let turnCount = 0;
|
|
27
|
+
let itemCount = 0;
|
|
28
|
+
let pages = 0;
|
|
29
|
+
const request = (method, params = {}) => {
|
|
30
|
+
signal?.throwIfAborted();
|
|
31
|
+
return client.request(method, { threadId, ...params }, { signal });
|
|
32
|
+
};
|
|
33
|
+
const { thread } = await request("thread/read", { includeTurns: false });
|
|
34
|
+
if (thread?.id !== threadId || !["idle", "notLoaded"].includes(thread.status?.type)) {
|
|
35
|
+
throw new Error("Codex native export requires the exact idle thread.");
|
|
36
|
+
}
|
|
37
|
+
if (thread.historyMode !== "paginated") {
|
|
38
|
+
throw Object.assign(new Error("Codex native retirement requires paginated history. This unsupported conversation needs operator cleanup."),
|
|
39
|
+
{ code: `${errorPrefix}codex_paginated_history_required` });
|
|
40
|
+
}
|
|
41
|
+
const goalResult = await request("thread/goal/get");
|
|
42
|
+
if (!Object.hasOwn(goalResult || {}, "goal")) throw new Error("Codex did not return its native goal state.");
|
|
43
|
+
const { goal } = goalResult;
|
|
44
|
+
if (goal?.status === "active") throw new Error("Pause the Codex goal before native history retirement.");
|
|
45
|
+
const metadata = threadHistoryMetadata(thread);
|
|
46
|
+
await emit({ type: "thread", thread: metadata, text: [] });
|
|
47
|
+
await emit({ type: "goal", goal: goal ?? null,
|
|
48
|
+
text: typeof goal?.objective === "string" ? [{ role: "goal", text: goal.objective, branchId: threadId,
|
|
49
|
+
...(Number.isFinite(goal.createdAt) ? { createdAt: new Date(goal.createdAt * 1000).toISOString() } : {}),
|
|
50
|
+
...(Number.isFinite(goal.updatedAt) ? { updatedAt: new Date(goal.updatedAt * 1000).toISOString() } : {}) }] : [] });
|
|
51
|
+
|
|
52
|
+
const pageAll = async (method, params, consume) => {
|
|
53
|
+
let cursor;
|
|
54
|
+
const cursors = new Set();
|
|
55
|
+
do {
|
|
56
|
+
if (++pages > maxPages) throw new Error("Codex native export exceeded its page limit; history was not retired.");
|
|
57
|
+
const page = await request(method, { ...params, limit: 100, sortDirection: "asc", ...(cursor ? { cursor } : {}) });
|
|
58
|
+
if (!Array.isArray(page?.data) || page.data.length > 100 ||
|
|
59
|
+
(page.nextCursor != null && (typeof page.nextCursor !== "string" || !page.nextCursor))) {
|
|
60
|
+
throw new Error("Codex returned an invalid native history page.");
|
|
61
|
+
}
|
|
62
|
+
for (const entry of page.data) await consume(entry);
|
|
63
|
+
cursor = page.nextCursor;
|
|
64
|
+
if (cursor && cursors.has(cursor)) throw new Error("Codex repeated a native history cursor.");
|
|
65
|
+
if (cursor) cursors.add(cursor);
|
|
66
|
+
} while (cursor);
|
|
67
|
+
};
|
|
68
|
+
await pageAll("thread/turns/list", { itemsView: "notLoaded" }, async (turn) => {
|
|
69
|
+
if (!turn?.id || turn.itemsView !== "notLoaded" || !Array.isArray(turn.items) || turn.items.length || turn.status === "inProgress") {
|
|
70
|
+
throw new Error("Codex returned an invalid or active native turn.");
|
|
71
|
+
}
|
|
72
|
+
turnCount++;
|
|
73
|
+
await emit({ type: "turn", turn, text: [] });
|
|
74
|
+
});
|
|
75
|
+
await pageAll("thread/items/list", {}, async ({ turnId, item } = {}) => {
|
|
76
|
+
if (!turnId || !item?.id || typeof item.type !== "string") throw new Error("Codex returned an invalid native item.");
|
|
77
|
+
itemCount++;
|
|
78
|
+
const user = codexAppServerUserMessageText(item);
|
|
79
|
+
const assistant = codexAppServerAssistantItemText(item);
|
|
80
|
+
await emit({ type: "item", turnId, item,
|
|
81
|
+
text: user || assistant ? [{ role: user ? "user" : "assistant", text: user || assistant,
|
|
82
|
+
branchId: threadId, turnId, messageId: item.id,
|
|
83
|
+
...(item.clientId ? { clientId: item.clientId } : {}),
|
|
84
|
+
...(item.phase ? { phase: item.phase } : {}) }] : [] });
|
|
85
|
+
});
|
|
86
|
+
const after = await request("thread/read", { includeTurns: false });
|
|
87
|
+
const afterGoal = await request("thread/goal/get");
|
|
88
|
+
if (!["idle", "notLoaded"].includes(after.thread?.status?.type) ||
|
|
89
|
+
canonicalJson(threadHistoryMetadata(after.thread)) !== canonicalJson(metadata) ||
|
|
90
|
+
canonicalJson(afterGoal.goal ?? null) !== canonicalJson(goal ?? null)) {
|
|
91
|
+
throw new Error("Codex native history changed during export; preserve it again before retirement.");
|
|
92
|
+
}
|
|
93
|
+
signal?.throwIfAborted();
|
|
94
|
+
return output.complete({ historyMode: "paginated", turnCount, itemCount });
|
|
95
|
+
}
|