responses-proxy 0.1.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 (161) hide show
  1. package/README.md +56 -0
  2. package/cli.js +118 -0
  3. package/dist/anthropic-messages.js +383 -0
  4. package/dist/anthropic-messages.test.js +209 -0
  5. package/dist/audit-log.js +138 -0
  6. package/dist/audit-log.test.js +480 -0
  7. package/dist/billing-expiration.js +70 -0
  8. package/dist/billing-expiration.test.js +114 -0
  9. package/dist/billing.js +716 -0
  10. package/dist/billing.test.js +228 -0
  11. package/dist/chatgpt-oauth-store.js +240 -0
  12. package/dist/chatgpt-oauth-store.test.js +88 -0
  13. package/dist/chatgpt-oauth.js +118 -0
  14. package/dist/chatgpt-oauth.test.js +63 -0
  15. package/dist/chatgpt-provider-auth.js +60 -0
  16. package/dist/chatgpt-provider-auth.test.js +101 -0
  17. package/dist/client/app-icon.svg +17 -0
  18. package/dist/client/assets/index-C7Vvhst8.js +14 -0
  19. package/dist/client/assets/index-DpqgYK3L.css +1 -0
  20. package/dist/client/favicon.svg +17 -0
  21. package/dist/client/index.html +31 -0
  22. package/dist/client-config-apply.js +345 -0
  23. package/dist/client-config-apply.test.js +185 -0
  24. package/dist/client-token-limits.js +111 -0
  25. package/dist/client-token-limits.test.js +129 -0
  26. package/dist/codex-config.js +47 -0
  27. package/dist/codex-setup.js +87 -0
  28. package/dist/codex-setup.test.js +30 -0
  29. package/dist/config.js +314 -0
  30. package/dist/cost-analytics.js +31 -0
  31. package/dist/cost-analytics.test.js +38 -0
  32. package/dist/customer-key-access.js +126 -0
  33. package/dist/customer-key-access.test.js +178 -0
  34. package/dist/customer-keys.js +209 -0
  35. package/dist/customer-keys.test.js +68 -0
  36. package/dist/customer-usage.js +18 -0
  37. package/dist/customer-usage.test.js +55 -0
  38. package/dist/dashboard-auth.js +318 -0
  39. package/dist/dashboard-auth.test.js +133 -0
  40. package/dist/dashboard-serving.test.js +235 -0
  41. package/dist/error-response.js +174 -0
  42. package/dist/error-response.test.js +88 -0
  43. package/dist/forward.js +357 -0
  44. package/dist/health-websocket-manager.js +174 -0
  45. package/dist/http-rate-limit.js +36 -0
  46. package/dist/http-rate-limit.test.js +62 -0
  47. package/dist/kiro-auth.js +136 -0
  48. package/dist/kiro-auth.test.js +234 -0
  49. package/dist/kiro-codewhisperer.js +646 -0
  50. package/dist/kiro-codewhisperer.test.js +219 -0
  51. package/dist/kiro-device-login.js +338 -0
  52. package/dist/kiro-eventstream.js +219 -0
  53. package/dist/kiro-eventstream.test.js +79 -0
  54. package/dist/kiro-forward.js +401 -0
  55. package/dist/kiro-import-cli.js +69 -0
  56. package/dist/kiro-import.js +94 -0
  57. package/dist/kiro-import.test.js +125 -0
  58. package/dist/kiro-token-store.js +196 -0
  59. package/dist/kiro-token-store.test.js +207 -0
  60. package/dist/krouter-usage.js +243 -0
  61. package/dist/model-combo-repository.js +147 -0
  62. package/dist/model-routing.js +69 -0
  63. package/dist/model-routing.test.js +41 -0
  64. package/dist/normalize-request.js +531 -0
  65. package/dist/normalize-request.test.js +277 -0
  66. package/dist/omv-public-firewall.test.js +11 -0
  67. package/dist/package.json +17 -0
  68. package/dist/prompt-cache-state.js +146 -0
  69. package/dist/prompt-cache-state.test.js +71 -0
  70. package/dist/prompt-cache.js +229 -0
  71. package/dist/provider-health-service.js +404 -0
  72. package/dist/provider-request-parameters.js +107 -0
  73. package/dist/provider-request-parameters.test.js +26 -0
  74. package/dist/provider-routing.js +114 -0
  75. package/dist/provider-routing.test.js +64 -0
  76. package/dist/provider-usage.js +314 -0
  77. package/dist/request-timeout-policy.js +61 -0
  78. package/dist/request-timeout-policy.test.js +40 -0
  79. package/dist/response-cache.js +69 -0
  80. package/dist/response-cache.test.js +28 -0
  81. package/dist/routing-combo-repository.js +300 -0
  82. package/dist/routing-engine.js +377 -0
  83. package/dist/routing-integration.js +155 -0
  84. package/dist/routing-simulation-engine.js +326 -0
  85. package/dist/rtk-layer.js +483 -0
  86. package/dist/rtk-layer.test.js +198 -0
  87. package/dist/runtime-provider-repository.js +1742 -0
  88. package/dist/runtime-provider-repository.test.js +1177 -0
  89. package/dist/schema.js +118 -0
  90. package/dist/schema.test.js +16 -0
  91. package/dist/sepay-webhook.js +87 -0
  92. package/dist/sepay-webhook.test.js +142 -0
  93. package/dist/server-body-limit.test.js +35 -0
  94. package/dist/server-client-token-limits.test.js +161 -0
  95. package/dist/server-codex-config-setup.test.js +76 -0
  96. package/dist/server-http-rate-limit.test.js +80 -0
  97. package/dist/server-response-cache.test.js +105 -0
  98. package/dist/server-routes-alias.test.js +39 -0
  99. package/dist/server-sepay-webhook-security.test.js +59 -0
  100. package/dist/server.js +5906 -0
  101. package/dist/session-log.js +178 -0
  102. package/dist/tailnet-funnel-script.test.js +33 -0
  103. package/dist/telegram-bot/actions.js +118 -0
  104. package/dist/telegram-bot/admin-actions.js +103 -0
  105. package/dist/telegram-bot/auth.js +46 -0
  106. package/dist/telegram-bot/auth.test.js +1 -0
  107. package/dist/telegram-bot/bot-identity-repository.js +189 -0
  108. package/dist/telegram-bot/bot-identity-repository.test.js +78 -0
  109. package/dist/telegram-bot/callbacks.js +30 -0
  110. package/dist/telegram-bot/codex-config-delivery.js +38 -0
  111. package/dist/telegram-bot/codex-config-delivery.test.js +75 -0
  112. package/dist/telegram-bot/commands/accounts.js +140 -0
  113. package/dist/telegram-bot/commands/apikey.js +737 -0
  114. package/dist/telegram-bot/commands/apply.js +265 -0
  115. package/dist/telegram-bot/commands/clients.js +13 -0
  116. package/dist/telegram-bot/commands/customer-billing.test.js +271 -0
  117. package/dist/telegram-bot/commands/grant.js +138 -0
  118. package/dist/telegram-bot/commands/grant.test.js +217 -0
  119. package/dist/telegram-bot/commands/help.js +52 -0
  120. package/dist/telegram-bot/commands/me.js +53 -0
  121. package/dist/telegram-bot/commands/models.js +6 -0
  122. package/dist/telegram-bot/commands/oauth.js +64 -0
  123. package/dist/telegram-bot/commands/plans.js +96 -0
  124. package/dist/telegram-bot/commands/providers.js +27 -0
  125. package/dist/telegram-bot/commands/quota.js +10 -0
  126. package/dist/telegram-bot/commands/renew-user.js +139 -0
  127. package/dist/telegram-bot/commands/renew-user.test.js +184 -0
  128. package/dist/telegram-bot/commands/renew.js +1369 -0
  129. package/dist/telegram-bot/commands/renew.test.js +1633 -0
  130. package/dist/telegram-bot/commands/start.js +212 -0
  131. package/dist/telegram-bot/commands/start.test.js +280 -0
  132. package/dist/telegram-bot/commands/status.js +6 -0
  133. package/dist/telegram-bot/commands/tailscale.js +15 -0
  134. package/dist/telegram-bot/commands/tailscale.test.js +76 -0
  135. package/dist/telegram-bot/commands/test.js +51 -0
  136. package/dist/telegram-bot/commands/test.test.js +14 -0
  137. package/dist/telegram-bot/commands/usage.js +10 -0
  138. package/dist/telegram-bot/config.js +98 -0
  139. package/dist/telegram-bot/config.test.js +42 -0
  140. package/dist/telegram-bot/customer-actions.js +160 -0
  141. package/dist/telegram-bot/customer-api-keys.js +68 -0
  142. package/dist/telegram-bot/customer-billing.js +72 -0
  143. package/dist/telegram-bot/customer-workspace-repository.js +134 -0
  144. package/dist/telegram-bot/customer-workspace-repository.test.js +47 -0
  145. package/dist/telegram-bot/dashboard-login.js +39 -0
  146. package/dist/telegram-bot/format.js +140 -0
  147. package/dist/telegram-bot/grants.js +370 -0
  148. package/dist/telegram-bot/grants.test.js +290 -0
  149. package/dist/telegram-bot/index.js +85 -0
  150. package/dist/telegram-bot/message-cleanup.js +55 -0
  151. package/dist/telegram-bot/message-cleanup.test.js +77 -0
  152. package/dist/telegram-bot/message-format.js +45 -0
  153. package/dist/telegram-bot/message-format.test.js +10 -0
  154. package/dist/telegram-bot/proxy-client.js +174 -0
  155. package/dist/telegram-bot/rate-limit.js +95 -0
  156. package/dist/telegram-bot/rate-limit.test.js +58 -0
  157. package/dist/telegram-bot/sessions.js +171 -0
  158. package/dist/telegram-bot/sessions.test.js +107 -0
  159. package/dist/telegram-bot/telegram-adapter.js +126 -0
  160. package/dist/telegram-bot/worker.js +63 -0
  161. package/package.json +39 -0
@@ -0,0 +1,63 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import { buildChatGptAuthUrl, generateChatGptPkceCodes, normalizeChatGptTokenBundle, parseChatGptJwtClaims, } from "./chatgpt-oauth.js";
4
+ function jwtPayload(payload) {
5
+ return `header.${Buffer.from(JSON.stringify(payload)).toString("base64url")}.signature`;
6
+ }
7
+ test("generates PKCE codes suitable for S256 OAuth", () => {
8
+ const pkce = generateChatGptPkceCodes();
9
+ assert.ok(pkce.codeVerifier.length >= 43);
10
+ assert.match(pkce.codeVerifier, /^[A-Za-z0-9_-]+$/);
11
+ assert.match(pkce.codeChallenge, /^[A-Za-z0-9_-]+$/);
12
+ assert.notEqual(pkce.codeVerifier, pkce.codeChallenge);
13
+ });
14
+ test("builds ChatGPT auth URL with CLIProxyAPI-compatible parameters", () => {
15
+ const url = new URL(buildChatGptAuthUrl({
16
+ CHATGPT_OAUTH_AUTH_URL: "https://auth.openai.com/oauth/authorize",
17
+ CHATGPT_OAUTH_CLIENT_ID: "client-id",
18
+ CHATGPT_OAUTH_REDIRECT_URI: "http://localhost:1455/auth/callback",
19
+ }, "state-value", {
20
+ codeVerifier: "verifier",
21
+ codeChallenge: "challenge",
22
+ }));
23
+ assert.equal(url.searchParams.get("client_id"), "client-id");
24
+ assert.equal(url.searchParams.get("response_type"), "code");
25
+ assert.equal(url.searchParams.get("redirect_uri"), "http://localhost:1455/auth/callback");
26
+ assert.equal(url.searchParams.get("scope"), "openid email profile offline_access");
27
+ assert.equal(url.searchParams.get("state"), "state-value");
28
+ assert.equal(url.searchParams.get("code_challenge"), "challenge");
29
+ assert.equal(url.searchParams.get("code_challenge_method"), "S256");
30
+ assert.equal(url.searchParams.get("prompt"), "login");
31
+ assert.equal(url.searchParams.get("id_token_add_organizations"), "true");
32
+ assert.equal(url.searchParams.get("codex_cli_simplified_flow"), "true");
33
+ });
34
+ test("parses account id and email from ChatGPT id token claims", () => {
35
+ const claims = parseChatGptJwtClaims(jwtPayload({
36
+ email: "user@example.com",
37
+ "https://api.openai.com/auth": {
38
+ account_id: "acct_123",
39
+ },
40
+ }));
41
+ assert.deepEqual(claims, {
42
+ email: "user@example.com",
43
+ accountId: "acct_123",
44
+ });
45
+ });
46
+ test("normalizes token response into persisted bundle", () => {
47
+ const now = new Date("2026-04-27T00:00:00.000Z");
48
+ const bundle = normalizeChatGptTokenBundle({
49
+ id_token: jwtPayload({
50
+ email: "user@example.com",
51
+ account_id: "acct_123",
52
+ }),
53
+ access_token: "access-token",
54
+ refresh_token: "refresh-token",
55
+ expires_in: 60,
56
+ }, now);
57
+ assert.equal(bundle.email, "user@example.com");
58
+ assert.equal(bundle.accountId, "acct_123");
59
+ assert.equal(bundle.accessToken, "access-token");
60
+ assert.equal(bundle.refreshToken, "refresh-token");
61
+ assert.equal(bundle.expiresAt, "2026-04-27T00:01:00.000Z");
62
+ assert.equal(bundle.lastRefreshAt, "2026-04-27T00:00:00.000Z");
63
+ });
@@ -0,0 +1,60 @@
1
+ import { refreshChatGptTokens } from "./chatgpt-oauth.js";
2
+ const refreshLocks = new Map();
3
+ const accountCursors = new Map();
4
+ export class AccountPoolAuthError extends Error {
5
+ statusCode = 409;
6
+ body;
7
+ constructor(code, message) {
8
+ super(message);
9
+ this.body = {
10
+ type: "authentication_error",
11
+ code,
12
+ message,
13
+ };
14
+ }
15
+ }
16
+ export async function resolveChatGptAccessToken(args) {
17
+ const account = args.provider.chatgptAccountId?.trim()
18
+ ? args.store.getAccount(args.provider.chatgptAccountId.trim())
19
+ : selectNextChatGptAccount(args.provider.id, args.store, args.rotationMode ?? "round_robin");
20
+ if (!account || account.disabled) {
21
+ throw new AccountPoolAuthError("ACCOUNT_POOL_UNAVAILABLE", "No connected accounts are available for this provider.");
22
+ }
23
+ const now = args.now ?? new Date();
24
+ const refreshLeadMs = args.config.CHATGPT_OAUTH_REFRESH_LEAD_DAYS * 24 * 60 * 60 * 1000;
25
+ if (Date.parse(account.expiresAt) - now.getTime() > refreshLeadMs) {
26
+ return account.accessToken;
27
+ }
28
+ const existingLock = refreshLocks.get(account.id);
29
+ if (existingLock) {
30
+ return existingLock;
31
+ }
32
+ const refreshPromise = refreshChatGptTokens(args.config, account.refreshToken, fetch, now)
33
+ .then((bundle) => args.store.updateTokens(account.id, bundle, now).accessToken)
34
+ .finally(() => {
35
+ refreshLocks.delete(account.id);
36
+ });
37
+ refreshLocks.set(account.id, refreshPromise);
38
+ return refreshPromise;
39
+ }
40
+ function selectNextChatGptAccount(providerId, store, rotationMode) {
41
+ const accounts = store.listAvailableAccounts();
42
+ if (!accounts.length) {
43
+ return undefined;
44
+ }
45
+ if (rotationMode === "first_available") {
46
+ return accounts[0];
47
+ }
48
+ if (rotationMode === "random") {
49
+ return accounts[Math.floor(Math.random() * accounts.length)];
50
+ }
51
+ const cursor = accountCursors.get(providerId) ?? 0;
52
+ const account = accounts[cursor % accounts.length];
53
+ accountCursors.set(providerId, (cursor + 1) % accounts.length);
54
+ return account;
55
+ }
56
+ export function buildChatGptCodexHeaders() {
57
+ return {
58
+ Originator: "codex-tui",
59
+ };
60
+ }
@@ -0,0 +1,101 @@
1
+ import assert from "node:assert/strict";
2
+ import { mkdtempSync, rmSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import path from "node:path";
5
+ import test from "node:test";
6
+ import { resolveChatGptAccessToken } from "./chatgpt-provider-auth.js";
7
+ import { ChatGptOAuthStore } from "./chatgpt-oauth-store.js";
8
+ const config = {
9
+ CHATGPT_OAUTH_REFRESH_LEAD_DAYS: 1,
10
+ CHATGPT_OAUTH_TOKEN_URL: "https://auth.openai.com/oauth/token",
11
+ CHATGPT_OAUTH_CLIENT_ID: "client",
12
+ };
13
+ const provider = {
14
+ id: "chatgpt-oauth",
15
+ name: "ChatGPT OAuth",
16
+ baseUrl: "https://chatgpt.com/backend-api/codex",
17
+ responsesUrl: "https://chatgpt.com/backend-api/codex/responses",
18
+ authMode: "chatgpt_oauth",
19
+ providerApiKeys: [],
20
+ clientApiKeys: [],
21
+ capabilities: {
22
+ usageCheckEnabled: false,
23
+ stripMaxOutputTokens: false,
24
+ requestParameterPolicy: {},
25
+ sanitizeReasoningSummary: false,
26
+ stripModelPrefixes: [],
27
+ },
28
+ };
29
+ test("ChatGPT OAuth shared provider rotates through available accounts", async () => {
30
+ const dir = mkdtempSync(path.join(tmpdir(), "responses-proxy-oauth-pool-"));
31
+ try {
32
+ const store = ChatGptOAuthStore.create(path.join(dir, "app.db"));
33
+ const now = new Date("2026-04-27T00:00:00.000Z");
34
+ store.upsertAccount({
35
+ idToken: "id-token-a",
36
+ accessToken: "access-token-a",
37
+ refreshToken: "refresh-token-a",
38
+ accountId: "acct_a",
39
+ email: "a@example.com",
40
+ expiresAt: "2026-05-27T00:00:00.000Z",
41
+ lastRefreshAt: now.toISOString(),
42
+ }, now);
43
+ store.upsertAccount({
44
+ idToken: "id-token-b",
45
+ accessToken: "access-token-b",
46
+ refreshToken: "refresh-token-b",
47
+ accountId: "acct_b",
48
+ email: "b@example.com",
49
+ expiresAt: "2026-05-27T00:00:00.000Z",
50
+ lastRefreshAt: now.toISOString(),
51
+ }, now);
52
+ assert.equal(await resolveChatGptAccessToken({ provider, store, config, now }), "access-token-a");
53
+ assert.equal(await resolveChatGptAccessToken({ provider, store, config, now }), "access-token-b");
54
+ assert.equal(await resolveChatGptAccessToken({ provider, store, config, now }), "access-token-a");
55
+ }
56
+ finally {
57
+ rmSync(dir, { recursive: true, force: true });
58
+ }
59
+ });
60
+ test("ChatGPT OAuth shared provider can use first available rotation", async () => {
61
+ const dir = mkdtempSync(path.join(tmpdir(), "responses-proxy-oauth-pool-"));
62
+ try {
63
+ const store = ChatGptOAuthStore.create(path.join(dir, "app.db"));
64
+ const now = new Date("2026-04-27T00:00:00.000Z");
65
+ store.upsertAccount({
66
+ idToken: "id-token-a",
67
+ accessToken: "first-token",
68
+ refreshToken: "refresh-token-a",
69
+ accountId: "acct_a",
70
+ email: "a@example.com",
71
+ expiresAt: "2026-05-27T00:00:00.000Z",
72
+ lastRefreshAt: now.toISOString(),
73
+ }, now);
74
+ store.upsertAccount({
75
+ idToken: "id-token-b",
76
+ accessToken: "second-token",
77
+ refreshToken: "refresh-token-b",
78
+ accountId: "acct_b",
79
+ email: "b@example.com",
80
+ expiresAt: "2026-05-27T00:00:00.000Z",
81
+ lastRefreshAt: now.toISOString(),
82
+ }, now);
83
+ assert.equal(await resolveChatGptAccessToken({
84
+ provider,
85
+ store,
86
+ config,
87
+ rotationMode: "first_available",
88
+ now,
89
+ }), "first-token");
90
+ assert.equal(await resolveChatGptAccessToken({
91
+ provider,
92
+ store,
93
+ config,
94
+ rotationMode: "first_available",
95
+ now,
96
+ }), "first-token");
97
+ }
98
+ finally {
99
+ rmSync(dir, { recursive: true, force: true });
100
+ }
101
+ });
@@ -0,0 +1,17 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" fill="none">
2
+ <defs>
3
+ <linearGradient id="iconBg" x1="38" y1="22" x2="212" y2="234" gradientUnits="userSpaceOnUse">
4
+ <stop stop-color="#60A5FA"/>
5
+ <stop offset="1" stop-color="#06B6D4"/>
6
+ </linearGradient>
7
+ <linearGradient id="iconLine" x1="58" y1="70" x2="202" y2="182" gradientUnits="userSpaceOnUse">
8
+ <stop stop-color="#F8FBFF"/>
9
+ <stop offset="1" stop-color="#A5F3FC"/>
10
+ </linearGradient>
11
+ </defs>
12
+ <rect width="256" height="256" rx="64" fill="#07111F"/>
13
+ <rect x="18" y="18" width="220" height="220" rx="52" fill="url(#iconBg)" fill-opacity=".16" stroke="url(#iconBg)" stroke-width="8"/>
14
+ <path d="M56 148h38l24-52 34 86 26-44h22" stroke="url(#iconLine)" stroke-width="18" stroke-linecap="round" stroke-linejoin="round"/>
15
+ <circle cx="56" cy="148" r="12" fill="#E0F2FE"/>
16
+ <circle cx="200" cy="138" r="12" fill="#67E8F9"/>
17
+ </svg>