@jskit-ai/assistant-core 0.2.6 → 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 +18 -12
  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,348 @@
1
+ import { CODEX_APP_SERVER_ACCOUNT_ID_MAX_LENGTH, codexAuthOutputRequiresReconnect, createCodexAccountReader } from "./codexAccountReader.js";
2
+ import { assertCodexAuthPreflightReady } from "./codexAuthPreflight.js";
3
+ import { normalizeNullableText as normalizeAgentText } from "./normalize.js";
4
+ import { tailTextFile } from "./codexProcess.js";
5
+
6
+ /** @typedef {import("./codexProvider.js").CodexAppServerAgentProvider} CodexAppServerAgentProvider */
7
+
8
+ export const codexAppServerHelperAuthError = createCodexAccountReader().error;
9
+
10
+ function codexAppServerHelperLoginParams(auth = {}, errorPrefix = "") {
11
+ if (auth.authMode === "chatgpt") {
12
+ return {
13
+ accessToken: auth.accessToken,
14
+ chatgptAccountId: auth.accountId,
15
+ chatgptPlanType: auth.planType || null,
16
+ type: "chatgptAuthTokens"
17
+ };
18
+ }
19
+ if (auth.authMode === "apiKey") {
20
+ return {
21
+ apiKey: auth.apiKey,
22
+ type: "apiKey"
23
+ };
24
+ }
25
+ throw codexAppServerHelperAuthError(
26
+ `${errorPrefix}codex_helper_auth_invalid`,
27
+ "The selected Codex authentication mode is unsupported. Reconnect Codex and retry."
28
+ );
29
+ }
30
+
31
+ function codexAppServerHelperLoginResponseType(auth = {}) {
32
+ return auth.authMode === "chatgpt" ? "chatgptAuthTokens" : "apiKey";
33
+ }
34
+
35
+ function codexAppServerHelperAccountType(auth = {}) {
36
+ return auth.authMode === "chatgpt" ? "chatgpt" : "apiKey";
37
+ }
38
+
39
+ // Without an application preparing external sign-in, a provider uses Codex's native sign-in.
40
+ function prepareNativeAuth() {
41
+ return "native";
42
+ }
43
+
44
+ function codexPlanUsage(limits) {
45
+ if (limits?.limitId && limits.limitId !== "codex") return { status: "unavailable", windows: [], checkedAt: Date.now() };
46
+ const windows = ["primary", "secondary"].flatMap((id) => {
47
+ const value = limits?.[id];
48
+ if (typeof value?.usedPercent !== "number" || !Number.isFinite(value.usedPercent) || value.usedPercent < 0) return [];
49
+ return [{
50
+ id,
51
+ remainingPercent: Math.max(0, 100 - Math.min(100, value.usedPercent)),
52
+ windowDurationMins: Number.isInteger(value.windowDurationMins) && value.windowDurationMins > 0 ? value.windowDurationMins : null,
53
+ resetsAt: Number.isSafeInteger(value.resetsAt) && value.resetsAt > 0 ? value.resetsAt : null
54
+ }];
55
+ });
56
+ return { status: windows.length ? "available" : "unavailable", windows, checkedAt: Date.now() };
57
+ }
58
+
59
+ export function codexAppServerHelperRequestError(error = null, reason = "", errorPrefix = "") {
60
+ if (
61
+ error?.name === "AbortError" ||
62
+ error?.code === "ABORT_ERR" ||
63
+ (typeof error?.code === "string" && error.code.startsWith(`${errorPrefix}codex_helper_`)) ||
64
+ error?.code === `${errorPrefix}codex_model_catalog_invalid`
65
+ ) {
66
+ return error;
67
+ }
68
+ const failure = new Error("Codex isolated helper execution failed. Retry the task.");
69
+ failure.code = `${errorPrefix}codex_helper_provider_request_failed`;
70
+ const providerCode = Number(error?.code);
71
+ if (Number.isSafeInteger(providerCode)) {
72
+ failure.providerCode = providerCode;
73
+ }
74
+ const normalizedReason = normalizeAgentText(reason);
75
+ if (normalizedReason && normalizedReason.length <= 128) {
76
+ failure.operation = normalizedReason;
77
+ }
78
+ return failure;
79
+ }
80
+ /**
81
+ * The provider's authentication: the helper account it signs its isolated runtime into, the runtime's
82
+ * sign-in checks, and the account's plan usage.
83
+ * @param {object} dependencies
84
+ * @param {CodexAppServerAgentProvider} dependencies.provider
85
+ */
86
+ export function createCodexProviderAuth({ provider }) {
87
+ // The helper account signed into this provider's isolated runtime, and whether helper work is refused.
88
+ let helperAuth = null;
89
+ let helperAuthBlocked = false;
90
+ let planUsage = null;
91
+ let planUsagePending = null;
92
+ // Counts account changes, so a plan-usage read that started before one is discarded.
93
+ let planUsageAuthGeneration = 0;
94
+
95
+ async function selectedHelperAuth() {
96
+ if (!provider.isHelperProvider()) {
97
+ return null;
98
+ }
99
+ if (provider.host.parameters.providerHome) {
100
+ return { identitySignature: await provider.credentials.identity(provider.host.parameters) };
101
+ }
102
+ return provider.accountReader.readSelected(provider.options);
103
+ }
104
+
105
+ async function assertHelperAccountIdentityCurrent() {
106
+ if (!provider.isHelperProvider() || !helperAuth) {
107
+ return;
108
+ }
109
+ if (helperAuthBlocked) {
110
+ throw provider.accountReader.error(
111
+ `${provider.errorPrefix}codex_helper_auth_changed`,
112
+ "The selected Codex account changed during helper work. Retry the task with the current account."
113
+ );
114
+ }
115
+ const current = await selectedHelperAuth();
116
+ if (current.identitySignature !== helperAuth.identitySignature) {
117
+ helperAuthBlocked = true;
118
+ throw provider.accountReader.error(
119
+ `${provider.errorPrefix}codex_helper_auth_changed`,
120
+ "The selected Codex account changed during helper work. Retry the task with the current account."
121
+ );
122
+ }
123
+ }
124
+
125
+ async function authenticateHelperClient(client, runtime = {}) {
126
+ if (!provider.isHelperProvider()) {
127
+ return null;
128
+ }
129
+ const auth = await selectedHelperAuth();
130
+ if (
131
+ !runtime.accountIdentitySignature ||
132
+ auth.identitySignature !== runtime.accountIdentitySignature
133
+ ) {
134
+ throw provider.accountReader.error(
135
+ `${provider.errorPrefix}codex_helper_auth_changed`,
136
+ "The selected Codex account changed while helper execution was starting. Retry the task."
137
+ );
138
+ }
139
+ helperAuth = auth;
140
+ helperAuthBlocked = false;
141
+ if (provider.host.parameters.providerHome) return auth;
142
+ try {
143
+ const login = await client.request(
144
+ "account/login/start",
145
+ codexAppServerHelperLoginParams(auth, provider.errorPrefix)
146
+ );
147
+ if (normalizeAgentText(login?.type) !== codexAppServerHelperLoginResponseType(auth)) {
148
+ throw new Error("invalid login response");
149
+ }
150
+ const account = await client.request("account/read", {
151
+ refreshToken: false
152
+ });
153
+ if (
154
+ account?.requiresOpenaiAuth !== true ||
155
+ normalizeAgentText(account?.account?.type) !== codexAppServerHelperAccountType(auth)
156
+ ) {
157
+ throw new Error("invalid account response");
158
+ }
159
+ return auth;
160
+ } catch {
161
+ helperAuth = null;
162
+ helperAuthBlocked = true;
163
+ throw provider.accountReader.error(
164
+ `${provider.errorPrefix}codex_helper_auth_unavailable`,
165
+ "Codex could not activate the selected account for isolated helper work. Reconnect Codex and retry."
166
+ );
167
+ }
168
+ }
169
+
170
+ async function refreshHelperChatgptAuth(params = {}) {
171
+ const currentAuth = helperAuth;
172
+ const previousAccountId = params.previousAccountId;
173
+ if (
174
+ !provider.isHelperProvider() ||
175
+ currentAuth?.authMode !== "chatgpt" ||
176
+ params.reason !== "unauthorized" ||
177
+ typeof previousAccountId !== "string" ||
178
+ previousAccountId.length === 0 ||
179
+ previousAccountId.length > CODEX_APP_SERVER_ACCOUNT_ID_MAX_LENGTH ||
180
+ previousAccountId !== currentAuth.accountId
181
+ ) {
182
+ helperAuthBlocked = true;
183
+ throw provider.accountReader.error(
184
+ `${provider.errorPrefix}codex_helper_auth_refresh_rejected`,
185
+ "Codex helper authentication refresh was rejected. Reconnect Codex and retry."
186
+ );
187
+ }
188
+ const refreshed = await selectedHelperAuth();
189
+ if (
190
+ refreshed.authMode !== "chatgpt" ||
191
+ refreshed.identitySignature !== currentAuth.identitySignature
192
+ ) {
193
+ helperAuthBlocked = true;
194
+ throw provider.accountReader.error(
195
+ `${provider.errorPrefix}codex_helper_auth_changed`,
196
+ "The selected Codex account changed during helper work. Retry the task with the current account."
197
+ );
198
+ }
199
+ if (refreshed.secretSignature === currentAuth.secretSignature) {
200
+ helperAuthBlocked = true;
201
+ throw provider.accountReader.error(
202
+ `${provider.errorPrefix}codex_helper_auth_refresh_pending`,
203
+ "The selected Codex account has not produced a newer access token yet. Reconnect Codex and retry."
204
+ );
205
+ }
206
+ helperAuth = refreshed;
207
+ return {
208
+ accessToken: refreshed.accessToken,
209
+ chatgptAccountId: refreshed.accountId,
210
+ chatgptPlanType: refreshed.planType || null
211
+ };
212
+ }
213
+
214
+ async function preflightAuth(reason = "codex-auth-preflight", modelProviderId = "") {
215
+ const { prepareAuth = prepareNativeAuth } = provider.options;
216
+ if (await prepareAuth(modelProviderId) === "external") {
217
+ await provider.assertRuntimeAuthReady(reason);
218
+ return;
219
+ }
220
+ if (provider.options.modelProviderId && provider.options.modelProviderId !== "openai") {
221
+ await provider.credentials.assertCurrent("");
222
+ return;
223
+ }
224
+ await provider.assertRuntimeAuthReady(reason);
225
+ try {
226
+ return await assertCodexAuthPreflightReady({
227
+ ...provider.options, codexCommand: provider.host.parameters.codexCommand, env: provider.host.parameters.env
228
+ }, {
229
+ execution: provider.host.execution, credentials: provider.credentials, reason
230
+ });
231
+ } catch (error) {
232
+ if (error?.code === provider.credentials.reconnectRequiredCode && provider.runtime?.runtimeDir) {
233
+ await provider.stopRuntimeAndRequireDrain(reason);
234
+ }
235
+ throw error;
236
+ }
237
+ }
238
+
239
+ async function assertRuntimeAuthReady(reason = "codex-app-server") {
240
+ if (provider.isHelperProvider() || provider.stale) {
241
+ return;
242
+ }
243
+ const runtime = provider.runtime || {};
244
+ let generationError = null;
245
+ try {
246
+ await provider.credentials.assertCurrent(runtime.authStateSignature);
247
+ } catch (error) {
248
+ generationError = error;
249
+ }
250
+ const logTail = await tailTextFile(runtime.logPath || "");
251
+ const reconnectRequired = codexAuthOutputRequiresReconnect(logTail);
252
+ if (!generationError && !reconnectRequired) {
253
+ return;
254
+ }
255
+ await provider.stopRuntimeAndRequireDrain(reason);
256
+ if (reconnectRequired) {
257
+ await provider.markReconnectRequired({
258
+ toolHomeSource: runtime.toolHomeSource || provider.options.toolHomeSource,
259
+ observed: logTail,
260
+ reason
261
+ });
262
+ throw provider.credentials.reconnectError({
263
+ observed: logTail
264
+ });
265
+ }
266
+ throw generationError;
267
+ }
268
+
269
+ async function markReconnectRequired({ reason = "codex-app-server", observed = "", toolHomeSource } = {}) {
270
+ await provider.credentials.markInvalid({ reason, toolHomeSource });
271
+ throw provider.credentials.reconnectError({ modelProviderId: provider.options.modelProviderId, observed });
272
+ }
273
+
274
+ async function readPlanUsage() {
275
+ const client = provider.client;
276
+ if (!client?.isOpen()) return { status: "unavailable", windows: [] };
277
+ if (planUsage && Date.now() - planUsage.checkedAt < 60_000) return planUsage;
278
+ if (planUsagePending) return planUsagePending;
279
+ const previous = planUsage;
280
+ const authGeneration = planUsageAuthGeneration;
281
+ const signal = AbortSignal.timeout(10_000);
282
+ const operation = (async () => {
283
+ try {
284
+ const account = await client.request("account/read", { refreshToken: false }, { signal });
285
+ if (provider.client !== client || planUsageAuthGeneration !== authGeneration) return { status: "unavailable", windows: [] };
286
+ if (account?.account?.type !== "chatgpt") {
287
+ planUsage = { status: "unsupported", windows: [], checkedAt: Date.now() };
288
+ } else {
289
+ const result = await client.request("account/rateLimits/read", {}, { signal });
290
+ if (provider.client !== client || planUsageAuthGeneration !== authGeneration) return { status: "unavailable", windows: [] };
291
+ // A live event received during the read is newer than this request.
292
+ if (planUsage === previous) planUsage = codexPlanUsage(result.rateLimitsByLimitId?.codex || result.rateLimits);
293
+ }
294
+ return planUsage;
295
+ } catch {
296
+ return { status: "unavailable", windows: [] };
297
+ }
298
+ })();
299
+ planUsagePending = operation;
300
+ try { return await operation; }
301
+ finally { if (planUsagePending === operation) planUsagePending = null; }
302
+ }
303
+
304
+ // A new connection reads the account's plan usage afresh.
305
+ function resetPlanUsage() {
306
+ planUsage = null;
307
+ planUsagePending = null;
308
+ }
309
+
310
+ // Account notifications change the plan usage the provider reports.
311
+ function observeAccountNotification(notification) {
312
+ if (notification.method === "account/updated") {
313
+ planUsage = null;
314
+ planUsageAuthGeneration += 1;
315
+ }
316
+ if (notification.method === "account/rateLimits/updated") {
317
+ const limits = notification.params?.rateLimits;
318
+ if (!limits?.limitId || limits.limitId === "codex") planUsage = codexPlanUsage(limits);
319
+ }
320
+ }
321
+
322
+ // A helper runtime that failed to start refuses helper work until it signs in again.
323
+ function blockHelper() {
324
+ helperAuth = null;
325
+ helperAuthBlocked = true;
326
+ }
327
+
328
+ // A closed provider keeps no helper account.
329
+ function forgetHelper() {
330
+ helperAuth = null;
331
+ helperAuthBlocked = false;
332
+ }
333
+
334
+ return Object.freeze({
335
+ selectedHelperAuth,
336
+ assertHelperAccountIdentityCurrent,
337
+ authenticateHelperClient,
338
+ refreshHelperChatgptAuth,
339
+ preflightAuth,
340
+ assertRuntimeAuthReady,
341
+ markReconnectRequired,
342
+ readPlanUsage,
343
+ resetPlanUsage,
344
+ observeAccountNotification,
345
+ blockHelper,
346
+ forgetHelper
347
+ });
348
+ }