-
Notifications
You must be signed in to change notification settings - Fork 11
Expand file tree
/
Copy pathopenai-client.ts
More file actions
126 lines (118 loc) · 4.31 KB
/
Copy pathopenai-client.ts
File metadata and controls
126 lines (118 loc) · 4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import * as fs from "fs";
import * as os from "os";
import * as path from "path";
import OpenAI from "openai";
import { Agent, fetch as undiciFetch } from "undici";
import { resolveCurrentSettings } from "../settings";
// Custom undici Agent with a 180-second keepAlive timeout. The default
// global fetch (undici) only keeps connections alive for 4 seconds, which
// is too short for a CLI where the user may spend 10–30 seconds reading
// output between prompts. By passing a dedicated Agent to undiciFetch we
// keep connections reusable for three minutes after the last request.
const keepAliveAgent = new Agent({ keepAliveTimeout: 180_000 });
// 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
// settings are always read fresh from the project / user config files.
let cachedOpenAI: OpenAI | null = null;
let cachedOpenAIKey = "";
export function createOpenAIClient(projectRoot: string = process.cwd()): {
client: OpenAI | null;
model: string;
baseURL: string;
temperature?: number;
thinkingEnabled: boolean;
reasoningEffort: "high" | "max";
debugLogEnabled: boolean;
telemetryEnabled: boolean;
notify?: string;
webSearchTool?: string;
env: Record<string, string>;
machineId?: string;
} {
const settings = resolveCurrentSettings(projectRoot);
if (!settings.apiKey) {
return {
client: null,
model: settings.model,
baseURL: settings.baseURL,
temperature: settings.temperature,
thinkingEnabled: settings.thinkingEnabled,
reasoningEffort: settings.reasoningEffort,
debugLogEnabled: settings.debugLogEnabled,
telemetryEnabled: settings.telemetryEnabled,
notify: settings.notify,
webSearchTool: settings.webSearchTool,
env: settings.env,
machineId: getMachineId(),
};
}
const cacheKey = `${settings.apiKey}::${settings.baseURL}::${JSON.stringify(settings.headers)}`;
if (cachedOpenAI && cachedOpenAIKey === cacheKey) {
return {
client: cachedOpenAI,
model: settings.model,
baseURL: settings.baseURL,
temperature: settings.temperature,
thinkingEnabled: settings.thinkingEnabled,
reasoningEffort: settings.reasoningEffort,
debugLogEnabled: settings.debugLogEnabled,
telemetryEnabled: settings.telemetryEnabled,
notify: settings.notify,
webSearchTool: settings.webSearchTool,
env: settings.env,
machineId: getMachineId(),
};
}
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 }),
});
cachedOpenAIKey = cacheKey;
// Fire-and-forget warmup: pre-establish TCP+TLS connection to the API
// server while the user is composing their first prompt. Bounded by a
// short timeout so a slow / unreachable API never blocks process exit.
void (async () => {
const ac = new AbortController();
const timer = setTimeout(() => ac.abort(), 3000);
try {
await cachedOpenAI.models.list({ signal: ac.signal }).catch(() => {});
} finally {
clearTimeout(timer);
}
})();
return {
client: cachedOpenAI,
model: settings.model,
baseURL: settings.baseURL,
temperature: settings.temperature,
thinkingEnabled: settings.thinkingEnabled,
reasoningEffort: settings.reasoningEffort,
debugLogEnabled: settings.debugLogEnabled,
telemetryEnabled: settings.telemetryEnabled,
notify: settings.notify,
webSearchTool: settings.webSearchTool,
env: settings.env,
machineId: getMachineId(),
};
}
function getMachineId(): string | undefined {
try {
const idPath = path.join(os.homedir(), ".deepcode", "machine-id");
if (fs.existsSync(idPath)) {
const raw = fs.readFileSync(idPath, "utf8").trim();
if (raw) {
return raw;
}
}
const generated = `${os.hostname()}-${Math.random().toString(36).slice(2)}-${Date.now()}`;
fs.mkdirSync(path.dirname(idPath), { recursive: true });
fs.writeFileSync(idPath, generated, "utf8");
return generated;
} catch {
return undefined;
}
}