@deepdream314/remodex 1.3.8 → 1.3.10
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/package.json +1 -1
- package/src/account-status.js +44 -2
- package/src/bridge.js +11 -5
- package/src/voice-handler.js +143 -27
package/package.json
CHANGED
package/src/account-status.js
CHANGED
|
@@ -24,8 +24,11 @@ function composeAccountStatus({
|
|
|
24
24
|
]) || null;
|
|
25
25
|
const tokenReady = Boolean(authToken);
|
|
26
26
|
const requiresOpenaiAuth = Boolean(accountRead?.requiresOpenaiAuth || authStatus?.requiresOpenaiAuth);
|
|
27
|
-
const
|
|
28
|
-
|
|
27
|
+
const needsReauth = !loginInFlight
|
|
28
|
+
&& requiresOpenaiAuth
|
|
29
|
+
&& !tokenReady
|
|
30
|
+
&& !hasAccountLogin
|
|
31
|
+
&& Boolean(authMethod);
|
|
29
32
|
const isAuthenticated = !needsReauth && (tokenReady || hasAccountLogin);
|
|
30
33
|
const status = isAuthenticated
|
|
31
34
|
? "authenticated"
|
|
@@ -77,6 +80,44 @@ function redactAuthStatus(authStatus = null, extras = {}) {
|
|
|
77
80
|
|
|
78
81
|
// ─── Settled snapshot helpers ───────────────────────────────
|
|
79
82
|
|
|
83
|
+
function applyLocalAuthFallbackToSettledAuthStatus(
|
|
84
|
+
authStatusResult = null,
|
|
85
|
+
localAuthResult = null
|
|
86
|
+
) {
|
|
87
|
+
const localAuthStatus = localAuthResult?.status === "fulfilled" ? localAuthResult.value : null;
|
|
88
|
+
if (!localAuthStatus?.token || !localAuthStatus?.isChatGPT) {
|
|
89
|
+
return authStatusResult;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (authStatusResult?.status === "fulfilled") {
|
|
93
|
+
const authStatus = authStatusResult.value;
|
|
94
|
+
if (normalizeString(authStatus?.authToken)) {
|
|
95
|
+
return authStatusResult;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
status: "fulfilled",
|
|
100
|
+
value: {
|
|
101
|
+
...authStatus,
|
|
102
|
+
authMethod: normalizeString(localAuthStatus.authMethod)
|
|
103
|
+
|| normalizeString(authStatus?.authMethod)
|
|
104
|
+
|| "chatgpt",
|
|
105
|
+
authToken: localAuthStatus.token,
|
|
106
|
+
requiresOpenaiAuth: localAuthStatus.requiresOpenaiAuth ?? authStatus?.requiresOpenaiAuth,
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
status: "fulfilled",
|
|
113
|
+
value: {
|
|
114
|
+
authMethod: normalizeString(localAuthStatus.authMethod) || "chatgpt",
|
|
115
|
+
authToken: localAuthStatus.token,
|
|
116
|
+
requiresOpenaiAuth: localAuthStatus.requiresOpenaiAuth,
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
|
|
80
121
|
// Collapses settled bridge RPC results into one safe snapshot, even if one side fails.
|
|
81
122
|
// Input: Promise.allSettled-style results → Output: sanitized account status object
|
|
82
123
|
// Throws if both the account read and auth status fail, so the bridge can surface a real error.
|
|
@@ -138,6 +179,7 @@ function parseBoolean(value) {
|
|
|
138
179
|
}
|
|
139
180
|
|
|
140
181
|
module.exports = {
|
|
182
|
+
applyLocalAuthFallbackToSettledAuthStatus,
|
|
141
183
|
composeAccountStatus,
|
|
142
184
|
composeSanitizedAuthStatusFromSettledResults,
|
|
143
185
|
redactAuthStatus,
|
package/src/bridge.js
CHANGED
|
@@ -22,8 +22,13 @@ const { handleGitRequest } = require("./git-handler");
|
|
|
22
22
|
const { handleThreadContextRequest } = require("./thread-context-handler");
|
|
23
23
|
const { handleWorkspaceRequest } = require("./workspace-handler");
|
|
24
24
|
const { createNotificationsHandler } = require("./notifications-handler");
|
|
25
|
-
const { createVoiceHandler, resolveVoiceAuth } = require("./voice-handler");
|
|
26
25
|
const {
|
|
26
|
+
createVoiceHandler,
|
|
27
|
+
readLocalChatGPTAuthTokenFromDisk,
|
|
28
|
+
resolveVoiceAuth,
|
|
29
|
+
} = require("./voice-handler");
|
|
30
|
+
const {
|
|
31
|
+
applyLocalAuthFallbackToSettledAuthStatus,
|
|
27
32
|
composeSanitizedAuthStatusFromSettledResults,
|
|
28
33
|
} = require("./account-status");
|
|
29
34
|
const { createBridgePackageVersionStatusReader } = require("./package-version-status");
|
|
@@ -528,7 +533,7 @@ function startBridge({
|
|
|
528
533
|
case "account/login/openOnMac":
|
|
529
534
|
return openPendingAuthLoginOnMac(params);
|
|
530
535
|
case "voice/resolveAuth":
|
|
531
|
-
return resolveVoiceAuth(sendCodexRequest);
|
|
536
|
+
return resolveVoiceAuth(sendCodexRequest, params);
|
|
532
537
|
default:
|
|
533
538
|
throw new Error(`Unsupported bridge-managed account method: ${method}`);
|
|
534
539
|
}
|
|
@@ -537,15 +542,16 @@ function startBridge({
|
|
|
537
542
|
// Combines account/read + getAuthStatus into one safe snapshot for the phone UI.
|
|
538
543
|
// The two RPCs are settled independently so one transient failure does not hide the other.
|
|
539
544
|
async function readSanitizedAuthStatus() {
|
|
540
|
-
const [accountReadResult, authStatusResult, bridgeVersionInfoResult] = await Promise.allSettled([
|
|
545
|
+
const [accountReadResult, authStatusResult, bridgeVersionInfoResult, localAuthResult] = await Promise.allSettled([
|
|
541
546
|
sendCodexRequest("account/read", {
|
|
542
547
|
refreshToken: false,
|
|
543
548
|
}),
|
|
544
549
|
sendCodexRequest("getAuthStatus", {
|
|
545
550
|
includeToken: true,
|
|
546
|
-
refreshToken:
|
|
551
|
+
refreshToken: false,
|
|
547
552
|
}),
|
|
548
553
|
readBridgePackageVersionStatus(),
|
|
554
|
+
readLocalChatGPTAuthTokenFromDisk(),
|
|
549
555
|
]);
|
|
550
556
|
|
|
551
557
|
return composeSanitizedAuthStatusFromSettledResults({
|
|
@@ -555,7 +561,7 @@ function startBridge({
|
|
|
555
561
|
value: normalizeAccountRead(accountReadResult.value),
|
|
556
562
|
}
|
|
557
563
|
: accountReadResult,
|
|
558
|
-
authStatusResult,
|
|
564
|
+
authStatusResult: applyLocalAuthFallbackToSettledAuthStatus(authStatusResult, localAuthResult),
|
|
559
565
|
loginInFlight: Boolean(pendingAuthLogin.loginId),
|
|
560
566
|
bridgeVersionInfo: bridgeVersionInfoResult.status === "fulfilled"
|
|
561
567
|
? bridgeVersionInfoResult.value
|
package/src/voice-handler.js
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
// Exports: createVoiceHandler
|
|
5
5
|
// Depends on: global fetch/FormData/Blob, local codex app-server auth via sendCodexRequest
|
|
6
6
|
|
|
7
|
+
const fs = require("fs/promises");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
|
|
7
11
|
const CHATGPT_TRANSCRIPTIONS_URL = "https://chatgpt.com/backend-api/transcribe";
|
|
8
12
|
const MAX_AUDIO_BYTES = 10 * 1024 * 1024;
|
|
9
13
|
const MAX_DURATION_MS = 60_000;
|
|
@@ -13,6 +17,7 @@ function createVoiceHandler({
|
|
|
13
17
|
fetchImpl = globalThis.fetch,
|
|
14
18
|
FormDataImpl = globalThis.FormData,
|
|
15
19
|
BlobImpl = globalThis.Blob,
|
|
20
|
+
readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk,
|
|
16
21
|
logPrefix = "[remodex]",
|
|
17
22
|
} = {}) {
|
|
18
23
|
function handleVoiceRequest(rawMessage, sendResponse) {
|
|
@@ -36,6 +41,7 @@ function createVoiceHandler({
|
|
|
36
41
|
fetchImpl,
|
|
37
42
|
FormDataImpl,
|
|
38
43
|
BlobImpl,
|
|
44
|
+
readLocalAuthToken,
|
|
39
45
|
})
|
|
40
46
|
.then((result) => {
|
|
41
47
|
sendResponse(JSON.stringify({ id, result }));
|
|
@@ -67,7 +73,7 @@ function createVoiceHandler({
|
|
|
67
73
|
// Validates iPhone-owned audio input and proxies it to the official transcription endpoint.
|
|
68
74
|
async function transcribeVoice(
|
|
69
75
|
params,
|
|
70
|
-
{ sendCodexRequest, fetchImpl, FormDataImpl, BlobImpl }
|
|
76
|
+
{ sendCodexRequest, fetchImpl, FormDataImpl, BlobImpl, readLocalAuthToken }
|
|
71
77
|
) {
|
|
72
78
|
if (typeof sendCodexRequest !== "function") {
|
|
73
79
|
throw voiceError("bridge_not_ready", "Voice transcription is not available right now.");
|
|
@@ -99,7 +105,7 @@ async function transcribeVoice(
|
|
|
99
105
|
throw voiceError("audio_too_large", "Voice messages are limited to 10 MB.");
|
|
100
106
|
}
|
|
101
107
|
|
|
102
|
-
const authContext = await loadAuthContext(sendCodexRequest);
|
|
108
|
+
const authContext = await loadAuthContext(sendCodexRequest, { readLocalAuthToken });
|
|
103
109
|
return requestTranscription({
|
|
104
110
|
authContext,
|
|
105
111
|
audioBuffer,
|
|
@@ -108,6 +114,7 @@ async function transcribeVoice(
|
|
|
108
114
|
FormDataImpl,
|
|
109
115
|
BlobImpl,
|
|
110
116
|
sendCodexRequest,
|
|
117
|
+
readLocalAuthToken,
|
|
111
118
|
});
|
|
112
119
|
}
|
|
113
120
|
|
|
@@ -119,6 +126,7 @@ async function requestTranscription({
|
|
|
119
126
|
FormDataImpl,
|
|
120
127
|
BlobImpl,
|
|
121
128
|
sendCodexRequest,
|
|
129
|
+
readLocalAuthToken,
|
|
122
130
|
}) {
|
|
123
131
|
const makeAttempt = async (activeAuthContext) => {
|
|
124
132
|
const formData = new FormDataImpl();
|
|
@@ -137,7 +145,10 @@ async function requestTranscription({
|
|
|
137
145
|
|
|
138
146
|
let response = await makeAttempt(authContext);
|
|
139
147
|
if (response.status === 401) {
|
|
140
|
-
const refreshedAuthContext = await loadAuthContext(sendCodexRequest
|
|
148
|
+
const refreshedAuthContext = await loadAuthContext(sendCodexRequest, {
|
|
149
|
+
forceRefresh: true,
|
|
150
|
+
readLocalAuthToken,
|
|
151
|
+
});
|
|
141
152
|
response = await makeAttempt(refreshedAuthContext);
|
|
142
153
|
}
|
|
143
154
|
|
|
@@ -170,16 +181,18 @@ async function requestTranscription({
|
|
|
170
181
|
}
|
|
171
182
|
|
|
172
183
|
// Reads the current bridge-owned auth state from the local codex app-server and refreshes if needed.
|
|
173
|
-
async function loadAuthContext(
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
184
|
+
async function loadAuthContext(
|
|
185
|
+
sendCodexRequest,
|
|
186
|
+
{
|
|
187
|
+
forceRefresh = false,
|
|
188
|
+
readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk,
|
|
189
|
+
} = {}
|
|
190
|
+
) {
|
|
191
|
+
const { authMethod, token, isChatGPT } = await resolveCurrentOrRefreshedAuthStatus(sendCodexRequest, {
|
|
192
|
+
forceRefresh,
|
|
193
|
+
readLocalAuthToken,
|
|
177
194
|
});
|
|
178
195
|
|
|
179
|
-
const authMethod = readString(authStatus?.authMethod);
|
|
180
|
-
const token = readString(authStatus?.authToken);
|
|
181
|
-
const isChatGPT = authMethod === "chatgpt" || authMethod === "chatgptAuthTokens";
|
|
182
|
-
|
|
183
196
|
if (!token) {
|
|
184
197
|
throw voiceError("not_authenticated", "Sign in with ChatGPT before using voice transcription.");
|
|
185
198
|
}
|
|
@@ -284,21 +297,18 @@ function voiceError(errorCode, userMessage) {
|
|
|
284
297
|
|
|
285
298
|
// Returns an ephemeral ChatGPT token so the phone can call the transcription API directly.
|
|
286
299
|
// Uses its own token resolution instead of loadAuthContext so errors are specific and actionable.
|
|
287
|
-
async function resolveVoiceAuth(
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const authMethod = readString(authStatus?.authMethod);
|
|
300
|
-
const token = readString(authStatus?.authToken);
|
|
301
|
-
const isChatGPT = authMethod === "chatgpt" || authMethod === "chatgptAuthTokens";
|
|
300
|
+
async function resolveVoiceAuth(
|
|
301
|
+
sendCodexRequest,
|
|
302
|
+
params = null,
|
|
303
|
+
{ readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk } = {}
|
|
304
|
+
) {
|
|
305
|
+
const forceRefresh = Boolean(params?.forceRefresh);
|
|
306
|
+
const { authMethod, token, isChatGPT, requiresOpenaiAuth } = await resolveCurrentOrRefreshedAuthStatus(sendCodexRequest, {
|
|
307
|
+
forceRefresh,
|
|
308
|
+
readLocalAuthToken,
|
|
309
|
+
rpcErrorCode: "auth_unavailable",
|
|
310
|
+
rpcErrorMessage: "Could not read ChatGPT session from the Mac runtime. Is the bridge running?",
|
|
311
|
+
});
|
|
302
312
|
|
|
303
313
|
// Check for a usable ChatGPT token first. The runtime may set requiresOpenaiAuth
|
|
304
314
|
// even when a valid ChatGPT session is present (the flag is about the runtime's
|
|
@@ -308,14 +318,120 @@ async function resolveVoiceAuth(sendCodexRequest) {
|
|
|
308
318
|
}
|
|
309
319
|
|
|
310
320
|
if (!token) {
|
|
311
|
-
console.error(`[remodex] voice/resolveAuth: no token. authMethod=${authMethod || "none"} requiresOpenaiAuth=${
|
|
321
|
+
console.error(`[remodex] voice/resolveAuth: no token. authMethod=${authMethod || "none"} requiresOpenaiAuth=${requiresOpenaiAuth}`);
|
|
312
322
|
throw voiceError("token_missing", "No ChatGPT session token available. Sign in to ChatGPT on the Mac.");
|
|
313
323
|
}
|
|
314
324
|
|
|
315
325
|
throw voiceError("not_chatgpt", "Voice transcription requires a ChatGPT account.");
|
|
316
326
|
}
|
|
317
327
|
|
|
328
|
+
async function resolveCurrentOrRefreshedAuthStatus(
|
|
329
|
+
sendCodexRequest,
|
|
330
|
+
{
|
|
331
|
+
forceRefresh = false,
|
|
332
|
+
readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk,
|
|
333
|
+
rpcErrorCode = "not_authenticated",
|
|
334
|
+
rpcErrorMessage = "Sign in with ChatGPT before using voice transcription.",
|
|
335
|
+
} = {}
|
|
336
|
+
) {
|
|
337
|
+
if (forceRefresh) {
|
|
338
|
+
const refreshedStatus = await readAuthStatus(sendCodexRequest, {
|
|
339
|
+
refreshToken: true,
|
|
340
|
+
rpcErrorCode,
|
|
341
|
+
rpcErrorMessage,
|
|
342
|
+
});
|
|
343
|
+
return withLocalAuthFallback(refreshedStatus, readLocalAuthToken);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const currentStatus = await withLocalAuthFallback(
|
|
347
|
+
await readAuthStatus(sendCodexRequest, {
|
|
348
|
+
refreshToken: false,
|
|
349
|
+
rpcErrorCode,
|
|
350
|
+
rpcErrorMessage,
|
|
351
|
+
}),
|
|
352
|
+
readLocalAuthToken
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
if (currentStatus.token) {
|
|
356
|
+
return currentStatus;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const refreshedStatus = await readAuthStatus(sendCodexRequest, {
|
|
360
|
+
refreshToken: true,
|
|
361
|
+
rpcErrorCode,
|
|
362
|
+
rpcErrorMessage,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
return withLocalAuthFallback(refreshedStatus, readLocalAuthToken);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function withLocalAuthFallback(status, readLocalAuthToken) {
|
|
369
|
+
if (status?.token || typeof readLocalAuthToken !== "function") {
|
|
370
|
+
return status;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
const localAuthStatus = await readLocalAuthToken().catch(() => null);
|
|
374
|
+
if (!localAuthStatus?.token || !localAuthStatus?.isChatGPT) {
|
|
375
|
+
return status;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
authMethod: localAuthStatus.authMethod || status.authMethod || "chatgpt",
|
|
380
|
+
token: localAuthStatus.token,
|
|
381
|
+
isChatGPT: true,
|
|
382
|
+
requiresOpenaiAuth: localAuthStatus.requiresOpenaiAuth ?? status.requiresOpenaiAuth,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
async function readLocalChatGPTAuthTokenFromDisk({
|
|
387
|
+
readFileImpl = fs.readFile,
|
|
388
|
+
codexHome = process.env.CODEX_HOME || path.join(os.homedir(), ".codex"),
|
|
389
|
+
} = {}) {
|
|
390
|
+
const authFile = path.join(codexHome, "auth.json");
|
|
391
|
+
const contents = await readFileImpl(authFile, "utf8");
|
|
392
|
+
const auth = JSON.parse(contents);
|
|
393
|
+
const authMethod = readString(auth?.auth_mode);
|
|
394
|
+
const tokenContainer = auth?.tokens && typeof auth.tokens === "object" ? auth.tokens : auth;
|
|
395
|
+
const token = readString(tokenContainer?.access_token);
|
|
396
|
+
const isChatGPT = token != null
|
|
397
|
+
&& authMethod !== "apikey"
|
|
398
|
+
&& authMethod !== "api_key"
|
|
399
|
+
&& authMethod !== "apiKey";
|
|
400
|
+
|
|
401
|
+
return {
|
|
402
|
+
authMethod: authMethod || (isChatGPT ? "chatgpt" : null),
|
|
403
|
+
token,
|
|
404
|
+
isChatGPT,
|
|
405
|
+
requiresOpenaiAuth: isChatGPT,
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async function readAuthStatus(
|
|
410
|
+
sendCodexRequest,
|
|
411
|
+
{ refreshToken, rpcErrorCode, rpcErrorMessage }
|
|
412
|
+
) {
|
|
413
|
+
let authStatus;
|
|
414
|
+
try {
|
|
415
|
+
authStatus = await sendCodexRequest("getAuthStatus", {
|
|
416
|
+
includeToken: true,
|
|
417
|
+
refreshToken,
|
|
418
|
+
});
|
|
419
|
+
} catch (err) {
|
|
420
|
+
throw voiceError(rpcErrorCode, rpcErrorMessage);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
const authMethod = readString(authStatus?.authMethod);
|
|
424
|
+
const token = readString(authStatus?.authToken);
|
|
425
|
+
return {
|
|
426
|
+
authMethod,
|
|
427
|
+
token,
|
|
428
|
+
isChatGPT: authMethod === "chatgpt" || authMethod === "chatgptAuthTokens",
|
|
429
|
+
requiresOpenaiAuth: Boolean(authStatus?.requiresOpenaiAuth),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
|
|
318
433
|
module.exports = {
|
|
319
434
|
createVoiceHandler,
|
|
435
|
+
readLocalChatGPTAuthTokenFromDisk,
|
|
320
436
|
resolveVoiceAuth,
|
|
321
437
|
};
|