@oh-my-pi/pi-coding-agent 15.10.4 → 15.10.6
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 +74 -0
- package/dist/types/capability/rule-buckets.d.ts +1 -1
- package/dist/types/capability/rule.d.ts +6 -1
- package/dist/types/cli/update-cli.d.ts +11 -1
- package/dist/types/config/model-registry.d.ts +18 -1
- package/dist/types/discovery/at-imports.d.ts +15 -0
- package/dist/types/edit/diff.d.ts +3 -2
- package/dist/types/eval/__tests__/helpers-local-roots.test.d.ts +1 -0
- package/dist/types/eval/backend.d.ts +7 -0
- package/dist/types/eval/js/context-manager.d.ts +1 -0
- package/dist/types/eval/js/executor.d.ts +2 -0
- package/dist/types/eval/js/index.d.ts +1 -1
- package/dist/types/eval/js/shared/helpers.d.ts +6 -0
- package/dist/types/eval/js/shared/runtime.d.ts +5 -0
- package/dist/types/eval/js/worker-protocol.d.ts +6 -0
- package/dist/types/eval/py/executor.d.ts +7 -0
- package/dist/types/eval/py/index.d.ts +1 -1
- package/dist/types/exa/index.d.ts +1 -19
- package/dist/types/exa/mcp-client.d.ts +10 -3
- package/dist/types/exa/types.d.ts +0 -83
- package/dist/types/export/ttsr.d.ts +14 -0
- package/dist/types/extensibility/extensions/types.d.ts +8 -1
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +1 -1
- package/dist/types/internal-urls/local-protocol.d.ts +10 -0
- package/dist/types/mcp/oauth-flow.d.ts +2 -2
- package/dist/types/modes/components/custom-editor.d.ts +3 -0
- package/dist/types/modes/components/{status-line.d.ts → status-line/component.d.ts} +2 -32
- package/dist/types/modes/components/status-line/index.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +31 -2
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +8 -0
- package/dist/types/modes/image-references.d.ts +8 -3
- package/dist/types/modes/interactive-mode.d.ts +9 -1
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +2 -2
- package/dist/types/session/agent-session.d.ts +0 -2
- package/dist/types/task/render.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +15 -0
- package/dist/types/tools/index.d.ts +17 -2
- package/dist/types/tools/render-utils.d.ts +1 -1
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/utils/block-context.d.ts +35 -0
- package/dist/types/utils/git.d.ts +6 -0
- package/dist/types/utils/image-loading.d.ts +12 -0
- package/package.json +29 -9
- package/src/capability/rule-buckets.ts +4 -2
- package/src/capability/rule.ts +10 -1
- package/src/cli/auth-broker-cli.ts +6 -7
- package/src/cli/auth-gateway-cli.ts +4 -3
- package/src/cli/list-models.ts +5 -0
- package/src/cli/update-cli.ts +138 -16
- package/src/commit/agentic/tools/split-commit.ts +8 -1
- package/src/config/model-provider-priority.ts +1 -0
- package/src/config/model-registry.ts +81 -2
- package/src/debug/index.ts +4 -8
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-rules/index.ts +4 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/helpers.ts +2 -1
- package/src/edit/diff.ts +114 -4
- package/src/edit/hashline/diff.ts +1 -1
- package/src/edit/hashline/execute.ts +1 -1
- package/src/edit/modes/patch.ts +6 -2
- package/src/edit/modes/replace.ts +1 -1
- package/src/edit/renderer.ts +12 -2
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/backend.ts +15 -0
- package/src/eval/js/context-manager.ts +4 -2
- package/src/eval/js/executor.ts +3 -0
- package/src/eval/js/index.ts +7 -1
- package/src/eval/js/shared/helpers.ts +53 -6
- package/src/eval/js/shared/runtime.ts +8 -0
- package/src/eval/js/worker-core.ts +1 -0
- package/src/eval/js/worker-protocol.ts +6 -0
- package/src/eval/py/executor.ts +12 -0
- package/src/eval/py/index.ts +7 -1
- package/src/eval/py/prelude.py +43 -4
- package/src/eval/py/runner.py +1 -0
- package/src/exa/index.ts +1 -26
- package/src/exa/mcp-client.ts +10 -10
- package/src/exa/types.ts +0 -97
- package/src/export/ttsr.ts +122 -1
- package/src/extensibility/extensions/types.ts +8 -1
- package/src/extensibility/legacy-pi-ai-shim.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +1 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +6 -5
- package/src/goals/tools/goal-tool.ts +1 -1
- package/src/internal-urls/docs-index.generated.ts +7 -6
- package/src/internal-urls/local-protocol.ts +13 -0
- package/src/lsp/render.ts +8 -6
- package/src/mcp/oauth-flow.ts +3 -3
- package/src/mcp/render.ts +7 -1
- package/src/modes/components/agent-dashboard.ts +6 -4
- package/src/modes/components/custom-editor.ts +12 -6
- package/src/modes/components/login-dialog.ts +1 -1
- package/src/modes/components/oauth-selector.ts +4 -4
- package/src/modes/components/read-tool-group.ts +10 -3
- package/src/modes/components/{status-line.ts → status-line/component.ts} +18 -40
- package/src/modes/components/status-line/index.ts +1 -0
- package/src/modes/components/status-line/types.ts +23 -8
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/transcript-container.ts +17 -10
- package/src/modes/components/user-message.ts +6 -3
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/event-controller.ts +8 -0
- package/src/modes/controllers/extension-ui-controller.ts +143 -127
- package/src/modes/controllers/input-controller.ts +60 -11
- package/src/modes/controllers/mcp-command-controller.ts +52 -17
- package/src/modes/controllers/selector-controller.ts +4 -11
- package/src/modes/controllers/ssh-command-controller.ts +2 -2
- package/src/modes/image-references.ts +13 -7
- package/src/modes/interactive-mode.ts +35 -3
- package/src/modes/rpc/rpc-mode.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -11
- package/src/modes/theme/theme.ts +95 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/ui-helpers.ts +14 -5
- package/src/prompts/tools/bash.md +1 -1
- package/src/prompts/tools/eval.md +4 -4
- package/src/sdk.ts +31 -14
- package/src/session/agent-session.ts +290 -196
- package/src/session/session-manager.ts +1 -1
- package/src/slash-commands/builtin-registry.ts +9 -1
- package/src/system-prompt.ts +15 -9
- package/src/task/index.ts +9 -1
- package/src/task/render.ts +36 -14
- package/src/tools/ask.ts +14 -5
- package/src/tools/bash-interactive.ts +1 -1
- package/src/tools/bash.ts +14 -2
- package/src/tools/browser/render.ts +5 -2
- package/src/tools/browser/tab-worker.ts +211 -91
- package/src/tools/debug.ts +5 -2
- package/src/tools/eval-render.ts +6 -3
- package/src/tools/eval.ts +1 -1
- package/src/tools/gh-renderer.ts +29 -15
- package/src/tools/index.ts +32 -4
- package/src/tools/inspect-image-renderer.ts +12 -5
- package/src/tools/job.ts +9 -6
- package/src/tools/memory-render.ts +19 -5
- package/src/tools/read.ts +165 -18
- package/src/tools/render-utils.ts +3 -1
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +1 -1
- package/src/tools/ssh.ts +4 -1
- package/src/tools/todo.ts +8 -1
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/write.ts +1 -1
- package/src/tui/code-cell.ts +1 -1
- package/src/utils/block-context.ts +312 -0
- package/src/utils/git.ts +41 -0
- package/src/utils/image-loading.ts +31 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/render.ts +14 -6
- package/dist/types/exa/factory.d.ts +0 -13
- package/dist/types/exa/render.d.ts +0 -19
- package/dist/types/exa/researcher.d.ts +0 -9
- package/dist/types/exa/search.d.ts +0 -9
- package/dist/types/exa/websets.d.ts +0 -9
- package/src/exa/factory.ts +0 -60
- package/src/exa/render.ts +0 -244
- package/src/exa/researcher.ts +0 -36
- package/src/exa/search.ts +0 -47
- package/src/exa/websets.ts +0 -248
package/src/cli/update-cli.ts
CHANGED
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
* Update CLI command handler.
|
|
3
3
|
*
|
|
4
4
|
* Handles `omp update` to check for and install updates.
|
|
5
|
-
* Uses
|
|
5
|
+
* Uses the installer that owns the active omp executable when it can be detected.
|
|
6
6
|
*/
|
|
7
7
|
import * as fs from "node:fs";
|
|
8
|
+
import * as os from "node:os";
|
|
8
9
|
import * as path from "node:path";
|
|
9
10
|
import { pipeline } from "node:stream/promises";
|
|
10
11
|
import { $which, APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
@@ -14,6 +15,8 @@ import { theme } from "../modes/theme/theme";
|
|
|
14
15
|
|
|
15
16
|
const REPO = "can1357/oh-my-pi";
|
|
16
17
|
const PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
18
|
+
const HOMEBREW_FORMULA = "can1357/tap/omp";
|
|
19
|
+
const MISE_TOOL = "github:can1357/oh-my-pi";
|
|
17
20
|
/**
|
|
18
21
|
* Official npm registry origin.
|
|
19
22
|
*
|
|
@@ -102,6 +105,46 @@ async function getBunGlobalBinDir(): Promise<string | undefined> {
|
|
|
102
105
|
}
|
|
103
106
|
}
|
|
104
107
|
|
|
108
|
+
async function getHomebrewFormulaPrefix(): Promise<string | undefined> {
|
|
109
|
+
if (!$which("brew")) return undefined;
|
|
110
|
+
for (const formula of [HOMEBREW_FORMULA, APP_NAME]) {
|
|
111
|
+
try {
|
|
112
|
+
const result = await $`brew --prefix ${formula}`.quiet().nothrow();
|
|
113
|
+
if (result.exitCode !== 0) continue;
|
|
114
|
+
const output = result.text().trim();
|
|
115
|
+
if (output.length > 0) return output;
|
|
116
|
+
} catch {}
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
async function getMiseBinDirs(): Promise<string[]> {
|
|
122
|
+
if (!$which("mise")) return [];
|
|
123
|
+
try {
|
|
124
|
+
const result = await $`mise bin-paths ${MISE_TOOL}`.quiet().nothrow();
|
|
125
|
+
if (result.exitCode !== 0) return [];
|
|
126
|
+
return result
|
|
127
|
+
.text()
|
|
128
|
+
.split(/\r?\n/)
|
|
129
|
+
.map(line => line.trim())
|
|
130
|
+
.filter(line => line.length > 0);
|
|
131
|
+
} catch {
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function getMiseDataDir(): string {
|
|
137
|
+
const override = process.env.MISE_DATA_DIR;
|
|
138
|
+
if (override && override.length > 0) return override;
|
|
139
|
+
if (process.platform === "win32") {
|
|
140
|
+
const localAppData = process.env.LOCALAPPDATA;
|
|
141
|
+
if (localAppData && localAppData.length > 0) return path.join(localAppData, "mise");
|
|
142
|
+
}
|
|
143
|
+
const xdgDataHome = process.env.XDG_DATA_HOME;
|
|
144
|
+
if (xdgDataHome && xdgDataHome.length > 0) return path.join(xdgDataHome, "mise");
|
|
145
|
+
return path.join(os.homedir(), ".local", "share", "mise");
|
|
146
|
+
}
|
|
147
|
+
|
|
105
148
|
function normalizePathForComparison(filePath: string): string {
|
|
106
149
|
const normalized = path.normalize(filePath);
|
|
107
150
|
if (process.platform === "win32") return normalized.toLowerCase();
|
|
@@ -129,34 +172,61 @@ function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
|
129
172
|
// is a junction when Bun is installed via Scoop, so `bun pm bin -g` and the
|
|
130
173
|
// PATH-resolved omp path can refer to the same directory through different
|
|
131
174
|
// strings. path.resolve does not traverse junctions/symlinks; realpath does.
|
|
132
|
-
// Resolve the file
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
|
|
175
|
+
// Resolve both the file and its parent directory: the file catches manager
|
|
176
|
+
// links like Homebrew's `bin/omp -> Cellar/.../bin/omp`; the parent fallback
|
|
177
|
+
// still tolerates fresh install paths where the file does not exist yet.
|
|
136
178
|
const dirReal = tryRealpath(path.resolve(directoryPath));
|
|
137
|
-
if (!
|
|
179
|
+
if (!dirReal) return false;
|
|
180
|
+
const fileReal = tryRealpath(path.resolve(filePath));
|
|
181
|
+
if (fileReal && isPathInDirectoryLexical(fileReal, dirReal)) return true;
|
|
182
|
+
const fileDir = tryRealpath(path.dirname(path.resolve(filePath)));
|
|
183
|
+
if (!fileDir) return false;
|
|
138
184
|
const resolvedFile = path.join(fileDir, path.basename(filePath));
|
|
139
185
|
return isPathInDirectoryLexical(resolvedFile, dirReal);
|
|
140
186
|
}
|
|
141
187
|
|
|
142
|
-
type
|
|
188
|
+
type UpdateMethod = "brew" | "mise" | "bun" | "binary";
|
|
189
|
+
|
|
190
|
+
interface UpdateMethodResolutionOptions {
|
|
191
|
+
homebrewPrefix?: string;
|
|
192
|
+
miseBinDirs?: readonly string[];
|
|
193
|
+
miseDataDir?: string;
|
|
194
|
+
}
|
|
143
195
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
196
|
+
type UpdateTarget = { method: "brew" } | { method: "mise" } | { method: "bun" } | { method: "binary"; path: string };
|
|
197
|
+
|
|
198
|
+
function resolveUpdateMethod(
|
|
199
|
+
ompPath: string,
|
|
200
|
+
bunBinDir: string | undefined,
|
|
201
|
+
options: UpdateMethodResolutionOptions = {},
|
|
202
|
+
): UpdateMethod {
|
|
203
|
+
const { homebrewPrefix, miseBinDirs = [], miseDataDir } = options;
|
|
204
|
+
if (homebrewPrefix && isPathInDirectory(ompPath, path.join(homebrewPrefix, "bin"))) return "brew";
|
|
205
|
+
if (miseBinDirs.some(dir => isPathInDirectory(ompPath, dir))) return "mise";
|
|
206
|
+
if (miseDataDir && isPathInDirectory(ompPath, path.join(miseDataDir, "shims"))) return "mise";
|
|
207
|
+
if (bunBinDir && isPathInDirectory(ompPath, bunBinDir)) return "bun";
|
|
208
|
+
return "binary";
|
|
147
209
|
}
|
|
148
210
|
|
|
149
|
-
export function resolveUpdateMethodForTest(
|
|
150
|
-
|
|
211
|
+
export function resolveUpdateMethodForTest(
|
|
212
|
+
ompPath: string,
|
|
213
|
+
bunBinDir: string | undefined,
|
|
214
|
+
options: UpdateMethodResolutionOptions = {},
|
|
215
|
+
): UpdateMethod {
|
|
216
|
+
return resolveUpdateMethod(ompPath, bunBinDir, options);
|
|
151
217
|
}
|
|
152
218
|
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
153
219
|
const bunBinDir = await getBunGlobalBinDir();
|
|
220
|
+
const homebrewPrefix = await getHomebrewFormulaPrefix();
|
|
221
|
+
const miseAvailable = $which("mise") !== undefined;
|
|
222
|
+
const miseBinDirs = miseAvailable ? await getMiseBinDirs() : [];
|
|
223
|
+
const miseDataDir = miseAvailable ? getMiseDataDir() : undefined;
|
|
154
224
|
const ompPath = resolveOmpPath();
|
|
155
225
|
|
|
156
226
|
if (ompPath) {
|
|
157
|
-
const method = resolveUpdateMethod(ompPath, bunBinDir);
|
|
158
|
-
if (method === "
|
|
159
|
-
return { method
|
|
227
|
+
const method = resolveUpdateMethod(ompPath, bunBinDir, { homebrewPrefix, miseBinDirs, miseDataDir });
|
|
228
|
+
if (method === "binary") return { method, path: ompPath };
|
|
229
|
+
return { method };
|
|
160
230
|
}
|
|
161
231
|
|
|
162
232
|
if (bunBinDir) return { method: "bun" };
|
|
@@ -376,6 +446,18 @@ export function buildBunInstallArgs(expectedVersion: string, nativeTag: string =
|
|
|
376
446
|
return args;
|
|
377
447
|
}
|
|
378
448
|
|
|
449
|
+
export function buildHomebrewUpdateArgs(force: boolean): string[] {
|
|
450
|
+
return [force ? "reinstall" : "upgrade", HOMEBREW_FORMULA];
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
export function buildMiseUpgradeArgs(): string[] {
|
|
454
|
+
return ["upgrade", MISE_TOOL, "--bump"];
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export function buildMiseForceInstallArgs(expectedVersion: string): string[] {
|
|
458
|
+
return ["install", "--force", `${MISE_TOOL}@${expectedVersion}`];
|
|
459
|
+
}
|
|
460
|
+
|
|
379
461
|
/**
|
|
380
462
|
* Update via bun package manager.
|
|
381
463
|
*/
|
|
@@ -390,6 +472,42 @@ async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
|
390
472
|
await printVerification(expectedVersion);
|
|
391
473
|
}
|
|
392
474
|
|
|
475
|
+
async function updateViaHomebrew(expectedVersion: string, force: boolean): Promise<void> {
|
|
476
|
+
console.log(chalk.dim("Updating Homebrew formulae..."));
|
|
477
|
+
const update = await $`brew update`.nothrow();
|
|
478
|
+
if (update.exitCode !== 0) {
|
|
479
|
+
throw new Error(`brew update failed with exit code ${update.exitCode}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log(chalk.dim("Updating via Homebrew..."));
|
|
483
|
+
const args = buildHomebrewUpdateArgs(force);
|
|
484
|
+
const result = await $`brew ${args}`.nothrow();
|
|
485
|
+
if (result.exitCode !== 0) {
|
|
486
|
+
throw new Error(`brew ${args[0]} failed with exit code ${result.exitCode}`);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
await printVerification(expectedVersion);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
async function updateViaMise(expectedVersion: string, force: boolean): Promise<void> {
|
|
493
|
+
console.log(chalk.dim("Updating via mise..."));
|
|
494
|
+
const args = buildMiseUpgradeArgs();
|
|
495
|
+
const result = await $`mise ${args}`.nothrow();
|
|
496
|
+
if (result.exitCode !== 0) {
|
|
497
|
+
throw new Error(`mise upgrade failed with exit code ${result.exitCode}`);
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (force) {
|
|
501
|
+
const forceArgs = buildMiseForceInstallArgs(expectedVersion);
|
|
502
|
+
const forceResult = await $`mise ${forceArgs}`.nothrow();
|
|
503
|
+
if (forceResult.exitCode !== 0) {
|
|
504
|
+
throw new Error(`mise install --force failed with exit code ${forceResult.exitCode}`);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
await printVerification(expectedVersion);
|
|
509
|
+
}
|
|
510
|
+
|
|
393
511
|
/**
|
|
394
512
|
* Download a release binary to a target path, replacing an existing file.
|
|
395
513
|
*/
|
|
@@ -457,7 +575,11 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
457
575
|
// Choose update method based on the prioritized omp binary in PATH
|
|
458
576
|
try {
|
|
459
577
|
const target = await resolveUpdateTarget();
|
|
460
|
-
if (target.method === "
|
|
578
|
+
if (target.method === "brew") {
|
|
579
|
+
await updateViaHomebrew(release.version, opts.force);
|
|
580
|
+
} else if (target.method === "mise") {
|
|
581
|
+
await updateViaMise(release.version, opts.force);
|
|
582
|
+
} else if (target.method === "bun") {
|
|
461
583
|
await updateViaBun(release.version);
|
|
462
584
|
} else {
|
|
463
585
|
await updateViaBinaryAt(target.path, release.version);
|
|
@@ -68,6 +68,7 @@ export function createSplitCommitTool(
|
|
|
68
68
|
const errors: string[] = [];
|
|
69
69
|
const warnings: string[] = [];
|
|
70
70
|
const diffText = await git.diff(cwd, { cached: true });
|
|
71
|
+
const validateHunksForDiff = git.createHunkSelectionValidator(diffText);
|
|
71
72
|
|
|
72
73
|
const commits: SplitCommitGroup[] = params.commits.map((commit, index) => {
|
|
73
74
|
const scope = commit.scope?.trim() || null;
|
|
@@ -102,7 +103,7 @@ export function createSplitCommitTool(
|
|
|
102
103
|
}
|
|
103
104
|
warnings.push(...summaryValidation.warnings.map(warning => `Commit ${index + 1}: ${warning}`));
|
|
104
105
|
warnings.push(...typeValidation.warnings.map(warning => `Commit ${index + 1}: ${warning}`));
|
|
105
|
-
const hunkValidation = validateHunkSelectors(index, changes, files);
|
|
106
|
+
const hunkValidation = validateHunkSelectors(index, changes, files, validateHunksForDiff);
|
|
106
107
|
warnings.push(...hunkValidation.warnings);
|
|
107
108
|
errors.push(...hunkValidation.errors);
|
|
108
109
|
errors.push(...validateDependencies(index, dependencies, params.commits.length));
|
|
@@ -186,6 +187,7 @@ function validateHunkSelectors(
|
|
|
186
187
|
commitIndex: number,
|
|
187
188
|
changes: SplitCommitGroup["changes"],
|
|
188
189
|
files: string[],
|
|
190
|
+
validateHunksForDiff: (changes: SplitCommitGroup["changes"]) => git.HunkSelectionValidationError[],
|
|
189
191
|
): { errors: string[]; warnings: string[] } {
|
|
190
192
|
const errors: string[] = [];
|
|
191
193
|
const warnings: string[] = [];
|
|
@@ -215,6 +217,11 @@ function validateHunkSelectors(
|
|
|
215
217
|
}
|
|
216
218
|
}
|
|
217
219
|
}
|
|
220
|
+
if (errors.length === 0) {
|
|
221
|
+
for (const error of validateHunksForDiff(changes)) {
|
|
222
|
+
errors.push(`${prefix}: ${error.message}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
218
225
|
return { errors, warnings };
|
|
219
226
|
}
|
|
220
227
|
|
|
@@ -96,8 +96,8 @@ const STARTUP_MODEL_CACHE_PROVIDER_IDS: readonly string[] = [
|
|
|
96
96
|
];
|
|
97
97
|
|
|
98
98
|
import type { ApiKeyResolver } from "@oh-my-pi/pi-ai";
|
|
99
|
-
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/
|
|
100
|
-
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/
|
|
99
|
+
import { registerOAuthProvider, unregisterOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
100
|
+
import type { OAuthCredentials, OAuthLoginCallbacks } from "@oh-my-pi/pi-ai/oauth/types";
|
|
101
101
|
import { isRecord, logger } from "@oh-my-pi/pi-utils";
|
|
102
102
|
import { parseModelString, resolveProviderModelReference } from "../config/model-resolver";
|
|
103
103
|
import { isValidThemeColor, type ThemeColor } from "../modes/theme/theme";
|
|
@@ -922,6 +922,9 @@ export class ModelRegistry {
|
|
|
922
922
|
#runtimeProviderOverrides: Map<string, ProviderOverride> = new Map();
|
|
923
923
|
#runtimeProvidersBySource: Map<string, Set<string>> = new Map();
|
|
924
924
|
#runtimeProviderSourceByName: Map<string, string> = new Map();
|
|
925
|
+
// Runtime model managers registered by extensions via fetchDynamicModels.
|
|
926
|
+
// Keyed by provider name; use the same SQLite cache path as builtins.
|
|
927
|
+
#runtimeModelManagers: Map<string, { options: ModelManagerOptions<Api>; sourceId: string }> = new Map();
|
|
925
928
|
#rebuildPending: boolean = false;
|
|
926
929
|
#rebuildSuspended: number = 0;
|
|
927
930
|
|
|
@@ -999,6 +1002,27 @@ export class ModelRegistry {
|
|
|
999
1002
|
}
|
|
1000
1003
|
}
|
|
1001
1004
|
|
|
1005
|
+
/**
|
|
1006
|
+
* Discover models for providers registered at runtime via `fetchDynamicModels`
|
|
1007
|
+
* (extension providers). Merges the discovered catalog into the existing model
|
|
1008
|
+
* set without reloading static models, so dynamically-discovered models from
|
|
1009
|
+
* other providers are preserved. No-op when no runtime providers are registered.
|
|
1010
|
+
*
|
|
1011
|
+
* Drives the same SQLite model cache as built-in providers, so the default
|
|
1012
|
+
* `online-if-uncached` strategy fetches at most once per cache TTL (24 h).
|
|
1013
|
+
*/
|
|
1014
|
+
async refreshRuntimeProviders(strategy: ModelRefreshStrategy = "online-if-uncached"): Promise<void> {
|
|
1015
|
+
if (this.#runtimeModelManagers.size === 0) {
|
|
1016
|
+
return;
|
|
1017
|
+
}
|
|
1018
|
+
this.#suspendRebuild();
|
|
1019
|
+
try {
|
|
1020
|
+
await this.#refreshRuntimeDiscoveries(strategy, new Set(this.#runtimeModelManagers.keys()));
|
|
1021
|
+
} finally {
|
|
1022
|
+
this.#resumeRebuild();
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1002
1026
|
#reloadStaticModels(): void {
|
|
1003
1027
|
const currentMtime = this.#modelsConfigFile.getMtimeMs();
|
|
1004
1028
|
if (currentMtime !== null && currentMtime === this.#lastStaticLoadMtime) {
|
|
@@ -1665,6 +1689,10 @@ export class ModelRegistry {
|
|
|
1665
1689
|
}
|
|
1666
1690
|
options.push(descriptor.createOptions(key));
|
|
1667
1691
|
}
|
|
1692
|
+
// Append runtime model managers registered by extensions via fetchDynamicModels.
|
|
1693
|
+
for (const { options: managerOpts } of this.#runtimeModelManagers.values()) {
|
|
1694
|
+
options.push(managerOpts);
|
|
1695
|
+
}
|
|
1668
1696
|
return options;
|
|
1669
1697
|
}
|
|
1670
1698
|
|
|
@@ -2396,6 +2424,7 @@ export class ModelRegistry {
|
|
|
2396
2424
|
this.#runtimeProviderApiKeys.delete(providerName);
|
|
2397
2425
|
this.#runtimeProviderOverrides.delete(providerName);
|
|
2398
2426
|
this.#runtimeModelOverlays = this.#runtimeModelOverlays.filter(overlay => overlay.provider !== providerName);
|
|
2427
|
+
this.#runtimeModelManagers.delete(providerName);
|
|
2399
2428
|
this.authStorage.removeConfigApiKey(providerName);
|
|
2400
2429
|
}
|
|
2401
2430
|
|
|
@@ -2559,6 +2588,47 @@ export class ModelRegistry {
|
|
|
2559
2588
|
return;
|
|
2560
2589
|
}
|
|
2561
2590
|
|
|
2591
|
+
if (config.fetchDynamicModels) {
|
|
2592
|
+
const fetcher = config.fetchDynamicModels;
|
|
2593
|
+
const providerBaseUrl = config.baseUrl ?? "";
|
|
2594
|
+
const providerApi = config.api;
|
|
2595
|
+
const providerHeaders = config.headers;
|
|
2596
|
+
const providerApiKey = config.apiKey;
|
|
2597
|
+
const providerAuthHeader = config.authHeader;
|
|
2598
|
+
const providerCompat = config.compat;
|
|
2599
|
+
const managerOptions: ModelManagerOptions<Api> = {
|
|
2600
|
+
providerId: providerName as Parameters<typeof createModelManager>[0]["providerId"],
|
|
2601
|
+
staticModels: [],
|
|
2602
|
+
cacheDbPath: this.#cacheDbPath,
|
|
2603
|
+
cacheTtlMs: 24 * 60 * 60 * 1000,
|
|
2604
|
+
dynamicModelsAuthoritative: true,
|
|
2605
|
+
fetchDynamicModels: async () => {
|
|
2606
|
+
const apiKey = await this.authStorage.peekApiKey(providerName);
|
|
2607
|
+
const resolvedKey = isAuthenticated(apiKey) ? apiKey : undefined;
|
|
2608
|
+
const modelDefs = await fetcher(resolvedKey);
|
|
2609
|
+
const results: Model<Api>[] = [];
|
|
2610
|
+
for (const modelDef of modelDefs) {
|
|
2611
|
+
const overlay = buildCustomModelOverlay(
|
|
2612
|
+
providerName,
|
|
2613
|
+
modelDef.baseUrl ?? providerBaseUrl,
|
|
2614
|
+
modelDef.api ?? providerApi,
|
|
2615
|
+
providerHeaders,
|
|
2616
|
+
providerApiKey,
|
|
2617
|
+
providerAuthHeader,
|
|
2618
|
+
providerCompat,
|
|
2619
|
+
undefined,
|
|
2620
|
+
modelDef as CustomModelDefinitionLike,
|
|
2621
|
+
);
|
|
2622
|
+
if (overlay) results.push(finalizeCustomModel(overlay, { useDefaults: true }));
|
|
2623
|
+
}
|
|
2624
|
+
return results;
|
|
2625
|
+
},
|
|
2626
|
+
};
|
|
2627
|
+
this.#runtimeModelManagers.set(providerName, { options: managerOptions, sourceId: sourceId ?? "" });
|
|
2628
|
+
// Discovery is driven by refreshRuntimeProviders() after the drain — not
|
|
2629
|
+
// here, so registration has no network side effect and callers can await.
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2562
2632
|
if (
|
|
2563
2633
|
config.baseUrl ||
|
|
2564
2634
|
config.headers ||
|
|
@@ -2636,6 +2706,15 @@ export interface ProviderConfigInput {
|
|
|
2636
2706
|
getApiKey?(credentials: OAuthCredentials): string;
|
|
2637
2707
|
modifyModels?(models: Model<Api>[], credentials: OAuthCredentials): Model<Api>[];
|
|
2638
2708
|
};
|
|
2709
|
+
/**
|
|
2710
|
+
* Async factory that fetches the live model list from the provider endpoint.
|
|
2711
|
+
* When present, the result is run through the same SQLite model-cache as
|
|
2712
|
+
* built-in providers (keyed by provider name, default 24 h TTL).
|
|
2713
|
+
* The factory receives the resolved API key (undefined when unauthenticated).
|
|
2714
|
+
*/
|
|
2715
|
+
fetchDynamicModels?: (
|
|
2716
|
+
apiKey: string | undefined,
|
|
2717
|
+
) => Promise<readonly NonNullable<ProviderConfigInput["models"]>[number][]>;
|
|
2639
2718
|
models?: Array<{
|
|
2640
2719
|
id: string;
|
|
2641
2720
|
name: string;
|
package/src/debug/index.ts
CHANGED
|
@@ -204,7 +204,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
204
204
|
this.ctx.statusContainer.clear();
|
|
205
205
|
|
|
206
206
|
const block = new TranscriptBlock();
|
|
207
|
-
block.addChild(new Text(theme.fg("success",
|
|
207
|
+
block.addChild(new Text(theme.fg("success", `+ Performance report saved`), 1, 0));
|
|
208
208
|
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
209
209
|
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
210
210
|
this.ctx.present(block);
|
|
@@ -261,7 +261,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
261
261
|
this.ctx.statusContainer.clear();
|
|
262
262
|
|
|
263
263
|
const block = new TranscriptBlock();
|
|
264
|
-
block.addChild(new Text(theme.fg("success",
|
|
264
|
+
block.addChild(new Text(theme.fg("success", `+ Report bundle saved`), 1, 0));
|
|
265
265
|
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
266
266
|
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
267
267
|
this.ctx.present(block);
|
|
@@ -298,7 +298,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
298
298
|
this.ctx.statusContainer.clear();
|
|
299
299
|
|
|
300
300
|
const block = new TranscriptBlock();
|
|
301
|
-
block.addChild(new Text(theme.fg("success",
|
|
301
|
+
block.addChild(new Text(theme.fg("success", `+ Memory report saved`), 1, 0));
|
|
302
302
|
block.addChild(new Text(theme.fg("dim", formatFileHyperlink(result.path)), 1, 0));
|
|
303
303
|
block.addChild(new Text(theme.fg("dim", `Files: ${result.files.length}`), 1, 0));
|
|
304
304
|
this.ctx.present(block);
|
|
@@ -480,11 +480,7 @@ export class DebugSelectorComponent extends Container {
|
|
|
480
480
|
|
|
481
481
|
this.ctx.present([
|
|
482
482
|
new Spacer(1),
|
|
483
|
-
new Text(
|
|
484
|
-
theme.fg("success", `${theme.status.success} Cleared ${result.removed} artifact directories`),
|
|
485
|
-
1,
|
|
486
|
-
0,
|
|
487
|
-
),
|
|
483
|
+
new Text(theme.fg("success", `- Cleared ${result.removed} artifact directories`), 1, 0),
|
|
488
484
|
]);
|
|
489
485
|
} catch (err) {
|
|
490
486
|
loader.stop();
|