harnery 0.22.0 → 0.22.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.
|
@@ -9,7 +9,16 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { StageSchema } from "./types.js";
|
|
11
11
|
export declare function validateAgainstSchema(value: unknown, schema: StageSchema, path?: string): string[];
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Parse one schema-gated JSON value.
|
|
14
|
+
*
|
|
15
|
+
* Exact JSON and a whole fenced value remain the normal paths. Some harnesses
|
|
16
|
+
* prepend a short narration despite an explicit JSON-only contract. Accept
|
|
17
|
+
* that drift only when the reply contains exactly one unambiguous top-level
|
|
18
|
+
* object or array, the value reaches the end of the reply, and JSON.parse
|
|
19
|
+
* accepts it. Trailing prose, multiple values, and malformed candidates remain
|
|
20
|
+
* failures.
|
|
21
|
+
*/
|
|
13
22
|
export declare function parseStageOutput(text: string): {
|
|
14
23
|
value?: unknown;
|
|
15
24
|
error?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAmG/F;AAED
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../../src/core/workflow/validate.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,SAAM,GAAG,MAAM,EAAE,CAmG/F;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAYlF"}
|
|
@@ -100,7 +100,16 @@ export function validateAgainstSchema(value, schema, path = "$") {
|
|
|
100
100
|
return [`${path}: unsupported schema type ${String(schema.type)}`];
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
/**
|
|
103
|
+
/**
|
|
104
|
+
* Parse one schema-gated JSON value.
|
|
105
|
+
*
|
|
106
|
+
* Exact JSON and a whole fenced value remain the normal paths. Some harnesses
|
|
107
|
+
* prepend a short narration despite an explicit JSON-only contract. Accept
|
|
108
|
+
* that drift only when the reply contains exactly one unambiguous top-level
|
|
109
|
+
* object or array, the value reaches the end of the reply, and JSON.parse
|
|
110
|
+
* accepts it. Trailing prose, multiple values, and malformed candidates remain
|
|
111
|
+
* failures.
|
|
112
|
+
*/
|
|
104
113
|
export function parseStageOutput(text) {
|
|
105
114
|
const stripped = text
|
|
106
115
|
.trim()
|
|
@@ -110,9 +119,74 @@ export function parseStageOutput(text) {
|
|
|
110
119
|
return { value: JSON.parse(stripped) };
|
|
111
120
|
}
|
|
112
121
|
catch (err) {
|
|
122
|
+
const embedded = singleTrailingJsonContainer(stripped);
|
|
123
|
+
if (embedded !== undefined)
|
|
124
|
+
return { value: embedded };
|
|
113
125
|
return { error: `not valid JSON: ${err.message}` };
|
|
114
126
|
}
|
|
115
127
|
}
|
|
128
|
+
function singleTrailingJsonContainer(text) {
|
|
129
|
+
const candidates = [];
|
|
130
|
+
let start = -1;
|
|
131
|
+
let stack = [];
|
|
132
|
+
let inString = false;
|
|
133
|
+
let escaped = false;
|
|
134
|
+
for (let index = 0; index < text.length; index++) {
|
|
135
|
+
const char = text[index];
|
|
136
|
+
if (start === -1) {
|
|
137
|
+
if (char === "{" || char === "[") {
|
|
138
|
+
start = index;
|
|
139
|
+
stack = [char];
|
|
140
|
+
inString = false;
|
|
141
|
+
escaped = false;
|
|
142
|
+
}
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (inString) {
|
|
146
|
+
if (escaped) {
|
|
147
|
+
escaped = false;
|
|
148
|
+
}
|
|
149
|
+
else if (char === "\\") {
|
|
150
|
+
escaped = true;
|
|
151
|
+
}
|
|
152
|
+
else if (char === '"') {
|
|
153
|
+
inString = false;
|
|
154
|
+
}
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (char === '"') {
|
|
158
|
+
inString = true;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (char === "{" || char === "[") {
|
|
162
|
+
stack.push(char);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (char !== "}" && char !== "]")
|
|
166
|
+
continue;
|
|
167
|
+
const expectedOpen = char === "}" ? "{" : "[";
|
|
168
|
+
if (stack.at(-1) !== expectedOpen) {
|
|
169
|
+
start = -1;
|
|
170
|
+
stack = [];
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
stack.pop();
|
|
174
|
+
if (stack.length > 0)
|
|
175
|
+
continue;
|
|
176
|
+
const end = index + 1;
|
|
177
|
+
try {
|
|
178
|
+
candidates.push({ end, value: JSON.parse(text.slice(start, end)) });
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// A balanced brace pair in narration is not a JSON candidate.
|
|
182
|
+
}
|
|
183
|
+
start = -1;
|
|
184
|
+
}
|
|
185
|
+
if (candidates.length !== 1 || text.slice(candidates[0].end).trim() !== "") {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
return candidates[0].value;
|
|
189
|
+
}
|
|
116
190
|
function short(value) {
|
|
117
191
|
const s = JSON.stringify(value);
|
|
118
192
|
return s === undefined ? String(value) : s.length > 60 ? `${s.slice(0, 60)}…` : s;
|
package/package.json
CHANGED
|
@@ -111,7 +111,16 @@ export function validateAgainstSchema(value: unknown, schema: StageSchema, path
|
|
|
111
111
|
}
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Parse one schema-gated JSON value.
|
|
116
|
+
*
|
|
117
|
+
* Exact JSON and a whole fenced value remain the normal paths. Some harnesses
|
|
118
|
+
* prepend a short narration despite an explicit JSON-only contract. Accept
|
|
119
|
+
* that drift only when the reply contains exactly one unambiguous top-level
|
|
120
|
+
* object or array, the value reaches the end of the reply, and JSON.parse
|
|
121
|
+
* accepts it. Trailing prose, multiple values, and malformed candidates remain
|
|
122
|
+
* failures.
|
|
123
|
+
*/
|
|
115
124
|
export function parseStageOutput(text: string): { value?: unknown; error?: string } {
|
|
116
125
|
const stripped = text
|
|
117
126
|
.trim()
|
|
@@ -120,10 +129,75 @@ export function parseStageOutput(text: string): { value?: unknown; error?: strin
|
|
|
120
129
|
try {
|
|
121
130
|
return { value: JSON.parse(stripped) };
|
|
122
131
|
} catch (err) {
|
|
132
|
+
const embedded = singleTrailingJsonContainer(stripped);
|
|
133
|
+
if (embedded !== undefined) return { value: embedded };
|
|
123
134
|
return { error: `not valid JSON: ${(err as Error).message}` };
|
|
124
135
|
}
|
|
125
136
|
}
|
|
126
137
|
|
|
138
|
+
function singleTrailingJsonContainer(text: string): unknown | undefined {
|
|
139
|
+
const candidates: Array<{ end: number; value: unknown }> = [];
|
|
140
|
+
let start = -1;
|
|
141
|
+
let stack: string[] = [];
|
|
142
|
+
let inString = false;
|
|
143
|
+
let escaped = false;
|
|
144
|
+
|
|
145
|
+
for (let index = 0; index < text.length; index++) {
|
|
146
|
+
const char = text[index]!;
|
|
147
|
+
if (start === -1) {
|
|
148
|
+
if (char === "{" || char === "[") {
|
|
149
|
+
start = index;
|
|
150
|
+
stack = [char];
|
|
151
|
+
inString = false;
|
|
152
|
+
escaped = false;
|
|
153
|
+
}
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (inString) {
|
|
158
|
+
if (escaped) {
|
|
159
|
+
escaped = false;
|
|
160
|
+
} else if (char === "\\") {
|
|
161
|
+
escaped = true;
|
|
162
|
+
} else if (char === '"') {
|
|
163
|
+
inString = false;
|
|
164
|
+
}
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (char === '"') {
|
|
168
|
+
inString = true;
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (char === "{" || char === "[") {
|
|
172
|
+
stack.push(char);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (char !== "}" && char !== "]") continue;
|
|
176
|
+
|
|
177
|
+
const expectedOpen = char === "}" ? "{" : "[";
|
|
178
|
+
if (stack.at(-1) !== expectedOpen) {
|
|
179
|
+
start = -1;
|
|
180
|
+
stack = [];
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
stack.pop();
|
|
184
|
+
if (stack.length > 0) continue;
|
|
185
|
+
|
|
186
|
+
const end = index + 1;
|
|
187
|
+
try {
|
|
188
|
+
candidates.push({ end, value: JSON.parse(text.slice(start, end)) });
|
|
189
|
+
} catch {
|
|
190
|
+
// A balanced brace pair in narration is not a JSON candidate.
|
|
191
|
+
}
|
|
192
|
+
start = -1;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (candidates.length !== 1 || text.slice(candidates[0]!.end).trim() !== "") {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return candidates[0]!.value;
|
|
199
|
+
}
|
|
200
|
+
|
|
127
201
|
function short(value: unknown): string {
|
|
128
202
|
const s = JSON.stringify(value);
|
|
129
203
|
return s === undefined ? String(value) : s.length > 60 ? `${s.slice(0, 60)}…` : s;
|