@deepdream314/remodex 1.3.9 → 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 +39 -0
- package/src/bridge.js +10 -4
- package/src/voice-handler.js +97 -12
package/package.json
CHANGED
package/src/account-status.js
CHANGED
|
@@ -80,6 +80,44 @@ function redactAuthStatus(authStatus = null, extras = {}) {
|
|
|
80
80
|
|
|
81
81
|
// ─── Settled snapshot helpers ───────────────────────────────
|
|
82
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
|
+
|
|
83
121
|
// Collapses settled bridge RPC results into one safe snapshot, even if one side fails.
|
|
84
122
|
// Input: Promise.allSettled-style results → Output: sanitized account status object
|
|
85
123
|
// Throws if both the account read and auth status fail, so the bridge can surface a real error.
|
|
@@ -141,6 +179,7 @@ function parseBoolean(value) {
|
|
|
141
179
|
}
|
|
142
180
|
|
|
143
181
|
module.exports = {
|
|
182
|
+
applyLocalAuthFallbackToSettledAuthStatus,
|
|
144
183
|
composeAccountStatus,
|
|
145
184
|
composeSanitizedAuthStatusFromSettledResults,
|
|
146
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,7 +542,7 @@ 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
|
}),
|
|
@@ -546,6 +551,7 @@ function startBridge({
|
|
|
546
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,8 +181,17 @@ 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
|
-
|
|
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,
|
|
194
|
+
});
|
|
175
195
|
|
|
176
196
|
if (!token) {
|
|
177
197
|
throw voiceError("not_authenticated", "Sign in with ChatGPT before using voice transcription.");
|
|
@@ -277,8 +297,15 @@ function voiceError(errorCode, userMessage) {
|
|
|
277
297
|
|
|
278
298
|
// Returns an ephemeral ChatGPT token so the phone can call the transcription API directly.
|
|
279
299
|
// Uses its own token resolution instead of loadAuthContext so errors are specific and actionable.
|
|
280
|
-
async function resolveVoiceAuth(
|
|
300
|
+
async function resolveVoiceAuth(
|
|
301
|
+
sendCodexRequest,
|
|
302
|
+
params = null,
|
|
303
|
+
{ readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk } = {}
|
|
304
|
+
) {
|
|
305
|
+
const forceRefresh = Boolean(params?.forceRefresh);
|
|
281
306
|
const { authMethod, token, isChatGPT, requiresOpenaiAuth } = await resolveCurrentOrRefreshedAuthStatus(sendCodexRequest, {
|
|
307
|
+
forceRefresh,
|
|
308
|
+
readLocalAuthToken,
|
|
282
309
|
rpcErrorCode: "auth_unavailable",
|
|
283
310
|
rpcErrorMessage: "Could not read ChatGPT session from the Mac runtime. Is the bridge running?",
|
|
284
311
|
});
|
|
@@ -301,25 +328,82 @@ async function resolveVoiceAuth(sendCodexRequest) {
|
|
|
301
328
|
async function resolveCurrentOrRefreshedAuthStatus(
|
|
302
329
|
sendCodexRequest,
|
|
303
330
|
{
|
|
331
|
+
forceRefresh = false,
|
|
332
|
+
readLocalAuthToken = readLocalChatGPTAuthTokenFromDisk,
|
|
304
333
|
rpcErrorCode = "not_authenticated",
|
|
305
334
|
rpcErrorMessage = "Sign in with ChatGPT before using voice transcription.",
|
|
306
335
|
} = {}
|
|
307
336
|
) {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
);
|
|
313
354
|
|
|
314
355
|
if (currentStatus.token) {
|
|
315
356
|
return currentStatus;
|
|
316
357
|
}
|
|
317
358
|
|
|
318
|
-
|
|
359
|
+
const refreshedStatus = await readAuthStatus(sendCodexRequest, {
|
|
319
360
|
refreshToken: true,
|
|
320
361
|
rpcErrorCode,
|
|
321
362
|
rpcErrorMessage,
|
|
322
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
|
+
};
|
|
323
407
|
}
|
|
324
408
|
|
|
325
409
|
async function readAuthStatus(
|
|
@@ -348,5 +432,6 @@ async function readAuthStatus(
|
|
|
348
432
|
|
|
349
433
|
module.exports = {
|
|
350
434
|
createVoiceHandler,
|
|
435
|
+
readLocalChatGPTAuthTokenFromDisk,
|
|
351
436
|
resolveVoiceAuth,
|
|
352
437
|
};
|