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
4 changes: 3 additions & 1 deletion packages/core/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}::${settings.timeoutMs ?? ""}::${settings.maxRetries ?? ""}`;
if (cachedOpenAI && cachedOpenAIKey === cacheKey) {
return {
client: cachedOpenAI,
Expand All @@ -72,6 +72,8 @@ export function createOpenAIClient(projectRoot: string = process.cwd()): {
cachedOpenAI = new OpenAI({
apiKey: settings.apiKey,
baseURL: settings.baseURL || undefined,
timeout: settings.timeoutMs,
maxRetries: settings.maxRetries,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
fetch: (url: any, init: any) => undiciFetch(url, { ...init, dispatcher: keepAliveAgent }),
});
Expand Down
55 changes: 55 additions & 0 deletions packages/core/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export type DeepcodingSettings = {
autoCompactWindow?: number | string;
model?: string;
temperature?: number;
timeoutMs?: number;
maxRetries?: number;
thinkingEnabled?: boolean;
reasoningEffort?: ReasoningEffort;
debugLogEnabled?: boolean;
Expand All @@ -106,6 +108,8 @@ export type ResolvedDeepcodingSettings = {
contextWindow: number;
autoCompactWindow: number;
temperature?: number;
timeoutMs?: number;
maxRetries?: number;
thinkingEnabled: boolean;
reasoningEffort: ReasoningEffort;
debugLogEnabled: boolean;
Expand Down Expand Up @@ -199,6 +203,52 @@ function trimString(value: unknown): string {
return typeof value === "string" ? value.trim() : "";
}

function parsePositiveInt(value: unknown): number | undefined {
if (typeof value === "number" && Number.isSafeInteger(value) && value > 0) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value.trim());
if (Number.isSafeInteger(parsed) && parsed > 0) {
return parsed;
}
}
return undefined;
}

function parseNonNegativeInt(value: unknown): number | undefined {
if (typeof value === "number" && Number.isSafeInteger(value) && value >= 0) {
return value;
}
if (typeof value === "string" && value.trim()) {
const parsed = Number(value.trim());
if (Number.isSafeInteger(parsed) && parsed >= 0) {
return parsed;
}
}
return undefined;
}

function firstPositiveInt(...values: unknown[]): number | undefined {
for (const value of values) {
const parsed = parsePositiveInt(value);
if (parsed !== undefined) {
return parsed;
}
}
return undefined;
}

function firstNonNegativeInt(...values: unknown[]): number | undefined {
for (const value of values) {
const parsed = parseNonNegativeInt(value);
if (parsed !== undefined) {
return parsed;
}
}
return undefined;
}

const VALID_PERMISSION_SCOPES = new Set<PermissionScope>([
"read-in-cwd",
"read-out-cwd",
Expand Down Expand Up @@ -561,6 +611,9 @@ export function resolveSettingsSources(
parseTemperature(userSettings?.temperature) ??
parseTemperature(userEnv.TEMPERATURE);

const timeoutMs = firstPositiveInt(systemEnv.TIMEOUT_MS, projectSettings?.timeoutMs, userSettings?.timeoutMs);
const maxRetries = firstNonNegativeInt(systemEnv.MAX_RETRIES, projectSettings?.maxRetries, userSettings?.maxRetries);

const debugLogEnabled =
parseBoolean(systemEnv.DEBUG_LOG_ENABLED) ??
parseBoolean(projectSettings?.debugLogEnabled) ??
Expand Down Expand Up @@ -593,6 +646,8 @@ export function resolveSettingsSources(
contextWindow,
autoCompactWindow,
temperature,
timeoutMs,
maxRetries,
thinkingEnabled,
reasoningEffort,
debugLogEnabled,
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/tests/settings-and-notify.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ import { applyModelConfigSelection, resolveSettings, resolveSettingsSources } fr

const TEST_PROCESS_ENV = {};

test("resolveSettings reads timeoutMs and maxRetries (#13)", () => {
const resolved = resolveSettings(
{ timeoutMs: 120000, maxRetries: 3, env: { API_KEY: "sk-test" } },
{ model: "default-model", baseURL: "https://default.example.com" },
TEST_PROCESS_ENV
);
assert.equal(resolved.timeoutMs, 120000);
assert.equal(resolved.maxRetries, 3);
});

test("resolveSettings prefers env TIMEOUT_MS and MAX_RETRIES over settings (#13)", () => {
const resolved = resolveSettings(
{ timeoutMs: 120000, maxRetries: 3, env: { API_KEY: "sk-test" } },
{ model: "default-model", baseURL: "https://default.example.com" },
{ DEEPCODE_TIMEOUT_MS: "90000", DEEPCODE_MAX_RETRIES: "5" }
);
assert.equal(resolved.timeoutMs, 90000);
assert.equal(resolved.maxRetries, 5);
});

test("resolveSettings reads top-level thinkingEnabled, notify, and webSearchTool", () => {
const resolved = resolveSettings(
{
Expand Down