@gapi/gcli 1.8.238 → 1.8.239

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.
@@ -0,0 +1,22 @@
1
+ ## 2026-10-11 - local-ai-runners: runner reports each model's loaded context window
2
+
3
+ **Feature:** local-ai-runners · **Type:** fix
4
+
5
+ - Heartbeat models now carry `runtimeContextLength` from Ollama `/api/ps`
6
+ (the window a loaded model really runs with, e.g. 32,768 for qwen3.8:27b
7
+ whose max is 262,144). The backend fits every request to it - fixes
8
+ `500 no user query found in messages` in long chats, caused by Ollama
9
+ silently truncating oversized prompts (backend half: graphql-server
10
+ `fix(local-ai-runners): fit requests to the model's real context window`).
11
+ - Models `/api/show` answers 404/410 for (a retired `glm-5.1:cloud` was
12
+ still listed by `/api/tags`) are no longer offered, and the answer is
13
+ cached instead of re-asked every heartbeat.
14
+ - **Test:** `npx jest test/runner.spec.ts --coverage=false` (fake Ollama
15
+ answers `/api/ps` with a loaded model at 65536 and 410 for a retired
16
+ cloud model; the heartbeat must carry the window and omit that model).
17
+ - **Debug:** `ollama ps` shows the live window the runner reports;
18
+ `db["local-runners"]` -> `models[].runtimeContextLength` on the backend.
19
+ An unloaded model reports nothing - the backend keeps its last value.
20
+ - **Verification:** gcli 27/27, tsc + eslint clean; live: runner reported
21
+ 30 models (retired cloud model gone) and the dev backend stored
22
+ qwen3.8:27b runtimeContextLength 32768.
@@ -34,7 +34,14 @@ Ollama: `--ollama` > `$OLLAMA_HOST` (scheme optional, `0.0.0.0` -> `127.0.0.1`)
34
34
  sequentially. `RunnerAuthError` = token refused -> exit 1 with a `--reset` hint.
35
35
  - `ollama.ts` - `ALLOWED_REQUESTS` (only `POST /v1/chat/completions`; the
36
36
  backend has its own copy), `OllamaModels` (`/api/tags` + cached
37
- `/api/show` per digest; drops models without the `completion` capability).
37
+ `/api/show` per digest; drops models without the `completion` capability
38
+ and models `/api/show` answers 404/410 for - e.g. retired `:cloud`
39
+ models, cached so they aren't re-asked every heartbeat). Each heartbeat
40
+ also reads `/api/ps` and reports `runtimeContextLength` - the window a
41
+ LOADED model actually runs with (OLLAMA_CONTEXT_LENGTH / Ollama default).
42
+ The backend fits every request to it, because Ollama silently drops the
43
+ oldest messages of a prompt that is too long (see the canonical doc,
44
+ "Context window").
38
45
  - `config.ts` (saved runner, URL derivation), `start.ts`, `list.ts`,
39
46
  `models.ts`, `delete.ts`, `index.ts` (command registration), `types.ts`.
40
47
  - `src/services/gql-client.ts` - `createLocalRunner`, `listLocalRunners`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gapi/gcli",
3
- "version": "1.8.238",
3
+ "version": "1.8.239",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/Stradivario/gapi.git"
package/release/index.js CHANGED
@@ -317007,12 +317007,41 @@ var init_ollama = __esm({
317007
317007
  throw new Error(`Ollama /api/tags returned ${res.status}`);
317008
317008
  }
317009
317009
  const { models = [] } = await res.json();
317010
- const described = await Promise.all(
317011
- models.map((tag) => this.describe(tag))
317012
- );
317013
- return described.filter(
317010
+ const [described, loaded] = await Promise.all([
317011
+ Promise.all(models.map((tag) => this.describe(tag))),
317012
+ this.loadedContextLengths()
317013
+ ]);
317014
+ for (const model of described) {
317015
+ model.runtimeContextLength = loaded.get(model.name);
317016
+ }
317017
+ return described.filter((m2) => !m2.unusable).filter(
317014
317018
  (m2) => !m2.capabilities.length || m2.capabilities.includes("completion")
317015
- );
317019
+ ).map((m2) => {
317020
+ delete m2.unusable;
317021
+ return m2;
317022
+ });
317023
+ }
317024
+ /**
317025
+ * The context window each LOADED model actually runs with (`/api/ps`,
317026
+ * what `ollama ps` shows) - OLLAMA_CONTEXT_LENGTH / Ollama's VRAM-based
317027
+ * default, usually far below the model's own `contextLength`. Ollama
317028
+ * silently drops the oldest messages of a prompt that doesn't fit, so the
317029
+ * backend sizes requests with this. Unloaded models aren't listed; the
317030
+ * backend keeps the last known value.
317031
+ */
317032
+ async loadedContextLengths() {
317033
+ try {
317034
+ const res = await this.fetchFn(`${this.baseUrl}/api/ps`);
317035
+ if (!res.ok) {
317036
+ return /* @__PURE__ */ new Map();
317037
+ }
317038
+ const { models = [] } = await res.json();
317039
+ return new Map(
317040
+ models.filter((m2) => typeof m2.context_length === "number").map((m2) => [m2.name ?? m2.model, m2.context_length])
317041
+ );
317042
+ } catch {
317043
+ return /* @__PURE__ */ new Map();
317044
+ }
317016
317045
  }
317017
317046
  async describe(tag) {
317018
317047
  const show = await this.show(tag);
@@ -317020,6 +317049,7 @@ var init_ollama = __esm({
317020
317049
  ([key]) => key.endsWith(".context_length")
317021
317050
  )?.[1];
317022
317051
  return {
317052
+ unusable: show?.unusable,
317023
317053
  name: tag.name,
317024
317054
  family: tag.details?.family,
317025
317055
  parameterSize: tag.details?.parameter_size,
@@ -317039,6 +317069,11 @@ var init_ollama = __esm({
317039
317069
  headers: { "Content-Type": "application/json" },
317040
317070
  body: JSON.stringify({ model: tag.name })
317041
317071
  });
317072
+ if (res.status === 404 || res.status === 410) {
317073
+ const gone = { unusable: true };
317074
+ this.showCache.set(key, gone);
317075
+ return gone;
317076
+ }
317042
317077
  if (!res.ok) {
317043
317078
  return void 0;
317044
317079
  }