codeshark-cli 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4 -1
- package/dist/modelAvailability.js +46 -8
- package/dist/repl.js +11 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -250,7 +250,10 @@ async function main() {
|
|
|
250
250
|
if (status === "available") {
|
|
251
251
|
console.log(` ${hex("#4ade80", "✓ Available")} ${model.label}`);
|
|
252
252
|
}
|
|
253
|
-
else {
|
|
253
|
+
else if (status === "busy") {
|
|
254
|
+
console.log(` ${hex("#fbbf24", "~ Lane busy")} ${model.label}${reason ? dim(` — ${reason}`) : ""}`);
|
|
255
|
+
}
|
|
256
|
+
else if (status === "unavailable") {
|
|
254
257
|
console.log(` ${hex("#f87171", "✗ Unavailable")} ${model.label}${reason ? dim(` — ${reason}`) : ""}`);
|
|
255
258
|
}
|
|
256
259
|
}
|
|
@@ -2,7 +2,9 @@ import { DEFAULT_GATEWAY_URL, envApiKey } from "./config.js";
|
|
|
2
2
|
import { MODELS } from "./models.js";
|
|
3
3
|
const statuses = new Map();
|
|
4
4
|
const reasons = new Map();
|
|
5
|
-
const TIMEOUT_MS =
|
|
5
|
+
const TIMEOUT_MS = 15_000;
|
|
6
|
+
/** Match the gateway's per-IP inflight cap so probes never queue behind themselves. */
|
|
7
|
+
const MAX_CONCURRENT_CHECKS = 2;
|
|
6
8
|
export function modelAvailability(modelId) {
|
|
7
9
|
return statuses.get(modelId) ?? "checking";
|
|
8
10
|
}
|
|
@@ -16,6 +18,25 @@ export function modelAvailabilitySnapshot() {
|
|
|
16
18
|
reason: modelAvailabilityReason(model.id),
|
|
17
19
|
}));
|
|
18
20
|
}
|
|
21
|
+
/**
|
|
22
|
+
* Classify a failed probe. "busy" covers transient lane problems (rate
|
|
23
|
+
* limits, upstream hiccups, timeouts) where the model is likely fine but
|
|
24
|
+
* the shared free lane is momentarily overloaded — those must NOT read as
|
|
25
|
+
* "model removed". Only explicit client errors mean the model itself is
|
|
26
|
+
* not servable right now.
|
|
27
|
+
*/
|
|
28
|
+
export function classifyProbeFailure(statusOrError) {
|
|
29
|
+
if (statusOrError instanceof Error) {
|
|
30
|
+
if (statusOrError.name === "AbortError")
|
|
31
|
+
return { status: "busy", reason: "lane busy — timed out" };
|
|
32
|
+
return { status: "busy", reason: statusOrError.message || "network error" };
|
|
33
|
+
}
|
|
34
|
+
if (statusOrError === 429)
|
|
35
|
+
return { status: "busy", reason: "rate limited — try again shortly" };
|
|
36
|
+
if (statusOrError >= 500)
|
|
37
|
+
return { status: "busy", reason: `lane busy — HTTP ${statusOrError}` };
|
|
38
|
+
return { status: "unavailable", reason: `HTTP ${statusOrError}` };
|
|
39
|
+
}
|
|
19
40
|
function endpointFor(model, cfg) {
|
|
20
41
|
if (model.provider === "gemini") {
|
|
21
42
|
return {
|
|
@@ -53,8 +74,12 @@ async function checkOne(model, cfg) {
|
|
|
53
74
|
body: JSON.stringify({ contents: [{ role: "user", parts: [{ text: "Reply with OK." }] }] }),
|
|
54
75
|
signal: controller.signal,
|
|
55
76
|
});
|
|
56
|
-
if (!response.ok)
|
|
57
|
-
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
const { status, reason } = classifyProbeFailure(response.status);
|
|
79
|
+
statuses.set(model.id, status);
|
|
80
|
+
reasons.set(model.id, reason);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
58
83
|
}
|
|
59
84
|
else {
|
|
60
85
|
const response = await fetch(target.url, {
|
|
@@ -63,15 +88,20 @@ async function checkOne(model, cfg) {
|
|
|
63
88
|
body: JSON.stringify({ model: model.model, messages: [{ role: "user", content: "Reply with OK." }], stream: false, max_tokens: 4 }),
|
|
64
89
|
signal: controller.signal,
|
|
65
90
|
});
|
|
66
|
-
if (!response.ok)
|
|
67
|
-
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
const { status, reason } = classifyProbeFailure(response.status);
|
|
93
|
+
statuses.set(model.id, status);
|
|
94
|
+
reasons.set(model.id, reason);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
68
97
|
}
|
|
69
98
|
statuses.set(model.id, "available");
|
|
70
99
|
reasons.delete(model.id);
|
|
71
100
|
}
|
|
72
101
|
catch (error) {
|
|
73
|
-
|
|
74
|
-
|
|
102
|
+
const { status, reason } = classifyProbeFailure(error instanceof Error ? error : new Error(String(error)));
|
|
103
|
+
statuses.set(model.id, status);
|
|
104
|
+
reasons.set(model.id, reason);
|
|
75
105
|
}
|
|
76
106
|
finally {
|
|
77
107
|
clearTimeout(timer);
|
|
@@ -82,5 +112,13 @@ export async function checkModelAvailability(cfg) {
|
|
|
82
112
|
statuses.set(model.id, "checking");
|
|
83
113
|
reasons.delete(model.id);
|
|
84
114
|
}
|
|
85
|
-
|
|
115
|
+
// Throttle to the gateway's per-IP inflight limit: firing all probes at
|
|
116
|
+
// once makes them queue behind each other and time out spuriously.
|
|
117
|
+
const queue = [...MODELS];
|
|
118
|
+
const workers = Array.from({ length: Math.min(MAX_CONCURRENT_CHECKS, queue.length) }, async () => {
|
|
119
|
+
for (let model = queue.shift(); model; model = queue.shift()) {
|
|
120
|
+
await checkOne(model, cfg);
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
await Promise.all(workers);
|
|
86
124
|
}
|
package/dist/repl.js
CHANGED
|
@@ -48,9 +48,14 @@ export function printModelInfo() {
|
|
|
48
48
|
for (const m of MODELS) {
|
|
49
49
|
const active = m.id === (findModel(cfg.model ?? "")?.id ?? DEFAULT_MODEL_ID);
|
|
50
50
|
const status = modelAvailability(m.id);
|
|
51
|
-
const marker = status === "available" ? hex("#4ade80", "●") : status === "unavailable" ? hex("#f87171", "✗") : dim("○");
|
|
52
|
-
const note = status === "unavailable"
|
|
53
|
-
|
|
51
|
+
const marker = status === "available" ? hex("#4ade80", "●") : status === "unavailable" ? hex("#f87171", "✗") : status === "busy" ? hex("#fbbf24", "◐") : dim("○");
|
|
52
|
+
const note = status === "unavailable"
|
|
53
|
+
? `Unavailable${modelAvailabilityReason(m.id) ? ` — ${modelAvailabilityReason(m.id)}` : ""}`
|
|
54
|
+
: status === "busy"
|
|
55
|
+
? `Lane busy${modelAvailabilityReason(m.id) ? ` — ${modelAvailabilityReason(m.id)}` : ""} — should still work`
|
|
56
|
+
: m.notes;
|
|
57
|
+
const styled = status === "unavailable" ? hex("#f87171", note) : status === "busy" ? hex("#fbbf24", note) : note;
|
|
58
|
+
console.log(` ${marker} ${m.label.padEnd(28)} ${dim(m.context.padEnd(5))} ${styled}`);
|
|
54
59
|
}
|
|
55
60
|
console.log("");
|
|
56
61
|
console.log(dim(" Switch with: /model <name>"));
|
|
@@ -81,6 +86,9 @@ export function switchModel(query) {
|
|
|
81
86
|
console.log(dim(` ${modelAvailabilityReason(entry.id) ?? "The boot health check failed."}`));
|
|
82
87
|
return;
|
|
83
88
|
}
|
|
89
|
+
if (modelAvailability(entry.id) === "busy") {
|
|
90
|
+
console.log(hex("#fbbf24", ` ⚠ ${entry.label}'s shared lane is busy right now — switching anyway. Requests may wait or retry.`));
|
|
91
|
+
}
|
|
84
92
|
cfg.model = entry.id;
|
|
85
93
|
saveConfig(cfg);
|
|
86
94
|
console.log(` ${hex("#4ade80", "✓")} Switched to ${bold(entry.label)} ${dim(`(${entry.context} context)`)}`);
|