@wingman-ai/gateway 0.2.4 → 0.3.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 (95) hide show
  1. package/.wingman/agents/coding/agent.md +5 -0
  2. package/.wingman/agents/coding-v2/agent.md +58 -0
  3. package/.wingman/agents/game-dev/agent.md +94 -0
  4. package/.wingman/agents/game-dev/art-generation.md +37 -0
  5. package/.wingman/agents/game-dev/asset-refinement.md +17 -0
  6. package/.wingman/agents/game-dev/planning-idea.md +17 -0
  7. package/.wingman/agents/game-dev/ui-specialist.md +17 -0
  8. package/.wingman/agents/main/agent.md +2 -0
  9. package/README.md +1 -0
  10. package/dist/agent/config/agentConfig.d.ts +4 -0
  11. package/dist/agent/config/mcpClientManager.cjs +44 -10
  12. package/dist/agent/config/mcpClientManager.d.ts +6 -2
  13. package/dist/agent/config/mcpClientManager.js +44 -10
  14. package/dist/agent/config/toolRegistry.cjs +3 -1
  15. package/dist/agent/config/toolRegistry.js +3 -1
  16. package/dist/agent/tests/mcpClientManager.test.cjs +124 -0
  17. package/dist/agent/tests/mcpClientManager.test.d.ts +1 -0
  18. package/dist/agent/tests/mcpClientManager.test.js +118 -0
  19. package/dist/agent/tools/command_execute.cjs +1 -1
  20. package/dist/agent/tools/command_execute.js +1 -1
  21. package/dist/cli/config/schema.d.ts +2 -0
  22. package/dist/cli/core/agentInvoker.cjs +55 -66
  23. package/dist/cli/core/agentInvoker.d.ts +10 -13
  24. package/dist/cli/core/agentInvoker.js +42 -62
  25. package/dist/cli/core/imagePersistence.cjs +125 -0
  26. package/dist/cli/core/imagePersistence.d.ts +24 -0
  27. package/dist/cli/core/imagePersistence.js +85 -0
  28. package/dist/cli/core/sessionManager.cjs +297 -40
  29. package/dist/cli/core/sessionManager.d.ts +9 -0
  30. package/dist/cli/core/sessionManager.js +297 -40
  31. package/dist/debug/terminalProbe.cjs +57 -0
  32. package/dist/debug/terminalProbe.d.ts +10 -0
  33. package/dist/debug/terminalProbe.js +20 -0
  34. package/dist/debug/terminalProbeAuth.cjs +140 -0
  35. package/dist/debug/terminalProbeAuth.d.ts +20 -0
  36. package/dist/debug/terminalProbeAuth.js +97 -0
  37. package/dist/gateway/http/fs.cjs +19 -0
  38. package/dist/gateway/http/fs.js +19 -0
  39. package/dist/gateway/http/sessions.cjs +25 -5
  40. package/dist/gateway/http/sessions.js +25 -5
  41. package/dist/gateway/server.cjs +112 -11
  42. package/dist/gateway/server.d.ts +2 -0
  43. package/dist/gateway/server.js +112 -11
  44. package/dist/providers/codex.cjs +230 -37
  45. package/dist/providers/codex.d.ts +2 -0
  46. package/dist/providers/codex.js +231 -38
  47. package/dist/tests/agentInvokerSummarization.test.cjs +56 -37
  48. package/dist/tests/agentInvokerSummarization.test.js +58 -39
  49. package/dist/tests/agentInvokerWorkdir.test.cjs +50 -0
  50. package/dist/tests/agentInvokerWorkdir.test.js +52 -2
  51. package/dist/tests/cli-init.test.cjs +36 -0
  52. package/dist/tests/cli-init.test.js +36 -0
  53. package/dist/tests/codex-provider.test.cjs +173 -0
  54. package/dist/tests/codex-provider.test.js +174 -1
  55. package/dist/tests/falRuntime.test.cjs +78 -0
  56. package/dist/tests/falRuntime.test.d.ts +1 -0
  57. package/dist/tests/falRuntime.test.js +72 -0
  58. package/dist/tests/falSummary.test.cjs +51 -0
  59. package/dist/tests/falSummary.test.d.ts +1 -0
  60. package/dist/tests/falSummary.test.js +45 -0
  61. package/dist/tests/gateway.test.cjs +109 -1
  62. package/dist/tests/gateway.test.js +109 -1
  63. package/dist/tests/imagePersistence.test.cjs +143 -0
  64. package/dist/tests/imagePersistence.test.d.ts +1 -0
  65. package/dist/tests/imagePersistence.test.js +137 -0
  66. package/dist/tests/sessionMessageAttachments.test.cjs +30 -0
  67. package/dist/tests/sessionMessageAttachments.test.js +30 -0
  68. package/dist/tests/sessionStateMessages.test.cjs +126 -0
  69. package/dist/tests/sessionStateMessages.test.js +126 -0
  70. package/dist/tests/sessions-api.test.cjs +117 -3
  71. package/dist/tests/sessions-api.test.js +118 -4
  72. package/dist/tests/terminalProbe.test.cjs +45 -0
  73. package/dist/tests/terminalProbe.test.d.ts +1 -0
  74. package/dist/tests/terminalProbe.test.js +39 -0
  75. package/dist/tests/terminalProbeAuth.test.cjs +85 -0
  76. package/dist/tests/terminalProbeAuth.test.d.ts +1 -0
  77. package/dist/tests/terminalProbeAuth.test.js +79 -0
  78. package/dist/tools/fal/runtime.cjs +103 -0
  79. package/dist/tools/fal/runtime.d.ts +10 -0
  80. package/dist/tools/fal/runtime.js +60 -0
  81. package/dist/tools/fal/summary.cjs +78 -0
  82. package/dist/tools/fal/summary.d.ts +22 -0
  83. package/dist/tools/fal/summary.js +41 -0
  84. package/dist/tools/mcp-fal-ai.cjs +1041 -0
  85. package/dist/tools/mcp-fal-ai.d.ts +1 -0
  86. package/dist/tools/mcp-fal-ai.js +1025 -0
  87. package/dist/types/mcp.cjs +2 -0
  88. package/dist/types/mcp.d.ts +8 -0
  89. package/dist/types/mcp.js +3 -1
  90. package/dist/webui/assets/index-0nUBsUUq.js +278 -0
  91. package/dist/webui/assets/index-kk7OrD-G.css +11 -0
  92. package/dist/webui/index.html +2 -2
  93. package/package.json +16 -13
  94. package/dist/webui/assets/index-DVWQluit.css +0 -11
  95. package/dist/webui/assets/index-Dlyzwalc.js +0 -270
@@ -336,12 +336,29 @@ class GatewayServer {
336
336
  const sessionManager = await this.getSessionManager(agentId);
337
337
  const existingSession = sessionManager.getSession(sessionKey);
338
338
  const session = existingSession || sessionManager.getOrCreateSession(sessionKey, agentId);
339
+ const requestId = msg.id || `req-${Date.now()}`;
339
340
  const workdir = session.metadata?.workdir ?? null;
340
341
  const defaultOutputDir = this.resolveDefaultOutputDir(agentId);
341
342
  const preview = hasContent ? content.trim() : buildAttachmentPreview(attachments);
342
343
  sessionManager.updateSession(session.id, {
344
+ messageCount: (session.messageCount ?? 0) + 1,
343
345
  lastMessagePreview: preview.substring(0, 200)
344
346
  });
347
+ try {
348
+ sessionManager.persistPendingMessage({
349
+ sessionId: sessionKey,
350
+ requestId,
351
+ message: {
352
+ id: `user-${requestId}`,
353
+ role: "user",
354
+ content,
355
+ attachments: attachments.length > 0 ? mapAttachmentsForPendingMessage(attachments) : void 0,
356
+ createdAt: Date.now()
357
+ }
358
+ });
359
+ } catch (error) {
360
+ this.logger.warn("Failed to persist pending user message", error);
361
+ }
345
362
  if (!existingSession) this.internalHooks?.emit({
346
363
  type: "session",
347
364
  action: "start",
@@ -444,9 +461,15 @@ class GatewayServer {
444
461
  this.activeSessionRequests.set(sessionQueueKey, msg.id);
445
462
  const outputManager = new OutputManager("interactive");
446
463
  let emittedAgentError = false;
464
+ let streamedCompletionResult;
447
465
  const outputHandler = (event)=>{
448
466
  const payloadWithSession = this.attachSessionContext(event, sessionKey, agentId);
449
- if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "agent-error" === payloadWithSession.type) emittedAgentError = true;
467
+ const payloadType = payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession) && "string" == typeof payloadWithSession.type ? payloadWithSession.type : "";
468
+ if ("agent-complete" === payloadType) {
469
+ if (payloadWithSession && "object" == typeof payloadWithSession && !Array.isArray(payloadWithSession)) streamedCompletionResult = payloadWithSession.result;
470
+ return;
471
+ }
472
+ if ("agent-error" === payloadType) emittedAgentError = true;
450
473
  const baseMessage = {
451
474
  type: "event:agent",
452
475
  id: msg.id,
@@ -477,17 +500,43 @@ class GatewayServer {
477
500
  abortController
478
501
  });
479
502
  try {
480
- await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
503
+ const invocationResult = await invoker.invokeAgent(agentId, content, sessionKey, attachments, {
481
504
  signal: abortController.signal
482
505
  });
483
- const updated = sessionManager.getSession(sessionKey);
484
- if (updated) sessionManager.updateSession(sessionKey, {
485
- messageCount: updated.messageCount + 1
506
+ if (msg.id) sessionManager.clearPendingMessagesForRequest(sessionKey, msg.id);
507
+ if (emittedAgentError) return;
508
+ const invocationCancelled = abortController.signal.aborted || "object" == typeof invocationResult && null !== invocationResult && !Array.isArray(invocationResult) && true === invocationResult.cancelled;
509
+ if (invocationCancelled) return void this.sendAgentError(ws, msg.id, "Request cancelled", {
510
+ sessionId: sessionKey,
511
+ agentId,
512
+ broadcastToSession: true,
513
+ exclude: ws
514
+ });
515
+ const completionResult = void 0 === streamedCompletionResult ? invocationResult : streamedCompletionResult;
516
+ this.sendAgentComplete(ws, msg.id, completionResult, {
517
+ sessionId: sessionKey,
518
+ agentId,
519
+ broadcastToSession: true,
520
+ exclude: ws
486
521
  });
487
522
  } catch (error) {
488
523
  this.logger.error("Agent invocation failed", error);
524
+ const message = error instanceof Error ? error.message : String(error);
525
+ if (msg.id) try {
526
+ sessionManager.persistPendingMessage({
527
+ sessionId: sessionKey,
528
+ requestId: msg.id,
529
+ message: {
530
+ id: msg.id,
531
+ role: "assistant",
532
+ content: message,
533
+ createdAt: Date.now()
534
+ }
535
+ });
536
+ } catch (persistError) {
537
+ this.logger.warn("Failed to persist pending assistant error message", persistError);
538
+ }
489
539
  if (!emittedAgentError) {
490
- const message = error instanceof Error ? error.message : String(error);
491
540
  const stack = error instanceof Error ? error.stack : void 0;
492
541
  this.sendAgentError(ws, msg.id, message, {
493
542
  sessionId: sessionKey,
@@ -563,6 +612,12 @@ class GatewayServer {
563
612
  },
564
613
  timestamp: Date.now()
565
614
  });
615
+ this.sendAgentError(ws, requestId, "Request cancelled", {
616
+ sessionId: queued.sessionKey,
617
+ agentId: queued.agentId,
618
+ broadcastToSession: true,
619
+ exclude: ws
620
+ });
566
621
  return;
567
622
  }
568
623
  this.sendMessage(ws, {
@@ -737,11 +792,25 @@ class GatewayServer {
737
792
  }
738
793
  sendMessage(ws, message) {
739
794
  try {
740
- ws.send(JSON.stringify(message));
795
+ const result = ws.send(JSON.stringify(message));
796
+ if ("number" == typeof result && result <= 0) return false;
797
+ return true;
741
798
  } catch (error) {
742
799
  this.log("error", "Failed to send message", error);
800
+ return false;
743
801
  }
744
802
  }
803
+ sendMessageWithRetry(ws, message, attempt = 0) {
804
+ if (this.sendMessage(ws, message)) return;
805
+ if (attempt >= 2) return void this.log("warn", "Dropping websocket message after retry attempts", {
806
+ type: message.type,
807
+ id: message.id
808
+ });
809
+ const delayMs = 25 * (attempt + 1);
810
+ setTimeout(()=>{
811
+ this.sendMessageWithRetry(ws, message, attempt + 1);
812
+ }, delayMs);
813
+ }
745
814
  sendError(ws, code, message) {
746
815
  const errorPayload = {
747
816
  code,
@@ -753,6 +822,25 @@ class GatewayServer {
753
822
  timestamp: Date.now()
754
823
  });
755
824
  }
825
+ sendAgentComplete(ws, requestId, result, options) {
826
+ let payload = {
827
+ type: "agent-complete",
828
+ result: result ?? null,
829
+ timestamp: new Date().toISOString()
830
+ };
831
+ if (options?.sessionId && options?.agentId) payload = this.attachSessionContext(payload, options.sessionId, options.agentId);
832
+ const baseMessage = {
833
+ type: "event:agent",
834
+ id: requestId,
835
+ payload,
836
+ timestamp: Date.now()
837
+ };
838
+ this.sendMessageWithRetry(ws, {
839
+ ...baseMessage,
840
+ clientId: ws.data.clientId
841
+ });
842
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
843
+ }
756
844
  sendAgentError(ws, requestId, message, options) {
757
845
  let payload = {
758
846
  type: "agent-error",
@@ -767,11 +855,11 @@ class GatewayServer {
767
855
  payload,
768
856
  timestamp: Date.now()
769
857
  };
770
- this.sendMessage(ws, {
858
+ this.sendMessageWithRetry(ws, {
771
859
  ...baseMessage,
772
860
  clientId: ws.data.clientId
773
861
  });
774
- if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude);
862
+ if (options?.broadcastToSession && options.sessionId) this.broadcastSessionEvent(options.sessionId, baseMessage, options.exclude, true);
775
863
  }
776
864
  cancelSocketAgentRequests(ws) {
777
865
  for (const [requestId, active] of this.activeAgentRequests)if (active.socket === ws) {
@@ -800,12 +888,13 @@ class GatewayServer {
800
888
  agentId
801
889
  };
802
890
  }
803
- broadcastSessionEvent(sessionId, message, exclude) {
891
+ broadcastSessionEvent(sessionId, message, exclude, reliable = false) {
804
892
  const subscribers = this.sessionSubscriptions.get(sessionId);
805
893
  if (!subscribers || 0 === subscribers.size) return 0;
806
894
  let sent = 0;
807
895
  for (const ws of subscribers)if (!exclude || ws !== exclude) {
808
- this.sendMessage(ws, message);
896
+ if (reliable) this.sendMessageWithRetry(ws, message);
897
+ else this.sendMessage(ws, message);
809
898
  sent++;
810
899
  }
811
900
  return sent;
@@ -1379,6 +1468,18 @@ function buildAttachmentPreview(attachments) {
1379
1468
  if (hasAudio) return count > 1 ? "Audio attachments" : "Audio attachment";
1380
1469
  return count > 1 ? "Image attachments" : "Image attachment";
1381
1470
  }
1471
+ function mapAttachmentsForPendingMessage(attachments) {
1472
+ return attachments.map((attachment)=>{
1473
+ const kind = isFileAttachment(attachment) ? "file" : isAudioAttachment(attachment) ? "audio" : "image";
1474
+ return {
1475
+ kind,
1476
+ dataUrl: attachment.dataUrl,
1477
+ name: attachment.name,
1478
+ mimeType: attachment.mimeType,
1479
+ size: attachment.size
1480
+ };
1481
+ });
1482
+ }
1382
1483
  function isAudioAttachment(attachment) {
1383
1484
  if ("audio" === attachment.kind) return true;
1384
1485
  if (attachment.mimeType?.startsWith("audio/")) return true;
@@ -34,6 +34,9 @@ const external_node_path_namespaceObject = require("node:path");
34
34
  const external_logger_cjs_namespaceObject = require("../logger.cjs");
35
35
  const CODEX_HOME_ENV = "CODEX_HOME";
36
36
  const CODEX_AUTH_FILE = "auth.json";
37
+ const CODEX_REFRESH_TOKEN_URL_OVERRIDE_ENV = "CODEX_REFRESH_TOKEN_URL_OVERRIDE";
38
+ const DEFAULT_CODEX_REFRESH_TOKEN_URL = "https://auth.openai.com/oauth/token";
39
+ const TOKEN_REFRESH_BUFFER_MS = 300000;
37
40
  const DEFAULT_CODEX_INSTRUCTIONS = "You are Wingman, a coding assistant. Follow the user's request exactly and keep tool usage focused.";
38
41
  const logger = (0, external_logger_cjs_namespaceObject.createLogger)();
39
42
  function getCodexAuthPath() {
@@ -43,53 +46,77 @@ function getCodexAuthPath() {
43
46
  }
44
47
  function resolveCodexAuthFromFile() {
45
48
  const authPath = getCodexAuthPath();
46
- if (!(0, external_node_fs_namespaceObject.existsSync)(authPath)) return {
49
+ const root = readCodexAuthRoot(authPath);
50
+ if (!root) return {
51
+ authPath
52
+ };
53
+ const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
54
+ const accessToken = firstNonEmptyString([
55
+ tokens?.access_token,
56
+ root.access_token
57
+ ]);
58
+ const refreshToken = firstNonEmptyString([
59
+ tokens?.refresh_token,
60
+ root.refresh_token
61
+ ]);
62
+ const idToken = firstNonEmptyString([
63
+ tokens?.id_token,
64
+ root.id_token
65
+ ]);
66
+ const accountId = firstNonEmptyString([
67
+ tokens?.account_id,
68
+ root.account_id,
69
+ extractAccountIdFromIdToken(idToken)
70
+ ]);
71
+ return {
72
+ accessToken,
73
+ refreshToken,
74
+ idToken,
75
+ accountId,
47
76
  authPath
48
77
  };
49
- try {
50
- const parsed = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)(authPath, "utf-8"));
51
- if (!parsed || "object" != typeof parsed) return {
52
- authPath
53
- };
54
- const root = parsed;
55
- const tokens = root.tokens && "object" == typeof root.tokens ? root.tokens : void 0;
56
- const accessToken = firstNonEmptyString([
57
- tokens?.access_token,
58
- root.access_token
59
- ]);
60
- const accountId = firstNonEmptyString([
61
- tokens?.account_id,
62
- root.account_id
63
- ]);
64
- return {
65
- accessToken,
66
- accountId,
67
- authPath
68
- };
69
- } catch {
70
- return {
71
- authPath
72
- };
73
- }
74
78
  }
75
79
  function createCodexFetch(options = {}) {
76
80
  const baseFetch = options.baseFetch || globalThis.fetch.bind(globalThis);
77
81
  return async (input, init)=>{
78
- const codexAuth = resolveCodexAuthFromFile();
79
- const accessToken = codexAuth.accessToken || options.fallbackToken;
80
- const accountId = codexAuth.accountId || options.fallbackAccountId;
82
+ let codexAuth = await maybeRefreshCodexAuth({
83
+ authState: resolveCodexAuthFromFile(),
84
+ baseFetch
85
+ });
86
+ let accessToken = codexAuth.accessToken || options.fallbackToken;
87
+ let accountId = codexAuth.accountId || options.fallbackAccountId;
81
88
  if (!accessToken) throw new Error("Codex credentials missing. Run `codex login` or set CODEX_ACCESS_TOKEN.");
82
- const headers = new Headers(init?.headers || {});
83
- headers.delete("authorization");
84
- headers.delete("x-api-key");
85
- headers.set("Authorization", `Bearer ${accessToken}`);
86
- if (accountId) headers.set("ChatGPT-Account-ID", accountId);
87
89
  const body = withCodexRequestDefaults(init?.body);
88
- const response = await baseFetch(input, {
89
- ...init,
90
- headers,
90
+ let response = await dispatchCodexRequest({
91
+ input,
92
+ init,
93
+ baseFetch,
94
+ accessToken,
95
+ accountId,
91
96
  body
92
97
  });
98
+ if ((401 === response.status || 403 === response.status) && canRetryCodexRequest(body) && codexAuth.refreshToken) {
99
+ const refreshed = await maybeRefreshCodexAuth({
100
+ authState: codexAuth,
101
+ baseFetch,
102
+ force: true
103
+ });
104
+ const refreshedAccessToken = refreshed.accessToken || options.fallbackToken;
105
+ const refreshedAccountId = refreshed.accountId || options.fallbackAccountId;
106
+ if (refreshedAccessToken && refreshedAccessToken !== accessToken) {
107
+ codexAuth = refreshed;
108
+ accessToken = refreshedAccessToken;
109
+ accountId = refreshedAccountId;
110
+ response = await dispatchCodexRequest({
111
+ input,
112
+ init,
113
+ baseFetch,
114
+ accessToken,
115
+ accountId,
116
+ body
117
+ });
118
+ }
119
+ }
93
120
  if (!response.ok) {
94
121
  let responseBody = "";
95
122
  try {
@@ -105,6 +132,172 @@ function createCodexFetch(options = {}) {
105
132
  return response;
106
133
  };
107
134
  }
135
+ async function dispatchCodexRequest(input) {
136
+ const headers = new Headers(input.init?.headers || {});
137
+ headers.delete("authorization");
138
+ headers.delete("x-api-key");
139
+ headers.set("Authorization", `Bearer ${input.accessToken}`);
140
+ if (input.accountId) headers.set("ChatGPT-Account-ID", input.accountId);
141
+ return input.baseFetch(input.input, {
142
+ ...input.init,
143
+ headers,
144
+ body: input.body
145
+ });
146
+ }
147
+ function canRetryCodexRequest(body) {
148
+ return null == body || "string" == typeof body || body instanceof URLSearchParams;
149
+ }
150
+ async function maybeRefreshCodexAuth(input) {
151
+ const { authState, baseFetch, force = false } = input;
152
+ if (!authState.refreshToken) return authState;
153
+ const shouldRefresh = force || !authState.accessToken || isTokenExpiredOrExpiring(authState.accessToken);
154
+ if (!shouldRefresh) return authState;
155
+ try {
156
+ const refreshed = await refreshCodexAuthToken(authState, baseFetch);
157
+ if (refreshed) return refreshed;
158
+ } catch (error) {
159
+ logger.warn("Failed to refresh Codex token", {
160
+ error: error instanceof Error ? error.message : String(error)
161
+ });
162
+ }
163
+ return authState;
164
+ }
165
+ async function refreshCodexAuthToken(authState, baseFetch) {
166
+ const refreshToken = authState.refreshToken;
167
+ if (!refreshToken) return;
168
+ const clientId = extractClientIdForRefresh(authState);
169
+ const tokenUrl = resolveCodexRefreshTokenUrl();
170
+ const form = new URLSearchParams({
171
+ grant_type: "refresh_token",
172
+ refresh_token: refreshToken
173
+ });
174
+ if (clientId) form.set("client_id", clientId);
175
+ const response = await baseFetch(tokenUrl, {
176
+ method: "POST",
177
+ headers: {
178
+ Accept: "application/json",
179
+ "Content-Type": "application/x-www-form-urlencoded"
180
+ },
181
+ body: form.toString()
182
+ });
183
+ if (!response.ok) {
184
+ const preview = await readResponsePreview(response);
185
+ logger.warn("Codex token refresh failed", {
186
+ status: response.status,
187
+ statusText: response.statusText || null,
188
+ bodyPreview: preview || null
189
+ });
190
+ return;
191
+ }
192
+ const payload = await response.json();
193
+ const accessToken = firstNonEmptyString([
194
+ payload.access_token
195
+ ]);
196
+ if (!accessToken) return void logger.warn("Codex token refresh failed: missing access_token");
197
+ const idToken = firstNonEmptyString([
198
+ payload.id_token,
199
+ authState.idToken
200
+ ]);
201
+ const refreshed = {
202
+ accessToken,
203
+ refreshToken: firstNonEmptyString([
204
+ payload.refresh_token,
205
+ authState.refreshToken
206
+ ]),
207
+ idToken,
208
+ accountId: firstNonEmptyString([
209
+ extractAccountIdFromIdToken(idToken),
210
+ authState.accountId
211
+ ])
212
+ };
213
+ persistCodexAuthUpdate(authState.authPath, refreshed);
214
+ return resolveCodexAuthFromFile();
215
+ }
216
+ async function readResponsePreview(response) {
217
+ try {
218
+ const text = await response.text();
219
+ return text.trim().slice(0, 1200);
220
+ } catch {
221
+ return "";
222
+ }
223
+ }
224
+ function persistCodexAuthUpdate(authPath, updated) {
225
+ const root = readCodexAuthRoot(authPath) || {};
226
+ const existingTokens = root.tokens && "object" == typeof root.tokens && !Array.isArray(root.tokens) ? root.tokens : {};
227
+ const tokens = {
228
+ ...existingTokens,
229
+ access_token: updated.accessToken
230
+ };
231
+ if (updated.refreshToken) tokens.refresh_token = updated.refreshToken;
232
+ if (updated.idToken) tokens.id_token = updated.idToken;
233
+ if (updated.accountId) tokens.account_id = updated.accountId;
234
+ root.tokens = tokens;
235
+ root.last_refresh = new Date().toISOString();
236
+ (0, external_node_fs_namespaceObject.writeFileSync)(authPath, `${JSON.stringify(root, null, 2)}\n`, "utf-8");
237
+ }
238
+ function readCodexAuthRoot(authPath) {
239
+ if (!(0, external_node_fs_namespaceObject.existsSync)(authPath)) return;
240
+ try {
241
+ const parsed = JSON.parse((0, external_node_fs_namespaceObject.readFileSync)(authPath, "utf-8"));
242
+ if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return;
243
+ return parsed;
244
+ } catch {
245
+ return;
246
+ }
247
+ }
248
+ function resolveCodexRefreshTokenUrl() {
249
+ const override = process.env[CODEX_REFRESH_TOKEN_URL_OVERRIDE_ENV];
250
+ if (override?.trim()) return override.trim();
251
+ return DEFAULT_CODEX_REFRESH_TOKEN_URL;
252
+ }
253
+ function extractClientIdForRefresh(authState) {
254
+ const accessTokenClaims = parseJwtPayload(authState.accessToken);
255
+ const accessTokenClientId = accessTokenClaims && "string" == typeof accessTokenClaims.client_id ? accessTokenClaims.client_id : void 0;
256
+ if (accessTokenClientId?.trim()) return accessTokenClientId.trim();
257
+ const idTokenClaims = parseJwtPayload(authState.idToken);
258
+ if (!idTokenClaims) return;
259
+ const aud = idTokenClaims.aud;
260
+ if ("string" == typeof aud && aud.trim()) return aud.trim();
261
+ if (Array.isArray(aud)) {
262
+ for (const value of aud)if ("string" == typeof value && value.trim()) return value.trim();
263
+ }
264
+ }
265
+ function isTokenExpiredOrExpiring(token) {
266
+ const expiryMs = extractTokenExpiryMs(token);
267
+ if (!expiryMs) return false;
268
+ return expiryMs <= Date.now() + TOKEN_REFRESH_BUFFER_MS;
269
+ }
270
+ function extractTokenExpiryMs(token) {
271
+ const payload = parseJwtPayload(token);
272
+ if (!payload || "number" != typeof payload.exp) return;
273
+ return 1000 * payload.exp;
274
+ }
275
+ function extractAccountIdFromIdToken(idToken) {
276
+ const payload = parseJwtPayload(idToken);
277
+ if (!payload) return;
278
+ const nested = payload["https://api.openai.com/auth"];
279
+ if (nested && "object" == typeof nested && !Array.isArray(nested)) {
280
+ const accountId = nested.chatgpt_account_id;
281
+ if ("string" == typeof accountId && accountId.trim()) return accountId.trim();
282
+ }
283
+ const direct = payload.chatgpt_account_id;
284
+ if ("string" == typeof direct && direct.trim()) return direct.trim();
285
+ }
286
+ function parseJwtPayload(token) {
287
+ if (!token) return;
288
+ const parts = token.split(".");
289
+ if (3 !== parts.length) return;
290
+ try {
291
+ const payload = parts[1];
292
+ const normalized = payload + "=".repeat((4 - payload.length % 4) % 4);
293
+ const decoded = Buffer.from(normalized, "base64url").toString("utf-8");
294
+ const parsed = JSON.parse(decoded);
295
+ if (!parsed || "object" != typeof parsed || Array.isArray(parsed)) return;
296
+ return parsed;
297
+ } catch {
298
+ return;
299
+ }
300
+ }
108
301
  function withCodexRequestDefaults(body) {
109
302
  if ("string" != typeof body || !body.trim()) return body;
110
303
  try {
@@ -1,6 +1,8 @@
1
1
  type FetchLike = (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => ReturnType<typeof fetch>;
2
2
  export interface CodexAuthState {
3
3
  accessToken?: string;
4
+ refreshToken?: string;
5
+ idToken?: string;
4
6
  accountId?: string;
5
7
  authPath: string;
6
8
  }