@tiens.nguyen/gu-cli 1.0.735 → 1.0.737
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/coding-provider-setup.mjs +18 -0
- package/gu-repl.mjs +28 -15
- package/package.json +1 -1
|
@@ -233,6 +233,24 @@ export function modelListRequest(url, key = "") {
|
|
|
233
233
|
return { url: modelsUrlFor(url), headers: keyed ? { Authorization: `Bearer ${k}` } : {} };
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Why a provider's model list could not be shown, and what to type instead (#266). Reported:
|
|
238
|
+
* "did not list its models without a key — type the id … what id?". The line now says WHY
|
|
239
|
+
* (no key saved / key refused / unreachable) and WHAT to type: the model's exact name as that
|
|
240
|
+
* provider calls it, with the current one as the example.
|
|
241
|
+
* reason: "no-key" | "refused" | "failed"
|
|
242
|
+
*/
|
|
243
|
+
export function describeListFailure({ host = "", reason = "failed", status = 0, keyName = "", example = "" } = {}) {
|
|
244
|
+
const what = `type the model's name exactly as ${host} calls it${example ? ` (e.g. ${example})` : ""}`;
|
|
245
|
+
if (reason === "no-key") {
|
|
246
|
+
return [`no API key is saved for ${host}, so it cannot list its models —`, `${what}, or add a key with ctrl+k first.`];
|
|
247
|
+
}
|
|
248
|
+
if (reason === "refused") {
|
|
249
|
+
return [`${host} refused the key${keyName ? ` "${keyName}"` : ""}${status ? ` (HTTP ${status})` : ""} when listing its models —`, `${what}, or fix the key with ctrl+k.`];
|
|
250
|
+
}
|
|
251
|
+
return [`couldn't list ${host}'s models${status ? ` (HTTP ${status})` : " (no answer)"} —`, `${what}.`];
|
|
252
|
+
}
|
|
253
|
+
|
|
236
254
|
/**
|
|
237
255
|
* The hosted providers offered by name when adding one (#261), before "another URL". Claude has
|
|
238
256
|
* no embedding models (Anthropic points to Voyage AI), so it is not offered for the embedder.
|
package/gu-repl.mjs
CHANGED
|
@@ -5356,7 +5356,7 @@ async function chooseCodingModelFrom(ps, all, question, current) {
|
|
|
5356
5356
|
let list = coders;
|
|
5357
5357
|
let showingAll = filtered === 0;
|
|
5358
5358
|
for (;;) {
|
|
5359
|
-
const rows = [...list, "type a model
|
|
5359
|
+
const rows = [...list, "type a model name…"];
|
|
5360
5360
|
// The row that lifts the filter, offered only while it is on and only when it hides something.
|
|
5361
5361
|
if (!showingAll) rows.push(`show all ${all.length} models…`);
|
|
5362
5362
|
// Opens on the CURRENT model where there is one — a long list that opens at the top tells the
|
|
@@ -5369,7 +5369,7 @@ async function chooseCodingModelFrom(ps, all, question, current) {
|
|
|
5369
5369
|
const idx = await pickFromList(rows, at >= 0 ? at : 0);
|
|
5370
5370
|
if (idx < 0) return null;
|
|
5371
5371
|
if (idx === list.length) {
|
|
5372
|
-
const typed = (await ask(" model
|
|
5372
|
+
const typed = (await ask(" model name: ")).trim();
|
|
5373
5373
|
return typed || null;
|
|
5374
5374
|
}
|
|
5375
5375
|
if (idx === list.length + 1) {
|
|
@@ -5395,13 +5395,13 @@ async function chooseCodingModelFrom(ps, all, question, current) {
|
|
|
5395
5395
|
* unlisted one may be exactly right.
|
|
5396
5396
|
*/
|
|
5397
5397
|
async function chooseFromModelList(all, question, current = 0) {
|
|
5398
|
-
const rows = [...all, "type a model
|
|
5398
|
+
const rows = [...all, "type a model name…"];
|
|
5399
5399
|
const at = typeof current === "string" ? all.indexOf(current) : Number(current) || 0;
|
|
5400
5400
|
console.log(dim(` ${question} (${PICK_HINT})`));
|
|
5401
5401
|
const idx = await pickFromList(rows, at >= 0 ? at : 0);
|
|
5402
5402
|
if (idx < 0) return null;
|
|
5403
5403
|
if (idx === all.length) {
|
|
5404
|
-
const typed = (await ask(" model
|
|
5404
|
+
const typed = (await ask(" model name: ")).trim();
|
|
5405
5405
|
return typed || null;
|
|
5406
5406
|
}
|
|
5407
5407
|
return all[idx];
|
|
@@ -5459,15 +5459,22 @@ async function changeProviderModel(ps, rows, p) {
|
|
|
5459
5459
|
const md = await import("./model-doctor.mjs");
|
|
5460
5460
|
const { proxyFetch } = await import("./proxy-dispatcher.mjs");
|
|
5461
5461
|
|
|
5462
|
-
//
|
|
5463
|
-
//
|
|
5464
|
-
//
|
|
5465
|
-
//
|
|
5466
|
-
//
|
|
5462
|
+
// LISTED WITH THIS PROVIDER'S OWN KEY (#266). This used to probe with NO key, on purpose: the
|
|
5463
|
+
// only key the terminal could fetch was the ACCOUNT DEFAULT, which may belong to another provider.
|
|
5464
|
+
// Since #245 a key belongs to its server, so the key used is the one bound to THIS row — the same
|
|
5465
|
+
// check addServer makes — and never another provider's. Keyed providers (Claude, OpenAI, Kimi,
|
|
5466
|
+
// ollama.com) can finally list their models here instead of asking for an id.
|
|
5467
|
+
const sk = await import("./server-keys.mjs");
|
|
5468
|
+
const own = await withPendingStatus(`reading the key for ${p.host}`, async () => {
|
|
5469
|
+
const keys = await fetchServerKeys(p.id);
|
|
5470
|
+
const inUse = sk.keyInUse(keys ?? [], p.id, sessionCodingKeyId);
|
|
5471
|
+
if (!inUse || inUse.backendId !== p.id) return { key: "", name: "" };
|
|
5472
|
+
const r = await fetchCodingKey(p.id);
|
|
5473
|
+
return r.keyId && r.keyId !== inUse.id ? { key: "", name: "" } : { key: r.key, name: String(inUse.name ?? "") };
|
|
5474
|
+
});
|
|
5467
5475
|
const probe = await withPendingStatus(`asking ${p.host} what it has`, async () => {
|
|
5468
5476
|
try {
|
|
5469
|
-
|
|
5470
|
-
const lr = ps.modelListRequest(p.url);
|
|
5477
|
+
const lr = ps.modelListRequest(p.url, own.key); // the list's own dialect too (Claude's headers, #261)
|
|
5471
5478
|
const res = await proxyFetch(lr.url, { headers: lr.headers, signal: AbortSignal.timeout(10_000) });
|
|
5472
5479
|
if (!res.ok) return { status: res.status };
|
|
5473
5480
|
return { body: await res.json().catch(() => null) };
|
|
@@ -5487,7 +5494,11 @@ async function changeProviderModel(ps, rows, p) {
|
|
|
5487
5494
|
const rn = ps.retiredNote(parsed.retired, p.host);
|
|
5488
5495
|
if (rn) console.log(GUTTER + dim(rn));
|
|
5489
5496
|
} else {
|
|
5490
|
-
|
|
5497
|
+
const refused = probe.status === 401 || probe.status === 403;
|
|
5498
|
+
const reason = refused ? (own.key ? "refused" : "no-key") : "failed";
|
|
5499
|
+
for (const l of ps.describeListFailure({ host: p.host, reason, status: probe.status, keyName: own.name, example: p.model })) {
|
|
5500
|
+
console.log(GUTTER + dim(l));
|
|
5501
|
+
}
|
|
5491
5502
|
}
|
|
5492
5503
|
|
|
5493
5504
|
let model = "";
|
|
@@ -5499,7 +5510,7 @@ async function changeProviderModel(ps, rows, p) {
|
|
|
5499
5510
|
}
|
|
5500
5511
|
} else {
|
|
5501
5512
|
console.log(GUTTER + dim(`Enter keeps ${p.model || "it unset"}`));
|
|
5502
|
-
model = (await ask(" model
|
|
5513
|
+
model = (await ask(" model name: ")).trim();
|
|
5503
5514
|
}
|
|
5504
5515
|
if (!model) {
|
|
5505
5516
|
console.log(GUTTER + dim("(nothing changed)\n"));
|
|
@@ -5837,7 +5848,7 @@ async function addServer({ role = "coding", dialect = "openai", url: presetUrl =
|
|
|
5837
5848
|
return;
|
|
5838
5849
|
}
|
|
5839
5850
|
} else {
|
|
5840
|
-
model = String((await ask(" model
|
|
5851
|
+
model = String((await ask(" model name: ")) ?? "").trim();
|
|
5841
5852
|
}
|
|
5842
5853
|
if (!model) {
|
|
5843
5854
|
console.log(GUTTER + dim("(cancelled — nothing was saved)\n"));
|
|
@@ -6125,10 +6136,12 @@ async function chooseModel() {
|
|
|
6125
6136
|
// Task #125: remember the chosen backend's coding URL so the GLOBAL output-token total is
|
|
6126
6137
|
// keyed to it. Default pick → "" so activeCodingUrl() falls back to defaultCodingUrl.
|
|
6127
6138
|
sessionCodingUrl = chosen.id === def ? "" : (chosen.url ?? "");
|
|
6139
|
+
// The server goes WITH the model, before any await (#263): set after the save below, the bar
|
|
6140
|
+
// could repaint in between and show the NEW model under the OLD server for a moment.
|
|
6141
|
+
activeCoderWhere = coderWhere(chosen.kind, chosen.url);
|
|
6128
6142
|
// WRITTEN NOW, not at the end of the next turn: `/model` then `/exit` is how the choice is
|
|
6129
6143
|
// made and then tested, and that path never reaches saveSession.
|
|
6130
6144
|
await saveSessionCoding(resolve(process.cwd()));
|
|
6131
|
-
activeCoderWhere = coderWhere(chosen.kind, chosen.url);
|
|
6132
6145
|
const where = showTag && !activeCoderWhere ? ` (${tags[idx]})` : "";
|
|
6133
6146
|
console.log(
|
|
6134
6147
|
accent(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tiens.nguyen/gu-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.737",
|
|
4
4
|
"description": "Gu CLI — the gu terminal plus the local worker that runs agent / OCR / PDF / embedding jobs on your Mac (Ollama / MLX / OpenAI-compatible).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|