@codeproxy/cli 0.1.0 → 0.2.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.
- package/README.md +84 -7
- package/README.zh-CN.md +87 -9
- package/dist/index.cjs +6 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ Point your Responses-API client at `http://127.0.0.1:8787`:
|
|
|
19
19
|
```bash
|
|
20
20
|
curl -N http://127.0.0.1:8787/v1/responses \
|
|
21
21
|
-H 'content-type: application/json' \
|
|
22
|
-
-H "authorization: Bearer
|
|
22
|
+
-H "authorization: Bearer \$API_KEY" \
|
|
23
23
|
-d '{"model":"deepseek-v4-pro","input":"Hello!","stream":true}'
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -47,7 +47,7 @@ See [config.example.json](./config.example.json) for a full example.
|
|
|
47
47
|
|
|
48
48
|
| Field | Type | Description |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| `format` | `"anthropic"` `|` `"openai-chat"` | Upstream API format. If omitted, inferred from `baseUrl` (path ending in `/messages`
|
|
50
|
+
| `format` | `"anthropic"` `|` `"openai-chat"` | Upstream API format. If omitted, inferred from `baseUrl` (path ending in `/messages` → `anthropic`, `/chat/completions` → `openai-chat`, otherwise falls back to `openai-chat`). The proper path suffix is appended automatically |
|
|
51
51
|
| `baseUrl` | `string` | **Required.** Upstream endpoint URL |
|
|
52
52
|
| `apiKey` | `string` | Upstream API key. Sent as `Authorization: Bearer <key>` (Anthropic: rewritten to `x-api-key`) |
|
|
53
53
|
| `model` | `string` | Override the `model` field in all incoming requests |
|
|
@@ -67,8 +67,7 @@ CLI flags > per-upstream fields > top-level fields > built-in defaults
|
|
|
67
67
|
|
|
68
68
|
#### Example: auto-fallback for text-only models
|
|
69
69
|
|
|
70
|
-
When `deepseek` has `dropImages: true` and the user sends an image, the proxy
|
|
71
|
-
automatically routes to `deepseek-vision` (which supports vision):
|
|
70
|
+
When `deepseek` has `dropImages: true` and the user sends an image, the proxy automatically routes to `kimi-vision` (uses Kimi K2.6):
|
|
72
71
|
|
|
73
72
|
```json
|
|
74
73
|
{
|
|
@@ -76,17 +75,95 @@ automatically routes to `deepseek-vision` (which supports vision):
|
|
|
76
75
|
"upstreams": {
|
|
77
76
|
"deepseek": {
|
|
78
77
|
"baseUrl": "https://api.deepseek.com/v1",
|
|
78
|
+
"apiKey": "sk-...",
|
|
79
79
|
"model": "deepseek-v4-pro",
|
|
80
80
|
"dropImages": true,
|
|
81
|
-
"fallback": "
|
|
81
|
+
"fallback": "kimi-vision"
|
|
82
82
|
},
|
|
83
|
-
"
|
|
83
|
+
"kimi-vision": {
|
|
84
|
+
"baseUrl": "https://api.moonshot.cn/v1",
|
|
85
|
+
"apiKey": "sk-...",
|
|
86
|
+
"model": "kimi-k2.6",
|
|
87
|
+
"headers": { "x-llm-api-key": "sk-..." }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Codex Configuration
|
|
94
|
+
|
|
95
|
+
Codex `0.128.0+` requires custom providers to speak the Responses API. `@codeproxy/cli` bridges this gap for any Chat Completions or Anthropic Messages upstream.
|
|
96
|
+
|
|
97
|
+
### Quick setup
|
|
98
|
+
|
|
99
|
+
1. Start the proxy:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx @codeproxy/cli --upstream-format openai-chat \
|
|
103
|
+
--base-url https://api.deepseek.com/v1 \
|
|
104
|
+
--apikey sk-your-key
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Add a custom provider in `~/.codex/config.toml`:
|
|
108
|
+
|
|
109
|
+
```toml
|
|
110
|
+
[model_providers.deepseek]
|
|
111
|
+
name = "DeepSeek"
|
|
112
|
+
base_url = "http://127.0.0.1:8787/v1"
|
|
113
|
+
wire_api = "responses"
|
|
114
|
+
|
|
115
|
+
[profiles.deepseek-pro]
|
|
116
|
+
model = "deepseek-v4-pro"
|
|
117
|
+
model_provider = "deepseek"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### With reasoning effort
|
|
121
|
+
|
|
122
|
+
```toml
|
|
123
|
+
[model_providers.deepseek]
|
|
124
|
+
name = "DeepSeek"
|
|
125
|
+
base_url = "http://127.0.0.1:8787/v1"
|
|
126
|
+
wire_api = "responses"
|
|
127
|
+
|
|
128
|
+
[profiles.deepseek-pro]
|
|
129
|
+
model = "deepseek-v4-pro"
|
|
130
|
+
model_provider = "deepseek"
|
|
131
|
+
model_reasoning_effort = "high"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`@codeproxy/cli` automatically maps `reasoning.effort` to the upstream's native format (e.g., `reasoning_effort` for OpenAI Chat, `thinking` blocks for Anthropic).
|
|
135
|
+
|
|
136
|
+
### Multiple upstreams via config file
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"currentUpstream": "deepseek-chat",
|
|
141
|
+
"upstreams": {
|
|
142
|
+
"deepseek-chat": {
|
|
84
143
|
"baseUrl": "https://api.deepseek.com/v1",
|
|
85
|
-
"
|
|
144
|
+
"apiKey": "sk-...",
|
|
145
|
+
"model": "deepseek-v4-pro",
|
|
146
|
+
"dropImages": true,
|
|
147
|
+
"fallback": "kimi-vision"
|
|
148
|
+
},
|
|
149
|
+
"kimi-vision": {
|
|
150
|
+
"baseUrl": "https://api.moonshot.cn/v1",
|
|
151
|
+
"apiKey": "sk-...",
|
|
152
|
+
"model": "kimi-k2.6",
|
|
153
|
+
"headers": { "x-llm-api-key": "sk-..." }
|
|
154
|
+
},
|
|
155
|
+
"claude": {
|
|
156
|
+
"format": "anthropic",
|
|
157
|
+
"baseUrl": "https://api.anthropic.com/v1",
|
|
158
|
+
"apiKey": "sk-ant-...",
|
|
159
|
+
"model": "claude-sonnet-4-20250514"
|
|
86
160
|
}
|
|
87
161
|
}
|
|
88
162
|
}
|
|
89
163
|
```
|
|
164
|
+
|
|
165
|
+
Switch upstreams by changing `currentUpstream` and restarting the proxy — no Codex config changes needed.
|
|
166
|
+
|
|
90
167
|
## Install
|
|
91
168
|
|
|
92
169
|
```bash
|
package/README.zh-CN.md
CHANGED
|
@@ -19,7 +19,7 @@ npx @codeproxy/cli --upstream-format openai-chat \
|
|
|
19
19
|
```bash
|
|
20
20
|
curl -N http://127.0.0.1:8787/v1/responses \
|
|
21
21
|
-H 'content-type: application/json' \
|
|
22
|
-
-H "authorization: Bearer
|
|
22
|
+
-H "authorization: Bearer \$API_KEY" \
|
|
23
23
|
-d '{"model":"deepseek-v4-pro","input":"Hello!","stream":true}'
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -47,7 +47,7 @@ npx @codeproxy/cli --config config.json
|
|
|
47
47
|
|
|
48
48
|
| 字段 | 类型 | 说明 |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| `format` | `"anthropic"` `|` `"openai-chat"` | 上游 API 格式。省略时从 `baseUrl` 推断(路径结尾是 `/messages`
|
|
50
|
+
| `format` | `"anthropic"` `|` `"openai-chat"` | 上游 API 格式。省略时从 `baseUrl` 推断(路径结尾是 `/messages` → `anthropic`,`/chat/completions` → `openai-chat`,否则回退到 `openai-chat`)。路径后缀会自动补全 |
|
|
51
51
|
| `baseUrl` | `string` | **必需。** 上游端点 URL |
|
|
52
52
|
| `apiKey` | `string` | 上游 API 密钥。作为 `Authorization: Bearer <key>` 发送(Anthropic 会转为 `x-api-key`) |
|
|
53
53
|
| `model` | `string` | 覆盖所有传入请求中的 `model` 字段 |
|
|
@@ -75,17 +75,95 @@ CLI 标志 > 每个上游的字段 > 顶层字段 > 内置默认值
|
|
|
75
75
|
"upstreams": {
|
|
76
76
|
"deepseek": {
|
|
77
77
|
"baseUrl": "https://api.deepseek.com/v1",
|
|
78
|
+
"apiKey": "sk-...",
|
|
78
79
|
"model": "deepseek-v4-pro",
|
|
79
80
|
"dropImages": true,
|
|
80
|
-
"fallback": "
|
|
81
|
+
"fallback": "kimi-vision"
|
|
81
82
|
},
|
|
82
|
-
"
|
|
83
|
+
"kimi-vision": {
|
|
84
|
+
"baseUrl": "https://api.moonshot.cn/v1",
|
|
85
|
+
"apiKey": "sk-...",
|
|
86
|
+
"model": "kimi-k2.6",
|
|
87
|
+
"headers": { "x-llm-api-key": "sk-..." }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Codex 配置
|
|
94
|
+
|
|
95
|
+
Codex `0.128.0+` 要求自定义 Provider 必须使用 Responses API。`@codeproxy/cli` 为任何 Chat Completions 或 Anthropic Messages 上游填补了这一差距。
|
|
96
|
+
|
|
97
|
+
### 快速设置
|
|
98
|
+
|
|
99
|
+
1. 启动代理:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
npx @codeproxy/cli --upstream-format openai-chat \
|
|
103
|
+
--base-url https://api.deepseek.com/v1 \
|
|
104
|
+
--apikey sk-your-key
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. 在 `~/.codex/config.toml` 中添加自定义 Provider:
|
|
108
|
+
|
|
109
|
+
```toml
|
|
110
|
+
[model_providers.deepseek]
|
|
111
|
+
name = "DeepSeek"
|
|
112
|
+
base_url = "http://127.0.0.1:8787/v1"
|
|
113
|
+
wire_api = "responses"
|
|
114
|
+
|
|
115
|
+
[profiles.deepseek-pro]
|
|
116
|
+
model = "deepseek-v4-pro"
|
|
117
|
+
model_provider = "deepseek"
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 配置推理力度
|
|
121
|
+
|
|
122
|
+
```toml
|
|
123
|
+
[model_providers.deepseek]
|
|
124
|
+
name = "DeepSeek"
|
|
125
|
+
base_url = "http://127.0.0.1:8787/v1"
|
|
126
|
+
wire_api = "responses"
|
|
127
|
+
|
|
128
|
+
[profiles.deepseek-pro]
|
|
129
|
+
model = "deepseek-v4-pro"
|
|
130
|
+
model_provider = "deepseek"
|
|
131
|
+
model_reasoning_effort = "high"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`@codeproxy/cli` 会自动将 `reasoning.effort` 映射为上游原生的推理参数(如 OpenAI Chat 的 `reasoning_effort`、Anthropic 的 `thinking` 块)。
|
|
135
|
+
|
|
136
|
+
### 多上游配置文件
|
|
137
|
+
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"currentUpstream": "deepseek-chat",
|
|
141
|
+
"upstreams": {
|
|
142
|
+
"deepseek-chat": {
|
|
83
143
|
"baseUrl": "https://api.deepseek.com/v1",
|
|
84
|
-
"
|
|
144
|
+
"apiKey": "sk-...",
|
|
145
|
+
"model": "deepseek-v4-pro",
|
|
146
|
+
"dropImages": true,
|
|
147
|
+
"fallback": "kimi-vision"
|
|
148
|
+
},
|
|
149
|
+
"kimi-vision": {
|
|
150
|
+
"baseUrl": "https://api.moonshot.cn/v1",
|
|
151
|
+
"apiKey": "sk-...",
|
|
152
|
+
"model": "kimi-k2.6",
|
|
153
|
+
"headers": { "x-llm-api-key": "sk-..." }
|
|
154
|
+
},
|
|
155
|
+
"claude": {
|
|
156
|
+
"format": "anthropic",
|
|
157
|
+
"baseUrl": "https://api.anthropic.com/v1",
|
|
158
|
+
"apiKey": "sk-ant-...",
|
|
159
|
+
"model": "claude-sonnet-4-20250514"
|
|
85
160
|
}
|
|
86
161
|
}
|
|
87
162
|
}
|
|
88
163
|
```
|
|
164
|
+
|
|
165
|
+
修改 `currentUpstream` 并重启代理即可切换上游 — 无需改动 Codex 配置。
|
|
166
|
+
|
|
89
167
|
## 安装
|
|
90
168
|
|
|
91
169
|
```bash
|
|
@@ -99,12 +177,12 @@ npm install -g @codeproxy/cli
|
|
|
99
177
|
| `--base-url <url>` | — | 上游端点(除非使用 `--config`,否则必需) |
|
|
100
178
|
| `--upstream-format <fmt>` | 自动推断 | `anthropic` 或 `openai-chat` |
|
|
101
179
|
| `--config <file>` | — | JSON 配置文件 |
|
|
102
|
-
| `--host <host>` | `127.0.0.1` |
|
|
180
|
+
| `--host <host>` | `127.0.0.1` | 绑定主机 |
|
|
103
181
|
| `-p, --port <port>` | `8787` | 绑定端口 |
|
|
104
182
|
| `--api-version <ver>` | `2023-06-01` | 覆盖 Anthropic 版本头 |
|
|
105
183
|
| `--apikey <key>` | — | 上游 API 密钥 |
|
|
106
|
-
| `--model <name>` | — |
|
|
107
|
-
| `--drop-images` | — |
|
|
184
|
+
| `--model <name>` | — | 覆盖所有请求的 model 字段 |
|
|
185
|
+
| `--drop-images` | — | 移除图片(纯文本模型) |
|
|
108
186
|
|
|
109
187
|
## 编程使用
|
|
110
188
|
|
|
@@ -118,6 +196,6 @@ const proxy = await startProxy({
|
|
|
118
196
|
});
|
|
119
197
|
```
|
|
120
198
|
|
|
121
|
-
##
|
|
199
|
+
## License
|
|
122
200
|
|
|
123
201
|
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -154,11 +154,13 @@ async function startProxy(options) {
|
|
|
154
154
|
requestInfo.method = req.method ?? "POST";
|
|
155
155
|
requestInfo.url = req.url ?? "/v1/responses";
|
|
156
156
|
requestInfo.startTime = start;
|
|
157
|
+
const abortController = new AbortController();
|
|
157
158
|
const timeoutMs = options.timeoutMs;
|
|
158
159
|
let timeoutTimer;
|
|
159
160
|
if (timeoutMs && timeoutMs > 0) {
|
|
160
161
|
timeoutTimer = setTimeout(() => {
|
|
161
162
|
logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);
|
|
163
|
+
abortController.abort();
|
|
162
164
|
res.destroy();
|
|
163
165
|
req.destroy();
|
|
164
166
|
}, timeoutMs);
|
|
@@ -172,7 +174,8 @@ async function startProxy(options) {
|
|
|
172
174
|
url: req.url ?? "/",
|
|
173
175
|
upstreamCapture,
|
|
174
176
|
requestInfo,
|
|
175
|
-
requestTracker
|
|
177
|
+
requestTracker,
|
|
178
|
+
signal: abortController.signal
|
|
176
179
|
});
|
|
177
180
|
} catch (err) {
|
|
178
181
|
logger?.error("[proxy-error]", err);
|
|
@@ -245,7 +248,8 @@ async function handleRequest(req, res, opts) {
|
|
|
245
248
|
const response = await opts.apiFetch(`http://local${urlPath}`, {
|
|
246
249
|
method,
|
|
247
250
|
headers,
|
|
248
|
-
body: body ? new Uint8Array(body) : void 0
|
|
251
|
+
body: body ? new Uint8Array(body) : void 0,
|
|
252
|
+
signal: opts.signal
|
|
249
253
|
});
|
|
250
254
|
const responseBodyText = response.body ? await response.clone().text() : "";
|
|
251
255
|
opts.requestTracker.remove(requestId);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/proxy.ts"],"names":["createResponsesFetch","http","resolve","Readable","mkdirSync","join","writeFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["// ==============================================================================\n// Helpers\n// ==============================================================================\n\n/**\n * Local HTTP proxy that exposes the Responses API and forwards translated\n * requests to the configured upstream API format.\n */\n\nimport http, { type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ==============================================================================\n// Helpers\n// ==============================================================================\nfunction fmtTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nfunction fmtDuration(ms: number): string {\n // ==============================================================================\n // Server\n // ==============================================================================\n\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60000);\n const seconds = Math.round((ms % 60000) / 1000);\n return `${minutes}:${String(seconds).padStart(2, '0')}`;\n}\nimport { createResponsesFetch, type CreateResponsesFetchOptions } from '@codeproxy/core';\n\nexport interface StartProxyOptions extends Omit<CreateResponsesFetchOptions, 'passthroughFetch'> {\n /** Host to bind to. Defaults to `127.0.0.1`. */\n host?: string;\n /** Port to listen on. Defaults to `8787`; pass `0` for a random free port. */\n port?: number;\n /** Enable permissive CORS (useful for local browser dev). Defaults to true. */\n cors?: boolean;\n /** Optional logger. Defaults to `console`. Pass `null` to silence. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'> | null;\n /** Optional callback to receive cache statistics after each request completes. */\n onCacheStats?: (stats: {\n cachedTokens: number;\n cacheCreationTokens: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n method?: string;\n url?: string;\n durationMs?: number;\n }) => void;\n}\n\nexport interface RunningProxy {\n host: string;\n port: number;\n url: string;\n server: Server;\n close: () => Promise<void>;\n}\n\nexport async function startProxy(options: StartProxyOptions): Promise<RunningProxy> {\n const host = options.host ?? '127.0.0.1';\n const port = options.port ?? 8787;\n const cors = options.cors ?? true;\n const logger = options.logger === null ? null : (options.logger ?? console);\n\n // Rolling average for request duration coloring (last 50 requests)\n const durationHistory: number[] = [];\n function updateRollingAverage(ms: number) {\n durationHistory.push(ms);\n if (durationHistory.length > 50) {\n durationHistory.shift();\n }\n return durationHistory.reduce((sum, val) => sum + val, 0) / durationHistory.length;\n }\n\n // Centralized status line for all active requests\n const activeRequests = new Map<string, { method: string; url: string; startTime: number }>();\n // ==============================================================================\n // Request Handler\n // ==============================================================================\n\n let statusTimerId: ReturnType<typeof setInterval> | null = null;\n\n function drawStatusLine() {\n if (activeRequests.size === 0) {\n return;\n }\n const parts = Array.from(activeRequests.entries()).map(([, req]) => {\n const elapsed = Date.now() - req.startTime;\n return `[${fmtDuration(elapsed)}]`;\n });\n process.stdout.write(`\\r\\x1b[K⏳ ${parts.join(', ')}`);\n }\n\n const requestTracker = {\n add(method: string, url: string): string {\n const id = `${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;\n activeRequests.set(id, { method, url, startTime: Date.now() });\n drawStatusLine();\n if (!statusTimerId) {\n statusTimerId = setInterval(drawStatusLine, 150);\n }\n return id;\n },\n remove(id: string) {\n activeRequests.delete(id);\n if (activeRequests.size === 0) {\n process.stdout.write('\\r\\x1b[K');\n if (statusTimerId) {\n clearInterval(statusTimerId);\n statusTimerId = null;\n }\n }\n },\n };\n\n const requestInfo = { method: '', url: '', startTime: 0, resultLog: '' };\n\n const upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n } = {};\n\n const baseFetch = options.fetch ?? globalThis.fetch;\n const capturingFetch: typeof fetch = async (input, init) => {\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method ?? input?.method ?? 'GET').toUpperCase();\n const reqHeaders = headersInitToObject(init?.headers);\n let reqBody: unknown = undefined;\n if (init?.body != null) {\n if (typeof init.body === 'string') {\n reqBody = tryParseJson(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else if (ArrayBuffer.isView(init.body)) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else {\n reqBody = String(init.body);\n }\n }\n upstreamCapture.request = { url, method, headers: reqHeaders, body: reqBody };\n\n const resp = await baseFetch(input, init);\n\n if (!resp.ok) {\n const clone = resp.clone();\n const text = await clone.text().catch(() => '');\n upstreamCapture.response = {\n status: resp.status,\n statusText: resp.statusText,\n headers: headersToObject(resp.headers),\n body: tryParseJson(text),\n };\n } else {\n upstreamCapture.response = undefined;\n }\n return resp;\n };\n\n const apiFetch = createResponsesFetch({\n upstreamFormat: options.upstreamFormat,\n baseUrl: options.baseUrl,\n apiVersion: options.apiVersion,\n model: options.model,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n dropImages: options.dropImages,\n fallbackUpstream: options.fallbackUpstream,\n fetch: capturingFetch,\n passthroughFetch: async () =>\n new Response(JSON.stringify({ error: { message: 'Not found' } }), {\n status: 404,\n headers: { 'content-type': 'application/json' },\n }),\n onCacheStats: (stats) => {\n const durationMs = requestInfo.startTime ? Date.now() - requestInfo.startTime : 0;\n const billedTokens = stats.inputTokens + stats.outputTokens - stats.cachedTokens;\n const parts = [\n `total=${stats.totalTokens}`,\n `input=${stats.inputTokens}`,\n `output=${stats.outputTokens}`,\n `cached=${stats.cachedTokens}`,\n `billed=${billedTokens}`,\n ];\n if (stats.cacheCreationTokens > 0) {\n parts.push(`cache_creation=${stats.cacheCreationTokens}`);\n }\n const avg = updateRollingAverage(durationMs);\n const ratio = avg > 0 ? durationMs / avg : 1;\n const color = ratio < 0.8 ? '\\x1b[32m' : ratio < 1.5 ? '\\x1b[33m' : '\\x1b[31m';\n const reset = '\\x1b[0m';\n const logMsg = `[${fmtTime(new Date())}] -> 200 (${color}${fmtDuration(durationMs)}${reset} avg=${fmtDuration(Math.round(avg))}) [${parts.join(', ')}]`;\n requestInfo.resultLog =\n stats.cachedTokens < 1024 && billedTokens > 0 ? `⚠️ NO CACHE -- ${logMsg}` : logMsg;\n\n if (options.onCacheStats) {\n options.onCacheStats({\n ...stats,\n method: requestInfo.method || undefined,\n url: requestInfo.url || undefined,\n durationMs: durationMs || undefined,\n });\n }\n },\n });\n\n const server = http.createServer(async (req, res) => {\n const start = Date.now();\n requestInfo.method = req.method ?? 'POST';\n requestInfo.url = req.url ?? '/v1/responses';\n requestInfo.startTime = start;\n\n const timeoutMs = options.timeoutMs;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n if (timeoutMs && timeoutMs > 0) {\n timeoutTimer = setTimeout(() => {\n logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);\n res.destroy();\n req.destroy();\n }, timeoutMs);\n }\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n await handleRequest(req, res, {\n apiFetch,\n cors,\n logger,\n method: req.method ?? 'POST',\n url: req.url ?? '/',\n upstreamCapture,\n requestInfo,\n requestTracker,\n });\n } catch (err) {\n logger?.error('[proxy-error]', err);\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n if (!res.headersSent) {\n res.writeHead(500, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n }\n } catch {\n // ignore\n }\n } finally {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer);\n }\n }\n });\n\n return new Promise((resolve, reject) => {\n server.listen(port, host, () => {\n const actualPort = (() => {\n // eslint-disable-next-line no-restricted-syntax -- net.Server.address() returns string | AddressInfo | null\n const addr = server.address() as { port: number } | null;\n return addr.port;\n })();\n const url = `http://${host}:${actualPort}`;\n logger?.log(`Proxy listening on ${url}`);\n logger?.log(`Upstream format: ${options.upstreamFormat}`);\n logger?.log(`Upstream URL: ${options.baseUrl}`);\n resolve({\n host,\n port: actualPort,\n url,\n server,\n close: () =>\n new Promise((res) => {\n server.close((err) => {\n if (err) {\n logger?.warn('Error closing server:', err);\n }\n res();\n });\n }),\n });\n });\n server.once('error', reject);\n });\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: {\n apiFetch: typeof fetch;\n cors: boolean;\n logger: Pick<Console, 'log' | 'warn' | 'error'> | null;\n method: string;\n url: string;\n upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n };\n requestInfo: { resultLog: string };\n requestTracker: { add: (method: string, url: string) => string; remove: (id: string) => void };\n },\n): Promise<void> {\n if (opts.cors) {\n setCorsHeaders(res);\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const method = req.method ?? 'GET';\n const urlPath = req.url ?? '/';\n const headers = flattenIncomingHeaders(req.headers);\n\n let body: Buffer | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await readIncomingBody(req);\n }\n\n if (!/^\\/v1\\/responses\\/?(?:\\?|$)/.test(urlPath)) {\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: `Not found: ${method} ${urlPath}` } }));\n return;\n }\n\n const requestBodyText = body ? body.toString('utf8') : undefined;\n\n const requestStart = Date.now();\n const requestId = opts.requestTracker.add(method, urlPath);\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const response = await opts.apiFetch(`http://local${urlPath}`, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n });\n\n // Consume response body so onCacheStats fires (for streaming responses)\n const responseBodyText = response.body ? await response.clone().text() : '';\n\n // Remove from active requests and write final result\n opts.requestTracker.remove(requestId);\n if (opts.logger) {\n if (response.status >= 400) {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n } else if (opts.requestInfo.resultLog) {\n process.stdout.write(`\\r\\x1b[K${opts.requestInfo.resultLog}\\n`);\n } else {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n }\n }\n if (response.status >= 400) {\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const filePath = saveErrorDump({\n method: opts.method,\n url: opts.url,\n clientRequest: {\n headers,\n body: tryParseJson(requestBodyText ?? ''),\n },\n upstreamRequest: opts.upstreamCapture.request,\n upstreamResponse: opts.upstreamCapture.response,\n proxyResponse: {\n status: response.status,\n headers: headersToObject(response.headers),\n body: tryParseJson(responseBodyText),\n },\n });\n opts.logger?.error(`[proxy-failure] full exchange saved to ${filePath}`);\n } catch (dumpErr) {\n opts.logger?.error('[proxy-failure] failed to persist error dump', dumpErr);\n }\n }\n\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n outHeaders[key] = value;\n });\n if (opts.cors) {\n Object.assign(outHeaders, corsHeaders());\n }\n\n res.writeHead(response.status, outHeaders);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- fetch response.body is not typed as node stream\n const typedBody = response.body! as unknown as import('stream/web').ReadableStream<Uint8Array>;\n const nodeStream = Readable.fromWeb(typedBody);\n nodeStream.pipe(res);\n await new Promise<void>((resolve, reject) => {\n nodeStream.once('end', resolve);\n nodeStream.once('error', reject);\n res.once('close', resolve);\n });\n } catch (err) {\n opts.requestTracker.remove(requestId);\n throw err;\n }\n}\n\nfunction readIncomingBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => {\n const buf: Buffer = chunk;\n chunks.push(buf);\n });\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\nfunction flattenIncomingHeaders(headers: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) {\n continue;\n }\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n return out;\n}\n\nfunction headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\nfunction setCorsHeaders(res: ServerResponse): void {\n const headers = corsHeaders();\n for (const [key, value] of Object.entries(headers)) {\n res.setHeader(key, value);\n }\n}\n\nfunction corsHeaders(): Record<string, string> {\n return {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'GET,POST,OPTIONS',\n 'access-control-allow-headers':\n 'authorization,content-type,x-api-key,anthropic-version,anthropic-beta,anthropic-dangerous-direct-browser-access',\n 'access-control-expose-headers': 'content-type',\n };\n}\n\nfunction tryParseJson(str: string | undefined | null): unknown {\n if (!str) {\n return str ?? null;\n }\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n return JSON.parse(str);\n } catch {\n return str;\n }\n}\n\nfunction headersInitToObject(headersInit: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!headersInit) {\n return out;\n }\n if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {\n headersInit.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n }\n if (Array.isArray(headersInit)) {\n for (const [key, value] of headersInit) {\n out[String(key).toLowerCase()] = String(value);\n }\n return out;\n }\n for (const [key, value] of Object.entries(headersInit)) {\n out[key.toLowerCase()] = String(value);\n }\n return out;\n}\n\nfunction saveErrorDump(dump: {\n method: string;\n url: string;\n clientRequest: { headers: Record<string, string>; body: unknown };\n upstreamRequest?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n upstreamResponse?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n proxyResponse: { status: number; headers: Record<string, string>; body: unknown };\n}): string {\n const dir = resolve(process.cwd(), 'logs');\n mkdirSync(dir, { recursive: true });\n const ts = new Date().toISOString().replace(/[:.]/g, '-');\n const status = dump.upstreamResponse?.status ?? dump.proxyResponse.status;\n const filename = `proxy-error-${ts}-${status}.json`;\n const filePath = join(dir, filename);\n const payload = {\n timestamp: new Date().toISOString(),\n ...dump,\n };\n redactAuth(payload.clientRequest?.headers);\n redactAuth(payload.upstreamRequest?.headers);\n writeFileSync(filePath, JSON.stringify(payload, null, 2));\n return filePath;\n}\n\nfunction redactAuth(headers: Record<string, string> | undefined): void {\n if (!headers) {\n return;\n }\n for (const key of Object.keys(headers)) {\n const lowerKey = key.toLowerCase();\n if (\n lowerKey === 'authorization' ||\n lowerKey === 'x-api-key' ||\n lowerKey === 'api-key' ||\n lowerKey === 'cookie'\n ) {\n headers[key] = '[REDACTED]';\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/proxy.ts"],"names":["createResponsesFetch","http","resolve","Readable","mkdirSync","join","writeFileSync"],"mappings":"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","file":"index.cjs","sourcesContent":["// ==============================================================================\n// Helpers\n// ==============================================================================\n\n/**\n * Local HTTP proxy that exposes the Responses API and forwards translated\n * requests to the configured upstream API format.\n */\n\nimport http, { type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ==============================================================================\n// Helpers\n// ==============================================================================\nfunction fmtTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nfunction fmtDuration(ms: number): string {\n // ==============================================================================\n // Server\n // ==============================================================================\n\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60000);\n const seconds = Math.round((ms % 60000) / 1000);\n return `${minutes}:${String(seconds).padStart(2, '0')}`;\n}\nimport { createResponsesFetch, type CreateResponsesFetchOptions } from '@codeproxy/core';\n\nexport interface StartProxyOptions extends Omit<CreateResponsesFetchOptions, 'passthroughFetch'> {\n /** Host to bind to. Defaults to `127.0.0.1`. */\n host?: string;\n /** Port to listen on. Defaults to `8787`; pass `0` for a random free port. */\n port?: number;\n /** Enable permissive CORS (useful for local browser dev). Defaults to true. */\n cors?: boolean;\n /** Optional logger. Defaults to `console`. Pass `null` to silence. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'> | null;\n /** Optional callback to receive cache statistics after each request completes. */\n onCacheStats?: (stats: {\n cachedTokens: number;\n cacheCreationTokens: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n method?: string;\n url?: string;\n durationMs?: number;\n }) => void;\n}\n\nexport interface RunningProxy {\n host: string;\n port: number;\n url: string;\n server: Server;\n close: () => Promise<void>;\n}\n\nexport async function startProxy(options: StartProxyOptions): Promise<RunningProxy> {\n const host = options.host ?? '127.0.0.1';\n const port = options.port ?? 8787;\n const cors = options.cors ?? true;\n const logger = options.logger === null ? null : (options.logger ?? console);\n\n // Rolling average for request duration coloring (last 50 requests)\n const durationHistory: number[] = [];\n function updateRollingAverage(ms: number) {\n durationHistory.push(ms);\n if (durationHistory.length > 50) {\n durationHistory.shift();\n }\n return durationHistory.reduce((sum, val) => sum + val, 0) / durationHistory.length;\n }\n\n // Centralized status line for all active requests\n const activeRequests = new Map<string, { method: string; url: string; startTime: number }>();\n // ==============================================================================\n // Request Handler\n // ==============================================================================\n\n let statusTimerId: ReturnType<typeof setInterval> | null = null;\n\n function drawStatusLine() {\n if (activeRequests.size === 0) {\n return;\n }\n const parts = Array.from(activeRequests.entries()).map(([, req]) => {\n const elapsed = Date.now() - req.startTime;\n return `[${fmtDuration(elapsed)}]`;\n });\n process.stdout.write(`\\r\\x1b[K⏳ ${parts.join(', ')}`);\n }\n\n const requestTracker = {\n add(method: string, url: string): string {\n const id = `${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;\n activeRequests.set(id, { method, url, startTime: Date.now() });\n drawStatusLine();\n if (!statusTimerId) {\n statusTimerId = setInterval(drawStatusLine, 150);\n }\n return id;\n },\n remove(id: string) {\n activeRequests.delete(id);\n if (activeRequests.size === 0) {\n process.stdout.write('\\r\\x1b[K');\n if (statusTimerId) {\n clearInterval(statusTimerId);\n statusTimerId = null;\n }\n }\n },\n };\n\n const requestInfo = { method: '', url: '', startTime: 0, resultLog: '' };\n\n const upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n } = {};\n\n const baseFetch = options.fetch ?? globalThis.fetch;\n const capturingFetch: typeof fetch = async (input, init) => {\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method ?? input?.method ?? 'GET').toUpperCase();\n const reqHeaders = headersInitToObject(init?.headers);\n let reqBody: unknown = undefined;\n if (init?.body != null) {\n if (typeof init.body === 'string') {\n reqBody = tryParseJson(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else if (ArrayBuffer.isView(init.body)) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else {\n reqBody = String(init.body);\n }\n }\n upstreamCapture.request = { url, method, headers: reqHeaders, body: reqBody };\n\n const resp = await baseFetch(input, init);\n\n if (!resp.ok) {\n const clone = resp.clone();\n const text = await clone.text().catch(() => '');\n upstreamCapture.response = {\n status: resp.status,\n statusText: resp.statusText,\n headers: headersToObject(resp.headers),\n body: tryParseJson(text),\n };\n } else {\n upstreamCapture.response = undefined;\n }\n return resp;\n };\n\n const apiFetch = createResponsesFetch({\n upstreamFormat: options.upstreamFormat,\n baseUrl: options.baseUrl,\n apiVersion: options.apiVersion,\n model: options.model,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n dropImages: options.dropImages,\n fallbackUpstream: options.fallbackUpstream,\n fetch: capturingFetch,\n passthroughFetch: async () =>\n new Response(JSON.stringify({ error: { message: 'Not found' } }), {\n status: 404,\n headers: { 'content-type': 'application/json' },\n }),\n onCacheStats: (stats) => {\n const durationMs = requestInfo.startTime ? Date.now() - requestInfo.startTime : 0;\n const billedTokens = stats.inputTokens + stats.outputTokens - stats.cachedTokens;\n const parts = [\n `total=${stats.totalTokens}`,\n `input=${stats.inputTokens}`,\n `output=${stats.outputTokens}`,\n `cached=${stats.cachedTokens}`,\n `billed=${billedTokens}`,\n ];\n if (stats.cacheCreationTokens > 0) {\n parts.push(`cache_creation=${stats.cacheCreationTokens}`);\n }\n const avg = updateRollingAverage(durationMs);\n const ratio = avg > 0 ? durationMs / avg : 1;\n const color = ratio < 0.8 ? '\\x1b[32m' : ratio < 1.5 ? '\\x1b[33m' : '\\x1b[31m';\n const reset = '\\x1b[0m';\n const logMsg = `[${fmtTime(new Date())}] -> 200 (${color}${fmtDuration(durationMs)}${reset} avg=${fmtDuration(Math.round(avg))}) [${parts.join(', ')}]`;\n requestInfo.resultLog =\n stats.cachedTokens < 1024 && billedTokens > 0 ? `⚠️ NO CACHE -- ${logMsg}` : logMsg;\n\n if (options.onCacheStats) {\n options.onCacheStats({\n ...stats,\n method: requestInfo.method || undefined,\n url: requestInfo.url || undefined,\n durationMs: durationMs || undefined,\n });\n }\n },\n });\n\n const server = http.createServer(async (req, res) => {\n const start = Date.now();\n requestInfo.method = req.method ?? 'POST';\n requestInfo.url = req.url ?? '/v1/responses';\n requestInfo.startTime = start;\n\n const abortController = new AbortController();\n const timeoutMs = options.timeoutMs;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n if (timeoutMs && timeoutMs > 0) {\n timeoutTimer = setTimeout(() => {\n logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);\n abortController.abort();\n res.destroy();\n req.destroy();\n }, timeoutMs);\n }\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n await handleRequest(req, res, {\n apiFetch,\n cors,\n logger,\n method: req.method ?? 'POST',\n url: req.url ?? '/',\n upstreamCapture,\n requestInfo,\n requestTracker,\n signal: abortController.signal,\n });\n } catch (err) {\n logger?.error('[proxy-error]', err);\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n if (!res.headersSent) {\n res.writeHead(500, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n }\n } catch {\n // ignore\n }\n } finally {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer);\n }\n }\n });\n\n return new Promise((resolve, reject) => {\n server.listen(port, host, () => {\n const actualPort = (() => {\n // eslint-disable-next-line no-restricted-syntax -- net.Server.address() returns string | AddressInfo | null\n const addr = server.address() as { port: number } | null;\n return addr.port;\n })();\n const url = `http://${host}:${actualPort}`;\n logger?.log(`Proxy listening on ${url}`);\n logger?.log(`Upstream format: ${options.upstreamFormat}`);\n logger?.log(`Upstream URL: ${options.baseUrl}`);\n resolve({\n host,\n port: actualPort,\n url,\n server,\n close: () =>\n new Promise((res) => {\n server.close((err) => {\n if (err) {\n logger?.warn('Error closing server:', err);\n }\n res();\n });\n }),\n });\n });\n server.once('error', reject);\n });\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: {\n apiFetch: typeof fetch;\n cors: boolean;\n logger: Pick<Console, 'log' | 'warn' | 'error'> | null;\n method: string;\n url: string;\n upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n };\n requestInfo: { resultLog: string };\n requestTracker: { add: (method: string, url: string) => string; remove: (id: string) => void };\n signal?: AbortSignal;\n },\n): Promise<void> {\n if (opts.cors) {\n setCorsHeaders(res);\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const method = req.method ?? 'GET';\n const urlPath = req.url ?? '/';\n const headers = flattenIncomingHeaders(req.headers);\n\n let body: Buffer | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await readIncomingBody(req);\n }\n\n if (!/^\\/v1\\/responses\\/?(?:\\?|$)/.test(urlPath)) {\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: `Not found: ${method} ${urlPath}` } }));\n return;\n }\n\n const requestBodyText = body ? body.toString('utf8') : undefined;\n\n const requestStart = Date.now();\n const requestId = opts.requestTracker.add(method, urlPath);\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const response = await opts.apiFetch(`http://local${urlPath}`, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n signal: opts.signal,\n });\n\n // Consume response body so onCacheStats fires (for streaming responses)\n const responseBodyText = response.body ? await response.clone().text() : '';\n\n // Remove from active requests and write final result\n opts.requestTracker.remove(requestId);\n if (opts.logger) {\n if (response.status >= 400) {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n } else if (opts.requestInfo.resultLog) {\n process.stdout.write(`\\r\\x1b[K${opts.requestInfo.resultLog}\\n`);\n } else {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n }\n }\n if (response.status >= 400) {\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const filePath = saveErrorDump({\n method: opts.method,\n url: opts.url,\n clientRequest: {\n headers,\n body: tryParseJson(requestBodyText ?? ''),\n },\n upstreamRequest: opts.upstreamCapture.request,\n upstreamResponse: opts.upstreamCapture.response,\n proxyResponse: {\n status: response.status,\n headers: headersToObject(response.headers),\n body: tryParseJson(responseBodyText),\n },\n });\n opts.logger?.error(`[proxy-failure] full exchange saved to ${filePath}`);\n } catch (dumpErr) {\n opts.logger?.error('[proxy-failure] failed to persist error dump', dumpErr);\n }\n }\n\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n outHeaders[key] = value;\n });\n if (opts.cors) {\n Object.assign(outHeaders, corsHeaders());\n }\n\n res.writeHead(response.status, outHeaders);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- fetch response.body is not typed as node stream\n const typedBody = response.body! as unknown as import('stream/web').ReadableStream<Uint8Array>;\n const nodeStream = Readable.fromWeb(typedBody);\n nodeStream.pipe(res);\n await new Promise<void>((resolve, reject) => {\n nodeStream.once('end', resolve);\n nodeStream.once('error', reject);\n res.once('close', resolve);\n });\n } catch (err) {\n opts.requestTracker.remove(requestId);\n throw err;\n }\n}\n\nfunction readIncomingBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => {\n const buf: Buffer = chunk;\n chunks.push(buf);\n });\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\nfunction flattenIncomingHeaders(headers: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) {\n continue;\n }\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n return out;\n}\n\nfunction headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\nfunction setCorsHeaders(res: ServerResponse): void {\n const headers = corsHeaders();\n for (const [key, value] of Object.entries(headers)) {\n res.setHeader(key, value);\n }\n}\n\nfunction corsHeaders(): Record<string, string> {\n return {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'GET,POST,OPTIONS',\n 'access-control-allow-headers':\n 'authorization,content-type,x-api-key,anthropic-version,anthropic-beta,anthropic-dangerous-direct-browser-access',\n 'access-control-expose-headers': 'content-type',\n };\n}\n\nfunction tryParseJson(str: string | undefined | null): unknown {\n if (!str) {\n return str ?? null;\n }\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n return JSON.parse(str);\n } catch {\n return str;\n }\n}\n\nfunction headersInitToObject(headersInit: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!headersInit) {\n return out;\n }\n if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {\n headersInit.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n }\n if (Array.isArray(headersInit)) {\n for (const [key, value] of headersInit) {\n out[String(key).toLowerCase()] = String(value);\n }\n return out;\n }\n for (const [key, value] of Object.entries(headersInit)) {\n out[key.toLowerCase()] = String(value);\n }\n return out;\n}\n\nfunction saveErrorDump(dump: {\n method: string;\n url: string;\n clientRequest: { headers: Record<string, string>; body: unknown };\n upstreamRequest?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n upstreamResponse?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n proxyResponse: { status: number; headers: Record<string, string>; body: unknown };\n}): string {\n const dir = resolve(process.cwd(), 'logs');\n mkdirSync(dir, { recursive: true });\n const ts = new Date().toISOString().replace(/[:.]/g, '-');\n const status = dump.upstreamResponse?.status ?? dump.proxyResponse.status;\n const filename = `proxy-error-${ts}-${status}.json`;\n const filePath = join(dir, filename);\n const payload = {\n timestamp: new Date().toISOString(),\n ...dump,\n };\n redactAuth(payload.clientRequest?.headers);\n redactAuth(payload.upstreamRequest?.headers);\n writeFileSync(filePath, JSON.stringify(payload, null, 2));\n return filePath;\n}\n\nfunction redactAuth(headers: Record<string, string> | undefined): void {\n if (!headers) {\n return;\n }\n for (const key of Object.keys(headers)) {\n const lowerKey = key.toLowerCase();\n if (\n lowerKey === 'authorization' ||\n lowerKey === 'x-api-key' ||\n lowerKey === 'api-key' ||\n lowerKey === 'cookie'\n ) {\n headers[key] = '[REDACTED]';\n }\n }\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -149,11 +149,13 @@ async function startProxy(options) {
|
|
|
149
149
|
requestInfo.method = req.method ?? "POST";
|
|
150
150
|
requestInfo.url = req.url ?? "/v1/responses";
|
|
151
151
|
requestInfo.startTime = start;
|
|
152
|
+
const abortController = new AbortController();
|
|
152
153
|
const timeoutMs = options.timeoutMs;
|
|
153
154
|
let timeoutTimer;
|
|
154
155
|
if (timeoutMs && timeoutMs > 0) {
|
|
155
156
|
timeoutTimer = setTimeout(() => {
|
|
156
157
|
logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);
|
|
158
|
+
abortController.abort();
|
|
157
159
|
res.destroy();
|
|
158
160
|
req.destroy();
|
|
159
161
|
}, timeoutMs);
|
|
@@ -167,7 +169,8 @@ async function startProxy(options) {
|
|
|
167
169
|
url: req.url ?? "/",
|
|
168
170
|
upstreamCapture,
|
|
169
171
|
requestInfo,
|
|
170
|
-
requestTracker
|
|
172
|
+
requestTracker,
|
|
173
|
+
signal: abortController.signal
|
|
171
174
|
});
|
|
172
175
|
} catch (err) {
|
|
173
176
|
logger?.error("[proxy-error]", err);
|
|
@@ -240,7 +243,8 @@ async function handleRequest(req, res, opts) {
|
|
|
240
243
|
const response = await opts.apiFetch(`http://local${urlPath}`, {
|
|
241
244
|
method,
|
|
242
245
|
headers,
|
|
243
|
-
body: body ? new Uint8Array(body) : void 0
|
|
246
|
+
body: body ? new Uint8Array(body) : void 0,
|
|
247
|
+
signal: opts.signal
|
|
244
248
|
});
|
|
245
249
|
const responseBodyText = response.body ? await response.clone().text() : "";
|
|
246
250
|
opts.requestTracker.remove(requestId);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server/proxy.ts"],"names":["resolve"],"mappings":"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","file":"index.js","sourcesContent":["// ==============================================================================\n// Helpers\n// ==============================================================================\n\n/**\n * Local HTTP proxy that exposes the Responses API and forwards translated\n * requests to the configured upstream API format.\n */\n\nimport http, { type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ==============================================================================\n// Helpers\n// ==============================================================================\nfunction fmtTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nfunction fmtDuration(ms: number): string {\n // ==============================================================================\n // Server\n // ==============================================================================\n\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60000);\n const seconds = Math.round((ms % 60000) / 1000);\n return `${minutes}:${String(seconds).padStart(2, '0')}`;\n}\nimport { createResponsesFetch, type CreateResponsesFetchOptions } from '@codeproxy/core';\n\nexport interface StartProxyOptions extends Omit<CreateResponsesFetchOptions, 'passthroughFetch'> {\n /** Host to bind to. Defaults to `127.0.0.1`. */\n host?: string;\n /** Port to listen on. Defaults to `8787`; pass `0` for a random free port. */\n port?: number;\n /** Enable permissive CORS (useful for local browser dev). Defaults to true. */\n cors?: boolean;\n /** Optional logger. Defaults to `console`. Pass `null` to silence. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'> | null;\n /** Optional callback to receive cache statistics after each request completes. */\n onCacheStats?: (stats: {\n cachedTokens: number;\n cacheCreationTokens: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n method?: string;\n url?: string;\n durationMs?: number;\n }) => void;\n}\n\nexport interface RunningProxy {\n host: string;\n port: number;\n url: string;\n server: Server;\n close: () => Promise<void>;\n}\n\nexport async function startProxy(options: StartProxyOptions): Promise<RunningProxy> {\n const host = options.host ?? '127.0.0.1';\n const port = options.port ?? 8787;\n const cors = options.cors ?? true;\n const logger = options.logger === null ? null : (options.logger ?? console);\n\n // Rolling average for request duration coloring (last 50 requests)\n const durationHistory: number[] = [];\n function updateRollingAverage(ms: number) {\n durationHistory.push(ms);\n if (durationHistory.length > 50) {\n durationHistory.shift();\n }\n return durationHistory.reduce((sum, val) => sum + val, 0) / durationHistory.length;\n }\n\n // Centralized status line for all active requests\n const activeRequests = new Map<string, { method: string; url: string; startTime: number }>();\n // ==============================================================================\n // Request Handler\n // ==============================================================================\n\n let statusTimerId: ReturnType<typeof setInterval> | null = null;\n\n function drawStatusLine() {\n if (activeRequests.size === 0) {\n return;\n }\n const parts = Array.from(activeRequests.entries()).map(([, req]) => {\n const elapsed = Date.now() - req.startTime;\n return `[${fmtDuration(elapsed)}]`;\n });\n process.stdout.write(`\\r\\x1b[K⏳ ${parts.join(', ')}`);\n }\n\n const requestTracker = {\n add(method: string, url: string): string {\n const id = `${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;\n activeRequests.set(id, { method, url, startTime: Date.now() });\n drawStatusLine();\n if (!statusTimerId) {\n statusTimerId = setInterval(drawStatusLine, 150);\n }\n return id;\n },\n remove(id: string) {\n activeRequests.delete(id);\n if (activeRequests.size === 0) {\n process.stdout.write('\\r\\x1b[K');\n if (statusTimerId) {\n clearInterval(statusTimerId);\n statusTimerId = null;\n }\n }\n },\n };\n\n const requestInfo = { method: '', url: '', startTime: 0, resultLog: '' };\n\n const upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n } = {};\n\n const baseFetch = options.fetch ?? globalThis.fetch;\n const capturingFetch: typeof fetch = async (input, init) => {\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method ?? input?.method ?? 'GET').toUpperCase();\n const reqHeaders = headersInitToObject(init?.headers);\n let reqBody: unknown = undefined;\n if (init?.body != null) {\n if (typeof init.body === 'string') {\n reqBody = tryParseJson(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else if (ArrayBuffer.isView(init.body)) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else {\n reqBody = String(init.body);\n }\n }\n upstreamCapture.request = { url, method, headers: reqHeaders, body: reqBody };\n\n const resp = await baseFetch(input, init);\n\n if (!resp.ok) {\n const clone = resp.clone();\n const text = await clone.text().catch(() => '');\n upstreamCapture.response = {\n status: resp.status,\n statusText: resp.statusText,\n headers: headersToObject(resp.headers),\n body: tryParseJson(text),\n };\n } else {\n upstreamCapture.response = undefined;\n }\n return resp;\n };\n\n const apiFetch = createResponsesFetch({\n upstreamFormat: options.upstreamFormat,\n baseUrl: options.baseUrl,\n apiVersion: options.apiVersion,\n model: options.model,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n dropImages: options.dropImages,\n fallbackUpstream: options.fallbackUpstream,\n fetch: capturingFetch,\n passthroughFetch: async () =>\n new Response(JSON.stringify({ error: { message: 'Not found' } }), {\n status: 404,\n headers: { 'content-type': 'application/json' },\n }),\n onCacheStats: (stats) => {\n const durationMs = requestInfo.startTime ? Date.now() - requestInfo.startTime : 0;\n const billedTokens = stats.inputTokens + stats.outputTokens - stats.cachedTokens;\n const parts = [\n `total=${stats.totalTokens}`,\n `input=${stats.inputTokens}`,\n `output=${stats.outputTokens}`,\n `cached=${stats.cachedTokens}`,\n `billed=${billedTokens}`,\n ];\n if (stats.cacheCreationTokens > 0) {\n parts.push(`cache_creation=${stats.cacheCreationTokens}`);\n }\n const avg = updateRollingAverage(durationMs);\n const ratio = avg > 0 ? durationMs / avg : 1;\n const color = ratio < 0.8 ? '\\x1b[32m' : ratio < 1.5 ? '\\x1b[33m' : '\\x1b[31m';\n const reset = '\\x1b[0m';\n const logMsg = `[${fmtTime(new Date())}] -> 200 (${color}${fmtDuration(durationMs)}${reset} avg=${fmtDuration(Math.round(avg))}) [${parts.join(', ')}]`;\n requestInfo.resultLog =\n stats.cachedTokens < 1024 && billedTokens > 0 ? `⚠️ NO CACHE -- ${logMsg}` : logMsg;\n\n if (options.onCacheStats) {\n options.onCacheStats({\n ...stats,\n method: requestInfo.method || undefined,\n url: requestInfo.url || undefined,\n durationMs: durationMs || undefined,\n });\n }\n },\n });\n\n const server = http.createServer(async (req, res) => {\n const start = Date.now();\n requestInfo.method = req.method ?? 'POST';\n requestInfo.url = req.url ?? '/v1/responses';\n requestInfo.startTime = start;\n\n const timeoutMs = options.timeoutMs;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n if (timeoutMs && timeoutMs > 0) {\n timeoutTimer = setTimeout(() => {\n logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);\n res.destroy();\n req.destroy();\n }, timeoutMs);\n }\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n await handleRequest(req, res, {\n apiFetch,\n cors,\n logger,\n method: req.method ?? 'POST',\n url: req.url ?? '/',\n upstreamCapture,\n requestInfo,\n requestTracker,\n });\n } catch (err) {\n logger?.error('[proxy-error]', err);\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n if (!res.headersSent) {\n res.writeHead(500, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n }\n } catch {\n // ignore\n }\n } finally {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer);\n }\n }\n });\n\n return new Promise((resolve, reject) => {\n server.listen(port, host, () => {\n const actualPort = (() => {\n // eslint-disable-next-line no-restricted-syntax -- net.Server.address() returns string | AddressInfo | null\n const addr = server.address() as { port: number } | null;\n return addr.port;\n })();\n const url = `http://${host}:${actualPort}`;\n logger?.log(`Proxy listening on ${url}`);\n logger?.log(`Upstream format: ${options.upstreamFormat}`);\n logger?.log(`Upstream URL: ${options.baseUrl}`);\n resolve({\n host,\n port: actualPort,\n url,\n server,\n close: () =>\n new Promise((res) => {\n server.close((err) => {\n if (err) {\n logger?.warn('Error closing server:', err);\n }\n res();\n });\n }),\n });\n });\n server.once('error', reject);\n });\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: {\n apiFetch: typeof fetch;\n cors: boolean;\n logger: Pick<Console, 'log' | 'warn' | 'error'> | null;\n method: string;\n url: string;\n upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n };\n requestInfo: { resultLog: string };\n requestTracker: { add: (method: string, url: string) => string; remove: (id: string) => void };\n },\n): Promise<void> {\n if (opts.cors) {\n setCorsHeaders(res);\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const method = req.method ?? 'GET';\n const urlPath = req.url ?? '/';\n const headers = flattenIncomingHeaders(req.headers);\n\n let body: Buffer | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await readIncomingBody(req);\n }\n\n if (!/^\\/v1\\/responses\\/?(?:\\?|$)/.test(urlPath)) {\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: `Not found: ${method} ${urlPath}` } }));\n return;\n }\n\n const requestBodyText = body ? body.toString('utf8') : undefined;\n\n const requestStart = Date.now();\n const requestId = opts.requestTracker.add(method, urlPath);\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const response = await opts.apiFetch(`http://local${urlPath}`, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n });\n\n // Consume response body so onCacheStats fires (for streaming responses)\n const responseBodyText = response.body ? await response.clone().text() : '';\n\n // Remove from active requests and write final result\n opts.requestTracker.remove(requestId);\n if (opts.logger) {\n if (response.status >= 400) {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n } else if (opts.requestInfo.resultLog) {\n process.stdout.write(`\\r\\x1b[K${opts.requestInfo.resultLog}\\n`);\n } else {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n }\n }\n if (response.status >= 400) {\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const filePath = saveErrorDump({\n method: opts.method,\n url: opts.url,\n clientRequest: {\n headers,\n body: tryParseJson(requestBodyText ?? ''),\n },\n upstreamRequest: opts.upstreamCapture.request,\n upstreamResponse: opts.upstreamCapture.response,\n proxyResponse: {\n status: response.status,\n headers: headersToObject(response.headers),\n body: tryParseJson(responseBodyText),\n },\n });\n opts.logger?.error(`[proxy-failure] full exchange saved to ${filePath}`);\n } catch (dumpErr) {\n opts.logger?.error('[proxy-failure] failed to persist error dump', dumpErr);\n }\n }\n\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n outHeaders[key] = value;\n });\n if (opts.cors) {\n Object.assign(outHeaders, corsHeaders());\n }\n\n res.writeHead(response.status, outHeaders);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- fetch response.body is not typed as node stream\n const typedBody = response.body! as unknown as import('stream/web').ReadableStream<Uint8Array>;\n const nodeStream = Readable.fromWeb(typedBody);\n nodeStream.pipe(res);\n await new Promise<void>((resolve, reject) => {\n nodeStream.once('end', resolve);\n nodeStream.once('error', reject);\n res.once('close', resolve);\n });\n } catch (err) {\n opts.requestTracker.remove(requestId);\n throw err;\n }\n}\n\nfunction readIncomingBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => {\n const buf: Buffer = chunk;\n chunks.push(buf);\n });\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\nfunction flattenIncomingHeaders(headers: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) {\n continue;\n }\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n return out;\n}\n\nfunction headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\nfunction setCorsHeaders(res: ServerResponse): void {\n const headers = corsHeaders();\n for (const [key, value] of Object.entries(headers)) {\n res.setHeader(key, value);\n }\n}\n\nfunction corsHeaders(): Record<string, string> {\n return {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'GET,POST,OPTIONS',\n 'access-control-allow-headers':\n 'authorization,content-type,x-api-key,anthropic-version,anthropic-beta,anthropic-dangerous-direct-browser-access',\n 'access-control-expose-headers': 'content-type',\n };\n}\n\nfunction tryParseJson(str: string | undefined | null): unknown {\n if (!str) {\n return str ?? null;\n }\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n return JSON.parse(str);\n } catch {\n return str;\n }\n}\n\nfunction headersInitToObject(headersInit: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!headersInit) {\n return out;\n }\n if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {\n headersInit.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n }\n if (Array.isArray(headersInit)) {\n for (const [key, value] of headersInit) {\n out[String(key).toLowerCase()] = String(value);\n }\n return out;\n }\n for (const [key, value] of Object.entries(headersInit)) {\n out[key.toLowerCase()] = String(value);\n }\n return out;\n}\n\nfunction saveErrorDump(dump: {\n method: string;\n url: string;\n clientRequest: { headers: Record<string, string>; body: unknown };\n upstreamRequest?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n upstreamResponse?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n proxyResponse: { status: number; headers: Record<string, string>; body: unknown };\n}): string {\n const dir = resolve(process.cwd(), 'logs');\n mkdirSync(dir, { recursive: true });\n const ts = new Date().toISOString().replace(/[:.]/g, '-');\n const status = dump.upstreamResponse?.status ?? dump.proxyResponse.status;\n const filename = `proxy-error-${ts}-${status}.json`;\n const filePath = join(dir, filename);\n const payload = {\n timestamp: new Date().toISOString(),\n ...dump,\n };\n redactAuth(payload.clientRequest?.headers);\n redactAuth(payload.upstreamRequest?.headers);\n writeFileSync(filePath, JSON.stringify(payload, null, 2));\n return filePath;\n}\n\nfunction redactAuth(headers: Record<string, string> | undefined): void {\n if (!headers) {\n return;\n }\n for (const key of Object.keys(headers)) {\n const lowerKey = key.toLowerCase();\n if (\n lowerKey === 'authorization' ||\n lowerKey === 'x-api-key' ||\n lowerKey === 'api-key' ||\n lowerKey === 'cookie'\n ) {\n headers[key] = '[REDACTED]';\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/server/proxy.ts"],"names":["resolve"],"mappings":";;;;;;;;AAiBA,SAAS,QAAQ,IAAA,EAAoB;AACnC,EAAA,OAAO,KAAK,kBAAA,CAAmB,OAAA,EAAS,EAAE,MAAA,EAAQ,OAAO,CAAA;AAC3D;AAEA,SAAS,YAAY,EAAA,EAAoB;AAKvC,EAAA,IAAI,KAAK,GAAA,EAAM;AACb,IAAA,OAAO,CAAA,EAAG,IAAA,CAAK,KAAA,CAAM,EAAE,CAAC,CAAA,EAAA,CAAA;AAAA,EAC1B;AACA,EAAA,IAAI,KAAK,GAAA,EAAO;AACd,IAAA,OAAO,CAAA,EAAA,CAAI,EAAA,GAAK,GAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAAA,EAClC;AACA,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAM,EAAA,GAAK,GAAK,CAAA;AACrC,EAAA,MAAM,OAAA,GAAU,IAAA,CAAK,KAAA,CAAO,EAAA,GAAK,MAAS,GAAI,CAAA;AAC9C,EAAA,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA,EAAI,MAAA,CAAO,OAAO,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AACvD;AAiCA,eAAsB,WAAW,OAAA,EAAmD;AAClF,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,WAAA;AAC7B,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,IAAA;AAC7B,EAAA,MAAM,IAAA,GAAO,QAAQ,IAAA,IAAQ,IAAA;AAC7B,EAAA,MAAM,SAAS,OAAA,CAAQ,MAAA,KAAW,IAAA,GAAO,IAAA,GAAQ,QAAQ,MAAA,IAAU,OAAA;AAGnE,EAAA,MAAM,kBAA4B,EAAC;AACnC,EAAA,SAAS,qBAAqB,EAAA,EAAY;AACxC,IAAA,eAAA,CAAgB,KAAK,EAAE,CAAA;AACvB,IAAA,IAAI,eAAA,CAAgB,SAAS,EAAA,EAAI;AAC/B,MAAA,eAAA,CAAgB,KAAA,EAAM;AAAA,IACxB;AACA,IAAA,OAAO,eAAA,CAAgB,OAAO,CAAC,GAAA,EAAK,QAAQ,GAAA,GAAM,GAAA,EAAK,CAAC,CAAA,GAAI,eAAA,CAAgB,MAAA;AAAA,EAC9E;AAGA,EAAA,MAAM,cAAA,uBAAqB,GAAA,EAAgE;AAK3F,EAAA,IAAI,aAAA,GAAuD,IAAA;AAE3D,EAAA,SAAS,cAAA,GAAiB;AACxB,IAAA,IAAI,cAAA,CAAe,SAAS,CAAA,EAAG;AAC7B,MAAA;AAAA,IACF;AACA,IAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,IAAA,CAAK,cAAA,CAAe,OAAA,EAAS,CAAA,CAAE,GAAA,CAAI,CAAC,GAAG,GAAG,CAAA,KAAM;AAClE,MAAA,MAAM,OAAA,GAAU,IAAA,CAAK,GAAA,EAAI,GAAI,GAAA,CAAI,SAAA;AACjC,MAAA,OAAO,CAAA,CAAA,EAAI,WAAA,CAAY,OAAO,CAAC,CAAA,CAAA,CAAA;AAAA,IACjC,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,OAAO,KAAA,CAAM,CAAA,eAAA,EAAa,MAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAE,CAAA;AAAA,EACtD;AAEA,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,GAAA,CAAI,QAAgB,GAAA,EAAqB;AACvC,MAAA,MAAM,EAAA,GAAK,CAAA,EAAG,IAAA,CAAK,GAAA,EAAK,CAAA,CAAA,EAAI,IAAA,CAAK,MAAA,EAAO,CAAE,SAAS,EAAE,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,CAAC,CAAC,CAAA,CAAA;AAClE,MAAA,cAAA,CAAe,GAAA,CAAI,IAAI,EAAE,MAAA,EAAQ,KAAK,SAAA,EAAW,IAAA,CAAK,GAAA,EAAI,EAAG,CAAA;AAC7D,MAAA,cAAA,EAAe;AACf,MAAA,IAAI,CAAC,aAAA,EAAe;AAClB,QAAA,aAAA,GAAgB,WAAA,CAAY,gBAAgB,GAAG,CAAA;AAAA,MACjD;AACA,MAAA,OAAO,EAAA;AAAA,IACT,CAAA;AAAA,IACA,OAAO,EAAA,EAAY;AACjB,MAAA,cAAA,CAAe,OAAO,EAAE,CAAA;AACxB,MAAA,IAAI,cAAA,CAAe,SAAS,CAAA,EAAG;AAC7B,QAAA,OAAA,CAAQ,MAAA,CAAO,MAAM,UAAU,CAAA;AAC/B,QAAA,IAAI,aAAA,EAAe;AACjB,UAAA,aAAA,CAAc,aAAa,CAAA;AAC3B,UAAA,aAAA,GAAgB,IAAA;AAAA,QAClB;AAAA,MACF;AAAA,IACF;AAAA,GACF;AAEA,EAAA,MAAM,WAAA,GAAc,EAAE,MAAA,EAAQ,EAAA,EAAI,KAAK,EAAA,EAAI,SAAA,EAAW,CAAA,EAAG,SAAA,EAAW,EAAA,EAAG;AAEvE,EAAA,MAAM,kBAQF,EAAC;AAEL,EAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,KAAA,IAAS,UAAA,CAAW,KAAA;AAC9C,EAAA,MAAM,cAAA,GAA+B,OAAO,KAAA,EAAO,IAAA,KAAS;AAC1D,IAAA,MAAM,GAAA,GACJ,OAAO,KAAA,KAAU,QAAA,GAAW,KAAA,GAAQ,iBAAiB,GAAA,GAAM,KAAA,CAAM,QAAA,EAAS,GAAI,KAAA,CAAM,GAAA;AACtF,IAAA,MAAM,UAAU,IAAA,EAAM,MAAA,IAAU,KAAA,EAAO,MAAA,IAAU,OAAO,WAAA,EAAY;AACpE,IAAA,MAAM,UAAA,GAAa,mBAAA,CAAoB,IAAA,EAAM,OAAO,CAAA;AACpD,IAAA,IAAI,OAAA,GAAmB,MAAA;AACvB,IAAA,IAAI,IAAA,EAAM,QAAQ,IAAA,EAAM;AACtB,MAAA,IAAI,OAAO,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU;AACjC,QAAA,OAAA,GAAU,YAAA,CAAa,KAAK,IAAI,CAAA;AAAA,MAClC,CAAA,MAAA,IAAW,IAAA,CAAK,IAAA,YAAgB,WAAA,EAAa;AAC3C,QAAA,OAAA,GAAU,aAAa,IAAI,WAAA,GAAc,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,MAC5D,CAAA,MAAA,IAAW,WAAA,CAAY,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AACxC,QAAA,OAAA,GAAU,aAAa,IAAI,WAAA,GAAc,MAAA,CAAO,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,MAC5D,CAAA,MAAO;AACL,QAAA,OAAA,GAAU,MAAA,CAAO,KAAK,IAAI,CAAA;AAAA,MAC5B;AAAA,IACF;AACA,IAAA,eAAA,CAAgB,UAAU,EAAE,GAAA,EAAK,QAAQ,OAAA,EAAS,UAAA,EAAY,MAAM,OAAA,EAAQ;AAE5E,IAAA,MAAM,IAAA,GAAO,MAAM,SAAA,CAAU,KAAA,EAAO,IAAI,CAAA;AAExC,IAAA,IAAI,CAAC,KAAK,EAAA,EAAI;AACZ,MAAA,MAAM,KAAA,GAAQ,KAAK,KAAA,EAAM;AACzB,MAAA,MAAM,OAAO,MAAM,KAAA,CAAM,MAAK,CAAE,KAAA,CAAM,MAAM,EAAE,CAAA;AAC9C,MAAA,eAAA,CAAgB,QAAA,GAAW;AAAA,QACzB,QAAQ,IAAA,CAAK,MAAA;AAAA,QACb,YAAY,IAAA,CAAK,UAAA;AAAA,QACjB,OAAA,EAAS,eAAA,CAAgB,IAAA,CAAK,OAAO,CAAA;AAAA,QACrC,IAAA,EAAM,aAAa,IAAI;AAAA,OACzB;AAAA,IACF,CAAA,MAAO;AACL,MAAA,eAAA,CAAgB,QAAA,GAAW,MAAA;AAAA,IAC7B;AACA,IAAA,OAAO,IAAA;AAAA,EACT,CAAA;AAEA,EAAA,MAAM,WAAW,oBAAA,CAAqB;AAAA,IACpC,gBAAgB,OAAA,CAAQ,cAAA;AAAA,IACxB,SAAS,OAAA,CAAQ,OAAA;AAAA,IACjB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,OAAO,OAAA,CAAQ,KAAA;AAAA,IACf,gBAAgB,OAAA,CAAQ,cAAA;AAAA,IACxB,WAAW,OAAA,CAAQ,SAAA;AAAA,IACnB,YAAY,OAAA,CAAQ,UAAA;AAAA,IACpB,kBAAkB,OAAA,CAAQ,gBAAA;AAAA,IAC1B,KAAA,EAAO,cAAA;AAAA,IACP,gBAAA,EAAkB,YAChB,IAAI,QAAA,CAAS,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,EAAE,OAAA,EAAS,WAAA,EAAY,EAAG,CAAA,EAAG;AAAA,MAChE,MAAA,EAAQ,GAAA;AAAA,MACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA;AAAmB,KAC/C,CAAA;AAAA,IACH,YAAA,EAAc,CAAC,KAAA,KAAU;AACvB,MAAA,MAAM,aAAa,WAAA,CAAY,SAAA,GAAY,KAAK,GAAA,EAAI,GAAI,YAAY,SAAA,GAAY,CAAA;AAChF,MAAA,MAAM,YAAA,GAAe,KAAA,CAAM,WAAA,GAAc,KAAA,CAAM,eAAe,KAAA,CAAM,YAAA;AACpE,MAAA,MAAM,KAAA,GAAQ;AAAA,QACZ,CAAA,MAAA,EAAS,MAAM,WAAW,CAAA,CAAA;AAAA,QAC1B,CAAA,MAAA,EAAS,MAAM,WAAW,CAAA,CAAA;AAAA,QAC1B,CAAA,OAAA,EAAU,MAAM,YAAY,CAAA,CAAA;AAAA,QAC5B,CAAA,OAAA,EAAU,MAAM,YAAY,CAAA,CAAA;AAAA,QAC5B,UAAU,YAAY,CAAA;AAAA,OACxB;AACA,MAAA,IAAI,KAAA,CAAM,sBAAsB,CAAA,EAAG;AACjC,QAAA,KAAA,CAAM,IAAA,CAAK,CAAA,eAAA,EAAkB,KAAA,CAAM,mBAAmB,CAAA,CAAE,CAAA;AAAA,MAC1D;AACA,MAAA,MAAM,GAAA,GAAM,qBAAqB,UAAU,CAAA;AAC3C,MAAA,MAAM,KAAA,GAAQ,GAAA,GAAM,CAAA,GAAI,UAAA,GAAa,GAAA,GAAM,CAAA;AAC3C,MAAA,MAAM,QAAQ,KAAA,GAAQ,GAAA,GAAM,UAAA,GAAa,KAAA,GAAQ,MAAM,UAAA,GAAa,UAAA;AACpE,MAAA,MAAM,KAAA,GAAQ,SAAA;AACd,MAAA,MAAM,MAAA,GAAS,CAAA,CAAA,EAAI,OAAA,iBAAQ,IAAI,IAAA,EAAM,CAAC,CAAA,UAAA,EAAa,KAAK,CAAA,EAAG,WAAA,CAAY,UAAU,CAAC,GAAG,KAAK,CAAA,KAAA,EAAQ,WAAA,CAAY,IAAA,CAAK,KAAA,CAAM,GAAG,CAAC,CAAC,CAAA,GAAA,EAAM,KAAA,CAAM,IAAA,CAAK,IAAI,CAAC,CAAA,CAAA,CAAA;AACpJ,MAAA,WAAA,CAAY,SAAA,GACV,MAAM,YAAA,GAAe,IAAA,IAAQ,eAAe,CAAA,GAAI,CAAA,yBAAA,EAAkB,MAAM,CAAA,CAAA,GAAK,MAAA;AAE/E,MAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,QAAA,OAAA,CAAQ,YAAA,CAAa;AAAA,UACnB,GAAG,KAAA;AAAA,UACH,MAAA,EAAQ,YAAY,MAAA,IAAU,MAAA;AAAA,UAC9B,GAAA,EAAK,YAAY,GAAA,IAAO,MAAA;AAAA,UACxB,YAAY,UAAA,IAAc;AAAA,SAC3B,CAAA;AAAA,MACH;AAAA,IACF;AAAA,GACD,CAAA;AAED,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,YAAA,CAAa,OAAO,KAAK,GAAA,KAAQ;AACnD,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,EAAI;AACvB,IAAA,WAAA,CAAY,MAAA,GAAS,IAAI,MAAA,IAAU,MAAA;AACnC,IAAA,WAAA,CAAY,GAAA,GAAM,IAAI,GAAA,IAAO,eAAA;AAC7B,IAAA,WAAA,CAAY,SAAA,GAAY,KAAA;AAExB,IAAA,MAAM,eAAA,GAAkB,IAAI,eAAA,EAAgB;AAC5C,IAAA,MAAM,YAAY,OAAA,CAAQ,SAAA;AAC1B,IAAA,IAAI,YAAA;AACJ,IAAA,IAAI,SAAA,IAAa,YAAY,CAAA,EAAG;AAC9B,MAAA,YAAA,GAAe,WAAW,MAAM;AAC9B,QAAA,MAAA,EAAQ,IAAA,CAAK,CAAA,2BAAA,EAA8B,SAAS,CAAA,YAAA,CAAc,CAAA;AAClE,QAAA,eAAA,CAAgB,KAAA,EAAM;AACtB,QAAA,GAAA,CAAI,OAAA,EAAQ;AACZ,QAAA,GAAA,CAAI,OAAA,EAAQ;AAAA,MACd,GAAG,SAAS,CAAA;AAAA,IACd;AAGA,IAAA,IAAI;AACF,MAAA,MAAM,aAAA,CAAc,KAAK,GAAA,EAAK;AAAA,QAC5B,QAAA;AAAA,QACA,IAAA;AAAA,QACA,MAAA;AAAA,QACA,MAAA,EAAQ,IAAI,MAAA,IAAU,MAAA;AAAA,QACtB,GAAA,EAAK,IAAI,GAAA,IAAO,GAAA;AAAA,QAChB,eAAA;AAAA,QACA,WAAA;AAAA,QACA,cAAA;AAAA,QACA,QAAQ,eAAA,CAAgB;AAAA,OACzB,CAAA;AAAA,IACH,SAAS,GAAA,EAAK;AACZ,MAAA,MAAA,EAAQ,KAAA,CAAM,iBAAiB,GAAG,CAAA;AAElC,MAAA,IAAI;AACF,QAAA,IAAI,CAAC,IAAI,WAAA,EAAa;AACpB,UAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,UAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,KAAA,EAAO,EAAE,OAAA,EAAS,uBAAA,EAAwB,EAAG,CAAC,CAAA;AAAA,QACzE;AAAA,MACF,CAAA,CAAA,MAAQ;AAAA,MAER;AAAA,IACF,CAAA,SAAE;AACA,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,YAAA,CAAa,YAAY,CAAA;AAAA,MAC3B;AAAA,IACF;AAAA,EACF,CAAC,CAAA;AAED,EAAA,OAAO,IAAI,OAAA,CAAQ,CAACA,QAAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,IAAA,EAAM,MAAM;AAC9B,MAAA,MAAM,cAAc,MAAM;AAExB,QAAA,MAAM,IAAA,GAAO,OAAO,OAAA,EAAQ;AAC5B,QAAA,OAAO,IAAA,CAAK,IAAA;AAAA,MACd,CAAA,GAAG;AACH,MAAA,MAAM,GAAA,GAAM,CAAA,OAAA,EAAU,IAAI,CAAA,CAAA,EAAI,UAAU,CAAA,CAAA;AACxC,MAAA,MAAA,EAAQ,GAAA,CAAI,CAAA,mBAAA,EAAsB,GAAG,CAAA,CAAE,CAAA;AACvC,MAAA,MAAA,EAAQ,GAAA,CAAI,CAAA,iBAAA,EAAoB,OAAA,CAAQ,cAAc,CAAA,CAAE,CAAA;AACxD,MAAA,MAAA,EAAQ,GAAA,CAAI,CAAA,cAAA,EAAiB,OAAA,CAAQ,OAAO,CAAA,CAAE,CAAA;AAC9C,MAAAA,QAAAA,CAAQ;AAAA,QACN,IAAA;AAAA,QACA,IAAA,EAAM,UAAA;AAAA,QACN,GAAA;AAAA,QACA,MAAA;AAAA,QACA,KAAA,EAAO,MACL,IAAI,OAAA,CAAQ,CAAC,GAAA,KAAQ;AACnB,UAAA,MAAA,CAAO,KAAA,CAAM,CAAC,GAAA,KAAQ;AACpB,YAAA,IAAI,GAAA,EAAK;AACP,cAAA,MAAA,EAAQ,IAAA,CAAK,yBAAyB,GAAG,CAAA;AAAA,YAC3C;AACA,YAAA,GAAA,EAAI;AAAA,UACN,CAAC,CAAA;AAAA,QACH,CAAC;AAAA,OACJ,CAAA;AAAA,IACH,CAAC,CAAA;AACD,IAAA,MAAA,CAAO,IAAA,CAAK,SAAS,MAAM,CAAA;AAAA,EAC7B,CAAC,CAAA;AACH;AAEA,eAAe,aAAA,CACb,GAAA,EACA,GAAA,EACA,IAAA,EAmBe;AACf,EAAA,IAAI,KAAK,IAAA,EAAM;AACb,IAAA,cAAA,CAAe,GAAG,CAAA;AAAA,EACpB;AAEA,EAAA,IAAI,GAAA,CAAI,WAAW,SAAA,EAAW;AAC5B,IAAA,GAAA,CAAI,UAAU,GAAG,CAAA;AACjB,IAAA,GAAA,CAAI,GAAA,EAAI;AACR,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,MAAA,GAAS,IAAI,MAAA,IAAU,KAAA;AAC7B,EAAA,MAAM,OAAA,GAAU,IAAI,GAAA,IAAO,GAAA;AAC3B,EAAA,MAAM,OAAA,GAAU,sBAAA,CAAuB,GAAA,CAAI,OAAO,CAAA;AAElD,EAAA,IAAI,IAAA;AACJ,EAAA,IAAI,MAAA,KAAW,KAAA,IAAS,MAAA,KAAW,MAAA,EAAQ;AACzC,IAAA,IAAA,GAAO,MAAM,iBAAiB,GAAG,CAAA;AAAA,EACnC;AAEA,EAAA,IAAI,CAAC,6BAAA,CAA8B,IAAA,CAAK,OAAO,CAAA,EAAG;AAChD,IAAA,GAAA,CAAI,SAAA,CAAU,GAAA,EAAK,EAAE,cAAA,EAAgB,oBAAoB,CAAA;AACzD,IAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,EAAE,OAAO,EAAE,OAAA,EAAS,CAAA,WAAA,EAAc,MAAM,CAAA,CAAA,EAAI,OAAO,CAAA,CAAA,EAAG,EAAG,CAAC,CAAA;AACjF,IAAA;AAAA,EACF;AAEA,EAAA,MAAM,eAAA,GAAkB,IAAA,GAAO,IAAA,CAAK,QAAA,CAAS,MAAM,CAAA,GAAI,MAAA;AAEvD,EAAA,MAAM,YAAA,GAAe,KAAK,GAAA,EAAI;AAC9B,EAAA,MAAM,SAAA,GAAY,IAAA,CAAK,cAAA,CAAe,GAAA,CAAI,QAAQ,OAAO,CAAA;AAGzD,EAAA,IAAI;AACF,IAAA,MAAM,WAAW,MAAM,IAAA,CAAK,QAAA,CAAS,CAAA,YAAA,EAAe,OAAO,CAAA,CAAA,EAAI;AAAA,MAC7D,MAAA;AAAA,MACA,OAAA;AAAA,MACA,IAAA,EAAM,IAAA,GAAO,IAAI,UAAA,CAAW,IAAI,CAAA,GAAI,KAAA,CAAA;AAAA,MACpC,QAAQ,IAAA,CAAK;AAAA,KACd,CAAA;AAGD,IAAA,MAAM,gBAAA,GAAmB,SAAS,IAAA,GAAO,MAAM,SAAS,KAAA,EAAM,CAAE,MAAK,GAAI,EAAA;AAGzE,IAAA,IAAA,CAAK,cAAA,CAAe,OAAO,SAAS,CAAA;AACpC,IAAA,IAAI,KAAK,MAAA,EAAQ;AACf,MAAA,IAAI,QAAA,CAAS,UAAU,GAAA,EAAK;AAC1B,QAAA,OAAA,CAAQ,MAAA,CAAO,KAAA;AAAA,UACb,CAAA,YAAA,EAAe,SAAS,MAAM,CAAA,GAAA,EAAM,YAAY,IAAA,CAAK,GAAA,EAAI,GAAI,YAAY,CAAC,CAAA;AAAA;AAAA,SAC5E;AAAA,MACF,CAAA,MAAA,IAAW,IAAA,CAAK,WAAA,CAAY,SAAA,EAAW;AACrC,QAAA,OAAA,CAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,QAAA,EAAW,IAAA,CAAK,YAAY,SAAS;AAAA,CAAI,CAAA;AAAA,MAChE,CAAA,MAAO;AACL,QAAA,OAAA,CAAQ,MAAA,CAAO,KAAA;AAAA,UACb,CAAA,YAAA,EAAe,SAAS,MAAM,CAAA,GAAA,EAAM,YAAY,IAAA,CAAK,GAAA,EAAI,GAAI,YAAY,CAAC,CAAA;AAAA;AAAA,SAC5E;AAAA,MACF;AAAA,IACF;AACA,IAAA,IAAI,QAAA,CAAS,UAAU,GAAA,EAAK;AAE1B,MAAA,IAAI;AACF,QAAA,MAAM,WAAW,aAAA,CAAc;AAAA,UAC7B,QAAQ,IAAA,CAAK,MAAA;AAAA,UACb,KAAK,IAAA,CAAK,GAAA;AAAA,UACV,aAAA,EAAe;AAAA,YACb,OAAA;AAAA,YACA,IAAA,EAAM,YAAA,CAAa,eAAA,IAAmB,EAAE;AAAA,WAC1C;AAAA,UACA,eAAA,EAAiB,KAAK,eAAA,CAAgB,OAAA;AAAA,UACtC,gBAAA,EAAkB,KAAK,eAAA,CAAgB,QAAA;AAAA,UACvC,aAAA,EAAe;AAAA,YACb,QAAQ,QAAA,CAAS,MAAA;AAAA,YACjB,OAAA,EAAS,eAAA,CAAgB,QAAA,CAAS,OAAO,CAAA;AAAA,YACzC,IAAA,EAAM,aAAa,gBAAgB;AAAA;AACrC,SACD,CAAA;AACD,QAAA,IAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,CAAA,uCAAA,EAA0C,QAAQ,CAAA,CAAE,CAAA;AAAA,MACzE,SAAS,OAAA,EAAS;AAChB,QAAA,IAAA,CAAK,MAAA,EAAQ,KAAA,CAAM,8CAAA,EAAgD,OAAO,CAAA;AAAA,MAC5E;AAAA,IACF;AAEA,IAAA,MAAM,aAAqC,EAAC;AAC5C,IAAA,QAAA,CAAS,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AACvC,MAAA,UAAA,CAAW,GAAG,CAAA,GAAI,KAAA;AAAA,IACpB,CAAC,CAAA;AACD,IAAA,IAAI,KAAK,IAAA,EAAM;AACb,MAAA,MAAA,CAAO,MAAA,CAAO,UAAA,EAAY,WAAA,EAAa,CAAA;AAAA,IACzC;AAEA,IAAA,GAAA,CAAI,SAAA,CAAU,QAAA,CAAS,MAAA,EAAQ,UAAU,CAAA;AAEzC,IAAA,IAAI,CAAC,SAAS,IAAA,EAAM;AAClB,MAAA,GAAA,CAAI,GAAA,EAAI;AACR,MAAA;AAAA,IACF;AAGA,IAAA,MAAM,YAAY,QAAA,CAAS,IAAA;AAC3B,IAAA,MAAM,UAAA,GAAa,QAAA,CAAS,OAAA,CAAQ,SAAS,CAAA;AAC7C,IAAA,UAAA,CAAW,KAAK,GAAG,CAAA;AACnB,IAAA,MAAM,IAAI,OAAA,CAAc,CAACA,QAAAA,EAAS,MAAA,KAAW;AAC3C,MAAA,UAAA,CAAW,IAAA,CAAK,OAAOA,QAAO,CAAA;AAC9B,MAAA,UAAA,CAAW,IAAA,CAAK,SAAS,MAAM,CAAA;AAC/B,MAAA,GAAA,CAAI,IAAA,CAAK,SAASA,QAAO,CAAA;AAAA,IAC3B,CAAC,CAAA;AAAA,EACH,SAAS,GAAA,EAAK;AACZ,IAAA,IAAA,CAAK,cAAA,CAAe,OAAO,SAAS,CAAA;AACpC,IAAA,MAAM,GAAA;AAAA,EACR;AACF;AAEA,SAAS,iBAAiB,GAAA,EAAuC;AAC/D,EAAA,OAAO,IAAI,OAAA,CAAQ,CAACA,QAAAA,EAAS,MAAA,KAAW;AACtC,IAAA,MAAM,SAAmB,EAAC;AAC1B,IAAA,GAAA,CAAI,EAAA,CAAG,MAAA,EAAQ,CAAC,KAAA,KAAU;AACxB,MAAA,MAAM,GAAA,GAAc,KAAA;AACpB,MAAA,MAAA,CAAO,KAAK,GAAG,CAAA;AAAA,IACjB,CAAC,CAAA;AACD,IAAA,GAAA,CAAI,EAAA,CAAG,OAAO,MAAMA,QAAAA,CAAQ,OAAO,MAAA,CAAO,MAAM,CAAC,CAAC,CAAA;AAClD,IAAA,GAAA,CAAI,EAAA,CAAG,SAAS,MAAM,CAAA;AAAA,EACxB,CAAC,CAAA;AACH;AAEA,SAAS,uBAAuB,OAAA,EAA6D;AAC3F,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,IAAI,SAAS,IAAA,EAAM;AACjB,MAAA;AAAA,IACF;AACA,IAAA,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,CAAA,GAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,GAAI,KAAA,CAAM,IAAA,CAAK,IAAI,CAAA,GAAI,OAAO,KAAK,CAAA;AAAA,EACjF;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,gBAAgB,OAAA,EAA0C;AACjE,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,OAAA,CAAQ,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AAC9B,IAAA,GAAA,CAAI,GAAG,CAAA,GAAI,KAAA;AAAA,EACb,CAAC,CAAA;AACD,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,eAAe,GAAA,EAA2B;AACjD,EAAA,MAAM,UAAU,WAAA,EAAY;AAC5B,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,OAAO,CAAA,EAAG;AAClD,IAAA,GAAA,CAAI,SAAA,CAAU,KAAK,KAAK,CAAA;AAAA,EAC1B;AACF;AAEA,SAAS,WAAA,GAAsC;AAC7C,EAAA,OAAO;AAAA,IACL,6BAAA,EAA+B,GAAA;AAAA,IAC/B,8BAAA,EAAgC,kBAAA;AAAA,IAChC,8BAAA,EACE,iHAAA;AAAA,IACF,+BAAA,EAAiC;AAAA,GACnC;AACF;AAEA,SAAS,aAAa,GAAA,EAAyC;AAC7D,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,OAAO,GAAA,IAAO,IAAA;AAAA,EAChB;AAEA,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,GAAA;AAAA,EACT;AACF;AAEA,SAAS,oBAAoB,WAAA,EAA8D;AACzF,EAAA,MAAM,MAA8B,EAAC;AACrC,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,OAAA,KAAY,WAAA,IAAe,WAAA,YAAuB,OAAA,EAAS;AACpE,IAAA,WAAA,CAAY,OAAA,CAAQ,CAAC,KAAA,EAAO,GAAA,KAAQ;AAClC,MAAA,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,CAAA,GAAI,KAAA;AAAA,IAC3B,CAAC,CAAA;AACD,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC9B,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,WAAA,EAAa;AACtC,MAAA,GAAA,CAAI,OAAO,GAAG,CAAA,CAAE,aAAa,CAAA,GAAI,OAAO,KAAK,CAAA;AAAA,IAC/C;AACA,IAAA,OAAO,GAAA;AAAA,EACT;AACA,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACtD,IAAA,GAAA,CAAI,GAAA,CAAI,WAAA,EAAa,CAAA,GAAI,OAAO,KAAK,CAAA;AAAA,EACvC;AACA,EAAA,OAAO,GAAA;AACT;AAEA,SAAS,cAAc,IAAA,EAYZ;AACT,EAAA,MAAM,GAAA,GAAM,OAAA,CAAQ,OAAA,CAAQ,GAAA,IAAO,MAAM,CAAA;AACzC,EAAA,SAAA,CAAU,GAAA,EAAK,EAAE,SAAA,EAAW,IAAA,EAAM,CAAA;AAClC,EAAA,MAAM,EAAA,GAAA,qBAAS,IAAA,EAAK,EAAE,aAAY,CAAE,OAAA,CAAQ,SAAS,GAAG,CAAA;AACxD,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,gBAAA,EAAkB,MAAA,IAAU,KAAK,aAAA,CAAc,MAAA;AACnE,EAAA,MAAM,QAAA,GAAW,CAAA,YAAA,EAAe,EAAE,CAAA,CAAA,EAAI,MAAM,CAAA,KAAA,CAAA;AAC5C,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,EAAK,QAAQ,CAAA;AACnC,EAAA,MAAM,OAAA,GAAU;AAAA,IACd,SAAA,EAAA,iBAAW,IAAI,IAAA,EAAK,EAAE,WAAA,EAAY;AAAA,IAClC,GAAG;AAAA,GACL;AACA,EAAA,UAAA,CAAW,OAAA,CAAQ,eAAe,OAAO,CAAA;AACzC,EAAA,UAAA,CAAW,OAAA,CAAQ,iBAAiB,OAAO,CAAA;AAC3C,EAAA,aAAA,CAAc,UAAU,IAAA,CAAK,SAAA,CAAU,OAAA,EAAS,IAAA,EAAM,CAAC,CAAC,CAAA;AACxD,EAAA,OAAO,QAAA;AACT;AAEA,SAAS,WAAW,OAAA,EAAmD;AACrE,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA;AAAA,EACF;AACA,EAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,EAAG;AACtC,IAAA,MAAM,QAAA,GAAW,IAAI,WAAA,EAAY;AACjC,IAAA,IACE,aAAa,eAAA,IACb,QAAA,KAAa,eACb,QAAA,KAAa,SAAA,IACb,aAAa,QAAA,EACb;AACA,MAAA,OAAA,CAAQ,GAAG,CAAA,GAAI,YAAA;AAAA,IACjB;AAAA,EACF;AACF","file":"index.js","sourcesContent":["// ==============================================================================\n// Helpers\n// ==============================================================================\n\n/**\n * Local HTTP proxy that exposes the Responses API and forwards translated\n * requests to the configured upstream API format.\n */\n\nimport http, { type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport { Readable } from 'node:stream';\nimport { mkdirSync, writeFileSync } from 'node:fs';\nimport { join, resolve } from 'node:path';\n\n// ==============================================================================\n// Helpers\n// ==============================================================================\nfunction fmtTime(date: Date): string {\n return date.toLocaleTimeString('en-US', { hour12: false });\n}\n\nfunction fmtDuration(ms: number): string {\n // ==============================================================================\n // Server\n // ==============================================================================\n\n if (ms < 1000) {\n return `${Math.round(ms)}ms`;\n }\n if (ms < 60000) {\n return `${(ms / 1000).toFixed(1)}s`;\n }\n const minutes = Math.floor(ms / 60000);\n const seconds = Math.round((ms % 60000) / 1000);\n return `${minutes}:${String(seconds).padStart(2, '0')}`;\n}\nimport { createResponsesFetch, type CreateResponsesFetchOptions } from '@codeproxy/core';\n\nexport interface StartProxyOptions extends Omit<CreateResponsesFetchOptions, 'passthroughFetch'> {\n /** Host to bind to. Defaults to `127.0.0.1`. */\n host?: string;\n /** Port to listen on. Defaults to `8787`; pass `0` for a random free port. */\n port?: number;\n /** Enable permissive CORS (useful for local browser dev). Defaults to true. */\n cors?: boolean;\n /** Optional logger. Defaults to `console`. Pass `null` to silence. */\n logger?: Pick<Console, 'log' | 'warn' | 'error'> | null;\n /** Optional callback to receive cache statistics after each request completes. */\n onCacheStats?: (stats: {\n cachedTokens: number;\n cacheCreationTokens: number;\n inputTokens: number;\n outputTokens: number;\n totalTokens: number;\n method?: string;\n url?: string;\n durationMs?: number;\n }) => void;\n}\n\nexport interface RunningProxy {\n host: string;\n port: number;\n url: string;\n server: Server;\n close: () => Promise<void>;\n}\n\nexport async function startProxy(options: StartProxyOptions): Promise<RunningProxy> {\n const host = options.host ?? '127.0.0.1';\n const port = options.port ?? 8787;\n const cors = options.cors ?? true;\n const logger = options.logger === null ? null : (options.logger ?? console);\n\n // Rolling average for request duration coloring (last 50 requests)\n const durationHistory: number[] = [];\n function updateRollingAverage(ms: number) {\n durationHistory.push(ms);\n if (durationHistory.length > 50) {\n durationHistory.shift();\n }\n return durationHistory.reduce((sum, val) => sum + val, 0) / durationHistory.length;\n }\n\n // Centralized status line for all active requests\n const activeRequests = new Map<string, { method: string; url: string; startTime: number }>();\n // ==============================================================================\n // Request Handler\n // ==============================================================================\n\n let statusTimerId: ReturnType<typeof setInterval> | null = null;\n\n function drawStatusLine() {\n if (activeRequests.size === 0) {\n return;\n }\n const parts = Array.from(activeRequests.entries()).map(([, req]) => {\n const elapsed = Date.now() - req.startTime;\n return `[${fmtDuration(elapsed)}]`;\n });\n process.stdout.write(`\\r\\x1b[K⏳ ${parts.join(', ')}`);\n }\n\n const requestTracker = {\n add(method: string, url: string): string {\n const id = `${Date.now()}:${Math.random().toString(36).slice(2, 8)}`;\n activeRequests.set(id, { method, url, startTime: Date.now() });\n drawStatusLine();\n if (!statusTimerId) {\n statusTimerId = setInterval(drawStatusLine, 150);\n }\n return id;\n },\n remove(id: string) {\n activeRequests.delete(id);\n if (activeRequests.size === 0) {\n process.stdout.write('\\r\\x1b[K');\n if (statusTimerId) {\n clearInterval(statusTimerId);\n statusTimerId = null;\n }\n }\n },\n };\n\n const requestInfo = { method: '', url: '', startTime: 0, resultLog: '' };\n\n const upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n } = {};\n\n const baseFetch = options.fetch ?? globalThis.fetch;\n const capturingFetch: typeof fetch = async (input, init) => {\n const url =\n typeof input === 'string' ? input : input instanceof URL ? input.toString() : input.url;\n const method = (init?.method ?? input?.method ?? 'GET').toUpperCase();\n const reqHeaders = headersInitToObject(init?.headers);\n let reqBody: unknown = undefined;\n if (init?.body != null) {\n if (typeof init.body === 'string') {\n reqBody = tryParseJson(init.body);\n } else if (init.body instanceof ArrayBuffer) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else if (ArrayBuffer.isView(init.body)) {\n reqBody = tryParseJson(new TextDecoder().decode(init.body));\n } else {\n reqBody = String(init.body);\n }\n }\n upstreamCapture.request = { url, method, headers: reqHeaders, body: reqBody };\n\n const resp = await baseFetch(input, init);\n\n if (!resp.ok) {\n const clone = resp.clone();\n const text = await clone.text().catch(() => '');\n upstreamCapture.response = {\n status: resp.status,\n statusText: resp.statusText,\n headers: headersToObject(resp.headers),\n body: tryParseJson(text),\n };\n } else {\n upstreamCapture.response = undefined;\n }\n return resp;\n };\n\n const apiFetch = createResponsesFetch({\n upstreamFormat: options.upstreamFormat,\n baseUrl: options.baseUrl,\n apiVersion: options.apiVersion,\n model: options.model,\n defaultHeaders: options.defaultHeaders,\n timeoutMs: options.timeoutMs,\n dropImages: options.dropImages,\n fallbackUpstream: options.fallbackUpstream,\n fetch: capturingFetch,\n passthroughFetch: async () =>\n new Response(JSON.stringify({ error: { message: 'Not found' } }), {\n status: 404,\n headers: { 'content-type': 'application/json' },\n }),\n onCacheStats: (stats) => {\n const durationMs = requestInfo.startTime ? Date.now() - requestInfo.startTime : 0;\n const billedTokens = stats.inputTokens + stats.outputTokens - stats.cachedTokens;\n const parts = [\n `total=${stats.totalTokens}`,\n `input=${stats.inputTokens}`,\n `output=${stats.outputTokens}`,\n `cached=${stats.cachedTokens}`,\n `billed=${billedTokens}`,\n ];\n if (stats.cacheCreationTokens > 0) {\n parts.push(`cache_creation=${stats.cacheCreationTokens}`);\n }\n const avg = updateRollingAverage(durationMs);\n const ratio = avg > 0 ? durationMs / avg : 1;\n const color = ratio < 0.8 ? '\\x1b[32m' : ratio < 1.5 ? '\\x1b[33m' : '\\x1b[31m';\n const reset = '\\x1b[0m';\n const logMsg = `[${fmtTime(new Date())}] -> 200 (${color}${fmtDuration(durationMs)}${reset} avg=${fmtDuration(Math.round(avg))}) [${parts.join(', ')}]`;\n requestInfo.resultLog =\n stats.cachedTokens < 1024 && billedTokens > 0 ? `⚠️ NO CACHE -- ${logMsg}` : logMsg;\n\n if (options.onCacheStats) {\n options.onCacheStats({\n ...stats,\n method: requestInfo.method || undefined,\n url: requestInfo.url || undefined,\n durationMs: durationMs || undefined,\n });\n }\n },\n });\n\n const server = http.createServer(async (req, res) => {\n const start = Date.now();\n requestInfo.method = req.method ?? 'POST';\n requestInfo.url = req.url ?? '/v1/responses';\n requestInfo.startTime = start;\n\n const abortController = new AbortController();\n const timeoutMs = options.timeoutMs;\n let timeoutTimer: ReturnType<typeof setTimeout> | undefined;\n if (timeoutMs && timeoutMs > 0) {\n timeoutTimer = setTimeout(() => {\n logger?.warn(`[timeout] request exceeded ${timeoutMs}ms, aborting`);\n abortController.abort();\n res.destroy();\n req.destroy();\n }, timeoutMs);\n }\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n await handleRequest(req, res, {\n apiFetch,\n cors,\n logger,\n method: req.method ?? 'POST',\n url: req.url ?? '/',\n upstreamCapture,\n requestInfo,\n requestTracker,\n signal: abortController.signal,\n });\n } catch (err) {\n logger?.error('[proxy-error]', err);\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n if (!res.headersSent) {\n res.writeHead(500, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: 'Internal server error' } }));\n }\n } catch {\n // ignore\n }\n } finally {\n if (timeoutTimer) {\n clearTimeout(timeoutTimer);\n }\n }\n });\n\n return new Promise((resolve, reject) => {\n server.listen(port, host, () => {\n const actualPort = (() => {\n // eslint-disable-next-line no-restricted-syntax -- net.Server.address() returns string | AddressInfo | null\n const addr = server.address() as { port: number } | null;\n return addr.port;\n })();\n const url = `http://${host}:${actualPort}`;\n logger?.log(`Proxy listening on ${url}`);\n logger?.log(`Upstream format: ${options.upstreamFormat}`);\n logger?.log(`Upstream URL: ${options.baseUrl}`);\n resolve({\n host,\n port: actualPort,\n url,\n server,\n close: () =>\n new Promise((res) => {\n server.close((err) => {\n if (err) {\n logger?.warn('Error closing server:', err);\n }\n res();\n });\n }),\n });\n });\n server.once('error', reject);\n });\n}\n\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n opts: {\n apiFetch: typeof fetch;\n cors: boolean;\n logger: Pick<Console, 'log' | 'warn' | 'error'> | null;\n method: string;\n url: string;\n upstreamCapture: {\n request?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n response?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n };\n requestInfo: { resultLog: string };\n requestTracker: { add: (method: string, url: string) => string; remove: (id: string) => void };\n signal?: AbortSignal;\n },\n): Promise<void> {\n if (opts.cors) {\n setCorsHeaders(res);\n }\n\n if (req.method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n\n const method = req.method ?? 'GET';\n const urlPath = req.url ?? '/';\n const headers = flattenIncomingHeaders(req.headers);\n\n let body: Buffer | undefined;\n if (method !== 'GET' && method !== 'HEAD') {\n body = await readIncomingBody(req);\n }\n\n if (!/^\\/v1\\/responses\\/?(?:\\?|$)/.test(urlPath)) {\n res.writeHead(404, { 'content-type': 'application/json' });\n res.end(JSON.stringify({ error: { message: `Not found: ${method} ${urlPath}` } }));\n return;\n }\n\n const requestBodyText = body ? body.toString('utf8') : undefined;\n\n const requestStart = Date.now();\n const requestId = opts.requestTracker.add(method, urlPath);\n\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const response = await opts.apiFetch(`http://local${urlPath}`, {\n method,\n headers,\n body: body ? new Uint8Array(body) : undefined,\n signal: opts.signal,\n });\n\n // Consume response body so onCacheStats fires (for streaming responses)\n const responseBodyText = response.body ? await response.clone().text() : '';\n\n // Remove from active requests and write final result\n opts.requestTracker.remove(requestId);\n if (opts.logger) {\n if (response.status >= 400) {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n } else if (opts.requestInfo.resultLog) {\n process.stdout.write(`\\r\\x1b[K${opts.requestInfo.resultLog}\\n`);\n } else {\n process.stdout.write(\n `\\r\\x1b[K<-- ${response.status} (${fmtDuration(Date.now() - requestStart)})\\n`,\n );\n }\n }\n if (response.status >= 400) {\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n const filePath = saveErrorDump({\n method: opts.method,\n url: opts.url,\n clientRequest: {\n headers,\n body: tryParseJson(requestBodyText ?? ''),\n },\n upstreamRequest: opts.upstreamCapture.request,\n upstreamResponse: opts.upstreamCapture.response,\n proxyResponse: {\n status: response.status,\n headers: headersToObject(response.headers),\n body: tryParseJson(responseBodyText),\n },\n });\n opts.logger?.error(`[proxy-failure] full exchange saved to ${filePath}`);\n } catch (dumpErr) {\n opts.logger?.error('[proxy-failure] failed to persist error dump', dumpErr);\n }\n }\n\n const outHeaders: Record<string, string> = {};\n response.headers.forEach((value, key) => {\n outHeaders[key] = value;\n });\n if (opts.cors) {\n Object.assign(outHeaders, corsHeaders());\n }\n\n res.writeHead(response.status, outHeaders);\n\n if (!response.body) {\n res.end();\n return;\n }\n\n // eslint-disable-next-line no-restricted-syntax -- fetch response.body is not typed as node stream\n const typedBody = response.body! as unknown as import('stream/web').ReadableStream<Uint8Array>;\n const nodeStream = Readable.fromWeb(typedBody);\n nodeStream.pipe(res);\n await new Promise<void>((resolve, reject) => {\n nodeStream.once('end', resolve);\n nodeStream.once('error', reject);\n res.once('close', resolve);\n });\n } catch (err) {\n opts.requestTracker.remove(requestId);\n throw err;\n }\n}\n\nfunction readIncomingBody(req: IncomingMessage): Promise<Buffer> {\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = [];\n req.on('data', (chunk) => {\n const buf: Buffer = chunk;\n chunks.push(buf);\n });\n req.on('end', () => resolve(Buffer.concat(chunks)));\n req.on('error', reject);\n });\n}\n\nfunction flattenIncomingHeaders(headers: IncomingMessage['headers']): Record<string, string> {\n const out: Record<string, string> = {};\n for (const [key, value] of Object.entries(headers)) {\n if (value == null) {\n continue;\n }\n out[key.toLowerCase()] = Array.isArray(value) ? value.join(', ') : String(value);\n }\n return out;\n}\n\nfunction headersToObject(headers: Headers): Record<string, string> {\n const out: Record<string, string> = {};\n headers.forEach((value, key) => {\n out[key] = value;\n });\n return out;\n}\n\nfunction setCorsHeaders(res: ServerResponse): void {\n const headers = corsHeaders();\n for (const [key, value] of Object.entries(headers)) {\n res.setHeader(key, value);\n }\n}\n\nfunction corsHeaders(): Record<string, string> {\n return {\n 'access-control-allow-origin': '*',\n 'access-control-allow-methods': 'GET,POST,OPTIONS',\n 'access-control-allow-headers':\n 'authorization,content-type,x-api-key,anthropic-version,anthropic-beta,anthropic-dangerous-direct-browser-access',\n 'access-control-expose-headers': 'content-type',\n };\n}\n\nfunction tryParseJson(str: string | undefined | null): unknown {\n if (!str) {\n return str ?? null;\n }\n // eslint-disable-next-line no-restricted-syntax -- try/catch needed for server-side HTTP error handling\n try {\n return JSON.parse(str);\n } catch {\n return str;\n }\n}\n\nfunction headersInitToObject(headersInit: HeadersInit | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!headersInit) {\n return out;\n }\n if (typeof Headers !== 'undefined' && headersInit instanceof Headers) {\n headersInit.forEach((value, key) => {\n out[key.toLowerCase()] = value;\n });\n return out;\n }\n if (Array.isArray(headersInit)) {\n for (const [key, value] of headersInit) {\n out[String(key).toLowerCase()] = String(value);\n }\n return out;\n }\n for (const [key, value] of Object.entries(headersInit)) {\n out[key.toLowerCase()] = String(value);\n }\n return out;\n}\n\nfunction saveErrorDump(dump: {\n method: string;\n url: string;\n clientRequest: { headers: Record<string, string>; body: unknown };\n upstreamRequest?: { url: string; method: string; headers: Record<string, string>; body: unknown };\n upstreamResponse?: {\n status: number;\n statusText: string;\n headers: Record<string, string>;\n body: unknown;\n };\n proxyResponse: { status: number; headers: Record<string, string>; body: unknown };\n}): string {\n const dir = resolve(process.cwd(), 'logs');\n mkdirSync(dir, { recursive: true });\n const ts = new Date().toISOString().replace(/[:.]/g, '-');\n const status = dump.upstreamResponse?.status ?? dump.proxyResponse.status;\n const filename = `proxy-error-${ts}-${status}.json`;\n const filePath = join(dir, filename);\n const payload = {\n timestamp: new Date().toISOString(),\n ...dump,\n };\n redactAuth(payload.clientRequest?.headers);\n redactAuth(payload.upstreamRequest?.headers);\n writeFileSync(filePath, JSON.stringify(payload, null, 2));\n return filePath;\n}\n\nfunction redactAuth(headers: Record<string, string> | undefined): void {\n if (!headers) {\n return;\n }\n for (const key of Object.keys(headers)) {\n const lowerKey = key.toLowerCase();\n if (\n lowerKey === 'authorization' ||\n lowerKey === 'x-api-key' ||\n lowerKey === 'api-key' ||\n lowerKey === 'cookie'\n ) {\n headers[key] = '[REDACTED]';\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codeproxy/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Local proxy server that converts any Chat Completions / Anthropic Messages API into OpenAI Responses API so Codex and Claude Code can use any LLM.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"openai",
|
|
@@ -69,6 +69,8 @@
|
|
|
69
69
|
"build": "tsup",
|
|
70
70
|
"typecheck": "tsc --noEmit",
|
|
71
71
|
"test": "vitest run",
|
|
72
|
-
"test:watch": "vitest"
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"lint": "eslint \"src/**/*.ts\" \"tests/**/*.ts\"",
|
|
74
|
+
"lint:fix": "eslint \"src/**/*.ts\" \"tests/**/*.ts\" --fix"
|
|
73
75
|
}
|
|
74
76
|
}
|