@oh-my-pi/pi-coding-agent 15.5.3 → 15.5.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 +55 -0
- package/dist/types/config/settings-schema.d.ts +27 -0
- package/dist/types/config.d.ts +31 -5
- package/dist/types/edit/file-snapshot-store.d.ts +18 -0
- package/dist/types/edit/hashline/diff.d.ts +35 -0
- package/dist/types/edit/hashline/execute.d.ts +28 -0
- package/dist/types/edit/hashline/filesystem.d.ts +57 -0
- package/dist/types/edit/hashline/index.d.ts +4 -0
- package/dist/types/edit/hashline/params.d.ts +11 -0
- package/dist/types/edit/index.d.ts +4 -3
- package/dist/types/edit/normalize.d.ts +4 -16
- package/dist/types/extensibility/legacy-pi-ai-shim.d.ts +23 -0
- package/dist/types/index.d.ts +0 -1
- package/dist/types/tools/fetch.d.ts +3 -0
- package/dist/types/tools/find.d.ts +7 -0
- package/dist/types/tools/index.d.ts +6 -5
- package/dist/types/tools/path-utils.d.ts +18 -0
- package/dist/types/utils/changelog.d.ts +8 -3
- package/package.json +8 -15
- package/scripts/build-binary.ts +11 -0
- package/src/config/settings-schema.ts +32 -0
- package/src/config.ts +42 -15
- package/src/edit/diff.ts +5 -3
- package/src/edit/file-snapshot-store.ts +22 -0
- package/src/edit/hashline/diff.ts +95 -0
- package/src/edit/hashline/execute.ts +181 -0
- package/src/edit/hashline/filesystem.ts +129 -0
- package/src/edit/hashline/index.ts +4 -0
- package/src/edit/hashline/params.ts +18 -0
- package/src/edit/index.ts +16 -27
- package/src/edit/normalize.ts +11 -41
- package/src/edit/renderer.ts +15 -8
- package/src/edit/streaming.ts +20 -134
- package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +47 -3
- package/src/index.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +1 -1
- package/src/main.ts +2 -1
- package/src/modes/rpc/rpc-client.ts +3 -1
- package/src/prompts/tools/find.md +3 -2
- package/src/sdk.ts +8 -1
- package/src/session/agent-session.ts +18 -2
- package/src/tools/ast-edit.ts +1 -1
- package/src/tools/ast-grep.ts +3 -3
- package/src/tools/fetch.ts +93 -50
- package/src/tools/find.ts +38 -6
- package/src/tools/index.ts +6 -5
- package/src/tools/path-utils.ts +81 -0
- package/src/tools/read.ts +71 -75
- package/src/tools/search.ts +136 -17
- package/src/tools/write.ts +3 -3
- package/src/utils/changelog.ts +11 -3
- package/src/utils/file-mentions.ts +1 -1
- package/dist/types/edit/file-read-cache.d.ts +0 -36
- package/dist/types/hashline/anchors.d.ts +0 -26
- package/dist/types/hashline/apply.d.ts +0 -14
- package/dist/types/hashline/constants.d.ts +0 -48
- package/dist/types/hashline/diff-preview.d.ts +0 -2
- package/dist/types/hashline/diff.d.ts +0 -16
- package/dist/types/hashline/execute.d.ts +0 -4
- package/dist/types/hashline/executor.d.ts +0 -56
- package/dist/types/hashline/hash.d.ts +0 -76
- package/dist/types/hashline/index.d.ts +0 -14
- package/dist/types/hashline/input.d.ts +0 -4
- package/dist/types/hashline/prefixes.d.ts +0 -7
- package/dist/types/hashline/recovery.d.ts +0 -21
- package/dist/types/hashline/stream.d.ts +0 -2
- package/dist/types/hashline/tokenizer.d.ts +0 -94
- package/dist/types/hashline/types.d.ts +0 -75
- package/src/edit/file-read-cache.ts +0 -138
- package/src/hashline/anchors.ts +0 -104
- package/src/hashline/apply.ts +0 -790
- package/src/hashline/bigrams.json +0 -649
- package/src/hashline/constants.ts +0 -60
- package/src/hashline/diff-preview.ts +0 -42
- package/src/hashline/diff.ts +0 -82
- package/src/hashline/execute.ts +0 -334
- package/src/hashline/executor.ts +0 -347
- package/src/hashline/grammar.lark +0 -22
- package/src/hashline/hash.ts +0 -131
- package/src/hashline/index.ts +0 -14
- package/src/hashline/input.ts +0 -137
- package/src/hashline/prefixes.ts +0 -111
- package/src/hashline/recovery.ts +0 -139
- package/src/hashline/stream.ts +0 -123
- package/src/hashline/tokenizer.ts +0 -473
- package/src/hashline/types.ts +0 -66
- package/src/prompts/tools/hashline.md +0 -83
package/src/edit/normalize.ts
CHANGED
|
@@ -1,51 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text normalization utilities for the edit tool.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Whitespace, Unicode, and indentation helpers. Line-ending and BOM
|
|
5
|
+
* primitives live in `@oh-my-pi/hashline` and are re-exported here so
|
|
6
|
+
* existing consumers see one stable surface.
|
|
5
7
|
*/
|
|
6
8
|
|
|
7
9
|
import { padding } from "@oh-my-pi/pi-tui";
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
const crlfIdx = content.indexOf("\r\n");
|
|
18
|
-
const lfIdx = content.indexOf("\n");
|
|
19
|
-
if (lfIdx === -1) return "\n";
|
|
20
|
-
if (crlfIdx === -1) return "\n";
|
|
21
|
-
return crlfIdx < lfIdx ? "\r\n" : "\n";
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Normalize all line endings to LF */
|
|
25
|
-
export function normalizeToLF(text: string): string {
|
|
26
|
-
return text.replace(/\r\n?/g, "\n");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Restore line endings to the specified type */
|
|
30
|
-
export function restoreLineEndings(text: string, ending: LineEnding): string {
|
|
31
|
-
return ending === "\r\n" ? text.replace(/\n/g, "\r\n") : text;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
35
|
-
// BOM Handling
|
|
36
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
37
|
-
|
|
38
|
-
export interface BomResult {
|
|
39
|
-
/** The BOM character if present, empty string otherwise */
|
|
40
|
-
bom: string;
|
|
41
|
-
/** The text without the BOM */
|
|
42
|
-
text: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Strip UTF-8 BOM if present */
|
|
46
|
-
export function stripBom(content: string): BomResult {
|
|
47
|
-
return content.startsWith("\uFEFF") ? { bom: "\uFEFF", text: content.slice(1) } : { bom: "", text: content };
|
|
48
|
-
}
|
|
11
|
+
export {
|
|
12
|
+
type BomResult,
|
|
13
|
+
detectLineEnding,
|
|
14
|
+
type LineEnding,
|
|
15
|
+
normalizeToLF,
|
|
16
|
+
restoreLineEndings,
|
|
17
|
+
stripBom,
|
|
18
|
+
} from "@oh-my-pi/hashline";
|
|
49
19
|
|
|
50
20
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
51
21
|
// Whitespace Utilities
|
package/src/edit/renderer.ts
CHANGED
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
* Edit tool renderer and LSP batching helpers.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { HL_FILE_PREFIX } from "@oh-my-pi/hashline";
|
|
5
6
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
7
|
import { Text, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
|
-
import { HL_FILE_PREFIX } from "../hashline/hash";
|
|
10
10
|
import type { FileDiagnosticsResult } from "../lsp";
|
|
11
11
|
import { renderDiff as renderDiffColored } from "../modes/components/diff";
|
|
12
12
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
@@ -235,14 +235,21 @@ function renderPlainTextPreview(text: string, uiTheme: Theme, filePath?: string)
|
|
|
235
235
|
|
|
236
236
|
function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, label = "streaming"): string {
|
|
237
237
|
if (!diff) return "";
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
const
|
|
238
|
+
// Hunk-aware truncation keeps the change rows themselves visible and
|
|
239
|
+
// trims surrounding context proportionally so a multi-hunk diff doesn't
|
|
240
|
+
// turn into just the tail of the last hunk while streaming.
|
|
241
|
+
const {
|
|
242
|
+
text: truncatedDiff,
|
|
243
|
+
hiddenHunks,
|
|
244
|
+
hiddenLines,
|
|
245
|
+
} = truncateDiffByHunk(diff, PREVIEW_LIMITS.DIFF_COLLAPSED_HUNKS, EDIT_STREAMING_PREVIEW_LINES);
|
|
242
246
|
let text = "\n\n";
|
|
243
|
-
text += renderDiffColored(
|
|
244
|
-
if (
|
|
245
|
-
|
|
247
|
+
text += renderDiffColored(truncatedDiff, { filePath: rawPath });
|
|
248
|
+
if (hiddenHunks > 0 || hiddenLines > 0) {
|
|
249
|
+
const remainder: string[] = [];
|
|
250
|
+
if (hiddenHunks > 0) remainder.push(`${hiddenHunks} more hunks`);
|
|
251
|
+
if (hiddenLines > 0) remainder.push(`${hiddenLines} more lines`);
|
|
252
|
+
text += uiTheme.fg("dim", `\n… (${label} +${remainder.join(", ")})`);
|
|
246
253
|
} else {
|
|
247
254
|
text += uiTheme.fg("dim", `\n(${label})`);
|
|
248
255
|
}
|
package/src/edit/streaming.ts
CHANGED
|
@@ -13,22 +13,18 @@
|
|
|
13
13
|
* the injected `editMode` rather than probing argument shape.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
17
16
|
import {
|
|
18
17
|
ABORT_MARKER,
|
|
19
18
|
BEGIN_PATCH_MARKER,
|
|
20
|
-
computeHashlineDiff,
|
|
21
|
-
computeHashlineSectionDiff,
|
|
22
19
|
containsRecognizableHashlineOperations,
|
|
23
20
|
END_PATCH_MARKER,
|
|
24
|
-
type HashlineInputSection,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "../hashline";
|
|
21
|
+
type PatchSection as HashlineInputSection,
|
|
22
|
+
Patch as HashlinePatch,
|
|
23
|
+
} from "@oh-my-pi/hashline";
|
|
28
24
|
import type { Theme } from "../modes/theme/theme";
|
|
29
|
-
import { replaceTabs, truncateToWidth } from "../tools/render-utils";
|
|
30
25
|
import { type EditMode, resolveEditMode } from "../utils/edit-mode";
|
|
31
26
|
import { computeEditDiff, type DiffError, type DiffResult } from "./diff";
|
|
27
|
+
import { computeHashlineDiff, computeHashlineSectionDiff } from "./hashline/diff";
|
|
32
28
|
import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
|
|
33
29
|
import { computePatchDiff, type PatchEditEntry } from "./modes/patch";
|
|
34
30
|
import type { ReplaceEditEntry } from "./modes/replace";
|
|
@@ -74,48 +70,6 @@ export interface EditStreamingStrategy<Args = unknown> {
|
|
|
74
70
|
renderStreamingFallback(args: Args, uiTheme: Theme): string;
|
|
75
71
|
}
|
|
76
72
|
|
|
77
|
-
const STREAMING_FALLBACK_LINES = 12;
|
|
78
|
-
const STREAMING_FALLBACK_WIDTH = 80;
|
|
79
|
-
|
|
80
|
-
// Streaming-preview classification reuses one tokenizer instance for the
|
|
81
|
-
// stateless predicates and `tokenize`/`tokenizeAll` helpers; instances are
|
|
82
|
-
// cheap, but keeping a single module-level reference matches the rest of
|
|
83
|
-
// the hashline package.
|
|
84
|
-
const HASHLINE_TOKENIZER = new HashlineTokenizer();
|
|
85
|
-
|
|
86
|
-
function trimHashlineStreamingSyntax(lines: string[]): string[] {
|
|
87
|
-
let index = lines.findIndex(line => line.trim().length > 0);
|
|
88
|
-
if (index === -1) return [];
|
|
89
|
-
|
|
90
|
-
if (HASHLINE_TOKENIZER.tokenize(lines[index]).kind === "envelope-begin") {
|
|
91
|
-
index++;
|
|
92
|
-
while (index < lines.length && lines[index].trim().length === 0) index++;
|
|
93
|
-
}
|
|
94
|
-
if (index < lines.length && HASHLINE_TOKENIZER.tokenize(lines[index]).kind === "header") {
|
|
95
|
-
index++;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return lines.slice(index).filter(line => !HASHLINE_TOKENIZER.isEnvelopeMarker(line));
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function renderHashlineInputFallback(input: string, uiTheme: Theme): string {
|
|
102
|
-
const lines = trimHashlineStreamingSyntax(sanitizeText(input).split("\n"));
|
|
103
|
-
if (!lines.some(line => line.trim().length > 0)) return "";
|
|
104
|
-
|
|
105
|
-
const displayLines = lines.slice(-STREAMING_FALLBACK_LINES);
|
|
106
|
-
const hidden = lines.length - displayLines.length;
|
|
107
|
-
let text = "\n\n";
|
|
108
|
-
text += displayLines
|
|
109
|
-
.map(line => uiTheme.fg("toolOutput", truncateToWidth(replaceTabs(line), STREAMING_FALLBACK_WIDTH)))
|
|
110
|
-
.join("\n");
|
|
111
|
-
if (hidden > 0) {
|
|
112
|
-
text += uiTheme.fg("dim", `\n… (streaming +${hidden} lines)`);
|
|
113
|
-
} else {
|
|
114
|
-
text += uiTheme.fg("dim", "\n(streaming)");
|
|
115
|
-
}
|
|
116
|
-
return text;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
73
|
// -----------------------------------------------------------------------------
|
|
120
74
|
// Partial-JSON handling
|
|
121
75
|
// -----------------------------------------------------------------------------
|
|
@@ -274,7 +228,6 @@ const patchStrategy: EditStreamingStrategy<PatchArgs> = {
|
|
|
274
228
|
|
|
275
229
|
interface HashlineArgs {
|
|
276
230
|
input?: string;
|
|
277
|
-
path?: string;
|
|
278
231
|
__partialJson?: string;
|
|
279
232
|
}
|
|
280
233
|
|
|
@@ -354,75 +307,6 @@ function buildApplyPatchNaturalOrderPreviews(input: string): PerFileDiffPreview[
|
|
|
354
307
|
return previews.length > 0 ? previews : null;
|
|
355
308
|
}
|
|
356
309
|
|
|
357
|
-
/**
|
|
358
|
-
* Hashline equivalent: emit each payload line as a `+added` line in the
|
|
359
|
-
* order the model typed it. We deliberately omit op headers and removal
|
|
360
|
-
* targets from the streaming preview because their content lives in the file
|
|
361
|
-
* and would require a costly re-apply per tick; the complete unified diff is
|
|
362
|
-
* shown once streaming finishes.
|
|
363
|
-
*/
|
|
364
|
-
function buildHashlineNaturalOrderPreviews(
|
|
365
|
-
input: string,
|
|
366
|
-
defaultPath: string | undefined,
|
|
367
|
-
): PerFileDiffPreview[] | null {
|
|
368
|
-
const groups = new Map<string, string[]>();
|
|
369
|
-
let currentPath = defaultPath ?? "";
|
|
370
|
-
const ensure = (sectionPath: string): string[] => {
|
|
371
|
-
let bucket = groups.get(sectionPath);
|
|
372
|
-
if (!bucket) {
|
|
373
|
-
bucket = [];
|
|
374
|
-
groups.set(sectionPath, bucket);
|
|
375
|
-
}
|
|
376
|
-
return bucket;
|
|
377
|
-
};
|
|
378
|
-
|
|
379
|
-
// Per-call instance: the streaming preview re-runs each tick with the
|
|
380
|
-
// cumulative input, and we need the line counter to start at 1. A
|
|
381
|
-
// dedicated tokenizer keeps the shared HASHLINE_TOKENIZER above free
|
|
382
|
-
// for stateless predicate use elsewhere in this module.
|
|
383
|
-
const streamer = new HashlineTokenizer();
|
|
384
|
-
for (const token of streamer.tokenizeAll(input)) {
|
|
385
|
-
switch (token.kind) {
|
|
386
|
-
case "envelope-begin":
|
|
387
|
-
case "envelope-end":
|
|
388
|
-
case "abort":
|
|
389
|
-
case "op-delete":
|
|
390
|
-
continue;
|
|
391
|
-
case "blank":
|
|
392
|
-
case "raw":
|
|
393
|
-
continue;
|
|
394
|
-
case "header":
|
|
395
|
-
currentPath = token.path;
|
|
396
|
-
if (currentPath) ensure(currentPath);
|
|
397
|
-
continue;
|
|
398
|
-
case "op-insert":
|
|
399
|
-
case "op-replace":
|
|
400
|
-
// Inline body on the op line itself (`N↓payload`, `A-B:payload`) is
|
|
401
|
-
// payload content that just happens to share a line with the op
|
|
402
|
-
// header — render it the same as a standalone payload token so
|
|
403
|
-
// the very first character the model types after the sigil shows
|
|
404
|
-
// up in the streaming preview. Without this, the preview is
|
|
405
|
-
// empty until a newline arrives, and the renderer falls back to
|
|
406
|
-
// raw input ("A-B: bla bla bla") instead of "+ bla bla bla".
|
|
407
|
-
if (!currentPath || token.inlineBody === undefined) continue;
|
|
408
|
-
ensure(currentPath).push(`+${token.inlineBody}`);
|
|
409
|
-
continue;
|
|
410
|
-
case "payload":
|
|
411
|
-
if (!currentPath) continue;
|
|
412
|
-
ensure(currentPath).push(`+${token.text}`);
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (groups.size === 0) return null;
|
|
418
|
-
const previews: PerFileDiffPreview[] = [];
|
|
419
|
-
for (const [sectionPath, body] of groups) {
|
|
420
|
-
if (body.length === 0) continue;
|
|
421
|
-
previews.push({ path: sectionPath, diff: body.join("\n") });
|
|
422
|
-
}
|
|
423
|
-
return previews.length > 0 ? previews : null;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
310
|
const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
427
311
|
extractCompleteEdits(args) {
|
|
428
312
|
return args;
|
|
@@ -431,25 +315,21 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
431
315
|
if (typeof args.input !== "string" || args.input.length === 0) return null;
|
|
432
316
|
const input = trimTrailingPartialLine(args.input, ctx.isStreaming);
|
|
433
317
|
if (input.length === 0) return null;
|
|
434
|
-
if (ctx.isStreaming) {
|
|
435
|
-
// Skip the costly per-tick re-apply and avoid `Diff.structuredPatch`
|
|
436
|
-
// reordering by showing payload lines in input order.
|
|
437
|
-
return buildHashlineNaturalOrderPreviews(input, args.path);
|
|
438
|
-
}
|
|
439
318
|
ctx.signal.throwIfAborted();
|
|
440
319
|
|
|
441
|
-
let sections: HashlineInputSection[];
|
|
320
|
+
let sections: readonly HashlineInputSection[];
|
|
442
321
|
try {
|
|
443
|
-
sections =
|
|
322
|
+
sections = HashlinePatch.parse(input, { cwd: ctx.cwd }).sections;
|
|
444
323
|
} catch {
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
|
|
324
|
+
// While streaming, the trailing op may still be mid-typed and fail
|
|
325
|
+
// to parse; suppress until the next chunk arrives. Once args are
|
|
326
|
+
// complete, surface the error so the model sees what went wrong.
|
|
327
|
+
if (ctx.isStreaming) return null;
|
|
328
|
+
const result = await computeHashlineDiff({ input }, ctx.cwd, {
|
|
448
329
|
autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
|
|
449
330
|
});
|
|
450
331
|
ctx.signal.throwIfAborted();
|
|
451
|
-
|
|
452
|
-
return [toPerFilePreview(args.path ?? "", result)];
|
|
332
|
+
return [toPerFilePreview("", result)];
|
|
453
333
|
}
|
|
454
334
|
if (sections.length === 0) return null;
|
|
455
335
|
|
|
@@ -468,6 +348,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
468
348
|
const section = sectionsToProcess[i];
|
|
469
349
|
const result = await computeHashlineSectionDiff(section, ctx.cwd, {
|
|
470
350
|
autoDropPureInsertDuplicates: ctx.hashlineAutoDropPureInsertDuplicates,
|
|
351
|
+
streaming: ctx.isStreaming,
|
|
471
352
|
});
|
|
472
353
|
ctx.signal.throwIfAborted();
|
|
473
354
|
// In a multi-section preview, ignore parse/apply errors from the
|
|
@@ -480,8 +361,13 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
480
361
|
}
|
|
481
362
|
return previews.length > 0 ? previews : null;
|
|
482
363
|
},
|
|
483
|
-
renderStreamingFallback(
|
|
484
|
-
|
|
364
|
+
renderStreamingFallback() {
|
|
365
|
+
// Never leak raw hashline syntax (`64:`, `|payload`, `¶path#hash`)
|
|
366
|
+
// to the user — the streaming preview already projects every
|
|
367
|
+
// parseable op onto the real file via applyPartialTo, and an
|
|
368
|
+
// unparseable trailing chunk renders as "no preview yet" rather
|
|
369
|
+
// than a sigil dump.
|
|
370
|
+
return "";
|
|
485
371
|
},
|
|
486
372
|
};
|
|
487
373
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility shim for legacy extensions importing the package root of
|
|
3
|
+
* `@oh-my-pi/pi-ai` (or one of its aliased scopes like `@earendil-works/pi-ai`
|
|
4
|
+
* or `@mariozechner/pi-ai`).
|
|
5
|
+
*
|
|
6
|
+
* pi-ai 15.1.0 removed the historical TypeBox root exports (`Type`, plus the
|
|
7
|
+
* runtime-relevant half of the `Static`/`TSchema` pair) from the package
|
|
8
|
+
* entrypoint. Legacy extensions still author parameter schemas as
|
|
9
|
+
* `Type.Object({ ... })`, so this file is served by `legacy-pi-compat.ts` in
|
|
10
|
+
* place of the real pi-ai entrypoint whenever a legacy extension imports the
|
|
11
|
+
* bare package root. Subpath imports (`@oh-my-pi/pi-ai/utils/oauth`, etc.)
|
|
12
|
+
* continue to resolve directly against the bundled pi-ai package.
|
|
13
|
+
*
|
|
14
|
+
* The `Type` runtime is borrowed from the Zod-backed TypeBox shim that
|
|
15
|
+
* already serves bare `@sinclair/typebox` imports for the same extension
|
|
16
|
+
* class, keeping the legacy-compat surface internally consistent.
|
|
17
|
+
*
|
|
18
|
+
* Type-level `Static` and `TSchema` continue to come from pi-ai's own
|
|
19
|
+
* `types.ts` via the `export *` below — pi-ai still exports both as types,
|
|
20
|
+
* only the runtime `Type` builder was removed.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
export * from "@oh-my-pi/pi-ai";
|
|
24
|
+
export { Type } from "./typebox";
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import * as url from "node:url";
|
|
5
|
+
import { isCompiledBinary } from "@oh-my-pi/pi-utils";
|
|
5
6
|
|
|
6
7
|
// Canonical scope for in-process pi packages. Plugins published against any of
|
|
7
8
|
// the aliased scopes below (mariozechner's original publish, earendil-works'
|
|
@@ -56,7 +57,34 @@ const resolvedSpecifierFallbacks = new Map<string, string>();
|
|
|
56
57
|
// relying on them must vendor `@sinclair/typebox` directly.
|
|
57
58
|
const TYPEBOX_SPECIFIER = "@sinclair/typebox";
|
|
58
59
|
const TYPEBOX_SPECIFIER_FILTER = /^@sinclair\/typebox$/;
|
|
59
|
-
|
|
60
|
+
|
|
61
|
+
// In-process compat shim paths. In dev `import.meta.dir` is the source folder of
|
|
62
|
+
// this file, so the dev branches resolve to the real `.ts` source. In compiled
|
|
63
|
+
// binaries `import.meta.dir` collapses to `/$bunfs/root`, so the runtime cannot
|
|
64
|
+
// recover the source layout that way; instead, each shim file is registered as
|
|
65
|
+
// a `--compile` entrypoint in `scripts/build-binary.ts`, which Bun emits into
|
|
66
|
+
// bunfs at a deterministic `--root`-relative path with a `.js` extension. The
|
|
67
|
+
// literals below must stay in sync with that listing — if either path drifts,
|
|
68
|
+
// every legacy plugin loading the shim fails with a missing-module error in
|
|
69
|
+
// release builds (without affecting `bun test`/dev).
|
|
70
|
+
const TYPEBOX_SHIM_PATH = isCompiledBinary()
|
|
71
|
+
? "/$bunfs/root/packages/coding-agent/src/extensibility/typebox.js"
|
|
72
|
+
: path.resolve(import.meta.dir, "../typebox.ts");
|
|
73
|
+
|
|
74
|
+
// Legacy extensions historically imported `Type` (and `Static`/`TSchema`) from
|
|
75
|
+
// the package root of `@(scope)/pi-ai`. pi-ai 15.1.0 removed the runtime `Type`
|
|
76
|
+
// export (see `packages/ai/CHANGELOG.md`), so the bare canonical specifier no
|
|
77
|
+
// longer satisfies those imports. The override below redirects only the bare
|
|
78
|
+
// pi-ai package root onto a sibling shim that re-exports the canonical surface
|
|
79
|
+
// plus the borrowed `Type` runtime from the Zod-backed TypeBox shim. Subpath
|
|
80
|
+
// imports such as `@oh-my-pi/pi-ai/utils/oauth` continue to resolve directly
|
|
81
|
+
// against the bundled pi-ai package.
|
|
82
|
+
const LEGACY_PI_AI_SHIM_PATH = isCompiledBinary()
|
|
83
|
+
? "/$bunfs/root/packages/coding-agent/src/extensibility/legacy-pi-ai-shim.js"
|
|
84
|
+
: path.resolve(import.meta.dir, "../legacy-pi-ai-shim.ts");
|
|
85
|
+
const LEGACY_PI_PACKAGE_ROOT_OVERRIDES: Record<string, string> = {
|
|
86
|
+
[`${CANONICAL_PI_SCOPE}/pi-ai`]: LEGACY_PI_AI_SHIM_PATH,
|
|
87
|
+
};
|
|
60
88
|
|
|
61
89
|
let isLegacyPiSpecifierShimInstalled = false;
|
|
62
90
|
|
|
@@ -85,6 +113,22 @@ function getResolvedSpecifier(specifier: string): string {
|
|
|
85
113
|
return resolved;
|
|
86
114
|
}
|
|
87
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Resolve a canonical `@oh-my-pi/*` specifier to a filesystem path, preferring
|
|
118
|
+
* a bundled compat shim when one is registered for the package root.
|
|
119
|
+
*
|
|
120
|
+
* Falls back to `getResolvedSpecifier` (which may throw under compiled binary
|
|
121
|
+
* mode); callers handle that the same way they would for non-overridden
|
|
122
|
+
* specifiers.
|
|
123
|
+
*/
|
|
124
|
+
function resolveCanonicalPiSpecifier(remappedSpecifier: string): string {
|
|
125
|
+
const override = LEGACY_PI_PACKAGE_ROOT_OVERRIDES[remappedSpecifier];
|
|
126
|
+
if (override) {
|
|
127
|
+
return override;
|
|
128
|
+
}
|
|
129
|
+
return getResolvedSpecifier(remappedSpecifier);
|
|
130
|
+
}
|
|
131
|
+
|
|
88
132
|
function toImportSpecifier(resolvedPath: string): string {
|
|
89
133
|
return url.pathToFileURL(resolvedPath).href;
|
|
90
134
|
}
|
|
@@ -99,7 +143,7 @@ function rewriteLegacyPiImports(source: string): string {
|
|
|
99
143
|
}
|
|
100
144
|
|
|
101
145
|
try {
|
|
102
|
-
return `${prefix}${toImportSpecifier(
|
|
146
|
+
return `${prefix}${toImportSpecifier(resolveCanonicalPiSpecifier(remappedSpecifier))}${suffix}`;
|
|
103
147
|
} catch {
|
|
104
148
|
// Resolution failed — typically in compiled binary mode where
|
|
105
149
|
// Bun.resolveSync cannot walk up from /$bunfs/root to find the
|
|
@@ -250,7 +294,7 @@ function resolveLegacyPiSpecifier(args: { path: string; importer: string }): { p
|
|
|
250
294
|
// Primary: resolve the canonical @oh-my-pi/* specifier from the host binary
|
|
251
295
|
// location. Works in dev mode and in source-link installs.
|
|
252
296
|
try {
|
|
253
|
-
return { path:
|
|
297
|
+
return { path: resolveCanonicalPiSpecifier(remappedSpecifier) };
|
|
254
298
|
} catch {
|
|
255
299
|
// Fallback for compiled binary mode: the bundled packages live inside
|
|
256
300
|
// /$bunfs/root and aren't reachable by filesystem resolution. Try the
|
package/src/index.ts
CHANGED
|
@@ -26,7 +26,6 @@ export * from "./extensibility/extensions";
|
|
|
26
26
|
export * from "./extensibility/skills";
|
|
27
27
|
// Slash commands
|
|
28
28
|
export { type FileSlashCommand, loadSlashCommands as discoverSlashCommands } from "./extensibility/slash-commands";
|
|
29
|
-
export * from "./hashline";
|
|
30
29
|
export type * from "./lsp";
|
|
31
30
|
// Main entry point
|
|
32
31
|
export * from "./main";
|