@tiens.nguyen/gu-cli 1.0.700 → 1.0.702

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.
@@ -416,3 +416,53 @@ export function describeNotOllama(host, role) {
416
416
  "for an OpenAI-compatible coder use /model → add or change an OpenAI-compatible provider — or type 'no' to skip.",
417
417
  ];
418
418
  }
419
+
420
+ // ---------------------------------------------------------------------------------------------
421
+ // #44 — WHAT TO CHANGE comes first.
422
+ // ---------------------------------------------------------------------------------------------
423
+
424
+ /**
425
+ * The first screen of /agent-model: what to change, before any model list.
426
+ *
427
+ * Reported (#44): client mode, agent on api.openai.com — /agent-model opened straight onto "130 on
428
+ * https://api.openai.com/v1" with the current model selected, and the ways to use an Ollama server
429
+ * or another provider sat after all 130 rows. Enter said "already using gpt-4.1-mini — nothing
430
+ * changed", and the only thing the screen seemed to allow was another OpenAI model. First-time setup
431
+ * worked; changing it afterwards did not look possible.
432
+ *
433
+ * So the choice of WHAT to change is its own short list, in the order it is most often wanted. The
434
+ * key entry appears only where a key applies. Returns [{ action, label }].
435
+ */
436
+ export function agentMenuOptions({ host = "", modelCount = 0, needsKey = false } = {}) {
437
+ const where = host || "this server";
438
+ return [
439
+ { action: "models", label: `pick another model on ${where}${modelCount ? ` (${modelCount})` : ""}` },
440
+ { action: "ollama", label: "use an Ollama server for the agent…" },
441
+ { action: "provider", label: "use another OpenAI-compatible provider…" },
442
+ ...(needsKey ? [{ action: "key", label: `enter a new API key for ${where}…` }] : []),
443
+ ];
444
+ }
445
+
446
+ /**
447
+ * The providers already saved on this machine that the agent could switch to — OpenAI-compatible
448
+ * rows, current one marked — then "add a new one". Switching to a provider you already set up must
449
+ * not mean typing its URL again. Returns [{ action: "row" | "add", row?, label }].
450
+ */
451
+ export function agentProviderChoices(settings) {
452
+ const rows = Array.isArray(settings?.agentCodingBackends) ? settings.agentCodingBackends : [];
453
+ const current = ensureV1Root(String(settings?.agentModelUrl ?? "")).toLowerCase();
454
+ const out = [];
455
+ for (const row of rows) {
456
+ if (!row || row.kind !== "openai" || !row.url) continue;
457
+ let host = String(row.url);
458
+ try {
459
+ host = new URL(String(row.url)).host;
460
+ } catch {
461
+ /* keep the raw url */
462
+ }
463
+ const isCurrent = ensureV1Root(String(row.url)).toLowerCase() === current;
464
+ out.push({ action: "row", row, label: `${host}${isCurrent ? " (current)" : ""}` });
465
+ }
466
+ out.push({ action: "add", label: "add a new OpenAI-compatible provider…" });
467
+ return out;
468
+ }
@@ -9694,22 +9694,25 @@ def run_agent_chat(cfg):
9694
9694
  f"{_host} refused: "
9695
9695
  + (_why or "the account is out of credit")
9696
9696
  + ". Nothing ran — top up the account, or switch the "
9697
- "coding model in Settings → Agent."
9697
+ "coding model (in gu: /model; on the web: Settings → Agent)."
9698
9698
  ) from e
9699
+ # THE TERMINAL WAY FIRST (#44): a client-mode machine has no web app,
9700
+ # so "Settings → Agent" alone was advice it could not follow.
9699
9701
  if coding_api_key:
9700
9702
  raise _AgentConfigError(
9701
9703
  f"The agent coding backend ({_host}) REJECTED the "
9702
- "coding API key. Nothing ran. Re-enter 'Agent coding "
9703
- "model API key' in Settings → Agent (the stored key is "
9704
- "write-only, so a stale one can't be inspected — just "
9705
- "save a fresh one)."
9704
+ "coding API key. Nothing ran. Enter a fresh key — in gu: "
9705
+ "press ctrl+k (or /model → the provider); on the web: "
9706
+ "Settings → Agent. The stored key is write-only, so a "
9707
+ "stale one can't be inspected — just save a fresh one."
9706
9708
  ) from e
9707
9709
  raise _AgentConfigError(
9708
9710
  f"The agent coding backend ({_host}) rejected the request: "
9709
9711
  "NO coding API key was sent, so the local agent key was "
9710
- "used instead. Enter the API key in Settings → Agent for "
9711
- "this coding backend (OpenAI-compatible, or Ollama with an "
9712
- "https://ollama.com URL — local Ollama needs no key)."
9712
+ "used instead. Enter the API key for this coding backend — in "
9713
+ "gu: press ctrl+k; on the web: Settings → Agent "
9714
+ "(OpenAI-compatible, or Ollama with an https://ollama.com URL — "
9715
+ "local Ollama needs no key)."
9713
9716
  ) from e
9714
9717
  if "404" in emsg and "not found" in emsg.lower():
9715
9718
  # Probe with the CODING key when there is one: using the agent
package/gu-repl.mjs CHANGED
@@ -6119,6 +6119,29 @@ async function pointAgentAtProvider(row, host) {
6119
6119
  await saveAgentModel(url, model, host);
6120
6120
  }
6121
6121
 
6122
+ /**
6123
+ * #44: switch the agent to a provider already saved on this machine, or add a new one.
6124
+ */
6125
+ async function chooseAgentProvider() {
6126
+ const am = await import("./agent-model-command.mjs");
6127
+ const settings = await withPendingStatus("reading your providers", fetchWorkerSettings);
6128
+ if (!settings) {
6129
+ console.log(red(" couldn't reach the API to read your providers.\n"));
6130
+ return;
6131
+ }
6132
+ const choices = am.agentProviderChoices(settings);
6133
+ if (choices.length === 1) return addCodingProvider("agent");
6134
+ console.log(dim(` Which OpenAI-compatible provider should the agent use? (${PICK_HINT})`));
6135
+ const idx = await pickFromList(choices.map((c) => c.label), 0);
6136
+ if (idx < 0) {
6137
+ console.log(dim(" (cancelled)\n"));
6138
+ return;
6139
+ }
6140
+ const pick = choices[idx];
6141
+ if (pick.action === "add") return addCodingProvider("agent");
6142
+ await pointAgentAtProvider(pick.row, hostOf(String(pick.row.url ?? "")));
6143
+ }
6144
+
6122
6145
  /**
6123
6146
  * #41: the agent model on an OLLAMA server — this machine's, a LAN box, or the Ollama cloud.
6124
6147
  *
@@ -6334,6 +6357,21 @@ async function chooseAgentModel() {
6334
6357
  return;
6335
6358
  }
6336
6359
 
6360
+ // WHAT TO CHANGE, FIRST (#44). The ways to use an Ollama server or another provider used to sit
6361
+ // after every model on this one — 130 rows down on api.openai.com — which read as "OpenAI models
6362
+ // are all you can choose". A short menu of what to change comes before any list.
6363
+ console.log(dim(` Agent model — the one that TALKS: ${current || "unset"} on ${listHost}`));
6364
+ const menu = am.agentMenuOptions({ host: listHost, modelCount: models.length, needsKey });
6365
+ const first = await pickFromList(menu.map((o) => o.label), 0);
6366
+ const firstAction = first >= 0 ? menu[first].action : "cancel";
6367
+ if (firstAction === "cancel") {
6368
+ console.log(dim(" (cancelled)\n"));
6369
+ return;
6370
+ }
6371
+ if (firstAction === "ollama") return addOllamaAgent();
6372
+ if (firstAction === "provider") return chooseAgentProvider();
6373
+ if (firstAction === "key") return enterAgentProviderKey(source.row, listHost);
6374
+
6337
6375
  const rows = am.agentModelRows(models, current);
6338
6376
  console.log(dim(` Agent model — the one that TALKS (currently ${current || "unset"})`));
6339
6377
  // Strip whichever list endpoint was asked, so the header names the SERVER rather than the path.
@@ -6360,28 +6398,13 @@ async function chooseAgentModel() {
6360
6398
  (r.current ? accent(" (current)") : "")
6361
6399
  );
6362
6400
  });
6363
- // APPENDED LAST, after every model, for the reason #223 records: a new entry must never shift
6364
- // an index someone already has muscle memory for. Offered even when the list is healthy,
6365
- // because "which model on the box I am pointed at" and "I want a different provider" are
6366
- // different questions, and only the first is answered by the rows above.
6367
- labels.push("add an OpenAI-compatible provider for the agent…");
6368
- // #41, appended AFTER the provider entry so no existing index moves.
6369
- labels.push("use an Ollama server for the agent…");
6401
+ // No other entries here any more (#44): the ways to change the provider are on the menu above,
6402
+ // where they can be seen, rather than after every model.
6370
6403
  const idx = await pickFromList(labels, Math.max(0, rows.findIndex((r) => r.current)));
6371
6404
  if (idx < 0) {
6372
6405
  console.log(dim(" (cancelled)\n"));
6373
6406
  return;
6374
6407
  }
6375
- // labels is now TWO LONGER than rows. Reading rows[idx] for an appended entry would
6376
- // dereference undefined and take the picker down, so they are answered before rows is touched.
6377
- if (idx === rows.length) {
6378
- await addCodingProvider("agent");
6379
- return;
6380
- }
6381
- if (idx === rows.length + 1) {
6382
- await addOllamaAgent();
6383
- return;
6384
- }
6385
6408
  const chosen = rows[idx].model;
6386
6409
  if (chosen === current) {
6387
6410
  console.log(dim(` already using ${current} — nothing changed.\n`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tiens.nguyen/gu-cli",
3
- "version": "1.0.700",
3
+ "version": "1.0.702",
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",