@qvac/sdk 0.10.0 → 0.10.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/NOTICE +28 -28
- package/dist/examples/{llamacpp-native-tools.js → tools/llamacpp-native-tools.js} +4 -47
- package/dist/examples/tools/llamacpp-tools-harmony-multiturn.d.ts +2 -0
- package/dist/examples/tools/llamacpp-tools-harmony-multiturn.js +81 -0
- package/dist/examples/{llamacpp-tools-pythonic.js → tools/llamacpp-tools-pythonic.js} +1 -20
- package/dist/examples/tools/shared.d.ts +21 -0
- package/dist/examples/tools/shared.js +42 -0
- package/dist/models/registry/models.d.ts +312 -1231
- package/dist/models/registry/models.js +2081 -3025
- package/dist/models/update-models/registry.d.ts +1 -0
- package/dist/models/update-models/registry.js +19 -1
- package/dist/schemas/common.d.ts +1 -0
- package/dist/schemas/completion-stream.d.ts +4 -0
- package/dist/schemas/completion-stream.js +2 -1
- package/dist/server/bare/delegate-rpc-client.js +10 -8
- package/dist/server/bare/plugins/llamacpp-completion/plugin.d.ts +1 -0
- package/dist/server/bare/plugins/llamacpp-completion/plugin.js +3 -3
- package/dist/server/bare/plugins/nmtcpp-translation/plugin.js +1 -82
- package/dist/server/bare/plugins/nmtcpp-translation/resolve-vocab.d.ts +32 -0
- package/dist/server/bare/plugins/nmtcpp-translation/resolve-vocab.js +117 -0
- package/dist/server/utils/completion-normalizer.js +168 -56
- package/dist/server/utils/tools/dialect.js +2 -0
- package/dist/server/utils/tools/parser.js +6 -0
- package/dist/server/utils/tools/parsers/harmony.d.ts +4 -0
- package/dist/server/utils/tools/parsers/harmony.js +59 -0
- package/package.json +1 -1
- /package/dist/examples/{llamacpp-native-tools.d.ts → tools/llamacpp-native-tools.d.ts} +0 -0
- /package/dist/examples/{llamacpp-tools-pythonic.d.ts → tools/llamacpp-tools-pythonic.d.ts} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { parseToolCalls } from "./tools/index.js";
|
|
2
2
|
const DIALECT_SPECS = {
|
|
3
3
|
hermes: {
|
|
4
|
-
|
|
4
|
+
toolFrames: [{ open: "<tool_call>", close: "</tool_call>" }],
|
|
5
5
|
},
|
|
6
6
|
// No streaming framing — tool calls are only recovered at finalization.
|
|
7
7
|
json: {
|
|
8
|
-
|
|
8
|
+
toolFrames: [],
|
|
9
9
|
},
|
|
10
10
|
pythonic: {
|
|
11
|
-
|
|
11
|
+
toolFrames: [
|
|
12
12
|
{ open: "<|tool_call_start|>", close: "<|tool_call_end|>" },
|
|
13
13
|
{
|
|
14
14
|
open: "<|start_header_id|>tool_call<|end_header_id|>",
|
|
@@ -16,36 +16,90 @@ const DIALECT_SPECS = {
|
|
|
16
16
|
},
|
|
17
17
|
],
|
|
18
18
|
},
|
|
19
|
+
harmony: {
|
|
20
|
+
toolFrames: [
|
|
21
|
+
{ open: "<|channel|>commentary to=functions.", close: "<|call|>" },
|
|
22
|
+
],
|
|
23
|
+
thinkingFrames: [
|
|
24
|
+
{ open: "<|channel|>analysis<|message|>", close: "<|end|>" },
|
|
25
|
+
],
|
|
26
|
+
contentFrames: [
|
|
27
|
+
{ open: "<|channel|>final<|message|>", close: "<|return|>" },
|
|
28
|
+
],
|
|
29
|
+
// Role headers between channel frames + stray `<|return|>` closes.
|
|
30
|
+
dropTokens: [
|
|
31
|
+
"<|start|>assistant",
|
|
32
|
+
"<|start|>system",
|
|
33
|
+
"<|start|>user",
|
|
34
|
+
"<|return|>",
|
|
35
|
+
],
|
|
36
|
+
},
|
|
19
37
|
};
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
// Capture-gated reasoning marker — the generic `<think>...</think>`
|
|
39
|
+
// convention used by reasoning-tuned models. When the gate is off the
|
|
40
|
+
// markers pass through into `contentDelta` unchanged (preserves the
|
|
41
|
+
// pre-dialect behavior).
|
|
42
|
+
const BUILTIN_THINKING = { open: "<think>", close: "</think>" };
|
|
22
43
|
export function createCompletionNormalizer(config) {
|
|
23
44
|
let seq = 0;
|
|
24
45
|
let state = "content";
|
|
25
46
|
let rawText = "";
|
|
26
47
|
const contentParts = [];
|
|
27
48
|
const thinkingParts = [];
|
|
28
|
-
//
|
|
29
|
-
// can match its own dialect markers.
|
|
49
|
+
// Tool frame: buffer keeps the open + close markers in place so
|
|
50
|
+
// `parseToolCalls` can match its own dialect markers.
|
|
30
51
|
let toolCallBuffer = "";
|
|
31
|
-
let
|
|
52
|
+
let activeToolFrame = null;
|
|
53
|
+
// Thinking frame: tracks which frame's close marker we're scanning for
|
|
54
|
+
// (could be the built-in `<think>` or a dialect-specific frame like
|
|
55
|
+
// Harmony's `<|channel|>analysis<|message|>...<|end|>`).
|
|
56
|
+
let activeThinkingFrame = null;
|
|
57
|
+
// Content frame: inner streamed as it arrives; partial close tail buffered.
|
|
58
|
+
let activeContentFrame = null;
|
|
32
59
|
// Trailing chars that could be a prefix of a watched marker, so markers
|
|
33
60
|
// split across token pushes aren't missed by indexOf.
|
|
34
61
|
let contentTail = "";
|
|
35
|
-
let
|
|
36
|
-
let
|
|
62
|
+
let toolFramingTail = "";
|
|
63
|
+
let thinkingFramingTail = "";
|
|
64
|
+
let contentFramingTail = "";
|
|
37
65
|
const streamedToolCallKeys = new Set();
|
|
38
66
|
const toolsEnabled = config.tools.length > 0 && config.capabilities.toolCalling !== "none";
|
|
39
|
-
const
|
|
40
|
-
|
|
67
|
+
const useToolFraming = toolsEnabled && config.capabilities.toolCalling === "textParse";
|
|
68
|
+
// Register `<think>` only when both capability + capture opt in.
|
|
69
|
+
// Dialect protocol markers always register; capture only gates emission.
|
|
70
|
+
const useBuiltinThinkTags = config.captureThinking &&
|
|
71
|
+
config.capabilities.thinkingFraming === "thinkTags";
|
|
41
72
|
const dialect = config.toolDialect ?? "hermes";
|
|
42
73
|
const dialectSpec = DIALECT_SPECS[dialect];
|
|
43
|
-
const
|
|
74
|
+
const toolFrames = useToolFraming
|
|
75
|
+
? dialectSpec.toolFrames
|
|
76
|
+
: [];
|
|
77
|
+
const thinkingFrames = [
|
|
78
|
+
...(useBuiltinThinkTags ? [BUILTIN_THINKING] : []),
|
|
79
|
+
...(dialectSpec.thinkingFrames ?? []),
|
|
80
|
+
];
|
|
81
|
+
const contentFrames = dialectSpec.contentFrames ?? [];
|
|
82
|
+
const dropTokens = dialectSpec.dropTokens ?? [];
|
|
83
|
+
const markerToEntry = new Map();
|
|
84
|
+
const dropTokenSet = new Set();
|
|
44
85
|
const contentMarkers = [];
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
86
|
+
function registerFrames(frames, kind) {
|
|
87
|
+
for (const f of frames) {
|
|
88
|
+
if (markerToEntry.has(f.open))
|
|
89
|
+
continue;
|
|
90
|
+
markerToEntry.set(f.open, { kind, spec: f });
|
|
91
|
+
contentMarkers.push(f.open);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
registerFrames(toolFrames, "tool");
|
|
95
|
+
registerFrames(thinkingFrames, "thinking");
|
|
96
|
+
registerFrames(contentFrames, "content");
|
|
97
|
+
for (const t of dropTokens) {
|
|
98
|
+
if (markerToEntry.has(t) || dropTokenSet.has(t))
|
|
99
|
+
continue;
|
|
100
|
+
dropTokenSet.add(t);
|
|
101
|
+
contentMarkers.push(t);
|
|
102
|
+
}
|
|
49
103
|
function nextSeq() {
|
|
50
104
|
return seq++;
|
|
51
105
|
}
|
|
@@ -55,11 +109,14 @@ export function createCompletionNormalizer(config) {
|
|
|
55
109
|
contentParts.push(text);
|
|
56
110
|
events.push({ type: "contentDelta", seq: nextSeq(), text });
|
|
57
111
|
}
|
|
112
|
+
// Capture-gated: thinking frames may register even when `captureThinking`
|
|
113
|
+
// is false (e.g. harmony's analysis channel, to strip markers from
|
|
114
|
+
// `contentDelta`) — drop the inner here instead of buffering it.
|
|
58
115
|
function emitThinking(events, text) {
|
|
59
116
|
if (!text)
|
|
60
117
|
return;
|
|
61
|
-
thinkingParts.push(text);
|
|
62
118
|
if (config.captureThinking) {
|
|
119
|
+
thinkingParts.push(text);
|
|
63
120
|
events.push({ type: "thinkingDelta", seq: nextSeq(), text });
|
|
64
121
|
}
|
|
65
122
|
}
|
|
@@ -102,10 +159,10 @@ export function createCompletionNormalizer(config) {
|
|
|
102
159
|
}
|
|
103
160
|
function flushToolBuffer(events, closed) {
|
|
104
161
|
const raw = toolCallBuffer;
|
|
105
|
-
const frame =
|
|
162
|
+
const frame = activeToolFrame;
|
|
106
163
|
toolCallBuffer = "";
|
|
107
|
-
|
|
108
|
-
|
|
164
|
+
activeToolFrame = null;
|
|
165
|
+
toolFramingTail = "";
|
|
109
166
|
if (!frame)
|
|
110
167
|
return;
|
|
111
168
|
// Empty-payload guard. Strip frame markers and check the inner is
|
|
@@ -150,64 +207,110 @@ export function createCompletionNormalizer(config) {
|
|
|
150
207
|
emitContent(events, raw);
|
|
151
208
|
}
|
|
152
209
|
}
|
|
210
|
+
function getStateTail() {
|
|
211
|
+
switch (state) {
|
|
212
|
+
case "content":
|
|
213
|
+
return contentTail;
|
|
214
|
+
case "thinkingFraming":
|
|
215
|
+
return thinkingFramingTail;
|
|
216
|
+
case "toolFraming":
|
|
217
|
+
return toolFramingTail;
|
|
218
|
+
case "contentFraming":
|
|
219
|
+
return contentFramingTail;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
153
222
|
function processSegment(events, text) {
|
|
154
|
-
let buf = (
|
|
155
|
-
? contentTail
|
|
156
|
-
: state === "thinking"
|
|
157
|
-
? thinkingTail
|
|
158
|
-
: toolTail) + text;
|
|
223
|
+
let buf = getStateTail() + text;
|
|
159
224
|
contentTail = "";
|
|
160
|
-
|
|
161
|
-
|
|
225
|
+
thinkingFramingTail = "";
|
|
226
|
+
toolFramingTail = "";
|
|
227
|
+
contentFramingTail = "";
|
|
162
228
|
while (buf.length > 0) {
|
|
163
|
-
if (state === "
|
|
164
|
-
if (!
|
|
229
|
+
if (state === "toolFraming") {
|
|
230
|
+
if (!activeToolFrame) {
|
|
165
231
|
state = "content";
|
|
166
232
|
continue;
|
|
167
233
|
}
|
|
168
|
-
const hit = buf.indexOf(
|
|
234
|
+
const hit = buf.indexOf(activeToolFrame.close);
|
|
169
235
|
if (hit >= 0) {
|
|
170
|
-
toolCallBuffer += buf.slice(0, hit +
|
|
171
|
-
buf = buf.slice(hit +
|
|
236
|
+
toolCallBuffer += buf.slice(0, hit + activeToolFrame.close.length);
|
|
237
|
+
buf = buf.slice(hit + activeToolFrame.close.length);
|
|
172
238
|
state = "content";
|
|
173
239
|
flushToolBuffer(events, true);
|
|
174
240
|
continue;
|
|
175
241
|
}
|
|
176
|
-
const tailLen = trailingPartialMarkerLen(buf, [
|
|
242
|
+
const tailLen = trailingPartialMarkerLen(buf, [activeToolFrame.close]);
|
|
177
243
|
const safeLen = buf.length - tailLen;
|
|
178
244
|
toolCallBuffer += buf.slice(0, safeLen);
|
|
179
|
-
|
|
245
|
+
toolFramingTail = buf.slice(safeLen);
|
|
180
246
|
buf = "";
|
|
181
247
|
continue;
|
|
182
248
|
}
|
|
183
|
-
if (state === "
|
|
184
|
-
|
|
249
|
+
if (state === "thinkingFraming") {
|
|
250
|
+
if (!activeThinkingFrame) {
|
|
251
|
+
state = "content";
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const closeMarker = activeThinkingFrame.close;
|
|
255
|
+
const closeIdx = buf.indexOf(closeMarker);
|
|
185
256
|
if (closeIdx >= 0) {
|
|
186
257
|
emitThinking(events, buf.slice(0, closeIdx));
|
|
187
|
-
buf = buf.slice(closeIdx +
|
|
258
|
+
buf = buf.slice(closeIdx + closeMarker.length);
|
|
259
|
+
activeThinkingFrame = null;
|
|
188
260
|
state = "content";
|
|
189
261
|
continue;
|
|
190
262
|
}
|
|
191
|
-
const tailLen = trailingPartialMarkerLen(buf, [
|
|
263
|
+
const tailLen = trailingPartialMarkerLen(buf, [closeMarker]);
|
|
192
264
|
const safeLen = buf.length - tailLen;
|
|
193
265
|
emitThinking(events, buf.slice(0, safeLen));
|
|
194
|
-
|
|
266
|
+
thinkingFramingTail = buf.slice(safeLen);
|
|
267
|
+
buf = "";
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (state === "contentFraming") {
|
|
271
|
+
if (!activeContentFrame) {
|
|
272
|
+
state = "content";
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const closeMarker = activeContentFrame.close;
|
|
276
|
+
const closeIdx = buf.indexOf(closeMarker);
|
|
277
|
+
if (closeIdx >= 0) {
|
|
278
|
+
emitContent(events, buf.slice(0, closeIdx));
|
|
279
|
+
activeContentFrame = null;
|
|
280
|
+
contentFramingTail = "";
|
|
281
|
+
buf = buf.slice(closeIdx + closeMarker.length);
|
|
282
|
+
state = "content";
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const tailLen = trailingPartialMarkerLen(buf, [closeMarker]);
|
|
286
|
+
const safeLen = buf.length - tailLen;
|
|
287
|
+
emitContent(events, buf.slice(0, safeLen));
|
|
288
|
+
contentFramingTail = buf.slice(safeLen);
|
|
195
289
|
buf = "";
|
|
196
290
|
continue;
|
|
197
291
|
}
|
|
198
292
|
const hit = findEarliestMarker(buf, contentMarkers);
|
|
199
293
|
if (hit) {
|
|
200
294
|
emitContent(events, buf.slice(0, hit.idx));
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
295
|
+
buf = buf.slice(hit.idx + hit.marker.length);
|
|
296
|
+
if (dropTokenSet.has(hit.marker)) {
|
|
297
|
+
// Glue token between frames — strip and stay in content state.
|
|
204
298
|
continue;
|
|
205
299
|
}
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
300
|
+
const entry = markerToEntry.get(hit.marker);
|
|
301
|
+
if (entry.kind === "tool") {
|
|
302
|
+
toolCallBuffer = hit.marker;
|
|
303
|
+
activeToolFrame = entry.spec;
|
|
304
|
+
state = "toolFraming";
|
|
305
|
+
}
|
|
306
|
+
else if (entry.kind === "thinking") {
|
|
307
|
+
activeThinkingFrame = entry.spec;
|
|
308
|
+
state = "thinkingFraming";
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
activeContentFrame = entry.spec;
|
|
312
|
+
state = "contentFraming";
|
|
313
|
+
}
|
|
211
314
|
continue;
|
|
212
315
|
}
|
|
213
316
|
const tailLen = trailingPartialMarkerLen(buf, contentMarkers);
|
|
@@ -234,24 +337,33 @@ export function createCompletionNormalizer(config) {
|
|
|
234
337
|
emitContent(events, contentTail);
|
|
235
338
|
contentTail = "";
|
|
236
339
|
}
|
|
237
|
-
else if (state === "
|
|
238
|
-
emitThinking(events,
|
|
239
|
-
|
|
340
|
+
else if (state === "thinkingFraming" && thinkingFramingTail) {
|
|
341
|
+
emitThinking(events, thinkingFramingTail);
|
|
342
|
+
thinkingFramingTail = "";
|
|
240
343
|
}
|
|
241
|
-
else if (state === "
|
|
242
|
-
toolCallBuffer +=
|
|
243
|
-
|
|
344
|
+
else if (state === "toolFraming" && toolFramingTail) {
|
|
345
|
+
toolCallBuffer += toolFramingTail;
|
|
346
|
+
toolFramingTail = "";
|
|
244
347
|
}
|
|
245
|
-
if (state === "
|
|
348
|
+
else if (state === "contentFraming" && contentFramingTail) {
|
|
349
|
+
// Cutoff inside a partial close marker — emit tail as content.
|
|
350
|
+
emitContent(events, contentFramingTail);
|
|
351
|
+
contentFramingTail = "";
|
|
352
|
+
}
|
|
353
|
+
if (state === "toolFraming" && activeToolFrame) {
|
|
246
354
|
if (opts?.error) {
|
|
247
355
|
emitContent(events, toolCallBuffer);
|
|
248
356
|
toolCallBuffer = "";
|
|
249
|
-
|
|
357
|
+
activeToolFrame = null;
|
|
250
358
|
}
|
|
251
359
|
else {
|
|
252
360
|
flushToolBuffer(events, false);
|
|
253
361
|
}
|
|
254
362
|
}
|
|
363
|
+
else if (state === "contentFraming") {
|
|
364
|
+
activeContentFrame = null;
|
|
365
|
+
}
|
|
366
|
+
activeThinkingFrame = null;
|
|
255
367
|
state = "content";
|
|
256
368
|
if (!opts?.error) {
|
|
257
369
|
if (opts?.toolCalls) {
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export function detectToolDialectFromName(name, path) {
|
|
7
7
|
const basename = path.toLowerCase().split(/[/\\]/).pop() ?? "";
|
|
8
8
|
const tag = `${(name ?? "").toLowerCase()}|${basename}`;
|
|
9
|
+
if (/gpt[_-]?oss/.test(tag))
|
|
10
|
+
return "harmony";
|
|
9
11
|
if (/lfm[_-]?\d/.test(tag))
|
|
10
12
|
return "pythonic";
|
|
11
13
|
return "hermes";
|
|
@@ -2,6 +2,7 @@ import { stripThinkingBlocks, } from "./shared.js";
|
|
|
2
2
|
import { parseGemmaFormat, parseGenericFormat, parseLlamacppFormat, } from "./parsers/json.js";
|
|
3
3
|
import { parseHermesFormat } from "./parsers/hermes.js";
|
|
4
4
|
import { parsePythonicFormat } from "./parsers/pythonic.js";
|
|
5
|
+
import { parseHarmonyFormat } from "./parsers/harmony.js";
|
|
5
6
|
function pickFormatParsers(dialect) {
|
|
6
7
|
switch (dialect) {
|
|
7
8
|
case "pythonic":
|
|
@@ -12,10 +13,15 @@ function pickFormatParsers(dialect) {
|
|
|
12
13
|
return [parseHermesFormat, parseGemmaFormat, parseLlamacppFormat];
|
|
13
14
|
case "json":
|
|
14
15
|
return [parseGemmaFormat, parseLlamacppFormat];
|
|
16
|
+
case "harmony":
|
|
17
|
+
return [parseHarmonyFormat];
|
|
15
18
|
default:
|
|
19
|
+
// Harmony first: `to=functions.` is uniquely Harmony and can't
|
|
20
|
+
// false-match other dialects.
|
|
16
21
|
// Pythonic last: its bare `[name(...)]` form can match payloads that
|
|
17
22
|
// look like other dialects.
|
|
18
23
|
return [
|
|
24
|
+
parseHarmonyFormat,
|
|
19
25
|
parseHermesFormat,
|
|
20
26
|
parseGemmaFormat,
|
|
21
27
|
parseLlamacppFormat,
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { generateStableToolCallId, validateToolArguments, } from "../shared.js";
|
|
2
|
+
const HARMONY_FRAME_RE = /to=functions\.([\w-]+)\s+<\|constrain\|>json<\|message\|>([\s\S]*?)(?=<\|call\|>|$)/g;
|
|
3
|
+
export function parseHarmonyFormat(text, tools) {
|
|
4
|
+
const toolCalls = [];
|
|
5
|
+
const errors = [];
|
|
6
|
+
if (!text.includes("to=functions.")) {
|
|
7
|
+
return { matched: false, toolCalls, errors };
|
|
8
|
+
}
|
|
9
|
+
HARMONY_FRAME_RE.lastIndex = 0;
|
|
10
|
+
let m;
|
|
11
|
+
while ((m = HARMONY_FRAME_RE.exec(text)) !== null) {
|
|
12
|
+
const name = m[1];
|
|
13
|
+
const payload = m[2]?.trim();
|
|
14
|
+
const raw = m[0];
|
|
15
|
+
if (!name || payload === undefined)
|
|
16
|
+
continue;
|
|
17
|
+
let parsedArgs;
|
|
18
|
+
try {
|
|
19
|
+
parsedArgs = JSON.parse(payload);
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
errors.push({
|
|
23
|
+
code: "PARSE_ERROR",
|
|
24
|
+
message: `Failed to parse Harmony tool call JSON for ${name}: ${error instanceof Error ? error.message : String(error)}`,
|
|
25
|
+
raw,
|
|
26
|
+
});
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (typeof parsedArgs !== "object" ||
|
|
30
|
+
parsedArgs === null ||
|
|
31
|
+
Array.isArray(parsedArgs)) {
|
|
32
|
+
errors.push({
|
|
33
|
+
code: "PARSE_ERROR",
|
|
34
|
+
message: `Harmony tool call payload for ${name} is not a JSON object`,
|
|
35
|
+
raw,
|
|
36
|
+
});
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const args = parsedArgs;
|
|
40
|
+
const validation = validateToolArguments(name, args, tools);
|
|
41
|
+
if (!validation.isValid && validation.error) {
|
|
42
|
+
errors.push({ ...validation.error, raw });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
toolCalls.push({
|
|
46
|
+
id: generateStableToolCallId(name, args),
|
|
47
|
+
name,
|
|
48
|
+
arguments: args,
|
|
49
|
+
raw,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Defer `matched` to extraction so partial Harmony shapes fall through
|
|
53
|
+
// to the next parser instead of short-circuiting the chain.
|
|
54
|
+
return {
|
|
55
|
+
matched: toolCalls.length > 0 || errors.length > 0,
|
|
56
|
+
toolCalls,
|
|
57
|
+
errors,
|
|
58
|
+
};
|
|
59
|
+
}
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|