-
-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathcodexLocalBackendProbe.ts
More file actions
133 lines (120 loc) · 3.53 KB
/
codexLocalBackendProbe.ts
File metadata and controls
133 lines (120 loc) · 3.53 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
127
128
129
130
131
132
133
import { Effect } from "effect";
export interface LocalBackendProbeResult {
readonly reachable: boolean;
readonly modelCount?: number;
readonly error?: string;
}
export interface LocalBackendProbes {
readonly ollama: LocalBackendProbeResult;
readonly lmstudio: LocalBackendProbeResult;
}
const DEFAULT_PROBE_TIMEOUT_MS = 1_500;
const OLLAMA_TAGS_URL = "http://localhost:11434/api/tags";
const LM_STUDIO_MODELS_URL = "http://localhost:1234/v1/models";
function isSuppressedByEnv(): boolean {
const env = process.env;
return env.OKCODE_DISABLE_LOCAL_BACKEND_PROBES === "1" || env.VITEST === "true";
}
function toErrorMessage(cause: unknown, fallback: string): string {
if (cause instanceof Error && cause.message.trim().length > 0) {
if (cause.name === "AbortError") {
return "timeout";
}
return cause.message;
}
if (typeof cause === "string" && cause.trim().length > 0) {
return cause;
}
return fallback;
}
function readModelCount(data: unknown, key: "models" | "data"): number | undefined {
if (!data || typeof data !== "object") {
return undefined;
}
const value = (data as Record<string, unknown>)[key];
if (Array.isArray(value)) {
return value.length;
}
return undefined;
}
async function probeHttp(input: {
readonly url: string;
readonly modelsKey: "models" | "data";
readonly timeoutMs: number;
}): Promise<LocalBackendProbeResult> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), input.timeoutMs);
try {
const response = await fetch(input.url, {
method: "GET",
signal: controller.signal,
headers: { accept: "application/json" },
});
if (!response.ok) {
return {
reachable: false,
error: `HTTP ${response.status}`,
};
}
try {
const body: unknown = await response.json();
const modelCount = readModelCount(body, input.modelsKey);
return modelCount !== undefined ? { reachable: true, modelCount } : { reachable: true };
} catch (cause) {
// Server responded 2xx but body wasn't JSON — still counts as reachable.
return {
reachable: true,
error: toErrorMessage(cause, "Non-JSON response"),
};
}
} catch (cause) {
return {
reachable: false,
error: toErrorMessage(cause, "Network error"),
};
} finally {
clearTimeout(timeout);
}
}
export interface ProbeLocalBackendOptions {
readonly timeoutMs?: number | undefined;
}
const UNREACHABLE_STUB: LocalBackendProbeResult = { reachable: false };
export const probeOllama = (
options: ProbeLocalBackendOptions = {},
): Effect.Effect<LocalBackendProbeResult> => {
if (isSuppressedByEnv()) {
return Effect.succeed(UNREACHABLE_STUB);
}
return Effect.promise(() =>
probeHttp({
url: OLLAMA_TAGS_URL,
modelsKey: "models",
timeoutMs: options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS,
}),
);
};
export const probeLmStudio = (
options: ProbeLocalBackendOptions = {},
): Effect.Effect<LocalBackendProbeResult> => {
if (isSuppressedByEnv()) {
return Effect.succeed(UNREACHABLE_STUB);
}
return Effect.promise(() =>
probeHttp({
url: LM_STUDIO_MODELS_URL,
modelsKey: "data",
timeoutMs: options.timeoutMs ?? DEFAULT_PROBE_TIMEOUT_MS,
}),
);
};
export const probeCodexLocalBackends = (
options: ProbeLocalBackendOptions = {},
): Effect.Effect<LocalBackendProbes> =>
Effect.all(
{
ollama: probeOllama(options),
lmstudio: probeLmStudio(options),
},
{ concurrency: "unbounded" },
);