Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"BASE_URL": "https://api.deepseek.com",
"API_KEY": "sk-..."
},
"headers": {
"User-Agent": "Mozilla/5.0 ..."
},
"thinkingEnabled": true,
"reasoningEffort": "max"
}
Expand Down
3 changes: 3 additions & 0 deletions README_cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
"BASE_URL": "https://api.deepseek.com",
"API_KEY": "sk-..."
},
"headers": {
"User-Agent": "Mozilla/5.0 ..."
},
"thinkingEnabled": true,
"reasoningEffort": "max"
}
Expand Down
3 changes: 3 additions & 0 deletions README_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Create `~/.deepcode/settings.json` with:
"BASE_URL": "https://api.deepseek.com",
"API_KEY": "sk-..."
},
"headers": {
"User-Agent": "Mozilla/5.0 ..."
},
"thinkingEnabled": true,
"reasoningEffort": "max"
}
Expand Down
4 changes: 4 additions & 0 deletions docs/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,9 @@ Persist state and notify the webview
"BASE_URL": "https://api.deepseek.com",
"MODEL": "deepseek-v4-pro"
},
"headers": {
"User-Agent": "Mozilla/5.0 ..."
},
"thinkingEnabled": true,
"reasoningEffort": "max",
"notify": "~/.deepcode/notify.sh"
Expand All @@ -241,6 +244,7 @@ Persist state and notify the webview
| `env.API_KEY` | string | Yes | - | API key for the configured provider |
| `env.BASE_URL` | string | No | `https://api.deepseek.com` | Base URL for a DeepSeek or other OpenAI-compatible endpoint |
| `env.MODEL` | string | No | `deepseek-v4-pro` | Model identifier passed to `chat.completions.create()` |
| `headers` | object | No | `{}` | Extra HTTP headers passed to the OpenAI-compatible SDK client, useful for providers that require custom headers such as `User-Agent` |
| `thinkingEnabled` | boolean | No | `true` for `deepseek-v4-flash` and `deepseek-v4-pro`; otherwise `false` | Enables the optional `thinking` request field when set to `true` |
| `reasoningEffort` | `"high"` or `"max"` | No | `"max"` | Controls DeepSeek thinking strength via `reasoning_effort` when thinking mode is enabled |
| `notify` | string | No | - | Executable script path triggered when a task ends in `completed` or `failed`, with `DURATION` set to the elapsed seconds |
Expand Down
3 changes: 2 additions & 1 deletion src/common/openai-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
};
}

const cacheKey = `${settings.apiKey}::${settings.baseURL}`;
const cacheKey = `${settings.apiKey}::${settings.baseURL}::${JSON.stringify(settings.headers)}`;
if (cachedOpenAI && cachedOpenAIKey === cacheKey) {
return {
client: cachedOpenAI,
Expand All @@ -72,6 +72,7 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
cachedOpenAI = new OpenAI({
apiKey: settings.apiKey,
baseURL: settings.baseURL || undefined,
defaultHeaders: settings.headers,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }),
});
Expand Down
1 change: 1 addition & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,7 @@ class DeepcodingViewProvider implements vscode.WebviewViewProvider {
const client = new OpenAI({
apiKey,
baseURL: baseURL || undefined,
defaultHeaders: settings.headers,
});

return {
Expand Down
23 changes: 23 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export type EnabledSkillsSettings = Record<string, boolean>;

export type DeepcodingSettings = {
env?: DeepcodingEnv;
headers?: Record<string, string | undefined>;
model?: string;
temperature?: number;
thinkingEnabled?: boolean;
Expand All @@ -62,6 +63,7 @@ export type DeepcodingSettings = {

export type ResolvedDeepcodingSettings = {
env: Record<string, string>;
headers: Record<string, string>;
apiKey?: string;
baseURL: string;
model: string;
Expand Down Expand Up @@ -230,6 +232,22 @@ function normalizeEnv(env: DeepcodingSettings["env"]): Record<string, string> {
return result;
}

function normalizeHeaders(headers: unknown): Record<string, string> {
const result: Record<string, string> = {};
if (!headers || typeof headers !== "object" || Array.isArray(headers)) {
return result;
}

for (const [key, value] of Object.entries(headers)) {
const headerName = key.trim();
if (!headerName || typeof value !== "string") {
continue;
}
result[headerName] = value;
}
return result;
}

export function collectDeepcodeEnv(processEnv: SettingsProcessEnv = process.env): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(processEnv)) {
Expand Down Expand Up @@ -322,6 +340,10 @@ export function resolveSettingsSources(
...projectEnv,
...systemEnv,
};
const headers = {
...normalizeHeaders(userSettings?.headers),
...normalizeHeaders(projectSettings?.headers),
};

const model =
trimString(systemEnv.MODEL) ||
Expand Down Expand Up @@ -380,6 +402,7 @@ export function resolveSettingsSources(

return {
env,
headers,
apiKey: trimString(env.API_KEY) || undefined,
baseURL: trimString(env.BASE_URL) || defaults.baseURL,
model,
Expand Down
30 changes: 30 additions & 0 deletions src/tests/settings-and-notify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,36 @@ test("resolveSettingsSources applies user, project, and DEEPCODE environment pre
assert.equal(resolved.env.WEBHOOK, "system-webhook");
});

test("resolveSettingsSources merges headers with project precedence", () => {
const resolved = resolveSettingsSources(
{
headers: {
"User-Agent": "user-agent",
"X-User": "1",
"X-Ignore": undefined,
},
},
{
headers: {
"User-Agent": "project-agent",
"X-Project": "2",
"X-Number": 123 as never,
},
},
{
model: "default-model",
baseURL: "https://default.example.com",
},
TEST_PROCESS_ENV
);

assert.deepEqual(resolved.headers, {
"User-Agent": "project-agent",
"X-User": "1",
"X-Project": "2",
});
});

test("resolveSettingsSources merges permission settings", () => {
const resolved = resolveSettingsSources(
{
Expand Down