@jsonstudio/llms 0.4.5 → 0.6.0
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 +11 -11
- 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 +119 -59
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +74 -13
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +0 -9
- 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 +44 -30
- 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 -6
- package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
- package/dist/conversion/shared/anthropic-message-utils.js +334 -14
- package/dist/conversion/shared/bridge-actions.js +267 -40
- package/dist/conversion/shared/bridge-message-utils.js +54 -8
- package/dist/conversion/shared/bridge-policies.js +29 -4
- 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 +108 -25
- 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/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 +45 -5
- package/dist/conversion/shared/tool-governor.js +5 -0
- package/dist/conversion/shared/tool-mapping.js +13 -2
- 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/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;
|
|
513
|
+
}
|
|
514
|
+
if (hasToolCalls) {
|
|
515
|
+
// Assistant-only tool call is valid; leave content empty to avoid fabricated summaries.
|
|
516
|
+
continue;
|
|
443
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];
|
|
@@ -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,32 +132,40 @@ 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' } },
|
|
129
|
-
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
139
|
+
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } },
|
|
140
|
+
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } }
|
|
130
141
|
],
|
|
131
142
|
outbound: [
|
|
132
143
|
{ name: 'messages.normalize-history' },
|
|
133
144
|
{ name: 'tools.capture-results' },
|
|
145
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
134
146
|
{ name: 'tools.ensure-placeholders' },
|
|
135
147
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
136
148
|
{ name: 'messages.ensure-system-instruction' },
|
|
137
149
|
reasoningAction('responses_reasoning'),
|
|
138
150
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
139
151
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
140
|
-
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
152
|
+
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } },
|
|
153
|
+
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } }
|
|
141
154
|
]
|
|
142
155
|
},
|
|
143
156
|
response: {
|
|
144
157
|
inbound: [
|
|
145
158
|
{ name: 'reasoning.attach-output' },
|
|
146
159
|
reasoningAction('responses_reasoning'),
|
|
147
|
-
|
|
160
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
161
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
162
|
+
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } }
|
|
148
163
|
],
|
|
149
164
|
outbound: [
|
|
150
165
|
reasoningAction('responses_reasoning'),
|
|
151
|
-
|
|
166
|
+
toolCallNormalizationAction('responses_tool_call'),
|
|
167
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
168
|
+
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } }
|
|
152
169
|
]
|
|
153
170
|
}
|
|
154
171
|
};
|
|
@@ -158,6 +175,7 @@ const OPENAI_CHAT_POLICY = {
|
|
|
158
175
|
request: {
|
|
159
176
|
inbound: [
|
|
160
177
|
reasoningAction('openai_chat_reasoning'),
|
|
178
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
161
179
|
{ name: 'messages.ensure-system-instruction' },
|
|
162
180
|
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } },
|
|
163
181
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } },
|
|
@@ -167,6 +185,7 @@ const OPENAI_CHAT_POLICY = {
|
|
|
167
185
|
outbound: [
|
|
168
186
|
{ name: 'messages.normalize-history' },
|
|
169
187
|
{ name: 'tools.capture-results' },
|
|
188
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
170
189
|
{ name: 'tools.ensure-placeholders' },
|
|
171
190
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
172
191
|
{ name: 'messages.ensure-system-instruction' },
|
|
@@ -180,10 +199,12 @@ const OPENAI_CHAT_POLICY = {
|
|
|
180
199
|
response: {
|
|
181
200
|
inbound: [
|
|
182
201
|
reasoningAction('openai_chat_reasoning'),
|
|
202
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
183
203
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } }
|
|
184
204
|
],
|
|
185
205
|
outbound: [
|
|
186
206
|
reasoningAction('openai_chat_reasoning'),
|
|
207
|
+
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
187
208
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } }
|
|
188
209
|
]
|
|
189
210
|
}
|
|
@@ -194,12 +215,14 @@ const ANTHROPIC_POLICY = {
|
|
|
194
215
|
request: {
|
|
195
216
|
inbound: [
|
|
196
217
|
reasoningAction('anthropic_reasoning'),
|
|
218
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
197
219
|
{ name: 'messages.ensure-system-instruction' },
|
|
198
220
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
199
221
|
],
|
|
200
222
|
outbound: [
|
|
201
223
|
{ name: 'messages.normalize-history' },
|
|
202
224
|
{ name: 'tools.capture-results' },
|
|
225
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
203
226
|
{ name: 'tools.ensure-placeholders' },
|
|
204
227
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
205
228
|
{ name: 'messages.ensure-system-instruction' },
|
|
@@ -210,10 +233,12 @@ const ANTHROPIC_POLICY = {
|
|
|
210
233
|
response: {
|
|
211
234
|
inbound: [
|
|
212
235
|
reasoningAction('anthropic_reasoning'),
|
|
236
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
213
237
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
214
238
|
],
|
|
215
239
|
outbound: [
|
|
216
240
|
reasoningAction('anthropic_reasoning'),
|
|
241
|
+
toolCallNormalizationAction('anthropic_tool_call'),
|
|
217
242
|
{ name: 'metadata.extra-fields', options: { allowedKeys: ANTHROPIC_ALLOWED_FIELDS } }
|
|
218
243
|
]
|
|
219
244
|
}
|
|
@@ -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;
|