@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
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 +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -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/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -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 +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- 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/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- 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/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- 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/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -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 +250 -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/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- 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 +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- 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 +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- 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/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- 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/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- 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 +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- 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";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import analysisSystemPrompt from "../../commit/prompts/analysis-system.md" with { type: "text" };
|
|
@@ -14,7 +14,7 @@ const ConventionalAnalysisTool = createConventionalAnalysisTool(
|
|
|
14
14
|
|
|
15
15
|
export interface ConventionalAnalysisInput {
|
|
16
16
|
model: Model<Api>;
|
|
17
|
-
apiKey:
|
|
17
|
+
apiKey: ApiKey;
|
|
18
18
|
thinkingLevel?: ThinkingLevel;
|
|
19
19
|
contextFiles?: Array<{ path: string; content: string }>;
|
|
20
20
|
userContext?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
@@ -19,7 +19,7 @@ const SummaryTool = {
|
|
|
19
19
|
|
|
20
20
|
export interface SummaryInput {
|
|
21
21
|
model: Model<Api>;
|
|
22
|
-
apiKey:
|
|
22
|
+
apiKey: ApiKey;
|
|
23
23
|
thinkingLevel?: ThinkingLevel;
|
|
24
24
|
commitType: string;
|
|
25
25
|
scope: string | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple, validateToolCall } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import * as z from "zod/v4";
|
|
@@ -25,7 +25,7 @@ export const changelogTool = {
|
|
|
25
25
|
|
|
26
26
|
export interface ChangelogPromptInput {
|
|
27
27
|
model: Model<Api>;
|
|
28
|
-
apiKey:
|
|
28
|
+
apiKey: ApiKey;
|
|
29
29
|
thinkingLevel?: ThinkingLevel;
|
|
30
30
|
changelogPath: string;
|
|
31
31
|
isPackageChangelog: boolean;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { CHANGELOG_CATEGORIES } from "../../commit/types";
|
|
6
6
|
import * as git from "../../utils/git";
|
|
@@ -15,7 +15,7 @@ const DEFAULT_MAX_DIFF_CHARS = 120_000;
|
|
|
15
15
|
export interface ChangelogFlowInput {
|
|
16
16
|
cwd: string;
|
|
17
17
|
model: Model<Api>;
|
|
18
|
-
apiKey:
|
|
18
|
+
apiKey: ApiKey;
|
|
19
19
|
thinkingLevel?: ThinkingLevel;
|
|
20
20
|
stagedFiles: string[];
|
|
21
21
|
dryRun: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { parseFileDiffs } from "../../commit/git/diff";
|
|
5
5
|
import type { ConventionalAnalysis } from "../../commit/types";
|
|
@@ -21,10 +21,10 @@ export interface MapReduceSettings {
|
|
|
21
21
|
|
|
22
22
|
export interface MapReduceInput {
|
|
23
23
|
model: Model<Api>;
|
|
24
|
-
apiKey:
|
|
24
|
+
apiKey: ApiKey;
|
|
25
25
|
thinkingLevel?: ThinkingLevel;
|
|
26
26
|
smolModel: Model<Api>;
|
|
27
|
-
smolApiKey:
|
|
27
|
+
smolApiKey: ApiKey;
|
|
28
28
|
smolThinkingLevel?: ThinkingLevel;
|
|
29
29
|
diff: string;
|
|
30
30
|
stat: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, AssistantMessage, Message, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import fileObserverSystemPrompt from "../../commit/prompts/file-observer-system.md" with { type: "text" };
|
|
@@ -18,7 +18,7 @@ const RETRY_BACKOFF_MS = 1000;
|
|
|
18
18
|
|
|
19
19
|
export interface MapPhaseInput {
|
|
20
20
|
model: Model<Api>;
|
|
21
|
-
apiKey:
|
|
21
|
+
apiKey: ApiKey;
|
|
22
22
|
thinkingLevel?: ThinkingLevel;
|
|
23
23
|
files: FileDiff[];
|
|
24
24
|
config?: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import reduceSystemPrompt from "../../commit/prompts/reduce-system.md" with { type: "text" };
|
|
@@ -12,7 +12,7 @@ const ReduceTool = createConventionalAnalysisTool("Synthesize file observations
|
|
|
12
12
|
|
|
13
13
|
export interface ReducePhaseInput {
|
|
14
14
|
model: Model<Api>;
|
|
15
|
-
apiKey:
|
|
15
|
+
apiKey: ApiKey;
|
|
16
16
|
thinkingLevel?: ThinkingLevel;
|
|
17
17
|
observations: FileObservation[];
|
|
18
18
|
stat: string;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { ApiKeyResolverRegistry } from "../config/api-key-resolver";
|
|
3
4
|
import { MODEL_ROLE_IDS } from "../config/model-registry";
|
|
4
5
|
import {
|
|
5
6
|
type ModelLookupRegistry,
|
|
@@ -12,13 +13,19 @@ import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
|
12
13
|
|
|
13
14
|
export interface ResolvedCommitModel {
|
|
14
15
|
model: Model<Api>;
|
|
15
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Resolver for the model's bearer: re-resolves on 401 / usage-limit so the
|
|
18
|
+
* whole commit pipeline (analysis, map/reduce, changelog) inherits the
|
|
19
|
+
* central force-refresh + account-rotation policy.
|
|
20
|
+
*/
|
|
21
|
+
apiKey: ApiKey;
|
|
16
22
|
thinkingLevel?: ThinkingLevel;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
|
-
type CommitModelRegistry = ModelLookupRegistry &
|
|
20
|
-
|
|
21
|
-
|
|
25
|
+
type CommitModelRegistry = ModelLookupRegistry &
|
|
26
|
+
ApiKeyResolverRegistry & {
|
|
27
|
+
getApiKey: (model: Model<Api>) => Promise<string | undefined>;
|
|
28
|
+
};
|
|
22
29
|
|
|
23
30
|
export async function resolvePrimaryModel(
|
|
24
31
|
override: string | undefined,
|
|
@@ -38,20 +45,32 @@ export async function resolvePrimaryModel(
|
|
|
38
45
|
if (!apiKey) {
|
|
39
46
|
throw new Error(`No API key available for model ${model.provider}/${model.id}`);
|
|
40
47
|
}
|
|
41
|
-
return {
|
|
48
|
+
return {
|
|
49
|
+
model,
|
|
50
|
+
apiKey: modelRegistry.resolver(model.provider, { baseUrl: model.baseUrl }),
|
|
51
|
+
thinkingLevel: resolved?.thinkingLevel,
|
|
52
|
+
};
|
|
42
53
|
}
|
|
43
54
|
|
|
44
55
|
export async function resolveSmolModel(
|
|
45
56
|
settings: Settings,
|
|
46
57
|
modelRegistry: CommitModelRegistry,
|
|
47
58
|
fallbackModel: Model<Api>,
|
|
48
|
-
fallbackApiKey:
|
|
59
|
+
fallbackApiKey: ApiKey,
|
|
49
60
|
): Promise<ResolvedCommitModel> {
|
|
50
61
|
const available = modelRegistry.getAvailable();
|
|
51
62
|
const resolvedSmol = resolveRoleSelection(["smol"], settings, available, modelRegistry);
|
|
52
63
|
if (resolvedSmol?.model) {
|
|
53
64
|
const apiKey = await modelRegistry.getApiKey(resolvedSmol.model);
|
|
54
|
-
if (apiKey)
|
|
65
|
+
if (apiKey) {
|
|
66
|
+
return {
|
|
67
|
+
model: resolvedSmol.model,
|
|
68
|
+
apiKey: modelRegistry.resolver(resolvedSmol.model.provider, {
|
|
69
|
+
baseUrl: resolvedSmol.model.baseUrl,
|
|
70
|
+
}),
|
|
71
|
+
thinkingLevel: resolvedSmol.thinkingLevel,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
const matchPreferences = { usageOrder: settings.getStorage()?.getModelUsageOrder() };
|
|
@@ -59,7 +78,12 @@ export async function resolveSmolModel(
|
|
|
59
78
|
const candidate = parseModelPattern(pattern, available, matchPreferences, { modelRegistry }).model;
|
|
60
79
|
if (!candidate) continue;
|
|
61
80
|
const apiKey = await modelRegistry.getApiKey(candidate);
|
|
62
|
-
if (apiKey)
|
|
81
|
+
if (apiKey) {
|
|
82
|
+
return {
|
|
83
|
+
model: candidate,
|
|
84
|
+
apiKey: modelRegistry.resolver(candidate.provider, { baseUrl: candidate.baseUrl }),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
63
87
|
}
|
|
64
88
|
|
|
65
89
|
return { model: fallbackModel, apiKey: fallbackApiKey };
|
package/src/commit/pipeline.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import type { Api, ApiKey, Model } from "@oh-my-pi/pi-ai";
|
|
4
4
|
import { getProjectDir, logger, prompt } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { ModelRegistry } from "../config/model-registry";
|
|
6
6
|
import { Settings } from "../config/settings";
|
|
@@ -145,10 +145,10 @@ async function generateAnalysis(input: {
|
|
|
145
145
|
contextFiles: Array<{ path: string; content: string }>;
|
|
146
146
|
userContext?: string;
|
|
147
147
|
primaryModel: Model<Api>;
|
|
148
|
-
primaryApiKey:
|
|
148
|
+
primaryApiKey: ApiKey;
|
|
149
149
|
primaryThinkingLevel?: ThinkingLevel;
|
|
150
150
|
smolModel: Model<Api>;
|
|
151
|
-
smolApiKey:
|
|
151
|
+
smolApiKey: ApiKey;
|
|
152
152
|
smolThinkingLevel?: ThinkingLevel;
|
|
153
153
|
commitSettings: {
|
|
154
154
|
mapReduceEnabled: boolean;
|
|
@@ -206,7 +206,7 @@ async function generateSummaryWithRetry(input: {
|
|
|
206
206
|
analysis: ConventionalAnalysis;
|
|
207
207
|
stat: string;
|
|
208
208
|
model: Model<Api>;
|
|
209
|
-
apiKey:
|
|
209
|
+
apiKey: ApiKey;
|
|
210
210
|
thinkingLevel?: ThinkingLevel;
|
|
211
211
|
userContext?: string;
|
|
212
212
|
}): Promise<{ summary: string }> {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ApiKeyResolver, AuthStorage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
|
|
3
|
+
export interface ApiKeyResolverOptions {
|
|
4
|
+
/** Session id for credential stickiness; read at resolve time by the caller. */
|
|
5
|
+
sessionId?: string;
|
|
6
|
+
/** Provider base URL hint forwarded to the auth-storage cascade. */
|
|
7
|
+
baseUrl?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Minimal slice of `ModelRegistry` the resolver needs. Typed structurally so
|
|
12
|
+
* narrower registry shells (e.g. the commit pipeline's `CommitModelRegistry`)
|
|
13
|
+
* can build resolvers without depending on the full class.
|
|
14
|
+
*/
|
|
15
|
+
export interface ApiKeyResolverRegistry {
|
|
16
|
+
getApiKeyForProvider(
|
|
17
|
+
provider: string,
|
|
18
|
+
sessionId?: string,
|
|
19
|
+
options?: { baseUrl?: string; forceRefresh?: boolean; signal?: AbortSignal },
|
|
20
|
+
): Promise<string | undefined>;
|
|
21
|
+
authStorage: Pick<AuthStorage, "rotateSessionCredential">;
|
|
22
|
+
/**
|
|
23
|
+
* Build an {@link ApiKeyResolver} implementing the central a/b/c auth-retry
|
|
24
|
+
* policy: initial → resolve; step (b) → force-refresh same account; step (c)
|
|
25
|
+
* → rotate to a sibling credential, then re-resolve.
|
|
26
|
+
*
|
|
27
|
+
* The resolver is stateless (safe to reuse across requests). Callers that
|
|
28
|
+
* need the initial key for a guard can call `resolveApiKeyOnce(resolver)`.
|
|
29
|
+
*/
|
|
30
|
+
resolver(provider: string, options?: ApiKeyResolverOptions): ApiKeyResolver;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Default implementation of {@link ApiKeyResolverRegistry.resolver}.
|
|
35
|
+
* Also usable standalone for structural registries that don't carry the method.
|
|
36
|
+
*/
|
|
37
|
+
export function createApiKeyResolver(
|
|
38
|
+
registry: Pick<ApiKeyResolverRegistry, "getApiKeyForProvider" | "authStorage">,
|
|
39
|
+
provider: string,
|
|
40
|
+
options: ApiKeyResolverOptions = {},
|
|
41
|
+
): ApiKeyResolver {
|
|
42
|
+
const { sessionId, baseUrl } = options;
|
|
43
|
+
return async ({ lastChance, error, signal }) => {
|
|
44
|
+
if (error === undefined) {
|
|
45
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl });
|
|
46
|
+
}
|
|
47
|
+
if (lastChance) {
|
|
48
|
+
// Account constraint (401 / usage / account-rate-limit): rotate to a
|
|
49
|
+
// sibling credential. We do NOT honor any retry-after here — if a
|
|
50
|
+
// sibling exists we switch immediately; the precise no-sibling backoff
|
|
51
|
+
// is owned by `markUsageLimitReached` (default + server usage-report
|
|
52
|
+
// reset) and the outer whole-turn retry layer.
|
|
53
|
+
await registry.authStorage.rotateSessionCredential(provider, sessionId, { error, signal });
|
|
54
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl });
|
|
55
|
+
}
|
|
56
|
+
return registry.getApiKeyForProvider(provider, sessionId, { baseUrl, forceRefresh: true, signal });
|
|
57
|
+
};
|
|
58
|
+
}
|