[Sync] Lua Hyprland chain, licence cleanup

This commit is contained in:
Coja committed 2026-11-09 18:25:35 +01:00
1 parent 93b4f695ce
commit 47166d877f
183 files changed
+5618 -5839

No files matched your search

+7 -4
View File
@@ -59,10 +59,11 @@ have a slash command to disable them for the current session without deleting.
| `vim-toggle.ts` | `/vim` — drops the modal editor for the rest of the session, and brings it back via `ctx.reload()`. Pairs with the **`pi-vim` package** (below), which registers no command of its own | **custom** | delete file → you lose the toggle, not the editor; remove the package to drop modal editing itself |
| `plan-mode/` | Read-only "plan" mode (disables edit/write, restricts bash) bound to **Shift+Tab**; `/plan` toggles, `--plan` starts in it, `/plan-done` clears a stuck progress checklist | **vendored** copy of pi's bundled `examples/extensions/plan-mode/`, rebound from upstream `Ctrl+Alt+P` | delete the folder **and** revert the `keybindings.json` `app.thinking.cycle` remap (see below) |
| `notify.ts` | Native terminal notification (OSC 777/99, Windows toast) when the agent goes idle | **verbatim** from pi's `examples/extensions/notify.ts` | delete file |
| `agent-state.ts` | Mirrors Pi's state (`agent_start` busy, `agent_settled` done, `ui_prompt_*` input, `session_shutdown` off) into the tmux pane it runs in via `~/.config/tmux/scripts/agent-state.sh`, for the status-bar agent badges; no-op outside tmux | own | delete file |
| `questionnaire.ts` | Interactive multi-question overlay the model can call as a tool | **near-verbatim** from pi's `examples/extensions/questionnaire.ts` (one-line fix) | delete file |
| `permission-gate.ts` | Prompts before dangerous `bash` commands (blocks outright with no UI). Local list covers git history rewrites, package managers, `systemctl` and irreversible disk ops on top of upstream's `rm -rf`/`sudo`/`chmod 777` | **adapted** from pi's `examples/extensions/permission-gate.ts` | delete file |
| `protected-paths.ts` | Blocks `write`/`edit` to secrets and repo plumbing (`secrets.fish`, `auth.json`, `.env`, `.ssh/`, `.git/`, …) | **adapted** from pi's `examples/extensions/protected-paths.ts` | delete file |
| `compaction-watch.ts` | Surfaces every compaction and its `reason`; loudly flags `overflow` (recovery rather than prevention), which means `reserveTokens` is below the model's `maxTokens` | **custom** | delete file |
| `compaction-watch.ts` | Surfaces every compaction and its `reason`; on `overflow` (recovery rather than prevention) it reads the turn that caused it and says which kind it was, with numbers — a reply pi itself had clamped (`max_tokens` = window − estimate − 4096, the usual one on thinking models) or a prompt the server really rejected | **custom** | delete file |
| `verbose.ts` | `/verbose [on|off|status]` and **Alt+V** toggle the *persisted* verbose default (`hideThinkingBlock`, `showCacheMissNotices`) and report pi's live keys — `ctrl+o` tool output, `ctrl+t` thinking, which an extension cannot drive itself | **custom** | delete file; the live keys are built in and keep working |
| `confirm-destructive.ts` | Confirms before session clear / switch / fork | **verbatim** from pi's `examples/extensions/confirm-destructive.ts` | delete file |
| `session-name.ts` | `/session-name [name]` — friendly names in the session picker instead of the first message | **verbatim** from pi's `examples/extensions/session-name.ts` | delete file |
@@ -117,7 +118,9 @@ pi install npm:pi-vim
> caret range would quietly drift off `pi --version`.
Configured under `settings.json > piVim`: `modeColors` (theme tokens, not raw ANSI),
`borderSync`/`labelSync` pinned so only the label is tinted, and
`borderSync` so the input border itself takes the mode color in NORMAL/VISUAL
(mauve/yellow) and is left to pi in INSERT/EX, `labelSync` so the label always carries
its mode color, and
`clipboardMirror: "yank"` — upstream's default `"all"` mirrors deletes too, so every
`dd`/`x` would clobber the OS clipboard. `exCommand.copyInputToClipboard` is left
**off**: it copies the composed prompt out, so upstream only honours it from the
@@ -171,14 +174,14 @@ Beyond pointing at local models, these values deviate from pi's defaults:
| Key | Value here | Stock default | Why |
|---|---|---|---|
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k–128k), but **≥ every model's `maxTokens`** — otherwise a turn can overflow the window before compaction even fires |
| `compaction.reserveTokens` | `8192` | 16384 | small local windows (24k–128k), but **≥ every model's `maxTokens` + 4096**: pi clamps each request's `max_tokens` to `contextWindow − estimate − 4096`, and with 24k/4096 that clamp starts exactly at this threshold (see CLAUDE.md for the `overflow` recoveries it can still produce) |
| `compaction.keepRecentTokens` | `4000` | 20000 | keep less verbatim, to pay for the larger reserve and leave room after a compaction for the next one |
| `npmCommand` | `fnm exec --using=22 -- npm` | `npm` | pin extension npm installs to Node 22 via fnm |
| `retry` / `httpIdleTimeoutMs` | long (1h provider timeout, 10m idle) | shorter | local models can be slow to first token |
| `defaultThinkingLevel` | `medium` | — | the default model is a thinking preset (`Qwen3.6-35B-A3B-Thinking`); Alt+T cycles it per session |
| `thinkingBudgets` | `512 / 1024 / 2048 / 3072` (minimal / low / medium / high) | 1024 / 2048 / 8192 / 16384 | per-request cap on the `<think>` block, sent only for models whose `compat.thinkingTokenBudgetField` is set (`Ornith`, as `thinking_budget_tokens`). pi clamps the budget to `maxTokens − 1024`, so on a 4096-token reply the stock values collapse to 3072 and leave the answer 1024 tokens — the recipe for "Response was truncated before completion." |
| `packages` | `["npm:pi-vim"]` | `[]` | the modal editor — see the pi-vim section above |
| `piVim` | mode colors, `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
| `piVim` | mode colors, border painted per mode (NORMAL/VISUAL), `clipboardMirror: "yank"`, `:` → pi bridge | — | package config, not a pi setting; ignored if the package is removed |
| `collapseChangelog` | `true` | `false` | condensed changelog after an upgrade instead of the full screen |
**Remove:** delete each key to fall back to pi's default (or drop the whole
@@ -0,0 +1,26 @@
/**
* Pi agent-state extension: mirrors Pi's state into the tmux pane it runs in via
* ~/.config/tmux/scripts/agent-state.sh (pi busy|done|input|off), so the tmux status bar
* shows which pane is done or waiting for you. The script is a no-op outside tmux.
*/
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { execFile } from "node:child_process";
import { homedir } from "node:os";
import { join } from "node:path";
const SCRIPT = join(homedir(), ".config", "tmux", "scripts", "agent-state.sh");
function state(s: "busy" | "done" | "input" | "error" | "off"): void {
if (!process.env.TMUX_PANE) return;
execFile(SCRIPT, ["pi", s], () => {});
}
export default function (pi: ExtensionAPI) {
pi.on("agent_start", async () => state("busy"));
// agent_settled, not agent_end: Pi may still retry, compact, or run queued follow-ups.
pi.on("agent_settled", async () => state("done"));
// Blocking confirm/select prompts from extensions (permission-gate, confirm-destructive, questionnaire).
pi.on("ui_prompt_start", async () => state("input"));
pi.on("ui_prompt_end", async () => state("busy"));
pi.on("session_shutdown", async () => state("off"));
}
+78 -18
View File
@@ -3,31 +3,94 @@
*
* Compaction is otherwise silent, which is why "compacting fails a lot" took code
* reading and arithmetic to diagnose rather than a glance at a log. This makes it
* visible, and specifically names the failure mode that was breaking it here before
* 2026-08-16.
* visible and, for the one reason that signals trouble, says WHICH trouble.
*
* pi reports a `reason` on every compaction:
* "manual" - you ran /compact
* "threshold" - the healthy path: contextTokens passed contextWindow - reserveTokens
* "overflow" - the window was ALREADY full; this is recovery, not prevention
* "overflow" - recovery, not prevention: the previous assistant turn ended badly,
* pi compacts and (when `willRetry`) re-runs that turn once
*
* An "overflow" compaction means the turn blew past contextWindow before the threshold
* could fire, which happens whenever `compaction.reserveTokens` is smaller than the
* model's `maxTokens`. pi then gets one recovery attempt, summarizing from an
* already-overflowed context — and when that does not fit either, the session dies with
* "Context overflow recovery failed after one compact-and-retry attempt".
* Since pi 0.84.3 "overflow" covers TWO different situations, and the numbers on the
* last assistant message tell them apart (CLAUDE.md > Compaction tuning has the
* arithmetic for both):
*
* So: seeing "threshold" is fine. Seeing "overflow" means the settings are wrong for the
* model in use, and this says so at the moment it happens instead of leaving you to
* infer it later. See CLAUDE.md > Compaction tuning.
* 1. A real overflow — the server rejected the prompt (stopReason "error" matching
* pi's overflow patterns), or reported more input than the declared window.
* Fix: contextWindow / reserveTokens / maxTokens are wrong for the model in use.
* 2. Truncated-reply recovery — the reply stopped with stopReason "length" while
* usage.output is BELOW the model's maxTokens. The request itself carried a smaller
* max_tokens: before every call pi-ai clamps it to
* `contextWindow − its own token estimate − 4096` (clampMaxTokensToContext,
* CONTEXT_SAFETY_TOKENS) and then shrinks the thinking budget so that only 1024
* answer tokens remain. The window was never full. On a 24k window with maxTokens
* 4096 the clamp bites at any estimate above 16384 — the same number as the
* compaction threshold with reserveTokens 8192 — so it hits in the gaps the threshold
* check never sees: the first request after a compaction (no server usage to anchor
* on, so the system prompt and the tool schemas join the estimate) and the first call
* of a user turn (the new message). Thinking models feel it first: 2048 of thinking
* leaves a clamped reply about 1024 tokens of answer, and a `write` payload is cut.
*
* The old message here blamed every "overflow" on `reserveTokens < maxTokens`, which is
* wrong for case 2 — the one that actually shows up (Ornith, 2026-10-22). Now the
* notification names the case and prints the numbers (output vs maxTokens, context vs
* window, the estimate the clamp implies), so the next occurrence is diagnosable from the
* screen instead of from a session file.
*
* Note there is no event for a *failed* auto-compaction — `onError` exists only on the
* manual `ctx.compact()` call — so catching the overflow entry is the earliest reliable
* warning available.
* warning available. Observe-only: it never alters the compaction itself.
*/
import type { AssistantMessage } from "@earendil-works/pi-ai";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
/** pi-ai's CONTEXT_SAFETY_TOKENS: the margin clampMaxTokensToContext keeps below the window. */
const PI_CONTEXT_SAFETY_TOKENS = 4096;
/** Last assistant message on the branch — the turn that triggered the recovery. */
function lastAssistant(entries: readonly unknown[]): AssistantMessage | undefined {
for (let i = entries.length - 1; i >= 0; i--) {
const e = entries[i] as { type?: string; message?: { role?: string } };
if (e.type === "message" && e.message?.role === "assistant") return e.message as AssistantMessage;
}
return undefined;
}
/** One line saying which kind of "overflow" this is, with the numbers that prove it. */
export function describeOverflow(last: AssistantMessage | undefined, window: number, maxTokens: number): string {
if (!last) return "no assistant message on the branch to inspect.";
const u = last.usage;
const output = u?.output ?? 0;
const context = (u?.input ?? 0) + (u?.cacheRead ?? 0) + (u?.cacheWrite ?? 0) + output;
const ctxStr = window > 0 ? `${context}/${window}` : `${context}`;
const checkWindow = "check contextWindow against the server ctx-size (CLAUDE.md > Compaction tuning).";
if (last.stopReason === "error") {
return `the server rejected the request at context ${ctxStr} (${last.errorMessage ?? "no message"}) — a real overflow; ${checkWindow}`;
}
if (last.stopReason === "length") {
if (output === 0 && window > 0 && context >= window * 0.99) {
return `the server produced no tokens at context ${ctxStr} — the prompt alone filled the window; ${checkWindow}`;
}
if (window > 0 && context >= window - 64) {
return (
`the server ran out of context mid-reply (${ctxStr}, output ${output}/${maxTokens}) — the prompt was ` +
`under-estimated (big tool results or images count more than chars ÷ 4); not a reserveTokens problem.`
);
}
if (maxTokens > 0 && output < maxTokens) {
const estimate = window > 0 ? ` pi's estimate at request time ≈ ${window - PI_CONTEXT_SAFETY_TOKENS - output}, server-side context ${ctxStr}.` : "";
return (
`truncated-reply recovery, NOT a full window: the reply stopped at ${output} of ${maxTokens} max tokens, ` +
`so pi had clamped max_tokens to window − estimate − ${PI_CONTEXT_SAFETY_TOKENS}.${estimate} ` +
`reserveTokens is not the knob — see CLAUDE.md > Compaction tuning > max_tokens clamp.`
);
}
}
return `stopReason ${last.stopReason}, context ${ctxStr}, output ${output}/${maxTokens} — input reported above the declared window; ${checkWindow}`;
}
export default function (pi: ExtensionAPI) {
let overflowCount = 0;
@@ -36,12 +99,9 @@ export default function (pi: ExtensionAPI) {
if (event.reason === "overflow") {
overflowCount++;
ctx.ui.notify(
`Compacting in OVERFLOW recovery (#${overflowCount}) — the context window was already ` +
`full before compaction could fire. Raise compaction.reserveTokens above this model's ` +
`maxTokens, or cap maxTokens in models.json. See CLAUDE.md > Compaction tuning.`,
"warning",
);
const model = ctx.model;
const verdict = describeOverflow(lastAssistant(event.branchEntries), model?.contextWindow ?? 0, model?.maxTokens ?? 0);
ctx.ui.notify(`Compacting in OVERFLOW recovery (#${overflowCount}) — ${verdict}`, "warning");
}
// Never alter the compaction itself — observe only.
+153 -16
View File
@@ -17,7 +17,13 @@
* 󰚩 model model id + · thinking level (Claude 🤖) U+F06A9 nf-md-robot
* 󰈚 context % of context window used (+ tokens) (Claude 📝) U+F021A nf-md-text_box
* 󰓅 t/s last response's decode throughput U+F04C5 nf-md-speedometer
* ↑ ↓ tokens session input / output tokens (Claude 💰) plain arrows
* (llama.cpp's own server-side number + dim ` pp N` prompt-processing
* speed when the final SSE chunk carries `timings`; otherwise the
* client-side clock -- see "server-side timings" below)
* ↑ ↓ tokens session prompt / output tokens (Claude 🔢) plain arrows
* (prompt = input + cache read + cache write: everything the server
* processed, matching the Claude bar — llama.cpp reports a cached
* prefix as cacheRead, so bare usage.input is a few dozen tokens)
*
* git, context, t/s and tokens only appear once there is data for them (i.e. after
* the first response, and git only inside a git repo). Extension statuses (e.g.
@@ -58,12 +64,111 @@ const CTX_WARN_AT = 80;
// direct access to it). Defaults to the settings.json default of "off".
let thinkingLevel = "off";
// Decode throughput (tokens/sec) of the most recent assistant response, measured
// from the first streamed token to message end so prompt-eval time is excluded.
// Decode throughput (tokens/sec) of the most recent assistant response. Preferred
// source: the server's own timings (below). Fallback: a client-side clock from the
// first streamed token to message end, so prompt-eval time is excluded.
let lastTps = 0;
let lastPp = 0; // prompt-processing t/s, server-side only (0 = unknown)
let genFirstTokenAt = 0;
let sawToken = false;
// --- server-side timings (llama.cpp) ----------------------------------------
// llama.cpp puts a `timings` object on the final SSE chunk (the one carrying `usage`):
// prompt_n / prompt_ms / prompt_per_second, predicted_n / predicted_ms /
// predicted_per_second, cache_n, draft_n / draft_n_accepted. pi-ai's openai-completions
// provider reads only `usage` and drops the rest, and the extension API has no HTTP
// hook, so the one way to see it is to wrap globalThis.fetch and tee the SSE body.
// That is sanctioned: pi's http-dispatcher installs undici's fetch at startup and
// deliberately keeps a later override ("If a caller replaced fetch after module load,
// preserve that deliberate override"), and pi-ai builds `new OpenAI({ fetch: undefined })`
// per request, which falls back to globalThis.fetch -- so a wrapper installed at
// session_start sees every chat completion. Only POST */chat/completions responses
// with a text/event-stream body are teed; other providers' streams carry no `timings`
// and are passed through untouched apart from the tee. The tap stays installed for the
// life of the process (`/statusbar` off just stops displaying it; `/reload` rebinds the
// sink instead of wrapping twice).
interface ServerTimings {
tps: number; // predicted_per_second
pp: number; // prompt_per_second
predictedN: number;
promptN: number;
cacheN: number;
}
// Latest timings seen on the wire, consumed by message_end. Paired with the message by
// predicted_n === usage.output, so a compaction/summary call's timings never get
// attributed to the next visible response.
let pendingServerTimings: ServerTimings | undefined;
const FETCH_TAP_KEY = "__piStatusbarFetchTap";
/** Read one branch of a teed SSE body and report every `timings` object it carries. */
async function scanSse(stream: ReadableStream<Uint8Array>, onTimings: (t: ServerTimings) => void): Promise<void> {
const reader = stream.getReader();
const dec = new TextDecoder();
let buf = "";
try {
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buf += dec.decode(value, { stream: true });
let nl: number;
while ((nl = buf.indexOf("\n")) >= 0) {
const line = buf.slice(0, nl).trim();
buf = buf.slice(nl + 1);
if (!line.startsWith("data:")) continue;
const payload = line.slice(5).trim();
if (!payload.includes('"timings"')) continue; // cheap pre-filter: parse only the final chunk
try {
const t = JSON.parse(payload)?.timings;
if (t && typeof t.predicted_per_second === "number") {
onTimings({
tps: t.predicted_per_second,
pp: typeof t.prompt_per_second === "number" ? t.prompt_per_second : 0,
predictedN: t.predicted_n ?? 0,
promptN: t.prompt_n ?? 0,
cacheN: t.cache_n ?? 0,
});
}
} catch {
// not JSON: ignore
}
}
}
} catch {
// aborted / cancelled stream: the primary branch reports that, we just stop
}
}
/** Wrap globalThis.fetch once; on /reload only rebind the sink. */
function installFetchTap(): void {
const g = globalThis as unknown as Record<string, unknown>;
const sink = (t: ServerTimings) => {
pendingServerTimings = t;
};
const existing = g[FETCH_TAP_KEY] as { sink: (t: ServerTimings) => void } | undefined;
if (existing) {
existing.sink = sink;
return;
}
const tap = { sink };
g[FETCH_TAP_KEY] = tap;
const orig = globalThis.fetch;
globalThis.fetch = (async (input: Parameters<typeof fetch>[0], init?: Parameters<typeof fetch>[1]) => {
const res = await orig(input, init);
let url = "";
try {
url = typeof input === "string" ? input : input instanceof URL ? input.href : ((input as Request)?.url ?? "");
} catch {
return res;
}
if (!url.endsWith("/chat/completions") || !res.body) return res;
if (!(res.headers.get("content-type") ?? "").includes("text/event-stream")) return res;
const [ours, theirs] = res.body.tee();
void scanSse(ours, (t) => tap.sink(t));
return new Response(theirs, res);
}) as typeof fetch;
}
// Set from inside the footer factory so events can trigger a re-render.
let requestRender: (() => void) | undefined;
@@ -74,12 +179,27 @@ function collapseHome(p: string): string {
return p;
}
/** 1234 -> 1.2k, 843000 -> 843k, 1234567 -> 1.2M (same boundaries as the Claude bar's
* fmt_tokens: 0.9995 / 9.95 so a value never prints as "10.0k" or "1000k"). */
function fmtTokens(n: number): string {
return n < 1000 ? `${n}` : `${(n / 1000).toFixed(1)}k`;
for (const [unit, div] of [
["M", 1e6],
["k", 1e3],
] as const) {
const v = n / div;
if (v < 0.9995) continue;
return v >= 9.95 ? `${v.toFixed(0)}${unit}` : `${v.toFixed(1)}${unit}`;
}
return `${Math.round(n)}`;
}
/** Cumulative session tokens, plus the last request's input tokens (~ current
* context-window fill). */
/** Cumulative session tokens, plus the last request's prompt size (~ current
* context-window fill).
*
* Prompt = input + cacheRead + cacheWrite. pi-ai splits a cached prefix out of
* `usage.input` (llama.cpp too: the second call of a session reports e.g. input 20,
* cacheRead 2417), so summing bare `input` undercounted ↑ and -- worse -- made the
* context % and the ≥80% warning read near zero on every cached call. */
function tokenStats(ctx: ExtensionContext): {
input: number;
output: number;
@@ -92,9 +212,10 @@ function tokenStats(ctx: ExtensionContext): {
if (entry.type === "message" && entry.message.role === "assistant") {
const usage = (entry.message as AssistantMessage).usage;
if (!usage) continue; // aborted stream: no usage — a throwing render kills the TUI
input += usage.input;
output += usage.output;
lastInput = usage.input;
const prompt = (usage.input ?? 0) + (usage.cacheRead ?? 0) + (usage.cacheWrite ?? 0);
input += prompt;
output += usage.output ?? 0;
lastInput = prompt;
}
}
return { input, output, lastInput };
@@ -161,8 +282,19 @@ export default function (pi: ExtensionAPI) {
pi.on("message_end", async (event, ctx) => {
if (event.message?.role !== "assistant") return;
const output = (event.message as AssistantMessage).usage?.output ?? 0;
const secs = genFirstTokenAt ? (Date.now() - genFirstTokenAt) / 1000 : 0;
if (secs > 0 && output > 0) lastTps = output / secs;
// The tee reader and pi-ai consume the same final chunk; pi-ai's path to this
// event is longer, but give the reader one macrotask in case it is still behind.
if (!pendingServerTimings) await new Promise((r) => setTimeout(r, 20));
const srv = pendingServerTimings;
pendingServerTimings = undefined;
if (srv && srv.predictedN > 0 && (output === 0 || srv.predictedN === output)) {
lastTps = srv.tps;
lastPp = srv.pp;
} else {
const secs = genFirstTokenAt ? (Date.now() - genFirstTokenAt) / 1000 : 0;
if (secs > 0 && output > 0) lastTps = output / secs;
lastPp = 0;
}
requestRender?.();
if (enabled) updateContextWidget(ctx);
});
@@ -217,14 +349,15 @@ export default function (pi: ExtensionAPI) {
parts.push(seg);
}
// 󰓅 tokens/sec of the last response
// 󰓅 tokens/sec of the last response (+ dim prompt-processing t/s when the
// server reported it)
if (lastTps > 0) {
parts.push(
theme.fg("accent", ICON.tps) + " " + theme.fg("text", lastTps.toFixed(1)) + theme.fg("dim", " t/s"),
);
let seg = theme.fg("accent", ICON.tps) + " " + theme.fg("text", lastTps.toFixed(1)) + theme.fg("dim", " t/s");
if (lastPp > 0) seg += theme.fg("dim", ` pp ${lastPp.toFixed(0)}`);
parts.push(seg);
}
// ↑ ↓ cumulative session tokens (Claude's 💰 slot; local models are $0)
// ↑ ↓ cumulative session prompt / output tokens (Claude's 🔢 segment)
if (input || output) {
parts.push(theme.fg("dim", `${ICON.up}${fmtTokens(input)} ${ICON.down}${fmtTokens(output)}`));
}
@@ -236,6 +369,10 @@ export default function (pi: ExtensionAPI) {
};
pi.on("session_start", async (_event, ctx) => {
// Installed here, not at load time: pi's http-dispatcher only swaps in undici's
// fetch (idle timeout, proxy) while globalThis.fetch is still the original, so the
// tap must not get there first. By session_start, startup is done.
installFetchTap();
if (enabled) install(ctx);
});
+4 -4
View File
@@ -107,11 +107,11 @@
},
{
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
"name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s)",
"reasoning": true,
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
"input": ["text", "image"],
"contextWindow": 24576,
"contextWindow": 32768,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
@@ -242,11 +242,11 @@
},
{
"id": "Ornith-1.5-35B-A3B-IQ3_XXS",
"name": "Ornith 1.5 35B · 24k · vision — agentic coder, thinking (~32 t/s)",
"name": "Ornith 1.5 35B · 32k · vision — agentic coder, thinking (~32 t/s)",
"reasoning": true,
"compat": { "thinkingFormat": "qwen-chat-template", "thinkingTokenBudgetField": "thinking_budget_tokens" },
"input": ["text", "image"],
"contextWindow": 24576,
"contextWindow": 32768,
"maxTokens": 4096,
"cost": { "input": 0, "output": 0, "cacheRead": 0, "cacheWrite": 0 }
},
+3 -3
View File
@@ -53,8 +53,8 @@
},
"borderSync": {
"insert": "host",
"normal": "host",
"visual": "host",
"normal": "mode",
"visual": "mode",
"ex": "host"
},
"labelSync": {
@@ -64,7 +64,7 @@
"ex": "mode"
}
},
"lastChangelogVersion": "1.0.0",
"lastChangelogVersion": "1.5.0",
"packages": [
"npm:pi-vim"
]