@jsonstudio/llms 0.4.6 → 0.6.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/dist/conversion/codecs/anthropic-openai-codec.js +28 -2
- package/dist/conversion/codecs/gemini-openai-codec.js +23 -0
- package/dist/conversion/codecs/responses-openai-codec.js +8 -1
- package/dist/conversion/hub/node-support.js +14 -1
- package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +66 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +284 -193
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +17 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.d.ts +5 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +17 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +19 -0
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +269 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +18 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +141 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +29 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +15 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +63 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +11 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +12 -0
- package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +6 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +13 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +43 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +22 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.d.ts +16 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +19 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.d.ts +17 -0
- package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +19 -0
- package/dist/conversion/hub/pipeline/stages/utils.d.ts +2 -0
- package/dist/conversion/hub/pipeline/stages/utils.js +11 -0
- package/dist/conversion/hub/pipeline/target-utils.d.ts +5 -0
- package/dist/conversion/hub/pipeline/target-utils.js +87 -0
- package/dist/conversion/hub/process/chat-process.js +23 -17
- package/dist/conversion/hub/response/provider-response.js +69 -122
- package/dist/conversion/hub/response/response-mappers.d.ts +19 -0
- package/dist/conversion/hub/response/response-mappers.js +22 -2
- package/dist/conversion/hub/response/response-runtime.d.ts +8 -0
- package/dist/conversion/hub/response/response-runtime.js +239 -6
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +8 -0
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +135 -55
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +80 -40
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +5 -29
- package/dist/conversion/hub/semantic-mappers/responses-mapper.js +16 -13
- package/dist/conversion/hub/snapshot-recorder.d.ts +13 -0
- package/dist/conversion/hub/snapshot-recorder.js +90 -50
- package/dist/conversion/hub/standardized-bridge.js +49 -38
- package/dist/conversion/hub/types/chat-envelope.d.ts +68 -0
- package/dist/conversion/hub/types/standardized.d.ts +97 -0
- package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +29 -2
- package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +68 -1
- package/dist/conversion/responses/responses-openai-bridge.d.ts +6 -1
- package/dist/conversion/responses/responses-openai-bridge.js +132 -10
- package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
- package/dist/conversion/shared/anthropic-message-utils.js +414 -26
- package/dist/conversion/shared/bridge-actions.js +267 -95
- package/dist/conversion/shared/bridge-message-utils.js +54 -8
- package/dist/conversion/shared/bridge-policies.js +21 -2
- package/dist/conversion/shared/chat-envelope-validator.d.ts +8 -0
- package/dist/conversion/shared/chat-envelope-validator.js +128 -0
- package/dist/conversion/shared/chat-request-filters.js +109 -28
- package/dist/conversion/shared/mcp-injection.js +41 -20
- package/dist/conversion/shared/openai-finalizer.d.ts +11 -0
- package/dist/conversion/shared/openai-finalizer.js +73 -0
- package/dist/conversion/shared/openai-message-normalize.js +32 -31
- package/dist/conversion/shared/protocol-state.d.ts +4 -0
- package/dist/conversion/shared/protocol-state.js +23 -0
- package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
- package/dist/conversion/shared/reasoning-normalizer.js +50 -18
- package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
- package/dist/conversion/shared/responses-output-builder.js +76 -25
- package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
- package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
- package/dist/conversion/shared/responses-response-utils.js +32 -2
- package/dist/conversion/shared/responses-tool-utils.js +28 -2
- package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
- package/dist/conversion/shared/snapshot-hooks.js +60 -6
- package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
- package/dist/conversion/shared/snapshot-utils.js +84 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +46 -7
- package/dist/conversion/shared/tool-mapping.js +13 -2
- package/dist/filters/index.d.ts +18 -0
- package/dist/filters/index.js +0 -1
- package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +13 -0
- package/dist/filters/special/request-streaming-to-nonstreaming.js +13 -1
- package/dist/filters/special/request-tool-choice-policy.js +3 -1
- package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
- package/dist/filters/special/request-tool-list-filter.js +20 -7
- package/dist/sse/shared/responses-output-normalizer.js +5 -4
- package/dist/sse/sse-to-json/builders/response-builder.js +24 -1
- package/dist/sse/types/responses-types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -184,22 +184,78 @@ function mapToolResults(state) {
|
|
|
184
184
|
}
|
|
185
185
|
return toolResults;
|
|
186
186
|
}
|
|
187
|
-
function
|
|
187
|
+
function findFirstNonToolIndex(messages, startIndex) {
|
|
188
|
+
let index = startIndex;
|
|
189
|
+
while (index < messages.length) {
|
|
190
|
+
const msg = messages[index];
|
|
191
|
+
if (!msg || typeof msg !== 'object') {
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
const role = String(msg.role || '').toLowerCase();
|
|
195
|
+
if (role !== 'tool') {
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
index++;
|
|
199
|
+
}
|
|
200
|
+
return index;
|
|
201
|
+
}
|
|
202
|
+
function extractToolCallIdentifier(message) {
|
|
203
|
+
const id = message.tool_call_id ||
|
|
204
|
+
message.call_id ||
|
|
205
|
+
message.tool_use_id ||
|
|
206
|
+
message.id;
|
|
207
|
+
return typeof id === 'string' && id.trim().length ? id.trim() : undefined;
|
|
208
|
+
}
|
|
209
|
+
function findToolResponseIndex(messages, startIndex, endIndex, callId) {
|
|
210
|
+
const limit = Math.min(endIndex, messages.length);
|
|
211
|
+
for (let i = startIndex; i < limit; i++) {
|
|
212
|
+
const msg = messages[i];
|
|
213
|
+
if (!msg || typeof msg !== 'object') {
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
const role = String(msg.role || '').toLowerCase();
|
|
217
|
+
if (role !== 'tool') {
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
const id = extractToolCallIdentifier(msg);
|
|
221
|
+
if (id === callId) {
|
|
222
|
+
return i;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return -1;
|
|
226
|
+
}
|
|
227
|
+
function extractToolResponse(messages, startIndex, callId) {
|
|
188
228
|
for (let i = startIndex; i < messages.length; i++) {
|
|
189
229
|
const msg = messages[i];
|
|
190
|
-
if (!msg || typeof msg !== 'object')
|
|
230
|
+
if (!msg || typeof msg !== 'object') {
|
|
191
231
|
continue;
|
|
232
|
+
}
|
|
192
233
|
const role = String(msg.role || '').toLowerCase();
|
|
193
|
-
if (role !== 'tool')
|
|
234
|
+
if (role !== 'tool') {
|
|
194
235
|
continue;
|
|
195
|
-
const id = msg.tool_call_id ||
|
|
196
|
-
msg.call_id ||
|
|
197
|
-
msg.tool_use_id;
|
|
198
|
-
if (typeof id === 'string' && id.trim() === callId) {
|
|
199
|
-
return true;
|
|
200
236
|
}
|
|
237
|
+
const id = extractToolCallIdentifier(msg);
|
|
238
|
+
if (id === callId) {
|
|
239
|
+
const [removed] = messages.splice(i, 1);
|
|
240
|
+
return removed;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return undefined;
|
|
244
|
+
}
|
|
245
|
+
function buildToolPlaceholder(callId, call, toolResults) {
|
|
246
|
+
const content = toolResults.get(callId) ?? '';
|
|
247
|
+
const inferredName = toolResults.get(`${callId}::name`) ||
|
|
248
|
+
normalizeToolName(call.name) ||
|
|
249
|
+
normalizeToolName(call?.function?.name);
|
|
250
|
+
const toolMessage = {
|
|
251
|
+
role: 'tool',
|
|
252
|
+
tool_call_id: callId,
|
|
253
|
+
content
|
|
254
|
+
};
|
|
255
|
+
if (inferredName) {
|
|
256
|
+
toolMessage.name = inferredName;
|
|
201
257
|
}
|
|
202
|
-
return
|
|
258
|
+
return toolMessage;
|
|
203
259
|
}
|
|
204
260
|
function normalizeToolName(value) {
|
|
205
261
|
if (typeof value === 'string' && value.trim().length) {
|
|
@@ -211,18 +267,25 @@ const ensureToolResponsePlaceholders = (ctx) => {
|
|
|
211
267
|
const messages = ensureMessagesArray(ctx.state);
|
|
212
268
|
if (!messages.length)
|
|
213
269
|
return;
|
|
214
|
-
const inserts = [];
|
|
215
270
|
const toolResults = mapToolResults(ctx.state);
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
271
|
+
let index = 0;
|
|
272
|
+
while (index < messages.length) {
|
|
273
|
+
const message = messages[index];
|
|
274
|
+
if (!message || typeof message !== 'object') {
|
|
275
|
+
index++;
|
|
219
276
|
continue;
|
|
277
|
+
}
|
|
220
278
|
const role = String(message.role || '').toLowerCase();
|
|
221
|
-
if (role !== 'assistant')
|
|
279
|
+
if (role !== 'assistant') {
|
|
280
|
+
index++;
|
|
222
281
|
continue;
|
|
282
|
+
}
|
|
223
283
|
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : undefined;
|
|
224
|
-
if (!toolCalls?.length)
|
|
284
|
+
if (!toolCalls?.length) {
|
|
285
|
+
index++;
|
|
225
286
|
continue;
|
|
287
|
+
}
|
|
288
|
+
let insertionIndex = index + 1;
|
|
226
289
|
for (const call of toolCalls) {
|
|
227
290
|
if (!call || typeof call !== 'object')
|
|
228
291
|
continue;
|
|
@@ -231,30 +294,24 @@ const ensureToolResponsePlaceholders = (ctx) => {
|
|
|
231
294
|
call.tool_call_id;
|
|
232
295
|
if (typeof callId !== 'string' || !callId.trim())
|
|
233
296
|
continue;
|
|
234
|
-
|
|
297
|
+
const normalizedId = callId.trim();
|
|
298
|
+
const windowEnd = findFirstNonToolIndex(messages, insertionIndex);
|
|
299
|
+
const existingIndex = findToolResponseIndex(messages, insertionIndex, windowEnd, normalizedId);
|
|
300
|
+
if (existingIndex >= 0) {
|
|
301
|
+
insertionIndex = existingIndex + 1;
|
|
235
302
|
continue;
|
|
236
|
-
const content = toolResults.get(callId) ?? '';
|
|
237
|
-
const inferredName = toolResults.get(`${callId}::name`) ||
|
|
238
|
-
normalizeToolName(call.name) ||
|
|
239
|
-
normalizeToolName(call?.function?.name);
|
|
240
|
-
const toolMessage = {
|
|
241
|
-
role: 'tool',
|
|
242
|
-
tool_call_id: callId,
|
|
243
|
-
content
|
|
244
|
-
};
|
|
245
|
-
if (inferredName) {
|
|
246
|
-
toolMessage.name = inferredName;
|
|
247
303
|
}
|
|
248
|
-
|
|
304
|
+
const relocated = extractToolResponse(messages, windowEnd, normalizedId);
|
|
305
|
+
if (relocated) {
|
|
306
|
+
messages.splice(insertionIndex, 0, relocated);
|
|
307
|
+
insertionIndex++;
|
|
308
|
+
continue;
|
|
309
|
+
}
|
|
310
|
+
const placeholder = buildToolPlaceholder(normalizedId, call, toolResults);
|
|
311
|
+
messages.splice(insertionIndex, 0, placeholder);
|
|
312
|
+
insertionIndex++;
|
|
249
313
|
}
|
|
250
|
-
|
|
251
|
-
if (!inserts.length)
|
|
252
|
-
return;
|
|
253
|
-
let offset = 0;
|
|
254
|
-
for (const entry of inserts) {
|
|
255
|
-
const targetIndex = Math.min(messages.length, entry.index + offset);
|
|
256
|
-
messages.splice(targetIndex, 0, entry.message);
|
|
257
|
-
offset++;
|
|
314
|
+
index = Math.max(index + 1, insertionIndex);
|
|
258
315
|
}
|
|
259
316
|
};
|
|
260
317
|
function assignReasoning(message, parts) {
|
|
@@ -434,13 +491,34 @@ const ensureOutputFieldsAction = (ctx) => {
|
|
|
434
491
|
continue;
|
|
435
492
|
}
|
|
436
493
|
if (role === 'assistant') {
|
|
494
|
+
const toolCalls = Array.isArray(message.tool_calls)
|
|
495
|
+
? message.tool_calls
|
|
496
|
+
: undefined;
|
|
497
|
+
const hasToolCalls = Boolean(toolCalls && toolCalls.length);
|
|
437
498
|
const text = flattenContentToString(message.content);
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
else if (typeof message.content !== 'string') {
|
|
499
|
+
const hasText = typeof text === 'string' && text.trim().length > 0;
|
|
500
|
+
if (text !== undefined && typeof message.content !== 'string') {
|
|
442
501
|
message.content = text;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (hasText) {
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const reasoningText = typeof message.reasoning_content === 'string' && message.reasoning_content.trim().length
|
|
508
|
+
? message.reasoning_content.trim()
|
|
509
|
+
: undefined;
|
|
510
|
+
if (reasoningText) {
|
|
511
|
+
message.content = reasoningText;
|
|
512
|
+
continue;
|
|
443
513
|
}
|
|
514
|
+
if (hasToolCalls) {
|
|
515
|
+
// Assistant-only tool call is valid; leave content empty to avoid fabricated summaries.
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const assistantFallback = typeof ctx.descriptor.options?.assistantFallback === 'string' && ctx.descriptor.options.assistantFallback.trim().length
|
|
519
|
+
? ctx.descriptor.options.assistantFallback.trim()
|
|
520
|
+
: 'Assistant response unavailable.';
|
|
521
|
+
message.content = assistantFallback;
|
|
444
522
|
}
|
|
445
523
|
}
|
|
446
524
|
};
|
|
@@ -478,6 +556,151 @@ registerBridgeAction('messages.ensure-output-fields', ensureOutputFieldsAction);
|
|
|
478
556
|
registerBridgeAction('tools.ensure-placeholders', ensureToolPlaceholdersAction);
|
|
479
557
|
registerBridgeAction('tools.capture-results', captureToolResultsAction);
|
|
480
558
|
registerBridgeAction('reasoning.attach-output', attachReasoningOutputAction);
|
|
559
|
+
function deriveToolIdPrefix(ctx) {
|
|
560
|
+
if (typeof ctx.descriptor.options?.idPrefix === 'string' && ctx.descriptor.options.idPrefix.trim().length) {
|
|
561
|
+
return ctx.descriptor.options.idPrefix.trim();
|
|
562
|
+
}
|
|
563
|
+
if (typeof ctx.requestId === 'string' && ctx.requestId.trim().length) {
|
|
564
|
+
const safe = ctx.requestId.trim().replace(/[^A-Za-z0-9]/g, '');
|
|
565
|
+
if (safe.length) {
|
|
566
|
+
return `${safe.slice(-24)}_tool`;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
const base = ctx.protocol || 'bridge';
|
|
570
|
+
return `${base}_tool`;
|
|
571
|
+
}
|
|
572
|
+
const normalizeToolIdentifiersAction = (ctx) => {
|
|
573
|
+
const messages = ensureMessagesArray(ctx.state);
|
|
574
|
+
const idPrefix = deriveToolIdPrefix(ctx);
|
|
575
|
+
let counter = 0;
|
|
576
|
+
const aliasMap = new Map();
|
|
577
|
+
const knownIds = new Set();
|
|
578
|
+
const pendingQueue = [];
|
|
579
|
+
const registerAlias = (raw, normalized) => {
|
|
580
|
+
if (typeof raw === 'string' && raw.trim().length) {
|
|
581
|
+
aliasMap.set(raw.trim(), normalized);
|
|
582
|
+
}
|
|
583
|
+
aliasMap.set(normalized, normalized);
|
|
584
|
+
};
|
|
585
|
+
const consumePending = (id) => {
|
|
586
|
+
const index = pendingQueue.indexOf(id);
|
|
587
|
+
if (index >= 0) {
|
|
588
|
+
pendingQueue.splice(index, 1);
|
|
589
|
+
}
|
|
590
|
+
};
|
|
591
|
+
const nextId = () => {
|
|
592
|
+
counter += 1;
|
|
593
|
+
return `${idPrefix}_${counter}`;
|
|
594
|
+
};
|
|
595
|
+
const normalizeIdValue = (raw, consumeQueue) => {
|
|
596
|
+
if (typeof raw === 'string' && raw.trim().length) {
|
|
597
|
+
const existing = raw.trim();
|
|
598
|
+
if (aliasMap.has(existing)) {
|
|
599
|
+
const normalized = aliasMap.get(existing);
|
|
600
|
+
knownIds.add(normalized);
|
|
601
|
+
return normalized;
|
|
602
|
+
}
|
|
603
|
+
if (consumeQueue && pendingQueue.length) {
|
|
604
|
+
const queued = pendingQueue.shift();
|
|
605
|
+
knownIds.add(queued);
|
|
606
|
+
aliasMap.set(existing, queued);
|
|
607
|
+
aliasMap.set(queued, queued);
|
|
608
|
+
return queued;
|
|
609
|
+
}
|
|
610
|
+
knownIds.add(existing);
|
|
611
|
+
aliasMap.set(existing, existing);
|
|
612
|
+
return existing;
|
|
613
|
+
}
|
|
614
|
+
if (consumeQueue && pendingQueue.length) {
|
|
615
|
+
const queued = pendingQueue.shift();
|
|
616
|
+
knownIds.add(queued);
|
|
617
|
+
aliasMap.set(queued, queued);
|
|
618
|
+
return queued;
|
|
619
|
+
}
|
|
620
|
+
const generated = nextId();
|
|
621
|
+
knownIds.add(generated);
|
|
622
|
+
aliasMap.set(generated, generated);
|
|
623
|
+
return generated;
|
|
624
|
+
};
|
|
625
|
+
const normalizeToolCall = (call, consumeQueue) => {
|
|
626
|
+
if (!call || typeof call !== 'object') {
|
|
627
|
+
return undefined;
|
|
628
|
+
}
|
|
629
|
+
const normalized = normalizeIdValue(call.id || call.tool_call_id || call.call_id, consumeQueue);
|
|
630
|
+
registerAlias(call.id, normalized);
|
|
631
|
+
registerAlias(call.tool_call_id, normalized);
|
|
632
|
+
registerAlias(call.call_id, normalized);
|
|
633
|
+
call.id = normalized;
|
|
634
|
+
call.tool_call_id = normalized;
|
|
635
|
+
call.call_id = normalized;
|
|
636
|
+
return normalized;
|
|
637
|
+
};
|
|
638
|
+
const normalizeToolOutputs = (outputs) => {
|
|
639
|
+
if (!Array.isArray(outputs)) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
outputs.forEach((entry) => {
|
|
643
|
+
if (!entry || typeof entry !== 'object') {
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const normalized = normalizeIdValue(entry.tool_call_id || entry.call_id || entry.id, true);
|
|
647
|
+
registerAlias(entry.tool_call_id, normalized);
|
|
648
|
+
registerAlias(entry.call_id, normalized);
|
|
649
|
+
consumePending(normalized);
|
|
650
|
+
entry.tool_call_id = normalized;
|
|
651
|
+
entry.call_id = normalized;
|
|
652
|
+
});
|
|
653
|
+
};
|
|
654
|
+
for (const message of messages) {
|
|
655
|
+
if (!message || typeof message !== 'object')
|
|
656
|
+
continue;
|
|
657
|
+
const role = String(message.role || '').toLowerCase();
|
|
658
|
+
if (role === 'assistant') {
|
|
659
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : undefined;
|
|
660
|
+
if (!toolCalls?.length)
|
|
661
|
+
continue;
|
|
662
|
+
for (const call of toolCalls) {
|
|
663
|
+
const normalized = normalizeToolCall(call, false);
|
|
664
|
+
if (normalized) {
|
|
665
|
+
pendingQueue.push(normalized);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
else if (role === 'tool') {
|
|
670
|
+
const normalized = normalizeIdValue(message.tool_call_id || message.call_id || message.id, true);
|
|
671
|
+
registerAlias(message.tool_call_id, normalized);
|
|
672
|
+
registerAlias(message.call_id, normalized);
|
|
673
|
+
consumePending(normalized);
|
|
674
|
+
message.tool_call_id = normalized;
|
|
675
|
+
message.call_id = normalized;
|
|
676
|
+
if (!message.id) {
|
|
677
|
+
message.id = normalized;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (ctx.state.rawRequest && typeof ctx.state.rawRequest === 'object') {
|
|
682
|
+
normalizeToolOutputs(ctx.state.rawRequest.tool_outputs);
|
|
683
|
+
const required = ctx.state.rawRequest.required_action;
|
|
684
|
+
if (required && typeof required === 'object') {
|
|
685
|
+
const submit = required.submit_tool_outputs;
|
|
686
|
+
if (submit && typeof submit === 'object' && Array.isArray(submit.tool_calls)) {
|
|
687
|
+
const submitCalls = submit.tool_calls;
|
|
688
|
+
submitCalls.forEach((call) => normalizeToolCall(call, false));
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (ctx.state.capturedToolResults && Array.isArray(ctx.state.capturedToolResults)) {
|
|
693
|
+
ctx.state.capturedToolResults.forEach((entry) => {
|
|
694
|
+
const normalized = normalizeIdValue(entry?.tool_call_id ?? entry?.call_id, true);
|
|
695
|
+
registerAlias(entry?.tool_call_id, normalized);
|
|
696
|
+
registerAlias(entry?.call_id, normalized);
|
|
697
|
+
consumePending(normalized);
|
|
698
|
+
entry.tool_call_id = normalized;
|
|
699
|
+
entry.call_id = normalized;
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
registerBridgeAction('tools.normalize-call-ids', normalizeToolIdentifiersAction);
|
|
481
704
|
const responsesOutputReasoningAction = (ctx) => {
|
|
482
705
|
if (ctx.stage !== 'response_inbound')
|
|
483
706
|
return;
|
|
@@ -633,6 +856,10 @@ const metadataProviderSentinelAction = (ctx) => {
|
|
|
633
856
|
metadata[targetKey] = provider;
|
|
634
857
|
return;
|
|
635
858
|
}
|
|
859
|
+
if (ctx.stage === 'request_outbound') {
|
|
860
|
+
delete payload[sentinel];
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
636
863
|
const metadata = ctx.state.metadata;
|
|
637
864
|
if (!metadata || typeof metadata !== 'object') {
|
|
638
865
|
delete payload[sentinel];
|
|
@@ -652,58 +879,3 @@ const metadataProviderSentinelAction = (ctx) => {
|
|
|
652
879
|
};
|
|
653
880
|
registerBridgeAction('metadata.provider-field', metadataProviderFieldAction);
|
|
654
881
|
registerBridgeAction('metadata.provider-sentinel', metadataProviderSentinelAction);
|
|
655
|
-
const metadataSystemSentinelAction = (ctx) => {
|
|
656
|
-
const sentinel = typeof ctx.descriptor.options?.sentinel === 'string'
|
|
657
|
-
? ctx.descriptor.options.sentinel
|
|
658
|
-
: undefined;
|
|
659
|
-
const targetKey = typeof ctx.descriptor.options?.target === 'string'
|
|
660
|
-
? ctx.descriptor.options.target
|
|
661
|
-
: 'rawSystem';
|
|
662
|
-
const stringifyOutbound = ctx.descriptor.options?.stringify !== false;
|
|
663
|
-
if (!sentinel)
|
|
664
|
-
return;
|
|
665
|
-
const payload = ctx.stage.startsWith('request') ? ctx.state.rawRequest :
|
|
666
|
-
ctx.stage.startsWith('response') ? ctx.state.rawResponse :
|
|
667
|
-
undefined;
|
|
668
|
-
if (!payload || typeof payload !== 'object')
|
|
669
|
-
return;
|
|
670
|
-
if (ctx.stage.endsWith('inbound')) {
|
|
671
|
-
const rawValue = payload[sentinel];
|
|
672
|
-
if (rawValue === undefined)
|
|
673
|
-
return;
|
|
674
|
-
let parsed = rawValue;
|
|
675
|
-
if (typeof rawValue === 'string') {
|
|
676
|
-
try {
|
|
677
|
-
parsed = JSON.parse(rawValue);
|
|
678
|
-
}
|
|
679
|
-
catch {
|
|
680
|
-
parsed = rawValue;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
delete payload[sentinel];
|
|
684
|
-
const metadata = ensureMetadataRecord(ctx.state);
|
|
685
|
-
metadata[targetKey] = parsed;
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
const metadata = ctx.state.metadata;
|
|
689
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
690
|
-
delete payload[sentinel];
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
const value = metadata[targetKey];
|
|
694
|
-
if (value === undefined) {
|
|
695
|
-
delete payload[sentinel];
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
if (stringifyOutbound) {
|
|
699
|
-
try {
|
|
700
|
-
payload[sentinel] = JSON.stringify(value);
|
|
701
|
-
}
|
|
702
|
-
catch {
|
|
703
|
-
payload[sentinel] = value;
|
|
704
|
-
}
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
payload[sentinel] = value;
|
|
708
|
-
};
|
|
709
|
-
registerBridgeAction('metadata.system-sentinel', metadataSystemSentinelAction);
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
import { normalizeFunctionCallId, normalizeFunctionCallOutputId } from './bridge-id-utils.js';
|
|
2
2
|
import { normalizeChatMessageContent } from './chat-output-normalizer.js';
|
|
3
|
+
function ensureAssistantToolCallIdentity(call, fallbackId) {
|
|
4
|
+
const resolved = (typeof call.id === 'string' && call.id.trim().length ? call.id.trim() : undefined) ??
|
|
5
|
+
(typeof call.tool_call_id === 'string' && call.tool_call_id.trim().length
|
|
6
|
+
? call.tool_call_id.trim()
|
|
7
|
+
: undefined) ??
|
|
8
|
+
(typeof call.call_id === 'string' && call.call_id.trim().length
|
|
9
|
+
? call.call_id.trim()
|
|
10
|
+
: undefined) ??
|
|
11
|
+
fallbackId;
|
|
12
|
+
call.id = resolved;
|
|
13
|
+
call.tool_call_id = resolved;
|
|
14
|
+
call.call_id = resolved;
|
|
15
|
+
return resolved;
|
|
16
|
+
}
|
|
3
17
|
export function coerceBridgeRole(role) {
|
|
4
18
|
if (typeof role === 'string') {
|
|
5
19
|
const normalized = role.toLowerCase();
|
|
@@ -159,7 +173,6 @@ export function convertMessagesToBridgeInput(options) {
|
|
|
159
173
|
if (typeof text === 'string') {
|
|
160
174
|
const tRole = role === 'assistant' ? 'output_text' : 'input_text';
|
|
161
175
|
const entry = {
|
|
162
|
-
type: 'message',
|
|
163
176
|
role,
|
|
164
177
|
content: [{ type: tRole, text }]
|
|
165
178
|
};
|
|
@@ -385,7 +398,18 @@ export function convertBridgeInputToChatMessages(options) {
|
|
|
385
398
|
}
|
|
386
399
|
const serialized = serializeToolArguments(parsedArgs, name, tools).trim();
|
|
387
400
|
toolNameById.set(callId, name);
|
|
388
|
-
|
|
401
|
+
const toolCall = {
|
|
402
|
+
id: callId,
|
|
403
|
+
call_id: callId,
|
|
404
|
+
tool_call_id: callId,
|
|
405
|
+
type: 'function',
|
|
406
|
+
function: { name, arguments: serialized }
|
|
407
|
+
};
|
|
408
|
+
messages.push({
|
|
409
|
+
role: 'assistant',
|
|
410
|
+
content: '',
|
|
411
|
+
tool_calls: [toolCall]
|
|
412
|
+
});
|
|
389
413
|
lastToolCallId = callId;
|
|
390
414
|
continue;
|
|
391
415
|
}
|
|
@@ -404,7 +428,12 @@ export function convertBridgeInputToChatMessages(options) {
|
|
|
404
428
|
contentStr = fallbackText;
|
|
405
429
|
}
|
|
406
430
|
const nm = toolNameById.get(String(toolCallId));
|
|
407
|
-
const toolMsg = {
|
|
431
|
+
const toolMsg = {
|
|
432
|
+
role: 'tool',
|
|
433
|
+
tool_call_id: String(toolCallId),
|
|
434
|
+
id: String(toolCallId),
|
|
435
|
+
content: contentStr
|
|
436
|
+
};
|
|
408
437
|
if (typeof nm === 'string' && nm.trim().length)
|
|
409
438
|
toolMsg.name = nm;
|
|
410
439
|
messages.push(toolMsg);
|
|
@@ -412,7 +441,12 @@ export function convertBridgeInputToChatMessages(options) {
|
|
|
412
441
|
catch {
|
|
413
442
|
const fallback = (output ?? fallbackText);
|
|
414
443
|
const nm = toolNameById.get(String(toolCallId));
|
|
415
|
-
const toolMsg = {
|
|
444
|
+
const toolMsg = {
|
|
445
|
+
role: 'tool',
|
|
446
|
+
tool_call_id: String(toolCallId),
|
|
447
|
+
id: String(toolCallId),
|
|
448
|
+
content: String(fallback)
|
|
449
|
+
};
|
|
416
450
|
if (typeof nm === 'string' && nm.trim().length)
|
|
417
451
|
toolMsg.name = nm;
|
|
418
452
|
messages.push(toolMsg);
|
|
@@ -425,8 +459,14 @@ export function convertBridgeInputToChatMessages(options) {
|
|
|
425
459
|
if (entry && typeof entry.message === 'object' && Array.isArray(entry.message?.content)) {
|
|
426
460
|
const explicit = entry.message;
|
|
427
461
|
const nested = processMessageBlocks(Array.isArray(explicit.content) ? explicit.content : [], resolveFunctionName, tools, toolNameById, lastToolCallId, fallbackText);
|
|
428
|
-
if (nested.toolCalls.length)
|
|
429
|
-
|
|
462
|
+
if (nested.toolCalls.length) {
|
|
463
|
+
nested.toolCalls.forEach((call, idx) => ensureAssistantToolCallIdentity(call, `fc_call_${messages.length + idx + 1}`));
|
|
464
|
+
messages.push({
|
|
465
|
+
role: 'assistant',
|
|
466
|
+
content: '',
|
|
467
|
+
tool_calls: nested.toolCalls
|
|
468
|
+
});
|
|
469
|
+
}
|
|
430
470
|
for (const msg of nested.toolMessages)
|
|
431
471
|
messages.push(msg);
|
|
432
472
|
const normalizedRole = coerceBridgeRole((explicit.role ?? entry.role) || 'user');
|
|
@@ -440,8 +480,14 @@ export function convertBridgeInputToChatMessages(options) {
|
|
|
440
480
|
}
|
|
441
481
|
if (!handledViaExplicitMessage) {
|
|
442
482
|
const nested = processMessageBlocks(Array.isArray(entry.content) ? entry.content : [], resolveFunctionName, tools, toolNameById, lastToolCallId, fallbackText);
|
|
443
|
-
if (nested.toolCalls.length)
|
|
444
|
-
|
|
483
|
+
if (nested.toolCalls.length) {
|
|
484
|
+
nested.toolCalls.forEach((call, idx) => ensureAssistantToolCallIdentity(call, `fc_call_${messages.length + idx + 1}`));
|
|
485
|
+
messages.push({
|
|
486
|
+
role: 'assistant',
|
|
487
|
+
content: '',
|
|
488
|
+
tool_calls: nested.toolCalls
|
|
489
|
+
});
|
|
490
|
+
}
|
|
445
491
|
for (const msg of nested.toolMessages)
|
|
446
492
|
messages.push(msg);
|
|
447
493
|
const normalizedRole = coerceBridgeRole(entry.role || 'user');
|
|
@@ -109,6 +109,15 @@ function reasoningAction(idPrefix) {
|
|
|
109
109
|
}
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
+
function toolCallNormalizationAction(idPrefix) {
|
|
113
|
+
if (idPrefix && idPrefix.trim().length) {
|
|
114
|
+
return {
|
|
115
|
+
name: 'tools.normalize-call-ids',
|
|
116
|
+
options: { idPrefix: idPrefix.trim() }
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
return { name: 'tools.normalize-call-ids' };
|
|
120
|
+
}
|
|
112
121
|
const RESPONSES_POLICY = {
|
|
113
122
|
id: 'openai-responses-default',
|
|
114
123
|
protocol: 'openai-responses',
|
|
@@ -123,6 +132,7 @@ const RESPONSES_POLICY = {
|
|
|
123
132
|
},
|
|
124
133
|
{ name: 'messages.ensure-system-instruction' },
|
|
125
134
|
reasoningAction('responses_reasoning'),
|
|
135
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
126
136
|
{ name: 'tools.ensure-placeholders' },
|
|
127
137
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
128
138
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
@@ -131,6 +141,7 @@ const RESPONSES_POLICY = {
|
|
|
131
141
|
outbound: [
|
|
132
142
|
{ name: 'messages.normalize-history' },
|
|
133
143
|
{ name: 'tools.capture-results' },
|
|
144
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
134
145
|
{ name: 'tools.ensure-placeholders' },
|
|
135
146
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
136
147
|
{ name: 'messages.ensure-system-instruction' },
|
|
@@ -144,10 +155,12 @@ const RESPONSES_POLICY = {
|
|
|
144
155
|
inbound: [
|
|
145
156
|
{ name: 'reasoning.attach-output' },
|
|
146
157
|
reasoningAction('responses_reasoning'),
|
|
158
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
147
159
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } }
|
|
148
160
|
],
|
|
149
161
|
outbound: [
|
|
150
162
|
reasoningAction('responses_reasoning'),
|
|
163
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
151
164
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } }
|
|
152
165
|
]
|
|
153
166
|
}
|
|
@@ -158,8 +171,8 @@ const OPENAI_CHAT_POLICY = {
|
|
|
158
171
|
request: {
|
|
159
172
|
inbound: [
|
|
160
173
|
reasoningAction('openai_chat_reasoning'),
|
|
174
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
161
175
|
{ name: 'messages.ensure-system-instruction' },
|
|
162
|
-
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } },
|
|
163
176
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } },
|
|
164
177
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
165
178
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
@@ -167,11 +180,11 @@ const OPENAI_CHAT_POLICY = {
|
|
|
167
180
|
outbound: [
|
|
168
181
|
{ name: 'messages.normalize-history' },
|
|
169
182
|
{ name: 'tools.capture-results' },
|
|
183
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
170
184
|
{ name: 'tools.ensure-placeholders' },
|
|
171
185
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
172
186
|
{ name: 'messages.ensure-system-instruction' },
|
|
173
187
|
reasoningAction('openai_chat_reasoning'),
|
|
174
|
-
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } },
|
|
175
188
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } },
|
|
176
189
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
177
190
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
@@ -180,10 +193,12 @@ const OPENAI_CHAT_POLICY = {
|
|
|
180
193
|
response: {
|
|
181
194
|
inbound: [
|
|
182
195
|
reasoningAction('openai_chat_reasoning'),
|
|
196
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
183
197
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } }
|
|
184
198
|
],
|
|
185
199
|
outbound: [
|
|
186
200
|
reasoningAction('openai_chat_reasoning'),
|
|
201
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
187
202
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } }
|
|
188
203
|
]
|
|
189
204
|
}
|
|
@@ -194,12 +209,14 @@ const ANTHROPIC_POLICY = {
|
|
|
194
209
|
request: {
|
|
195
210
|
inbound: [
|
|
196
211
|
reasoningAction('anthropic_reasoning'),
|
|
212
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
197
213
|
{ name: 'messages.ensure-system-instruction' },
|
|
198
214
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
199
215
|
],
|
|
200
216
|
outbound: [
|
|
201
217
|
{ name: 'messages.normalize-history' },
|
|
202
218
|
{ name: 'tools.capture-results' },
|
|
219
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
203
220
|
{ name: 'tools.ensure-placeholders' },
|
|
204
221
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
205
222
|
{ name: 'messages.ensure-system-instruction' },
|
|
@@ -210,10 +227,12 @@ const ANTHROPIC_POLICY = {
|
|
|
210
227
|
response: {
|
|
211
228
|
inbound: [
|
|
212
229
|
reasoningAction('anthropic_reasoning'),
|
|
230
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
213
231
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
214
232
|
],
|
|
215
233
|
outbound: [
|
|
216
234
|
reasoningAction('anthropic_reasoning'),
|
|
235
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
217
236
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
218
237
|
]
|
|
219
238
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ChatEnvelope } from '../hub/types/chat-envelope.js';
|
|
2
|
+
export type ChatValidationStage = 'req_inbound' | 'req_outbound' | 'resp_inbound' | 'resp_outbound';
|
|
3
|
+
export interface ChatEnvelopeValidationOptions {
|
|
4
|
+
stage: ChatValidationStage;
|
|
5
|
+
direction: 'request' | 'response';
|
|
6
|
+
source?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function validateChatEnvelope(chat: ChatEnvelope, options: ChatEnvelopeValidationOptions): void;
|