@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
|
@@ -2,6 +2,7 @@ import { isJsonObject, jsonClone } from '../types/json.js';
|
|
|
2
2
|
import { createBridgeActionState, runBridgeActionPipeline } from '../../shared/bridge-actions.js';
|
|
3
3
|
import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-policies.js';
|
|
4
4
|
import { normalizeChatMessageContent } from '../../shared/chat-output-normalizer.js';
|
|
5
|
+
import { ensureProtocolState } from '../../shared/protocol-state.js';
|
|
5
6
|
const CHAT_PARAMETER_KEYS = [
|
|
6
7
|
'model',
|
|
7
8
|
'temperature',
|
|
@@ -25,7 +26,9 @@ const KNOWN_TOP_LEVEL_FIELDS = new Set([
|
|
|
25
26
|
'messages',
|
|
26
27
|
'tools',
|
|
27
28
|
'tool_outputs',
|
|
28
|
-
...CHAT_PARAMETER_KEYS
|
|
29
|
+
...CHAT_PARAMETER_KEYS,
|
|
30
|
+
'stageExpectations',
|
|
31
|
+
'stages'
|
|
29
32
|
]);
|
|
30
33
|
function flattenSystemContent(content) {
|
|
31
34
|
if (typeof content === 'string')
|
|
@@ -56,39 +59,47 @@ function normalizeToolContent(content) {
|
|
|
56
59
|
return String(content ?? '');
|
|
57
60
|
}
|
|
58
61
|
}
|
|
62
|
+
function recordToolCallIssues(message, messageIndex, missing) {
|
|
63
|
+
const toolCalls = Array.isArray(message.tool_calls) ? message.tool_calls : undefined;
|
|
64
|
+
if (!toolCalls?.length)
|
|
65
|
+
return;
|
|
66
|
+
toolCalls.forEach((entry, callIndex) => {
|
|
67
|
+
if (!isJsonObject(entry)) {
|
|
68
|
+
missing.push({
|
|
69
|
+
path: `messages[${messageIndex}].tool_calls[${callIndex}]`,
|
|
70
|
+
reason: 'invalid_tool_call_entry',
|
|
71
|
+
originalValue: jsonClone(entry)
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const fnBlock = entry.function;
|
|
76
|
+
if (!isJsonObject(fnBlock)) {
|
|
77
|
+
missing.push({
|
|
78
|
+
path: `messages[${messageIndex}].tool_calls[${callIndex}].function`,
|
|
79
|
+
reason: 'missing_tool_function',
|
|
80
|
+
originalValue: jsonClone(fnBlock)
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const fnName = fnBlock.name;
|
|
85
|
+
if (typeof fnName !== 'string' || !fnName.trim().length) {
|
|
86
|
+
missing.push({
|
|
87
|
+
path: `messages[${messageIndex}].tool_calls[${callIndex}].function.name`,
|
|
88
|
+
reason: 'missing_tool_name'
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
59
93
|
function collectSystemRawBlocks(raw) {
|
|
60
94
|
if (!Array.isArray(raw))
|
|
61
95
|
return undefined;
|
|
62
96
|
const blocks = [];
|
|
63
|
-
const pushText = (text) => {
|
|
64
|
-
if (typeof text === 'string' && text.trim().length) {
|
|
65
|
-
blocks.push({ type: 'text', text });
|
|
66
|
-
}
|
|
67
|
-
};
|
|
68
97
|
raw.forEach((entry) => {
|
|
69
98
|
if (!isJsonObject(entry))
|
|
70
99
|
return;
|
|
71
|
-
if (entry.role !== 'system')
|
|
72
|
-
return;
|
|
73
|
-
const content = entry.content;
|
|
74
|
-
if (typeof content === 'string') {
|
|
75
|
-
pushText(content);
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
if (Array.isArray(content)) {
|
|
79
|
-
content.forEach((part) => {
|
|
80
|
-
if (typeof part === 'string') {
|
|
81
|
-
pushText(part);
|
|
82
|
-
}
|
|
83
|
-
else if (isJsonObject(part) && typeof part.text === 'string') {
|
|
84
|
-
pushText(part.text);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
100
|
+
if (String(entry.role ?? '').toLowerCase() !== 'system')
|
|
87
101
|
return;
|
|
88
|
-
|
|
89
|
-
if (isJsonObject(content) && typeof content.text === 'string') {
|
|
90
|
-
pushText(content.text);
|
|
91
|
-
}
|
|
102
|
+
blocks.push(jsonClone(entry));
|
|
92
103
|
});
|
|
93
104
|
return blocks.length ? blocks : undefined;
|
|
94
105
|
}
|
|
@@ -120,7 +131,8 @@ function normalizeChatMessages(raw) {
|
|
|
120
131
|
const chatMessage = value;
|
|
121
132
|
if (roleValue !== 'system' && roleValue !== 'tool') {
|
|
122
133
|
const normalizedContent = normalizeChatMessageContent(chatMessage.content);
|
|
123
|
-
|
|
134
|
+
const shouldOverwriteContent = !Array.isArray(chatMessage.content);
|
|
135
|
+
if (shouldOverwriteContent && normalizedContent.contentText !== undefined) {
|
|
124
136
|
chatMessage.content = normalizedContent.contentText;
|
|
125
137
|
}
|
|
126
138
|
if (typeof normalizedContent.reasoningText === 'string' && normalizedContent.reasoningText.trim().length) {
|
|
@@ -128,6 +140,10 @@ function normalizeChatMessages(raw) {
|
|
|
128
140
|
}
|
|
129
141
|
}
|
|
130
142
|
norm.messages.push(chatMessage);
|
|
143
|
+
const toolCallCandidate = value.tool_calls;
|
|
144
|
+
if (Array.isArray(toolCallCandidate) && toolCallCandidate.length) {
|
|
145
|
+
recordToolCallIssues(value, index, norm.missingFields);
|
|
146
|
+
}
|
|
131
147
|
if (roleValue === 'system') {
|
|
132
148
|
const segment = flattenSystemContent(chatMessage.content);
|
|
133
149
|
if (segment.trim().length) {
|
|
@@ -197,6 +213,29 @@ function extractParameters(body) {
|
|
|
197
213
|
}
|
|
198
214
|
return Object.keys(params).length ? params : undefined;
|
|
199
215
|
}
|
|
216
|
+
function collectExtraFields(body) {
|
|
217
|
+
const extras = {};
|
|
218
|
+
for (const [key, value] of Object.entries(body)) {
|
|
219
|
+
if (KNOWN_TOP_LEVEL_FIELDS.has(key)) {
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
if (value !== undefined) {
|
|
223
|
+
extras[key] = jsonClone(value);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return Object.keys(extras).length ? extras : undefined;
|
|
227
|
+
}
|
|
228
|
+
function applyExtraFields(body, metadata) {
|
|
229
|
+
if (!metadata || !metadata.extraFields || !isJsonObject(metadata.extraFields)) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
for (const [key, value] of Object.entries(metadata.extraFields)) {
|
|
233
|
+
if (body[key] !== undefined) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
body[key] = jsonClone(value);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
200
239
|
export class ChatSemanticMapper {
|
|
201
240
|
async toChat(format, ctx) {
|
|
202
241
|
const payload = (format.payload ?? {});
|
|
@@ -213,12 +252,17 @@ export class ChatSemanticMapper {
|
|
|
213
252
|
metadata.systemInstructions = normalized.systemSegments;
|
|
214
253
|
}
|
|
215
254
|
const rawSystemBlocks = collectSystemRawBlocks(payload.messages);
|
|
216
|
-
if (
|
|
217
|
-
|
|
255
|
+
if (rawSystemBlocks) {
|
|
256
|
+
const protocolState = ensureProtocolState(metadata, 'openai');
|
|
257
|
+
protocolState.systemMessages = jsonClone(rawSystemBlocks);
|
|
218
258
|
}
|
|
219
259
|
if (normalized.missingFields.length) {
|
|
220
260
|
metadata.missingFields = normalized.missingFields;
|
|
221
261
|
}
|
|
262
|
+
const extraFields = collectExtraFields(payload);
|
|
263
|
+
if (extraFields) {
|
|
264
|
+
metadata.extraFields = extraFields;
|
|
265
|
+
}
|
|
222
266
|
try {
|
|
223
267
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-chat' });
|
|
224
268
|
const actions = resolvePolicyActions(bridgePolicy, 'request_inbound');
|
|
@@ -258,6 +302,7 @@ export class ChatSemanticMapper {
|
|
|
258
302
|
tools: chat.tools ?? (chat.metadata?.toolsFieldPresent ? [] : undefined),
|
|
259
303
|
...(chat.parameters || {})
|
|
260
304
|
};
|
|
305
|
+
applyExtraFields(payload, chat.metadata);
|
|
261
306
|
try {
|
|
262
307
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-chat' });
|
|
263
308
|
const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
@@ -288,17 +333,12 @@ export class ChatSemanticMapper {
|
|
|
288
333
|
catch {
|
|
289
334
|
// ignore policy failures
|
|
290
335
|
}
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
if (chat.toolOutputs?.length) {
|
|
300
|
-
payload.tool_outputs = chat.toolOutputs;
|
|
301
|
-
}
|
|
336
|
+
// Do not forward tool_outputs to provider wire formats. OpenAI Chat
|
|
337
|
+
// endpoints expect tool results to appear as tool role messages, and
|
|
338
|
+
// sending the legacy top-level field causes upstream HTTP 400 responses.
|
|
339
|
+
// Concrete translation happens earlier when responses input is unfolded
|
|
340
|
+
// into ChatEnvelope.messages, so the provider request only needs the
|
|
341
|
+
// canonical message list.
|
|
302
342
|
if (payload.max_tokens === undefined && typeof payload.max_output_tokens === 'number') {
|
|
303
343
|
payload.max_tokens = payload.max_output_tokens;
|
|
304
344
|
delete payload.max_output_tokens;
|
|
@@ -5,6 +5,7 @@ import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-p
|
|
|
5
5
|
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../shared/metadata-passthrough.js';
|
|
6
6
|
import { mapBridgeToolsToChat, mapChatToolsToBridge } from '../../shared/tool-mapping.js';
|
|
7
7
|
import { prepareGeminiToolsForBridge, buildGeminiToolsFromBridge } from '../../shared/gemini-tool-utils.js';
|
|
8
|
+
import { ensureProtocolState, getProtocolState } from '../../shared/protocol-state.js';
|
|
8
9
|
const GENERATION_CONFIG_KEYS = [
|
|
9
10
|
{ source: 'temperature', target: 'temperature' },
|
|
10
11
|
{ source: 'topP', target: 'top_p' },
|
|
@@ -153,11 +154,9 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
153
154
|
model: chat.parameters?.model || 'models/gemini-pro',
|
|
154
155
|
contents
|
|
155
156
|
};
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
else if (metadata?.rawSystemInstruction !== undefined) {
|
|
160
|
-
request.systemInstruction = metadata.rawSystemInstruction;
|
|
157
|
+
const geminiState = getProtocolState(metadata, 'gemini');
|
|
158
|
+
if (geminiState?.systemInstruction !== undefined) {
|
|
159
|
+
request.systemInstruction = jsonClone(geminiState.systemInstruction);
|
|
161
160
|
}
|
|
162
161
|
else if (metadata?.systemInstructions && Array.isArray(metadata.systemInstructions)) {
|
|
163
162
|
const sysBlocks = metadata.systemInstructions
|
|
@@ -192,15 +191,6 @@ function buildGeminiRequestFromChat(chat, metadata) {
|
|
|
192
191
|
request.metadata = request.metadata ?? {};
|
|
193
192
|
request.metadata.__rcc_stream = chat.parameters.stream;
|
|
194
193
|
}
|
|
195
|
-
if (metadata?.rawSystem !== undefined) {
|
|
196
|
-
request.metadata = request.metadata ?? {};
|
|
197
|
-
try {
|
|
198
|
-
request.metadata.__rcc_raw_system = JSON.stringify(metadata.rawSystem);
|
|
199
|
-
}
|
|
200
|
-
catch {
|
|
201
|
-
// ignore serialization errors
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
194
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
205
195
|
request.metadata = request.metadata ?? {};
|
|
206
196
|
request.metadata.__rcc_tools_field_present = '1';
|
|
@@ -285,8 +275,7 @@ export class GeminiSemanticMapper {
|
|
|
285
275
|
}
|
|
286
276
|
if (payload.systemInstruction !== undefined) {
|
|
287
277
|
const rawSystem = jsonClone(payload.systemInstruction);
|
|
288
|
-
metadata.
|
|
289
|
-
metadata.rawSystem = rawSystem;
|
|
278
|
+
ensureProtocolState(metadata, 'gemini').systemInstruction = rawSystem;
|
|
290
279
|
}
|
|
291
280
|
if (payload.safetySettings) {
|
|
292
281
|
metadata.safetySettings = jsonClone(payload.safetySettings);
|
|
@@ -329,7 +318,6 @@ export class GeminiSemanticMapper {
|
|
|
329
318
|
if (providerMetadataSource) {
|
|
330
319
|
const providerMetadata = jsonClone(providerMetadataSource);
|
|
331
320
|
let toolsFieldPresent = false;
|
|
332
|
-
let rawSystemValue;
|
|
333
321
|
if (isJsonObject(providerMetadata)) {
|
|
334
322
|
delete providerMetadata.__rcc_stream;
|
|
335
323
|
if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_tools_field_present')) {
|
|
@@ -338,24 +326,12 @@ export class GeminiSemanticMapper {
|
|
|
338
326
|
delete providerMetadata.__rcc_tools_field_present;
|
|
339
327
|
}
|
|
340
328
|
if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_raw_system')) {
|
|
341
|
-
const rawSystemSentinel = providerMetadata.__rcc_raw_system;
|
|
342
329
|
delete providerMetadata.__rcc_raw_system;
|
|
343
|
-
if (typeof rawSystemSentinel === 'string') {
|
|
344
|
-
try {
|
|
345
|
-
rawSystemValue = JSON.parse(rawSystemSentinel);
|
|
346
|
-
}
|
|
347
|
-
catch {
|
|
348
|
-
rawSystemValue = undefined;
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
330
|
}
|
|
352
331
|
}
|
|
353
332
|
if (toolsFieldPresent) {
|
|
354
333
|
metadata.toolsFieldPresent = true;
|
|
355
334
|
}
|
|
356
|
-
if (rawSystemValue !== undefined) {
|
|
357
|
-
metadata.rawSystem = rawSystemValue;
|
|
358
|
-
}
|
|
359
335
|
metadata.providerMetadata = providerMetadata;
|
|
360
336
|
}
|
|
361
337
|
return {
|
|
@@ -12,10 +12,7 @@ const RESPONSES_PARAMETER_KEYS = [
|
|
|
12
12
|
'response_format',
|
|
13
13
|
'tool_choice',
|
|
14
14
|
'parallel_tool_calls',
|
|
15
|
-
'metadata',
|
|
16
|
-
'store',
|
|
17
15
|
'user',
|
|
18
|
-
'include',
|
|
19
16
|
'logit_bias',
|
|
20
17
|
'seed',
|
|
21
18
|
'stop',
|
|
@@ -211,21 +208,27 @@ export class ResponsesSemanticMapper {
|
|
|
211
208
|
.filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
|
|
212
209
|
.map(message => serializeSystemContent(message))
|
|
213
210
|
.filter((content) => typeof content === 'string' && content.length > 0);
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
211
|
+
const capturedContext = chat.metadata?.responsesContext;
|
|
212
|
+
const responsesContext = isJsonObject(capturedContext)
|
|
213
|
+
? {
|
|
214
|
+
...capturedContext,
|
|
215
|
+
originalSystemMessages
|
|
216
|
+
}
|
|
217
|
+
: {
|
|
218
|
+
metadata: (chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined),
|
|
219
|
+
originalSystemMessages
|
|
220
|
+
};
|
|
217
221
|
const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
|
|
218
222
|
const responses = responsesResult.request;
|
|
219
223
|
if (chat.parameters && chat.parameters.stream !== undefined) {
|
|
220
224
|
responses.stream = chat.parameters.stream;
|
|
221
225
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
226
|
+
// Do not forward ChatEnvelope.toolOutputs to OpenAI Responses create requests.
|
|
227
|
+
// Upstream expects historical tool results to remain inside input[] as
|
|
228
|
+
// tool role messages; sending the legacy top-level `tool_outputs` field
|
|
229
|
+
// causes providers like FAI to reject the request (HTTP 400). Any actual
|
|
230
|
+
// submit_tool_outputs call should be issued via the dedicated endpoint
|
|
231
|
+
// upstream, not through this mapper.
|
|
229
232
|
try {
|
|
230
233
|
const bridgePolicy = resolveBridgePolicy({ protocol: 'openai-responses', moduleType: 'openai-responses' });
|
|
231
234
|
const actions = resolvePolicyActions(bridgePolicy, 'request_outbound');
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { StageRecorder } from './format-adapters/index.js';
|
|
2
|
+
import type { AdapterContext } from './types/chat-envelope.js';
|
|
3
|
+
export interface SnapshotStageRecorderOptions {
|
|
4
|
+
context: AdapterContext;
|
|
5
|
+
endpoint: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class SnapshotStageRecorder implements StageRecorder {
|
|
8
|
+
private readonly options;
|
|
9
|
+
private readonly writer?;
|
|
10
|
+
constructor(options: SnapshotStageRecorderOptions);
|
|
11
|
+
record(stage: string, payload: object): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function createSnapshotRecorder(context: AdapterContext, endpoint: string): StageRecorder;
|
|
@@ -1,69 +1,109 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import fs from 'node:fs/promises';
|
|
1
|
+
import { createSnapshotWriter } from '../shared/snapshot-utils.js';
|
|
2
|
+
import { jsonClone } from './types/json.js';
|
|
5
3
|
export class SnapshotStageRecorder {
|
|
6
4
|
options;
|
|
5
|
+
writer;
|
|
7
6
|
constructor(options) {
|
|
8
7
|
this.options = options;
|
|
8
|
+
this.writer = createSnapshotWriter({
|
|
9
|
+
requestId: options.context.requestId,
|
|
10
|
+
endpoint: options.endpoint
|
|
11
|
+
});
|
|
9
12
|
}
|
|
10
13
|
record(stage, payload) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
stage,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
/* ignore hook errors */
|
|
25
|
-
});
|
|
14
|
+
if (!this.writer) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const normalized = normalizeStagePayload(stage, payload);
|
|
18
|
+
if (!normalized) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
try {
|
|
22
|
+
this.writer(stage, normalized);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// ignore snapshot write errors
|
|
26
|
+
}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
28
29
|
export function createSnapshotRecorder(context, endpoint) {
|
|
29
30
|
return new SnapshotStageRecorder({ context, endpoint });
|
|
30
31
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
const STAGE_KIND_MAP = {
|
|
33
|
+
req_inbound_stage2_semantic_map: 'request_inbound',
|
|
34
|
+
req_outbound_stage1_semantic_map: 'request_outbound',
|
|
35
|
+
resp_inbound_stage3_semantic_map: 'response_inbound',
|
|
36
|
+
resp_outbound_stage1_client_remap: 'response_outbound'
|
|
37
|
+
};
|
|
38
|
+
function normalizeStagePayload(stage, payload) {
|
|
39
|
+
const kind = STAGE_KIND_MAP[stage];
|
|
40
|
+
if (!kind) {
|
|
41
|
+
return payload;
|
|
42
|
+
}
|
|
43
|
+
if ((kind === 'request_inbound' || kind === 'request_outbound') && isChatEnvelope(payload)) {
|
|
44
|
+
return buildOpenAIChatSnapshot(payload);
|
|
45
|
+
}
|
|
46
|
+
if (kind === 'response_inbound' || kind === 'response_outbound') {
|
|
47
|
+
return cloneJson(payload);
|
|
48
|
+
}
|
|
49
|
+
return payload;
|
|
39
50
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
function isChatEnvelope(value) {
|
|
52
|
+
return Boolean(value &&
|
|
53
|
+
typeof value === 'object' &&
|
|
54
|
+
Array.isArray(value.messages) &&
|
|
55
|
+
value.metadata &&
|
|
56
|
+
typeof value.metadata === 'object');
|
|
57
|
+
}
|
|
58
|
+
function buildOpenAIChatSnapshot(envelope) {
|
|
59
|
+
const snapshot = {};
|
|
60
|
+
if (envelope.parameters && Object.keys(envelope.parameters).length) {
|
|
61
|
+
Object.assign(snapshot, jsonClone(envelope.parameters));
|
|
43
62
|
}
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
snapshot.messages = jsonClone(envelope.messages);
|
|
64
|
+
if (envelope.tools && envelope.tools.length) {
|
|
65
|
+
snapshot.tools = jsonClone(envelope.tools);
|
|
46
66
|
}
|
|
67
|
+
if (envelope.toolOutputs && envelope.toolOutputs.length) {
|
|
68
|
+
snapshot.tool_outputs = jsonClone(envelope.toolOutputs);
|
|
69
|
+
}
|
|
70
|
+
const meta = buildMetaSnapshot(envelope.metadata);
|
|
71
|
+
if (meta) {
|
|
72
|
+
snapshot.meta = meta;
|
|
73
|
+
}
|
|
74
|
+
return snapshot;
|
|
75
|
+
}
|
|
76
|
+
function buildMetaSnapshot(metadata) {
|
|
77
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
80
|
+
const meta = {};
|
|
81
|
+
if (metadata.context) {
|
|
82
|
+
meta.context = jsonClone(metadata.context);
|
|
83
|
+
}
|
|
84
|
+
if (Array.isArray(metadata.missingFields) && metadata.missingFields.length) {
|
|
85
|
+
meta.missing_fields = jsonClone(metadata.missingFields);
|
|
86
|
+
}
|
|
87
|
+
const extraKeys = Object.keys(metadata).filter((key) => key !== 'context' && key !== 'missingFields');
|
|
88
|
+
if (extraKeys.length) {
|
|
89
|
+
const extras = {};
|
|
90
|
+
for (const key of extraKeys) {
|
|
91
|
+
const value = metadata[key];
|
|
92
|
+
if (value !== undefined) {
|
|
93
|
+
extras[key] = jsonClone(value);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (Object.keys(extras).length) {
|
|
97
|
+
meta.extra = extras;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return Object.keys(meta).length ? meta : undefined;
|
|
47
101
|
}
|
|
48
|
-
|
|
102
|
+
function cloneJson(payload) {
|
|
49
103
|
try {
|
|
50
|
-
|
|
51
|
-
const dir = path.join(SNAPSHOT_BASE, folder);
|
|
52
|
-
await ensureDir(dir);
|
|
53
|
-
const safeStage = options.stage.replace(/[^\w.-]/g, '_');
|
|
54
|
-
const safeRequestId = options.requestId.replace(/[^\w.-]/g, '_');
|
|
55
|
-
const file = path.join(dir, `${safeRequestId}_${safeStage}.json`);
|
|
56
|
-
const payload = {
|
|
57
|
-
meta: {
|
|
58
|
-
stage: options.stage,
|
|
59
|
-
timestamp: Date.now(),
|
|
60
|
-
endpoint: options.endpoint
|
|
61
|
-
},
|
|
62
|
-
body: options.data
|
|
63
|
-
};
|
|
64
|
-
await fs.writeFile(file, JSON.stringify(payload, null, 2), 'utf-8');
|
|
104
|
+
return JSON.parse(JSON.stringify(payload));
|
|
65
105
|
}
|
|
66
|
-
catch
|
|
67
|
-
|
|
106
|
+
catch {
|
|
107
|
+
return payload;
|
|
68
108
|
}
|
|
69
109
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isJsonObject, jsonClone } from './types/json.js';
|
|
1
2
|
const HUB_CAPTURE_KEY = '__hub_capture';
|
|
2
3
|
export function chatEnvelopeToStandardized(chat, options) {
|
|
3
4
|
const parameters = { ...(chat.parameters ?? {}) };
|
|
@@ -15,8 +16,8 @@ export function chatEnvelopeToStandardized(chat, options) {
|
|
|
15
16
|
if (isJsonObject(chat.metadata?.extraFields)) {
|
|
16
17
|
hubState.extraFields = chat.metadata?.extraFields;
|
|
17
18
|
}
|
|
18
|
-
if (chat.metadata?.
|
|
19
|
-
hubState.
|
|
19
|
+
if (isJsonObject(chat.metadata?.protocolState)) {
|
|
20
|
+
hubState.protocolState = chat.metadata?.protocolState;
|
|
20
21
|
}
|
|
21
22
|
hubState.context = options.adapterContext;
|
|
22
23
|
if (hubStateContextPopulated(hubState)) {
|
|
@@ -41,13 +42,16 @@ export function chatEnvelopeToStandardized(chat, options) {
|
|
|
41
42
|
export function standardizedToChatEnvelope(request, options) {
|
|
42
43
|
const adapterContext = options.adapterContext;
|
|
43
44
|
const hubState = extractHubCapture(request);
|
|
44
|
-
const messages = request.messages.map((message) =>
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
const messages = request.messages.map((message) => {
|
|
46
|
+
const restoredContent = restoreMessageContent(message.content);
|
|
47
|
+
return {
|
|
48
|
+
role: message.role,
|
|
49
|
+
content: restoredContent,
|
|
50
|
+
tool_calls: mapToolCalls(message.tool_calls),
|
|
51
|
+
tool_call_id: message.tool_call_id,
|
|
52
|
+
name: message.name
|
|
53
|
+
};
|
|
54
|
+
});
|
|
51
55
|
const tools = mapStandardizedTools(request.tools);
|
|
52
56
|
const parameters = {
|
|
53
57
|
...(request.parameters ?? {}),
|
|
@@ -68,8 +72,8 @@ export function standardizedToChatEnvelope(request, options) {
|
|
|
68
72
|
if (hubState?.extraFields) {
|
|
69
73
|
metadata.extraFields = hubState.extraFields;
|
|
70
74
|
}
|
|
71
|
-
if (hubState?.
|
|
72
|
-
metadata.
|
|
75
|
+
if (hubState?.protocolState) {
|
|
76
|
+
metadata.protocolState = hubState.protocolState;
|
|
73
77
|
}
|
|
74
78
|
return {
|
|
75
79
|
messages,
|
|
@@ -88,7 +92,7 @@ function extractModel(parameters) {
|
|
|
88
92
|
function normalizeChatMessage(message) {
|
|
89
93
|
const normalized = {
|
|
90
94
|
role: message.role,
|
|
91
|
-
content:
|
|
95
|
+
content: cloneMessageContent(message.content)
|
|
92
96
|
};
|
|
93
97
|
if (Array.isArray(message.tool_calls) && message.tool_calls.length) {
|
|
94
98
|
normalized.tool_calls = message.tool_calls
|
|
@@ -103,7 +107,7 @@ function normalizeChatMessage(message) {
|
|
|
103
107
|
}
|
|
104
108
|
return normalized;
|
|
105
109
|
}
|
|
106
|
-
function
|
|
110
|
+
function cloneMessageContent(content) {
|
|
107
111
|
if (content === undefined || content === null) {
|
|
108
112
|
return null;
|
|
109
113
|
}
|
|
@@ -111,21 +115,35 @@ function normalizeContent(content) {
|
|
|
111
115
|
return content;
|
|
112
116
|
}
|
|
113
117
|
if (Array.isArray(content)) {
|
|
114
|
-
|
|
115
|
-
.map((part) => {
|
|
116
|
-
if (part && typeof part === 'object') {
|
|
117
|
-
if (typeof part.text === 'string') {
|
|
118
|
-
return String(part.text);
|
|
119
|
-
}
|
|
120
|
-
return JSON.stringify(part);
|
|
121
|
-
}
|
|
122
|
-
return '';
|
|
123
|
-
})
|
|
124
|
-
.filter(Boolean);
|
|
125
|
-
return parts.join('\n');
|
|
118
|
+
return cloneContentArray(content);
|
|
126
119
|
}
|
|
127
120
|
return String(content);
|
|
128
121
|
}
|
|
122
|
+
function restoreMessageContent(content) {
|
|
123
|
+
if (content === undefined) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
if (content === null) {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (typeof content === 'string') {
|
|
130
|
+
return content;
|
|
131
|
+
}
|
|
132
|
+
return cloneContentArray(content);
|
|
133
|
+
}
|
|
134
|
+
function cloneContentArray(parts) {
|
|
135
|
+
return parts.map((part) => {
|
|
136
|
+
if (part && typeof part === 'object') {
|
|
137
|
+
return jsonClone(part);
|
|
138
|
+
}
|
|
139
|
+
const text = typeof part === 'string' ? part : String(part ?? '');
|
|
140
|
+
const fallback = {
|
|
141
|
+
type: 'text',
|
|
142
|
+
text
|
|
143
|
+
};
|
|
144
|
+
return fallback;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
129
147
|
function normalizeToolCall(toolCall) {
|
|
130
148
|
if (!toolCall || typeof toolCall !== 'object') {
|
|
131
149
|
return null;
|
|
@@ -137,20 +155,16 @@ function normalizeToolCall(toolCall) {
|
|
|
137
155
|
if (type !== 'function') {
|
|
138
156
|
return null;
|
|
139
157
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
if (typeof fnName !== 'string' || !fnName.trim()) {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
const args = fn.arguments;
|
|
158
|
+
const fnRecord = fn && typeof fn === 'object' ? fn : undefined;
|
|
159
|
+
const rawName = typeof fnRecord?.name === 'string' ? fnRecord.name : '';
|
|
160
|
+
const normalizedName = rawName.trim();
|
|
161
|
+
const args = fnRecord?.arguments;
|
|
148
162
|
const serializedArgs = typeof args === 'string' ? args : safeStringify(args ?? {});
|
|
149
163
|
return {
|
|
150
164
|
id: id.trim(),
|
|
151
165
|
type: 'function',
|
|
152
166
|
function: {
|
|
153
|
-
name:
|
|
167
|
+
name: normalizedName.length ? normalizedName : rawName,
|
|
154
168
|
arguments: serializedArgs
|
|
155
169
|
}
|
|
156
170
|
};
|
|
@@ -218,9 +232,6 @@ function normalizeTools(tools) {
|
|
|
218
232
|
}
|
|
219
233
|
return normalized;
|
|
220
234
|
}
|
|
221
|
-
function isJsonObject(value) {
|
|
222
|
-
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
223
|
-
}
|
|
224
235
|
function extractHubCapture(request) {
|
|
225
236
|
const captured = request.metadata?.capturedContext;
|
|
226
237
|
if (!captured || typeof captured !== 'object') {
|
|
@@ -247,5 +258,5 @@ function hubStateContextPopulated(state) {
|
|
|
247
258
|
(state.missingFields && state.missingFields.length) ||
|
|
248
259
|
state.providerMetadata ||
|
|
249
260
|
state.extraFields ||
|
|
250
|
-
state.
|
|
261
|
+
(state.protocolState && Object.keys(state.protocolState).length));
|
|
251
262
|
}
|