@vacbo/opencode-anthropic-fix 0.1.7 → 0.1.9

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 (107) hide show
  1. package/README.md +88 -88
  2. package/dist/opencode-anthropic-auth-cli.mjs +804 -507
  3. package/dist/opencode-anthropic-auth-plugin.js +4751 -4109
  4. package/package.json +67 -59
  5. package/src/__tests__/billing-edge-cases.test.ts +59 -59
  6. package/src/__tests__/bun-proxy.parallel.test.ts +388 -382
  7. package/src/__tests__/cc-comparison.test.ts +87 -87
  8. package/src/__tests__/cc-credentials.test.ts +254 -250
  9. package/src/__tests__/cch-drift-checker.test.ts +51 -51
  10. package/src/__tests__/cch-native-style.test.ts +56 -56
  11. package/src/__tests__/debug-gating.test.ts +42 -42
  12. package/src/__tests__/decomposition-smoke.test.ts +68 -68
  13. package/src/__tests__/fingerprint-regression.test.ts +575 -566
  14. package/src/__tests__/helpers/conversation-history.smoke.test.ts +271 -271
  15. package/src/__tests__/helpers/conversation-history.ts +119 -119
  16. package/src/__tests__/helpers/deferred.smoke.test.ts +103 -103
  17. package/src/__tests__/helpers/deferred.ts +69 -69
  18. package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +155 -155
  19. package/src/__tests__/helpers/in-memory-storage.ts +88 -88
  20. package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +68 -68
  21. package/src/__tests__/helpers/mock-bun-proxy.ts +189 -189
  22. package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +273 -273
  23. package/src/__tests__/helpers/plugin-fetch-harness.ts +288 -288
  24. package/src/__tests__/helpers/sse.smoke.test.ts +236 -236
  25. package/src/__tests__/helpers/sse.ts +209 -209
  26. package/src/__tests__/index.parallel.test.ts +605 -595
  27. package/src/__tests__/sanitization-regex.test.ts +112 -112
  28. package/src/__tests__/state-bounds.test.ts +90 -90
  29. package/src/account-identity.test.ts +197 -192
  30. package/src/account-identity.ts +69 -67
  31. package/src/account-state.test.ts +86 -86
  32. package/src/account-state.ts +25 -25
  33. package/src/accounts/matching.test.ts +335 -0
  34. package/src/accounts/matching.ts +167 -0
  35. package/src/accounts/persistence.test.ts +345 -0
  36. package/src/accounts/persistence.ts +432 -0
  37. package/src/accounts/repair.test.ts +276 -0
  38. package/src/accounts/repair.ts +407 -0
  39. package/src/accounts.dedup.test.ts +621 -621
  40. package/src/accounts.test.ts +933 -929
  41. package/src/accounts.ts +633 -989
  42. package/src/backoff.test.ts +345 -345
  43. package/src/backoff.ts +219 -219
  44. package/src/betas.ts +124 -124
  45. package/src/bun-fetch.test.ts +345 -342
  46. package/src/bun-fetch.ts +424 -424
  47. package/src/bun-proxy.test.ts +25 -25
  48. package/src/bun-proxy.ts +209 -209
  49. package/src/cc-credentials.ts +111 -111
  50. package/src/circuit-breaker.test.ts +184 -184
  51. package/src/circuit-breaker.ts +169 -169
  52. package/src/cli/commands/auth.ts +963 -0
  53. package/src/cli/commands/config.ts +547 -0
  54. package/src/cli/formatting.test.ts +406 -0
  55. package/src/cli/formatting.ts +219 -0
  56. package/src/cli.ts +255 -2022
  57. package/src/commands/handlers/betas.ts +100 -0
  58. package/src/commands/handlers/config.ts +99 -0
  59. package/src/commands/handlers/files.ts +375 -0
  60. package/src/commands/oauth-flow.ts +181 -166
  61. package/src/commands/prompts.ts +61 -61
  62. package/src/commands/router.test.ts +421 -0
  63. package/src/commands/router.ts +143 -635
  64. package/src/config.test.ts +482 -482
  65. package/src/config.ts +412 -404
  66. package/src/constants.ts +48 -48
  67. package/src/drift/cch-constants.ts +95 -95
  68. package/src/env.ts +111 -105
  69. package/src/headers/billing.ts +33 -33
  70. package/src/headers/builder.ts +130 -130
  71. package/src/headers/cch.ts +75 -75
  72. package/src/headers/stainless.ts +25 -25
  73. package/src/headers/user-agent.ts +23 -23
  74. package/src/index.ts +436 -828
  75. package/src/models.ts +27 -27
  76. package/src/oauth.test.ts +102 -102
  77. package/src/oauth.ts +178 -178
  78. package/src/parent-pid-watcher.test.ts +148 -148
  79. package/src/parent-pid-watcher.ts +69 -69
  80. package/src/plugin-helpers.ts +82 -82
  81. package/src/refresh-helpers.ts +145 -139
  82. package/src/refresh-lock.test.ts +94 -94
  83. package/src/refresh-lock.ts +93 -93
  84. package/src/request/body.history.test.ts +579 -571
  85. package/src/request/body.ts +255 -255
  86. package/src/request/metadata.ts +65 -65
  87. package/src/request/retry.test.ts +156 -156
  88. package/src/request/retry.ts +67 -67
  89. package/src/request/url.ts +21 -21
  90. package/src/request-orchestration-helpers.ts +648 -0
  91. package/src/response/index.ts +5 -5
  92. package/src/response/mcp.ts +58 -58
  93. package/src/response/streaming.test.ts +313 -311
  94. package/src/response/streaming.ts +412 -410
  95. package/src/rotation.test.ts +304 -301
  96. package/src/rotation.ts +205 -205
  97. package/src/storage.test.ts +547 -547
  98. package/src/storage.ts +315 -291
  99. package/src/system-prompt/builder.ts +38 -38
  100. package/src/system-prompt/index.ts +5 -5
  101. package/src/system-prompt/normalize.ts +60 -60
  102. package/src/system-prompt/sanitize.ts +30 -30
  103. package/src/thinking.ts +21 -20
  104. package/src/token-refresh.test.ts +265 -265
  105. package/src/token-refresh.ts +219 -214
  106. package/src/types.ts +30 -30
  107. package/dist/bun-proxy.mjs +0 -291
@@ -3,35 +3,35 @@ import { describe, expect, it, vi } from "vitest";
3
3
  import { createProxyRequestHandler } from "./bun-proxy.js";
4
4
 
5
5
  function makeProxyRequest(headers?: HeadersInit): Request {
6
- const requestHeaders = new Headers(headers);
7
- requestHeaders.set("x-proxy-url", "https://api.anthropic.com/v1/messages");
8
- requestHeaders.set("content-type", "application/json");
6
+ const requestHeaders = new Headers(headers);
7
+ requestHeaders.set("x-proxy-url", "https://api.anthropic.com/v1/messages");
8
+ requestHeaders.set("content-type", "application/json");
9
9
 
10
- return new Request("http://127.0.0.1/proxy", {
11
- method: "POST",
12
- headers: requestHeaders,
13
- body: JSON.stringify({ ok: true }),
14
- });
10
+ return new Request("http://127.0.0.1/proxy", {
11
+ method: "POST",
12
+ headers: requestHeaders,
13
+ body: JSON.stringify({ ok: true }),
14
+ });
15
15
  }
16
16
 
17
17
  describe("createProxyRequestHandler", () => {
18
- it("forwards retry requests with keepalive disabled to the upstream fetch", async () => {
19
- const upstreamFetch = vi.fn(async (_input, init?: RequestInit) => {
20
- expect(init?.keepalive).toBe(false);
21
- const forwardedHeaders = init?.headers instanceof Headers ? init.headers : new Headers(init?.headers);
22
- expect(forwardedHeaders.get("connection")).toBe("close");
23
- expect(forwardedHeaders.get("x-proxy-disable-keepalive")).toBeNull();
24
- return new Response("ok", { status: 200 });
25
- });
26
- const handler = createProxyRequestHandler({
27
- fetchImpl: upstreamFetch as typeof fetch,
28
- allowHosts: ["api.anthropic.com"],
29
- requestTimeoutMs: 50,
30
- });
18
+ it("forwards retry requests with keepalive disabled to the upstream fetch", async () => {
19
+ const upstreamFetch = vi.fn(async (_input, init?: RequestInit) => {
20
+ expect(init?.keepalive).toBe(false);
21
+ const forwardedHeaders = init?.headers instanceof Headers ? init.headers : new Headers(init?.headers);
22
+ expect(forwardedHeaders.get("connection")).toBe("close");
23
+ expect(forwardedHeaders.get("x-proxy-disable-keepalive")).toBeNull();
24
+ return new Response("ok", { status: 200 });
25
+ });
26
+ const handler = createProxyRequestHandler({
27
+ fetchImpl: upstreamFetch as typeof fetch,
28
+ allowHosts: ["api.anthropic.com"],
29
+ requestTimeoutMs: 50,
30
+ });
31
31
 
32
- const response = await handler(makeProxyRequest({ "x-proxy-disable-keepalive": "true" }));
32
+ const response = await handler(makeProxyRequest({ "x-proxy-disable-keepalive": "true" }));
33
33
 
34
- await expect(response.text()).resolves.toBe("ok");
35
- expect(upstreamFetch).toHaveBeenCalledTimes(1);
36
- });
34
+ await expect(response.text()).resolves.toBe("ok");
35
+ expect(upstreamFetch).toHaveBeenCalledTimes(1);
36
+ });
37
37
  });
package/src/bun-proxy.ts CHANGED
@@ -12,300 +12,300 @@ const PROXY_DISABLE_KEEPALIVE_HEADER = "x-proxy-disable-keepalive";
12
12
  const DEBUG_ENABLED = process.env.OPENCODE_ANTHROPIC_DEBUG === "1";
13
13
 
14
14
  interface ProxyRequestHandlerOptions {
15
- fetchImpl: typeof fetch;
16
- allowHosts?: string[];
17
- requestTimeoutMs?: number;
15
+ fetchImpl: typeof fetch;
16
+ allowHosts?: string[];
17
+ requestTimeoutMs?: number;
18
18
  }
19
19
 
20
20
  interface ProxyProcessRuntimeOptions {
21
- argv?: string[];
22
- exit?: (code?: number) => void;
23
- parentWatcherFactory?: ParentWatcherFactory;
21
+ argv?: string[];
22
+ exit?: (code?: number) => void;
23
+ parentWatcherFactory?: ParentWatcherFactory;
24
24
  }
25
25
 
26
26
  interface ParentWatcher {
27
- start(): void;
28
- stop(): void;
27
+ start(): void;
28
+ stop(): void;
29
29
  }
30
30
 
31
31
  interface ParentWatcherFactoryOptions {
32
- parentPid: number;
33
- onParentExit: (exitCode?: number) => void;
34
- pollIntervalMs?: number;
35
- exitCode?: number;
32
+ parentPid: number;
33
+ onParentExit: (exitCode?: number) => void;
34
+ pollIntervalMs?: number;
35
+ exitCode?: number;
36
36
  }
37
37
 
38
38
  type ParentWatcherFactory = (options: ParentWatcherFactoryOptions) => ParentWatcher;
39
39
 
40
40
  type RequestInitWithDuplex = RequestInit & {
41
- duplex?: "half";
41
+ duplex?: "half";
42
42
  };
43
43
 
44
44
  interface AbortContext {
45
- timeoutSignal: AbortSignal;
46
- cancelTimeout(): void;
45
+ timeoutSignal: AbortSignal;
46
+ cancelTimeout(): void;
47
47
  }
48
48
 
49
49
  function isMainModule(argv: string[] = process.argv): boolean {
50
- return Boolean(argv[1]) && resolve(argv[1]) === fileURLToPath(import.meta.url);
50
+ return Boolean(argv[1]) && resolve(argv[1]) === fileURLToPath(import.meta.url);
51
51
  }
52
52
 
53
53
  function parseInteger(value: string | undefined): number | null {
54
- const parsed = Number.parseInt(value ?? "", 10);
55
- return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
54
+ const parsed = Number.parseInt(value ?? "", 10);
55
+ return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
56
56
  }
57
57
 
58
58
  function parseParentPid(argv: string[]): number | null {
59
- const inlineValue = argv
60
- .map((argument) => argument.match(/^--parent-pid=(\d+)$/)?.[1] ?? null)
61
- .find((value) => value !== null);
59
+ const inlineValue = argv
60
+ .map((argument) => argument.match(/^--parent-pid=(\d+)$/)?.[1] ?? null)
61
+ .find((value) => value !== null);
62
62
 
63
- if (inlineValue) {
64
- return parseInteger(inlineValue);
65
- }
63
+ if (inlineValue) {
64
+ return parseInteger(inlineValue);
65
+ }
66
66
 
67
- const flagIndex = argv.indexOf("--parent-pid");
68
- return flagIndex >= 0 ? parseInteger(argv[flagIndex + 1]) : null;
67
+ const flagIndex = argv.indexOf("--parent-pid");
68
+ return flagIndex >= 0 ? parseInteger(argv[flagIndex + 1]) : null;
69
69
  }
70
70
 
71
71
  function createNoopWatcher(): ParentWatcher {
72
- return {
73
- start(): void {},
74
- stop(): void {},
75
- };
72
+ return {
73
+ start(): void {},
74
+ stop(): void {},
75
+ };
76
76
  }
77
77
 
78
78
  function createDefaultParentWatcherFactory(): ParentWatcherFactory {
79
- return ({ parentPid, onParentExit, pollIntervalMs, exitCode }): ParentWatcher =>
80
- new ParentPidWatcher({
81
- parentPid,
82
- pollIntervalMs,
83
- onParentGone: () => {
84
- onParentExit(exitCode);
85
- },
86
- });
79
+ return ({ parentPid, onParentExit, pollIntervalMs, exitCode }): ParentWatcher =>
80
+ new ParentPidWatcher({
81
+ parentPid,
82
+ pollIntervalMs,
83
+ onParentGone: () => {
84
+ onParentExit(exitCode);
85
+ },
86
+ });
87
87
  }
88
88
 
89
89
  function sanitizeForwardHeaders(source: Headers, forceFreshConnection = false): Headers {
90
- const headers = new Headers(source);
91
- [PROXY_DISABLE_KEEPALIVE_HEADER, "x-proxy-url", "host", "connection", "content-length"].forEach((headerName) => {
92
- headers.delete(headerName);
93
- });
90
+ const headers = new Headers(source);
91
+ [PROXY_DISABLE_KEEPALIVE_HEADER, "x-proxy-url", "host", "connection", "content-length"].forEach((headerName) => {
92
+ headers.delete(headerName);
93
+ });
94
94
 
95
- if (forceFreshConnection) {
96
- headers.set("connection", "close");
97
- }
95
+ if (forceFreshConnection) {
96
+ headers.set("connection", "close");
97
+ }
98
98
 
99
- return headers;
99
+ return headers;
100
100
  }
101
101
 
102
102
  function copyResponseHeaders(source: Headers): Headers {
103
- const headers = new Headers(source);
104
- ["transfer-encoding", "content-encoding"].forEach((headerName) => {
105
- headers.delete(headerName);
106
- });
107
- return headers;
103
+ const headers = new Headers(source);
104
+ ["transfer-encoding", "content-encoding"].forEach((headerName) => {
105
+ headers.delete(headerName);
106
+ });
107
+ return headers;
108
108
  }
109
109
 
110
110
  function resolveTargetUrl(req: Request, allowedHosts: ReadonlySet<string>): URL | Response {
111
- const targetUrl = req.headers.get("x-proxy-url");
112
-
113
- if (!targetUrl) {
114
- return new Response("Missing x-proxy-url", { status: 400 });
115
- }
111
+ const targetUrl = req.headers.get("x-proxy-url");
116
112
 
117
- try {
118
- const parsedUrl = new URL(targetUrl);
119
- if (allowedHosts.size > 0 && !allowedHosts.has(parsedUrl.hostname)) {
120
- return new Response(`Host not allowed: ${parsedUrl.hostname}`, { status: 403 });
113
+ if (!targetUrl) {
114
+ return new Response("Missing x-proxy-url", { status: 400 });
121
115
  }
122
116
 
123
- return parsedUrl;
124
- } catch {
125
- return new Response("Invalid x-proxy-url", { status: 400 });
126
- }
117
+ try {
118
+ const parsedUrl = new URL(targetUrl);
119
+ if (allowedHosts.size > 0 && !allowedHosts.has(parsedUrl.hostname)) {
120
+ return new Response(`Host not allowed: ${parsedUrl.hostname}`, { status: 403 });
121
+ }
122
+
123
+ return parsedUrl;
124
+ } catch {
125
+ return new Response("Invalid x-proxy-url", { status: 400 });
126
+ }
127
127
  }
128
128
 
129
129
  function createAbortContext(requestTimeoutMs: number): AbortContext {
130
- const timeoutController = new AbortController();
131
- const timer = setTimeout(() => {
132
- timeoutController.abort(new DOMException("Upstream request timed out", "TimeoutError"));
133
- }, requestTimeoutMs);
134
-
135
- timer.unref?.();
136
-
137
- return {
138
- timeoutSignal: timeoutController.signal,
139
- cancelTimeout(): void {
140
- clearTimeout(timer);
141
- },
142
- };
130
+ const timeoutController = new AbortController();
131
+ const timer = setTimeout(() => {
132
+ timeoutController.abort(new DOMException("Upstream request timed out", "TimeoutError"));
133
+ }, requestTimeoutMs);
134
+
135
+ timer.unref?.();
136
+
137
+ return {
138
+ timeoutSignal: timeoutController.signal,
139
+ cancelTimeout(): void {
140
+ clearTimeout(timer);
141
+ },
142
+ };
143
143
  }
144
144
 
145
145
  function isAbortError(error: unknown): boolean {
146
- return error instanceof DOMException
147
- ? error.name === "AbortError" || error.name === "TimeoutError"
148
- : error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
146
+ return error instanceof DOMException
147
+ ? error.name === "AbortError" || error.name === "TimeoutError"
148
+ : error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
149
149
  }
150
150
 
151
151
  function isTimeoutAbort(signal: AbortSignal): boolean {
152
- const reason = signal.reason;
153
- return reason instanceof DOMException
154
- ? reason.name === "TimeoutError"
155
- : reason instanceof Error && reason.name === "TimeoutError";
152
+ const reason = signal.reason;
153
+ return reason instanceof DOMException
154
+ ? reason.name === "TimeoutError"
155
+ : reason instanceof Error && reason.name === "TimeoutError";
156
156
  }
157
157
 
158
158
  function createAbortResponse(req: Request, timeoutSignal: AbortSignal): Response {
159
- return req.signal.aborted
160
- ? new Response("Client disconnected", { status: 499 })
161
- : isTimeoutAbort(timeoutSignal)
162
- ? new Response("Upstream request timed out", { status: 504 })
163
- : new Response("Upstream request aborted", { status: 499 });
159
+ return req.signal.aborted
160
+ ? new Response("Client disconnected", { status: 499 })
161
+ : isTimeoutAbort(timeoutSignal)
162
+ ? new Response("Upstream request timed out", { status: 504 })
163
+ : new Response("Upstream request aborted", { status: 499 });
164
164
  }
165
165
 
166
166
  async function createUpstreamInit(req: Request, signal: AbortSignal): Promise<RequestInitWithDuplex> {
167
- const method = req.method || "GET";
168
- const hasBody = method !== "GET" && method !== "HEAD";
169
- const bodyText = hasBody ? await req.text() : "";
170
- const forceFreshConnection = req.headers.get(PROXY_DISABLE_KEEPALIVE_HEADER) === "true";
171
-
172
- return {
173
- method,
174
- headers: sanitizeForwardHeaders(req.headers, forceFreshConnection),
175
- signal,
176
- ...(forceFreshConnection ? { keepalive: false } : {}),
177
- ...(hasBody && bodyText.length > 0 ? { body: bodyText } : {}),
178
- };
167
+ const method = req.method || "GET";
168
+ const hasBody = method !== "GET" && method !== "HEAD";
169
+ const bodyText = hasBody ? await req.text() : "";
170
+ const forceFreshConnection = req.headers.get(PROXY_DISABLE_KEEPALIVE_HEADER) === "true";
171
+
172
+ return {
173
+ method,
174
+ headers: sanitizeForwardHeaders(req.headers, forceFreshConnection),
175
+ signal,
176
+ ...(forceFreshConnection ? { keepalive: false } : {}),
177
+ ...(hasBody && bodyText.length > 0 ? { body: bodyText } : {}),
178
+ };
179
179
  }
180
180
 
181
181
  function logRequest(targetUrl: URL, req: Request): void {
182
- if (!DEBUG_ENABLED) {
183
- return;
184
- }
185
-
186
- const logHeaders = Object.fromEntries(
187
- [...sanitizeForwardHeaders(req.headers).entries()].map(([key, value]) => [
188
- key,
189
- key === "authorization" ? "Bearer ***" : value,
190
- ]),
191
- );
192
-
193
- console.error("\n[bun-proxy] === FORWARDED REQUEST ===");
194
- console.error(`[bun-proxy] ${req.method} ${targetUrl.toString()}`);
195
- console.error(`[bun-proxy] Headers: ${JSON.stringify(logHeaders, null, 2)}`);
196
- console.error("[bun-proxy] =========================\n");
182
+ if (!DEBUG_ENABLED) {
183
+ return;
184
+ }
185
+
186
+ const logHeaders = Object.fromEntries(
187
+ [...sanitizeForwardHeaders(req.headers).entries()].map(([key, value]) => [
188
+ key,
189
+ key === "authorization" ? "Bearer ***" : value,
190
+ ]),
191
+ );
192
+
193
+ console.error("\n[bun-proxy] === FORWARDED REQUEST ===");
194
+ console.error(`[bun-proxy] ${req.method} ${targetUrl.toString()}`);
195
+ console.error(`[bun-proxy] Headers: ${JSON.stringify(logHeaders, null, 2)}`);
196
+ console.error("[bun-proxy] =========================\n");
197
197
  }
198
198
 
199
199
  export function createProxyRequestHandler(options: ProxyRequestHandlerOptions): (req: Request) => Promise<Response> {
200
- const allowedHosts = new Set(options.allowHosts ?? DEFAULT_ALLOWED_HOSTS);
201
- const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
202
-
203
- return async function handleProxyRequest(req: Request): Promise<Response> {
204
- if (new URL(req.url).pathname === HEALTH_PATH) {
205
- return new Response("ok");
206
- }
200
+ const allowedHosts = new Set(options.allowHosts ?? DEFAULT_ALLOWED_HOSTS);
201
+ const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
202
+
203
+ return async function handleProxyRequest(req: Request): Promise<Response> {
204
+ if (new URL(req.url).pathname === HEALTH_PATH) {
205
+ return new Response("ok");
206
+ }
207
+
208
+ const targetUrl = resolveTargetUrl(req, allowedHosts);
209
+ if (targetUrl instanceof Response) {
210
+ return targetUrl;
211
+ }
212
+
213
+ const abortContext = createAbortContext(requestTimeoutMs);
214
+ const upstreamSignal = AbortSignal.any([req.signal, abortContext.timeoutSignal]);
215
+ const upstreamInit = await createUpstreamInit(req, upstreamSignal);
216
+ logRequest(targetUrl, req);
217
+
218
+ try {
219
+ const upstreamResponse = await options.fetchImpl(targetUrl.toString(), upstreamInit);
220
+ return new Response(upstreamResponse.body, {
221
+ status: upstreamResponse.status,
222
+ statusText: upstreamResponse.statusText,
223
+ headers: copyResponseHeaders(upstreamResponse.headers),
224
+ });
225
+ } catch (error) {
226
+ if (upstreamSignal.aborted && isAbortError(error)) {
227
+ return createAbortResponse(req, abortContext.timeoutSignal);
228
+ }
229
+
230
+ const message = error instanceof Error ? error.message : String(error);
231
+ return new Response(message, { status: 502 });
232
+ } finally {
233
+ abortContext.cancelTimeout();
234
+ }
235
+ };
236
+ }
207
237
 
208
- const targetUrl = resolveTargetUrl(req, allowedHosts);
209
- if (targetUrl instanceof Response) {
210
- return targetUrl;
238
+ export function createProxyProcessRuntime(options: ProxyProcessRuntimeOptions = {}): ParentWatcher {
239
+ const argv = options.argv ?? process.argv;
240
+ const parentPid = parseParentPid(argv);
241
+ if (!parentPid) {
242
+ return createNoopWatcher();
211
243
  }
212
244
 
213
- const abortContext = createAbortContext(requestTimeoutMs);
214
- const upstreamSignal = AbortSignal.any([req.signal, abortContext.timeoutSignal]);
215
- const upstreamInit = await createUpstreamInit(req, upstreamSignal);
216
- logRequest(targetUrl, req);
245
+ const exit = options.exit ?? process.exit;
246
+ const parentWatcherFactory = options.parentWatcherFactory ?? createDefaultParentWatcherFactory();
217
247
 
218
- try {
219
- const upstreamResponse = await options.fetchImpl(targetUrl.toString(), upstreamInit);
220
- return new Response(upstreamResponse.body, {
221
- status: upstreamResponse.status,
222
- statusText: upstreamResponse.statusText,
223
- headers: copyResponseHeaders(upstreamResponse.headers),
224
- });
225
- } catch (error) {
226
- if (upstreamSignal.aborted && isAbortError(error)) {
227
- return createAbortResponse(req, abortContext.timeoutSignal);
228
- }
229
-
230
- const message = error instanceof Error ? error.message : String(error);
231
- return new Response(message, { status: 502 });
232
- } finally {
233
- abortContext.cancelTimeout();
234
- }
235
- };
236
- }
237
-
238
- export function createProxyProcessRuntime(options: ProxyProcessRuntimeOptions = {}): ParentWatcher {
239
- const argv = options.argv ?? process.argv;
240
- const parentPid = parseParentPid(argv);
241
- if (!parentPid) {
242
- return createNoopWatcher();
243
- }
244
-
245
- const exit = options.exit ?? process.exit;
246
- const parentWatcherFactory = options.parentWatcherFactory ?? createDefaultParentWatcherFactory();
247
-
248
- return parentWatcherFactory({
249
- parentPid,
250
- pollIntervalMs: DEFAULT_PARENT_POLL_INTERVAL_MS,
251
- exitCode: DEFAULT_PARENT_EXIT_CODE,
252
- onParentExit: (exitCode) => {
253
- exit(exitCode ?? DEFAULT_PARENT_EXIT_CODE);
254
- },
255
- });
248
+ return parentWatcherFactory({
249
+ parentPid,
250
+ pollIntervalMs: DEFAULT_PARENT_POLL_INTERVAL_MS,
251
+ exitCode: DEFAULT_PARENT_EXIT_CODE,
252
+ onParentExit: (exitCode) => {
253
+ exit(exitCode ?? DEFAULT_PARENT_EXIT_CODE);
254
+ },
255
+ });
256
256
  }
257
257
 
258
258
  function assertBunRuntime(): typeof Bun {
259
- if (typeof Bun === "undefined") {
260
- throw new Error("bun-proxy.ts must be executed with Bun.");
261
- }
259
+ if (typeof Bun === "undefined") {
260
+ throw new Error("bun-proxy.ts must be executed with Bun.");
261
+ }
262
262
 
263
- return Bun;
263
+ return Bun;
264
264
  }
265
265
 
266
266
  async function runProxyProcess(): Promise<void> {
267
- const bun = assertBunRuntime();
268
- const watcher = createProxyProcessRuntime();
269
- const server = bun.serve({
270
- port: 0,
271
- fetch: createProxyRequestHandler({
272
- fetchImpl: fetch,
273
- allowHosts: DEFAULT_ALLOWED_HOSTS,
274
- requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
275
- }),
276
- });
277
-
278
- const lifecycle = {
279
- closed: false,
280
- };
281
-
282
- const shutdown = (exitCode = 0): void => {
283
- if (lifecycle.closed) {
284
- return;
285
- }
267
+ const bun = assertBunRuntime();
268
+ const watcher = createProxyProcessRuntime();
269
+ const server = bun.serve({
270
+ port: 0,
271
+ fetch: createProxyRequestHandler({
272
+ fetchImpl: fetch,
273
+ allowHosts: DEFAULT_ALLOWED_HOSTS,
274
+ requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS,
275
+ }),
276
+ });
286
277
 
287
- lifecycle.closed = true;
288
- watcher.stop();
289
- server.stop(true);
290
- process.exit(exitCode);
291
- };
278
+ const lifecycle = {
279
+ closed: false,
280
+ };
292
281
 
293
- process.on("SIGTERM", () => {
294
- shutdown(0);
295
- });
282
+ const shutdown = (exitCode = 0): void => {
283
+ if (lifecycle.closed) {
284
+ return;
285
+ }
296
286
 
297
- process.on("SIGINT", () => {
298
- shutdown(0);
299
- });
287
+ lifecycle.closed = true;
288
+ watcher.stop();
289
+ server.stop(true);
290
+ process.exit(exitCode);
291
+ };
292
+
293
+ process.on("SIGTERM", () => {
294
+ shutdown(0);
295
+ });
300
296
 
301
- watcher.start();
302
- process.stdout.write(`BUN_PROXY_PORT=${server.port}\n`);
297
+ process.on("SIGINT", () => {
298
+ shutdown(0);
299
+ });
300
+
301
+ watcher.start();
302
+ process.stdout.write(`BUN_PROXY_PORT=${server.port}\n`);
303
303
  }
304
304
 
305
305
  if (isMainModule()) {
306
- void runProxyProcess().catch((error) => {
307
- const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
308
- process.stderr.write(`${message}\n`);
309
- process.exit(1);
310
- });
306
+ void runProxyProcess().catch((error) => {
307
+ const message = error instanceof Error ? (error.stack ?? error.message) : String(error);
308
+ process.stderr.write(`${message}\n`);
309
+ process.exit(1);
310
+ });
311
311
  }