@jsonstudio/llms 0.6.198 → 0.6.203

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.
@@ -0,0 +1,845 @@
1
+ import { getCompatProfile } from './compat-profile-store.js';
2
+ import { UniversalShapeFilter } from '../../../compat/actions/universal-shape-filter.js';
3
+ import { ResponseBlacklistSanitizer } from '../../../compat/actions/response-blacklist.js';
4
+ import { applyFieldMappings } from '../../../compat/actions/field-mapping.js';
5
+ import { sanitizeToolSchema } from '../../../compat/actions/tool-schema.js';
6
+ import { applyRequestRules } from '../../../compat/actions/request-rules.js';
7
+ import { applyAutoThinking as runAutoThinking } from '../../../compat/actions/auto-thinking.js';
8
+ import { normalizeResponsePayload } from '../../../compat/actions/response-normalize.js';
9
+ import { validateResponsePayload } from '../../../compat/actions/response-validate.js';
10
+ import { writeCompatSnapshot } from '../../../compat/actions/snapshot.js';
11
+ import { applyQwenRequestTransform, applyQwenResponseTransform } from '../../../compat/actions/qwen-transform.js';
12
+ import { extractGlmToolMarkup } from '../../../compat/actions/glm-tool-extraction.js';
13
+ const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
14
+ const INTERNAL_STATE = Symbol('compat.internal_state');
15
+ export function runRequestCompatPipeline(profileId, payload, options) {
16
+ const profile = getCompatProfile(profileId);
17
+ if (!profile) {
18
+ return { payload };
19
+ }
20
+ const stage = pickStageConfig(profile, 'request');
21
+ if (!stage) {
22
+ return { payload };
23
+ }
24
+ const mutated = structuredClone(payload);
25
+ const state = initializeInternalState(mutated, 'request', options?.adapterContext);
26
+ if (Array.isArray(stage.mappings)) {
27
+ for (const mapping of stage.mappings) {
28
+ applyMapping(mutated, mapping, state);
29
+ }
30
+ }
31
+ return {
32
+ payload: mutated,
33
+ appliedProfile: profile.id
34
+ };
35
+ }
36
+ export function runResponseCompatPipeline(profileId, payload, options) {
37
+ const profile = getCompatProfile(profileId);
38
+ if (!profile) {
39
+ return { payload };
40
+ }
41
+ const stage = pickStageConfig(profile, 'response');
42
+ if (!stage) {
43
+ return { payload };
44
+ }
45
+ const mutated = structuredClone(payload);
46
+ const state = initializeInternalState(mutated, 'response', options?.adapterContext);
47
+ if (Array.isArray(stage.mappings)) {
48
+ for (const mapping of stage.mappings) {
49
+ applyMapping(mutated, mapping, state);
50
+ }
51
+ }
52
+ if (Array.isArray(stage.filters)) {
53
+ for (const filter of stage.filters) {
54
+ applyFilter(mutated, filter);
55
+ }
56
+ }
57
+ const requestIdFallback = state.originalRequestId || state.adapterContext?.requestId;
58
+ if (requestIdFallback && typeof mutated.request_id !== 'string') {
59
+ mutated.request_id = requestIdFallback;
60
+ }
61
+ return {
62
+ payload: mutated,
63
+ appliedProfile: profile.id
64
+ };
65
+ }
66
+ function pickStageConfig(profile, stage) {
67
+ if (!profile) {
68
+ return null;
69
+ }
70
+ if (stage === 'request' && profile.request) {
71
+ return profile.request;
72
+ }
73
+ if (stage === 'response' && profile.response) {
74
+ return profile.response;
75
+ }
76
+ if (profile.direction && profile.direction !== stage) {
77
+ return null;
78
+ }
79
+ if (profile.mappings || profile.filters) {
80
+ return {
81
+ mappings: profile.mappings,
82
+ filters: profile.filters
83
+ };
84
+ }
85
+ return null;
86
+ }
87
+ function applyMapping(root, mapping, state) {
88
+ switch (mapping.action) {
89
+ case 'remove':
90
+ removePath(root, mapping.path);
91
+ break;
92
+ case 'rename':
93
+ renamePath(root, mapping.from, mapping.to);
94
+ break;
95
+ case 'set':
96
+ setPath(root, mapping.path, mapping.value);
97
+ break;
98
+ case 'stringify':
99
+ stringifyPath(root, mapping.path, mapping.fallback);
100
+ break;
101
+ case 'parse_json':
102
+ parseJsonPath(root, mapping.path, mapping.fallback);
103
+ break;
104
+ case 'set_default':
105
+ setDefaultPath(root, mapping.path, mapping.value, mapping.valueSource);
106
+ break;
107
+ case 'normalize_tool_choice':
108
+ normalizeToolChoice(root, mapping);
109
+ break;
110
+ case 'inject_instruction':
111
+ injectInstruction(root, mapping);
112
+ break;
113
+ case 'convert_responses_output_to_choices':
114
+ convertResponsesOutputToChoices(root);
115
+ break;
116
+ case 'extract_glm_tool_markup':
117
+ extractGlmToolMarkup(root);
118
+ break;
119
+ case 'dto_unwrap':
120
+ dtoUnwrap(root, state);
121
+ break;
122
+ case 'dto_rewrap':
123
+ dtoRewrap(root, state);
124
+ break;
125
+ case 'shape_filter':
126
+ applyShapeFilterMapping(root, mapping, state);
127
+ break;
128
+ case 'field_map':
129
+ applyFieldMap(root, mapping, state);
130
+ break;
131
+ case 'tool_schema_sanitize':
132
+ applyToolSchemaSanitize(root, mapping);
133
+ break;
134
+ case 'apply_rules':
135
+ applyRules(root, mapping, state);
136
+ break;
137
+ case 'auto_thinking':
138
+ applyAutoThinkingAction(root, mapping, state);
139
+ break;
140
+ case 'snapshot':
141
+ triggerSnapshot(root, mapping, state);
142
+ break;
143
+ case 'resp_blacklist':
144
+ applyResponseBlacklist(root, mapping, state);
145
+ break;
146
+ case 'response_normalize':
147
+ applyResponseNormalize(root, mapping, state);
148
+ break;
149
+ case 'response_validate':
150
+ if (state.direction === 'response') {
151
+ validateResponsePayload(root, mapping.config);
152
+ }
153
+ break;
154
+ case 'qwen_request_transform':
155
+ replaceRoot(root, applyQwenRequestTransform(root));
156
+ break;
157
+ case 'qwen_response_transform':
158
+ replaceRoot(root, applyQwenResponseTransform(root));
159
+ break;
160
+ default:
161
+ break;
162
+ }
163
+ }
164
+ function applyFilter(payload, filter) {
165
+ if (filter.action === 'rate_limit_text') {
166
+ if (detectRateLimitText(payload, filter.needle)) {
167
+ const err = new Error('Provider returned rate limit notice');
168
+ err.code = RATE_LIMIT_ERROR;
169
+ err.statusCode = 429;
170
+ throw err;
171
+ }
172
+ }
173
+ }
174
+ function initializeInternalState(root, direction, adapterContext) {
175
+ const state = {
176
+ direction,
177
+ adapterContext,
178
+ originalRequestId: direction === 'response' ? extractRequestId(root) : undefined
179
+ };
180
+ Object.defineProperty(root, INTERNAL_STATE, {
181
+ value: state,
182
+ enumerable: false,
183
+ configurable: true
184
+ });
185
+ return state;
186
+ }
187
+ function replaceRoot(target, source) {
188
+ if (target === source) {
189
+ return;
190
+ }
191
+ for (const key of Object.keys(target)) {
192
+ delete target[key];
193
+ }
194
+ for (const [key, value] of Object.entries(source)) {
195
+ target[key] = value;
196
+ }
197
+ }
198
+ function dtoUnwrap(root, state) {
199
+ const original = structuredClone(root);
200
+ if (isRecord(original.data)) {
201
+ state.dtoEnvelope = { original, isDto: true };
202
+ replaceRoot(root, original.data);
203
+ }
204
+ else {
205
+ state.dtoEnvelope = { original, isDto: false };
206
+ }
207
+ }
208
+ function dtoRewrap(root, state) {
209
+ const envelope = state.dtoEnvelope;
210
+ if (!envelope) {
211
+ return;
212
+ }
213
+ if (!envelope.isDto) {
214
+ state.dtoEnvelope = undefined;
215
+ return;
216
+ }
217
+ const rebuilt = structuredClone(envelope.original);
218
+ rebuilt.data = structuredClone(root);
219
+ replaceRoot(root, rebuilt);
220
+ state.dtoEnvelope = undefined;
221
+ }
222
+ function applyShapeFilterMapping(root, mapping, state) {
223
+ const target = mapping.target ?? state.direction;
224
+ const filter = new UniversalShapeFilter(mapping.config);
225
+ const filtered = target === 'request'
226
+ ? filter.applyRequestFilter(root)
227
+ : filter.applyResponseFilter(root, state.adapterContext);
228
+ if (filtered === root) {
229
+ return;
230
+ }
231
+ replaceRoot(root, filtered);
232
+ }
233
+ function applyFieldMap(root, mapping, state) {
234
+ const direction = mapping.direction ?? state.direction;
235
+ const result = applyFieldMappings(root, mapping.config);
236
+ replaceRoot(root, result);
237
+ }
238
+ function applyToolSchemaSanitize(root, mapping) {
239
+ const sanitized = sanitizeToolSchema(root, mapping.mode ?? 'glm_shell');
240
+ replaceRoot(root, sanitized);
241
+ }
242
+ function applyRules(root, mapping, state) {
243
+ if (state.direction !== 'request') {
244
+ return;
245
+ }
246
+ const result = applyRequestRules(root, mapping.config);
247
+ replaceRoot(root, result);
248
+ }
249
+ function applyAutoThinkingAction(root, mapping, state) {
250
+ if (state.direction !== 'request') {
251
+ return;
252
+ }
253
+ runAutoThinking(root, mapping.config);
254
+ }
255
+ function triggerSnapshot(root, mapping, state) {
256
+ void writeCompatSnapshot({
257
+ phase: mapping.phase,
258
+ requestId: state.adapterContext?.requestId,
259
+ entryEndpoint: state.adapterContext?.entryEndpoint,
260
+ data: structuredClone(root)
261
+ });
262
+ }
263
+ function applyResponseBlacklist(root, mapping, state) {
264
+ if (state.direction !== 'response') {
265
+ return;
266
+ }
267
+ const sanitizer = new ResponseBlacklistSanitizer(mapping.config);
268
+ const result = sanitizer.apply(root);
269
+ replaceRoot(root, result);
270
+ }
271
+ function applyResponseNormalize(root, mapping, state) {
272
+ if (state.direction !== 'response') {
273
+ return;
274
+ }
275
+ const result = normalizeResponsePayload(root, mapping.config);
276
+ replaceRoot(root, result);
277
+ }
278
+ function detectRateLimitText(payload, needle) {
279
+ if (!needle || !payload) {
280
+ return false;
281
+ }
282
+ const normalizedNeedle = needle.toLowerCase();
283
+ const stack = [payload];
284
+ while (stack.length) {
285
+ const current = stack.pop();
286
+ if (typeof current === 'string') {
287
+ if (current.toLowerCase().includes(normalizedNeedle)) {
288
+ return true;
289
+ }
290
+ continue;
291
+ }
292
+ if (Array.isArray(current)) {
293
+ for (const entry of current) {
294
+ stack.push(entry);
295
+ }
296
+ continue;
297
+ }
298
+ if (isRecord(current)) {
299
+ for (const value of Object.values(current)) {
300
+ stack.push(value);
301
+ }
302
+ }
303
+ }
304
+ return false;
305
+ }
306
+ function parsePath(path) {
307
+ if (!path || typeof path !== 'string') {
308
+ return [];
309
+ }
310
+ return path
311
+ .split('.')
312
+ .map((segment) => segment.trim())
313
+ .filter(Boolean)
314
+ .map((segment) => {
315
+ if (segment.endsWith('[*]')) {
316
+ return {
317
+ key: segment.slice(0, -3),
318
+ wildcard: true
319
+ };
320
+ }
321
+ return {
322
+ key: segment,
323
+ wildcard: false
324
+ };
325
+ });
326
+ }
327
+ function removePath(root, path) {
328
+ const steps = parsePath(path);
329
+ if (!steps.length) {
330
+ return;
331
+ }
332
+ const parentSteps = steps.slice(0, -1);
333
+ const finalStep = steps[steps.length - 1];
334
+ const targets = resolveTargets(root, parentSteps);
335
+ for (const target of targets) {
336
+ if (isRecord(target) && finalStep && finalStep.key in target) {
337
+ delete target[finalStep.key];
338
+ }
339
+ }
340
+ }
341
+ function renamePath(root, fromPath, toPath) {
342
+ const value = getPathValue(root, fromPath);
343
+ if (value === undefined) {
344
+ return;
345
+ }
346
+ setPath(root, toPath, value);
347
+ removePath(root, fromPath);
348
+ }
349
+ function setPath(root, path, value) {
350
+ const steps = parsePath(path);
351
+ if (!steps.length) {
352
+ return;
353
+ }
354
+ let cursor = root;
355
+ for (let i = 0; i < steps.length; i++) {
356
+ const step = steps[i];
357
+ if (step.wildcard) {
358
+ throw new Error(`Cannot set wildcard path: ${path}`);
359
+ }
360
+ if (i === steps.length - 1) {
361
+ if (isRecord(cursor)) {
362
+ cursor[step.key] = structuredClone(value);
363
+ }
364
+ }
365
+ else {
366
+ if (!isRecord(cursor[step.key])) {
367
+ cursor[step.key] = {};
368
+ }
369
+ cursor = cursor[step.key];
370
+ }
371
+ }
372
+ }
373
+ function getPathValue(root, path) {
374
+ const steps = parsePath(path);
375
+ if (!steps.length) {
376
+ return undefined;
377
+ }
378
+ let cursor = root;
379
+ for (const step of steps) {
380
+ if (step.wildcard) {
381
+ if (!isRecord(cursor)) {
382
+ return undefined;
383
+ }
384
+ const arr = cursor[step.key];
385
+ if (!Array.isArray(arr) || !arr.length) {
386
+ return undefined;
387
+ }
388
+ cursor = arr[0];
389
+ continue;
390
+ }
391
+ if (!isRecord(cursor)) {
392
+ return undefined;
393
+ }
394
+ cursor = cursor[step.key];
395
+ if (cursor === undefined) {
396
+ return undefined;
397
+ }
398
+ }
399
+ return cursor;
400
+ }
401
+ function stringifyPath(root, path, fallback) {
402
+ const steps = parsePath(path);
403
+ if (!steps.length) {
404
+ return;
405
+ }
406
+ const parentSteps = steps.slice(0, -1);
407
+ const finalStep = steps[steps.length - 1];
408
+ const targets = resolveTargets(root, parentSteps);
409
+ for (const target of targets) {
410
+ if (!isRecord(target) || !finalStep) {
411
+ continue;
412
+ }
413
+ const current = target[finalStep.key];
414
+ if (typeof current === 'string') {
415
+ continue;
416
+ }
417
+ try {
418
+ if (current === undefined) {
419
+ target[finalStep.key] = JSON.stringify(fallback ?? {});
420
+ }
421
+ else {
422
+ target[finalStep.key] = JSON.stringify(current);
423
+ }
424
+ }
425
+ catch {
426
+ try {
427
+ target[finalStep.key] = JSON.stringify(fallback ?? {});
428
+ }
429
+ catch {
430
+ target[finalStep.key] = '{}';
431
+ }
432
+ }
433
+ }
434
+ }
435
+ function parseJsonPath(root, path, fallback) {
436
+ const steps = parsePath(path);
437
+ if (!steps.length) {
438
+ return;
439
+ }
440
+ const parentSteps = steps.slice(0, -1);
441
+ const finalStep = steps[steps.length - 1];
442
+ const targets = resolveTargets(root, parentSteps);
443
+ for (const target of targets) {
444
+ if (!isRecord(target) || !finalStep) {
445
+ continue;
446
+ }
447
+ const current = target[finalStep.key];
448
+ if (current === undefined || current === null) {
449
+ if (fallback !== undefined) {
450
+ target[finalStep.key] = structuredClone(fallback);
451
+ }
452
+ continue;
453
+ }
454
+ if (typeof current !== 'string') {
455
+ continue;
456
+ }
457
+ const trimmed = current.trim();
458
+ if (!trimmed) {
459
+ if (fallback !== undefined) {
460
+ target[finalStep.key] = structuredClone(fallback);
461
+ }
462
+ continue;
463
+ }
464
+ try {
465
+ target[finalStep.key] = JSON.parse(trimmed);
466
+ }
467
+ catch {
468
+ if (fallback !== undefined) {
469
+ target[finalStep.key] = structuredClone(fallback);
470
+ }
471
+ }
472
+ }
473
+ }
474
+ function setDefaultPath(root, path, value, source) {
475
+ const current = getPathValue(root, path);
476
+ if (current !== undefined) {
477
+ return;
478
+ }
479
+ let finalValue = value;
480
+ if (source === 'timestamp_seconds') {
481
+ finalValue = Math.floor(Date.now() / 1000);
482
+ }
483
+ else if (source === 'chat_completion_id') {
484
+ finalValue = `chatcmpl_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
485
+ }
486
+ if (finalValue === undefined) {
487
+ return;
488
+ }
489
+ setPath(root, path, finalValue);
490
+ }
491
+ function normalizeToolChoice(root, mapping) {
492
+ const path = mapping.path || 'tool_choice';
493
+ const current = getPathValue(root, path);
494
+ if (!current || typeof current !== 'object' || Array.isArray(current)) {
495
+ return;
496
+ }
497
+ const replacement = mapping.objectReplacement ?? 'required';
498
+ setPath(root, path, replacement);
499
+ }
500
+ function injectInstruction(root, mapping) {
501
+ const raw = getPathValue(root, mapping.sourcePath);
502
+ removePath(root, mapping.sourcePath);
503
+ const value = typeof raw === 'string' ? raw.trim() : '';
504
+ if (!value) {
505
+ return;
506
+ }
507
+ let text = value;
508
+ if (mapping.stripHtml) {
509
+ text = stripHtml(text);
510
+ }
511
+ const maxLength = resolveMaxLength(mapping.maxLengthEnv);
512
+ if (maxLength && text.length > maxLength) {
513
+ text = text.slice(0, maxLength);
514
+ }
515
+ if (!text) {
516
+ return;
517
+ }
518
+ const targetPath = mapping.targetPath || 'input';
519
+ const targetArray = ensureArray(root, targetPath);
520
+ const message = {
521
+ type: 'message',
522
+ role: mapping.role || 'system',
523
+ content: [
524
+ {
525
+ type: mapping.contentType || 'input_text',
526
+ text
527
+ }
528
+ ]
529
+ };
530
+ targetArray.unshift(message);
531
+ }
532
+ function resolveTargets(root, steps) {
533
+ if (!steps.length) {
534
+ return [root];
535
+ }
536
+ const results = [];
537
+ const traverse = (node, index) => {
538
+ const step = steps[index];
539
+ if (!step || !isRecord(node)) {
540
+ return;
541
+ }
542
+ const next = node[step.key];
543
+ if (step.wildcard && Array.isArray(next)) {
544
+ if (index === steps.length - 1) {
545
+ for (const child of next) {
546
+ if (isRecord(child)) {
547
+ results.push(child);
548
+ }
549
+ }
550
+ }
551
+ else {
552
+ for (const child of next) {
553
+ traverse(child, index + 1);
554
+ }
555
+ }
556
+ return;
557
+ }
558
+ if (!step.wildcard && isRecord(next)) {
559
+ if (index === steps.length - 1) {
560
+ results.push(next);
561
+ }
562
+ else {
563
+ traverse(next, index + 1);
564
+ }
565
+ }
566
+ };
567
+ traverse(root, 0);
568
+ return results;
569
+ }
570
+ function isRecord(value) {
571
+ return typeof value === 'object' && value !== null;
572
+ }
573
+ function ensureArray(root, path) {
574
+ const steps = parsePath(path);
575
+ if (!steps.length) {
576
+ throw new Error(`Invalid array path: ${path}`);
577
+ }
578
+ let cursor = root;
579
+ for (let i = 0; i < steps.length; i++) {
580
+ const step = steps[i];
581
+ if (step.wildcard) {
582
+ throw new Error(`Array path does not support wildcards: ${path}`);
583
+ }
584
+ if (i === steps.length - 1) {
585
+ if (!Array.isArray(cursor[step.key])) {
586
+ cursor[step.key] = [];
587
+ }
588
+ if (!Array.isArray(cursor[step.key])) {
589
+ cursor[step.key] = [];
590
+ }
591
+ return cursor[step.key];
592
+ }
593
+ if (!isRecord(cursor[step.key])) {
594
+ cursor[step.key] = {};
595
+ }
596
+ cursor = cursor[step.key];
597
+ }
598
+ throw new Error(`Failed to resolve array path: ${path}`);
599
+ }
600
+ function stripHtml(value) {
601
+ return value.replace(/<\/?[^>]+(>|$)/g, '');
602
+ }
603
+ function resolveMaxLength(envVars) {
604
+ if (!envVars || !envVars.length) {
605
+ return undefined;
606
+ }
607
+ for (const envName of envVars) {
608
+ if (!envName)
609
+ continue;
610
+ const raw = process.env[envName];
611
+ if (!raw) {
612
+ continue;
613
+ }
614
+ const parsed = Number(raw);
615
+ if (Number.isFinite(parsed) && parsed > 0) {
616
+ return Math.floor(parsed);
617
+ }
618
+ }
619
+ return undefined;
620
+ }
621
+ function convertResponsesOutputToChoices(root) {
622
+ if (!isRecord(root)) {
623
+ return;
624
+ }
625
+ const existingChoices = root.choices;
626
+ if (Array.isArray(existingChoices) && existingChoices.length > 0) {
627
+ return;
628
+ }
629
+ const choicesFromOutput = buildChoicesFromResponsesOutput(root);
630
+ if (choicesFromOutput.length > 0) {
631
+ root.choices = choicesFromOutput;
632
+ return;
633
+ }
634
+ const fallbackText = extractOutputText(root);
635
+ if (typeof fallbackText === 'string') {
636
+ root.choices = [
637
+ {
638
+ index: 0,
639
+ finish_reason: normalizeFinishReason(typeof root.status === 'string' ? root.status : 'stop'),
640
+ message: {
641
+ role: 'assistant',
642
+ content: fallbackText
643
+ }
644
+ }
645
+ ];
646
+ }
647
+ }
648
+ function buildChoicesFromResponsesOutput(root) {
649
+ const outputEntries = Array.isArray(root.output) ? root.output : [];
650
+ const choices = [];
651
+ outputEntries.forEach((entry) => {
652
+ if (!isRecord(entry)) {
653
+ return;
654
+ }
655
+ if ('type' in entry && typeof entry.type === 'string' && entry.type !== 'message') {
656
+ return;
657
+ }
658
+ const choice = convertOutputEntryToChoice(entry, choices.length, root);
659
+ if (choice) {
660
+ choices.push(choice);
661
+ }
662
+ });
663
+ return choices;
664
+ }
665
+ function convertOutputEntryToChoice(entry, index, root) {
666
+ const message = buildMessageFromOutputEntry(entry, index);
667
+ if (!message) {
668
+ return null;
669
+ }
670
+ const finishReasonCandidate = (typeof entry.stop_reason === 'string' && entry.stop_reason) ||
671
+ (typeof entry.finish_reason === 'string' && entry.finish_reason) ||
672
+ (typeof entry.status === 'string' && entry.status) ||
673
+ (typeof root.status === 'string' && root.status) ||
674
+ 'stop';
675
+ return {
676
+ index,
677
+ finish_reason: normalizeFinishReason(finishReasonCandidate),
678
+ message
679
+ };
680
+ }
681
+ function buildMessageFromOutputEntry(entry, choiceIndex) {
682
+ const role = normalizeRole(typeof entry.role === 'string' ? entry.role : 'assistant');
683
+ const contentArray = Array.isArray(entry.content) ? entry.content : [];
684
+ const textSegments = [];
685
+ const toolCalls = [];
686
+ contentArray.forEach((part, partIndex) => {
687
+ if (!isRecord(part)) {
688
+ const fallback = coerceText(part);
689
+ if (fallback) {
690
+ textSegments.push(fallback);
691
+ }
692
+ return;
693
+ }
694
+ const type = typeof part.type === 'string'
695
+ ? part.type
696
+ : typeof part.content_type === 'string'
697
+ ? part.content_type
698
+ : '';
699
+ switch (type) {
700
+ case 'output_text': {
701
+ const txt = typeof part.text === 'string'
702
+ ? part.text
703
+ : coerceText(part.text);
704
+ if (txt) {
705
+ textSegments.push(txt);
706
+ }
707
+ break;
708
+ }
709
+ case 'tool_call': {
710
+ const normalized = normalizeToolCall(part, choiceIndex, toolCalls.length);
711
+ if (normalized) {
712
+ toolCalls.push(normalized);
713
+ }
714
+ break;
715
+ }
716
+ case 'input_text':
717
+ case 'reasoning_content': {
718
+ const txt = typeof part.text === 'string'
719
+ ? part.text
720
+ : coerceText(part.text);
721
+ if (txt) {
722
+ textSegments.push(txt);
723
+ }
724
+ break;
725
+ }
726
+ default: {
727
+ const fallback = coerceText(part);
728
+ if (fallback) {
729
+ textSegments.push(fallback);
730
+ }
731
+ break;
732
+ }
733
+ }
734
+ });
735
+ const message = {
736
+ role,
737
+ content: textSegments.join('')
738
+ };
739
+ if (toolCalls.length) {
740
+ message.tool_calls = toolCalls;
741
+ if (typeof message.content !== 'string') {
742
+ message.content = '';
743
+ }
744
+ }
745
+ return message;
746
+ }
747
+ function normalizeToolCall(part, choiceIndex, callIndex) {
748
+ const payload = isRecord(part.tool_call) ? part.tool_call : part;
749
+ const fnPayload = isRecord(payload.function)
750
+ ? payload.function
751
+ : isRecord(part.function)
752
+ ? part.function
753
+ : null;
754
+ const name = typeof fnPayload?.name === 'string' ? fnPayload.name : undefined;
755
+ if (!name) {
756
+ return null;
757
+ }
758
+ const rawArgs = fnPayload?.arguments;
759
+ let argString;
760
+ if (typeof rawArgs === 'string') {
761
+ argString = rawArgs;
762
+ }
763
+ else {
764
+ try {
765
+ argString = JSON.stringify(rawArgs ?? {});
766
+ }
767
+ catch {
768
+ argString = '{}';
769
+ }
770
+ }
771
+ const idCandidate = (typeof payload.id === 'string' && payload.id) ||
772
+ (typeof payload.tool_call_id === 'string'
773
+ ? payload.tool_call_id
774
+ : undefined) ||
775
+ (typeof payload.call_id === 'string'
776
+ ? payload.call_id
777
+ : undefined);
778
+ return {
779
+ id: idCandidate || `call_${choiceIndex}_${callIndex}`,
780
+ type: 'function',
781
+ function: {
782
+ name,
783
+ arguments: argString
784
+ }
785
+ };
786
+ }
787
+ function extractOutputText(root) {
788
+ const textCandidate = root.output_text;
789
+ if (typeof textCandidate === 'string' && textCandidate.length > 0) {
790
+ return textCandidate;
791
+ }
792
+ return undefined;
793
+ }
794
+ function normalizeRole(role) {
795
+ const normalized = role.toLowerCase();
796
+ if (normalized === 'assistant' || normalized === 'system' || normalized === 'user' || normalized === 'tool') {
797
+ return normalized;
798
+ }
799
+ return 'assistant';
800
+ }
801
+ function coerceText(value) {
802
+ if (typeof value === 'string') {
803
+ return value;
804
+ }
805
+ if (typeof value === 'number' || typeof value === 'boolean') {
806
+ return String(value);
807
+ }
808
+ if (Array.isArray(value)) {
809
+ return value.map((entry) => coerceText(entry)).join('');
810
+ }
811
+ if (isRecord(value)) {
812
+ try {
813
+ return JSON.stringify(value);
814
+ }
815
+ catch {
816
+ return '';
817
+ }
818
+ }
819
+ return '';
820
+ }
821
+ function normalizeFinishReason(reason) {
822
+ const normalized = reason.toLowerCase();
823
+ if (normalized.includes('tool')) {
824
+ return 'tool_calls';
825
+ }
826
+ if (normalized.includes('length') || normalized.includes('max_token') || normalized.includes('in_progress')) {
827
+ return 'length';
828
+ }
829
+ if (normalized.includes('filter')) {
830
+ return 'content_filter';
831
+ }
832
+ return 'stop';
833
+ }
834
+ function extractRequestId(node) {
835
+ if (typeof node.request_id === 'string') {
836
+ return node.request_id;
837
+ }
838
+ const dataNode = isRecord(node.data)
839
+ ? node.data
840
+ : undefined;
841
+ if (dataNode && typeof dataNode.request_id === 'string') {
842
+ return dataNode.request_id;
843
+ }
844
+ return undefined;
845
+ }