@jskit-ai/assistant-core 0.1.199 → 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.
Files changed (164) hide show
  1. package/examples/conversation/README.md +8 -1
  2. package/examples/conversation/package.json +5 -5
  3. package/fixtures/responsive-assistant/App.vue +4 -1
  4. package/package.json +99 -36
  5. package/src/client/conversation/AssistantConversationElement.vue +27 -4
  6. package/src/client/conversation/AssistantConversationStatus.vue +24 -0
  7. package/src/client/conversation/AssistantGoalControl.vue +8 -5
  8. package/src/client/conversation/AssistantTranscript.vue +10 -3
  9. package/src/client/conversation/index.js +2 -0
  10. package/src/client/conversation/messageDelivery.js +115 -32
  11. package/src/client/conversation/retainedConversation.js +32 -0
  12. package/src/client/conversation/useAssistantQuestions.js +76 -0
  13. package/src/client/lib/assistantApi.js +72 -2
  14. package/src/server/conversation/agentRun.js +222 -0
  15. package/src/server/conversation/attachments.js +93 -0
  16. package/src/server/conversation/claudeClient.js +221 -0
  17. package/src/server/conversation/claudeHistory.js +256 -0
  18. package/src/server/conversation/claudeProcess.js +327 -0
  19. package/src/server/conversation/claudeTools.js +42 -0
  20. package/src/server/conversation/claudeTurn.js +1082 -0
  21. package/src/server/conversation/codexConfiguration.js +1183 -0
  22. package/src/server/conversation/codexConversations.js +1082 -0
  23. package/src/server/conversation/codexDetachedTurn.js +332 -0
  24. package/src/server/conversation/codexEvents.js +194 -1
  25. package/src/server/conversation/codexGoalCommands.js +124 -0
  26. package/src/server/conversation/codexHelperExecution.js +306 -0
  27. package/src/server/conversation/codexHelperThreadLedger.js +804 -0
  28. package/src/server/conversation/codexHelperThreadRecovery.js +395 -0
  29. package/src/server/conversation/codexHelperThreads.js +638 -0
  30. package/src/server/conversation/codexHistoryAdapter.js +369 -0
  31. package/src/server/conversation/codexMessageCommands.js +663 -0
  32. package/src/server/conversation/codexMessageDelivery.js +211 -0
  33. package/src/server/conversation/codexNativeHistoryExport.js +95 -0
  34. package/src/server/conversation/codexNotificationObserver.js +482 -0
  35. package/src/server/conversation/codexNotificationQueue.js +103 -0
  36. package/src/server/conversation/codexObservationControl.js +174 -0
  37. package/src/server/conversation/codexOutput.js +931 -0
  38. package/src/server/conversation/codexProcess.js +1559 -0
  39. package/src/server/conversation/codexProcessLeader.js +14 -0
  40. package/src/server/conversation/codexProvider.js +2710 -0
  41. package/src/server/conversation/codexProviderOwner.js +1058 -0
  42. package/src/server/conversation/codexProviderSelection.js +218 -0
  43. package/src/server/conversation/codexReasoning.js +314 -0
  44. package/src/server/conversation/codexRenewalCommands.js +290 -0
  45. package/src/server/conversation/codexResultSettlement.js +409 -0
  46. package/src/server/conversation/codexRunJournal.js +658 -0
  47. package/src/server/conversation/codexRuntimeLifecycle.js +288 -0
  48. package/src/server/conversation/codexSessionCleanup.js +96 -0
  49. package/src/server/conversation/codexSessionReconciliation.js +284 -0
  50. package/src/server/conversation/codexThreadReadiness.js +397 -0
  51. package/src/server/conversation/codexThreadStatus.js +354 -0
  52. package/src/server/conversation/codexTurn.js +701 -255
  53. package/src/server/conversation/codexTurnControl.js +317 -0
  54. package/src/server/conversation/codexTurnRecovery.js +990 -0
  55. package/src/server/conversation/codexTurnState.js +549 -0
  56. package/src/server/conversation/commandHook.js +18 -0
  57. package/src/server/conversation/commandWrapper.js +37 -0
  58. package/src/server/conversation/configuration.js +43 -0
  59. package/src/server/conversation/continuity.js +289 -0
  60. package/src/server/conversation/fileStorage.js +55 -0
  61. package/src/server/conversation/hookBridge.js +19 -0
  62. package/src/server/conversation/index.js +13 -0
  63. package/src/server/conversation/localExecution.js +665 -0
  64. package/src/server/conversation/memoryStorage.js +7 -72
  65. package/src/server/conversation/nativeHistoryExport.js +90 -0
  66. package/src/server/conversation/nativeStorage.js +23 -0
  67. package/src/server/conversation/openCodeClient.js +209 -19
  68. package/src/server/conversation/openCodePlugin.js +84 -0
  69. package/src/server/conversation/openCodeProcess.js +476 -0
  70. package/src/server/conversation/openCodeRuntime.js +1578 -0
  71. package/src/server/conversation/openCodeTools.js +60 -0
  72. package/src/server/conversation/openCodeTurn.js +557 -0
  73. package/src/server/conversation/provider.js +30 -0
  74. package/src/server/conversation/providers/api.js +136 -0
  75. package/src/server/conversation/providers/claude.js +63 -0
  76. package/src/server/conversation/providers/claudeDriver.js +425 -0
  77. package/src/server/conversation/providers/codex.js +248 -0
  78. package/src/server/conversation/providers/codexDriver.js +1078 -0
  79. package/src/server/conversation/providers/opencode.js +49 -0
  80. package/src/server/conversation/providers/opencodeDriver.js +650 -0
  81. package/src/server/conversation/providers/scoped.js +312 -0
  82. package/src/server/conversation/runtime.js +2220 -0
  83. package/src/server/conversation/runtimeStateUpgrade.js +125 -0
  84. package/src/server/conversation/storage.js +184 -0
  85. package/src/server/conversation/streams.js +7 -2
  86. package/src/server/conversation/structuredOutput.js +240 -0
  87. package/src/server/conversation/systemPrompt.js +54 -0
  88. package/src/server/conversation/tools.js +84 -0
  89. package/src/server/conversation/transcript.js +5 -4
  90. package/src/server/hosts/claudeHistory.js +5 -0
  91. package/src/server/hosts/claudeProcess.js +11 -0
  92. package/src/server/hosts/claudeTurn.js +4 -0
  93. package/src/server/hosts/codexConfiguration.js +20 -0
  94. package/src/server/hosts/codexEvents.js +13 -0
  95. package/src/server/hosts/codexProcess.js +12 -0
  96. package/src/server/hosts/codexProvider.js +23 -0
  97. package/src/server/hosts/codexTurn.js +20 -0
  98. package/src/server/hosts/nativeHistory.js +3 -0
  99. package/src/server/hosts/openCodeClient.js +3 -0
  100. package/src/server/hosts/openCodeProcess.js +23 -0
  101. package/src/server/hosts/openCodeTurn.js +7 -0
  102. package/src/server/index.js +2 -0
  103. package/src/server/lib/aiConnectionClient.js +13 -6
  104. package/src/server/lib/assistantCompletion.js +190 -0
  105. package/src/server/lib/assistantToolLoop.js +314 -0
  106. package/src/server/lib/serviceToolCatalog.js +25 -10
  107. package/src/shared/assistantResponseText.js +29 -0
  108. package/src/shared/conversation/attachments.js +19 -0
  109. package/src/shared/conversation/index.js +3 -0
  110. package/src/shared/conversation/pagination.js +78 -0
  111. package/src/shared/conversation/patches.js +150 -0
  112. package/src/shared/conversation/questions.js +48 -0
  113. package/src/shared/conversation/streaming.js +7 -4
  114. package/src/shared/index.js +2 -0
  115. package/src/shared/nativeProviders.js +45 -0
  116. package/src/testing/conversationStorageContract.js +31 -1
  117. package/src/testing/nativeCodex.js +11 -0
  118. package/src/testing/nativeOpenCode.js +3 -0
  119. package/test/aiConnectionClient.test.js +2 -0
  120. package/test/apiConversationDriver.test.js +73 -0
  121. package/test/assistantApiSurfaceHeader.test.js +85 -0
  122. package/test/assistantBoundedToolLoop.test.js +285 -0
  123. package/test/assistantScroll.browser.test.js +16 -0
  124. package/test/claudeClient.test.js +189 -0
  125. package/test/claudeConversationSession.test.js +1173 -0
  126. package/test/claudeInstructionResume.test.js +82 -0
  127. package/test/claudeProcess.test.js +204 -0
  128. package/test/claudeTurn.test.js +253 -0
  129. package/test/codexConversationSession.test.js +2122 -0
  130. package/test/codexGoal.test.js +37 -0
  131. package/test/codexHelperDeletion.test.js +163 -0
  132. package/test/codexHelperThreadLedger.test.js +494 -0
  133. package/test/codexHistoryAdapter.test.js +541 -0
  134. package/test/codexInteractiveArguments.test.js +30 -0
  135. package/test/codexIsolation.test.js +756 -0
  136. package/test/codexModelCatalog.test.js +362 -0
  137. package/test/codexNativeHistoryExport.test.js +81 -0
  138. package/test/codexThreadSelection.test.js +136 -0
  139. package/test/codexTurn.test.js +2158 -0
  140. package/test/conversation.test.js +110 -0
  141. package/test/conversationAgentRun.test.js +326 -0
  142. package/test/conversationApiFacilities.test.js +90 -0
  143. package/test/conversationAttachments.test.js +238 -0
  144. package/test/conversationCommandWrapper.test.js +48 -0
  145. package/test/conversationConfiguration.test.js +174 -0
  146. package/test/conversationNativeStorage.test.js +25 -0
  147. package/test/conversationReplacement.test.js +172 -0
  148. package/test/conversationRuntime.test.js +241 -0
  149. package/test/conversationRuntimeStateUpgrade.test.js +144 -0
  150. package/test/conversationSession.test.js +1080 -0
  151. package/test/conversationStreaming.test.js +68 -0
  152. package/test/conversationSystemPrompt.test.js +84 -0
  153. package/test/conversationTools.test.js +416 -0
  154. package/test/fileConversationStorage.test.js +73 -0
  155. package/test/fixtures/codexHelperThreadLedger.js +39 -0
  156. package/test/localConversationExecution.test.js +90 -0
  157. package/test/messageDelivery.test.js +234 -1
  158. package/test/nativeConversationRetirement.test.js +215 -0
  159. package/test/openCodeClient.test.js +515 -0
  160. package/test/openCodeProcess.test.js +177 -0
  161. package/test/openCodeRuntime.test.js +322 -0
  162. package/test/openCodeTools.test.js +287 -0
  163. package/test/opencodeConversationSession.test.js +789 -0
  164. package/test/serviceToolCatalog.test.js +24 -0
@@ -0,0 +1,218 @@
1
+ import { normalizeCodexRunText } from "./codexTurnState.js";
2
+ import { codexAppServerProjectHookTrustConfig } from "./codexConfiguration.js";
3
+ import { createCodexAppServerModelCatalogCache } from "./codexProvider.js";
4
+ import { readCodexAppServerAccountIdentity } from "./codexHelperExecution.js";
5
+
6
+ // Private composition of unchanged operations from the original run owner.
7
+ export function createCodexProviderSelection({
8
+ providerSessions,
9
+ turnState,
10
+ helperLifecycle,
11
+ helperOwnershipError,
12
+ modelCatalogCacheMs,
13
+ errorPrefix
14
+ }) {
15
+ const readCodexAppServerModelCatalog = createCodexAppServerModelCatalogCache({
16
+ cacheMs: modelCatalogCacheMs,
17
+ errorPrefix
18
+ });
19
+ function managedProviderKeyForThread(sessionId, workdir, threadId) {
20
+ for (const [providerKey, managed] of providerSessions.managed) {
21
+ const provider = providerSessions.owner.providers.get(providerKey);
22
+ if (
23
+ managed.sessionId !== sessionId ||
24
+ managed.workdir !== workdir ||
25
+ managed.threadId !== threadId ||
26
+ provider?.isAvailable?.() !== true
27
+ ) {
28
+ continue;
29
+ }
30
+ return providerKey;
31
+ }
32
+ return null;
33
+ }
34
+
35
+ async function ensureCodexAppServerProviderForManagedThread(session = {}, options = {}) {
36
+ const normalizedSessionId = normalizeCodexRunText(session.sessionId || session.id);
37
+ const turn = turnState(session);
38
+ if (!normalizedSessionId || !turn.threadId) {
39
+ return null;
40
+ }
41
+ const normalizedExecutionRoot = normalizeCodexRunText(options.executionRoot);
42
+ const normalizedWorkdir = normalizeCodexRunText(options.workdir);
43
+ for (const [providerKey, managed] of providerSessions.managed.entries()) {
44
+ const fields = providerSessions.keyFields(providerKey);
45
+ if (
46
+ fields.sessionId !== normalizedSessionId ||
47
+ (normalizedExecutionRoot && fields.executionRoot !== normalizedExecutionRoot) ||
48
+ (normalizedWorkdir && fields.workdir !== normalizedWorkdir) ||
49
+ normalizeCodexRunText(managed?.sessionId) !== normalizedSessionId ||
50
+ normalizeCodexRunText(managed?.threadId) !== turn.threadId
51
+ ) {
52
+ continue;
53
+ }
54
+ const provider = providerSessions.owner.providers.get(providerKey);
55
+ if (provider) {
56
+ return {
57
+ provider: await providerSessions.owner.ensureSession(
58
+ providerSessions.context(normalizedSessionId, managed.providerOptions)
59
+ ),
60
+ providerKey,
61
+ providerOptions: managed.providerOptions
62
+ };
63
+ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ async function ensureCodexAppServerProviderForActiveTurn(session = {}, options = {}) {
69
+ if (!turnState(session).active) {
70
+ return null;
71
+ }
72
+ return ensureCodexAppServerProviderForManagedThread(session, options);
73
+ }
74
+
75
+ async function acquireCodexAppServerOutputProvider(sessionId, session, managedIdentity, prepareProviderOptions) {
76
+ const activeProvider = await ensureCodexAppServerProviderForActiveTurn(session, managedIdentity);
77
+ const provider = activeProvider?.provider;
78
+ if (provider) return provider;
79
+ const providerOptions = await prepareProviderOptions();
80
+ return await providerSessions.owner.ensureSession(
81
+ providerSessions.context(normalizeCodexRunText(sessionId), providerOptions)
82
+ );
83
+ }
84
+
85
+ async function codexAppServerRuntimeForVisibleTerminal(sessionId, session, options, managedIdentity, prepareProviderOptions) {
86
+ const activeProvider = await ensureCodexAppServerProviderForActiveTurn(session, managedIdentity);
87
+ const providerOptions = activeProvider?.providerOptions || await prepareProviderOptions();
88
+ const provider = activeProvider?.provider || await providerSessions.owner.ensureSession(
89
+ providerSessions.context(normalizeCodexRunText(sessionId), providerOptions)
90
+ );
91
+ await codexAppServerProjectHookTrustConfig(provider, options.workdir, {
92
+ persist: true
93
+ });
94
+ const providerKey = activeProvider?.providerKey || providerSessions.context(sessionId, providerOptions).providerKey;
95
+ return providerSessions.owner.acquireRuntime({
96
+ operation: () => provider.ensureRuntime(),
97
+ provider,
98
+ providerKey,
99
+ providerOptions
100
+ });
101
+ }
102
+
103
+ function codexAppServerMessageContext(sessionId, context) {
104
+ const { runtime, session } = context;
105
+ return {
106
+ runtime,
107
+ session,
108
+ threadId: context.threadId,
109
+ async acquireProvider(currentSession) {
110
+ const activeProvider = await ensureCodexAppServerProviderForManagedThread(
111
+ currentSession, context.managedIdentity(currentSession)
112
+ );
113
+ if (activeProvider) return { provider: activeProvider.provider || null, reused: true };
114
+ return {
115
+ provider: await providerSessions.owner.ensureSession(providerSessions.context(
116
+ normalizeCodexRunText(sessionId), await context.providerOptions(currentSession)
117
+ )),
118
+ reused: false
119
+ };
120
+ }
121
+ };
122
+ }
123
+
124
+ function codexAppServerControlContext(sessionId, context) {
125
+ return {
126
+ runtime: context.runtime,
127
+ admissionError: context.admissionError,
128
+ threadId: context.threadId,
129
+ async acquireProvider(currentSession) {
130
+ const activeProvider = await ensureCodexAppServerProviderForActiveTurn(
131
+ currentSession, context.managedIdentity(currentSession)
132
+ );
133
+ return activeProvider?.provider || await providerSessions.owner.ensureSession(
134
+ providerSessions.context(normalizeCodexRunText(sessionId), await context.providerOptions(currentSession))
135
+ );
136
+ },
137
+ observationOwner(threadId) {
138
+ for (const [key, managed] of providerSessions.managed) {
139
+ const owner = providerSessions.owner.providers.get(key);
140
+ if (managed.sessionId === sessionId && managed.threadId === threadId && owner?.observationFailure) {
141
+ return owner;
142
+ }
143
+ }
144
+ return null;
145
+ }
146
+ };
147
+ }
148
+
149
+ function codexAppServerObservationRecoveryContext(sessionId, context) {
150
+ return {
151
+ threadId: context.threadId,
152
+ async acquireProvider(current) {
153
+ return providerSessions.owner.ensureSession(providerSessions.context(
154
+ normalizeCodexRunText(sessionId), await context.providerOptions(current)
155
+ ));
156
+ }
157
+ };
158
+ }
159
+
160
+ function codexAppServerSessionObserverOptions(sessionId, providerOptions) {
161
+ return {
162
+ get providerKey() {
163
+ return providerSessions.context(normalizeCodexRunText(sessionId), providerOptions).providerKey;
164
+ }
165
+ };
166
+ }
167
+
168
+ function rememberCodexAppServerPreparedThread(sessionId, providerOptions, context, thread) {
169
+ const connection = providerSessions.context(sessionId, providerOptions);
170
+ connection.runOwner.runtimeLifecycle.rememberManagedSession(connection.providerKey, {
171
+ providerOptions,
172
+ sessionId,
173
+ executionRoot: context.executionRoot,
174
+ threadId: thread.threadId,
175
+ workdir: context.workdir
176
+ });
177
+ }
178
+
179
+
180
+ async function readExecutionProfileModelCatalog(sessionId, preparation) {
181
+ if (!preparation.assistantScope) {
182
+ const restoration = await preparation.helperRestoration;
183
+ helperLifecycle.assertRestored(await helperLifecycle.restoreThreads(
184
+ restoration.projectRuntimeRoot, restoration.context
185
+ ));
186
+ }
187
+ const provider = preparation.provider || await providerSessions.owner.ensureSession(
188
+ providerSessions.context(normalizeCodexRunText(sessionId), await preparation.providerOptions)
189
+ );
190
+ return readCodexAppServerModelCatalog(provider, { signal: preparation.signal });
191
+ }
192
+
193
+ async function readProviderAccountIdentity(sessionId, preparation) {
194
+ const restoration = await preparation.helperRestoration;
195
+ helperLifecycle.assertRestored(await helperLifecycle.restoreThreads(
196
+ restoration.projectRuntimeRoot, restoration.context
197
+ ));
198
+ const providerOptions = await preparation.providerOptions;
199
+ const provider = await providerSessions.owner.withLifecycle(() =>
200
+ providerSessions.owner.providerForSession(providerSessions.context(sessionId, providerOptions))
201
+ );
202
+ return readCodexAppServerAccountIdentity(provider, helperOwnershipError);
203
+ }
204
+
205
+ return {
206
+ readExecutionProfileModelCatalog,
207
+ readProviderAccountIdentity,
208
+ managedProviderKeyForThread,
209
+ ensureCodexAppServerProviderForActiveTurn,
210
+ acquireCodexAppServerOutputProvider,
211
+ codexAppServerRuntimeForVisibleTerminal,
212
+ codexAppServerMessageContext,
213
+ codexAppServerControlContext,
214
+ codexAppServerObservationRecoveryContext,
215
+ codexAppServerSessionObserverOptions,
216
+ rememberCodexAppServerPreparedThread
217
+ };
218
+ }
@@ -0,0 +1,314 @@
1
+ import { normalizeCodexRunText, codexAppServerTurnCanReceiveProviderActivity, codexAppServerTurnKey } from "./codexTurnState.js";
2
+ import { codexAppServerContentText, codexAppServerNotificationItem, codexAppServerOutputOwnerTurnId, codexAppServerNotificationParams, codexAppServerNotificationTurnId } from "./codexEvents.js";
3
+
4
+ // Original per-turn reasoning channels, persistence queue and cleanup.
5
+ // The containing output instance shares these exact Maps with its run owner.
6
+ export function createCodexAppServerReasoning({
7
+ createStore,
8
+ publish,
9
+ turnStateFromAgentRun,
10
+ debugLog,
11
+ readCodexAppServerAgentRunForSession
12
+ }) {
13
+ const codexAppServerReasoningTurns = new Map();
14
+ const codexAppServerReasoningPersistQueues = new Map();
15
+
16
+ function codexAppServerReasoningTurnKey(threadId = "", turnId = "") {
17
+ return codexAppServerTurnKey(threadId, turnId || "*");
18
+ }
19
+
20
+ function codexAppServerReasoningTurnState(threadId = "", turnId = "") {
21
+ const key = codexAppServerReasoningTurnKey(threadId, turnId);
22
+ const existing = codexAppServerReasoningTurns.get(key);
23
+ if (existing) {
24
+ return existing;
25
+ }
26
+ const created = {
27
+ createdAt: new Date().toISOString(),
28
+ segments: [],
29
+ channels: new Map(),
30
+ summaries: new Map()
31
+ };
32
+ codexAppServerReasoningTurns.set(key, created);
33
+ return created;
34
+ }
35
+
36
+ function codexAppServerReasoningExistingTurnState(threadId = "", turnId = "") {
37
+ return codexAppServerReasoningTurns.get(codexAppServerReasoningTurnKey(threadId, turnId)) ||
38
+ codexAppServerReasoningTurns.get(codexAppServerReasoningTurnKey(threadId, "*"));
39
+ }
40
+
41
+ function codexAppServerReasoningSummaryKey(notification = {}) {
42
+ const params = codexAppServerNotificationParams(notification);
43
+ const item = codexAppServerNotificationItem(notification);
44
+ const itemId = normalizeCodexRunText(params.itemId || item?.id || "summary");
45
+ const summaryIndex = String(params.summaryIndex ?? params.contentIndex ?? params.index ?? 0).trim() || "0";
46
+ return `${itemId}:${summaryIndex}`;
47
+ }
48
+
49
+ function createCodexAppServerReasoningSegment(state = {}, summary = null, summaryKey = "") {
50
+ const segment = {
51
+ chunks: [],
52
+ persistedAt: "",
53
+ persistedText: "",
54
+ summaryKey
55
+ };
56
+ if (Array.isArray(state.segments)) {
57
+ state.segments.push(segment);
58
+ }
59
+ if (summary) {
60
+ summary.currentSegment = segment;
61
+ }
62
+ return segment;
63
+ }
64
+
65
+ function codexAppServerReasoningSummaryDisplayText(value = "") {
66
+ let text = normalizeCodexRunText(value).replace(/\r\n/gu, "\n");
67
+ if (!text) {
68
+ return "";
69
+ }
70
+ text = text.replace(/\*\*([^*\n][\s\S]*?)\*\*/gu, "$1");
71
+ text = text.replace(/^\*\*\s*/u, "").replace(/\s*\*\*$/u, "");
72
+ return text
73
+ .split("\n")
74
+ .map((line) => line.trim())
75
+ .filter(Boolean)
76
+ .join(" ")
77
+ .replace(/\s+/gu, " ")
78
+ .trim();
79
+ }
80
+
81
+ function codexAppServerReasoningDisplayText(value = "") {
82
+ return normalizeCodexRunText(value)
83
+ .replace(/\r\n/gu, "\n")
84
+ .split(/\n{2,}/u)
85
+ .map(codexAppServerReasoningSummaryDisplayText)
86
+ .filter(Boolean)
87
+ .join("\n\n")
88
+ .trim();
89
+ }
90
+
91
+ function recordCodexAppServerReasoningNotification(threadId = "", notification = {}, {
92
+ turnId = ""
93
+ } = {}) {
94
+ const method = normalizeCodexRunText(notification.method);
95
+ const item = codexAppServerNotificationItem(notification);
96
+ const completed = method === "item/completed" && item?.type === "reasoning";
97
+ if (!completed && !["item/reasoning/summaryPartAdded", "item/reasoning/summaryTextDelta", "item/reasoning/textDelta"].includes(method)) {
98
+ return false;
99
+ }
100
+ const normalizedThreadId = normalizeCodexRunText(threadId);
101
+ const normalizedTurnId = normalizeCodexRunText(turnId) || codexAppServerNotificationTurnId(notification);
102
+ if (!normalizedThreadId) {
103
+ return false;
104
+ }
105
+ const state = codexAppServerReasoningTurnState(normalizedThreadId, normalizedTurnId);
106
+ const params = codexAppServerNotificationParams(notification);
107
+ const itemId = normalizeCodexRunText(params.itemId || item?.id || "summary");
108
+ if (completed) {
109
+ // Completed-only providers expose the same readable parts in the item.
110
+ // Streamed items have already been recorded; do not replay their text.
111
+ if (state.channels.has(itemId)) return false;
112
+ const summary = codexAppServerContentText(item.summary);
113
+ const text = normalizeCodexRunText(summary) ? summary : codexAppServerContentText(item.content);
114
+ return recordCodexAppServerReasoningNotification(normalizedThreadId, {
115
+ method: normalizeCodexRunText(summary) ? "item/reasoning/summaryTextDelta" : "item/reasoning/textDelta",
116
+ params: { itemId, delta: text }
117
+ }, { turnId: normalizedTurnId });
118
+ }
119
+ const channel = method === "item/reasoning/textDelta" ? "content" : "summary";
120
+ // Some providers expose both channels. Keep the first readable stream once.
121
+ if (state.channels.has(itemId) && state.channels.get(itemId) !== channel) return false;
122
+ const summaryKey = codexAppServerReasoningSummaryKey(notification);
123
+ const summary = state.summaries.get(summaryKey) || {
124
+ currentSegment: null
125
+ };
126
+ if (!summary.currentSegment) {
127
+ createCodexAppServerReasoningSegment(state, summary, summaryKey);
128
+ }
129
+ if (method !== "item/reasoning/summaryPartAdded") {
130
+ let delta = codexAppServerContentText(params.delta || params.text);
131
+ if (delta) {
132
+ state.channels.set(itemId, channel);
133
+ const startsNewSegment = /^\s*\n/u.test(delta) &&
134
+ summary.currentSegment?.chunks?.length &&
135
+ summary.currentSegment?.persistedText;
136
+ if (startsNewSegment) {
137
+ delta = delta.replace(/^\s*\n+/u, "");
138
+ createCodexAppServerReasoningSegment(state, summary, summaryKey);
139
+ }
140
+ summary.currentSegment.chunks.push(delta);
141
+ state.summaries.set(summaryKey, summary);
142
+ return true;
143
+ }
144
+ }
145
+ state.summaries.set(summaryKey, summary);
146
+ return false;
147
+ }
148
+
149
+ async function recordCodexAppServerReasoningForSession(sessionId = "", threadId = "", notifications = []) {
150
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
151
+ const normalizedThreadId = normalizeCodexRunText(threadId);
152
+ const store = await createStore(normalizedSessionId);
153
+ const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
154
+ const currentTurn = turnStateFromAgentRun(run || {});
155
+ const ownerTurnId = codexAppServerOutputOwnerTurnId({
156
+ notificationThreadId: normalizedThreadId,
157
+ notificationTurnId: codexAppServerNotificationTurnId(notifications[0]),
158
+ trackedActive: currentTurn.active,
159
+ trackedState: currentTurn.state,
160
+ trackedThreadId: currentTurn.threadId,
161
+ trackedTurnId: currentTurn.turnId
162
+ });
163
+ let changed = false;
164
+ for (const notification of notifications) {
165
+ if (recordCodexAppServerReasoningNotification(normalizedThreadId, notification, { turnId: ownerTurnId })) {
166
+ changed = true;
167
+ }
168
+ }
169
+ if (!changed) return false;
170
+ await queueCodexAppServerReasoningPersist(
171
+ normalizedSessionId,
172
+ normalizedThreadId,
173
+ ownerTurnId
174
+ );
175
+ return true;
176
+ }
177
+
178
+ function readCodexAppServerReasoningText(threadId = "", turnId = "") {
179
+ const state = codexAppServerReasoningExistingTurnState(threadId, turnId);
180
+ if (!state) {
181
+ return "";
182
+ }
183
+ return (Array.isArray(state.segments) ? state.segments : [])
184
+ .map((segment) => codexAppServerReasoningDisplayText(segment.chunks.join("")))
185
+ .filter(Boolean)
186
+ .join("\n\n")
187
+ .trim();
188
+ }
189
+
190
+ function codexAppServerReasoningPersistKey(sessionId = "", threadId = "", turnId = "") {
191
+ return [
192
+ normalizeCodexRunText(sessionId),
193
+ normalizeCodexRunText(threadId),
194
+ normalizeCodexRunText(turnId) || "*"
195
+ ].filter(Boolean).join(":");
196
+ }
197
+
198
+ async function persistCodexAppServerReasoningSummary(sessionId = "", threadId = "", turnId = "") {
199
+ const normalizedSessionId = normalizeCodexRunText(sessionId);
200
+ const state = codexAppServerReasoningExistingTurnState(threadId, turnId);
201
+ const segments = Array.isArray(state?.segments) ? state.segments : [];
202
+ const pendingSegments = segments
203
+ .map((segment) => ({
204
+ segment,
205
+ text: codexAppServerReasoningDisplayText(segment.chunks.join(""))
206
+ }))
207
+ .filter(({ segment, text }) => text && segment.persistedText !== text);
208
+ if (!normalizedSessionId || !state || !pendingSegments.length) {
209
+ return;
210
+ }
211
+ const store = await createStore(normalizedSessionId);
212
+ const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
213
+ if (normalizeCodexRunText(run?.inputSource) === "terminal") {
214
+ return;
215
+ }
216
+ const turn = turnStateFromAgentRun(run || {});
217
+ if (!codexAppServerTurnCanReceiveProviderActivity(turn, threadId, turnId)) {
218
+ debugLog("appServerReasoningSummary.ignored", {
219
+ currentState: turn.state,
220
+ currentStatus: turn.status,
221
+ currentThreadId: turn.threadId,
222
+ currentTurnId: turn.turnId,
223
+ sessionId: normalizedSessionId,
224
+ threadId: normalizeCodexRunText(threadId),
225
+ turnId: normalizeCodexRunText(turnId)
226
+ });
227
+ return;
228
+ }
229
+ for (const {
230
+ segment,
231
+ text
232
+ } of pendingSegments) {
233
+ segment.persistedAt ||= new Date().toISOString();
234
+ const written = await store.writeConversationThinkingMessage(normalizedSessionId, {
235
+ at: segment.persistedAt,
236
+ nativeIdentity: { threadId, turnId },
237
+ requireOpenTurn: false,
238
+ text
239
+ });
240
+ if (!written) {
241
+ continue;
242
+ }
243
+ segment.persistedText = text;
244
+ await publish(normalizedSessionId, {
245
+ payload: {
246
+ conversationLogPatch: {
247
+ turn: written,
248
+ type: "upsert-turn"
249
+ }
250
+ },
251
+ reason: "codex-app-server-reasoning-summary"
252
+ });
253
+ }
254
+ }
255
+
256
+ function queueCodexAppServerReasoningPersist(sessionId = "", threadId = "", turnId = "") {
257
+ const key = codexAppServerReasoningPersistKey(sessionId, threadId, turnId);
258
+ if (!key) {
259
+ return Promise.resolve();
260
+ }
261
+ return runQueuedCodexAppServerReasoningPersist(key, sessionId, threadId, turnId);
262
+ }
263
+
264
+ async function flushCodexAppServerReasoningPersist(sessionId = "", threadId = "", turnId = "") {
265
+ const key = codexAppServerReasoningPersistKey(sessionId, threadId, turnId);
266
+ const queued = key ? codexAppServerReasoningPersistQueues.get(key) : null;
267
+ if (queued) {
268
+ await queued.catch(() => null);
269
+ }
270
+ await persistCodexAppServerReasoningSummary(sessionId, threadId, turnId);
271
+ }
272
+
273
+ function runQueuedCodexAppServerReasoningPersist(key = "", sessionId = "", threadId = "", turnId = "") {
274
+ const previous = codexAppServerReasoningPersistQueues.get(key) || Promise.resolve();
275
+ const next = previous
276
+ .catch(() => null)
277
+ .then(() => persistCodexAppServerReasoningSummary(sessionId, threadId, turnId));
278
+ codexAppServerReasoningPersistQueues.set(key, next);
279
+ next
280
+ .finally(() => {
281
+ if (codexAppServerReasoningPersistQueues.get(key) === next) {
282
+ codexAppServerReasoningPersistQueues.delete(key);
283
+ }
284
+ })
285
+ .catch(() => null);
286
+ return next;
287
+ }
288
+
289
+ function cleanupCodexAppServerReasoningTurn(threadId = "", turnId = "") {
290
+ codexAppServerReasoningTurns.delete(codexAppServerReasoningTurnKey(threadId, turnId));
291
+ codexAppServerReasoningTurns.delete(codexAppServerReasoningTurnKey(threadId, "*"));
292
+ }
293
+
294
+ function splitCodexAppServerReasoningTurn(threadId = "", turnId = "") {
295
+ const state = codexAppServerReasoningExistingTurnState(threadId, turnId);
296
+ if (!state) {
297
+ return false;
298
+ }
299
+ for (const summary of state.summaries.values()) {
300
+ summary.currentSegment = null;
301
+ }
302
+ return true;
303
+ }
304
+
305
+ return {
306
+ reasoningTurns: codexAppServerReasoningTurns,
307
+ reasoningPersistQueues: codexAppServerReasoningPersistQueues,
308
+ recordReasoningForSession: recordCodexAppServerReasoningForSession,
309
+ readReasoningText: readCodexAppServerReasoningText,
310
+ flushReasoningPersist: flushCodexAppServerReasoningPersist,
311
+ cleanupReasoningTurn: cleanupCodexAppServerReasoningTurn,
312
+ splitReasoningTurn: splitCodexAppServerReasoningTurn
313
+ };
314
+ }