@botpress/zai 2.4.2 → 2.5.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/dist/index.d.ts +146 -22
- package/dist/index.js +1 -0
- package/dist/micropatch.js +273 -0
- package/dist/operations/patch.js +398 -0
- package/e2e/data/cache.jsonl +105 -0
- package/package.json +2 -2
- package/src/index.ts +1 -0
- package/src/micropatch.ts +364 -0
- package/src/operations/patch.ts +656 -0
package/dist/index.d.ts
CHANGED
|
@@ -676,7 +676,7 @@ declare class Response<T = any, S = T> implements PromiseLike<S> {
|
|
|
676
676
|
}>;
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
-
type Options$
|
|
679
|
+
type Options$b = {
|
|
680
680
|
/** The maximum number of tokens to generate */
|
|
681
681
|
length?: number;
|
|
682
682
|
};
|
|
@@ -732,7 +732,7 @@ declare module '@botpress/zai' {
|
|
|
732
732
|
* )
|
|
733
733
|
* ```
|
|
734
734
|
*/
|
|
735
|
-
text(prompt: string, options?: Options$
|
|
735
|
+
text(prompt: string, options?: Options$b): Response<string>;
|
|
736
736
|
}
|
|
737
737
|
}
|
|
738
738
|
|
|
@@ -741,7 +741,7 @@ type Example$3 = {
|
|
|
741
741
|
output: string;
|
|
742
742
|
instructions?: string;
|
|
743
743
|
};
|
|
744
|
-
type Options$
|
|
744
|
+
type Options$a = {
|
|
745
745
|
/** Examples to guide the rewriting */
|
|
746
746
|
examples?: Array<Example$3>;
|
|
747
747
|
/** The maximum number of tokens to generate */
|
|
@@ -833,11 +833,11 @@ declare module '@botpress/zai' {
|
|
|
833
833
|
* // Result: "If the user is active AND has permission, then allow access"
|
|
834
834
|
* ```
|
|
835
835
|
*/
|
|
836
|
-
rewrite(original: string, prompt: string, options?: Options$
|
|
836
|
+
rewrite(original: string, prompt: string, options?: Options$a): Response<string>;
|
|
837
837
|
}
|
|
838
838
|
}
|
|
839
839
|
|
|
840
|
-
type Options$
|
|
840
|
+
type Options$9 = {
|
|
841
841
|
/** What should the text be summarized to? */
|
|
842
842
|
prompt?: string;
|
|
843
843
|
/** How to format the example text */
|
|
@@ -930,7 +930,7 @@ declare module '@botpress/zai' {
|
|
|
930
930
|
* const summary = await response
|
|
931
931
|
* ```
|
|
932
932
|
*/
|
|
933
|
-
summarize(original: string, options?: Options$
|
|
933
|
+
summarize(original: string, options?: Options$9): Response<string>;
|
|
934
934
|
}
|
|
935
935
|
}
|
|
936
936
|
|
|
@@ -940,7 +940,7 @@ type Example$2 = {
|
|
|
940
940
|
reason?: string;
|
|
941
941
|
condition?: string;
|
|
942
942
|
};
|
|
943
|
-
type Options$
|
|
943
|
+
type Options$8 = {
|
|
944
944
|
/** Examples to check the condition against */
|
|
945
945
|
examples?: Array<Example$2>;
|
|
946
946
|
};
|
|
@@ -1021,7 +1021,7 @@ declare module '@botpress/zai' {
|
|
|
1021
1021
|
* const followsConventions = await zai.check(code, 'Does this follow naming conventions?')
|
|
1022
1022
|
* ```
|
|
1023
1023
|
*/
|
|
1024
|
-
check(input: unknown, condition: string, options?: Options$
|
|
1024
|
+
check(input: unknown, condition: string, options?: Options$8): Response<{
|
|
1025
1025
|
/** Whether the condition is true or not */
|
|
1026
1026
|
value: boolean;
|
|
1027
1027
|
/** The explanation of the decision */
|
|
@@ -1035,7 +1035,7 @@ type Example$1 = {
|
|
|
1035
1035
|
filter: boolean;
|
|
1036
1036
|
reason?: string;
|
|
1037
1037
|
};
|
|
1038
|
-
type Options$
|
|
1038
|
+
type Options$7 = {
|
|
1039
1039
|
/** The maximum number of tokens per item */
|
|
1040
1040
|
tokensPerItem?: number;
|
|
1041
1041
|
/** Examples to filter the condition against */
|
|
@@ -1129,11 +1129,11 @@ declare module '@botpress/zai' {
|
|
|
1129
1129
|
* })
|
|
1130
1130
|
* ```
|
|
1131
1131
|
*/
|
|
1132
|
-
filter<T>(input: Array<T>, condition: string, options?: Options$
|
|
1132
|
+
filter<T>(input: Array<T>, condition: string, options?: Options$7): Response<Array<T>>;
|
|
1133
1133
|
}
|
|
1134
1134
|
}
|
|
1135
1135
|
|
|
1136
|
-
type Options$
|
|
1136
|
+
type Options$6 = {
|
|
1137
1137
|
/** Instructions to guide the user on how to extract the data */
|
|
1138
1138
|
instructions?: string;
|
|
1139
1139
|
/** The maximum number of tokens per chunk */
|
|
@@ -1214,7 +1214,7 @@ declare module '@botpress/zai' {
|
|
|
1214
1214
|
* console.log(`Extraction took ${elapsed}ms and cost $${usage.cost.total}`)
|
|
1215
1215
|
* ```
|
|
1216
1216
|
*/
|
|
1217
|
-
extract<S extends OfType<any>>(input: unknown, schema: S, options?: Options$
|
|
1217
|
+
extract<S extends OfType<any>>(input: unknown, schema: S, options?: Options$6): Response<S['_output']>;
|
|
1218
1218
|
}
|
|
1219
1219
|
}
|
|
1220
1220
|
|
|
@@ -1233,7 +1233,7 @@ type Example<T extends string> = {
|
|
|
1233
1233
|
explanation?: string;
|
|
1234
1234
|
}>>;
|
|
1235
1235
|
};
|
|
1236
|
-
type Options$
|
|
1236
|
+
type Options$5<T extends string> = {
|
|
1237
1237
|
/** Examples to help the user make a decision */
|
|
1238
1238
|
examples?: Array<Example<T>>;
|
|
1239
1239
|
/** Instructions to guide the user on how to extract the data */
|
|
@@ -1363,7 +1363,7 @@ declare module '@botpress/zai' {
|
|
|
1363
1363
|
* })
|
|
1364
1364
|
* ```
|
|
1365
1365
|
*/
|
|
1366
|
-
label<T extends string>(input: unknown, labels: Labels<T>, options?: Options$
|
|
1366
|
+
label<T extends string>(input: unknown, labels: Labels<T>, options?: Options$5<T>): Response<{
|
|
1367
1367
|
[K in T]: {
|
|
1368
1368
|
explanation: string;
|
|
1369
1369
|
value: boolean;
|
|
@@ -1385,7 +1385,7 @@ type InitialGroup = {
|
|
|
1385
1385
|
label: string;
|
|
1386
1386
|
elements?: unknown[];
|
|
1387
1387
|
};
|
|
1388
|
-
type Options$
|
|
1388
|
+
type Options$4 = {
|
|
1389
1389
|
instructions?: string;
|
|
1390
1390
|
tokensPerElement?: number;
|
|
1391
1391
|
chunkLength?: number;
|
|
@@ -1543,12 +1543,12 @@ declare module '@botpress/zai' {
|
|
|
1543
1543
|
* })
|
|
1544
1544
|
* ```
|
|
1545
1545
|
*/
|
|
1546
|
-
group<T>(input: Array<T>, options?: Options$
|
|
1546
|
+
group<T>(input: Array<T>, options?: Options$4): Response<Array<Group<T>>, Record<string, T[]>>;
|
|
1547
1547
|
}
|
|
1548
1548
|
}
|
|
1549
1549
|
|
|
1550
1550
|
type RatingInstructions = string | Record<string, string>;
|
|
1551
|
-
type Options$
|
|
1551
|
+
type Options$3 = {
|
|
1552
1552
|
/** The maximum number of tokens per item */
|
|
1553
1553
|
tokensPerItem?: number;
|
|
1554
1554
|
/** The maximum number of items to rate per chunk */
|
|
@@ -1679,11 +1679,11 @@ declare module '@botpress/zai' {
|
|
|
1679
1679
|
* console.log('Score breakdown:', sorted[0].rating)
|
|
1680
1680
|
* ```
|
|
1681
1681
|
*/
|
|
1682
|
-
rate<T, I extends RatingInstructions>(input: Array<T>, instructions: I, options?: Options$
|
|
1682
|
+
rate<T, I extends RatingInstructions>(input: Array<T>, instructions: I, options?: Options$3): Response<Array<RatingResult<I>>, Array<SimplifiedRatingResult<I>>>;
|
|
1683
1683
|
}
|
|
1684
1684
|
}
|
|
1685
1685
|
|
|
1686
|
-
type Options$
|
|
1686
|
+
type Options$2 = {
|
|
1687
1687
|
/** The maximum number of tokens per item */
|
|
1688
1688
|
tokensPerItem?: number;
|
|
1689
1689
|
};
|
|
@@ -1804,7 +1804,7 @@ declare module '@botpress/zai' {
|
|
|
1804
1804
|
* )
|
|
1805
1805
|
* ```
|
|
1806
1806
|
*/
|
|
1807
|
-
sort<T>(input: Array<T>, instructions: string, options?: Options$
|
|
1807
|
+
sort<T>(input: Array<T>, instructions: string, options?: Options$2): Response<Array<T>, Array<T>>;
|
|
1808
1808
|
}
|
|
1809
1809
|
}
|
|
1810
1810
|
|
|
@@ -1885,7 +1885,7 @@ type AnswerExample<T> = {
|
|
|
1885
1885
|
/** The expected answer result */
|
|
1886
1886
|
result: AnswerResult<T>;
|
|
1887
1887
|
};
|
|
1888
|
-
type Options<T> = {
|
|
1888
|
+
type Options$1<T> = {
|
|
1889
1889
|
/** Examples to help guide answer generation */
|
|
1890
1890
|
examples?: AnswerExample<T>[];
|
|
1891
1891
|
/** Additional instructions for answer generation */
|
|
@@ -1999,7 +1999,131 @@ declare module '@botpress/zai' {
|
|
|
1999
1999
|
* }
|
|
2000
2000
|
* ```
|
|
2001
2001
|
*/
|
|
2002
|
-
answer<T>(documents: T[], question: string, options?: Options<T>): Response<AnswerResult<T>, AnswerResult<T>>;
|
|
2002
|
+
answer<T>(documents: T[], question: string, options?: Options$1<T>): Response<AnswerResult<T>, AnswerResult<T>>;
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
2005
|
+
|
|
2006
|
+
/**
|
|
2007
|
+
* Represents a file to be patched
|
|
2008
|
+
*/
|
|
2009
|
+
type File = {
|
|
2010
|
+
/** The file path (e.g., 'src/components/Button.tsx') */
|
|
2011
|
+
path: string;
|
|
2012
|
+
/** The file name (e.g., 'Button.tsx') */
|
|
2013
|
+
name: string;
|
|
2014
|
+
/** The file content */
|
|
2015
|
+
content: string;
|
|
2016
|
+
/** The patch operations that were applied (only present in output) */
|
|
2017
|
+
patch?: string;
|
|
2018
|
+
};
|
|
2019
|
+
type Options = {
|
|
2020
|
+
/**
|
|
2021
|
+
* Maximum tokens per chunk when processing large files or many files.
|
|
2022
|
+
* If a single file exceeds this limit, it will be split into chunks.
|
|
2023
|
+
* If all files together exceed this limit, they will be processed in batches.
|
|
2024
|
+
* If not specified, all files must fit in a single prompt.
|
|
2025
|
+
*/
|
|
2026
|
+
maxTokensPerChunk?: number;
|
|
2027
|
+
};
|
|
2028
|
+
declare module '@botpress/zai' {
|
|
2029
|
+
interface Zai {
|
|
2030
|
+
/**
|
|
2031
|
+
* Patches files based on natural language instructions using the micropatch protocol.
|
|
2032
|
+
*
|
|
2033
|
+
* This operation takes an array of files and instructions, then returns the modified files.
|
|
2034
|
+
* It uses a token-efficient line-based patching protocol (micropatch) that allows precise
|
|
2035
|
+
* modifications without regenerating entire files.
|
|
2036
|
+
*
|
|
2037
|
+
* @param files - Array of files to patch, each with path, name, and content
|
|
2038
|
+
* @param instructions - Natural language instructions describing what changes to make
|
|
2039
|
+
* @param options - Optional configuration for patch generation
|
|
2040
|
+
* @returns Response promise resolving to array of patched files
|
|
2041
|
+
*
|
|
2042
|
+
* @example Simple text replacement
|
|
2043
|
+
* ```typescript
|
|
2044
|
+
* const files = [{
|
|
2045
|
+
* path: 'src/hello.ts',
|
|
2046
|
+
* name: 'hello.ts',
|
|
2047
|
+
* content: 'console.log("Hello World")'
|
|
2048
|
+
* }]
|
|
2049
|
+
*
|
|
2050
|
+
* const patched = await zai.patch(
|
|
2051
|
+
* files,
|
|
2052
|
+
* 'change the message to say "Hi World"'
|
|
2053
|
+
* )
|
|
2054
|
+
* // patched[0].content contains: console.log("Hi World")
|
|
2055
|
+
* // patched[0].patch contains: ◼︎=1|console.log("Hi World")
|
|
2056
|
+
* ```
|
|
2057
|
+
*
|
|
2058
|
+
* @example Adding documentation
|
|
2059
|
+
* ```typescript
|
|
2060
|
+
* const files = [{
|
|
2061
|
+
* path: 'src/utils.ts',
|
|
2062
|
+
* name: 'utils.ts',
|
|
2063
|
+
* content: 'export function add(a: number, b: number) {\n return a + b\n}'
|
|
2064
|
+
* }]
|
|
2065
|
+
*
|
|
2066
|
+
* const patched = await zai.patch(
|
|
2067
|
+
* files,
|
|
2068
|
+
* 'add JSDoc comments to all exported functions'
|
|
2069
|
+
* )
|
|
2070
|
+
* ```
|
|
2071
|
+
*
|
|
2072
|
+
* @example Patching multiple files
|
|
2073
|
+
* ```typescript
|
|
2074
|
+
* const files = [
|
|
2075
|
+
* { path: 'package.json', name: 'package.json', content: '...' },
|
|
2076
|
+
* { path: 'config.json', name: 'config.json', content: '...' }
|
|
2077
|
+
* ]
|
|
2078
|
+
*
|
|
2079
|
+
* const patched = await zai.patch(
|
|
2080
|
+
* files,
|
|
2081
|
+
* 'update version to 2.0.0 in all config files'
|
|
2082
|
+
* )
|
|
2083
|
+
* ```
|
|
2084
|
+
*
|
|
2085
|
+
* @example Refactoring code
|
|
2086
|
+
* ```typescript
|
|
2087
|
+
* const files = [{
|
|
2088
|
+
* path: 'src/api.ts',
|
|
2089
|
+
* name: 'api.ts',
|
|
2090
|
+
* content: 'function fetchUser() { ... }'
|
|
2091
|
+
* }]
|
|
2092
|
+
*
|
|
2093
|
+
* const patched = await zai.patch(
|
|
2094
|
+
* files,
|
|
2095
|
+
* 'convert fetchUser to an async function and add error handling'
|
|
2096
|
+
* )
|
|
2097
|
+
* ```
|
|
2098
|
+
*
|
|
2099
|
+
* @example Removing code
|
|
2100
|
+
* ```typescript
|
|
2101
|
+
* const files = [{
|
|
2102
|
+
* path: 'src/legacy.ts',
|
|
2103
|
+
* name: 'legacy.ts',
|
|
2104
|
+
* content: 'const debug = true\nconsole.log("Debug mode")\nfunction main() {...}'
|
|
2105
|
+
* }]
|
|
2106
|
+
*
|
|
2107
|
+
* const patched = await zai.patch(
|
|
2108
|
+
* files,
|
|
2109
|
+
* 'remove all debug-related code'
|
|
2110
|
+
* )
|
|
2111
|
+
* ```
|
|
2112
|
+
*
|
|
2113
|
+
* @example Inspecting applied patches
|
|
2114
|
+
* ```typescript
|
|
2115
|
+
* const patched = await zai.patch(files, 'add error handling')
|
|
2116
|
+
*
|
|
2117
|
+
* // Check what patches were applied
|
|
2118
|
+
* for (const file of patched) {
|
|
2119
|
+
* if (file.patch) {
|
|
2120
|
+
* console.log(`Patches for ${file.path}:`)
|
|
2121
|
+
* console.log(file.patch)
|
|
2122
|
+
* }
|
|
2123
|
+
* }
|
|
2124
|
+
* ```
|
|
2125
|
+
*/
|
|
2126
|
+
patch(files: Array<File>, instructions: string, options?: Options): Response<Array<File>>;
|
|
2003
2127
|
}
|
|
2004
2128
|
}
|
|
2005
2129
|
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
export class Micropatch {
|
|
2
|
+
_text;
|
|
3
|
+
_eol;
|
|
4
|
+
/**
|
|
5
|
+
* Create a Micropatch instance.
|
|
6
|
+
* @param source The file contents.
|
|
7
|
+
* @param eol Line ending style. If omitted, it is auto-detected from `source` (CRLF if any CRLF is present; otherwise LF).
|
|
8
|
+
*/
|
|
9
|
+
constructor(source, eol) {
|
|
10
|
+
this._text = source;
|
|
11
|
+
this._eol = eol ?? Micropatch.detectEOL(source);
|
|
12
|
+
}
|
|
13
|
+
/** Get current text. */
|
|
14
|
+
getText() {
|
|
15
|
+
return this._text;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Replace current text.
|
|
19
|
+
* Useful if you want to "load" a new snapshot without reconstructing the class.
|
|
20
|
+
*/
|
|
21
|
+
setText(source, eol) {
|
|
22
|
+
this._text = source;
|
|
23
|
+
this._eol = eol ?? Micropatch.detectEOL(source);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Apply ops text to current buffer.
|
|
27
|
+
* @param opsText One or more operations in the v0.3 syntax.
|
|
28
|
+
* @returns The updated text (also stored internally).
|
|
29
|
+
* @throws If the patch contains invalid syntax (e.g., range on insert).
|
|
30
|
+
*/
|
|
31
|
+
apply(opsText) {
|
|
32
|
+
const ops = Micropatch.parseOps(opsText);
|
|
33
|
+
this._text = Micropatch._applyOps(this._text, ops, this._eol);
|
|
34
|
+
return this._text;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Render a numbered view of the current buffer (token-cheap preview for models).
|
|
38
|
+
* Format: `NNN|<line>`, starting at 001.
|
|
39
|
+
*/
|
|
40
|
+
renderNumberedView() {
|
|
41
|
+
const NL = this._eol === "lf" ? "\n" : "\r\n";
|
|
42
|
+
const lines = Micropatch._splitEOL(this._text);
|
|
43
|
+
return lines.map((l, i) => `${String(i + 1).padStart(3, "0")}|${l}`).join(NL);
|
|
44
|
+
}
|
|
45
|
+
// ---------------------- Static helpers ----------------------
|
|
46
|
+
/** Detect EOL style from content. */
|
|
47
|
+
static detectEOL(source) {
|
|
48
|
+
return /\r\n/.test(source) ? "crlf" : "lf";
|
|
49
|
+
}
|
|
50
|
+
/** Split text into lines, preserving empty last line if present. */
|
|
51
|
+
static _splitEOL(text) {
|
|
52
|
+
const parts = text.split(/\r?\n/);
|
|
53
|
+
return parts;
|
|
54
|
+
}
|
|
55
|
+
/** Join lines with the chosen EOL. */
|
|
56
|
+
static _joinEOL(lines, eol) {
|
|
57
|
+
const NL = eol === "lf" ? "\n" : "\r\n";
|
|
58
|
+
return lines.join(NL);
|
|
59
|
+
}
|
|
60
|
+
/** Unescape payload text: `\◼︎` → `◼︎`. */
|
|
61
|
+
static _unescapeMarker(s) {
|
|
62
|
+
return s.replace(/\\◼︎/g, "\u25FC\uFE0E");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Parse ops text (v0.3).
|
|
66
|
+
* - Ignores blank lines and lines not starting with `◼︎` (you can keep comments elsewhere).
|
|
67
|
+
* - Validates ranges for allowed ops.
|
|
68
|
+
*/
|
|
69
|
+
static parseOps(opsText) {
|
|
70
|
+
const lines = opsText.split(/\r?\n/);
|
|
71
|
+
const ops = [];
|
|
72
|
+
const headerRe = /^◼︎([<>=-])(\d+)(?:-(\d+))?(?:\|(.*))?$/;
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < lines.length) {
|
|
75
|
+
const line = lines[i];
|
|
76
|
+
if (!line) {
|
|
77
|
+
i++;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (!line.startsWith("\u25FC\uFE0E")) {
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const m = headerRe.exec(line);
|
|
85
|
+
if (!m || !m[1] || !m[2]) {
|
|
86
|
+
throw new Error(`Invalid op syntax at line ${i + 1}: ${line}`);
|
|
87
|
+
}
|
|
88
|
+
const op = m[1];
|
|
89
|
+
const aNum = parseInt(m[2], 10);
|
|
90
|
+
const bNum = m[3] ? parseInt(m[3], 10) : void 0;
|
|
91
|
+
const firstPayload = m[4] ?? "";
|
|
92
|
+
if (aNum < 1 || bNum !== void 0 && bNum < aNum) {
|
|
93
|
+
throw new Error(`Invalid line/range at line ${i + 1}: ${line}`);
|
|
94
|
+
}
|
|
95
|
+
if (op === "<" || op === ">") {
|
|
96
|
+
if (bNum !== void 0) {
|
|
97
|
+
throw new Error(`Insert cannot target a range (line ${i + 1})`);
|
|
98
|
+
}
|
|
99
|
+
const text = Micropatch._unescapeMarker(firstPayload);
|
|
100
|
+
ops.push({ k: op, n: aNum, s: text });
|
|
101
|
+
i++;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (op === "-") {
|
|
105
|
+
if (firstPayload !== "") {
|
|
106
|
+
throw new Error(`Delete must not have a payload (line ${i + 1})`);
|
|
107
|
+
}
|
|
108
|
+
if (bNum === void 0) {
|
|
109
|
+
ops.push({ k: "-", n: aNum });
|
|
110
|
+
} else {
|
|
111
|
+
ops.push({ k: "--", a: aNum, b: bNum });
|
|
112
|
+
}
|
|
113
|
+
i++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (op === "=") {
|
|
117
|
+
const payload = [Micropatch._unescapeMarker(firstPayload)];
|
|
118
|
+
let j = i + 1;
|
|
119
|
+
while (j < lines.length) {
|
|
120
|
+
const nextLine = lines[j];
|
|
121
|
+
if (!nextLine || nextLine.startsWith("\u25FC\uFE0E")) break;
|
|
122
|
+
payload.push(Micropatch._unescapeMarker(nextLine));
|
|
123
|
+
j++;
|
|
124
|
+
}
|
|
125
|
+
if (bNum === void 0) {
|
|
126
|
+
ops.push({ k: "=", n: aNum, s: payload });
|
|
127
|
+
} else {
|
|
128
|
+
ops.push({ k: "=-", a: aNum, b: bNum, s: payload });
|
|
129
|
+
}
|
|
130
|
+
i = j;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
i++;
|
|
134
|
+
}
|
|
135
|
+
return Micropatch._canonicalizeOrder(ops);
|
|
136
|
+
}
|
|
137
|
+
/** Order ops deterministically according to the spec. */
|
|
138
|
+
static _canonicalizeOrder(ops) {
|
|
139
|
+
const delS = [];
|
|
140
|
+
const delR = [];
|
|
141
|
+
const eqS = [];
|
|
142
|
+
const eqR = [];
|
|
143
|
+
const insB = [];
|
|
144
|
+
const insA = [];
|
|
145
|
+
for (const o of ops) {
|
|
146
|
+
switch (o.k) {
|
|
147
|
+
case "-":
|
|
148
|
+
delS.push(o);
|
|
149
|
+
break;
|
|
150
|
+
case "--":
|
|
151
|
+
delR.push(o);
|
|
152
|
+
break;
|
|
153
|
+
case "=":
|
|
154
|
+
eqS.push(o);
|
|
155
|
+
break;
|
|
156
|
+
case "=-":
|
|
157
|
+
eqR.push(o);
|
|
158
|
+
break;
|
|
159
|
+
case "<":
|
|
160
|
+
insB.push(o);
|
|
161
|
+
break;
|
|
162
|
+
case ">":
|
|
163
|
+
insA.push(o);
|
|
164
|
+
break;
|
|
165
|
+
default:
|
|
166
|
+
break;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
delS.sort((a, b) => b.n - a.n);
|
|
170
|
+
delR.sort((a, b) => b.a - a.a);
|
|
171
|
+
eqS.sort((a, b) => a.n - b.n);
|
|
172
|
+
eqR.sort((a, b) => a.a - b.a);
|
|
173
|
+
insB.sort((a, b) => a.n - b.n);
|
|
174
|
+
insA.sort((a, b) => a.n - b.n);
|
|
175
|
+
return [].concat(delS, delR, eqS, eqR, insB, insA);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Apply normalized ops to given source.
|
|
179
|
+
* - Uses a live index map from ORIGINAL 1-based addresses → current positions.
|
|
180
|
+
* - Skips ops whose targets can no longer be mapped (idempotency-friendly).
|
|
181
|
+
*/
|
|
182
|
+
static _applyOps(source, ops, eol) {
|
|
183
|
+
const lines = Micropatch._splitEOL(source);
|
|
184
|
+
const idx = Array.from({ length: lines.length }, (_, i) => i);
|
|
185
|
+
const map = (n) => idx[n - 1] ?? -1;
|
|
186
|
+
const bump = (from, delta) => {
|
|
187
|
+
for (let i = 0; i < idx.length; i++) {
|
|
188
|
+
const current = idx[i];
|
|
189
|
+
if (current !== void 0 && current >= from) {
|
|
190
|
+
idx[i] = current + delta;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
for (const o of ops) {
|
|
195
|
+
switch (o.k) {
|
|
196
|
+
case "-": {
|
|
197
|
+
const i = map(o.n);
|
|
198
|
+
if (i >= 0 && i < lines.length) {
|
|
199
|
+
lines.splice(i, 1);
|
|
200
|
+
bump(i, -1);
|
|
201
|
+
}
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case "--": {
|
|
205
|
+
const a = map(o.a);
|
|
206
|
+
const b = map(o.b);
|
|
207
|
+
if (a >= 0 && b >= a && b < lines.length) {
|
|
208
|
+
lines.splice(a, b - a + 1);
|
|
209
|
+
bump(a, -(b - a + 1));
|
|
210
|
+
}
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
case "=": {
|
|
214
|
+
const i = map(o.n);
|
|
215
|
+
if (i >= 0 && i < lines.length) {
|
|
216
|
+
const rep = o.s;
|
|
217
|
+
lines.splice(i, 1, ...rep);
|
|
218
|
+
bump(i + 1, rep.length - 1);
|
|
219
|
+
}
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "=-": {
|
|
223
|
+
const a = map(o.a);
|
|
224
|
+
const b = map(o.b);
|
|
225
|
+
if (a >= 0 && b >= a && b < lines.length) {
|
|
226
|
+
const rep = o.s;
|
|
227
|
+
lines.splice(a, b - a + 1, ...rep);
|
|
228
|
+
bump(a + 1, rep.length - (b - a + 1));
|
|
229
|
+
}
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "<": {
|
|
233
|
+
const i = Math.max(0, Math.min(map(o.n), lines.length));
|
|
234
|
+
if (i >= 0) {
|
|
235
|
+
lines.splice(i, 0, o.s);
|
|
236
|
+
bump(i, 1);
|
|
237
|
+
}
|
|
238
|
+
break;
|
|
239
|
+
}
|
|
240
|
+
case ">": {
|
|
241
|
+
const i = Math.max(0, Math.min(map(o.n) + 1, lines.length));
|
|
242
|
+
if (i >= 0) {
|
|
243
|
+
lines.splice(i, 0, o.s);
|
|
244
|
+
bump(i, 1);
|
|
245
|
+
}
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
default:
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return Micropatch._joinEOL(lines, eol);
|
|
253
|
+
}
|
|
254
|
+
// ---------------------- Convenience APIs ----------------------
|
|
255
|
+
/**
|
|
256
|
+
* Convenience: one-shot apply.
|
|
257
|
+
* @param source Text to patch.
|
|
258
|
+
* @param opsText Operations text.
|
|
259
|
+
* @param eol EOL style (auto-detected if omitted).
|
|
260
|
+
*/
|
|
261
|
+
static applyText(source, opsText, eol) {
|
|
262
|
+
const inst = new Micropatch(source, eol);
|
|
263
|
+
return inst.apply(opsText);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Convenience: parse only.
|
|
267
|
+
* Useful for validation without applying.
|
|
268
|
+
*/
|
|
269
|
+
static validate(opsText) {
|
|
270
|
+
const ops = Micropatch.parseOps(opsText);
|
|
271
|
+
return { ok: true, count: ops.length };
|
|
272
|
+
}
|
|
273
|
+
}
|