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
17 changes: 16 additions & 1 deletion packages/core/src/common/openai-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as os from "os";
import * as path from "path";
import OpenAI from "openai";
import { Agent, fetch as undiciFetch } from "undici";
import type { HeadersInit } from "undici";
import { resolveCurrentSettings } from "../settings";

// Custom undici Agent with a 180-second keepAlive timeout. The default
Expand All @@ -12,6 +13,19 @@ import { resolveCurrentSettings } from "../settings";
// keep connections reusable for three minutes after the last request.
const keepAliveAgent = new Agent({ keepAliveTimeout: 180_000 });

const DEFAULT_USER_AGENT = `deepcode-cli (Node.js ${process.version})`;

// Inject a default User-Agent header into outgoing requests. Some CDNs and
// WAFs (e.g. Cloudflare) block requests that arrive without a User-Agent as a
// basic anti-bot measure. (#177)
export function buildClientHeaders(initHeaders?: HeadersInit): Headers {
const headers = new Headers(initHeaders);
if (!headers.has("User-Agent")) {
headers.set("User-Agent", DEFAULT_USER_AGENT);
}
return headers;
}

// Module-level cache for the OpenAI client instance. The client itself is
// a stateless fetch wrapper, so it is safe to share across calls as long as
// the apiKey + baseURL stay the same. Model, thinking-mode and other
Expand Down Expand Up @@ -73,7 +87,8 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
apiKey: settings.apiKey,
baseURL: settings.baseURL || undefined,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }),
fetch: (url: any, init: any) =>
undiciFetch(url, { ...init, headers: buildClientHeaders(init?.headers), dispatcher: keepAliveAgent }),
});
cachedOpenAIKey = cacheKey;

Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/tests/openai-client.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildClientHeaders } from "../common/openai-client";

test("buildClientHeaders injects a default User-Agent when none is set", () => {
const headers = buildClientHeaders(undefined);
assert.match(headers.get("User-Agent") ?? "", /^deepcode-cli \(Node\.js v\d+\.\d+\.\d+\)$/);
});

test("buildClientHeaders preserves existing headers and a custom User-Agent", () => {
const headers = buildClientHeaders({ Authorization: "Bearer sk-test", "User-Agent": "custom-agent" });
assert.equal(headers.get("Authorization"), "Bearer sk-test");
assert.equal(headers.get("User-Agent"), "custom-agent");
});

test("buildClientHeaders accepts tuple-array headers and keeps its entries", () => {
const headers = buildClientHeaders([["Content-Type", "application/json"]]);
assert.equal(headers.get("Content-Type"), "application/json");
assert.match(headers.get("User-Agent") ?? "", /^deepcode-cli /);
});