oc-codex-multi-auth 6.1.2 → 6.1.6

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 (84) hide show
  1. package/README.md +27 -23
  2. package/config/README.md +35 -35
  3. package/config/opencode-legacy.json +80 -30
  4. package/config/opencode-modern.json +14 -4
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +157 -11
  7. package/dist/index.js.map +1 -1
  8. package/dist/lib/accounts/persistence.d.ts.map +1 -1
  9. package/dist/lib/accounts/persistence.js +1 -0
  10. package/dist/lib/accounts/persistence.js.map +1 -1
  11. package/dist/lib/accounts/state.d.ts +2 -2
  12. package/dist/lib/accounts/state.d.ts.map +1 -1
  13. package/dist/lib/accounts/state.js +87 -19
  14. package/dist/lib/accounts/state.js.map +1 -1
  15. package/dist/lib/auth/auth.d.ts +2 -2
  16. package/dist/lib/auth/auth.d.ts.map +1 -1
  17. package/dist/lib/auth/auth.js +9 -6
  18. package/dist/lib/auth/auth.js.map +1 -1
  19. package/dist/lib/auth/login-runner.d.ts +1 -0
  20. package/dist/lib/auth/login-runner.d.ts.map +1 -1
  21. package/dist/lib/auth/login-runner.js +3 -0
  22. package/dist/lib/auth/login-runner.js.map +1 -1
  23. package/dist/lib/auth/scopes.d.ts +5 -0
  24. package/dist/lib/auth/scopes.d.ts.map +1 -0
  25. package/dist/lib/auth/scopes.js +23 -0
  26. package/dist/lib/auth/scopes.js.map +1 -0
  27. package/dist/lib/auth/server.d.ts +6 -1
  28. package/dist/lib/auth/server.d.ts.map +1 -1
  29. package/dist/lib/auth/server.js +95 -51
  30. package/dist/lib/auth/server.js.map +1 -1
  31. package/dist/lib/codex-usage.d.ts +91 -0
  32. package/dist/lib/codex-usage.d.ts.map +1 -0
  33. package/dist/lib/codex-usage.js +368 -0
  34. package/dist/lib/codex-usage.js.map +1 -0
  35. package/dist/lib/oauth-constants.d.ts +5 -5
  36. package/dist/lib/oauth-constants.d.ts.map +1 -1
  37. package/dist/lib/oauth-constants.js +5 -5
  38. package/dist/lib/oauth-constants.js.map +1 -1
  39. package/dist/lib/prompts/codex.d.ts +2 -0
  40. package/dist/lib/prompts/codex.d.ts.map +1 -1
  41. package/dist/lib/prompts/codex.js +43 -11
  42. package/dist/lib/prompts/codex.js.map +1 -1
  43. package/dist/lib/request/fetch-helpers.d.ts +2 -2
  44. package/dist/lib/request/fetch-helpers.d.ts.map +1 -1
  45. package/dist/lib/request/fetch-helpers.js +37 -25
  46. package/dist/lib/request/fetch-helpers.js.map +1 -1
  47. package/dist/lib/request/helpers/model-map.d.ts +1 -0
  48. package/dist/lib/request/helpers/model-map.d.ts.map +1 -1
  49. package/dist/lib/request/helpers/model-map.js +28 -25
  50. package/dist/lib/request/helpers/model-map.js.map +1 -1
  51. package/dist/lib/request/request-transformer.d.ts +1 -0
  52. package/dist/lib/request/request-transformer.d.ts.map +1 -1
  53. package/dist/lib/request/request-transformer.js +46 -22
  54. package/dist/lib/request/request-transformer.js.map +1 -1
  55. package/dist/lib/schemas.d.ts +8 -0
  56. package/dist/lib/schemas.d.ts.map +1 -1
  57. package/dist/lib/schemas.js +3 -0
  58. package/dist/lib/schemas.js.map +1 -1
  59. package/dist/lib/storage/migrations.d.ts +4 -0
  60. package/dist/lib/storage/migrations.d.ts.map +1 -1
  61. package/dist/lib/storage/migrations.js +1 -0
  62. package/dist/lib/storage/migrations.js.map +1 -1
  63. package/dist/lib/tools/codex-limits.d.ts.map +1 -1
  64. package/dist/lib/tools/codex-limits.js +39 -340
  65. package/dist/lib/tools/codex-limits.js.map +1 -1
  66. package/dist/lib/tools/codex-switch.d.ts.map +1 -1
  67. package/dist/lib/tools/codex-switch.js +9 -0
  68. package/dist/lib/tools/codex-switch.js.map +1 -1
  69. package/dist/lib/tui-quota-cache.d.ts +33 -0
  70. package/dist/lib/tui-quota-cache.d.ts.map +1 -0
  71. package/dist/lib/tui-quota-cache.js +247 -0
  72. package/dist/lib/tui-quota-cache.js.map +1 -0
  73. package/dist/lib/tui-status.d.ts +55 -0
  74. package/dist/lib/tui-status.d.ts.map +1 -0
  75. package/dist/lib/tui-status.js +343 -0
  76. package/dist/lib/tui-status.js.map +1 -0
  77. package/dist/lib/types.d.ts +3 -1
  78. package/dist/lib/types.d.ts.map +1 -1
  79. package/dist/tui.d.ts +6 -0
  80. package/dist/tui.d.ts.map +1 -0
  81. package/dist/tui.js +363 -0
  82. package/dist/tui.js.map +1 -0
  83. package/package.json +16 -2
  84. package/scripts/install-oc-codex-multi-auth-core.js +119 -18
@@ -1,15 +1,30 @@
1
1
  import http from "node:http";
2
2
  import { logError, logWarn } from "../logger.js";
3
3
  import { oauthSuccessHtml } from "../oauth-success.js";
4
- import { OAUTH_CALLBACK_BIND_URL, OAUTH_CALLBACK_LOOPBACK_HOST, OAUTH_CALLBACK_PATH, OAUTH_CALLBACK_PORT, } from "../oauth-constants.js";
4
+ import { OAUTH_CALLBACK_BIND_HOSTS, OAUTH_CALLBACK_BIND_URL, OAUTH_CALLBACK_PATH, OAUTH_CALLBACK_PORT, } from "../oauth-constants.js";
5
+ function closeServer(server) {
6
+ try {
7
+ server.close();
8
+ }
9
+ catch (err) {
10
+ logError(`Failed to close OAuth server: ${err?.message ?? String(err)}`);
11
+ }
12
+ }
5
13
  /**
6
- * Start a small local HTTP server that waits for /auth/callback and returns the code
14
+ * Start a small local HTTP server that waits for /auth/callback and returns the code.
15
+ *
16
+ * The public redirect URI uses localhost because that is what the Codex OAuth
17
+ * client registration expects. Bind both concrete loopback interfaces so
18
+ * Windows dual-stack localhost resolution can reach either 127.0.0.1 or ::1.
19
+ *
7
20
  * @param options - OAuth state for validation
8
21
  * @returns Promise that resolves to server info
9
22
  */
10
- export function startLocalOAuthServer({ state }) {
23
+ export async function startLocalOAuthServer({ state }) {
11
24
  let pollAborted = false;
12
- const server = http.createServer((req, res) => {
25
+ let lastCode;
26
+ const allServers = [];
27
+ const handler = (req, res) => {
13
28
  try {
14
29
  const url = new URL(req.url || "", "http://localhost");
15
30
  if (url.pathname !== OAUTH_CALLBACK_PATH) {
@@ -34,60 +49,89 @@ export function startLocalOAuthServer({ state }) {
34
49
  res.setHeader("X-Content-Type-Options", "nosniff");
35
50
  res.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'none'");
36
51
  res.end(oauthSuccessHtml);
37
- server._lastCode = code;
52
+ lastCode = code;
53
+ for (const server of allServers) {
54
+ server._lastCode = code;
55
+ }
38
56
  }
39
57
  catch (err) {
40
58
  logError(`Request handler error: ${err?.message ?? String(err)}`);
41
59
  res.statusCode = 500;
42
60
  res.end("Internal error");
43
61
  }
44
- });
45
- server.unref();
46
- return new Promise((resolve) => {
47
- server
48
- .listen(OAUTH_CALLBACK_PORT, OAUTH_CALLBACK_LOOPBACK_HOST, () => {
49
- resolve({
50
- port: OAUTH_CALLBACK_PORT,
51
- ready: true,
52
- close: () => {
53
- pollAborted = true;
54
- server.close();
55
- },
56
- waitForCode: async () => {
57
- const POLL_INTERVAL_MS = 100;
58
- const TIMEOUT_MS = 5 * 60 * 1000;
59
- const maxIterations = Math.floor(TIMEOUT_MS / POLL_INTERVAL_MS);
60
- const poll = () => new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
61
- for (let i = 0; i < maxIterations; i++) {
62
- if (pollAborted)
63
- return null;
64
- const lastCode = server._lastCode;
65
- if (lastCode)
66
- return { code: lastCode };
67
- await poll();
68
- }
69
- logWarn("OAuth poll timeout after 5 minutes");
70
- return null;
71
- },
72
- });
73
- })
74
- .on("error", (err) => {
75
- logError(`Failed to bind ${OAUTH_CALLBACK_BIND_URL} (${err?.code}). Suggest device code or manual URL paste.`);
76
- resolve({
77
- port: OAUTH_CALLBACK_PORT,
78
- ready: false,
79
- close: () => {
80
- pollAborted = true;
81
- try {
82
- server.close();
83
- }
84
- catch (err) {
85
- logError(`Failed to close OAuth server: ${err?.message ?? String(err)}`);
86
- }
87
- },
88
- waitForCode: () => Promise.resolve(null),
62
+ };
63
+ const bindServer = (host) => {
64
+ const server = http.createServer(handler);
65
+ allServers.push(server);
66
+ server.unref();
67
+ return new Promise((resolve) => {
68
+ let settled = false;
69
+ const settle = (value) => {
70
+ if (settled)
71
+ return;
72
+ settled = true;
73
+ resolve(value);
74
+ };
75
+ server
76
+ .on("error", (err) => {
77
+ const message = host === OAUTH_CALLBACK_BIND_HOSTS[0]
78
+ ? `Failed to bind ${OAUTH_CALLBACK_BIND_URL} (${err?.code}). Suggest device code or manual URL paste.`
79
+ : `Failed to bind http://[::1]:${OAUTH_CALLBACK_PORT} (${err?.code}); continuing if IPv4 loopback is available.`;
80
+ if (host === OAUTH_CALLBACK_BIND_HOSTS[0]) {
81
+ logError(message);
82
+ }
83
+ else {
84
+ logWarn(message);
85
+ }
86
+ settle(null);
87
+ })
88
+ .listen(OAUTH_CALLBACK_PORT, host, () => {
89
+ settle(server);
89
90
  });
90
91
  });
91
- });
92
+ };
93
+ const boundServers = (await Promise.all(OAUTH_CALLBACK_BIND_HOSTS.map((host) => bindServer(host)))).filter((server) => server !== null);
94
+ if (boundServers.length === 0) {
95
+ return {
96
+ port: OAUTH_CALLBACK_PORT,
97
+ ready: false,
98
+ close: () => {
99
+ pollAborted = true;
100
+ for (const server of allServers) {
101
+ closeServer(server);
102
+ }
103
+ },
104
+ waitForCode: () => Promise.resolve(null),
105
+ };
106
+ }
107
+ return {
108
+ port: OAUTH_CALLBACK_PORT,
109
+ ready: true,
110
+ close: () => {
111
+ pollAborted = true;
112
+ for (const server of boundServers) {
113
+ closeServer(server);
114
+ }
115
+ },
116
+ waitForCode: async () => {
117
+ const POLL_INTERVAL_MS = 100;
118
+ const TIMEOUT_MS = 5 * 60 * 1000;
119
+ const maxIterations = Math.floor(TIMEOUT_MS / POLL_INTERVAL_MS);
120
+ const poll = () => new Promise((r) => setTimeout(r, POLL_INTERVAL_MS));
121
+ for (let i = 0; i < maxIterations; i++) {
122
+ if (pollAborted)
123
+ return null;
124
+ const serverCode = allServers
125
+ .map((server) => server._lastCode)
126
+ .find((code) => typeof code === "string" && code.length > 0);
127
+ const code = lastCode ?? serverCode;
128
+ if (code)
129
+ return { code };
130
+ await poll();
131
+ }
132
+ logWarn("OAuth poll timeout after 5 minutes");
133
+ return null;
134
+ },
135
+ };
92
136
  }
93
137
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../lib/auth/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EACN,yBAAyB,EACzB,uBAAuB,EACvB,mBAAmB,EACnB,mBAAmB,GACnB,MAAM,uBAAuB,CAAC;AAI/B,SAAS,WAAW,CAAC,MAAmB;IACvC,IAAI,CAAC;QACJ,MAAM,CAAC,KAAK,EAAE,CAAC;IAChB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACd,QAAQ,CAAC,iCAAkC,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IACrF,CAAC;AACF,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAE,KAAK,EAAqB;IACvE,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,QAA4B,CAAC;IACjC,MAAM,UAAU,GAAyB,EAAE,CAAC;IAE5C,MAAM,OAAO,GAAG,CAAC,GAAyB,EAAE,GAAwB,EAAQ,EAAE;QAC7E,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,EAAE,EAAE,kBAAkB,CAAC,CAAC;YACvD,IAAI,GAAG,CAAC,QAAQ,KAAK,mBAAmB,EAAE,CAAC;gBAC1C,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBACrB,OAAO;YACR,CAAC;YACD,IAAI,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,EAAE,CAAC;gBAC7C,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;gBAC1B,OAAO;YACR,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YAC1C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACX,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;gBACtC,OAAO;YACR,CAAC;YACD,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;YAC1D,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;YACzC,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;YACnD,GAAG,CAAC,SAAS,CAAC,yBAAyB,EAAE,uCAAuC,CAAC,CAAC;YAClF,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;YAC1B,QAAQ,GAAG,IAAI,CAAC;YAChB,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;gBACjC,MAAM,CAAC,SAAS,GAAG,IAAI,CAAC;YACzB,CAAC;QACF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACd,QAAQ,CAAC,0BAA2B,GAAa,EAAE,OAAO,IAAI,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC7E,GAAG,CAAC,UAAU,GAAG,GAAG,CAAC;YACrB,GAAG,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;QAC3B,CAAC;IACF,CAAC,CAAC;IAEF,MAAM,UAAU,GAAG,CAAC,IAAY,EAA+B,EAAE;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAuB,CAAC;QAChE,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACxB,MAAM,CAAC,KAAK,EAAE,CAAC;QAEf,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;YAC9B,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,MAAM,MAAM,GAAG,CAAC,KAAyB,EAAQ,EAAE;gBAClD,IAAI,OAAO;oBAAE,OAAO;gBACpB,OAAO,GAAG,IAAI,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC,CAAC;YAEF,MAAM;iBACJ,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;gBAC3C,MAAM,OAAO,GACZ,IAAI,KAAK,yBAAyB,CAAC,CAAC,CAAC;oBACpC,CAAC,CAAC,kBAAkB,uBAAuB,KAAK,GAAG,EAAE,IAAI,6CAA6C;oBACtG,CAAC,CAAC,+BAA+B,mBAAmB,KAAK,GAAG,EAAE,IAAI,8CAA8C,CAAC;gBACnH,IAAI,IAAI,KAAK,yBAAyB,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC3C,QAAQ,CAAC,OAAO,CAAC,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACP,OAAO,CAAC,OAAO,CAAC,CAAC;gBAClB,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,CAAC;YACd,CAAC,CAAC;iBACD,MAAM,CAAC,mBAAmB,EAAE,IAAI,EAAE,GAAG,EAAE;gBACvC,MAAM,CAAC,MAAM,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC;IAEF,MAAM,YAAY,GAAG,CAAC,MAAM,OAAO,CAAC,GAAG,CACtC,yBAAyB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CACzD,CAAC,CAAC,MAAM,CAAC,CAAC,MAAM,EAAyB,EAAE,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC;IAE9D,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC/B,OAAO;YACN,IAAI,EAAE,mBAAmB;YACzB,KAAK,EAAE,KAAK;YACZ,KAAK,EAAE,GAAG,EAAE;gBACX,WAAW,GAAG,IAAI,CAAC;gBACnB,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;oBACjC,WAAW,CAAC,MAAM,CAAC,CAAC;gBACrB,CAAC;YACF,CAAC;YACD,WAAW,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;SACxC,CAAC;IACH,CAAC;IAED,OAAO;QACN,IAAI,EAAE,mBAAmB;QACzB,KAAK,EAAE,IAAI;QACX,KAAK,EAAE,GAAG,EAAE;YACX,WAAW,GAAG,IAAI,CAAC;YACnB,KAAK,MAAM,MAAM,IAAI,YAAY,EAAE,CAAC;gBACnC,WAAW,CAAC,MAAM,CAAC,CAAC;YACrB,CAAC;QACF,CAAC;QACD,WAAW,EAAE,KAAK,IAAI,EAAE;YACvB,MAAM,gBAAgB,GAAG,GAAG,CAAC;YAC7B,MAAM,UAAU,GAAG,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YACjC,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,GAAG,gBAAgB,CAAC,CAAC;YAChE,MAAM,IAAI,GAAG,GAAG,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,gBAAgB,CAAC,CAAC,CAAC;YAC7E,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,EAAE,CAAC,EAAE,EAAE,CAAC;gBACxC,IAAI,WAAW;oBAAE,OAAO,IAAI,CAAC;gBAC7B,MAAM,UAAU,GAAG,UAAU;qBAC3B,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC;qBACjC,IAAI,CAAC,CAAC,IAAI,EAAkB,EAAE,CAAC,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;gBAC9E,MAAM,IAAI,GAAG,QAAQ,IAAI,UAAU,CAAC;gBACpC,IAAI,IAAI;oBAAE,OAAO,EAAE,IAAI,EAAE,CAAC;gBAC1B,MAAM,IAAI,EAAE,CAAC;YACd,CAAC;YACD,OAAO,CAAC,oCAAoC,CAAC,CAAC;YAC9C,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,91 @@
1
+ import { type AccountMetadataV3, type AccountStorageV3 } from "./storage.js";
2
+ export type UsageWindow = {
3
+ used_percent?: number;
4
+ limit_window_seconds?: number;
5
+ reset_at?: number;
6
+ reset_after_seconds?: number;
7
+ } | null;
8
+ export type LimitWindow = {
9
+ usedPercent?: number;
10
+ windowMinutes?: number;
11
+ resetAtMs?: number;
12
+ };
13
+ export type UsageRateLimit = {
14
+ primary_window?: UsageWindow;
15
+ secondary_window?: UsageWindow;
16
+ } | null;
17
+ export type UsageCredits = {
18
+ has_credits?: boolean;
19
+ unlimited?: boolean;
20
+ balance?: string | null;
21
+ } | null;
22
+ export type UsagePayload = {
23
+ plan_type?: string;
24
+ rate_limit?: UsageRateLimit;
25
+ code_review_rate_limit?: UsageRateLimit;
26
+ additional_rate_limits?: Array<{
27
+ limit_name?: string;
28
+ metered_feature?: string;
29
+ rate_limit?: UsageRateLimit;
30
+ }> | null;
31
+ credits?: UsageCredits;
32
+ };
33
+ export type UsageLimitPayload = {
34
+ name: string;
35
+ windowMinutes: number | null;
36
+ usedPercent: number | null;
37
+ leftPercent: number | null;
38
+ resetAtMs: number | null;
39
+ summary: string;
40
+ };
41
+ export type AdditionalUsageLimit = {
42
+ name: string;
43
+ window: LimitWindow;
44
+ };
45
+ export type CodexUsageSummary = {
46
+ planType: string | null;
47
+ credits: string | null;
48
+ primary: LimitWindow;
49
+ secondary: LimitWindow;
50
+ codeReview: LimitWindow;
51
+ additionalLimits: AdditionalUsageLimit[];
52
+ limits: UsageLimitPayload[];
53
+ };
54
+ export type EnsureCodexUsageAccessTokenResult = {
55
+ accessToken: string;
56
+ refreshed: boolean;
57
+ persisted: boolean;
58
+ };
59
+ export type UsageAccountSelection = {
60
+ index: number;
61
+ account: AccountMetadataV3;
62
+ };
63
+ export declare function getUsageLeftPercent(usedPercent: number | undefined): number | undefined;
64
+ export declare function formatUsageWindowLabel(windowMinutes: number | undefined): string;
65
+ export declare function formatUsageReset(resetAtMs: number | undefined): string | undefined;
66
+ export declare function mapUsageWindow(window: UsageWindow): LimitWindow;
67
+ export declare function formatUsageLimitTitle(windowMinutes: number | undefined, fallback?: string): string;
68
+ export declare function formatUsageLimitSummary(window: LimitWindow): string;
69
+ export declare function toUsageLimitPayload(name: string, window: LimitWindow): UsageLimitPayload;
70
+ export declare function formatUsageCredits(credits: UsageCredits): string | undefined;
71
+ export declare function formatAdditionalUsageLimitName(name: string | undefined): string;
72
+ export declare function hasUsageWindow(window: LimitWindow): boolean;
73
+ export declare function parseCodexUsagePayload(payload: UsagePayload): CodexUsageSummary;
74
+ export declare function fetchCodexUsage(params: {
75
+ accountId: string;
76
+ accessToken: string;
77
+ organizationId: string | undefined;
78
+ timeoutMs?: number;
79
+ }): Promise<UsagePayload>;
80
+ export declare function ensureCodexUsageAccessToken(params: {
81
+ storage: AccountStorageV3;
82
+ account: AccountMetadataV3;
83
+ }): Promise<EnsureCodexUsageAccessTokenResult>;
84
+ export declare function deduplicateUsageAccountIndices(storage: AccountStorageV3): number[];
85
+ export declare function resolveCodexUsageActiveAccount(storage: AccountStorageV3): UsageAccountSelection | null;
86
+ export declare function resolveCodexUsageAccountId(params: {
87
+ account: AccountMetadataV3;
88
+ accessToken: string;
89
+ }): string | undefined;
90
+ export declare function createUsageAccountFingerprint(account: AccountMetadataV3): string;
91
+ //# sourceMappingURL=codex-usage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-usage.d.ts","sourceRoot":"","sources":["../../lib/codex-usage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,368 @@
1
+ import { createHash } from "node:crypto";
2
+ import { extractAccountId } from "./accounts.js";
3
+ import { getFetchTimeoutMs, loadPluginConfig } from "./config.js";
4
+ import { CODEX_BASE_URL, PLUGIN_NAME } from "./constants.js";
5
+ import { createUsageRequestTimeoutError } from "./error-sentinels.js";
6
+ import { logWarn } from "./logger.js";
7
+ import { queuedRefresh } from "./refresh-queue.js";
8
+ import { createCodexHeaders } from "./request/fetch-helpers.js";
9
+ import { withAccountStorageTransaction, } from "./storage.js";
10
+ const usageErrorBodyMaxChars = 4096;
11
+ export function getUsageLeftPercent(usedPercent) {
12
+ return typeof usedPercent === "number" && Number.isFinite(usedPercent)
13
+ ? Math.max(0, Math.min(100, Math.round(100 - usedPercent)))
14
+ : undefined;
15
+ }
16
+ export function formatUsageWindowLabel(windowMinutes) {
17
+ if (!windowMinutes ||
18
+ !Number.isFinite(windowMinutes) ||
19
+ windowMinutes <= 0) {
20
+ return "quota";
21
+ }
22
+ if (windowMinutes % 1440 === 0)
23
+ return `${windowMinutes / 1440}d`;
24
+ if (windowMinutes % 60 === 0)
25
+ return `${windowMinutes / 60}h`;
26
+ return `${windowMinutes}m`;
27
+ }
28
+ export function formatUsageReset(resetAtMs) {
29
+ if (!resetAtMs || !Number.isFinite(resetAtMs) || resetAtMs <= 0) {
30
+ return undefined;
31
+ }
32
+ const date = new Date(resetAtMs);
33
+ if (!Number.isFinite(date.getTime()))
34
+ return undefined;
35
+ const now = new Date();
36
+ const sameDay = now.getFullYear() === date.getFullYear() &&
37
+ now.getMonth() === date.getMonth() &&
38
+ now.getDate() === date.getDate();
39
+ const time = date.toLocaleTimeString(undefined, {
40
+ hour: "2-digit",
41
+ minute: "2-digit",
42
+ hour12: false,
43
+ });
44
+ if (sameDay)
45
+ return time;
46
+ const day = date.toLocaleDateString(undefined, {
47
+ month: "short",
48
+ day: "2-digit",
49
+ });
50
+ return `${time} on ${day}`;
51
+ }
52
+ export function mapUsageWindow(window) {
53
+ if (!window)
54
+ return {};
55
+ return {
56
+ usedPercent: typeof window.used_percent === "number" &&
57
+ Number.isFinite(window.used_percent)
58
+ ? window.used_percent
59
+ : undefined,
60
+ windowMinutes: typeof window.limit_window_seconds === "number" &&
61
+ Number.isFinite(window.limit_window_seconds)
62
+ ? Math.max(1, Math.ceil(window.limit_window_seconds / 60))
63
+ : undefined,
64
+ resetAtMs: typeof window.reset_at === "number" && window.reset_at > 0
65
+ ? window.reset_at * 1000
66
+ : typeof window.reset_after_seconds === "number" &&
67
+ window.reset_after_seconds > 0
68
+ ? Date.now() + window.reset_after_seconds * 1000
69
+ : undefined,
70
+ };
71
+ }
72
+ export function formatUsageLimitTitle(windowMinutes, fallback = "quota") {
73
+ if (windowMinutes === 300)
74
+ return "5h limit";
75
+ if (windowMinutes === 10080)
76
+ return "Weekly limit";
77
+ if (fallback !== "quota")
78
+ return fallback;
79
+ return `${formatUsageWindowLabel(windowMinutes)} limit`;
80
+ }
81
+ export function formatUsageLimitSummary(window) {
82
+ const left = getUsageLeftPercent(window.usedPercent);
83
+ const reset = formatUsageReset(window.resetAtMs);
84
+ if (left !== undefined && reset)
85
+ return `${left}% left (resets ${reset})`;
86
+ if (left !== undefined)
87
+ return `${left}% left`;
88
+ if (reset)
89
+ return `resets ${reset}`;
90
+ return "unavailable";
91
+ }
92
+ export function toUsageLimitPayload(name, window) {
93
+ return {
94
+ name,
95
+ windowMinutes: window.windowMinutes ?? null,
96
+ usedPercent: typeof window.usedPercent === "number" ? window.usedPercent : null,
97
+ leftPercent: getUsageLeftPercent(window.usedPercent) ?? null,
98
+ resetAtMs: window.resetAtMs ?? null,
99
+ summary: formatUsageLimitSummary(window),
100
+ };
101
+ }
102
+ export function formatUsageCredits(credits) {
103
+ if (!credits)
104
+ return undefined;
105
+ if (credits.unlimited)
106
+ return "unlimited";
107
+ if (typeof credits.balance === "string" && credits.balance.trim()) {
108
+ return credits.balance.trim();
109
+ }
110
+ if (credits.has_credits)
111
+ return "available";
112
+ return undefined;
113
+ }
114
+ export function formatAdditionalUsageLimitName(name) {
115
+ if (!name)
116
+ return "Additional limit";
117
+ if (name === "code_review_rate_limit")
118
+ return "Code review";
119
+ return name
120
+ .replace(/[_-]+/g, " ")
121
+ .replace(/\b\w/g, (match) => match.toUpperCase());
122
+ }
123
+ export function hasUsageWindow(window) {
124
+ return Boolean(window.windowMinutes ||
125
+ typeof window.usedPercent === "number" ||
126
+ window.resetAtMs);
127
+ }
128
+ export function parseCodexUsagePayload(payload) {
129
+ const primary = mapUsageWindow(payload.rate_limit?.primary_window ?? null);
130
+ const secondary = mapUsageWindow(payload.rate_limit?.secondary_window ?? null);
131
+ const codeReviewRateLimit = payload.code_review_rate_limit ??
132
+ payload.additional_rate_limits?.find((entry) => entry.limit_name === "code_review_rate_limit")?.rate_limit ??
133
+ null;
134
+ const codeReview = mapUsageWindow(codeReviewRateLimit?.primary_window ?? null);
135
+ const credits = formatUsageCredits(payload.credits ?? null);
136
+ const additionalLimits = (payload.additional_rate_limits ?? [])
137
+ .filter((entry) => entry.limit_name !== "code_review_rate_limit")
138
+ .map((entry) => ({
139
+ name: formatAdditionalUsageLimitName(entry.limit_name ?? entry.metered_feature),
140
+ window: mapUsageWindow(entry.rate_limit?.primary_window ?? null),
141
+ }));
142
+ const limits = [
143
+ toUsageLimitPayload(formatUsageLimitTitle(primary.windowMinutes), primary),
144
+ toUsageLimitPayload(formatUsageLimitTitle(secondary.windowMinutes), secondary),
145
+ ];
146
+ if (hasUsageWindow(codeReview)) {
147
+ limits.push(toUsageLimitPayload("Code review", codeReview));
148
+ }
149
+ for (const limit of additionalLimits) {
150
+ limits.push(toUsageLimitPayload(limit.name, limit.window));
151
+ }
152
+ return {
153
+ planType: payload.plan_type ?? null,
154
+ credits: credits ?? null,
155
+ primary,
156
+ secondary,
157
+ codeReview,
158
+ additionalLimits,
159
+ limits,
160
+ };
161
+ }
162
+ function sanitizeUsageErrorMessage(status, bodyText) {
163
+ const normalized = bodyText.replace(/\s+/g, " ").trim();
164
+ const redacted = normalized
165
+ .replace(/Bearer\s+\S+/gi, "Bearer [redacted]")
166
+ .replace(/eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g, "[redacted-token]")
167
+ .replace(/\bsk-[A-Za-z0-9][A-Za-z0-9._:-]{19,}\b/gi, "[redacted-token]")
168
+ .replace(/\b[a-f0-9]{40,}\b/gi, "[redacted-token]");
169
+ return redacted ? `HTTP ${status}: ${redacted.slice(0, 200)}` : `HTTP ${status}`;
170
+ }
171
+ function isAbortError(error) {
172
+ return ((error instanceof Error && error.name === "AbortError") ||
173
+ (typeof DOMException !== "undefined" &&
174
+ error instanceof DOMException &&
175
+ error.name === "AbortError"));
176
+ }
177
+ export async function fetchCodexUsage(params) {
178
+ const headers = createCodexHeaders(undefined, params.accountId, params.accessToken, {
179
+ organizationId: params.organizationId,
180
+ });
181
+ headers.set("accept", "application/json");
182
+ const controller = new AbortController();
183
+ const timeout = setTimeout(() => controller.abort(), params.timeoutMs ?? getFetchTimeoutMs(loadPluginConfig()));
184
+ try {
185
+ const response = await fetch(`${CODEX_BASE_URL}/wham/usage`, {
186
+ method: "GET",
187
+ headers,
188
+ signal: controller.signal,
189
+ });
190
+ if (!response.ok) {
191
+ let bodyText = "";
192
+ try {
193
+ bodyText = (await response.text()).slice(0, usageErrorBodyMaxChars);
194
+ }
195
+ catch (error) {
196
+ if (isAbortError(error) || controller.signal.aborted) {
197
+ throw createUsageRequestTimeoutError();
198
+ }
199
+ throw error;
200
+ }
201
+ if (controller.signal.aborted) {
202
+ throw createUsageRequestTimeoutError();
203
+ }
204
+ throw new Error(sanitizeUsageErrorMessage(response.status, bodyText));
205
+ }
206
+ return (await response.json());
207
+ }
208
+ catch (error) {
209
+ if (isAbortError(error)) {
210
+ throw createUsageRequestTimeoutError();
211
+ }
212
+ throw error;
213
+ }
214
+ finally {
215
+ clearTimeout(timeout);
216
+ }
217
+ }
218
+ function applyRefreshedCredentials(target, result) {
219
+ target.refreshToken = result.refresh;
220
+ target.accessToken = result.access;
221
+ target.expiresAt = result.expires;
222
+ }
223
+ async function persistRefreshedCredentials(params) {
224
+ return await withAccountStorageTransaction(async (current, persist) => {
225
+ const latestStorage = current ??
226
+ {
227
+ version: 3,
228
+ accounts: [],
229
+ activeIndex: 0,
230
+ activeIndexByFamily: {},
231
+ };
232
+ const uniqueMatch = (matches) => matches.length === 1 ? matches[0] : undefined;
233
+ let updated = false;
234
+ if (params.previousRefreshToken) {
235
+ for (const storedAccount of latestStorage.accounts) {
236
+ if (storedAccount.refreshToken === params.previousRefreshToken) {
237
+ applyRefreshedCredentials(storedAccount, params.refreshResult);
238
+ updated = true;
239
+ }
240
+ }
241
+ }
242
+ if (!updated) {
243
+ const normalizedOrganizationId = params.organizationId?.trim() ?? "";
244
+ const normalizedEmail = params.email?.trim().toLowerCase();
245
+ const orgScopedMatches = params.accountId
246
+ ? latestStorage.accounts.filter((storedAccount) => storedAccount.accountId === params.accountId &&
247
+ (storedAccount.organizationId?.trim() ?? "") ===
248
+ normalizedOrganizationId)
249
+ : [];
250
+ const accountIdMatches = params.accountId
251
+ ? latestStorage.accounts.filter((storedAccount) => storedAccount.accountId === params.accountId)
252
+ : [];
253
+ const emailMatches = normalizedEmail && !params.accountId
254
+ ? latestStorage.accounts.filter((storedAccount) => storedAccount.email?.trim().toLowerCase() === normalizedEmail)
255
+ : [];
256
+ const fallbackTarget = uniqueMatch(orgScopedMatches) ??
257
+ uniqueMatch(accountIdMatches) ??
258
+ uniqueMatch(emailMatches);
259
+ if (fallbackTarget) {
260
+ applyRefreshedCredentials(fallbackTarget, params.refreshResult);
261
+ updated = true;
262
+ }
263
+ }
264
+ if (updated) {
265
+ await persist(latestStorage);
266
+ }
267
+ if (!updated) {
268
+ logWarn(`[${PLUGIN_NAME}] persistRefreshedCredentials could not find a matching stored account. Refreshed credentials remain in-memory for this invocation only.`, {
269
+ accountId: params.accountId,
270
+ organizationId: params.organizationId,
271
+ });
272
+ }
273
+ return updated;
274
+ });
275
+ }
276
+ export async function ensureCodexUsageAccessToken(params) {
277
+ let accessToken = params.account.accessToken;
278
+ if (typeof accessToken === "string" &&
279
+ accessToken &&
280
+ typeof params.account.expiresAt === "number" &&
281
+ params.account.expiresAt > Date.now() + 30_000) {
282
+ return { accessToken, refreshed: false, persisted: false };
283
+ }
284
+ const previousRefreshToken = params.account.refreshToken;
285
+ if (!previousRefreshToken) {
286
+ throw new Error("Cannot refresh: account has no refresh token");
287
+ }
288
+ const refreshResult = await queuedRefresh(previousRefreshToken);
289
+ if (refreshResult.type !== "success") {
290
+ throw new Error(refreshResult.message ?? refreshResult.reason);
291
+ }
292
+ let refreshedCount = 0;
293
+ for (const storedAccount of params.storage.accounts) {
294
+ if (storedAccount.refreshToken === previousRefreshToken) {
295
+ applyRefreshedCredentials(storedAccount, refreshResult);
296
+ refreshedCount += 1;
297
+ }
298
+ }
299
+ if (refreshedCount === 0) {
300
+ applyRefreshedCredentials(params.account, refreshResult);
301
+ }
302
+ const persisted = await persistRefreshedCredentials({
303
+ previousRefreshToken,
304
+ accountId: params.account.accountId,
305
+ organizationId: params.account.organizationId,
306
+ email: params.account.email,
307
+ refreshResult,
308
+ });
309
+ accessToken = refreshResult.access;
310
+ return { accessToken, refreshed: true, persisted };
311
+ }
312
+ export function deduplicateUsageAccountIndices(storage) {
313
+ const seenTokens = new Set();
314
+ const uniqueIndices = [];
315
+ for (let i = 0; i < storage.accounts.length; i += 1) {
316
+ const account = storage.accounts[i];
317
+ if (!account)
318
+ continue;
319
+ const refreshToken = typeof account.refreshToken === "string"
320
+ ? account.refreshToken.trim()
321
+ : "";
322
+ if (refreshToken && seenTokens.has(refreshToken))
323
+ continue;
324
+ if (refreshToken)
325
+ seenTokens.add(refreshToken);
326
+ uniqueIndices.push(i);
327
+ }
328
+ return uniqueIndices;
329
+ }
330
+ export function resolveCodexUsageActiveAccount(storage) {
331
+ if (storage.accounts.length === 0)
332
+ return null;
333
+ const rawIndex = storage.activeIndexByFamily?.codex ?? storage.activeIndex;
334
+ const numericIndex = typeof rawIndex === "number" && Number.isFinite(rawIndex) ? rawIndex : 0;
335
+ const index = Math.max(0, Math.min(storage.accounts.length - 1, Math.trunc(numericIndex)));
336
+ const activeAccount = storage.accounts[index];
337
+ if (!activeAccount)
338
+ return null;
339
+ const activeLastUsed = typeof activeAccount.lastUsed === "number" && Number.isFinite(activeAccount.lastUsed)
340
+ ? activeAccount.lastUsed
341
+ : 0;
342
+ let newestIndex = index;
343
+ let newestLastUsed = activeLastUsed;
344
+ for (let i = 0; i < storage.accounts.length; i += 1) {
345
+ const account = storage.accounts[i];
346
+ const lastUsed = typeof account?.lastUsed === "number" && Number.isFinite(account.lastUsed)
347
+ ? account.lastUsed
348
+ : 0;
349
+ if (account && lastUsed > newestLastUsed) {
350
+ newestIndex = i;
351
+ newestLastUsed = lastUsed;
352
+ }
353
+ }
354
+ const account = storage.accounts[newestIndex];
355
+ return account ? { index: newestIndex, account } : null;
356
+ }
357
+ export function resolveCodexUsageAccountId(params) {
358
+ return params.account.accountId ?? extractAccountId(params.accessToken);
359
+ }
360
+ export function createUsageAccountFingerprint(account) {
361
+ const fingerprintSource = [
362
+ account.accountId ?? "",
363
+ account.organizationId ?? "",
364
+ account.refreshToken ?? "",
365
+ ].join("\0");
366
+ return createHash("sha256").update(fingerprintSource).digest("hex").slice(0, 16);
367
+ }
368
+ //# sourceMappingURL=codex-usage.js.map