@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 CHANGED
@@ -676,7 +676,7 @@ declare class Response<T = any, S = T> implements PromiseLike<S> {
676
676
  }>;
677
677
  }
678
678
 
679
- type Options$a = {
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$a): Response<string>;
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$9 = {
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$9): Response<string>;
836
+ rewrite(original: string, prompt: string, options?: Options$a): Response<string>;
837
837
  }
838
838
  }
839
839
 
840
- type Options$8 = {
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$8): Response<string>;
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$7 = {
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$7): Response<{
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$6 = {
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$6): Response<Array<T>>;
1132
+ filter<T>(input: Array<T>, condition: string, options?: Options$7): Response<Array<T>>;
1133
1133
  }
1134
1134
  }
1135
1135
 
1136
- type Options$5 = {
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$5): Response<S['_output']>;
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$4<T extends string> = {
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$4<T>): Response<{
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$3 = {
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$3): Response<Array<Group<T>>, Record<string, T[]>>;
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$2 = {
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$2): Response<Array<RatingResult<I>>, Array<SimplifiedRatingResult<I>>>;
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$1 = {
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$1): Response<Array<T>, Array<T>>;
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
@@ -10,4 +10,5 @@ import "./operations/group";
10
10
  import "./operations/rate";
11
11
  import "./operations/sort";
12
12
  import "./operations/answer";
13
+ import "./operations/patch";
13
14
  export { Zai };
@@ -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
+ }