Skip to content

Commit a41fbc9

Browse files
committed
feat: support custom OpenAI headers
Some OpenAI-compatible relay/NewAPI providers sit behind WAF or Cloudflare rules that may block requests using the SDK default request headers. Allow users to configure additional HTTP headers, such as a custom User-Agent, without patching the extension source code. Changes: - add a top-level `headers` setting and normalize string header values - merge user and project headers with project-level precedence - pass configured headers to the OpenAI SDK via `defaultHeaders` - include headers in the shared client cache key - document the setting with a User-Agent example - add resolver coverage for header merging behavior Validation: - npx tsx --test src/tests/settings-and-notify.test.ts - npm run typecheck - npm run bundle
1 parent a971e07 commit a41fbc9

8 files changed

Lines changed: 106 additions & 29 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
"BASE_URL": "https://api.deepseek.com",
1414
"API_KEY": "sk-..."
1515
},
16+
"headers": {
17+
"User-Agent": "Mozilla/5.0 ..."
18+
},
1619
"thinkingEnabled": true,
1720
"reasoningEffort": "max"
1821
}
@@ -21,12 +24,14 @@
2124
## 主要功能
2225

2326
### **Skills**
27+
2428
Deep Code 支持 agent skills,允许您扩展助手的能力:
2529

2630
- **User-level Skills**:从 `~/.agents/skills/` 目录中发现并激活 skills。
2731
- **Project-level Skills**:从 `./.agents/skills/` 目录中加载项目专属 skills,并兼容旧的 `./.deepcode/skills/` 目录。
2832

2933
### **为 DeepSeek 优化**
34+
3035
- 专门为 DeepSeek 模型性能调优。
3136
- 通过使用[上下文缓存](https://api-docs.deepseek.com/guides/kv_cache)来降低成本。
3237
- 原生支持[思考模式](https://api-docs.deepseek.com/guides/thinking_mode)和思考强度控制。
@@ -83,6 +88,7 @@ Deep Code支持多模态,但目前deepseek-v4不支持多模态。有些模型
8388
```
8489

8590
## 获取帮助
91+
8692
- 在 GitHub Issues 上报告错误或请求功能 (https://github.com/lessweb/deepcode/issues)
8793

8894
## 支持我们

README_cn.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
"BASE_URL": "https://api.deepseek.com",
1414
"API_KEY": "sk-..."
1515
},
16+
"headers": {
17+
"User-Agent": "Mozilla/5.0 ..."
18+
},
1619
"thinkingEnabled": true,
1720
"reasoningEffort": "max"
1821
}
@@ -21,12 +24,14 @@
2124
## 主要功能
2225

2326
### **Skills**
27+
2428
Deep Code 支持 agent skills,允许您扩展助手的能力:
2529

2630
- **User-level Skills**:从 `~/.agents/skills/` 目录中发现并激活 skills。
2731
- **Project-level Skills**:从 `./.agents/skills/` 目录中加载项目专属 skills,并兼容旧的 `./.deepcode/skills/` 目录。
2832

2933
### **为 DeepSeek 优化**
34+
3035
- 专门为 DeepSeek 模型性能调优。
3136
- 通过使用[上下文缓存](https://api-docs.deepseek.com/guides/kv_cache)来降低成本。
3237
- 原生支持[思考模式](https://api-docs.deepseek.com/guides/thinking_mode)和思考强度控制。
@@ -83,6 +88,7 @@ Deep Code支持多模态,但目前deepseek-v4不支持多模态。有些模型
8388
```
8489

8590
## 获取帮助
91+
8692
- 在 GitHub Issues 上报告错误或请求功能 (https://github.com/lessweb/deepcode/issues)
8793

8894
## 支持我们

README_en.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Create `~/.deepcode/settings.json` with:
1313
"BASE_URL": "https://api.deepseek.com",
1414
"API_KEY": "sk-..."
1515
},
16+
"headers": {
17+
"User-Agent": "Mozilla/5.0 ..."
18+
},
1619
"thinkingEnabled": true,
1720
"reasoningEffort": "max"
1821
}
@@ -21,12 +24,14 @@ Create `~/.deepcode/settings.json` with:
2124
## Key Features
2225

2326
### **Skills**
27+
2428
Deep Code supports agent skills that allows you to extend the assistant's capabilities:
2529

2630
- **User-level Skills**: discovered and activated from `~/.agents/skills/`.
2731
- **Project-level Skills**: loaded from `./.agents/skills/` for project-specific workflows, with legacy `./.deepcode/skills/` compatibility.
2832

2933
### **Optimized for DeepSeek**
34+
3035
- Specifically tuned for DeepSeek model performance.
3136
- Reduce costs by using [Context Caching](https://api-docs.deepseek.com/guides/kv_cache).
3237
- Natively supports [Thinking Mode](https://api-docs.deepseek.com/guides/thinking_mode) and Thinking Effort Control.
@@ -84,4 +89,5 @@ Yes. Just set `env.BASE_URL` in `~/.deepcode/settings.json` to an OpenAI-compati
8489
```
8590

8691
## Getting Help
92+
8793
- Report bugs or request features on GitHub Issues (https://github.com/lessweb/deepcode/issues)

docs/guide.md

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -153,29 +153,29 @@ The extension uses VS Code's Webview API for bidirectional communication between
153153

154154
### Frontend -> Backend Message Types
155155

156-
| Type | Payload | Description |
157-
|------|---------|-------------|
158-
| `ready` | `{}` | Webview signals it is ready to receive initial state |
159-
| `requestSkills` | `{}` | Request the currently available skill list |
160-
| `userPrompt` | `{ prompt: string, skills?: SkillInfo[] }` | Submit a prompt with optional selected skills |
161-
| `interrupt` | `{}` | Interrupt the active session |
162-
| `createNewSession` | `{}` | Start a new session |
163-
| `selectSession` | `{ sessionId: string }` | Load a specific session |
164-
| `backToList` | `{}` | Return to the session list view |
156+
| Type | Payload | Description |
157+
| ------------------ | ------------------------------------------ | ---------------------------------------------------- |
158+
| `ready` | `{}` | Webview signals it is ready to receive initial state |
159+
| `requestSkills` | `{}` | Request the currently available skill list |
160+
| `userPrompt` | `{ prompt: string, skills?: SkillInfo[] }` | Submit a prompt with optional selected skills |
161+
| `interrupt` | `{}` | Interrupt the active session |
162+
| `createNewSession` | `{}` | Start a new session |
163+
| `selectSession` | `{ sessionId: string }` | Load a specific session |
164+
| `backToList` | `{}` | Return to the session list view |
165165

166166
### Backend -> Frontend Message Types
167167

168-
| Type | Payload | Description |
169-
|------|---------|-------------|
170-
| `initializeEmpty` | `{ sessions, status }` | Show an empty composer state |
171-
| `loadSession` | `{ sessionId, summary, status, sessions, messages }` | Load a session and its visible messages |
172-
| `showSessionsList` | `{ sessions }` | Refresh the session dropdown data |
173-
| `skillsList` | `{ skills }` | Update the available skill list |
174-
| `sessionStatus` | `{ sessionId, status }` | Update the status of the current session |
175-
| `userMessage` | `{ content }` | Append the raw user text bubble |
176-
| `assistant` | `{ html }` | Append a direct assistant HTML message, typically for failures |
177-
| `appendMessage` | `{ message, shouldConnect }` | Append a structured session message generated during execution |
178-
| `loading` | `{ value: boolean }` | Toggle the loading indicator |
168+
| Type | Payload | Description |
169+
| ------------------ | ---------------------------------------------------- | -------------------------------------------------------------- |
170+
| `initializeEmpty` | `{ sessions, status }` | Show an empty composer state |
171+
| `loadSession` | `{ sessionId, summary, status, sessions, messages }` | Load a session and its visible messages |
172+
| `showSessionsList` | `{ sessions }` | Refresh the session dropdown data |
173+
| `skillsList` | `{ skills }` | Update the available skill list |
174+
| `sessionStatus` | `{ sessionId, status }` | Update the status of the current session |
175+
| `userMessage` | `{ content }` | Append the raw user text bubble |
176+
| `assistant` | `{ html }` | Append a direct assistant HTML message, typically for failures |
177+
| `appendMessage` | `{ message, shouldConnect }` | Append a structured session message generated during execution |
178+
| `loading` | `{ value: boolean }` | Toggle the loading indicator |
179179

180180
### Communication Flow Overview
181181

@@ -228,6 +228,9 @@ Persist state and notify the webview
228228
"BASE_URL": "https://api.deepseek.com",
229229
"MODEL": "deepseek-v4-pro"
230230
},
231+
"headers": {
232+
"User-Agent": "Mozilla/5.0 ..."
233+
},
231234
"thinkingEnabled": true,
232235
"reasoningEffort": "max",
233236
"notify": "~/.deepcode/notify.sh"
@@ -236,14 +239,15 @@ Persist state and notify the webview
236239

237240
### Configuration Options
238241

239-
| Field | Type | Required | Default | Description |
240-
|-------|------|----------|---------|-------------|
241-
| `env.API_KEY` | string | Yes | - | API key for the configured provider |
242-
| `env.BASE_URL` | string | No | `https://api.deepseek.com` | Base URL for a DeepSeek or other OpenAI-compatible endpoint |
243-
| `env.MODEL` | string | No | `deepseek-v4-pro` | Model identifier passed to `chat.completions.create()` |
244-
| `thinkingEnabled` | boolean | No | `true` for `deepseek-v4-flash` and `deepseek-v4-pro`; otherwise `false` | Enables the optional `thinking` request field when set to `true` |
245-
| `reasoningEffort` | `"high"` or `"max"` | No | `"max"` | Controls DeepSeek thinking strength via `reasoning_effort` when thinking mode is enabled |
246-
| `notify` | string | No | - | Executable script path triggered when a task ends in `completed` or `failed`, with `DURATION` set to the elapsed seconds |
242+
| Field | Type | Required | Default | Description |
243+
| ----------------- | ------------------- | -------- | ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
244+
| `env.API_KEY` | string | Yes | - | API key for the configured provider |
245+
| `env.BASE_URL` | string | No | `https://api.deepseek.com` | Base URL for a DeepSeek or other OpenAI-compatible endpoint |
246+
| `env.MODEL` | string | No | `deepseek-v4-pro` | Model identifier passed to `chat.completions.create()` |
247+
| `headers` | object | No | `{}` | Extra HTTP headers passed to the OpenAI-compatible SDK client, useful for providers that require custom headers such as `User-Agent` |
248+
| `thinkingEnabled` | boolean | No | `true` for `deepseek-v4-flash` and `deepseek-v4-pro`; otherwise `false` | Enables the optional `thinking` request field when set to `true` |
249+
| `reasoningEffort` | `"high"` or `"max"` | No | `"max"` | Controls DeepSeek thinking strength via `reasoning_effort` when thinking mode is enabled |
250+
| `notify` | string | No | - | Executable script path triggered when a task ends in `completed` or `failed`, with `DURATION` set to the elapsed seconds |
247251

248252
---
249253

src/common/openai-client.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
5151
};
5252
}
5353

54-
const cacheKey = `${settings.apiKey}::${settings.baseURL}`;
54+
const cacheKey = `${settings.apiKey}::${settings.baseURL}::${JSON.stringify(settings.headers)}`;
5555
if (cachedOpenAI && cachedOpenAIKey === cacheKey) {
5656
return {
5757
client: cachedOpenAI,
@@ -72,6 +72,7 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
7272
cachedOpenAI = new OpenAI({
7373
apiKey: settings.apiKey,
7474
baseURL: settings.baseURL || undefined,
75+
defaultHeaders: settings.headers,
7576
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7677
fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }),
7778
});

src/extension.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,7 @@ class DeepcodingViewProvider implements vscode.WebviewViewProvider {
424424
const client = new OpenAI({
425425
apiKey,
426426
baseURL: baseURL || undefined,
427+
defaultHeaders: settings.headers,
427428
});
428429

429430
return {

src/settings.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ export type EnabledSkillsSettings = Record<string, boolean>;
4747

4848
export type DeepcodingSettings = {
4949
env?: DeepcodingEnv;
50+
headers?: Record<string, string | undefined>;
5051
model?: string;
5152
temperature?: number;
5253
thinkingEnabled?: boolean;
@@ -62,6 +63,7 @@ export type DeepcodingSettings = {
6263

6364
export type ResolvedDeepcodingSettings = {
6465
env: Record<string, string>;
66+
headers: Record<string, string>;
6567
apiKey?: string;
6668
baseURL: string;
6769
model: string;
@@ -230,6 +232,22 @@ function normalizeEnv(env: DeepcodingSettings["env"]): Record<string, string> {
230232
return result;
231233
}
232234

235+
function normalizeHeaders(headers: unknown): Record<string, string> {
236+
const result: Record<string, string> = {};
237+
if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
238+
return result;
239+
}
240+
241+
for (const [key, value] of Object.entries(headers)) {
242+
const headerName = key.trim();
243+
if (!headerName || typeof value !== "string") {
244+
continue;
245+
}
246+
result[headerName] = value;
247+
}
248+
return result;
249+
}
250+
233251
export function collectDeepcodeEnv(processEnv: SettingsProcessEnv = process.env): Record<string, string> {
234252
const result: Record<string, string> = {};
235253
for (const [key, value] of Object.entries(processEnv)) {
@@ -322,6 +340,10 @@ export function resolveSettingsSources(
322340
...projectEnv,
323341
...systemEnv,
324342
};
343+
const headers = {
344+
...normalizeHeaders(userSettings?.headers),
345+
...normalizeHeaders(projectSettings?.headers),
346+
};
325347

326348
const model =
327349
trimString(systemEnv.MODEL) ||
@@ -380,6 +402,7 @@ export function resolveSettingsSources(
380402

381403
return {
382404
env,
405+
headers,
383406
apiKey: trimString(env.API_KEY) || undefined,
384407
baseURL: trimString(env.BASE_URL) || defaults.baseURL,
385408
model,

src/tests/settings-and-notify.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,36 @@ test("resolveSettingsSources applies user, project, and DEEPCODE environment pre
191191
assert.equal(resolved.env.WEBHOOK, "system-webhook");
192192
});
193193

194+
test("resolveSettingsSources merges headers with project precedence", () => {
195+
const resolved = resolveSettingsSources(
196+
{
197+
headers: {
198+
"User-Agent": "user-agent",
199+
"X-User": "1",
200+
"X-Ignore": undefined,
201+
},
202+
},
203+
{
204+
headers: {
205+
"User-Agent": "project-agent",
206+
"X-Project": "2",
207+
"X-Number": 123 as never,
208+
},
209+
},
210+
{
211+
model: "default-model",
212+
baseURL: "https://default.example.com",
213+
},
214+
TEST_PROCESS_ENV
215+
);
216+
217+
assert.deepEqual(resolved.headers, {
218+
"User-Agent": "project-agent",
219+
"X-User": "1",
220+
"X-Project": "2",
221+
});
222+
});
223+
194224
test("resolveSettingsSources merges permission settings", () => {
195225
const resolved = resolveSettingsSources(
196226
{

0 commit comments

Comments
 (0)