@relayflows/sdk 2.0.28 → 2.0.30
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/authored-flow-executor.d.ts.map +1 -1
- package/dist/authored-flow-executor.js +10 -1
- package/dist/authored-flow-executor.js.map +1 -1
- package/dist/authored-step-index.d.ts +6 -0
- package/dist/authored-step-index.d.ts.map +1 -1
- package/dist/authored-step-index.js +42 -14
- package/dist/authored-step-index.js.map +1 -1
- package/dist/bundle-extensions.d.ts.map +1 -1
- package/dist/bundle-extensions.js +4 -7
- package/dist/bundle-extensions.js.map +1 -1
- package/dist/bundle.d.ts.map +1 -1
- package/dist/bundle.js +41 -7
- package/dist/bundle.js.map +1 -1
- package/dist/canonical.d.ts.map +1 -1
- package/dist/canonical.js +18 -6
- package/dist/canonical.js.map +1 -1
- package/dist/cli/status.d.ts +12 -0
- package/dist/cli/status.d.ts.map +1 -1
- package/dist/cli/status.js +39 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/flow-extension-compat.d.ts.map +1 -1
- package/dist/flow-extension-compat.js +26 -8
- package/dist/flow-extension-compat.js.map +1 -1
- package/dist/flow-extension-loader.d.ts +7 -0
- package/dist/flow-extension-loader.d.ts.map +1 -1
- package/dist/flow-extension-loader.js +48 -22
- package/dist/flow-extension-loader.js.map +1 -1
- package/dist/fs-descriptor.d.ts +17 -0
- package/dist/fs-descriptor.d.ts.map +1 -0
- package/dist/fs-descriptor.js +96 -0
- package/dist/fs-descriptor.js.map +1 -0
- package/dist/hosted-base-snapshot.d.ts +25 -0
- package/dist/hosted-base-snapshot.d.ts.map +1 -0
- package/dist/hosted-base-snapshot.js +278 -0
- package/dist/hosted-base-snapshot.js.map +1 -0
- package/dist/hosted-extension-declarations.d.ts +10 -0
- package/dist/hosted-extension-declarations.d.ts.map +1 -0
- package/dist/hosted-extension-declarations.js +272 -0
- package/dist/hosted-extension-declarations.js.map +1 -0
- package/dist/hosted-extension-isolation.d.ts +82 -0
- package/dist/hosted-extension-isolation.d.ts.map +1 -0
- package/dist/hosted-extension-isolation.js +322 -0
- package/dist/hosted-extension-isolation.js.map +1 -0
- package/dist/hosted-extension-manifest.d.ts +9 -0
- package/dist/hosted-extension-manifest.d.ts.map +1 -0
- package/dist/hosted-extension-manifest.js +235 -0
- package/dist/hosted-extension-manifest.js.map +1 -0
- package/dist/hosted-extension-protocol.d.ts +16 -0
- package/dist/hosted-extension-protocol.d.ts.map +1 -0
- package/dist/hosted-extension-protocol.js +224 -0
- package/dist/hosted-extension-protocol.js.map +1 -0
- package/dist/hosted-extension-runtime.d.ts +30 -0
- package/dist/hosted-extension-runtime.d.ts.map +1 -0
- package/dist/hosted-extension-runtime.js +167 -0
- package/dist/hosted-extension-runtime.js.map +1 -0
- package/dist/hosted-extension-sandbox-source.d.ts +9 -0
- package/dist/hosted-extension-sandbox-source.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox-source.js +95 -0
- package/dist/hosted-extension-sandbox-source.js.map +1 -0
- package/dist/hosted-extension-sandbox.d.ts +39 -0
- package/dist/hosted-extension-sandbox.d.ts.map +1 -0
- package/dist/hosted-extension-sandbox.js +418 -0
- package/dist/hosted-extension-sandbox.js.map +1 -0
- package/dist/hosted-project.d.ts +3 -0
- package/dist/hosted-project.d.ts.map +1 -0
- package/dist/hosted-project.js +19 -0
- package/dist/hosted-project.js.map +1 -0
- package/dist/hosted-promise-safety.d.ts +12 -0
- package/dist/hosted-promise-safety.d.ts.map +1 -0
- package/dist/hosted-promise-safety.js +63 -0
- package/dist/hosted-promise-safety.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/intrinsic-array.d.ts +3 -0
- package/dist/intrinsic-array.d.ts.map +1 -0
- package/dist/intrinsic-array.js +14 -0
- package/dist/intrinsic-array.js.map +1 -0
- package/dist/json-value.d.ts +6 -1
- package/dist/json-value.d.ts.map +1 -1
- package/dist/json-value.js +155 -36
- package/dist/json-value.js.map +1 -1
- package/dist/llm-worker.d.ts +9 -0
- package/dist/llm-worker.d.ts.map +1 -1
- package/dist/llm-worker.js +15 -1
- package/dist/llm-worker.js.map +1 -1
- package/dist/plugin-store.d.ts +8 -2
- package/dist/plugin-store.d.ts.map +1 -1
- package/dist/plugin-store.js +233 -56
- package/dist/plugin-store.js.map +1 -1
- package/dist/semver-range.d.ts.map +1 -1
- package/dist/semver-range.js +27 -15
- package/dist/semver-range.js.map +1 -1
- package/package.json +2 -2
- package/src/authored-flow-executor.ts +10 -1
- package/src/authored-step-index.ts +46 -12
- package/src/bundle-extensions.ts +6 -5
- package/src/bundle.ts +52 -7
- package/src/canonical.ts +20 -6
- package/src/cli/status.ts +50 -1
- package/src/flow-extension-compat.ts +32 -7
- package/src/flow-extension-loader.ts +56 -21
- package/src/fs-descriptor.ts +127 -0
- package/src/hosted-base-snapshot.ts +351 -0
- package/src/hosted-extension-declarations.ts +387 -0
- package/src/hosted-extension-isolation.ts +457 -0
- package/src/hosted-extension-manifest.ts +249 -0
- package/src/hosted-extension-protocol.ts +262 -0
- package/src/hosted-extension-runtime.ts +266 -0
- package/src/hosted-extension-sandbox-source.ts +94 -0
- package/src/hosted-extension-sandbox.ts +489 -0
- package/src/hosted-project.ts +18 -0
- package/src/hosted-promise-safety.ts +68 -0
- package/src/index.ts +18 -0
- package/src/intrinsic-array.ts +14 -0
- package/src/json-value.ts +184 -32
- package/src/llm-worker.ts +15 -1
- package/src/plugin-store.ts +323 -56
- package/src/semver-range.ts +39 -14
package/src/json-value.ts
CHANGED
|
@@ -1,4 +1,38 @@
|
|
|
1
1
|
import { isProxy } from 'node:util/types';
|
|
2
|
+
import { appendIntrinsicArray } from './intrinsic-array.js';
|
|
3
|
+
|
|
4
|
+
const ARRAY_IS_ARRAY = Array.isArray;
|
|
5
|
+
const ARRAY_PROTOTYPE = Array.prototype;
|
|
6
|
+
const ERROR = Error;
|
|
7
|
+
const IS_PROXY = isProxy;
|
|
8
|
+
const JSON_STRINGIFY = JSON.stringify;
|
|
9
|
+
const NUMBER = Number;
|
|
10
|
+
const NUMBER_IS_FINITE = Number.isFinite;
|
|
11
|
+
const NUMBER_IS_INTEGER = Number.isInteger;
|
|
12
|
+
const OBJECT_CREATE = Object.create;
|
|
13
|
+
const OBJECT_DEFINE_PROPERTY = Object.defineProperty;
|
|
14
|
+
const OBJECT_FREEZE = Object.freeze;
|
|
15
|
+
const OBJECT_GET_OWN_PROPERTY_DESCRIPTOR = Object.getOwnPropertyDescriptor;
|
|
16
|
+
const OBJECT_GET_PROTOTYPE_OF = Object.getPrototypeOf;
|
|
17
|
+
const OBJECT_HAS_OWN = Object.hasOwn;
|
|
18
|
+
const OBJECT_PROTOTYPE = Object.prototype;
|
|
19
|
+
const REGEXP_TEST = Function.prototype.call.bind(RegExp.prototype.test) as (
|
|
20
|
+
regexp: RegExp, value: string,
|
|
21
|
+
) => boolean;
|
|
22
|
+
const STRING = String;
|
|
23
|
+
const STRING_CHAR_CODE_AT = Function.prototype.call.bind(String.prototype.charCodeAt) as (
|
|
24
|
+
value: string, index: number,
|
|
25
|
+
) => number;
|
|
26
|
+
const WEAK_SET = WeakSet;
|
|
27
|
+
const WEAK_SET_ADD = Function.prototype.call.bind(WeakSet.prototype.add) as <T extends object>(
|
|
28
|
+
set: WeakSet<T>, value: T,
|
|
29
|
+
) => WeakSet<T>;
|
|
30
|
+
const WEAK_SET_DELETE = Function.prototype.call.bind(WeakSet.prototype.delete) as <T extends object>(
|
|
31
|
+
set: WeakSet<T>, value: T,
|
|
32
|
+
) => boolean;
|
|
33
|
+
const WEAK_SET_HAS = Function.prototype.call.bind(WeakSet.prototype.has) as <T extends object>(
|
|
34
|
+
set: WeakSet<T>, value: T,
|
|
35
|
+
) => boolean;
|
|
2
36
|
|
|
3
37
|
export type JsonValue =
|
|
4
38
|
| null
|
|
@@ -8,15 +42,45 @@ export type JsonValue =
|
|
|
8
42
|
| JsonValue[]
|
|
9
43
|
| { [key: string]: JsonValue };
|
|
10
44
|
|
|
45
|
+
export interface JsonSnapshotLimits {
|
|
46
|
+
readonly maxDepth: number;
|
|
47
|
+
readonly maxNodes: number;
|
|
48
|
+
readonly maxBytes: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface SnapshotBudget {
|
|
52
|
+
readonly limits?: JsonSnapshotLimits;
|
|
53
|
+
nodes: number;
|
|
54
|
+
properties: number;
|
|
55
|
+
bytes: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
11
58
|
/** Copy runtime input into frozen, behavior-free JSON data. */
|
|
12
|
-
export function snapshotJsonValue(value: unknown, at: string): JsonValue {
|
|
13
|
-
return snapshot(value, at, new
|
|
59
|
+
export function snapshotJsonValue(value: unknown, at: string, limits?: JsonSnapshotLimits): JsonValue {
|
|
60
|
+
return snapshot(value, at, new WEAK_SET<object>(), { limits, nodes: 0, properties: 0, bytes: 0 }, 0);
|
|
14
61
|
}
|
|
15
62
|
|
|
16
|
-
function snapshot(
|
|
17
|
-
|
|
63
|
+
function snapshot(
|
|
64
|
+
value: unknown,
|
|
65
|
+
at: string,
|
|
66
|
+
ancestors: WeakSet<object>,
|
|
67
|
+
budget: SnapshotBudget,
|
|
68
|
+
depth: number,
|
|
69
|
+
): JsonValue {
|
|
70
|
+
consumeNode(budget, at, depth);
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
consumeStringBytes(budget, value, at);
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (value === null || typeof value === 'boolean') {
|
|
76
|
+
consumeBytes(budget, value === null ? 4 : value ? 4 : 5, at);
|
|
77
|
+
return value;
|
|
78
|
+
}
|
|
18
79
|
if (typeof value === 'number') {
|
|
19
|
-
if (
|
|
80
|
+
if (NUMBER_IS_FINITE(value)) {
|
|
81
|
+
consumeBytes(budget, STRING(value).length, at);
|
|
82
|
+
return value;
|
|
83
|
+
}
|
|
20
84
|
throw nonJson(at, 'numbers must be finite');
|
|
21
85
|
}
|
|
22
86
|
if (typeof value !== 'object') {
|
|
@@ -25,15 +89,15 @@ function snapshot(value: unknown, at: string, ancestors: WeakSet<object>): JsonV
|
|
|
25
89
|
// Every ordinary reflective operation on a Proxy can execute author code.
|
|
26
90
|
// Node and Bun expose this trap-free brand check, so reject before touching
|
|
27
91
|
// its prototype, keys, descriptors, or identity collection.
|
|
28
|
-
if (
|
|
29
|
-
if (ancestors
|
|
30
|
-
ancestors
|
|
92
|
+
if (IS_PROXY(value)) throw nonJson(at, 'Proxy objects are not allowed');
|
|
93
|
+
if (WEAK_SET_HAS(ancestors, value)) throw nonJson(at, 'cycles are not allowed');
|
|
94
|
+
WEAK_SET_ADD(ancestors, value);
|
|
31
95
|
try {
|
|
32
|
-
return
|
|
33
|
-
? snapshotArray(value, at, ancestors)
|
|
34
|
-
: snapshotObject(value, at, ancestors);
|
|
96
|
+
return ARRAY_IS_ARRAY(value)
|
|
97
|
+
? snapshotArray(value, at, ancestors, budget, depth)
|
|
98
|
+
: snapshotObject(value, at, ancestors, budget, depth);
|
|
35
99
|
} finally {
|
|
36
|
-
ancestors
|
|
100
|
+
WEAK_SET_DELETE(ancestors, value);
|
|
37
101
|
}
|
|
38
102
|
}
|
|
39
103
|
|
|
@@ -41,53 +105,88 @@ function snapshotArray(
|
|
|
41
105
|
value: unknown[],
|
|
42
106
|
at: string,
|
|
43
107
|
ancestors: WeakSet<object>,
|
|
108
|
+
budget: SnapshotBudget,
|
|
109
|
+
depth: number,
|
|
44
110
|
): JsonValue[] {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
111
|
+
if (OBJECT_GET_PROTOTYPE_OF(value) !== ARRAY_PROTOTYPE
|
|
112
|
+
|| OBJECT_GET_PROTOTYPE_OF(ARRAY_PROTOTYPE) !== OBJECT_PROTOTYPE) {
|
|
113
|
+
throw nonJson(at, 'only arrays with the intrinsic prototype are allowed');
|
|
114
|
+
}
|
|
115
|
+
consumeBytes(budget, 2, at);
|
|
116
|
+
if (budget.limits !== undefined && value.length > budget.limits.maxNodes - budget.nodes) {
|
|
117
|
+
throw nonJson(at, 'snapshot depth or node limit exceeded');
|
|
118
|
+
}
|
|
119
|
+
// `Reflect.ownKeys` allocates the complete key list before a caller can
|
|
120
|
+
// enforce cardinality. Enumerate JSON-visible keys one at a time instead.
|
|
121
|
+
// Symbols and non-enumerable properties are deliberately ignored, matching
|
|
122
|
+
// JSON.stringify; the copied value has neither, so they cannot affect later
|
|
123
|
+
// validation or serialization.
|
|
124
|
+
for (const key in value) {
|
|
125
|
+
consumeProperty(budget, at);
|
|
126
|
+
if (!OBJECT_HAS_OWN(value, key)) continue;
|
|
127
|
+
if (!isArrayIndex(key, value.length)) {
|
|
49
128
|
throw nonJson(at, 'arrays may contain only indexed data');
|
|
50
129
|
}
|
|
51
130
|
}
|
|
52
131
|
const out: JsonValue[] = [];
|
|
53
132
|
for (let index = 0; index < value.length; index += 1) {
|
|
54
|
-
|
|
133
|
+
if (index > 0) consumeBytes(budget, 1, at);
|
|
134
|
+
const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(value, STRING(index));
|
|
55
135
|
if (descriptor === undefined) throw nonJson(`${at}[${index}]`, 'array holes are not allowed');
|
|
56
|
-
out
|
|
136
|
+
appendIntrinsicArray(out, snapshotDescriptor(descriptor, `${at}[${index}]`, ancestors, budget, depth + 1));
|
|
57
137
|
}
|
|
58
|
-
|
|
138
|
+
// JSON.stringify consults `toJSON` before applying array semantics. Shadow
|
|
139
|
+
// any poisoned Array.prototype hook with inert, non-JSON-visible data while
|
|
140
|
+
// retaining the intrinsic prototype expected by downstream array consumers.
|
|
141
|
+
OBJECT_DEFINE_PROPERTY(out, 'toJSON', { value: undefined });
|
|
142
|
+
return OBJECT_FREEZE(out) as unknown as JsonValue[];
|
|
59
143
|
}
|
|
60
144
|
|
|
61
145
|
function snapshotObject(
|
|
62
146
|
value: object,
|
|
63
147
|
at: string,
|
|
64
148
|
ancestors: WeakSet<object>,
|
|
149
|
+
budget: SnapshotBudget,
|
|
150
|
+
depth: number,
|
|
65
151
|
): { [key: string]: JsonValue } {
|
|
66
|
-
const prototype =
|
|
67
|
-
if (prototype !==
|
|
152
|
+
const prototype = OBJECT_GET_PROTOTYPE_OF(value);
|
|
153
|
+
if (prototype !== OBJECT_PROTOTYPE && prototype !== null) {
|
|
68
154
|
throw nonJson(at, 'only plain objects are allowed');
|
|
69
155
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
156
|
+
consumeBytes(budget, 2, at);
|
|
157
|
+
const out = OBJECT_CREATE(null) as { [key: string]: JsonValue };
|
|
158
|
+
let included = 0;
|
|
159
|
+
// Avoid materializing an attacker-sized key array in the unsandboxed
|
|
160
|
+
// parent. This visits only JSON-visible own string properties; symbols and
|
|
161
|
+
// non-enumerable properties are omitted exactly as JSON.stringify omits
|
|
162
|
+
// them, and cannot influence the null-prototype copy.
|
|
163
|
+
for (const key in value) {
|
|
164
|
+
consumeProperty(budget, at);
|
|
165
|
+
if (!OBJECT_HAS_OWN(value, key)) continue;
|
|
73
166
|
const childAt = propertyPath(at, key);
|
|
74
|
-
const descriptor =
|
|
167
|
+
const descriptor = OBJECT_GET_OWN_PROPERTY_DESCRIPTOR(value, key);
|
|
75
168
|
if (descriptor === undefined) throw nonJson(childAt, 'missing property descriptor');
|
|
76
169
|
const child = descriptorValue(descriptor, childAt);
|
|
77
170
|
// JSON.stringify and the pre-existing compiler omit undefined object
|
|
78
171
|
// optionals. Arrays remain strict because undefined there becomes null.
|
|
79
172
|
if (child === undefined) continue;
|
|
80
|
-
|
|
173
|
+
if (included > 0) consumeBytes(budget, 1, at);
|
|
174
|
+
consumeStringBytes(budget, key, childAt);
|
|
175
|
+
consumeBytes(budget, 1, childAt);
|
|
176
|
+
out[key] = snapshot(child, childAt, ancestors, budget, depth + 1);
|
|
177
|
+
included += 1;
|
|
81
178
|
}
|
|
82
|
-
return
|
|
179
|
+
return OBJECT_FREEZE(out);
|
|
83
180
|
}
|
|
84
181
|
|
|
85
182
|
function snapshotDescriptor(
|
|
86
183
|
descriptor: PropertyDescriptor,
|
|
87
184
|
at: string,
|
|
88
185
|
ancestors: WeakSet<object>,
|
|
186
|
+
budget: SnapshotBudget,
|
|
187
|
+
depth: number,
|
|
89
188
|
): JsonValue {
|
|
90
|
-
return snapshot(descriptorValue(descriptor, at), at, ancestors);
|
|
189
|
+
return snapshot(descriptorValue(descriptor, at), at, ancestors, budget, depth);
|
|
91
190
|
}
|
|
92
191
|
|
|
93
192
|
function descriptorValue(descriptor: PropertyDescriptor, at: string): unknown {
|
|
@@ -97,14 +196,67 @@ function descriptorValue(descriptor: PropertyDescriptor, at: string): unknown {
|
|
|
97
196
|
}
|
|
98
197
|
|
|
99
198
|
function isArrayIndex(key: string, length: number): boolean {
|
|
100
|
-
const index =
|
|
101
|
-
return
|
|
199
|
+
const index = NUMBER(key);
|
|
200
|
+
return NUMBER_IS_INTEGER(index) && index >= 0 && index < length && STRING(index) === key;
|
|
102
201
|
}
|
|
103
202
|
|
|
104
203
|
function propertyPath(at: string, key: string): string {
|
|
105
|
-
|
|
204
|
+
if (key.length > 100) return `${at}[long property]`;
|
|
205
|
+
return REGEXP_TEST(/^[A-Za-z_$][A-Za-z0-9_$]*$/, key)
|
|
206
|
+
? `${at}.${key}`
|
|
207
|
+
: `${at}[${JSON_STRINGIFY(key)}]`;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function consumeNode(budget: SnapshotBudget, at: string, depth: number): void {
|
|
211
|
+
budget.nodes += 1;
|
|
212
|
+
if (budget.limits !== undefined
|
|
213
|
+
&& (depth > budget.limits.maxDepth || budget.nodes > budget.limits.maxNodes)) {
|
|
214
|
+
throw nonJson(at, 'snapshot depth or node limit exceeded');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function consumeProperty(budget: SnapshotBudget, at: string): void {
|
|
219
|
+
if (budget.limits === undefined) return;
|
|
220
|
+
budget.properties += 1;
|
|
221
|
+
if (budget.properties > budget.limits.maxNodes) {
|
|
222
|
+
throw nonJson(at, 'snapshot depth or node limit exceeded');
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Count JSON's UTF-8 string encoding without allocating the escaped value. */
|
|
227
|
+
function consumeStringBytes(budget: SnapshotBudget, value: string, at: string): void {
|
|
228
|
+
consumeBytes(budget, 2, at);
|
|
229
|
+
if (budget.limits !== undefined && value.length > budget.limits.maxBytes - budget.bytes) {
|
|
230
|
+
throw nonJson(at, 'snapshot byte limit exceeded');
|
|
231
|
+
}
|
|
232
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
233
|
+
const code = STRING_CHAR_CODE_AT(value, index);
|
|
234
|
+
if (code === 0x22 || code === 0x5c || code === 0x08 || code === 0x09
|
|
235
|
+
|| code === 0x0a || code === 0x0c || code === 0x0d) {
|
|
236
|
+
consumeBytes(budget, 2, at);
|
|
237
|
+
} else if (code < 0x20 || (code >= 0xd800 && code <= 0xdfff)) {
|
|
238
|
+
if (code >= 0xd800 && code <= 0xdbff && index + 1 < value.length) {
|
|
239
|
+
const low = STRING_CHAR_CODE_AT(value, index + 1);
|
|
240
|
+
if (low >= 0xdc00 && low <= 0xdfff) {
|
|
241
|
+
consumeBytes(budget, 4, at);
|
|
242
|
+
index += 1;
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
consumeBytes(budget, 6, at);
|
|
247
|
+
} else if (code < 0x80) consumeBytes(budget, 1, at);
|
|
248
|
+
else if (code < 0x800) consumeBytes(budget, 2, at);
|
|
249
|
+
else consumeBytes(budget, 3, at);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function consumeBytes(budget: SnapshotBudget, bytes: number, at: string): void {
|
|
254
|
+
budget.bytes += bytes;
|
|
255
|
+
if (budget.limits !== undefined && budget.bytes > budget.limits.maxBytes) {
|
|
256
|
+
throw nonJson(at, 'snapshot byte limit exceeded');
|
|
257
|
+
}
|
|
106
258
|
}
|
|
107
259
|
|
|
108
260
|
function nonJson(at: string, detail: string): Error {
|
|
109
|
-
return new
|
|
261
|
+
return new ERROR(`${at}: expected JSON-compatible data; ${detail}`);
|
|
110
262
|
}
|
package/src/llm-worker.ts
CHANGED
|
@@ -67,7 +67,7 @@ export class LlmWorker extends EventEmitter {
|
|
|
67
67
|
let detail = result.stderr_tail;
|
|
68
68
|
if (reason === 'success' && schema !== undefined) {
|
|
69
69
|
try {
|
|
70
|
-
output = JSON.parse(result.stdout_tail);
|
|
70
|
+
output = JSON.parse(unfenced(result.stdout_tail));
|
|
71
71
|
const invalid = jsonSchemaOutputError(schema, output);
|
|
72
72
|
if (invalid !== undefined) {
|
|
73
73
|
reason = 'verification_failed';
|
|
@@ -98,3 +98,17 @@ export class LlmWorker extends EventEmitter {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A reply that is exactly one markdown code fence around a value, reduced to
|
|
104
|
+
* that value. Models add the fence despite the instruction not to; the schema
|
|
105
|
+
* still judges what is inside it. A fence opens with three or more backticks
|
|
106
|
+
* or tildes and closes with a run of the same character at least as long.
|
|
107
|
+
* Anything else (prose around the JSON, two fences) is returned unchanged and
|
|
108
|
+
* fails the parse as before.
|
|
109
|
+
*/
|
|
110
|
+
export function unfenced(text: string): string {
|
|
111
|
+
const fence = /^\s*(?:(`{3,})[^`\n]*\n([\s\S]*?)\n?\1`*|(~{3,})[^\n]*\n([\s\S]*?)\n?\3~*)\s*$/.exec(text);
|
|
112
|
+
if (fence === null) return text;
|
|
113
|
+
return fence[2] ?? fence[4]!;
|
|
114
|
+
}
|