@tiens.nguyen/gu-cli 1.0.821 → 1.0.822
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/gu-repl.mjs +12 -2
- package/package.json +1 -1
- package/terminal-title.mjs +32 -6
package/gu-repl.mjs
CHANGED
|
@@ -154,7 +154,7 @@ import {
|
|
|
154
154
|
parseModelFallback,
|
|
155
155
|
} from "./model-fallback.mjs";
|
|
156
156
|
import { codingChoiceTags, coderDisplayName, coderWhere, whereOfActiveCoder } from "./coding-choice-tags.mjs";
|
|
157
|
-
import { TITLE_POP, TITLE_PUSH, idleTitle, needsSummary, summariseForTitle, titleSeq, trimTitle } from "./terminal-title.mjs";
|
|
157
|
+
import { TITLE_POP, TITLE_PUSH, idleTitle, needsSummary, summariseForTitle, titleModel, titleSeq, trimTitle } from "./terminal-title.mjs";
|
|
158
158
|
import { IMAGE_NEEDS_CLAUDE, coderTakesImagesFor, clipboardImageToFile, fitImageNotice, imageMarkersForDisplay, createImageStore, imagePasteKey, imagePathFromPaste, isClaudeCoderUrl, isImagePasteKey, storeImageFile } from "./image-paste.mjs";
|
|
159
159
|
import {
|
|
160
160
|
isDoubleClick,
|
|
@@ -1633,7 +1633,17 @@ function showQuestionInTitle(question) {
|
|
|
1633
1633
|
void (async () => {
|
|
1634
1634
|
const settings = await fetchWorkerSettings();
|
|
1635
1635
|
if (!settings || mine !== _titleSeq) return;
|
|
1636
|
-
|
|
1636
|
+
// #358: the coder this turn runs on, with the key bound to its server — not the agent-model
|
|
1637
|
+
// setting, which can name a model the turns never use, called with no key at all.
|
|
1638
|
+
const target = titleModel(settings, { url: activeCodingUrl(), model: activeCodingModelId() });
|
|
1639
|
+
if (!target) return;
|
|
1640
|
+
const key = target.backendId ? await fetchKeyForBackend(target.backendId) : null;
|
|
1641
|
+
if (mine !== _titleSeq) return;
|
|
1642
|
+
const title = await summariseForTitle(question, settings, {
|
|
1643
|
+
fetchImpl: doctorFetch,
|
|
1644
|
+
target,
|
|
1645
|
+
apiKey: key?.apiKey ?? "",
|
|
1646
|
+
});
|
|
1637
1647
|
if (title && mine === _titleSeq) writeTitle(title);
|
|
1638
1648
|
})().catch(() => {});
|
|
1639
1649
|
}
|
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.822",
|
|
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",
|
package/terminal-title.mjs
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* shortened, and keep it there until the next question.
|
|
8
8
|
*
|
|
9
9
|
* TWO STEPS, AND THE FIRST NEVER WAITS FOR THE SECOND. The trimmed question goes up the moment
|
|
10
|
-
* the turn starts. Only a question too long to fit is then sent to the
|
|
11
|
-
* few-word summary, which replaces the trimmed one if — and only if — it arrives usable and the
|
|
10
|
+
* the turn starts. Only a question too long to fit is then sent to the coder the turn runs on
|
|
11
|
+
* (with its server's key — see titleModel) for a few-word summary, which replaces the trimmed one if — and only if — it arrives usable and the
|
|
12
12
|
* user has not asked something else in the meantime. Every way that call can fail (no model
|
|
13
13
|
* configured, a provider that wants a key this side does not hold, a timeout, a model that
|
|
14
14
|
* answers with an essay) ends the same way: the trimmed question stays.
|
|
@@ -98,6 +98,30 @@ export function agentModelFromSettings(settings) {
|
|
|
98
98
|
return { url, model: str(settings?.agentModelName) || "default_model" };
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/** A server URL reduced to what identifies it: no case, no trailing slash, no trailing /v1. */
|
|
102
|
+
function serverOf(url) {
|
|
103
|
+
return String(url ?? "").trim().toLowerCase().replace(/\/+$/, "").replace(/\/v1$/, "");
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* WHICH MODEL WRITES THE TITLE (#358): the coder this turn runs on, and the server row its key
|
|
108
|
+
* is bound to. → { url, model, backendId } or null.
|
|
109
|
+
*
|
|
110
|
+
* It used to be the account's agent-model setting, which on a real account was OpenAI o1 —
|
|
111
|
+
* not what the turns ran on (kimi-k3 on ollama.com), and called without a key, so every long
|
|
112
|
+
* question stayed trimmed. The coder is the model the user has already chosen and keyed; the
|
|
113
|
+
* agent-model setting is only the fallback for a session whose coder is not known yet.
|
|
114
|
+
*/
|
|
115
|
+
export function titleModel(settings, coder = {}) {
|
|
116
|
+
const rows = Array.isArray(settings?.agentCodingBackends) ? settings.agentCodingBackends : [];
|
|
117
|
+
const rowFor = (url) => rows.find((r) => r?.id && serverOf(r.url) === serverOf(url))?.id ?? "";
|
|
118
|
+
const url = String(coder?.url ?? "").trim();
|
|
119
|
+
const model = String(coder?.model ?? "").trim();
|
|
120
|
+
if (url && model) return { url, model, backendId: rowFor(url) };
|
|
121
|
+
const agent = agentModelFromSettings(settings);
|
|
122
|
+
return agent ? { ...agent, backendId: rowFor(agent.url) } : null;
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
export function summaryRequest(model, question) {
|
|
102
126
|
const q = Array.from(cleanTitleText(question)).slice(0, SUMMARY_INPUT_MAX).join("");
|
|
103
127
|
return {
|
|
@@ -140,20 +164,22 @@ export function pickSummary(body, max = TITLE_MAX) {
|
|
|
140
164
|
}
|
|
141
165
|
|
|
142
166
|
/**
|
|
143
|
-
* Ask
|
|
167
|
+
* Ask a model for a title. → the title, or "" for every reason there is not one.
|
|
168
|
+
* `target` ({ url, model }, see titleModel) picks the model — the agent-model setting when absent —
|
|
169
|
+
* and `apiKey` is sent as a Bearer token when the server needs one.
|
|
144
170
|
* Never throws: this runs beside a turn and must not be able to disturb it.
|
|
145
171
|
*/
|
|
146
172
|
export async function summariseForTitle(
|
|
147
173
|
question,
|
|
148
174
|
settings,
|
|
149
|
-
{ fetchImpl = fetch, timeoutMs = 20_000 } = {},
|
|
175
|
+
{ fetchImpl = fetch, timeoutMs = 20_000, target = null, apiKey = "" } = {},
|
|
150
176
|
) {
|
|
151
|
-
const agent = agentModelFromSettings(settings);
|
|
177
|
+
const agent = target?.url && target?.model ? target : agentModelFromSettings(settings);
|
|
152
178
|
if (!agent || !needsSummary(question)) return "";
|
|
153
179
|
try {
|
|
154
180
|
const res = await fetchImpl(chatEndpoint(agent.url), {
|
|
155
181
|
method: "POST",
|
|
156
|
-
headers: { "Content-Type": "application/json" },
|
|
182
|
+
headers: { "Content-Type": "application/json", ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}) },
|
|
157
183
|
body: JSON.stringify(summaryRequest(agent.model, question)),
|
|
158
184
|
signal: AbortSignal.timeout(timeoutMs),
|
|
159
185
|
});
|