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.
- package/LICENSE +37 -0
- package/README.md +507 -0
- package/assets/opencode-logo-ornate-dark.svg +18 -0
- package/assets/readme-hero.svg +31 -0
- package/config/README.md +110 -0
- package/config/minimal-opencode.json +13 -0
- package/config/opencode-legacy.json +572 -0
- package/config/opencode-modern.json +240 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +971 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/accounts.d.ts +120 -0
- package/dist/lib/accounts.d.ts.map +1 -0
- package/dist/lib/accounts.js +579 -0
- package/dist/lib/accounts.js.map +1 -0
- package/dist/lib/auth/auth.d.ts +51 -0
- package/dist/lib/auth/auth.d.ts.map +1 -0
- package/dist/lib/auth/auth.js +180 -0
- package/dist/lib/auth/auth.js.map +1 -0
- package/dist/lib/auth/browser.d.ts +17 -0
- package/dist/lib/auth/browser.d.ts.map +1 -0
- package/dist/lib/auth/browser.js +83 -0
- package/dist/lib/auth/browser.js.map +1 -0
- package/dist/lib/auth/server.d.ts +10 -0
- package/dist/lib/auth/server.d.ts.map +1 -0
- package/dist/lib/auth/server.js +85 -0
- package/dist/lib/auth/server.js.map +1 -0
- package/dist/lib/auto-update-checker.d.ts +10 -0
- package/dist/lib/auto-update-checker.d.ts.map +1 -0
- package/dist/lib/auto-update-checker.js +129 -0
- package/dist/lib/auto-update-checker.js.map +1 -0
- package/dist/lib/cli.d.ts +9 -0
- package/dist/lib/cli.d.ts.map +1 -0
- package/dist/lib/cli.js +50 -0
- package/dist/lib/cli.js.map +1 -0
- package/dist/lib/config.d.ts +17 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +102 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +74 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +74 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/context-overflow.d.ts +27 -0
- package/dist/lib/context-overflow.d.ts.map +1 -0
- package/dist/lib/context-overflow.js +124 -0
- package/dist/lib/context-overflow.js.map +1 -0
- package/dist/lib/index.d.ts +13 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +13 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/logger.d.ts +22 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +175 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/oauth-success.html +712 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts +19 -0
- package/dist/lib/prompts/codex-opencode-bridge.d.ts.map +1 -0
- package/dist/lib/prompts/codex-opencode-bridge.js +152 -0
- package/dist/lib/prompts/codex-opencode-bridge.js.map +1 -0
- package/dist/lib/prompts/codex.d.ts +32 -0
- package/dist/lib/prompts/codex.d.ts.map +1 -0
- package/dist/lib/prompts/codex.js +262 -0
- package/dist/lib/prompts/codex.js.map +1 -0
- package/dist/lib/prompts/opencode-codex.d.ts +21 -0
- package/dist/lib/prompts/opencode-codex.d.ts.map +1 -0
- package/dist/lib/prompts/opencode-codex.js +91 -0
- package/dist/lib/prompts/opencode-codex.js.map +1 -0
- package/dist/lib/recovery/constants.d.ts +12 -0
- package/dist/lib/recovery/constants.d.ts.map +1 -0
- package/dist/lib/recovery/constants.js +25 -0
- package/dist/lib/recovery/constants.js.map +1 -0
- package/dist/lib/recovery/index.d.ts +12 -0
- package/dist/lib/recovery/index.d.ts.map +1 -0
- package/dist/lib/recovery/index.js +12 -0
- package/dist/lib/recovery/index.js.map +1 -0
- package/dist/lib/recovery/storage.d.ts +24 -0
- package/dist/lib/recovery/storage.d.ts.map +1 -0
- package/dist/lib/recovery/storage.js +354 -0
- package/dist/lib/recovery/storage.js.map +1 -0
- package/dist/lib/recovery/types.d.ts +116 -0
- package/dist/lib/recovery/types.d.ts.map +1 -0
- package/dist/lib/recovery/types.js +7 -0
- package/dist/lib/recovery/types.js.map +1 -0
- package/dist/lib/recovery.d.ts +31 -0
- package/dist/lib/recovery.d.ts.map +1 -0
- package/dist/lib/recovery.js +308 -0
- package/dist/lib/recovery.js.map +1 -0
- package/dist/lib/refresh-queue.d.ts +100 -0
- package/dist/lib/refresh-queue.d.ts.map +1 -0
- package/dist/lib/refresh-queue.js +196 -0
- package/dist/lib/refresh-queue.js.map +1 -0
- package/dist/lib/request/fetch-helpers.d.ts +81 -0
- package/dist/lib/request/fetch-helpers.d.ts.map +1 -0
- package/dist/lib/request/fetch-helpers.js +325 -0
- package/dist/lib/request/fetch-helpers.js.map +1 -0
- package/dist/lib/request/helpers/input-utils.d.ts +7 -0
- package/dist/lib/request/helpers/input-utils.d.ts.map +1 -0
- package/dist/lib/request/helpers/input-utils.js +213 -0
- package/dist/lib/request/helpers/input-utils.js.map +1 -0
- package/dist/lib/request/helpers/model-map.d.ts +28 -0
- package/dist/lib/request/helpers/model-map.d.ts.map +1 -0
- package/dist/lib/request/helpers/model-map.js +109 -0
- package/dist/lib/request/helpers/model-map.js.map +1 -0
- package/dist/lib/request/rate-limit-backoff.d.ts +17 -0
- package/dist/lib/request/rate-limit-backoff.d.ts.map +1 -0
- package/dist/lib/request/rate-limit-backoff.js +74 -0
- package/dist/lib/request/rate-limit-backoff.js.map +1 -0
- package/dist/lib/request/request-transformer.d.ts +93 -0
- package/dist/lib/request/request-transformer.d.ts.map +1 -0
- package/dist/lib/request/request-transformer.js +405 -0
- package/dist/lib/request/request-transformer.js.map +1 -0
- package/dist/lib/request/response-handler.d.ts +14 -0
- package/dist/lib/request/response-handler.d.ts.map +1 -0
- package/dist/lib/request/response-handler.js +90 -0
- package/dist/lib/request/response-handler.js.map +1 -0
- package/dist/lib/rotation.d.ts +121 -0
- package/dist/lib/rotation.d.ts.map +1 -0
- package/dist/lib/rotation.js +248 -0
- package/dist/lib/rotation.js.map +1 -0
- package/dist/lib/storage.d.ts +91 -0
- package/dist/lib/storage.d.ts.map +1 -0
- package/dist/lib/storage.js +323 -0
- package/dist/lib/storage.js.map +1 -0
- package/dist/lib/types.d.ts +185 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/types.js.map +1 -0
- package/package.json +86 -0
- package/scripts/copy-oauth-success.js +37 -0
- package/scripts/install-opencode-codex-auth.js +193 -0
- package/scripts/test-all-models.sh +260 -0
- 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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"}
|