@jskit-ai/assistant-core 0.2.7 → 0.2.8

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
@@ -0,0 +1,437 @@
1
+ import { codexAppServerTextHasControlCharacters } from "./codexConfiguration.js";
2
+ import { isRecord, normalizePositiveInteger } from "@jskit-ai/kernel/shared/support/normalize";
3
+ import { normalizeNullableText as normalizeAgentText } from "./normalize.js";
4
+ import path from "node:path";
5
+
6
+ /** @typedef {import("./codexProvider.js").CodexAppServerAgentProvider} CodexAppServerAgentProvider */
7
+
8
+ const CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT = 100;
9
+
10
+ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES = 100;
11
+
12
+ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRIES = 1000;
13
+
14
+ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRY_BYTES = 32 * 1024;
15
+
16
+ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_TOTAL_BYTES = 2 * 1024 * 1024;
17
+
18
+ const CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH = 512;
19
+
20
+ const CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT = 100;
21
+
22
+ const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_PAGES = 100;
23
+
24
+ export const CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT = 1000;
25
+
26
+ export const CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH = 512;
27
+
28
+ const CODEX_NATIVE_STORAGE_SOURCE_KINDS = ["cli", "vscode", "exec", "appServer", "subAgent", "subAgentReview",
29
+ "subAgentCompact", "subAgentThreadSpawn", "subAgentOther", "unknown"];
30
+
31
+ function codexAppServerThreadInventoryError(errorCode, label, detail) {
32
+ const error = new Error(`Codex ${label} thread inventory ${detail}.`);
33
+ error.code = errorCode;
34
+ return error;
35
+ }
36
+
37
+ // Why a listed inventory entry is refused, or "" for a valid thread.
38
+ function codexAppServerThreadInventoryRefusal(entry, threadId, { cwd, verifyCwd }) {
39
+ if (!isRecord(entry)) return "listed an entry that is not a thread";
40
+ if (!threadId) return "listed a thread without an id";
41
+ if (threadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH) {
42
+ return `listed a thread id longer than ${CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH} characters`;
43
+ }
44
+ if (codexAppServerTextHasControlCharacters(threadId)) return "listed a thread id with control characters";
45
+ if (verifyCwd && normalizeAgentText(entry.cwd) !== cwd) return `listed thread ${threadId} outside the requested directory`;
46
+ return "";
47
+ }
48
+
49
+ async function listBoundedCodexAppServerThreadIds({
50
+ ancestorThreadId = "",
51
+ archived = false,
52
+ client,
53
+ cwd = "",
54
+ errorCode = "",
55
+ findThreadId = "",
56
+ label = "",
57
+ requestLabel = "",
58
+ runRequest,
59
+ signal = null,
60
+ sourceKinds = ["appServer"],
61
+ state,
62
+ verifyCwd = false
63
+ } = {}) {
64
+ const seenCursors = new Set();
65
+ let cursor = "";
66
+ for (let page = 0; page < CODEX_APP_SERVER_THREAD_INVENTORY_MAX_PAGES; page += 1) {
67
+ if (cursor && seenCursors.has(cursor)) {
68
+ throw codexAppServerThreadInventoryError(
69
+ errorCode,
70
+ label,
71
+ "repeated a pagination cursor"
72
+ );
73
+ }
74
+ if (cursor) {
75
+ seenCursors.add(cursor);
76
+ }
77
+ const response = await runRequest(
78
+ () => client.request("thread/list", {
79
+ archived,
80
+ ...(cursor ? { cursor } : {}),
81
+ ...(ancestorThreadId ? { ancestorThreadId } : cwd ? { cwd } : {}),
82
+ limit: CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT,
83
+ modelProviders: [],
84
+ sourceKinds,
85
+ useStateDbOnly: false
86
+ }, { signal }),
87
+ requestLabel
88
+ );
89
+ if (!Array.isArray(response?.data) || response.data.length > CODEX_APP_SERVER_THREAD_INVENTORY_PAGE_LIMIT) {
90
+ throw codexAppServerThreadInventoryError(errorCode, label, "returned a malformed page");
91
+ }
92
+ // Only thread ids are retained, so only they are bounded. A search for one
93
+ // thread retains none and is bounded by the page limit alone.
94
+ if (!findThreadId && state.entryCount + response.data.length > CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT) {
95
+ throw codexAppServerThreadInventoryError(errorCode, label,
96
+ `exceeded its ${CODEX_APP_SERVER_THREAD_INVENTORY_MAX_COUNT}-thread limit`);
97
+ }
98
+ for (const entry of response.data) {
99
+ const threadId = isRecord(entry) ? normalizeAgentText(entry.id) : "";
100
+ const refusal = codexAppServerThreadInventoryRefusal(entry, threadId, { cwd, verifyCwd });
101
+ if (refusal) {
102
+ throw codexAppServerThreadInventoryError(errorCode, label, refusal);
103
+ }
104
+ if (findThreadId) {
105
+ if (threadId === findThreadId) {
106
+ state.found = true;
107
+ return state;
108
+ }
109
+ continue;
110
+ }
111
+ state.entryCount += 1;
112
+ state.threadIds.add(threadId);
113
+ }
114
+ const nextCursor = response.nextCursor === undefined || response.nextCursor === null || response.nextCursor === ""
115
+ ? ""
116
+ : typeof response.nextCursor === "string"
117
+ ? response.nextCursor.trim()
118
+ : null;
119
+ if (
120
+ nextCursor === null ||
121
+ nextCursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH ||
122
+ (nextCursor && seenCursors.has(nextCursor))
123
+ ) {
124
+ throw codexAppServerThreadInventoryError(
125
+ errorCode,
126
+ label,
127
+ "returned an invalid pagination cursor"
128
+ );
129
+ }
130
+ if (!nextCursor) {
131
+ return state;
132
+ }
133
+ cursor = nextCursor;
134
+ }
135
+ throw codexAppServerThreadInventoryError(
136
+ errorCode,
137
+ label,
138
+ "exceeded its page limit"
139
+ );
140
+ }
141
+ /**
142
+ * The provider's inventories: loaded and stored threads, models, configuration and hooks.
143
+ * @param {object} dependencies
144
+ * @param {CodexAppServerAgentProvider} dependencies.provider
145
+ */
146
+ export function createCodexProviderInventories({ provider }) {
147
+ async function listLoadedThreads(params = {}) {
148
+ const client = await provider.activeClient();
149
+ return provider.runRequest(
150
+ () => client.request("thread/loaded/list", params),
151
+ "codex-app-server-thread-loaded-list"
152
+ );
153
+ }
154
+
155
+ async function listAppServerThreadsForCwd({
156
+ cwd = "",
157
+ signal = null
158
+ } = {}) {
159
+ const normalizedCwd = normalizeAgentText(cwd);
160
+ if (!normalizedCwd || codexAppServerTextHasControlCharacters(normalizedCwd)) {
161
+ const error = new Error("Codex session thread inventory requires an exact cwd.");
162
+ error.code = `${provider.errorPrefix}codex_session_thread_inventory_invalid`;
163
+ throw error;
164
+ }
165
+ const client = await provider.activeClient();
166
+ const state = await listBoundedCodexAppServerThreadIds({
167
+ archived: false,
168
+ client,
169
+ cwd: normalizedCwd,
170
+ errorCode: `${provider.errorPrefix}codex_session_thread_inventory_invalid`,
171
+ label: "session",
172
+ requestLabel: "codex-app-server-session-thread-list",
173
+ runRequest: provider.runRequest.bind(provider),
174
+ signal,
175
+ state: {
176
+ entryCount: 0,
177
+ threadIds: new Set()
178
+ },
179
+ verifyCwd: true
180
+ });
181
+ return Object.freeze({
182
+ cwd: normalizedCwd,
183
+ threadIds: Object.freeze([...state.threadIds].sort())
184
+ });
185
+ }
186
+
187
+ /**
188
+ * Native thread/delete cascades through all spawned descendants, including
189
+ * archived and non-app-server threads. Never infer its scope from cwd alone.
190
+ * @param {CodexAppServerAgentProvider} provider
191
+ */
192
+ async function listThreadDescendants(threadId) {
193
+ const ancestorThreadId = normalizeAgentText(threadId);
194
+ if (!ancestorThreadId || ancestorThreadId.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH ||
195
+ codexAppServerTextHasControlCharacters(ancestorThreadId)) {
196
+ throw new TypeError("Codex descendant inventory requires an exact thread id.");
197
+ }
198
+ const client = await provider.activeClient();
199
+ const state = { entryCount: 0, threadIds: new Set() };
200
+ for (const archived of [false, true]) {
201
+ await listBoundedCodexAppServerThreadIds({
202
+ ancestorThreadId, archived, client, state,
203
+ sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
204
+ errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "retirement",
205
+ requestLabel: "codex-app-server-descendant-list", runRequest: provider.runRequest.bind(provider)
206
+ });
207
+ }
208
+ return [...state.threadIds].sort();
209
+ }
210
+
211
+ async function listNativeThreadsForCwd(cwd) {
212
+ if (!path.isAbsolute(cwd || "") || codexAppServerTextHasControlCharacters(cwd)) throw new TypeError("Native inventory requires an exact absolute directory.");
213
+ const client = await provider.activeClient();
214
+ const state = { entryCount: 0, threadIds: new Set() };
215
+ for (const archived of [false, true]) {
216
+ await listBoundedCodexAppServerThreadIds({ archived, client, cwd, state, verifyCwd: true,
217
+ sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
218
+ errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "native storage",
219
+ requestLabel: "codex-app-server-native-storage-list", runRequest: provider.runRequest.bind(provider) });
220
+ }
221
+ return [...state.threadIds].sort();
222
+ }
223
+
224
+ async function nativeThreadExists(threadId, { signal } = {}) {
225
+ const id = normalizeAgentText(threadId);
226
+ if (!id || id.length > CODEX_APP_SERVER_THREAD_INVENTORY_ID_MAX_LENGTH || codexAppServerTextHasControlCharacters(id)) {
227
+ throw new TypeError("Native existence checks require an exact thread id.");
228
+ }
229
+ const client = await provider.activeClient();
230
+ const state = { entryCount: 0, found: false, threadIds: new Set() };
231
+ for (const archived of [false, true]) {
232
+ await listBoundedCodexAppServerThreadIds({ archived, client, findThreadId: id, state, signal,
233
+ sourceKinds: CODEX_NATIVE_STORAGE_SOURCE_KINDS,
234
+ errorCode: `${provider.errorPrefix}codex_retirement_inventory_invalid`, label: "native storage",
235
+ requestLabel: "codex-app-server-native-existence", runRequest: provider.runRequest.bind(provider) });
236
+ if (state.found) return true;
237
+ }
238
+ return false;
239
+ }
240
+
241
+ async function listHelperThreads({
242
+ signal = null
243
+ } = {}) {
244
+ const client = await provider.activeClient();
245
+ const execution = await provider.currentHelperExecutionContext();
246
+ const state = {
247
+ entryCount: 0,
248
+ threadIds: new Set()
249
+ };
250
+ for (const archived of [false, true]) {
251
+ await listBoundedCodexAppServerThreadIds({
252
+ archived,
253
+ client,
254
+ cwd: execution.cwd,
255
+ errorCode: `${provider.errorPrefix}codex_helper_thread_inventory_invalid`,
256
+ label: "helper",
257
+ requestLabel: "codex-app-server-helper-thread-list",
258
+ runRequest: provider.runRequest.bind(provider),
259
+ signal,
260
+ state
261
+ });
262
+ }
263
+ return Object.freeze({
264
+ threadIds: Object.freeze([...state.threadIds].sort())
265
+ });
266
+ }
267
+
268
+ async function listModels(params = {}, {
269
+ signal = null
270
+ } = {}) {
271
+ const client = await provider.activeClient();
272
+ const data = [];
273
+ const seenCursors = new Set();
274
+ const includeHidden = params.includeHidden === true;
275
+ const limit = normalizePositiveInteger(
276
+ params.limit,
277
+ { fallback: CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT }
278
+ );
279
+ const boundedLimit = Math.min(limit, CODEX_APP_SERVER_MODEL_CATALOG_PAGE_LIMIT);
280
+ let totalBytes = 0;
281
+ let cursor = params.cursor === undefined || params.cursor === null || params.cursor === ""
282
+ ? ""
283
+ : typeof params.cursor === "string"
284
+ ? params.cursor.trim()
285
+ : null;
286
+ if (cursor === null || cursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH) {
287
+ throw provider.modelCatalogError("Codex model catalog cursor is invalid.");
288
+ }
289
+ for (let page = 0; page < CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES; page += 1) {
290
+ if (cursor && seenCursors.has(cursor)) {
291
+ throw provider.modelCatalogError("Codex model catalog repeated a pagination cursor.");
292
+ }
293
+ if (cursor) {
294
+ seenCursors.add(cursor);
295
+ }
296
+ const response = await provider.runRequest(
297
+ () => client.request("model/list", {
298
+ ...(cursor ? { cursor } : {}),
299
+ includeHidden,
300
+ limit: boundedLimit
301
+ }, { signal }),
302
+ "codex-app-server-model-list"
303
+ );
304
+ if (!Array.isArray(response?.data)) {
305
+ throw provider.modelCatalogError(
306
+ "Codex model catalog response did not contain a data array."
307
+ );
308
+ }
309
+ if (
310
+ response.data.length > boundedLimit ||
311
+ data.length + response.data.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRIES
312
+ ) {
313
+ throw provider.modelCatalogError("Codex model catalog exceeded its entry limit.");
314
+ }
315
+ for (const entry of response.data) {
316
+ if (!isRecord(entry)) {
317
+ throw provider.modelCatalogError("Codex model catalog contained an invalid entry.");
318
+ }
319
+ let entryBytes = 0;
320
+ try {
321
+ entryBytes = Buffer.byteLength(JSON.stringify(entry), "utf8");
322
+ } catch {
323
+ throw provider.modelCatalogError("Codex model catalog contained an invalid entry.");
324
+ }
325
+ if (
326
+ entryBytes > CODEX_APP_SERVER_MODEL_CATALOG_MAX_ENTRY_BYTES ||
327
+ totalBytes + entryBytes > CODEX_APP_SERVER_MODEL_CATALOG_MAX_TOTAL_BYTES
328
+ ) {
329
+ throw provider.modelCatalogError("Codex model catalog exceeded its response limit.");
330
+ }
331
+ totalBytes += entryBytes;
332
+ data.push(entry);
333
+ }
334
+ const nextCursor = response.nextCursor === undefined || response.nextCursor === null || response.nextCursor === ""
335
+ ? ""
336
+ : typeof response.nextCursor === "string"
337
+ ? response.nextCursor.trim()
338
+ : null;
339
+ if (nextCursor === null || nextCursor.length > CODEX_APP_SERVER_MODEL_CATALOG_MAX_CURSOR_LENGTH) {
340
+ throw provider.modelCatalogError("Codex model catalog cursor is invalid.");
341
+ }
342
+ if (!nextCursor) {
343
+ return {
344
+ data,
345
+ nextCursor: null
346
+ };
347
+ }
348
+ if (seenCursors.has(nextCursor)) {
349
+ throw provider.modelCatalogError("Codex model catalog repeated a pagination cursor.");
350
+ }
351
+ cursor = nextCursor;
352
+ }
353
+ throw provider.modelCatalogError(
354
+ `Codex model catalog exceeded ${CODEX_APP_SERVER_MODEL_CATALOG_MAX_PAGES} pages.`
355
+ );
356
+ }
357
+
358
+ async function readConfig(params = {}) {
359
+ const client = await provider.activeClient();
360
+ const cwd = normalizeAgentText(params.cwd);
361
+ return provider.runRequest(
362
+ () => client.request("config/read", {
363
+ ...(cwd ? { cwd } : {}),
364
+ includeLayers: params.includeLayers === true
365
+ }),
366
+ "codex-app-server-config-read"
367
+ );
368
+ }
369
+
370
+ async function trustProject(cwd = "") {
371
+ if (!path.isAbsolute(cwd)) throw new TypeError("Codex project trust requires an absolute worktree path.");
372
+ const projectRoot = path.resolve(cwd);
373
+ const client = await provider.activeClient();
374
+ return provider.runRequest(async () => {
375
+ const { config } = await client.request("config/read", { cwd: projectRoot, includeLayers: false });
376
+ if (config?.projects?.[projectRoot]?.trust_level === "trusted") return;
377
+ // Codex discovers project hooks before applying thread config overrides.
378
+ // Trust only this managed worktree through its native configuration API.
379
+ await client.request("config/batchWrite", {
380
+ edits: [{
381
+ keyPath: `projects.${JSON.stringify(projectRoot)}.trust_level`,
382
+ value: "trusted",
383
+ mergeStrategy: "upsert"
384
+ }],
385
+ expectedVersion: null,
386
+ filePath: null,
387
+ reloadUserConfig: true
388
+ });
389
+ }, "codex-app-server-project-trust");
390
+ }
391
+
392
+ async function listHooks(cwds = []) {
393
+ const client = await provider.activeClient();
394
+ return provider.runRequest(
395
+ () => client.request("hooks/list", {
396
+ cwds: (Array.isArray(cwds) ? cwds : [cwds])
397
+ .map(normalizeAgentText)
398
+ .filter(Boolean)
399
+ }),
400
+ "codex-app-server-hooks-list"
401
+ );
402
+ }
403
+
404
+ async function writeHookTrustState(state = {}) {
405
+ if (!isRecord(state) || Object.keys(state).length === 0) {
406
+ return null;
407
+ }
408
+ const client = await provider.activeClient();
409
+ return provider.runRequest(
410
+ () => client.request("config/batchWrite", {
411
+ edits: [{
412
+ keyPath: "hooks.state",
413
+ mergeStrategy: "upsert",
414
+ value: state
415
+ }],
416
+ expectedVersion: null,
417
+ filePath: null,
418
+ reloadUserConfig: true
419
+ }),
420
+ "codex-app-server-hook-trust-write"
421
+ );
422
+ }
423
+
424
+ return Object.freeze({
425
+ listLoadedThreads,
426
+ listAppServerThreadsForCwd,
427
+ listThreadDescendants,
428
+ listNativeThreadsForCwd,
429
+ nativeThreadExists,
430
+ listHelperThreads,
431
+ listModels,
432
+ readConfig,
433
+ trustProject,
434
+ listHooks,
435
+ writeHookTrustState
436
+ });
437
+ }