Skip to content

Commit 99c4c5b

Browse files
Lellansinclaude
andcommitted
refactor: extract settings I/O and OpenAI client from app.tsx to common/
Move settings file read/write and resolveCurrentSettings to src/common/settings.ts, and createOpenAIClient + getMachineId to src/common/openai-client.ts. This removes fs/os/path/openai imports from the UI layer. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent a99b0bd commit 99c4c5b

4 files changed

Lines changed: 162 additions & 155 deletions

File tree

src/common/openai-client.ts

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import * as fs from "fs";
2+
import * as os from "os";
3+
import * as path from "path";
4+
import OpenAI from "openai";
5+
import { resolveCurrentSettings } from "./settings";
6+
7+
export function createOpenAIClient(projectRoot: string = process.cwd()): {
8+
client: OpenAI | null;
9+
model: string;
10+
baseURL: string;
11+
thinkingEnabled: boolean;
12+
reasoningEffort: "high" | "max";
13+
debugLogEnabled: boolean;
14+
notify?: string;
15+
webSearchTool?: string;
16+
env: Record<string, string>;
17+
machineId?: string;
18+
} {
19+
const settings = resolveCurrentSettings(projectRoot);
20+
if (!settings.apiKey) {
21+
return {
22+
client: null,
23+
model: settings.model,
24+
baseURL: settings.baseURL,
25+
thinkingEnabled: settings.thinkingEnabled,
26+
reasoningEffort: settings.reasoningEffort,
27+
debugLogEnabled: settings.debugLogEnabled,
28+
notify: settings.notify,
29+
webSearchTool: settings.webSearchTool,
30+
env: settings.env,
31+
machineId: getMachineId(),
32+
};
33+
}
34+
35+
const client = new OpenAI({
36+
apiKey: settings.apiKey,
37+
baseURL: settings.baseURL || undefined,
38+
});
39+
return {
40+
client,
41+
model: settings.model,
42+
baseURL: settings.baseURL,
43+
thinkingEnabled: settings.thinkingEnabled,
44+
reasoningEffort: settings.reasoningEffort,
45+
debugLogEnabled: settings.debugLogEnabled,
46+
notify: settings.notify,
47+
webSearchTool: settings.webSearchTool,
48+
env: settings.env,
49+
machineId: getMachineId(),
50+
};
51+
}
52+
53+
function getMachineId(): string | undefined {
54+
try {
55+
const idPath = path.join(os.homedir(), ".deepcode", "machine-id");
56+
if (fs.existsSync(idPath)) {
57+
const raw = fs.readFileSync(idPath, "utf8").trim();
58+
if (raw) {
59+
return raw;
60+
}
61+
}
62+
const generated = `${os.hostname()}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
63+
fs.mkdirSync(path.dirname(idPath), { recursive: true });
64+
fs.writeFileSync(idPath, generated, "utf8");
65+
return generated;
66+
} catch {
67+
return undefined;
68+
}
69+
}

src/common/settings.ts

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import * as fs from "fs";
2+
import * as os from "os";
3+
import * as path from "path";
4+
import {
5+
type DeepcodingSettings,
6+
type ModelConfigSelection,
7+
type ResolvedDeepcodingSettings,
8+
applyModelConfigSelection,
9+
resolveSettingsSources,
10+
} from "../settings";
11+
12+
const DEFAULT_MODEL = "deepseek-v4-pro";
13+
const DEFAULT_BASE_URL = "https://api.deepseek.com";
14+
15+
function getUserSettingsPath(): string {
16+
return path.join(os.homedir(), ".deepcode", "settings.json");
17+
}
18+
19+
function getProjectSettingsPath(projectRoot: string): string {
20+
return path.join(projectRoot, ".deepcode", "settings.json");
21+
}
22+
23+
function readSettingsFile(settingsPath: string): DeepcodingSettings | null {
24+
try {
25+
if (!fs.existsSync(settingsPath)) {
26+
return null;
27+
}
28+
const raw = fs.readFileSync(settingsPath, "utf8");
29+
return JSON.parse(raw) as DeepcodingSettings;
30+
} catch {
31+
return null;
32+
}
33+
}
34+
35+
function writeSettingsFile(settingsPath: string, settings: DeepcodingSettings): void {
36+
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
37+
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
38+
}
39+
40+
export function readSettings(): DeepcodingSettings | null {
41+
return readSettingsFile(getUserSettingsPath());
42+
}
43+
44+
export function readProjectSettings(projectRoot: string = process.cwd()): DeepcodingSettings | null {
45+
return readSettingsFile(getProjectSettingsPath(projectRoot));
46+
}
47+
48+
export function writeSettings(settings: DeepcodingSettings): void {
49+
const settingsPath = getUserSettingsPath();
50+
writeSettingsFile(settingsPath, settings);
51+
}
52+
53+
export function writeProjectSettings(settings: DeepcodingSettings, projectRoot: string = process.cwd()): void {
54+
const settingsPath = getProjectSettingsPath(projectRoot);
55+
writeSettingsFile(settingsPath, settings);
56+
}
57+
58+
export function writeModelConfigSelection(
59+
selection: ModelConfigSelection,
60+
current: ModelConfigSelection = resolveCurrentSettings(),
61+
projectRoot: string = process.cwd()
62+
): { changed: boolean; settings: DeepcodingSettings } {
63+
const projectSettingsPath = getProjectSettingsPath(projectRoot);
64+
const shouldWriteProjectSettings = fs.existsSync(projectSettingsPath);
65+
const rawSettings = shouldWriteProjectSettings ? readProjectSettings(projectRoot) : readSettings();
66+
const result = applyModelConfigSelection(rawSettings, current, selection);
67+
if (result.changed) {
68+
if (shouldWriteProjectSettings) {
69+
writeProjectSettings(result.settings, projectRoot);
70+
} else {
71+
writeSettings(result.settings);
72+
}
73+
}
74+
return result;
75+
}
76+
77+
export function resolveCurrentSettings(projectRoot: string = process.cwd()): ResolvedDeepcodingSettings {
78+
return resolveSettingsSources(
79+
readSettings(),
80+
readProjectSettings(projectRoot),
81+
{
82+
model: DEFAULT_MODEL,
83+
baseURL: DEFAULT_BASE_URL,
84+
},
85+
process.env
86+
);
87+
}

src/ui/App.tsx

Lines changed: 3 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
22
import { Box, Static, Text, useApp, useStdout, useWindowSize } from "ink";
33
import chalk from "chalk";
4-
import * as fs from "fs";
5-
import * as os from "os";
6-
import * as path from "path";
7-
import OpenAI from "openai";
84
import {
95
type LlmStreamProgress,
106
type MessageMeta,
@@ -16,13 +12,9 @@ import {
1612
type UndoTarget,
1713
type UserPromptContent,
1814
} from "../session";
19-
import {
20-
applyModelConfigSelection,
21-
type DeepcodingSettings,
22-
type ModelConfigSelection,
23-
type ResolvedDeepcodingSettings,
24-
resolveSettingsSources,
25-
} from "../settings";
15+
import { type ModelConfigSelection, type ResolvedDeepcodingSettings } from "../settings";
16+
import { resolveCurrentSettings, writeModelConfigSelection } from "../common/settings";
17+
import { createOpenAIClient } from "../common/openai-client";
2618
import { PromptInput, type PromptDraft, type PromptSubmission } from "./PromptInput";
2719
import { MessageView, RawModeExitPrompt } from "./components";
2820
import { SessionList } from "./SessionList";
@@ -42,9 +34,6 @@ import { buildExitSummaryText } from "./exitSummary";
4234
import { RawMode, useRawModeContext } from "./contexts";
4335
import { renderMessageToStdout } from "./components/MessageView/utils";
4436

45-
const DEFAULT_MODEL = "deepseek-v4-pro";
46-
const DEFAULT_BASE_URL = "https://api.deepseek.com";
47-
4837
type View = "chat" | "session-list" | "undo" | "mcp-status";
4938

5039
type AppProps = {
@@ -772,144 +761,6 @@ function buildStatusLine(entry: SessionEntry): string {
772761
return parts.join(" · ");
773762
}
774763

775-
export function readSettings(): DeepcodingSettings | null {
776-
return readSettingsFile(getUserSettingsPath());
777-
}
778-
779-
export function readProjectSettings(projectRoot: string = process.cwd()): DeepcodingSettings | null {
780-
return readSettingsFile(getProjectSettingsPath(projectRoot));
781-
}
782-
783-
function readSettingsFile(settingsPath: string): DeepcodingSettings | null {
784-
try {
785-
if (!fs.existsSync(settingsPath)) {
786-
return null;
787-
}
788-
const raw = fs.readFileSync(settingsPath, "utf8");
789-
return JSON.parse(raw) as DeepcodingSettings;
790-
} catch {
791-
return null;
792-
}
793-
}
794-
795-
export function writeSettings(settings: DeepcodingSettings): void {
796-
const settingsPath = getUserSettingsPath();
797-
writeSettingsFile(settingsPath, settings);
798-
}
799-
800-
export function writeProjectSettings(settings: DeepcodingSettings, projectRoot: string = process.cwd()): void {
801-
const settingsPath = getProjectSettingsPath(projectRoot);
802-
writeSettingsFile(settingsPath, settings);
803-
}
804-
805-
function writeSettingsFile(settingsPath: string, settings: DeepcodingSettings): void {
806-
fs.mkdirSync(path.dirname(settingsPath), { recursive: true });
807-
fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`, "utf8");
808-
}
809-
810-
export function writeModelConfigSelection(
811-
selection: ModelConfigSelection,
812-
current: ModelConfigSelection = resolveCurrentSettings(),
813-
projectRoot: string = process.cwd()
814-
): { changed: boolean; settings: DeepcodingSettings } {
815-
const projectSettingsPath = getProjectSettingsPath(projectRoot);
816-
const shouldWriteProjectSettings = fs.existsSync(projectSettingsPath);
817-
const rawSettings = shouldWriteProjectSettings ? readProjectSettings(projectRoot) : readSettings();
818-
const result = applyModelConfigSelection(rawSettings, current, selection);
819-
if (result.changed) {
820-
if (shouldWriteProjectSettings) {
821-
writeProjectSettings(result.settings, projectRoot);
822-
} else {
823-
writeSettings(result.settings);
824-
}
825-
}
826-
return result;
827-
}
828-
829-
export function resolveCurrentSettings(projectRoot: string = process.cwd()): ResolvedDeepcodingSettings {
830-
return resolveSettingsSources(
831-
readSettings(),
832-
readProjectSettings(projectRoot),
833-
{
834-
model: DEFAULT_MODEL,
835-
baseURL: DEFAULT_BASE_URL,
836-
},
837-
process.env
838-
);
839-
}
840-
841-
export function createOpenAIClient(projectRoot: string = process.cwd()): {
842-
client: OpenAI | null;
843-
model: string;
844-
baseURL: string;
845-
thinkingEnabled: boolean;
846-
reasoningEffort: "high" | "max";
847-
debugLogEnabled: boolean;
848-
notify?: string;
849-
webSearchTool?: string;
850-
env: Record<string, string>;
851-
machineId?: string;
852-
} {
853-
const settings = resolveCurrentSettings(projectRoot);
854-
if (!settings.apiKey) {
855-
return {
856-
client: null,
857-
model: settings.model,
858-
baseURL: settings.baseURL,
859-
thinkingEnabled: settings.thinkingEnabled,
860-
reasoningEffort: settings.reasoningEffort,
861-
debugLogEnabled: settings.debugLogEnabled,
862-
notify: settings.notify,
863-
webSearchTool: settings.webSearchTool,
864-
env: settings.env,
865-
machineId: getMachineId(),
866-
};
867-
}
868-
869-
const client = new OpenAI({
870-
apiKey: settings.apiKey,
871-
baseURL: settings.baseURL || undefined,
872-
});
873-
return {
874-
client,
875-
model: settings.model,
876-
baseURL: settings.baseURL,
877-
thinkingEnabled: settings.thinkingEnabled,
878-
reasoningEffort: settings.reasoningEffort,
879-
debugLogEnabled: settings.debugLogEnabled,
880-
notify: settings.notify,
881-
webSearchTool: settings.webSearchTool,
882-
env: settings.env,
883-
machineId: getMachineId(),
884-
};
885-
}
886-
887-
function getMachineId(): string | undefined {
888-
try {
889-
const idPath = path.join(os.homedir(), ".deepcode", "machine-id");
890-
if (fs.existsSync(idPath)) {
891-
const raw = fs.readFileSync(idPath, "utf8").trim();
892-
if (raw) {
893-
return raw;
894-
}
895-
}
896-
const generated = `${os.hostname()}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
897-
fs.mkdirSync(path.dirname(idPath), { recursive: true });
898-
fs.writeFileSync(idPath, generated, "utf8");
899-
return generated;
900-
} catch {
901-
return undefined;
902-
}
903-
}
904-
905-
function getUserSettingsPath(): string {
906-
return path.join(os.homedir(), ".deepcode", "settings.json");
907-
}
908-
909-
function getProjectSettingsPath(projectRoot: string): string {
910-
return path.join(projectRoot, ".deepcode", "settings.json");
911-
}
912-
913764
function formatThinkingMode(settings: Pick<ModelConfigSelection, "thinkingEnabled" | "reasoningEffort">): string {
914765
if (!settings.thinkingEnabled) {
915766
return "no thinking";

src/ui/index.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export {
1111
writeProjectSettings,
1212
writeModelConfigSelection,
1313
resolveCurrentSettings,
14-
createOpenAIClient,
15-
buildPromptDraftFromSessionMessage,
16-
} from "./App";
14+
} from "../common/settings";
15+
export { createOpenAIClient } from "../common/openai-client";
16+
export { buildPromptDraftFromSessionMessage } from "./App";
1717
export { default as AppContainer } from "./AppContainer";
1818
export { AskUserQuestionPrompt } from "./AskUserQuestionPrompt";
1919
export { MessageView } from "./components";

0 commit comments

Comments
 (0)