@oh-my-pi/pi-utils 18.9.0 → 18.9.2

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 CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [18.9.2] - 2026-10-11
6
+
7
+ ### Changed
8
+
9
+ - Improved streaming JSON parsing performance for tool arguments containing many string values.
10
+
11
+ ### Fixed
12
+
13
+ - Fixed parsing of long strings that contain closing delimiters other than double or single quotes.
14
+
5
15
  ## [18.9.0] - 2026-10-11
6
16
 
7
17
  ### Breaking Changes
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-utils",
3
- "version": "18.9.0",
3
+ "version": "18.9.2",
4
4
  "description": "Shared utilities for pi packages",
5
5
  "keywords": [
6
6
  "cli",
@@ -57,7 +57,7 @@
57
57
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
58
58
  },
59
59
  "dependencies": {
60
- "@oh-my-pi/pi-natives": "18.9.0"
60
+ "@oh-my-pi/pi-natives": "18.9.2"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@types/bun": "^1.3.14"
package/src/json-lexer.ts CHANGED
@@ -186,6 +186,17 @@ const enum QuoteLook {
186
186
  */
187
187
  export class JsonLexer {
188
188
  pos: number;
189
+ /**
190
+ * Shared across {@link string} calls: `src` has no backslash in
191
+ * `[#backslashFrom, #backslashAt)` and `#backslashAt` is a backslash or
192
+ * `src.length`. An escape-free run can extend past its string's closing
193
+ * quote to the end of the document, so searching per string would rescan
194
+ * the same tail for every sibling string (quadratic on objects with many
195
+ * short values). `src` is immutable, so the range never goes stale; a
196
+ * cursor outside it (e.g. a rollback) re-searches.
197
+ */
198
+ #backslashFrom = 0;
199
+ #backslashAt = -1;
189
200
 
190
201
  constructor(
191
202
  readonly src: string,
@@ -228,13 +239,16 @@ export class JsonLexer {
228
239
  // malformed structure fails loudly instead of silently swallowing
229
240
  // commas/colons or sibling members.
230
241
  const lenient = quote === SQUOTE || this.mode === "streaming";
231
- const quoteChar = quote === QUOTE ? '"' : "'";
242
+ // Any delimiter code unit, not just `"`/`'`; a value above 0xFFFF matches
243
+ // no code unit, so such a string never closes.
244
+ const quoteChar = String.fromCharCode(quote);
232
245
  // Next quote / backslash at or after the cursor, cached across iterations
233
246
  // and re-searched only once the cursor passes them, so every character is
234
247
  // scanned at most once per kind and the bulk path stays linear even on
235
248
  // escape-dense input (re-searching the far closing quote after each
236
- // escape would be quadratic). `n` means "none before end of input".
237
- let nextQuote = -1;
249
+ // escape would be quadratic). `n` means "none before end of input". The
250
+ // backslash search result is shared across calls via `#backslashAt`.
251
+ let nextQuote = quoteChar.charCodeAt(0) === quote ? -1 : n;
238
252
  let nextBackslash = -1;
239
253
  while (i < n) {
240
254
  // Bulk-skip a run of ordinary characters: jump to the nearest quote or
@@ -249,8 +263,12 @@ export class JsonLexer {
249
263
  nextQuote = q === -1 ? n : q;
250
264
  }
251
265
  if (nextBackslash < i) {
252
- const b = s.indexOf("\\", i);
253
- nextBackslash = b === -1 ? n : b;
266
+ if (i < this.#backslashFrom || i > this.#backslashAt) {
267
+ const b = s.indexOf("\\", i);
268
+ this.#backslashFrom = i;
269
+ this.#backslashAt = b === -1 ? n : b;
270
+ }
271
+ nextBackslash = this.#backslashAt;
254
272
  }
255
273
  i = nextQuote < nextBackslash ? nextQuote : nextBackslash;
256
274
  if (i >= n) break;