@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.0
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/CHANGELOG.md +63 -1
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +8 -1
- package/dist/types/config/settings-schema.d.ts +32 -6
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/custom-editor.d.ts +2 -1
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/task/executor.d.ts +7 -0
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/render-utils.d.ts +8 -0
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +1 -1
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/gallery-cli.ts +223 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +221 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +16 -16
- package/src/config/settings-schema.ts +18 -5
- package/src/config/settings.ts +11 -0
- package/src/dap/client.ts +14 -16
- package/src/edit/renderer.ts +36 -48
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/agent-bridge.ts +34 -7
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/main.ts +44 -49
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/modes/components/custom-editor.ts +10 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tool-execution.ts +61 -16
- package/src/modes/controllers/command-controller.ts +13 -2
- package/src/modes/controllers/input-controller.ts +11 -3
- package/src/modes/controllers/selector-controller.ts +2 -2
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +17 -3
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/types.ts +1 -1
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/sdk.ts +21 -23
- package/src/session/agent-session.ts +7 -7
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/task/executor.ts +20 -2
- package/src/task/render.ts +1 -2
- package/src/telemetry-export.ts +25 -7
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +94 -84
- package/src/tools/render-utils.ts +17 -3
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/search.ts +173 -81
- package/src/tools/todo.ts +20 -7
- package/src/tools/write.ts +22 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render `omp gallery` output to PNG screenshots via VHS.
|
|
3
|
+
*
|
|
4
|
+
* ANSI escapes are invisible to anything that can only read raw bytes (e.g.
|
|
5
|
+
* agents), so `--screenshot` drives the rendered gallery through a real virtual
|
|
6
|
+
* terminal (VHS + ttyd + ffmpeg) and writes the captured frame to disk. The
|
|
7
|
+
* gallery is pre-rendered to truecolor ANSI in this process — where the user's
|
|
8
|
+
* theme and symbol preset are correct — then `cat`'d inside VHS so the captured
|
|
9
|
+
* pixels match exactly what the live TUI would draw.
|
|
10
|
+
*
|
|
11
|
+
* VHS is a hard dependency of this path: if it is not installed we fail loudly
|
|
12
|
+
* rather than degrade to a lossy fallback.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import * as path from "node:path";
|
|
17
|
+
import { $which } from "@oh-my-pi/pi-utils";
|
|
18
|
+
import { theme } from "../modes/theme/theme";
|
|
19
|
+
import type { GallerySection } from "./gallery-cli";
|
|
20
|
+
|
|
21
|
+
/** Nerd Font family so the gallery's icon glyphs (PUA) render instead of tofu. */
|
|
22
|
+
export const DEFAULT_SCREENSHOT_FONT = "JetBrainsMono Nerd Font";
|
|
23
|
+
export const DEFAULT_SCREENSHOT_FONT_SIZE = 18;
|
|
24
|
+
|
|
25
|
+
/** Inner padding (px) VHS leaves around the terminal grid. */
|
|
26
|
+
const PADDING = 14;
|
|
27
|
+
const LINE_HEIGHT = 1.0;
|
|
28
|
+
/**
|
|
29
|
+
* Upper-bound cell metrics relative to font size. Real monospace cells are
|
|
30
|
+
* smaller, so over-provisioning the canvas guarantees the gallery never
|
|
31
|
+
* soft-wraps (too few columns) or scrolls off the top (too few rows). The slack
|
|
32
|
+
* shows up only as a modest background margin, which is harmless for review.
|
|
33
|
+
*/
|
|
34
|
+
const CELL_WIDTH_RATIO = 0.65;
|
|
35
|
+
const CELL_HEIGHT_RATIO = 1.5;
|
|
36
|
+
/** Keep each image well under headless-Chromium's tall-canvas limits. */
|
|
37
|
+
const MAX_IMAGE_HEIGHT_PX = 8000;
|
|
38
|
+
|
|
39
|
+
export interface GalleryScreenshotOptions {
|
|
40
|
+
/** Gallery render width in columns (matches the ANSI line width). */
|
|
41
|
+
width: number;
|
|
42
|
+
/** VHS `FontFamily`. */
|
|
43
|
+
font?: string;
|
|
44
|
+
/** VHS `FontSize`. */
|
|
45
|
+
fontSize?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Output destination. When omitted, PNGs land in a fresh temp directory.
|
|
48
|
+
* With multiple images the path is suffixed (`name-01.png`, `name-02.png`).
|
|
49
|
+
*/
|
|
50
|
+
out?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Capture the gallery sections as one or more PNGs and return their absolute
|
|
55
|
+
* paths. Tall galleries are split across images so no single capture exceeds
|
|
56
|
+
* the terminal-canvas height limit.
|
|
57
|
+
*/
|
|
58
|
+
export async function captureGalleryScreenshots(
|
|
59
|
+
sections: GallerySection[],
|
|
60
|
+
options: GalleryScreenshotOptions,
|
|
61
|
+
): Promise<string[]> {
|
|
62
|
+
const vhs = $which("vhs");
|
|
63
|
+
if (!vhs) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
"`omp gallery --screenshot` requires VHS, which is not installed. " +
|
|
66
|
+
"Install it (e.g. `brew install vhs`, or see https://github.com/charmbracelet/vhs) and retry.",
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const font = options.font ?? DEFAULT_SCREENSHOT_FONT;
|
|
71
|
+
const fontSize = options.fontSize ?? DEFAULT_SCREENSHOT_FONT_SIZE;
|
|
72
|
+
const cellHeight = fontSize * Math.max(LINE_HEIGHT, 1) * CELL_HEIGHT_RATIO;
|
|
73
|
+
const cellWidth = fontSize * CELL_WIDTH_RATIO;
|
|
74
|
+
const rowBudget = Math.max(40, Math.floor((MAX_IMAGE_HEIGHT_PX - 2 * PADDING) / cellHeight) - 2);
|
|
75
|
+
const chunks = chunkGallerySections(sections, rowBudget);
|
|
76
|
+
const themeJson = buildVhsTheme();
|
|
77
|
+
|
|
78
|
+
const baseDir = options.out
|
|
79
|
+
? path.dirname(path.resolve(options.out))
|
|
80
|
+
: fs.mkdtempSync(path.join(os.tmpdir(), "omp-gallery-"));
|
|
81
|
+
await fs.promises.mkdir(baseDir, { recursive: true });
|
|
82
|
+
|
|
83
|
+
const outPaths: string[] = [];
|
|
84
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
85
|
+
if (chunks.length > 1) {
|
|
86
|
+
process.stderr.write(`Rendering gallery screenshot ${i + 1}/${chunks.length}…\n`);
|
|
87
|
+
}
|
|
88
|
+
const outPng = resolveScreenshotOutputPath(options.out, baseDir, i, chunks.length);
|
|
89
|
+
const lines = chunks[i].flatMap(section => section.lines);
|
|
90
|
+
await renderChunk({ vhs, lines, outPng, font, fontSize, cellWidth, cellHeight, width: options.width, themeJson });
|
|
91
|
+
outPaths.push(outPng);
|
|
92
|
+
}
|
|
93
|
+
return outPaths;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface RenderChunkArgs {
|
|
97
|
+
vhs: string;
|
|
98
|
+
lines: string[];
|
|
99
|
+
outPng: string;
|
|
100
|
+
font: string;
|
|
101
|
+
fontSize: number;
|
|
102
|
+
cellWidth: number;
|
|
103
|
+
cellHeight: number;
|
|
104
|
+
width: number;
|
|
105
|
+
themeJson: string;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async function renderChunk(args: RenderChunkArgs): Promise<void> {
|
|
109
|
+
const rows = args.lines.length;
|
|
110
|
+
const widthPx = Math.ceil(args.width * args.cellWidth) + 2 * PADDING;
|
|
111
|
+
const heightPx = Math.ceil((rows + 2) * args.cellHeight) + 2 * PADDING;
|
|
112
|
+
|
|
113
|
+
const dir = path.dirname(args.outPng);
|
|
114
|
+
const stem = path.basename(args.outPng, path.extname(args.outPng));
|
|
115
|
+
const ansiPath = path.join(dir, `.${stem}.ansi`);
|
|
116
|
+
const tapePath = path.join(dir, `.${stem}.tape`);
|
|
117
|
+
const gifPath = path.join(dir, `.${stem}.gif`);
|
|
118
|
+
|
|
119
|
+
// CRLF so each gallery line is its own terminal row regardless of how the
|
|
120
|
+
// captured shell handles bare LF.
|
|
121
|
+
await Bun.write(ansiPath, `${args.lines.join("\r\n")}\r\n`);
|
|
122
|
+
await Bun.write(
|
|
123
|
+
tapePath,
|
|
124
|
+
buildTape({
|
|
125
|
+
gifPath,
|
|
126
|
+
outPng: args.outPng,
|
|
127
|
+
ansiPath,
|
|
128
|
+
widthPx,
|
|
129
|
+
heightPx,
|
|
130
|
+
font: args.font,
|
|
131
|
+
fontSize: args.fontSize,
|
|
132
|
+
themeJson: args.themeJson,
|
|
133
|
+
}),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
const result = await Bun.$`${args.vhs} ${tapePath}`.quiet().nothrow();
|
|
138
|
+
if (result.exitCode !== 0 || !(await Bun.file(args.outPng).exists())) {
|
|
139
|
+
const detail = result.stderr.toString().trim() || result.stdout.toString().trim();
|
|
140
|
+
throw new Error(`VHS failed to render the gallery screenshot${detail ? `: ${detail.slice(-600)}` : ""}`);
|
|
141
|
+
}
|
|
142
|
+
} finally {
|
|
143
|
+
await Promise.all([
|
|
144
|
+
fs.promises.rm(ansiPath, { force: true }),
|
|
145
|
+
fs.promises.rm(tapePath, { force: true }),
|
|
146
|
+
fs.promises.rm(gifPath, { force: true }),
|
|
147
|
+
]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
interface TapeArgs {
|
|
152
|
+
gifPath: string;
|
|
153
|
+
outPng: string;
|
|
154
|
+
ansiPath: string;
|
|
155
|
+
widthPx: number;
|
|
156
|
+
heightPx: number;
|
|
157
|
+
font: string;
|
|
158
|
+
fontSize: number;
|
|
159
|
+
themeJson: string;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function buildTape(args: TapeArgs): string {
|
|
163
|
+
// `Output` (a throwaway GIF) is mandatory for VHS to record; the screenshot
|
|
164
|
+
// is captured from the final visible frame. Setup is hidden so the typed
|
|
165
|
+
// `cat` command and shell prompt never appear in the capture, and a trailing
|
|
166
|
+
// `sleep` keeps the shell from drawing a fresh prompt under the output.
|
|
167
|
+
const shellCommand = `clear; cat ${shellSingleQuote(args.ansiPath)}; sleep 120`;
|
|
168
|
+
return `${[
|
|
169
|
+
`Output ${JSON.stringify(args.gifPath)}`,
|
|
170
|
+
`Set Width ${args.widthPx}`,
|
|
171
|
+
`Set Height ${args.heightPx}`,
|
|
172
|
+
`Set FontFamily ${JSON.stringify(args.font)}`,
|
|
173
|
+
`Set FontSize ${args.fontSize}`,
|
|
174
|
+
`Set Padding ${PADDING}`,
|
|
175
|
+
`Set LineHeight ${LINE_HEIGHT}`,
|
|
176
|
+
`Set Theme ${args.themeJson}`,
|
|
177
|
+
"Hide",
|
|
178
|
+
`Type ${JSON.stringify(shellCommand)}`,
|
|
179
|
+
"Enter",
|
|
180
|
+
"Sleep 1.2s",
|
|
181
|
+
"Show",
|
|
182
|
+
"Sleep 400ms",
|
|
183
|
+
`Screenshot ${JSON.stringify(args.outPng)}`,
|
|
184
|
+
].join("\n")}\n`;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Build the VHS terminal theme. Only background/foreground/cursor matter: the
|
|
189
|
+
* gallery emits truecolor (`38;2`/`48;2`) escapes, so the 16-color palette is
|
|
190
|
+
* never consulted — it is filler to satisfy VHS's theme schema.
|
|
191
|
+
*/
|
|
192
|
+
function buildVhsTheme(): string {
|
|
193
|
+
const background = parseAnsiRgb(theme.getBgAnsi("statusLineBg")) ?? (theme.isLight ? "#ffffff" : "#1a1a1a");
|
|
194
|
+
const foreground = theme.isLight ? "#1a1a1a" : "#d4d4d4";
|
|
195
|
+
const selection = theme.isLight ? "#c8d6ff" : "#404862";
|
|
196
|
+
return JSON.stringify({
|
|
197
|
+
name: "omp-gallery",
|
|
198
|
+
background,
|
|
199
|
+
foreground,
|
|
200
|
+
cursor: foreground,
|
|
201
|
+
selection,
|
|
202
|
+
black: "#000000",
|
|
203
|
+
red: "#ff5555",
|
|
204
|
+
green: "#50fa7b",
|
|
205
|
+
yellow: "#f1fa8c",
|
|
206
|
+
blue: "#6272ff",
|
|
207
|
+
magenta: "#ff79c6",
|
|
208
|
+
cyan: "#8be9fd",
|
|
209
|
+
white: "#bfbfbf",
|
|
210
|
+
brightBlack: "#4d4d4d",
|
|
211
|
+
brightRed: "#ff6e6e",
|
|
212
|
+
brightGreen: "#69ff94",
|
|
213
|
+
brightYellow: "#ffffa5",
|
|
214
|
+
brightBlue: "#8aa0ff",
|
|
215
|
+
brightMagenta: "#ff92df",
|
|
216
|
+
brightCyan: "#a4ffff",
|
|
217
|
+
brightWhite: "#ffffff",
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Extract `#rrggbb` from a truecolor SGR escape (`…38;2;r;g;b…` / `…48;2;…`). */
|
|
222
|
+
function parseAnsiRgb(ansi: string): string | undefined {
|
|
223
|
+
const match = /[34]8;2;(\d+);(\d+);(\d+)/.exec(ansi);
|
|
224
|
+
if (!match) return undefined;
|
|
225
|
+
const hex = (value: string) => Number(value).toString(16).padStart(2, "0");
|
|
226
|
+
return `#${hex(match[1])}${hex(match[2])}${hex(match[3])}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** POSIX single-quote a path for embedding in the VHS shell command. */
|
|
230
|
+
function shellSingleQuote(value: string): string {
|
|
231
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Resolve a chunk's PNG path. A single image keeps the bare name (or the exact
|
|
236
|
+
* `out`); multiple images gain a zero-padded `-NN` suffix so they sort and never
|
|
237
|
+
* collide.
|
|
238
|
+
*/
|
|
239
|
+
export function resolveScreenshotOutputPath(
|
|
240
|
+
out: string | undefined,
|
|
241
|
+
baseDir: string,
|
|
242
|
+
index: number,
|
|
243
|
+
total: number,
|
|
244
|
+
): string {
|
|
245
|
+
if (total === 1) {
|
|
246
|
+
return out ? path.resolve(out) : path.join(baseDir, "gallery.png");
|
|
247
|
+
}
|
|
248
|
+
const suffix = String(index + 1).padStart(2, "0");
|
|
249
|
+
if (out) {
|
|
250
|
+
const resolved = path.resolve(out);
|
|
251
|
+
const ext = path.extname(resolved) || ".png";
|
|
252
|
+
const stem = path.basename(resolved, ext);
|
|
253
|
+
return path.join(path.dirname(resolved), `${stem}-${suffix}${ext}`);
|
|
254
|
+
}
|
|
255
|
+
return path.join(baseDir, `gallery-${suffix}.png`);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Group whole tool sections into chunks that stay under `rowBudget` rows. A
|
|
260
|
+
* single section larger than the budget gets its own (taller) image rather than
|
|
261
|
+
* being split mid-renderer.
|
|
262
|
+
*/
|
|
263
|
+
export function chunkGallerySections(sections: GallerySection[], rowBudget: number): GallerySection[][] {
|
|
264
|
+
const chunks: GallerySection[][] = [];
|
|
265
|
+
let current: GallerySection[] = [];
|
|
266
|
+
let currentRows = 0;
|
|
267
|
+
for (const section of sections) {
|
|
268
|
+
const rows = section.lines.length;
|
|
269
|
+
if (current.length > 0 && currentRows + rows > rowBudget) {
|
|
270
|
+
chunks.push(current);
|
|
271
|
+
current = [];
|
|
272
|
+
currentRows = 0;
|
|
273
|
+
}
|
|
274
|
+
current.push(section);
|
|
275
|
+
currentRows += rows;
|
|
276
|
+
}
|
|
277
|
+
if (current.length > 0) chunks.push(current);
|
|
278
|
+
return chunks.length > 0 ? chunks : [[]];
|
|
279
|
+
}
|
package/src/cli-commands.ts
CHANGED
|
@@ -22,6 +22,7 @@ export const commands: CommandEntry[] = [
|
|
|
22
22
|
{ name: "config", load: () => import("./commands/config").then(m => m.default) },
|
|
23
23
|
{ name: "dry-balance", load: () => import("./commands/dry-balance").then(m => m.default) },
|
|
24
24
|
{ name: "grep", load: () => import("./commands/grep").then(m => m.default) },
|
|
25
|
+
{ name: "gallery", load: () => import("./commands/gallery").then(m => m.default) },
|
|
25
26
|
{ name: "grievances", load: () => import("./commands/grievances").then(m => m.default) },
|
|
26
27
|
{ name: "install", load: () => import("./commands/install").then(m => m.default) },
|
|
27
28
|
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render every built-in tool's renderer across its lifecycle states.
|
|
3
|
+
*/
|
|
4
|
+
import { Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
5
|
+
import { GALLERY_STATES, type GalleryState, runGalleryCommand } from "../cli/gallery-cli";
|
|
6
|
+
|
|
7
|
+
export default class Gallery extends Command {
|
|
8
|
+
static description = "Preview tool renderers across streaming, in-progress, success, and failure states";
|
|
9
|
+
|
|
10
|
+
static flags = {
|
|
11
|
+
tool: Flags.string({ char: "t", description: "Render a single tool by name" }),
|
|
12
|
+
state: Flags.string({
|
|
13
|
+
char: "s",
|
|
14
|
+
description: "Render only the given lifecycle state(s)",
|
|
15
|
+
options: [...GALLERY_STATES],
|
|
16
|
+
multiple: true,
|
|
17
|
+
}),
|
|
18
|
+
width: Flags.integer({ char: "w", description: "Render width in columns" }),
|
|
19
|
+
expanded: Flags.boolean({
|
|
20
|
+
char: "e",
|
|
21
|
+
description: "Render the expanded variant of each renderer",
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
plain: Flags.boolean({ description: "Strip ANSI styling from the output", default: false }),
|
|
25
|
+
screenshot: Flags.boolean({
|
|
26
|
+
description:
|
|
27
|
+
"Capture the rendered output as PNG screenshot(s) via VHS instead of printing ANSI (requires vhs)",
|
|
28
|
+
default: false,
|
|
29
|
+
}),
|
|
30
|
+
out: Flags.string({
|
|
31
|
+
char: "o",
|
|
32
|
+
description: "Screenshot output path (with --screenshot); suffixed per image when split across multiple",
|
|
33
|
+
}),
|
|
34
|
+
font: Flags.string({ description: "Screenshot font family (default: JetBrainsMono Nerd Font)" }),
|
|
35
|
+
"font-size": Flags.integer({ description: "Screenshot font size in points (default: 18)" }),
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
async run(): Promise<void> {
|
|
39
|
+
const { flags } = await this.parse(Gallery);
|
|
40
|
+
await runGalleryCommand({
|
|
41
|
+
tool: flags.tool,
|
|
42
|
+
states: flags.state as GalleryState[] | undefined,
|
|
43
|
+
width: flags.width,
|
|
44
|
+
expanded: flags.expanded,
|
|
45
|
+
plain: flags.plain,
|
|
46
|
+
screenshot: flags.screenshot,
|
|
47
|
+
out: flags.out,
|
|
48
|
+
font: flags.font,
|
|
49
|
+
fontSize: flags["font-size"],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
package/src/commands/launch.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Root command for the coding agent CLI.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
5
|
+
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai/effort";
|
|
6
6
|
import { APP_NAME } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { Args, Command, Flags } from "@oh-my-pi/pi-utils/cli";
|
|
8
8
|
import { parseArgs } from "../cli/args";
|
|
@@ -21,6 +21,7 @@ interface AppKeybindings {
|
|
|
21
21
|
"app.clear": true;
|
|
22
22
|
"app.exit": true;
|
|
23
23
|
"app.suspend": true;
|
|
24
|
+
"app.display.reset": true;
|
|
24
25
|
"app.thinking.cycle": true;
|
|
25
26
|
"app.thinking.toggle": true;
|
|
26
27
|
"app.model.cycleForward": true;
|
|
@@ -86,6 +87,10 @@ export const KEYBINDINGS = {
|
|
|
86
87
|
defaultKeys: "ctrl+z",
|
|
87
88
|
description: "Suspend application",
|
|
88
89
|
},
|
|
90
|
+
"app.display.reset": {
|
|
91
|
+
defaultKeys: "ctrl+l",
|
|
92
|
+
description: "Reset terminal display",
|
|
93
|
+
},
|
|
89
94
|
"app.thinking.cycle": {
|
|
90
95
|
defaultKeys: "shift+tab",
|
|
91
96
|
description: "Cycle thinking level",
|
|
@@ -103,7 +108,7 @@ export const KEYBINDINGS = {
|
|
|
103
108
|
description: "Cycle to previous model",
|
|
104
109
|
},
|
|
105
110
|
"app.model.select": {
|
|
106
|
-
defaultKeys: "
|
|
111
|
+
defaultKeys: "alt+m",
|
|
107
112
|
description: "Select model",
|
|
108
113
|
},
|
|
109
114
|
"app.model.selectTemporary": {
|
|
@@ -216,6 +221,7 @@ const KEYBINDING_NAME_MIGRATIONS = {
|
|
|
216
221
|
clear: "app.clear",
|
|
217
222
|
exit: "app.exit",
|
|
218
223
|
suspend: "app.suspend",
|
|
224
|
+
displayReset: "app.display.reset",
|
|
219
225
|
cycleThinkingLevel: "app.thinking.cycle",
|
|
220
226
|
cycleModelForward: "app.model.cycleForward",
|
|
221
227
|
cycleModelBackward: "app.model.cycleBackward",
|
|
@@ -444,7 +450,6 @@ function migrateKeybindingsConfigFile(agentDir: string): void {
|
|
|
444
450
|
|
|
445
451
|
const FOLLOW_UP_KEYBINDING: AppKeybinding = "app.message.followUp";
|
|
446
452
|
const WINDOWS_FOLLOW_UP_FALLBACK_KEY: KeyId = "ctrl+q";
|
|
447
|
-
|
|
448
453
|
function keyListIncludes(keys: KeyId | KeyId[] | undefined, target: KeyId): boolean {
|
|
449
454
|
if (keys === undefined) return false;
|
|
450
455
|
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
@@ -525,10 +530,14 @@ export class KeybindingsManager extends TuiKeybindingsManager {
|
|
|
525
530
|
|
|
526
531
|
getKeys(keybinding: Keybinding): KeyId[] {
|
|
527
532
|
const keys = super.getKeys(keybinding);
|
|
528
|
-
if (keybinding
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
533
|
+
if (keybinding === FOLLOW_UP_KEYBINDING) {
|
|
534
|
+
if (this.#userBindings[FOLLOW_UP_KEYBINDING] !== undefined) return keys;
|
|
535
|
+
if (!userBindingClaimsKey(this.#userBindings, WINDOWS_FOLLOW_UP_FALLBACK_KEY, FOLLOW_UP_KEYBINDING)) {
|
|
536
|
+
return keys;
|
|
537
|
+
}
|
|
538
|
+
return removeKey(keys, WINDOWS_FOLLOW_UP_FALLBACK_KEY);
|
|
539
|
+
}
|
|
540
|
+
return keys;
|
|
532
541
|
}
|
|
533
542
|
|
|
534
543
|
getResolvedBindings(): KeybindingsConfig {
|
|
@@ -58,7 +58,7 @@ const EMPTY_COMPILED_EQUIVALENCE: CompiledEquivalenceConfig = {
|
|
|
58
58
|
};
|
|
59
59
|
const kModelResolutionCache = Symbol("model-equivalence.resolutionCache");
|
|
60
60
|
interface CompiledEquivalenceConfigWithCache extends CompiledEquivalenceConfig {
|
|
61
|
-
[kModelResolutionCache]?:
|
|
61
|
+
[kModelResolutionCache]?: Map<string, ResolvedCanonicalModel>;
|
|
62
62
|
}
|
|
63
63
|
const FAMILY_EXTRACTION_PATTERNS = [
|
|
64
64
|
/(?:^|[/:._-])((?:claude|gemini|gpt|grok|glm|qwen|minimax|kimi|deepseek|llama|gemma|nova|mistral|ministral|pixtral|codestral|devstral|magistral|ernie|doubao|seed|aion|olmo|molmo|nemotron|palmyra|command|codex|coder|o[1345])[-a-z0-9.]+)(?::|$)/i,
|
|
@@ -128,10 +128,18 @@ function normalizeCanonicalIdKey(canonicalId: string): string {
|
|
|
128
128
|
return canonicalId.trim().toLowerCase();
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
function getCanonicalSuffixAliasKey(candidate: string): string {
|
|
132
|
+
return PENALTY_HAS_UPPERCASE.test(candidate) ? normalizeCanonicalIdKey(candidate) : candidate;
|
|
133
|
+
}
|
|
134
|
+
|
|
131
135
|
export function formatCanonicalVariantSelector(model: Model<Api>): string {
|
|
132
136
|
return `${model.provider}/${model.id}`;
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
function getModelResolutionCacheKey(model: Model<Api>): string {
|
|
140
|
+
return `${model.provider}\0${model.id}`;
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
function buildOverrideMap(overrides: Record<string, string> | undefined): Map<string, string> {
|
|
136
144
|
const result = new Map<string, string>();
|
|
137
145
|
if (!overrides) {
|
|
@@ -159,13 +167,24 @@ function buildExclusionSet(exclusions: readonly string[] | undefined): Set<strin
|
|
|
159
167
|
return result;
|
|
160
168
|
}
|
|
161
169
|
|
|
170
|
+
const compiledEquivalenceCache = new WeakMap<ModelEquivalenceConfig, CompiledEquivalenceConfig>();
|
|
162
171
|
function compileEquivalenceConfig(config: ModelEquivalenceConfig | undefined): CompiledEquivalenceConfig {
|
|
172
|
+
if (config) {
|
|
173
|
+
const cached = compiledEquivalenceCache.get(config);
|
|
174
|
+
if (cached) {
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
163
178
|
const overrides = buildOverrideMap(config?.overrides);
|
|
164
179
|
const exclude = buildExclusionSet(config?.exclude);
|
|
165
180
|
if (overrides.size === 0 && exclude.size === 0) {
|
|
166
181
|
return EMPTY_COMPILED_EQUIVALENCE;
|
|
167
182
|
}
|
|
168
|
-
|
|
183
|
+
const compiled: CompiledEquivalenceConfig = { overrides, exclude };
|
|
184
|
+
if (config) {
|
|
185
|
+
compiledEquivalenceCache.set(config, compiled);
|
|
186
|
+
}
|
|
187
|
+
return compiled;
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
function addCanonicalCandidate(candidates: Set<string>, candidate: string): void {
|
|
@@ -277,7 +296,7 @@ function expandCompactSeriesMinorVersions(candidate: string): string[] {
|
|
|
277
296
|
// safely return the same instance. Cap keeps memory bounded under adversarial
|
|
278
297
|
// model-id churn.
|
|
279
298
|
const QUALIFIED_NAMESPACE_SUFFIX_CACHE = new Map<string, string[]>();
|
|
280
|
-
const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP =
|
|
299
|
+
const QUALIFIED_NAMESPACE_SUFFIX_CACHE_CAP = 4096;
|
|
281
300
|
function getQualifiedNamespaceSuffixes(candidate: string): string[] {
|
|
282
301
|
const cached = QUALIFIED_NAMESPACE_SUFFIX_CACHE.get(candidate);
|
|
283
302
|
if (cached !== undefined) {
|
|
@@ -670,7 +689,7 @@ function expandHeavyCanonicalCandidates(normalized: string, queue: string[]): vo
|
|
|
670
689
|
// is unused — kept for signature stability). The returned array is consumed via
|
|
671
690
|
// `.filter` at every callsite, so sharing the cached instance is safe.
|
|
672
691
|
const HEURISTIC_CANDIDATES_CACHE = new Map<string, string[]>();
|
|
673
|
-
const HEURISTIC_CANDIDATES_CACHE_CAP =
|
|
692
|
+
const HEURISTIC_CANDIDATES_CACHE_CAP = 4096;
|
|
674
693
|
function getHeuristicCanonicalCandidates(modelId: string, _officialIds?: ReadonlySet<string>): string[] {
|
|
675
694
|
const cached = HEURISTIC_CANDIDATES_CACHE.get(modelId);
|
|
676
695
|
if (cached !== undefined) {
|
|
@@ -728,10 +747,10 @@ function getPreferredFallbackCanonicalCandidate(modelId: string, candidates: rea
|
|
|
728
747
|
|
|
729
748
|
function resolveCanonicalIdForModel(
|
|
730
749
|
model: Model<Api>,
|
|
750
|
+
selector: string,
|
|
731
751
|
equivalence: CompiledEquivalenceConfig,
|
|
732
752
|
referenceData: CanonicalReferenceData,
|
|
733
753
|
): ResolvedCanonicalModel {
|
|
734
|
-
const selector = formatCanonicalVariantSelector(model);
|
|
735
754
|
const normalizedSelector = normalizeSelectorKey(selector);
|
|
736
755
|
|
|
737
756
|
if (equivalence.overrides.has(normalizedSelector)) {
|
|
@@ -753,9 +772,12 @@ function resolveCanonicalIdForModel(
|
|
|
753
772
|
}
|
|
754
773
|
|
|
755
774
|
const heuristicCandidates = getHeuristicCanonicalCandidates(model.id, referenceData.officialIds);
|
|
756
|
-
const officialMatches = new Set(
|
|
775
|
+
const officialMatches = new Set<string>();
|
|
757
776
|
for (const candidate of heuristicCandidates) {
|
|
758
|
-
|
|
777
|
+
if (referenceData.officialIds.has(candidate)) {
|
|
778
|
+
officialMatches.add(candidate);
|
|
779
|
+
}
|
|
780
|
+
const aliased = referenceData.suffixAliases.get(getCanonicalSuffixAliasKey(candidate));
|
|
759
781
|
if (aliased) {
|
|
760
782
|
officialMatches.add(aliased);
|
|
761
783
|
}
|
|
@@ -814,17 +836,18 @@ export function buildCanonicalModelIndex(
|
|
|
814
836
|
const compiledWithCache = compiledEquivalence as CompiledEquivalenceConfigWithCache;
|
|
815
837
|
let modelCache = compiledWithCache[kModelResolutionCache];
|
|
816
838
|
if (!modelCache) {
|
|
817
|
-
modelCache = new
|
|
839
|
+
modelCache = new Map<string, ResolvedCanonicalModel>();
|
|
818
840
|
compiledWithCache[kModelResolutionCache] = modelCache;
|
|
819
841
|
}
|
|
820
842
|
|
|
821
843
|
for (const model of models) {
|
|
822
|
-
|
|
844
|
+
const selector = formatCanonicalVariantSelector(model);
|
|
845
|
+
const cacheKey = getModelResolutionCacheKey(model);
|
|
846
|
+
let canonical = modelCache.get(cacheKey);
|
|
823
847
|
if (!canonical) {
|
|
824
|
-
canonical = resolveCanonicalIdForModel(model, compiledEquivalence, referenceData);
|
|
825
|
-
modelCache.set(
|
|
848
|
+
canonical = resolveCanonicalIdForModel(model, selector, compiledEquivalence, referenceData);
|
|
849
|
+
modelCache.set(cacheKey, canonical);
|
|
826
850
|
}
|
|
827
|
-
const selector = formatCanonicalVariantSelector(model);
|
|
828
851
|
const variant: CanonicalModelVariant = {
|
|
829
852
|
canonicalId: canonical.id,
|
|
830
853
|
selector,
|
|
@@ -4,34 +4,49 @@ const MODEL_ID_SEGMENT_PATTERN = /[a-z0-9.:-]+/g;
|
|
|
4
4
|
const MODEL_FAMILY_PREFIX_PATTERN =
|
|
5
5
|
/^(claude|gemini|gpt|grok|glm|qwen|deepseek|kimi|mimo|doubao|ernie|gpt-oss|gemma|minimax|step|command|jamba|llama|o[1345])/i;
|
|
6
6
|
|
|
7
|
-
function
|
|
8
|
-
return
|
|
7
|
+
function normalizeModelIdWhitespace(value: string): string {
|
|
8
|
+
return value.trim().replace(/\s+/g, " ");
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
/** Ordering for model-like segments: longest first, ties broken lexicographically. */
|
|
11
12
|
function compareSegmentPreference(left: string, right: string): number {
|
|
12
|
-
|
|
13
|
-
return right.length - left.length;
|
|
14
|
-
}
|
|
15
|
-
return left.localeCompare(right);
|
|
13
|
+
return left.length !== right.length ? right.length - left.length : left.localeCompare(right);
|
|
16
14
|
}
|
|
17
15
|
|
|
18
16
|
export function getModelLikeIdSegments(modelId: string): string[] {
|
|
19
|
-
const
|
|
20
|
-
if (!
|
|
21
|
-
const segments = (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return unique;
|
|
17
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
18
|
+
if (!matches) return [];
|
|
19
|
+
const segments = new Set<string>();
|
|
20
|
+
for (const segment of matches) {
|
|
21
|
+
if (MODEL_FAMILY_PREFIX_PATTERN.test(segment) && /\d/.test(segment)) segments.add(segment);
|
|
22
|
+
}
|
|
23
|
+
return [...segments].sort(compareSegmentPreference);
|
|
27
24
|
}
|
|
28
25
|
|
|
29
26
|
export function getLongestModelLikeIdSegment(modelId: string): string | undefined {
|
|
30
|
-
|
|
27
|
+
const matches = normalizeModelIdWhitespace(modelId).toLowerCase().match(MODEL_ID_SEGMENT_PATTERN);
|
|
28
|
+
if (!matches) return undefined;
|
|
29
|
+
let best: string | undefined;
|
|
30
|
+
for (const segment of matches) {
|
|
31
|
+
if (
|
|
32
|
+
MODEL_FAMILY_PREFIX_PATTERN.test(segment) &&
|
|
33
|
+
/\d/.test(segment) &&
|
|
34
|
+
(best === undefined || compareSegmentPreference(segment, best) < 0)
|
|
35
|
+
) {
|
|
36
|
+
best = segment;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return best;
|
|
31
40
|
}
|
|
32
41
|
|
|
33
|
-
function
|
|
34
|
-
|
|
42
|
+
function hasBracketAffixMarker(value: string): boolean {
|
|
43
|
+
for (let index = 0; index < value.length; index++) {
|
|
44
|
+
const code = value.charCodeAt(index);
|
|
45
|
+
if (code === 91 || code === 93 || code === 0x3010 || code === 0x3011) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return false;
|
|
35
50
|
}
|
|
36
51
|
|
|
37
52
|
/**
|
|
@@ -39,18 +54,20 @@ function normalizeModelIdWhitespace(value: string): string {
|
|
|
39
54
|
* upstream model id, e.g.
|
|
40
55
|
* "[Kiro] claude-opus-4-8" -> "claude-opus-4-8"
|
|
41
56
|
* "[gcli转] gemini-3.1-pro-preview [假流]" -> "gemini-3.1-pro-preview"
|
|
57
|
+
*
|
|
58
|
+
* Candidates are returned most-stripped first: both ends, then leading-only, then trailing-only.
|
|
42
59
|
*/
|
|
43
60
|
export function getBracketStrippedModelIdCandidates(modelId: string): string[] {
|
|
61
|
+
if (!hasBracketAffixMarker(modelId)) return [];
|
|
44
62
|
const normalized = normalizeModelIdWhitespace(modelId);
|
|
45
63
|
if (!normalized) return [];
|
|
46
64
|
|
|
47
|
-
const
|
|
48
|
-
const withoutLeading = normalizeModelIdWhitespace(
|
|
65
|
+
const strippedLeading = normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "");
|
|
66
|
+
const withoutLeading = normalizeModelIdWhitespace(strippedLeading);
|
|
49
67
|
const withoutTrailing = normalizeModelIdWhitespace(normalized.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
50
|
-
const withoutBoth = normalizeModelIdWhitespace(
|
|
51
|
-
normalized.replace(LEADING_BRACKETED_AFFIX_PATTERN, "").replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""),
|
|
52
|
-
);
|
|
68
|
+
const withoutBoth = normalizeModelIdWhitespace(strippedLeading.replace(TRAILING_BRACKETED_AFFIX_PATTERN, ""));
|
|
53
69
|
|
|
70
|
+
const candidates = new Set<string>();
|
|
54
71
|
for (const candidate of [withoutBoth, withoutLeading, withoutTrailing]) {
|
|
55
72
|
if (candidate && candidate !== normalized) {
|
|
56
73
|
candidates.add(candidate);
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
+
import { registerCustomApi, unregisterCustomApis } from "@oh-my-pi/pi-ai/api-registry";
|
|
3
|
+
import { readModelCache } from "@oh-my-pi/pi-ai/model-cache";
|
|
4
|
+
import { createModelManager, type ModelManagerOptions, type ModelRefreshStrategy } from "@oh-my-pi/pi-ai/model-manager";
|
|
5
|
+
import { enrichModelThinking } from "@oh-my-pi/pi-ai/model-thinking";
|
|
6
|
+
import { getBundledModels, getBundledProviders } from "@oh-my-pi/pi-ai/models";
|
|
2
7
|
import {
|
|
3
|
-
type Api,
|
|
4
|
-
type AssistantMessageEventStream,
|
|
5
|
-
type Context,
|
|
6
|
-
createModelManager,
|
|
7
|
-
enrichModelThinking,
|
|
8
|
-
getBundledModels,
|
|
9
|
-
getBundledProviders,
|
|
10
8
|
googleAntigravityModelManagerOptions,
|
|
11
9
|
googleGeminiCliModelManagerOptions,
|
|
12
|
-
type Model,
|
|
13
|
-
type ModelManagerOptions,
|
|
14
|
-
type ModelRefreshStrategy,
|
|
15
10
|
openaiCodexModelManagerOptions,
|
|
16
11
|
PROVIDER_DESCRIPTORS,
|
|
17
|
-
readModelCache,
|
|
18
|
-
registerCustomApi,
|
|
19
|
-
type SimpleStreamOptions,
|
|
20
|
-
type ThinkingConfig,
|
|
21
12
|
UNK_CONTEXT_WINDOW,
|
|
22
13
|
UNK_MAX_TOKENS,
|
|
23
|
-
|
|
24
|
-
} from "@oh-my-pi/pi-ai";
|
|
14
|
+
} from "@oh-my-pi/pi-ai/provider-models";
|
|
15
|
+
import type { Api, Context, Model, SimpleStreamOptions, ThinkingConfig } from "@oh-my-pi/pi-ai/types";
|
|
16
|
+
import type { AssistantMessageEventStream } from "@oh-my-pi/pi-ai/utils/event-stream";
|
|
25
17
|
|
|
26
18
|
// Sentinel for local-only OAuth token (LM Studio, vLLM) — declared inline to avoid loading
|
|
27
19
|
// any provider module at startup. Must match `DEFAULT_LOCAL_TOKEN` in oauth/lm-studio.ts.
|
|
@@ -2609,6 +2601,14 @@ export class ModelRegistry {
|
|
|
2609
2601
|
}
|
|
2610
2602
|
return true;
|
|
2611
2603
|
}
|
|
2604
|
+
|
|
2605
|
+
/**
|
|
2606
|
+
* Clear all cooldown suppressions recorded via {@link suppressSelector}.
|
|
2607
|
+
* Used to reset retry-fallback cooldown state without a full {@link refresh}.
|
|
2608
|
+
*/
|
|
2609
|
+
clearSuppressedSelectors(): void {
|
|
2610
|
+
this.#suppressedSelectors.clear();
|
|
2611
|
+
}
|
|
2612
2612
|
}
|
|
2613
2613
|
|
|
2614
2614
|
/**
|