oc-chatgpt-multi-auth 4.9.0

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 (134) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +507 -0
  3. package/assets/opencode-logo-ornate-dark.svg +18 -0
  4. package/assets/readme-hero.svg +31 -0
  5. package/config/README.md +110 -0
  6. package/config/minimal-opencode.json +13 -0
  7. package/config/opencode-legacy.json +572 -0
  8. package/config/opencode-modern.json +240 -0
  9. package/dist/index.d.ts +45 -0
  10. package/dist/index.d.ts.map +1 -0
  11. package/dist/index.js +971 -0
  12. package/dist/index.js.map +1 -0
  13. package/dist/lib/accounts.d.ts +120 -0
  14. package/dist/lib/accounts.d.ts.map +1 -0
  15. package/dist/lib/accounts.js +579 -0
  16. package/dist/lib/accounts.js.map +1 -0
  17. package/dist/lib/auth/auth.d.ts +51 -0
  18. package/dist/lib/auth/auth.d.ts.map +1 -0
  19. package/dist/lib/auth/auth.js +180 -0
  20. package/dist/lib/auth/auth.js.map +1 -0
  21. package/dist/lib/auth/browser.d.ts +17 -0
  22. package/dist/lib/auth/browser.d.ts.map +1 -0
  23. package/dist/lib/auth/browser.js +83 -0
  24. package/dist/lib/auth/browser.js.map +1 -0
  25. package/dist/lib/auth/server.d.ts +10 -0
  26. package/dist/lib/auth/server.d.ts.map +1 -0
  27. package/dist/lib/auth/server.js +85 -0
  28. package/dist/lib/auth/server.js.map +1 -0
  29. package/dist/lib/auto-update-checker.d.ts +10 -0
  30. package/dist/lib/auto-update-checker.d.ts.map +1 -0
  31. package/dist/lib/auto-update-checker.js +129 -0
  32. package/dist/lib/auto-update-checker.js.map +1 -0
  33. package/dist/lib/cli.d.ts +9 -0
  34. package/dist/lib/cli.d.ts.map +1 -0
  35. package/dist/lib/cli.js +50 -0
  36. package/dist/lib/cli.js.map +1 -0
  37. package/dist/lib/config.d.ts +17 -0
  38. package/dist/lib/config.d.ts.map +1 -0
  39. package/dist/lib/config.js +102 -0
  40. package/dist/lib/config.js.map +1 -0
  41. package/dist/lib/constants.d.ts +74 -0
  42. package/dist/lib/constants.d.ts.map +1 -0
  43. package/dist/lib/constants.js +74 -0
  44. package/dist/lib/constants.js.map +1 -0
  45. package/dist/lib/context-overflow.d.ts +27 -0
  46. package/dist/lib/context-overflow.d.ts.map +1 -0
  47. package/dist/lib/context-overflow.js +124 -0
  48. package/dist/lib/context-overflow.js.map +1 -0
  49. package/dist/lib/index.d.ts +13 -0
  50. package/dist/lib/index.d.ts.map +1 -0
  51. package/dist/lib/index.js +13 -0
  52. package/dist/lib/index.js.map +1 -0
  53. package/dist/lib/logger.d.ts +22 -0
  54. package/dist/lib/logger.d.ts.map +1 -0
  55. package/dist/lib/logger.js +175 -0
  56. package/dist/lib/logger.js.map +1 -0
  57. package/dist/lib/oauth-success.html +712 -0
  58. package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
  59. package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
  60. package/dist/lib/prompts/codex-opencode-bridge.js +152 -0
  61. package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
  62. package/dist/lib/prompts/codex.d.ts +32 -0
  63. package/dist/lib/prompts/codex.d.ts.map +1 -0
  64. package/dist/lib/prompts/codex.js +262 -0
  65. package/dist/lib/prompts/codex.js.map +1 -0
  66. package/dist/lib/prompts/opencode-codex.d.ts +21 -0
  67. package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
  68. package/dist/lib/prompts/opencode-codex.js +91 -0
  69. package/dist/lib/prompts/opencode-codex.js.map +1 -0
  70. package/dist/lib/recovery/constants.d.ts +12 -0
  71. package/dist/lib/recovery/constants.d.ts.map +1 -0
  72. package/dist/lib/recovery/constants.js +25 -0
  73. package/dist/lib/recovery/constants.js.map +1 -0
  74. package/dist/lib/recovery/index.d.ts +12 -0
  75. package/dist/lib/recovery/index.d.ts.map +1 -0
  76. package/dist/lib/recovery/index.js +12 -0
  77. package/dist/lib/recovery/index.js.map +1 -0
  78. package/dist/lib/recovery/storage.d.ts +24 -0
  79. package/dist/lib/recovery/storage.d.ts.map +1 -0
  80. package/dist/lib/recovery/storage.js +354 -0
  81. package/dist/lib/recovery/storage.js.map +1 -0
  82. package/dist/lib/recovery/types.d.ts +116 -0
  83. package/dist/lib/recovery/types.d.ts.map +1 -0
  84. package/dist/lib/recovery/types.js +7 -0
  85. package/dist/lib/recovery/types.js.map +1 -0
  86. package/dist/lib/recovery.d.ts +31 -0
  87. package/dist/lib/recovery.d.ts.map +1 -0
  88. package/dist/lib/recovery.js +308 -0
  89. package/dist/lib/recovery.js.map +1 -0
  90. package/dist/lib/refresh-queue.d.ts +100 -0
  91. package/dist/lib/refresh-queue.d.ts.map +1 -0
  92. package/dist/lib/refresh-queue.js +196 -0
  93. package/dist/lib/refresh-queue.js.map +1 -0
  94. package/dist/lib/request/fetch-helpers.d.ts +81 -0
  95. package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
  96. package/dist/lib/request/fetch-helpers.js +325 -0
  97. package/dist/lib/request/fetch-helpers.js.map +1 -0
  98. package/dist/lib/request/helpers/input-utils.d.ts +7 -0
  99. package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
  100. package/dist/lib/request/helpers/input-utils.js +213 -0
  101. package/dist/lib/request/helpers/input-utils.js.map +1 -0
  102. package/dist/lib/request/helpers/model-map.d.ts +28 -0
  103. package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
  104. package/dist/lib/request/helpers/model-map.js +109 -0
  105. package/dist/lib/request/helpers/model-map.js.map +1 -0
  106. package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
  107. package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
  108. package/dist/lib/request/rate-limit-backoff.js +74 -0
  109. package/dist/lib/request/rate-limit-backoff.js.map +1 -0
  110. package/dist/lib/request/request-transformer.d.ts +93 -0
  111. package/dist/lib/request/request-transformer.d.ts.map +1 -0
  112. package/dist/lib/request/request-transformer.js +405 -0
  113. package/dist/lib/request/request-transformer.js.map +1 -0
  114. package/dist/lib/request/response-handler.d.ts +14 -0
  115. package/dist/lib/request/response-handler.d.ts.map +1 -0
  116. package/dist/lib/request/response-handler.js +90 -0
  117. package/dist/lib/request/response-handler.js.map +1 -0
  118. package/dist/lib/rotation.d.ts +121 -0
  119. package/dist/lib/rotation.d.ts.map +1 -0
  120. package/dist/lib/rotation.js +248 -0
  121. package/dist/lib/rotation.js.map +1 -0
  122. package/dist/lib/storage.d.ts +91 -0
  123. package/dist/lib/storage.d.ts.map +1 -0
  124. package/dist/lib/storage.js +323 -0
  125. package/dist/lib/storage.js.map +1 -0
  126. package/dist/lib/types.d.ts +185 -0
  127. package/dist/lib/types.d.ts.map +1 -0
  128. package/dist/lib/types.js +2 -0
  129. package/dist/lib/types.js.map +1 -0
  130. package/package.json +86 -0
  131. package/scripts/copy-oauth-success.js +37 -0
  132. package/scripts/install-opencode-codex-auth.js +193 -0
  133. package/scripts/test-all-models.sh +260 -0
  134. package/scripts/validate-model-map.sh +97 -0
@@ -0,0 +1,308 @@
1
+ import { createLogger } from "./logger.js";
2
+ import { readParts, findMessagesWithThinkingBlocks, findMessagesWithOrphanThinking, findMessageByIndexNeedingThinking, prependThinkingPart, stripThinkingParts, } from "./recovery/storage.js";
3
+ const RECOVERY_RESUME_TEXT = "[session recovered - continuing previous task]";
4
+ function getErrorMessage(error) {
5
+ if (!error)
6
+ return "";
7
+ if (typeof error === "string")
8
+ return error.toLowerCase();
9
+ const errorObj = error;
10
+ const paths = [
11
+ errorObj.data,
12
+ errorObj.error,
13
+ errorObj,
14
+ errorObj.data?.error,
15
+ ];
16
+ for (const obj of paths) {
17
+ if (obj && typeof obj === "object") {
18
+ const msg = obj.message;
19
+ if (typeof msg === "string" && msg.length > 0) {
20
+ return msg.toLowerCase();
21
+ }
22
+ }
23
+ }
24
+ try {
25
+ return JSON.stringify(error).toLowerCase();
26
+ }
27
+ catch {
28
+ return "";
29
+ }
30
+ }
31
+ function extractMessageIndex(error) {
32
+ const message = getErrorMessage(error);
33
+ const match = message.match(/messages\.(\d+)/);
34
+ if (!match || !match[1])
35
+ return null;
36
+ return parseInt(match[1], 10);
37
+ }
38
+ export function detectErrorType(error) {
39
+ const message = getErrorMessage(error);
40
+ if (message.includes("tool_use") && message.includes("tool_result")) {
41
+ return "tool_result_missing";
42
+ }
43
+ if (message.includes("thinking") &&
44
+ (message.includes("first block") ||
45
+ message.includes("must start with") ||
46
+ message.includes("preceeding") ||
47
+ (message.includes("expected") && message.includes("found")))) {
48
+ return "thinking_block_order";
49
+ }
50
+ if (message.includes("thinking is disabled") && message.includes("cannot contain")) {
51
+ return "thinking_disabled_violation";
52
+ }
53
+ return null;
54
+ }
55
+ export function isRecoverableError(error) {
56
+ return detectErrorType(error) !== null;
57
+ }
58
+ function extractToolUseIds(parts) {
59
+ return parts
60
+ .filter((p) => p.type === "tool_use" && !!p.id)
61
+ .map((p) => p.id);
62
+ }
63
+ async function recoverToolResultMissing(client, sessionID, failedMsg) {
64
+ let parts = failedMsg.parts || [];
65
+ if (parts.length === 0 && failedMsg.info?.id) {
66
+ const storedParts = readParts(failedMsg.info.id);
67
+ parts = storedParts.map((p) => ({
68
+ type: p.type === "tool" ? "tool_use" : p.type,
69
+ id: "callID" in p ? p.callID : p.id,
70
+ name: "tool" in p ? p.tool : undefined,
71
+ input: "state" in p ? p.state?.input : undefined,
72
+ }));
73
+ }
74
+ const toolUseIds = extractToolUseIds(parts);
75
+ if (toolUseIds.length === 0) {
76
+ return false;
77
+ }
78
+ const toolResultParts = toolUseIds.map((id) => ({
79
+ type: "tool_result",
80
+ tool_use_id: id,
81
+ content: "Operation cancelled by user (ESC pressed)",
82
+ }));
83
+ try {
84
+ await client.session.prompt({
85
+ path: { id: sessionID },
86
+ // @ts-expect-error - SDK types may not include tool_result parts
87
+ body: { parts: toolResultParts },
88
+ });
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ async function recoverThinkingBlockOrder(sessionID, _failedMsg, error) {
96
+ const targetIndex = extractMessageIndex(error);
97
+ if (targetIndex !== null) {
98
+ const targetMessageID = findMessageByIndexNeedingThinking(sessionID, targetIndex);
99
+ if (targetMessageID) {
100
+ return prependThinkingPart(sessionID, targetMessageID);
101
+ }
102
+ }
103
+ const orphanMessages = findMessagesWithOrphanThinking(sessionID);
104
+ if (orphanMessages.length === 0) {
105
+ return false;
106
+ }
107
+ let anySuccess = false;
108
+ for (const messageID of orphanMessages) {
109
+ if (prependThinkingPart(sessionID, messageID)) {
110
+ anySuccess = true;
111
+ }
112
+ }
113
+ return anySuccess;
114
+ }
115
+ async function recoverThinkingDisabledViolation(sessionID, _failedMsg) {
116
+ const messagesWithThinking = findMessagesWithThinkingBlocks(sessionID);
117
+ if (messagesWithThinking.length === 0) {
118
+ return false;
119
+ }
120
+ let anySuccess = false;
121
+ for (const messageID of messagesWithThinking) {
122
+ if (stripThinkingParts(messageID)) {
123
+ anySuccess = true;
124
+ }
125
+ }
126
+ return anySuccess;
127
+ }
128
+ function findLastUserMessage(messages) {
129
+ for (let i = messages.length - 1; i >= 0; i--) {
130
+ if (messages[i]?.info?.role === "user") {
131
+ return messages[i];
132
+ }
133
+ }
134
+ return undefined;
135
+ }
136
+ function extractResumeConfig(userMessage, sessionID) {
137
+ return {
138
+ sessionID,
139
+ agent: userMessage?.info?.agent,
140
+ model: userMessage?.info?.model,
141
+ };
142
+ }
143
+ async function resumeSession(client, config, directory) {
144
+ try {
145
+ await client.session.prompt({
146
+ path: { id: config.sessionID },
147
+ body: {
148
+ parts: [{ type: "text", text: RECOVERY_RESUME_TEXT }],
149
+ agent: config.agent,
150
+ model: config.model,
151
+ },
152
+ query: { directory },
153
+ });
154
+ return true;
155
+ }
156
+ catch {
157
+ return false;
158
+ }
159
+ }
160
+ const TOAST_TITLES = {
161
+ tool_result_missing: "Tool Crash Recovery",
162
+ thinking_block_order: "Thinking Block Recovery",
163
+ thinking_disabled_violation: "Thinking Strip Recovery",
164
+ };
165
+ const TOAST_MESSAGES = {
166
+ tool_result_missing: "Injecting cancelled tool results...",
167
+ thinking_block_order: "Fixing message structure...",
168
+ thinking_disabled_violation: "Stripping thinking blocks...",
169
+ };
170
+ export function getRecoveryToastContent(errorType) {
171
+ if (!errorType) {
172
+ return {
173
+ title: "Session Recovery",
174
+ message: "Attempting to recover session...",
175
+ };
176
+ }
177
+ return {
178
+ title: TOAST_TITLES[errorType] || "Session Recovery",
179
+ message: TOAST_MESSAGES[errorType] || "Attempting to recover session...",
180
+ };
181
+ }
182
+ export function getRecoverySuccessToast() {
183
+ return {
184
+ title: "Session Recovered",
185
+ message: "Continuing where you left off...",
186
+ };
187
+ }
188
+ export function getRecoveryFailureToast() {
189
+ return {
190
+ title: "Recovery Failed",
191
+ message: "Please retry or start a new session.",
192
+ };
193
+ }
194
+ export function createSessionRecoveryHook(ctx, config) {
195
+ if (!config.sessionRecovery) {
196
+ return null;
197
+ }
198
+ const { client, directory } = ctx;
199
+ const processingErrors = new Set();
200
+ let onAbortCallback = null;
201
+ let onRecoveryCompleteCallback = null;
202
+ const setOnAbortCallback = (callback) => {
203
+ onAbortCallback = callback;
204
+ };
205
+ const setOnRecoveryCompleteCallback = (callback) => {
206
+ onRecoveryCompleteCallback = callback;
207
+ };
208
+ const handleSessionRecovery = async (info) => {
209
+ if (!info || info.role !== "assistant" || !info.error)
210
+ return false;
211
+ const errorType = detectErrorType(info.error);
212
+ if (!errorType)
213
+ return false;
214
+ const sessionID = info.sessionID;
215
+ if (!sessionID)
216
+ return false;
217
+ let assistantMsgID = info.id;
218
+ const log = createLogger("session-recovery");
219
+ log.debug("Recovery attempt started", {
220
+ errorType,
221
+ sessionID,
222
+ providedMsgID: assistantMsgID ?? "none",
223
+ });
224
+ if (onAbortCallback) {
225
+ onAbortCallback(sessionID);
226
+ }
227
+ await client.session.abort({ path: { id: sessionID } }).catch(() => { });
228
+ const messagesResp = await client.session.messages({
229
+ path: { id: sessionID },
230
+ query: { directory },
231
+ });
232
+ const msgs = messagesResp.data;
233
+ if (!assistantMsgID && msgs && msgs.length > 0) {
234
+ for (let i = msgs.length - 1; i >= 0; i--) {
235
+ const m = msgs[i];
236
+ if (m && m.info?.role === "assistant" && m.info?.id) {
237
+ assistantMsgID = m.info.id;
238
+ log.debug("Found assistant message ID from session messages", {
239
+ msgID: assistantMsgID,
240
+ msgIndex: i,
241
+ });
242
+ break;
243
+ }
244
+ }
245
+ }
246
+ if (!assistantMsgID) {
247
+ log.debug("No assistant message ID found, cannot recover");
248
+ return false;
249
+ }
250
+ if (processingErrors.has(assistantMsgID))
251
+ return false;
252
+ processingErrors.add(assistantMsgID);
253
+ try {
254
+ const failedMsg = msgs?.find((m) => m.info?.id === assistantMsgID);
255
+ if (!failedMsg) {
256
+ return false;
257
+ }
258
+ const toastContent = getRecoveryToastContent(errorType);
259
+ await client.tui
260
+ .showToast({
261
+ body: {
262
+ title: toastContent.title,
263
+ message: toastContent.message,
264
+ variant: "warning",
265
+ },
266
+ })
267
+ .catch(() => { });
268
+ let success = false;
269
+ if (errorType === "tool_result_missing") {
270
+ success = await recoverToolResultMissing(client, sessionID, failedMsg);
271
+ }
272
+ else if (errorType === "thinking_block_order") {
273
+ success = await recoverThinkingBlockOrder(sessionID, failedMsg, info.error);
274
+ if (success && config.autoResume) {
275
+ const lastUser = findLastUserMessage(msgs ?? []);
276
+ const resumeConfig = extractResumeConfig(lastUser, sessionID);
277
+ await resumeSession(client, resumeConfig, directory);
278
+ }
279
+ }
280
+ else if (errorType === "thinking_disabled_violation") {
281
+ success = await recoverThinkingDisabledViolation(sessionID, failedMsg);
282
+ if (success && config.autoResume) {
283
+ const lastUser = findLastUserMessage(msgs ?? []);
284
+ const resumeConfig = extractResumeConfig(lastUser, sessionID);
285
+ await resumeSession(client, resumeConfig, directory);
286
+ }
287
+ }
288
+ return success;
289
+ }
290
+ catch (err) {
291
+ log.error("Recovery failed", { error: String(err) });
292
+ return false;
293
+ }
294
+ finally {
295
+ processingErrors.delete(assistantMsgID);
296
+ if (sessionID && onRecoveryCompleteCallback) {
297
+ onRecoveryCompleteCallback(sessionID);
298
+ }
299
+ }
300
+ };
301
+ return {
302
+ handleSessionRecovery,
303
+ isRecoverableError,
304
+ setOnAbortCallback,
305
+ setOnRecoveryCompleteCallback,
306
+ };
307
+ }
308
+ //# sourceMappingURL=recovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../lib/recovery.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Refresh Queue Module
3
+ *
4
+ * Prevents race conditions when multiple concurrent requests try to refresh
5
+ * the same account's token simultaneously. Instead of firing parallel refresh
6
+ * requests, subsequent callers await the existing in-flight refresh.
7
+ *
8
+ * Ported from antigravity-auth refresh-queue.ts pattern.
9
+ */
10
+ import type { TokenResult } from "./types.js";
11
+ /**
12
+ * Manages queued token refresh operations to prevent race conditions.
13
+ *
14
+ * When multiple concurrent requests need to refresh the same account's token,
15
+ * only the first request triggers the actual refresh. Subsequent requests
16
+ * await the same promise, ensuring:
17
+ * - No duplicate refresh API calls for the same refresh token
18
+ * - Consistent token state across all waiting callers
19
+ * - Reduced load on OpenAI's token endpoint
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const queue = new RefreshQueue();
24
+ *
25
+ * // These three concurrent calls will only trigger ONE actual refresh
26
+ * const [result1, result2, result3] = await Promise.all([
27
+ * queue.refresh(refreshToken),
28
+ * queue.refresh(refreshToken),
29
+ * queue.refresh(refreshToken),
30
+ * ]);
31
+ *
32
+ * // All three get the same result
33
+ * console.log(result1 === result2); // true (same object reference)
34
+ * ```
35
+ */
36
+ export declare class RefreshQueue {
37
+ private pending;
38
+ /**
39
+ * Maximum time to keep a refresh entry in the queue (prevents memory leaks
40
+ * from stuck requests). After this timeout, the entry is removed and new
41
+ * callers will trigger a fresh refresh.
42
+ */
43
+ private readonly maxEntryAgeMs;
44
+ /**
45
+ * Create a new RefreshQueue instance.
46
+ * @param maxEntryAgeMs - Maximum age for pending entries before cleanup (default: 30s)
47
+ */
48
+ constructor(maxEntryAgeMs?: number);
49
+ /**
50
+ * Refresh a token, deduplicating concurrent requests for the same refresh token.
51
+ *
52
+ * If a refresh is already in-flight for this token, returns the existing promise.
53
+ * Otherwise, initiates a new refresh and caches the promise for other callers.
54
+ *
55
+ * @param refreshToken - The refresh token to use
56
+ * @returns Token result (success with new tokens, or failure)
57
+ */
58
+ refresh(refreshToken: string): Promise<TokenResult>;
59
+ /**
60
+ * Execute the actual refresh and log results.
61
+ */
62
+ private executeRefresh;
63
+ /**
64
+ * Remove stale entries that have been pending too long.
65
+ * This prevents memory leaks from stuck or abandoned refresh operations.
66
+ */
67
+ private cleanup;
68
+ /**
69
+ * Check if there's an in-flight refresh for a given token.
70
+ * @param refreshToken - The refresh token to check
71
+ * @returns True if refresh is in progress
72
+ */
73
+ isRefreshing(refreshToken: string): boolean;
74
+ /**
75
+ * Get the number of pending refresh operations.
76
+ * Useful for debugging and monitoring.
77
+ */
78
+ get pendingCount(): number;
79
+ /**
80
+ * Clear all pending entries (primarily for testing).
81
+ */
82
+ clear(): void;
83
+ }
84
+ /**
85
+ * Get the singleton RefreshQueue instance.
86
+ * @param maxEntryAgeMs - Maximum age for pending entries (only used on first call)
87
+ * @returns The global RefreshQueue instance
88
+ */
89
+ export declare function getRefreshQueue(maxEntryAgeMs?: number): RefreshQueue;
90
+ /**
91
+ * Reset the singleton instance (primarily for testing).
92
+ */
93
+ export declare function resetRefreshQueue(): void;
94
+ /**
95
+ * Convenience function to refresh a token using the singleton queue.
96
+ * @param refreshToken - The refresh token to use
97
+ * @returns Token result
98
+ */
99
+ export declare function queuedRefresh(refreshToken: string): Promise<TokenResult>;
100
+ //# sourceMappingURL=refresh-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-queue.d.ts","sourceRoot":"","sources":["../../lib/refresh-queue.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAa9C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAAwC;IAEvD;;;;OAIG;IACH,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IAEvC;;;OAGG;gBACS,aAAa,GAAE,MAAe;IAI1C;;;;;;;;OAQG;IACG,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IA4BzD;;OAEG;YACW,cAAc;IAsC5B;;;OAGG;IACH,OAAO,CAAC,OAAO;IAmBf;;;;OAIG;IACH,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO;IAI3C;;;OAGG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACH,KAAK,IAAI,IAAI;CAGd;AAQD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAKpE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAGxC;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAE9E"}
@@ -0,0 +1,196 @@
1
+ /**
2
+ * Refresh Queue Module
3
+ *
4
+ * Prevents race conditions when multiple concurrent requests try to refresh
5
+ * the same account's token simultaneously. Instead of firing parallel refresh
6
+ * requests, subsequent callers await the existing in-flight refresh.
7
+ *
8
+ * Ported from antigravity-auth refresh-queue.ts pattern.
9
+ */
10
+ import { refreshAccessToken } from "./auth/auth.js";
11
+ import { createLogger } from "./logger.js";
12
+ const log = createLogger("refresh-queue");
13
+ /**
14
+ * Manages queued token refresh operations to prevent race conditions.
15
+ *
16
+ * When multiple concurrent requests need to refresh the same account's token,
17
+ * only the first request triggers the actual refresh. Subsequent requests
18
+ * await the same promise, ensuring:
19
+ * - No duplicate refresh API calls for the same refresh token
20
+ * - Consistent token state across all waiting callers
21
+ * - Reduced load on OpenAI's token endpoint
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * const queue = new RefreshQueue();
26
+ *
27
+ * // These three concurrent calls will only trigger ONE actual refresh
28
+ * const [result1, result2, result3] = await Promise.all([
29
+ * queue.refresh(refreshToken),
30
+ * queue.refresh(refreshToken),
31
+ * queue.refresh(refreshToken),
32
+ * ]);
33
+ *
34
+ * // All three get the same result
35
+ * console.log(result1 === result2); // true (same object reference)
36
+ * ```
37
+ */
38
+ export class RefreshQueue {
39
+ pending = new Map();
40
+ /**
41
+ * Maximum time to keep a refresh entry in the queue (prevents memory leaks
42
+ * from stuck requests). After this timeout, the entry is removed and new
43
+ * callers will trigger a fresh refresh.
44
+ */
45
+ maxEntryAgeMs;
46
+ /**
47
+ * Create a new RefreshQueue instance.
48
+ * @param maxEntryAgeMs - Maximum age for pending entries before cleanup (default: 30s)
49
+ */
50
+ constructor(maxEntryAgeMs = 30_000) {
51
+ this.maxEntryAgeMs = maxEntryAgeMs;
52
+ }
53
+ /**
54
+ * Refresh a token, deduplicating concurrent requests for the same refresh token.
55
+ *
56
+ * If a refresh is already in-flight for this token, returns the existing promise.
57
+ * Otherwise, initiates a new refresh and caches the promise for other callers.
58
+ *
59
+ * @param refreshToken - The refresh token to use
60
+ * @returns Token result (success with new tokens, or failure)
61
+ */
62
+ async refresh(refreshToken) {
63
+ // Clean up stale entries first
64
+ this.cleanup();
65
+ // Check for existing in-flight refresh
66
+ const existing = this.pending.get(refreshToken);
67
+ if (existing) {
68
+ log.info("Reusing in-flight refresh for token", {
69
+ tokenSuffix: refreshToken.slice(-6),
70
+ waitingMs: Date.now() - existing.startedAt,
71
+ });
72
+ return existing.promise;
73
+ }
74
+ // Start a new refresh
75
+ const startedAt = Date.now();
76
+ const promise = this.executeRefresh(refreshToken);
77
+ this.pending.set(refreshToken, { promise, startedAt });
78
+ try {
79
+ return await promise;
80
+ }
81
+ finally {
82
+ // Clean up after completion
83
+ this.pending.delete(refreshToken);
84
+ }
85
+ }
86
+ /**
87
+ * Execute the actual refresh and log results.
88
+ */
89
+ async executeRefresh(refreshToken) {
90
+ const startTime = Date.now();
91
+ log.info("Starting token refresh", { tokenSuffix: refreshToken.slice(-6) });
92
+ try {
93
+ const result = await refreshAccessToken(refreshToken);
94
+ const duration = Date.now() - startTime;
95
+ if (result.type === "success") {
96
+ log.info("Token refresh succeeded", {
97
+ tokenSuffix: refreshToken.slice(-6),
98
+ durationMs: duration,
99
+ });
100
+ }
101
+ else {
102
+ log.warn("Token refresh failed", {
103
+ tokenSuffix: refreshToken.slice(-6),
104
+ reason: result.reason,
105
+ durationMs: duration,
106
+ });
107
+ }
108
+ return result;
109
+ }
110
+ catch (error) {
111
+ const duration = Date.now() - startTime;
112
+ log.error("Token refresh threw exception", {
113
+ tokenSuffix: refreshToken.slice(-6),
114
+ error: error?.message ?? String(error),
115
+ durationMs: duration,
116
+ });
117
+ return {
118
+ type: "failed",
119
+ reason: "network_error",
120
+ message: error?.message ?? "Unknown error during refresh",
121
+ };
122
+ }
123
+ }
124
+ /**
125
+ * Remove stale entries that have been pending too long.
126
+ * This prevents memory leaks from stuck or abandoned refresh operations.
127
+ */
128
+ cleanup() {
129
+ const now = Date.now();
130
+ const staleTokens = [];
131
+ for (const [token, entry] of this.pending.entries()) {
132
+ if (now - entry.startedAt > this.maxEntryAgeMs) {
133
+ staleTokens.push(token);
134
+ }
135
+ }
136
+ for (const token of staleTokens) {
137
+ log.warn("Removing stale refresh entry", {
138
+ tokenSuffix: token.slice(-6),
139
+ ageMs: now - (this.pending.get(token)?.startedAt ?? now),
140
+ });
141
+ this.pending.delete(token);
142
+ }
143
+ }
144
+ /**
145
+ * Check if there's an in-flight refresh for a given token.
146
+ * @param refreshToken - The refresh token to check
147
+ * @returns True if refresh is in progress
148
+ */
149
+ isRefreshing(refreshToken) {
150
+ return this.pending.has(refreshToken);
151
+ }
152
+ /**
153
+ * Get the number of pending refresh operations.
154
+ * Useful for debugging and monitoring.
155
+ */
156
+ get pendingCount() {
157
+ return this.pending.size;
158
+ }
159
+ /**
160
+ * Clear all pending entries (primarily for testing).
161
+ */
162
+ clear() {
163
+ this.pending.clear();
164
+ }
165
+ }
166
+ // ============================================================================
167
+ // Singleton Instance
168
+ // ============================================================================
169
+ let refreshQueueInstance = null;
170
+ /**
171
+ * Get the singleton RefreshQueue instance.
172
+ * @param maxEntryAgeMs - Maximum age for pending entries (only used on first call)
173
+ * @returns The global RefreshQueue instance
174
+ */
175
+ export function getRefreshQueue(maxEntryAgeMs) {
176
+ if (!refreshQueueInstance) {
177
+ refreshQueueInstance = new RefreshQueue(maxEntryAgeMs);
178
+ }
179
+ return refreshQueueInstance;
180
+ }
181
+ /**
182
+ * Reset the singleton instance (primarily for testing).
183
+ */
184
+ export function resetRefreshQueue() {
185
+ refreshQueueInstance?.clear();
186
+ refreshQueueInstance = null;
187
+ }
188
+ /**
189
+ * Convenience function to refresh a token using the singleton queue.
190
+ * @param refreshToken - The refresh token to use
191
+ * @returns Token result
192
+ */
193
+ export async function queuedRefresh(refreshToken) {
194
+ return getRefreshQueue().refresh(refreshToken);
195
+ }
196
+ //# sourceMappingURL=refresh-queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh-queue.js","sourceRoot":"","sources":["../../lib/refresh-queue.ts"],"names":[],"mappings":"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"}