@oh-my-pi/pi-ai 13.5.2 → 13.5.3

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,11 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.5.3] - 2026-03-01
6
+ ### Fixed
7
+
8
+ - Fixed tool argument coercion to handle malformed JSON with trailing wrapper braces by parsing leading JSON containers
9
+
5
10
  ## [13.4.0] - 2026-03-01
6
11
 
7
12
  ### Breaking Changes
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-ai",
4
- "version": "13.5.2",
4
+ "version": "13.5.3",
5
5
  "description": "Unified LLM API with automatic model discovery and provider configuration",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,7 +41,7 @@
41
41
  "@aws-sdk/client-bedrock-runtime": "^3.998",
42
42
  "@bufbuild/protobuf": "^2.11",
43
43
  "@google/genai": "^1.43",
44
- "@oh-my-pi/pi-utils": "13.5.2",
44
+ "@oh-my-pi/pi-utils": "13.5.3",
45
45
  "@sinclair/typebox": "^0.34",
46
46
  "@smithy/node-http-handler": "^4.4",
47
47
  "ajv": "^8.18",
@@ -89,6 +89,55 @@ function tryParseNumberString(value: string, expectedTypes: string[]): { value:
89
89
  return { value: parsed, changed: true };
90
90
  }
91
91
 
92
+ function tryParseLeadingJsonContainer(value: string): unknown | undefined {
93
+ const firstChar = value[0];
94
+ const closingChar = firstChar === "{" ? "}" : firstChar === "[" ? "]" : undefined;
95
+ if (!closingChar) return undefined;
96
+
97
+ let depth = 0;
98
+ let inString = false;
99
+ let escaped = false;
100
+
101
+ for (let index = 0; index < value.length; index += 1) {
102
+ const char = value[index];
103
+
104
+ if (inString) {
105
+ if (escaped) {
106
+ escaped = false;
107
+ continue;
108
+ }
109
+ if (char === "\\") {
110
+ escaped = true;
111
+ continue;
112
+ }
113
+ if (char === '"') inString = false;
114
+ continue;
115
+ }
116
+
117
+ if (char === '"') {
118
+ inString = true;
119
+ continue;
120
+ }
121
+
122
+ if (char === firstChar) {
123
+ depth += 1;
124
+ continue;
125
+ }
126
+
127
+ if (char !== closingChar) continue;
128
+ depth -= 1;
129
+ if (depth !== 0) continue;
130
+
131
+ try {
132
+ return JSON.parse(value.slice(0, index + 1)) as unknown;
133
+ } catch {
134
+ return undefined;
135
+ }
136
+ }
137
+
138
+ return undefined;
139
+ }
140
+
92
141
  /**
93
142
  * Attempts to parse a string as JSON if it looks like a JSON literal and
94
143
  * the parsed result matches one of the expected types.
@@ -112,8 +161,8 @@ function tryParseJsonForTypes(value: string, expectedTypes: string[]): { value:
112
161
  }
113
162
 
114
163
  // Quick syntactic checks to avoid unnecessary parse attempts
115
- const looksJsonObject = trimmed.startsWith("{") && trimmed.endsWith("}");
116
- const looksJsonArray = trimmed.startsWith("[") && trimmed.endsWith("]");
164
+ const looksJsonObject = trimmed.startsWith("{");
165
+ const looksJsonArray = trimmed.startsWith("[");
117
166
  const looksJsonLiteral =
118
167
  trimmed === "true" || trimmed === "false" || trimmed === "null" || JSON_NUMBER_PATTERN.test(trimmed);
119
168
 
@@ -128,7 +177,12 @@ function tryParseJsonForTypes(value: string, expectedTypes: string[]): { value:
128
177
  return { value: parsed, changed: true };
129
178
  }
130
179
  } catch {
131
- // Invalid JSON - leave as-is
180
+ if (looksJsonObject || looksJsonArray) {
181
+ const parsed = tryParseLeadingJsonContainer(trimmed);
182
+ if (parsed !== undefined && matchesExpectedType(parsed, expectedTypes)) {
183
+ return { value: parsed, changed: true };
184
+ }
185
+ }
132
186
  return { value, changed: false };
133
187
  }
134
188