@vacbo/opencode-anthropic-fix 0.0.45 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/bun-proxy.mjs +282 -55
- package/dist/opencode-anthropic-auth-cli.mjs +194 -55
- package/dist/opencode-anthropic-auth-plugin.js +1801 -613
- package/package.json +4 -4
- package/src/__tests__/billing-edge-cases.test.ts +84 -0
- package/src/__tests__/bun-proxy.parallel.test.ts +460 -0
- package/src/__tests__/debug-gating.test.ts +76 -0
- package/src/__tests__/decomposition-smoke.test.ts +92 -0
- package/src/__tests__/fingerprint-regression.test.ts +1 -1
- package/src/__tests__/helpers/conversation-history.smoke.test.ts +338 -0
- package/src/__tests__/helpers/conversation-history.ts +376 -0
- package/src/__tests__/helpers/deferred.smoke.test.ts +161 -0
- package/src/__tests__/helpers/deferred.ts +122 -0
- package/src/__tests__/helpers/in-memory-storage.smoke.test.ts +166 -0
- package/src/__tests__/helpers/in-memory-storage.ts +152 -0
- package/src/__tests__/helpers/mock-bun-proxy.smoke.test.ts +92 -0
- package/src/__tests__/helpers/mock-bun-proxy.ts +229 -0
- package/src/__tests__/helpers/plugin-fetch-harness.smoke.test.ts +337 -0
- package/src/__tests__/helpers/plugin-fetch-harness.ts +401 -0
- package/src/__tests__/helpers/sse.smoke.test.ts +243 -0
- package/src/__tests__/helpers/sse.ts +288 -0
- package/src/__tests__/index.parallel.test.ts +711 -0
- package/src/__tests__/sanitization-regex.test.ts +65 -0
- package/src/__tests__/state-bounds.test.ts +110 -0
- package/src/account-identity.test.ts +213 -0
- package/src/account-identity.ts +108 -0
- package/src/accounts.dedup.test.ts +696 -0
- package/src/accounts.test.ts +2 -1
- package/src/accounts.ts +485 -191
- package/src/bun-fetch.test.ts +379 -0
- package/src/bun-fetch.ts +447 -191
- package/src/bun-proxy.ts +289 -57
- package/src/circuit-breaker.test.ts +274 -0
- package/src/circuit-breaker.ts +235 -0
- package/src/cli.test.ts +1 -0
- package/src/cli.ts +37 -18
- package/src/commands/router.ts +25 -5
- package/src/env.ts +1 -0
- package/src/headers/billing.ts +11 -5
- package/src/index.ts +224 -247
- package/src/oauth.ts +7 -1
- package/src/parent-pid-watcher.test.ts +219 -0
- package/src/parent-pid-watcher.ts +99 -0
- package/src/plugin-helpers.ts +112 -0
- package/src/refresh-helpers.ts +169 -0
- package/src/refresh-lock.test.ts +36 -9
- package/src/refresh-lock.ts +2 -2
- package/src/request/body.history.test.ts +398 -0
- package/src/request/body.ts +200 -13
- package/src/request/metadata.ts +6 -2
- package/src/response/index.ts +1 -1
- package/src/response/mcp.ts +60 -31
- package/src/response/streaming.test.ts +382 -0
- package/src/response/streaming.ts +403 -76
- package/src/storage.test.ts +127 -104
- package/src/storage.ts +152 -62
- package/src/system-prompt/builder.ts +33 -3
- package/src/system-prompt/sanitize.ts +12 -2
- package/src/token-refresh.test.ts +84 -1
- package/src/token-refresh.ts +14 -8
package/README.md
CHANGED
|
@@ -33,6 +33,8 @@ opencode
|
|
|
33
33
|
|
|
34
34
|
That's it. OpenCode will now use your Claude subscription directly. All model costs show as $0.00.
|
|
35
35
|
|
|
36
|
+
Manual parallel QA: `bash scripts/qa-parallel.sh`
|
|
37
|
+
|
|
36
38
|
## What This Fork Adds
|
|
37
39
|
|
|
38
40
|
The [original plugin](https://github.com/anomalyco/opencode-anthropic-auth) provided basic OAuth support. This fork adds:
|
|
@@ -555,6 +557,17 @@ Configuration is stored at `~/.config/opencode/anthropic-auth.json`. All setting
|
|
|
555
557
|
- In OAuth mode, the plugin always includes `oauth-2025-04-20` in `anthropic-beta`.
|
|
556
558
|
- This applies to all models, including Haiku.
|
|
557
559
|
|
|
560
|
+
## Per-instance Proxy Lifecycle
|
|
561
|
+
|
|
562
|
+
The plugin uses a dedicated HTTP proxy per OpenCode instance to handle TLS fingerprint mimicry. This architecture provides isolation and graceful degradation:
|
|
563
|
+
|
|
564
|
+
- **Each OpenCode instance owns its own proxy** — when you open multiple OpenCode tabs or windows, each gets an independent proxy process
|
|
565
|
+
- **Proxy dies with parent process** — the proxy monitors its parent PID and exits automatically if the parent dies, preventing orphaned processes
|
|
566
|
+
- **Ephemeral port allocation** — each proxy binds to an available ephemeral port (port 0) to avoid conflicts between instances
|
|
567
|
+
- **Graceful fallback to native fetch** — if Bun is unavailable or the proxy fails to start, requests fall back to native Node.js fetch without TLS mimicry
|
|
568
|
+
|
|
569
|
+
This design ensures that proxy failures are isolated to a single OpenCode instance and never affect other running instances.
|
|
570
|
+
|
|
558
571
|
## How It Works
|
|
559
572
|
|
|
560
573
|
When you make a request through OpenCode:
|
|
@@ -666,6 +679,12 @@ Make sure `~/.local/bin` is on your PATH:
|
|
|
666
679
|
export PATH="$HOME/.local/bin:$PATH"
|
|
667
680
|
```
|
|
668
681
|
|
|
682
|
+
## Known Limitations
|
|
683
|
+
|
|
684
|
+
- **Windows native fetch fallback** — On Windows, the Bun-based TLS mimicry proxy is not available. Requests fall back to native Node.js fetch without fingerprint mimicry. This is a platform limitation; the plugin still functions but with reduced request signature parity.
|
|
685
|
+
|
|
686
|
+
- **Claude Code refresh blocking** — When reusing Claude Code credentials, token refresh can block for up to 60 seconds while invoking the `claude` CLI. This is a known latent issue in the credential reuse flow and is outside the scope of this plugin's control.
|
|
687
|
+
|
|
669
688
|
## License
|
|
670
689
|
|
|
671
690
|
Same as upstream. See [anomalyco/opencode-anthropic-auth](https://github.com/anomalyco/opencode-anthropic-auth).
|
package/dist/bun-proxy.mjs
CHANGED
|
@@ -1,64 +1,291 @@
|
|
|
1
1
|
// src/bun-proxy.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
// src/parent-pid-watcher.ts
|
|
6
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
7
|
+
function assertValidParentPid(parentPid) {
|
|
8
|
+
if (!Number.isInteger(parentPid) || parentPid <= 0) {
|
|
9
|
+
throw new Error("Parent PID must be a positive integer.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function assertValidPollInterval(pollIntervalMs) {
|
|
13
|
+
if (!Number.isFinite(pollIntervalMs) || pollIntervalMs <= 0) {
|
|
14
|
+
throw new Error("Poll interval must be a positive number.");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function isParentAlive(parentPid) {
|
|
18
|
+
try {
|
|
19
|
+
process.kill(parentPid, 0);
|
|
20
|
+
return true;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
const code = error.code;
|
|
23
|
+
if (code === "ESRCH") {
|
|
24
|
+
return false;
|
|
8
25
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
return new Response("Missing x-proxy-url", { status: 400 });
|
|
26
|
+
if (code === "EPERM") {
|
|
27
|
+
return true;
|
|
12
28
|
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
var ParentPidWatcher = class {
|
|
33
|
+
parentPid;
|
|
34
|
+
pollIntervalMs;
|
|
35
|
+
onParentGone;
|
|
36
|
+
interval = null;
|
|
37
|
+
shouldMonitorPpidDrift = false;
|
|
38
|
+
constructor(options) {
|
|
39
|
+
this.parentPid = options.parentPid;
|
|
40
|
+
this.pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
41
|
+
this.onParentGone = options.onParentGone;
|
|
42
|
+
}
|
|
43
|
+
start() {
|
|
44
|
+
if (this.interval) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
assertValidParentPid(this.parentPid);
|
|
48
|
+
assertValidPollInterval(this.pollIntervalMs);
|
|
49
|
+
this.shouldMonitorPpidDrift = process.ppid === this.parentPid;
|
|
50
|
+
this.interval = setInterval(() => {
|
|
51
|
+
if (this.shouldMonitorPpidDrift && process.ppid !== this.parentPid) {
|
|
52
|
+
this.handleParentGone();
|
|
53
|
+
return;
|
|
35
54
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
55
|
+
if (!isParentAlive(this.parentPid)) {
|
|
56
|
+
this.handleParentGone();
|
|
57
|
+
}
|
|
58
|
+
}, this.pollIntervalMs);
|
|
59
|
+
}
|
|
60
|
+
stop() {
|
|
61
|
+
if (!this.interval) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
clearInterval(this.interval);
|
|
65
|
+
this.interval = null;
|
|
66
|
+
this.shouldMonitorPpidDrift = false;
|
|
67
|
+
}
|
|
68
|
+
handleParentGone() {
|
|
69
|
+
this.stop();
|
|
70
|
+
this.onParentGone();
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/bun-proxy.ts
|
|
75
|
+
var DEFAULT_ALLOWED_HOSTS = ["api.anthropic.com", "platform.claude.com"];
|
|
76
|
+
var DEFAULT_REQUEST_TIMEOUT_MS = 6e5;
|
|
77
|
+
var DEFAULT_PARENT_EXIT_CODE = 1;
|
|
78
|
+
var DEFAULT_PARENT_POLL_INTERVAL_MS = 5e3;
|
|
79
|
+
var HEALTH_PATH = "/__health";
|
|
80
|
+
var DEBUG_ENABLED = process.env.OPENCODE_ANTHROPIC_DEBUG === "1";
|
|
81
|
+
function isMainModule(argv = process.argv) {
|
|
82
|
+
return Boolean(argv[1]) && resolve(argv[1]) === fileURLToPath(import.meta.url);
|
|
83
|
+
}
|
|
84
|
+
function parseInteger(value) {
|
|
85
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
86
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : null;
|
|
87
|
+
}
|
|
88
|
+
function parseParentPid(argv) {
|
|
89
|
+
const inlineValue = argv.map((argument) => argument.match(/^--parent-pid=(\d+)$/)?.[1] ?? null).find((value) => value !== null);
|
|
90
|
+
if (inlineValue) {
|
|
91
|
+
return parseInteger(inlineValue);
|
|
92
|
+
}
|
|
93
|
+
const flagIndex = argv.indexOf("--parent-pid");
|
|
94
|
+
return flagIndex >= 0 ? parseInteger(argv[flagIndex + 1]) : null;
|
|
95
|
+
}
|
|
96
|
+
function createNoopWatcher() {
|
|
97
|
+
return {
|
|
98
|
+
start() {
|
|
99
|
+
},
|
|
100
|
+
stop() {
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function createDefaultParentWatcherFactory() {
|
|
105
|
+
return ({ parentPid, onParentExit, pollIntervalMs, exitCode }) => new ParentPidWatcher({
|
|
106
|
+
parentPid,
|
|
107
|
+
pollIntervalMs,
|
|
108
|
+
onParentGone: () => {
|
|
109
|
+
onParentExit(exitCode);
|
|
43
110
|
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
function sanitizeForwardHeaders(source) {
|
|
114
|
+
const headers = new Headers(source);
|
|
115
|
+
["x-proxy-url", "host", "connection", "content-length"].forEach((headerName) => {
|
|
116
|
+
headers.delete(headerName);
|
|
117
|
+
});
|
|
118
|
+
return headers;
|
|
119
|
+
}
|
|
120
|
+
function copyResponseHeaders(source) {
|
|
121
|
+
const headers = new Headers(source);
|
|
122
|
+
["transfer-encoding", "content-encoding"].forEach((headerName) => {
|
|
123
|
+
headers.delete(headerName);
|
|
124
|
+
});
|
|
125
|
+
return headers;
|
|
126
|
+
}
|
|
127
|
+
function resolveTargetUrl(req, allowedHosts) {
|
|
128
|
+
const targetUrl = req.headers.get("x-proxy-url");
|
|
129
|
+
if (!targetUrl) {
|
|
130
|
+
return new Response("Missing x-proxy-url", { status: 400 });
|
|
131
|
+
}
|
|
132
|
+
try {
|
|
133
|
+
const parsedUrl = new URL(targetUrl);
|
|
134
|
+
if (allowedHosts.size > 0 && !allowedHosts.has(parsedUrl.hostname)) {
|
|
135
|
+
return new Response(`Host not allowed: ${parsedUrl.hostname}`, { status: 403 });
|
|
136
|
+
}
|
|
137
|
+
return parsedUrl;
|
|
138
|
+
} catch {
|
|
139
|
+
return new Response("Invalid x-proxy-url", { status: 400 });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function createAbortContext(requestTimeoutMs) {
|
|
143
|
+
const timeoutController = new AbortController();
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
timeoutController.abort(new DOMException("Upstream request timed out", "TimeoutError"));
|
|
146
|
+
}, requestTimeoutMs);
|
|
147
|
+
timer.unref?.();
|
|
148
|
+
return {
|
|
149
|
+
timeoutSignal: timeoutController.signal,
|
|
150
|
+
cancelTimeout() {
|
|
151
|
+
clearTimeout(timer);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function isAbortError(error) {
|
|
156
|
+
return error instanceof DOMException ? error.name === "AbortError" || error.name === "TimeoutError" : error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
157
|
+
}
|
|
158
|
+
function isTimeoutAbort(signal) {
|
|
159
|
+
const reason = signal.reason;
|
|
160
|
+
return reason instanceof DOMException ? reason.name === "TimeoutError" : reason instanceof Error && reason.name === "TimeoutError";
|
|
161
|
+
}
|
|
162
|
+
function createAbortResponse(req, timeoutSignal) {
|
|
163
|
+
return req.signal.aborted ? new Response("Client disconnected", { status: 499 }) : isTimeoutAbort(timeoutSignal) ? new Response("Upstream request timed out", { status: 504 }) : new Response("Upstream request aborted", { status: 499 });
|
|
164
|
+
}
|
|
165
|
+
async function createUpstreamInit(req, signal) {
|
|
166
|
+
const method = req.method || "GET";
|
|
167
|
+
const hasBody = method !== "GET" && method !== "HEAD";
|
|
168
|
+
const bodyText = hasBody ? await req.text() : "";
|
|
169
|
+
return {
|
|
170
|
+
method,
|
|
171
|
+
headers: sanitizeForwardHeaders(req.headers),
|
|
172
|
+
signal,
|
|
173
|
+
...hasBody && bodyText.length > 0 ? { body: bodyText } : {}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function logRequest(targetUrl, req) {
|
|
177
|
+
if (!DEBUG_ENABLED) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const logHeaders = Object.fromEntries(
|
|
181
|
+
[...sanitizeForwardHeaders(req.headers).entries()].map(([key, value]) => [
|
|
182
|
+
key,
|
|
183
|
+
key === "authorization" ? "Bearer ***" : value
|
|
184
|
+
])
|
|
185
|
+
);
|
|
186
|
+
console.error("\n[bun-proxy] === FORWARDED REQUEST ===");
|
|
187
|
+
console.error(`[bun-proxy] ${req.method} ${targetUrl.toString()}`);
|
|
188
|
+
console.error(`[bun-proxy] Headers: ${JSON.stringify(logHeaders, null, 2)}`);
|
|
189
|
+
console.error("[bun-proxy] =========================\n");
|
|
190
|
+
}
|
|
191
|
+
function createProxyRequestHandler(options) {
|
|
192
|
+
const allowedHosts = new Set(options.allowHosts ?? DEFAULT_ALLOWED_HOSTS);
|
|
193
|
+
const requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
194
|
+
return async function handleProxyRequest(req) {
|
|
195
|
+
if (new URL(req.url).pathname === HEALTH_PATH) {
|
|
196
|
+
return new Response("ok");
|
|
197
|
+
}
|
|
198
|
+
const targetUrl = resolveTargetUrl(req, allowedHosts);
|
|
199
|
+
if (targetUrl instanceof Response) {
|
|
200
|
+
return targetUrl;
|
|
201
|
+
}
|
|
202
|
+
const abortContext = createAbortContext(requestTimeoutMs);
|
|
203
|
+
const upstreamSignal = AbortSignal.any([req.signal, abortContext.timeoutSignal]);
|
|
204
|
+
const upstreamInit = await createUpstreamInit(req, upstreamSignal);
|
|
205
|
+
logRequest(targetUrl, req);
|
|
44
206
|
try {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
207
|
+
const upstreamResponse = await options.fetchImpl(targetUrl.toString(), upstreamInit);
|
|
208
|
+
return new Response(upstreamResponse.body, {
|
|
209
|
+
status: upstreamResponse.status,
|
|
210
|
+
statusText: upstreamResponse.statusText,
|
|
211
|
+
headers: copyResponseHeaders(upstreamResponse.headers)
|
|
49
212
|
});
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
213
|
+
} catch (error) {
|
|
214
|
+
if (upstreamSignal.aborted && isAbortError(error)) {
|
|
215
|
+
return createAbortResponse(req, abortContext.timeoutSignal);
|
|
216
|
+
}
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
return new Response(message, { status: 502 });
|
|
219
|
+
} finally {
|
|
220
|
+
abortContext.cancelTimeout();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function createProxyProcessRuntime(options = {}) {
|
|
225
|
+
const argv = options.argv ?? process.argv;
|
|
226
|
+
const parentPid = parseParentPid(argv);
|
|
227
|
+
if (!parentPid) {
|
|
228
|
+
return createNoopWatcher();
|
|
229
|
+
}
|
|
230
|
+
const exit = options.exit ?? process.exit;
|
|
231
|
+
const parentWatcherFactory = options.parentWatcherFactory ?? createDefaultParentWatcherFactory();
|
|
232
|
+
return parentWatcherFactory({
|
|
233
|
+
parentPid,
|
|
234
|
+
pollIntervalMs: DEFAULT_PARENT_POLL_INTERVAL_MS,
|
|
235
|
+
exitCode: DEFAULT_PARENT_EXIT_CODE,
|
|
236
|
+
onParentExit: (exitCode) => {
|
|
237
|
+
exit(exitCode ?? DEFAULT_PARENT_EXIT_CODE);
|
|
61
238
|
}
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
function assertBunRuntime() {
|
|
242
|
+
if (typeof Bun === "undefined") {
|
|
243
|
+
throw new Error("bun-proxy.ts must be executed with Bun.");
|
|
62
244
|
}
|
|
63
|
-
|
|
64
|
-
|
|
245
|
+
return Bun;
|
|
246
|
+
}
|
|
247
|
+
async function runProxyProcess() {
|
|
248
|
+
const bun = assertBunRuntime();
|
|
249
|
+
const watcher = createProxyProcessRuntime();
|
|
250
|
+
const server = bun.serve({
|
|
251
|
+
port: 0,
|
|
252
|
+
fetch: createProxyRequestHandler({
|
|
253
|
+
fetchImpl: fetch,
|
|
254
|
+
allowHosts: DEFAULT_ALLOWED_HOSTS,
|
|
255
|
+
requestTimeoutMs: DEFAULT_REQUEST_TIMEOUT_MS
|
|
256
|
+
})
|
|
257
|
+
});
|
|
258
|
+
const lifecycle = {
|
|
259
|
+
closed: false
|
|
260
|
+
};
|
|
261
|
+
const shutdown = (exitCode = 0) => {
|
|
262
|
+
if (lifecycle.closed) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
lifecycle.closed = true;
|
|
266
|
+
watcher.stop();
|
|
267
|
+
server.stop(true);
|
|
268
|
+
process.exit(exitCode);
|
|
269
|
+
};
|
|
270
|
+
process.on("SIGTERM", () => {
|
|
271
|
+
shutdown(0);
|
|
272
|
+
});
|
|
273
|
+
process.on("SIGINT", () => {
|
|
274
|
+
shutdown(0);
|
|
275
|
+
});
|
|
276
|
+
watcher.start();
|
|
277
|
+
process.stdout.write(`BUN_PROXY_PORT=${server.port}
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
if (isMainModule()) {
|
|
281
|
+
void runProxyProcess().catch((error) => {
|
|
282
|
+
const message = error instanceof Error ? error.stack ?? error.message : String(error);
|
|
283
|
+
process.stderr.write(`${message}
|
|
284
|
+
`);
|
|
285
|
+
process.exit(1);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
export {
|
|
289
|
+
createProxyProcessRuntime,
|
|
290
|
+
createProxyRequestHandler
|
|
291
|
+
};
|