@jskit-ai/assistant-core 0.2.7 → 0.2.9

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 (214) hide show
  1. package/examples/conversation/package.json +5 -5
  2. package/package.json +18 -7
  3. package/src/client/browser.js +21 -0
  4. package/src/client/conversation/AssistantAttachmentQueue.vue +25 -108
  5. package/src/client/conversation/AssistantComposerActions.vue +1 -1
  6. package/src/client/conversation/AssistantConversationElement.vue +14 -52
  7. package/src/client/conversation/AssistantGoalControl.vue +13 -10
  8. package/src/client/conversation/AssistantPromptInput.vue +6 -91
  9. package/src/client/conversation/AssistantTranscript.css +505 -0
  10. package/src/client/conversation/AssistantTranscript.vue +24 -1197
  11. package/src/client/conversation/attachmentFiles.js +158 -0
  12. package/src/client/conversation/attachmentQueueItems.js +121 -0
  13. package/src/client/conversation/attachmentUploadRows.js +265 -0
  14. package/src/client/conversation/conversationDisplay.js +54 -0
  15. package/src/client/conversation/messageDelivery.js +13 -9
  16. package/src/client/conversation/retainedConversation.js +14 -3
  17. package/src/client/conversation/submitText.js +13 -2
  18. package/src/client/conversation/transcriptEntries.js +225 -0
  19. package/src/client/conversation/useAssistantAttachments.js +86 -388
  20. package/src/client/conversation/useAssistantQuestions.js +16 -3
  21. package/src/client/conversation/useAssistantSuggestions.js +16 -2
  22. package/src/client/conversation/useScrollToBottom.js +17 -19
  23. package/src/client/conversation/useTextareaAutoGrow.js +115 -0
  24. package/src/client/conversation/useTranscriptScrollFollow.js +525 -0
  25. package/src/client/lib/assistantApi.js +192 -161
  26. package/src/client/types.js +110 -0
  27. package/src/server/conversation/attachments.js +21 -6
  28. package/src/server/conversation/boundedToolLoop.js +1 -0
  29. package/src/server/conversation/claudeClient.js +71 -20
  30. package/src/server/conversation/claudeCommandRun.js +191 -0
  31. package/src/server/conversation/claudeHistory.js +7 -0
  32. package/src/server/conversation/claudeNativeTurn.js +708 -0
  33. package/src/server/conversation/claudeProcess.js +186 -112
  34. package/src/server/conversation/claudeProcessStart.js +214 -0
  35. package/src/server/conversation/claudePromptDelivery.js +183 -0
  36. package/src/server/conversation/claudeSessionCleanup.js +248 -0
  37. package/src/server/conversation/claudeTools.js +15 -3
  38. package/src/server/conversation/claudeTurn.js +212 -1388
  39. package/src/server/conversation/codexAccountReader.js +168 -0
  40. package/src/server/conversation/codexAuthPreflight.js +5 -4
  41. package/src/server/conversation/codexClient.js +18 -6
  42. package/src/server/conversation/codexConfiguration.js +8 -844
  43. package/src/server/conversation/codexConversationStops.js +377 -0
  44. package/src/server/conversation/codexConversationStore.js +119 -85
  45. package/src/server/conversation/codexConversationTurns.js +378 -0
  46. package/src/server/conversation/codexConversations.js +134 -866
  47. package/src/server/conversation/codexDetachedConversation.js +203 -0
  48. package/src/server/conversation/codexDetachedTurn.js +145 -104
  49. package/src/server/conversation/codexEvents.js +5 -200
  50. package/src/server/conversation/codexGoalCommands.js +14 -4
  51. package/src/server/conversation/codexHelperExecution.js +57 -57
  52. package/src/server/conversation/codexHelperThreadLedger.js +30 -288
  53. package/src/server/conversation/codexHelperThreadRecord.js +304 -0
  54. package/src/server/conversation/codexHelperThreadRecovery.js +54 -76
  55. package/src/server/conversation/codexHelperThreads.js +94 -93
  56. package/src/server/conversation/codexHistoryAdapter.js +13 -8
  57. package/src/server/conversation/codexIsolation.js +672 -0
  58. package/src/server/conversation/codexLiveItems.js +379 -0
  59. package/src/server/conversation/codexMessageCommands.js +68 -286
  60. package/src/server/conversation/codexMessageDelivery.js +46 -37
  61. package/src/server/conversation/codexModelCatalog.js +86 -7
  62. package/src/server/conversation/codexNotificationObserver.js +81 -88
  63. package/src/server/conversation/codexNotificationQueue.js +21 -10
  64. package/src/server/conversation/codexObservationControl.js +105 -89
  65. package/src/server/conversation/codexOutput.js +84 -419
  66. package/src/server/conversation/codexProcess.js +29 -1053
  67. package/src/server/conversation/codexProcessLeader.js +1 -0
  68. package/src/server/conversation/codexPromptDispatch.js +241 -0
  69. package/src/server/conversation/codexProvider.js +147 -1807
  70. package/src/server/conversation/codexProviderAuth.js +348 -0
  71. package/src/server/conversation/codexProviderInvalidation.js +371 -0
  72. package/src/server/conversation/codexProviderInventories.js +437 -0
  73. package/src/server/conversation/codexProviderOwner.js +149 -957
  74. package/src/server/conversation/codexProviderRelease.js +465 -0
  75. package/src/server/conversation/codexProviderSelection.js +26 -15
  76. package/src/server/conversation/codexProviderThreads.js +698 -0
  77. package/src/server/conversation/codexProviderTurns.js +453 -0
  78. package/src/server/conversation/codexReasoning.js +32 -23
  79. package/src/server/conversation/codexRenewalCommands.js +18 -6
  80. package/src/server/conversation/codexRenewalThreads.js +7 -22
  81. package/src/server/conversation/codexResultSettlement.js +70 -88
  82. package/src/server/conversation/codexRunJournal.js +91 -97
  83. package/src/server/conversation/codexRuntime.js +426 -0
  84. package/src/server/conversation/codexRuntimeInventory.js +113 -0
  85. package/src/server/conversation/codexRuntimeLifecycle.js +39 -13
  86. package/src/server/conversation/codexRuntimeStart.js +431 -0
  87. package/src/server/conversation/codexRuntimeStop.js +324 -0
  88. package/src/server/conversation/codexSessionCleanup.js +25 -10
  89. package/src/server/conversation/codexSessionReconciliation.js +21 -7
  90. package/src/server/conversation/codexThreadReadiness.js +49 -52
  91. package/src/server/conversation/codexThreadSnapshot.js +210 -0
  92. package/src/server/conversation/codexThreadStatus.js +48 -62
  93. package/src/server/conversation/codexTurn.js +171 -55
  94. package/src/server/conversation/codexTurnActivity.js +210 -0
  95. package/src/server/conversation/codexTurnControl.js +44 -48
  96. package/src/server/conversation/codexTurnEndings.js +610 -0
  97. package/src/server/conversation/codexTurnRecovery.js +97 -848
  98. package/src/server/conversation/codexTurnState.js +138 -111
  99. package/src/server/conversation/completedResponses.js +227 -0
  100. package/src/server/conversation/configuration.js +2 -2
  101. package/src/server/conversation/continuity.js +43 -6
  102. package/src/server/conversation/conversationDelivery.js +394 -0
  103. package/src/server/conversation/conversationEntries.js +365 -0
  104. package/src/server/conversation/conversationEvents.js +192 -0
  105. package/src/server/conversation/conversationGoals.js +187 -0
  106. package/src/server/conversation/conversationHandles.js +237 -0
  107. package/src/server/conversation/conversationReads.js +206 -0
  108. package/src/server/conversation/conversationRefusals.js +58 -0
  109. package/src/server/conversation/conversationReplacement.js +254 -0
  110. package/src/server/conversation/conversationSending.js +273 -0
  111. package/src/server/conversation/conversationStopping.js +121 -0
  112. package/src/server/conversation/conversationTurnDelivery.js +413 -0
  113. package/src/server/conversation/conversationTurns.js +511 -0
  114. package/src/server/conversation/deliveryTiming.js +25 -3
  115. package/src/server/conversation/fileStorage.js +11 -6
  116. package/src/server/conversation/hookBridge.js +14 -3
  117. package/src/server/conversation/index.js +1 -1
  118. package/src/server/conversation/localExecution.js +82 -625
  119. package/src/server/conversation/nativeCommandCallbacks.js +10 -0
  120. package/src/server/conversation/nativeConversationLifecycle.js +379 -0
  121. package/src/server/conversation/nativeHistoryExport.js +27 -18
  122. package/src/server/conversation/normalize.js +9 -0
  123. package/src/server/conversation/openCodeClient.js +295 -366
  124. package/src/server/conversation/openCodeNativeHistory.js +141 -0
  125. package/src/server/conversation/openCodeNativeSession.js +79 -0
  126. package/src/server/conversation/openCodeProcess.js +109 -53
  127. package/src/server/conversation/openCodeProcessOwner.js +565 -0
  128. package/src/server/conversation/openCodeRuntime.js +111 -1814
  129. package/src/server/conversation/openCodeSessionTurns.js +547 -0
  130. package/src/server/conversation/openCodeTemporaryConversations.js +394 -0
  131. package/src/server/conversation/openCodeTools.js +28 -14
  132. package/src/server/conversation/openCodeTurn.js +80 -74
  133. package/src/server/conversation/openCodeTurnMonitor.js +388 -0
  134. package/src/server/conversation/processIdentity.js +582 -0
  135. package/src/server/conversation/provider.js +21 -6
  136. package/src/server/conversation/providers/api.js +268 -107
  137. package/src/server/conversation/providers/claude.js +29 -6
  138. package/src/server/conversation/providers/claudeDriver.js +325 -250
  139. package/src/server/conversation/providers/codex.js +69 -33
  140. package/src/server/conversation/providers/codexDriver.js +503 -1081
  141. package/src/server/conversation/providers/codexDriverConnection.js +331 -0
  142. package/src/server/conversation/providers/codexDriverTurns.js +464 -0
  143. package/src/server/conversation/providers/opencode.js +30 -4
  144. package/src/server/conversation/providers/opencodeDriver.js +321 -659
  145. package/src/server/conversation/providers/opencodeStandalone.js +475 -0
  146. package/src/server/conversation/providers/scoped.js +64 -19
  147. package/src/server/conversation/runtime.js +171 -2863
  148. package/src/server/conversation/storage.js +91 -50
  149. package/src/server/conversation/streams.js +15 -2
  150. package/src/server/conversation/structuredOutput.js +7 -11
  151. package/src/server/conversation/systemPrompt.js +13 -1
  152. package/src/server/conversation/tools.js +47 -15
  153. package/src/server/conversation/transcript.js +49 -20
  154. package/src/server/hosts/claudeTurn.js +2 -4
  155. package/src/server/hosts/codexConfiguration.js +6 -8
  156. package/src/server/hosts/codexEvents.js +4 -2
  157. package/src/server/hosts/codexProcess.js +1 -1
  158. package/src/server/hosts/codexProvider.js +11 -9
  159. package/src/server/hosts/codexTurn.js +3 -3
  160. package/src/server/hosts/openCodeClient.js +4 -2
  161. package/src/server/index.js +0 -2
  162. package/src/server/lib/aiConnectionClient.js +35 -25
  163. package/src/server/lib/ndjson.js +1 -0
  164. package/src/server/lib/providers/anthropicClient.js +15 -6
  165. package/src/server/lib/providers/common.js +10 -6
  166. package/src/server/lib/providers/openAiCompatibleClient.js +40 -23
  167. package/src/server/lib/serviceToolCatalog.js +29 -283
  168. package/src/server/lib/serviceToolEntries.js +270 -0
  169. package/src/server/types.js +78 -0
  170. package/src/shared/assistantResource.js +0 -23
  171. package/src/shared/conversation/answerChoiceSugar.js +2 -14
  172. package/src/shared/conversation/messageInputSugar.js +18 -0
  173. package/src/shared/conversation/numberedQuestionSugar.js +6 -16
  174. package/src/shared/conversation/pagination.js +1 -1
  175. package/src/shared/conversation/questions.js +1 -0
  176. package/src/shared/conversation/streaming.js +1 -0
  177. package/src/shared/index.js +0 -1
  178. package/src/testing/conversationStorageContract.js +20 -0
  179. package/test/aiConnectionClient.test.js +42 -1
  180. package/test/apiConversationDriver.test.js +217 -38
  181. package/test/assistantBoundedToolLoop.test.js +1 -1
  182. package/test/claudeConversationSession.test.js +15 -11
  183. package/test/claudeInstructionResume.test.js +1 -1
  184. package/test/claudeProcess.test.js +9 -9
  185. package/test/claudeTurn.test.js +21 -8
  186. package/test/codexAccountChange.test.js +5 -3
  187. package/test/codexConversationSession.test.js +22 -21
  188. package/test/codexEvents.test.js +3 -1
  189. package/test/codexHelperDeletion.test.js +3 -7
  190. package/test/codexIsolation.test.js +6 -6
  191. package/test/codexModelCatalog.test.js +6 -7
  192. package/test/codexProcess.test.js +3 -3
  193. package/test/codexThreadSelection.test.js +4 -1
  194. package/test/codexTurn.test.js +29 -20
  195. package/test/conversationApiFacilities.test.js +11 -9
  196. package/test/conversationAttachments.test.js +3 -3
  197. package/test/conversationConfiguration.test.js +4 -4
  198. package/test/conversationNativeStorage.test.js +11 -10
  199. package/test/conversationReplacement.test.js +13 -13
  200. package/test/conversationRuntime.test.js +9 -5
  201. package/test/conversationSession.test.js +78 -22
  202. package/test/conversationTools.test.js +47 -26
  203. package/test/fileConversationStorage.test.js +10 -3
  204. package/test/fixtures/codexHelperThreadLedger.js +3 -3
  205. package/test/frameworkActions.test.js +33 -18
  206. package/test/localConversationExecution.test.js +4 -4
  207. package/test/localExecution.test.js +1 -1
  208. package/test/openCodeConnectionChange.test.js +3 -2
  209. package/test/openCodeRuntime.test.js +8 -7
  210. package/test/openCodeTools.test.js +1 -1
  211. package/test/opencodeConversationSession.test.js +4 -3
  212. package/test/serviceToolCatalog.test.js +3 -8
  213. package/src/server/lib/assistantCompletion.js +0 -190
  214. package/src/server/lib/assistantToolLoop.js +0 -259
@@ -1,12 +1,25 @@
1
- import { normalizeCodexRunText, codexAppServerPendingUserMessageOwnership } from "./codexTurnState.js";
1
+ import { codexAppServerPendingUserMessageOwnership } from "./codexTurnState.js";
2
2
  import {
3
3
  codexAppServerNotificationItem,
4
4
  codexAppServerUserMessageText,
5
5
  codexAppServerNotificationTurnId
6
6
  } from "./codexEvents.js";
7
+ import { normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
8
+ import { ignoreReceipt } from "./nativeCommandCallbacks.js";
7
9
 
8
- // Original request deduplication and authored receipt persistence.
9
- // One instance and its two Maps belong to the containing retained run owner.
10
+ /**
11
+ * Request deduplication and authored receipt persistence. One instance and its two Maps belong to its run owner.
12
+ * @param {object} dependencies
13
+ * @param {import("./codexTurn.js").CodexSessionNamespace} dependencies.namespace
14
+ * @param {import("./codexTurn.js").CodexPublish} dependencies.publish
15
+ * @param {import("./codexTurn.js").CodexMessageMetadataHooks} dependencies.messageMetadata
16
+ * @param {import("./codexTurn.js").CodexCreateStore} dependencies.createStore
17
+ * @param {import("./codexTurn.js").CodexAgentRunTurnStateReader} dependencies.turnStateFromAgentRun
18
+ * @param {string} dependencies.deliveryStateMetadataKey
19
+ * @param {import("./codexOutput.js").CodexAppServerOutput} dependencies.output
20
+ * @param {import("./codexRunJournal.js").CodexRunJournal} dependencies.journal
21
+ * @param {import("./codexTurnRecovery.js").CodexTurnRecovery} dependencies.recovery
22
+ */
10
23
  export function createCodexAppServerMessageDelivery({
11
24
  namespace,
12
25
  publish,
@@ -18,12 +31,6 @@ export function createCodexAppServerMessageDelivery({
18
31
  journal,
19
32
  recovery
20
33
  }) {
21
- const {
22
- readAgentRunForSession: readCodexAppServerAgentRunForSession,
23
- writeMirroredTerminalMessage: writeMirroredCodexAppServerTerminalMessage
24
- } = output;
25
- const { writeUserMessageOwnership: writeCodexAppServerUserMessageOwnership } = journal;
26
- const { markProviderTurnActive: markCodexAppServerProviderTurnActive } = recovery;
27
34
  const codexAppServerMessageDeliveries = new Map();
28
35
  const codexAppServerPendingUserMessages = new Map();
29
36
 
@@ -36,15 +43,18 @@ export function createCodexAppServerMessageDelivery({
36
43
  }
37
44
 
38
45
  async function withCodexAppServerMessageDelivery(sessionId, messageId, pendingMessage, operation) {
39
- const normalizedMessageId = normalizeCodexRunText(messageId);
46
+ const normalizedMessageId = normalizeText(messageId);
40
47
  const deliveryKey = normalizedMessageId ? messageDeliveryKey(sessionId, normalizedMessageId) : "";
41
48
  const existing = deliveryKey ? codexAppServerMessageDeliveries.get(deliveryKey) : null;
42
49
  if (existing) {
43
50
  return existing;
44
51
  }
45
52
  if (deliveryKey) {
53
+ const { onNativeReceipt = ignoreReceipt, onReceiptPersisted = ignoreReceipt } = pendingMessage;
46
54
  codexAppServerPendingUserMessages.set(deliveryKey, {
47
55
  ...pendingMessage,
56
+ onNativeReceipt,
57
+ onReceiptPersisted,
48
58
  receipt: Promise.withResolvers()
49
59
  });
50
60
  }
@@ -72,36 +82,33 @@ export function createCodexAppServerMessageDelivery({
72
82
  nativeIdentity = null,
73
83
  data
74
84
  ) {
75
- const normalizedSessionId = normalizeCodexRunText(sessionId);
76
- const message = normalizeCodexRunText(text);
85
+ const normalizedSessionId = normalizeText(sessionId);
86
+ const message = normalizeText(text);
77
87
  if (
78
88
  !normalizedSessionId ||
79
- (!message && !(Array.isArray(attachments) && attachments.length)) ||
80
- typeof runtime?.store?.writeConversationUserMessage !== "function"
89
+ (!message && !(Array.isArray(attachments) && attachments.length))
81
90
  ) {
82
91
  return null;
83
92
  }
84
- const pendingMessage = pendingUserMessage(normalizedSessionId, normalizeCodexRunText(messageId));
93
+ const pendingMessage = pendingUserMessage(normalizedSessionId, normalizeText(messageId));
85
94
  if (pendingMessage?.recording) {
86
95
  return pendingMessage.recording;
87
96
  }
88
97
  const authored = pendingMessage?.authoredInput;
89
- const applicationData = authored?.messageId === normalizeCodexRunText(messageId) ? authored.data : data;
98
+ const applicationData = authored?.messageId === normalizeText(messageId) ? authored.data : data;
90
99
  const recording = (async () => {
91
100
  const written = await runtime.store.writeConversationUserMessage(normalizedSessionId, {
92
101
  attachments,
93
102
  nativeIdentity,
94
- messageId: normalizeCodexRunText(messageId),
103
+ messageId: normalizeText(messageId),
95
104
  text: message,
96
105
  ...(applicationData !== undefined ? { data: structuredClone(applicationData) } : {}),
97
- turnMetadata: messageMetadata.delivered
98
- ? await messageMetadata.delivered(runtime.store, normalizedSessionId, turnMetadata)
99
- : turnMetadata
106
+ turnMetadata: await messageMetadata.delivered(runtime.store, normalizedSessionId, turnMetadata)
100
107
  });
101
108
  if (!written) {
102
109
  return null;
103
110
  }
104
- pendingMessage?.onReceiptPersisted?.();
111
+ pendingMessage?.onReceiptPersisted();
105
112
  await publish(normalizedSessionId, {
106
113
  payload: {
107
114
  conversationLogPatch: {
@@ -120,16 +127,16 @@ export function createCodexAppServerMessageDelivery({
120
127
  }
121
128
 
122
129
  async function mirrorCodexAppServerTerminalUserMessage(sessionId = "", threadId = "", notification = {}) {
123
- const normalizedSessionId = normalizeCodexRunText(sessionId);
124
- const normalizedThreadId = normalizeCodexRunText(threadId);
130
+ const normalizedSessionId = normalizeText(sessionId);
131
+ const normalizedThreadId = normalizeText(threadId);
125
132
  const item = codexAppServerNotificationItem(notification);
126
133
  const text = codexAppServerUserMessageText(item);
127
- const clientId = normalizeCodexRunText(item?.clientId);
128
- if (!normalizedSessionId || (!text && (!clientId || normalizeCodexRunText(item?.type) !== "userMessage"))) {
134
+ const clientId = normalizeText(item?.clientId);
135
+ if (!normalizedSessionId || (!text && (!clientId || normalizeText(item?.type) !== "userMessage"))) {
129
136
  return;
130
137
  }
131
138
  const store = await createStore(normalizedSessionId);
132
- const run = await readCodexAppServerAgentRunForSession(store, normalizedSessionId);
139
+ const run = await output.readAgentRunForSession(store, normalizedSessionId);
133
140
  let ownership = codexAppServerPendingUserMessageOwnership(run, clientId);
134
141
  if (!ownership && clientId && await store.conversationMessageIdExists(normalizedSessionId, clientId)) return;
135
142
  const receiptId = ownership?.clientId || clientId;
@@ -149,7 +156,7 @@ export function createCodexAppServerMessageDelivery({
149
156
  Array.isArray(recoveredMessage?.displayAttachments) && recoveredMessage.displayAttachments.length
150
157
  ))) return;
151
158
  if (ownership) {
152
- pendingMessage?.onNativeReceipt?.();
159
+ pendingMessage?.onNativeReceipt();
153
160
  // The provider's user-message receipt precedes its answer notifications.
154
161
  // Persist the authored message here so answers cannot overtake it while
155
162
  // the original HTTP request is still finishing startup bookkeeping.
@@ -159,7 +166,7 @@ export function createCodexAppServerMessageDelivery({
159
166
  normalizedSessionId,
160
167
  pendingMessage.text,
161
168
  ownership.clientId,
162
- { ...await messageMetadata.actor?.(pendingMessage.actorContext), ...pendingMessage.turnMetadata },
169
+ { ...await messageMetadata.actor(pendingMessage.actorContext), ...pendingMessage.turnMetadata },
163
170
  pendingMessage.attachments,
164
171
  { threadId: normalizedThreadId, turnId: codexAppServerNotificationTurnId(notification) }
165
172
  );
@@ -173,18 +180,18 @@ export function createCodexAppServerMessageDelivery({
173
180
  }
174
181
  const turn = turnStateFromAgentRun(run || {});
175
182
  const providerTurnId = codexAppServerNotificationTurnId(notification);
176
- const providerTurnAlreadyTracked = normalizeCodexRunText(turn.state) === "active" &&
177
- (!normalizedThreadId || normalizeCodexRunText(turn.threadId) === normalizedThreadId) &&
178
- (!providerTurnId || normalizeCodexRunText(turn.turnId) === providerTurnId);
183
+ const providerTurnAlreadyTracked = normalizeText(turn.state) === "active" &&
184
+ (!normalizedThreadId || normalizeText(turn.threadId) === normalizedThreadId) &&
185
+ (!providerTurnId || normalizeText(turn.turnId) === providerTurnId);
179
186
  if (!providerTurnAlreadyTracked) {
180
- await markCodexAppServerProviderTurnActive(normalizedSessionId, {
187
+ await recovery.markProviderTurnActive(normalizedSessionId, {
181
188
  inputSource: ownership.inputSource,
182
189
  status: "inProgress",
183
190
  threadId: normalizedThreadId,
184
191
  turnId: providerTurnId
185
192
  });
186
193
  }
187
- await writeCodexAppServerUserMessageOwnership(
194
+ await journal.writeUserMessageOwnership(
188
195
  store,
189
196
  normalizedSessionId,
190
197
  ownership.clientId,
@@ -198,12 +205,12 @@ export function createCodexAppServerMessageDelivery({
198
205
  }
199
206
  return;
200
207
  }
201
- await markCodexAppServerProviderTurnActive(normalizedSessionId, {
208
+ await recovery.markProviderTurnActive(normalizedSessionId, {
202
209
  status: "inProgress",
203
210
  threadId: normalizedThreadId,
204
211
  turnId: codexAppServerNotificationTurnId(notification)
205
212
  });
206
- await writeMirroredCodexAppServerTerminalMessage({
213
+ await output.writeMirroredTerminalMessage({
207
214
  notification,
208
215
  role: "user",
209
216
  sessionId: normalizedSessionId,
@@ -212,12 +219,14 @@ export function createCodexAppServerMessageDelivery({
212
219
  });
213
220
  }
214
221
 
215
- return {
222
+ return Object.freeze({
216
223
  messageDeliveries: codexAppServerMessageDeliveries,
217
224
  pendingUserMessages: codexAppServerPendingUserMessages,
218
225
  pendingUserMessage,
219
226
  withMessageDelivery: withCodexAppServerMessageDelivery,
220
227
  writeDeliveredUserMessage: writeCodexAppServerDeliveredUserMessage,
221
228
  mirrorTerminalUserMessage: mirrorCodexAppServerTerminalUserMessage
222
- };
229
+ });
223
230
  }
231
+
232
+ /** @typedef {ReturnType<typeof createCodexAppServerMessageDelivery>} CodexAppServerMessageDelivery */
@@ -1,4 +1,5 @@
1
1
  import { normalizeNullableText as normalizeAgentText } from "./normalize.js";
2
+ import { codexAppServerOwnedRuntimeKey, codexAppServerOwnedRuntimes, withRuntimeLifecycle } from "./codexRuntimeInventory.js";
2
3
 
3
4
  // Bounded live model catalog, cached per provider connection generation.
4
5
  export function codexAppServerModelCatalogError(message = "", errorPrefix = "") {
@@ -7,13 +8,9 @@ export function codexAppServerModelCatalogError(message = "", errorPrefix = "")
7
8
  return error;
8
9
  }
9
10
 
10
- export function codexAppServerProviderConnectionGeneration(provider = null) {
11
- const generation = typeof provider?.currentConnectionGeneration === "function"
12
- ? provider.currentConnectionGeneration()
13
- : typeof provider?.connectionGeneration === "function"
14
- ? provider.connectionGeneration()
15
- : provider?.connectionGeneration;
16
- return normalizeAgentText(generation || "");
11
+ /** @param {import("./codexProvider.js").CodexAppServerAgentProvider} provider */
12
+ export function codexAppServerProviderConnectionGeneration(provider) {
13
+ return normalizeAgentText(provider.currentConnectionGeneration() || "");
17
14
  }
18
15
 
19
16
  function codexAppServerModelCatalogSnapshot(value = null, errorPrefix = "") {
@@ -39,6 +36,7 @@ export function createCodexAppServerModelCatalogCache({
39
36
  } = {}) {
40
37
  const codexAppServerModelCatalogs = new WeakMap();
41
38
  return async function readCodexAppServerModelCatalog(provider, { signal = null } = {}) {
39
+ // readable-check: allow method-probe - safety invariant: a provider without live model discovery is refused with codex_model_catalog_unavailable
42
40
  if (typeof provider.listModels !== "function") {
43
41
  const error = new Error("Codex live model discovery is unavailable.");
44
42
  error.code = `${errorPrefix}codex_model_catalog_unavailable`;
@@ -95,3 +93,84 @@ export function createCodexAppServerModelCatalogCache({
95
93
  }
96
94
  };
97
95
  }
96
+
97
+ /**
98
+ * The chat model catalogue an owner reads from its native runtime, cached per runtime identity. A runtime it
99
+ * starts only for the read stops again.
100
+ * @param {object} dependencies
101
+ * @param {<T>(operation: () => T) => Promise<Awaited<T>>} dependencies.withLifecycle
102
+ * @param {() => void} dependencies.assertOpen
103
+ * @param {(request: { operation: () => Promise<object>, provider: object, providerOptions: object }) => Promise<object>} dependencies.acquireRuntime
104
+ * @param {(record: object) => Promise<object>} dependencies.stopOwnedRuntime
105
+ * @param {(provider: object) => void} dependencies.forgetOwnedRuntime
106
+ */
107
+ export function createCodexRuntimeModelCatalog({ withLifecycle, assertOpen, acquireRuntime, stopOwnedRuntime, forgetOwnedRuntime }) {
108
+ let codexAppServerChatModelCatalog = null;
109
+
110
+ function readModelCatalog({
111
+ prepareProviderOptions,
112
+ providerFactory,
113
+ resources = null,
114
+ signal = null,
115
+ cacheMs = 30_000
116
+ } = {}) {
117
+ return withLifecycle(async () => {
118
+ assertOpen();
119
+ signal?.throwIfAborted();
120
+ const providerOptions = await prepareProviderOptions();
121
+ return withRuntimeLifecycle(codexAppServerOwnedRuntimeKey("", providerOptions), async () => {
122
+ const existing = codexAppServerOwnedRuntimes.get(
123
+ codexAppServerOwnedRuntimeKey("", providerOptions)
124
+ );
125
+ const provider = existing?.provider || providerFactory(providerOptions);
126
+ let runtime = null;
127
+ let catalog = null;
128
+ let identity = "";
129
+ try {
130
+ assertOpen();
131
+ identity = JSON.stringify(await provider.currentRuntimeInfo());
132
+ signal?.throwIfAborted();
133
+ if (codexAppServerChatModelCatalog?.identity === identity &&
134
+ codexAppServerChatModelCatalog.expiresAt > Date.now()) {
135
+ return codexAppServerChatModelCatalog.value;
136
+ }
137
+ if (!existing?.provider) {
138
+ runtime = await acquireRuntime({
139
+ operation: () => provider.ensureRuntime(),
140
+ provider,
141
+ providerOptions
142
+ });
143
+ }
144
+ catalog = await provider.listModels({ includeHidden: false, limit: 100 }, { signal });
145
+ } finally {
146
+ if (!existing?.provider) {
147
+ try {
148
+ if ((runtime || provider.runtime)?.reused === false) {
149
+ await stopOwnedRuntime({ provider, resources });
150
+ } else {
151
+ forgetOwnedRuntime(provider);
152
+ }
153
+ } finally {
154
+ provider.close();
155
+ }
156
+ }
157
+ }
158
+ codexAppServerChatModelCatalog = {
159
+ expiresAt: Date.now() + cacheMs,
160
+ identity,
161
+ value: catalog
162
+ };
163
+ return catalog;
164
+ });
165
+ });
166
+ }
167
+
168
+ function forgetModelCatalog() {
169
+ codexAppServerChatModelCatalog = null;
170
+ }
171
+
172
+ return Object.freeze({
173
+ read: readModelCatalog,
174
+ forget: forgetModelCatalog
175
+ });
176
+ }