@jsonstudio/llms 0.6.0 → 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/hub/process/chat-process.js +12 -6
- package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +20 -0
- package/dist/conversion/hub/semantic-mappers/chat-mapper.js +6 -27
- package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +5 -20
- package/dist/conversion/hub/standardized-bridge.js +6 -9
- package/dist/conversion/responses/responses-openai-bridge.js +0 -4
- package/dist/conversion/shared/anthropic-message-utils.js +80 -12
- package/dist/conversion/shared/bridge-actions.js +0 -55
- package/dist/conversion/shared/bridge-policies.js +4 -10
- package/dist/conversion/shared/chat-request-filters.js +1 -3
- package/dist/conversion/shared/protocol-state.d.ts +4 -0
- package/dist/conversion/shared/protocol-state.js +23 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +1 -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/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
|
@@ -29,7 +29,7 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
29
29
|
const providerProtocol = readString(metadata.providerProtocol) ?? readString(metadata.provider) ?? 'openai-chat';
|
|
30
30
|
const metadataToolHints = metadata.toolFilterHints;
|
|
31
31
|
const metadataStreamFlag = metadata.stream;
|
|
32
|
-
const
|
|
32
|
+
const inboundStreamIntent = typeof metadataStreamFlag === 'boolean' ? metadataStreamFlag : request.parameters?.stream === true;
|
|
33
33
|
const shaped = {
|
|
34
34
|
model: request.model,
|
|
35
35
|
messages: deepClone(request.messages),
|
|
@@ -43,10 +43,11 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
43
43
|
requestId: context.requestId,
|
|
44
44
|
model: request.model,
|
|
45
45
|
profile: providerProtocol,
|
|
46
|
-
stream:
|
|
46
|
+
stream: inboundStreamIntent,
|
|
47
47
|
toolFilterHints: metadataToolHints
|
|
48
48
|
});
|
|
49
49
|
const governed = normalizeRecord(governedPayload);
|
|
50
|
+
const providerStreamIntent = typeof governed.stream === 'boolean' ? governed.stream : undefined;
|
|
50
51
|
const merged = {
|
|
51
52
|
...request,
|
|
52
53
|
messages: Array.isArray(governed.messages)
|
|
@@ -60,14 +61,19 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
60
61
|
metadata: {
|
|
61
62
|
...request.metadata,
|
|
62
63
|
toolChoice: readToolChoice(governed.tool_choice),
|
|
63
|
-
originalStream:
|
|
64
|
-
stream:
|
|
65
|
-
|
|
66
|
-
: request.parameters?.stream,
|
|
64
|
+
originalStream: inboundStreamIntent,
|
|
65
|
+
stream: inboundStreamIntent,
|
|
66
|
+
providerStream: providerStreamIntent,
|
|
67
67
|
governedTools: governed.tools !== undefined,
|
|
68
68
|
governanceTimestamp: Date.now()
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
|
+
if (typeof inboundStreamIntent === 'boolean') {
|
|
72
|
+
merged.metadata = {
|
|
73
|
+
...merged.metadata,
|
|
74
|
+
inboundStream: inboundStreamIntent
|
|
75
|
+
};
|
|
76
|
+
}
|
|
71
77
|
if (typeof governed.stream === 'boolean') {
|
|
72
78
|
merged.parameters = {
|
|
73
79
|
...merged.parameters,
|
|
@@ -5,6 +5,7 @@ import { resolveBridgePolicy, resolvePolicyActions } from '../../shared/bridge-p
|
|
|
5
5
|
import { encodeMetadataPassthrough, extractMetadataPassthrough } from '../../shared/metadata-passthrough.js';
|
|
6
6
|
import { buildAnthropicToolAliasMap } from '../../shared/anthropic-message-utils.js';
|
|
7
7
|
import { ChatSemanticMapper } from './chat-mapper.js';
|
|
8
|
+
import { ensureProtocolState, getProtocolState } from '../../shared/protocol-state.js';
|
|
8
9
|
const ANTHROPIC_PARAMETER_KEYS = [
|
|
9
10
|
'model',
|
|
10
11
|
'temperature',
|
|
@@ -53,6 +54,16 @@ function collectParameters(payload) {
|
|
|
53
54
|
}
|
|
54
55
|
return Object.keys(params).length ? params : undefined;
|
|
55
56
|
}
|
|
57
|
+
function cloneAnthropicSystemBlocks(value) {
|
|
58
|
+
if (value === undefined || value === null) {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
const blocks = Array.isArray(value) ? value : [value];
|
|
62
|
+
if (!blocks.length) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
return blocks.map((entry) => jsonClone(entry));
|
|
66
|
+
}
|
|
56
67
|
export class AnthropicSemanticMapper {
|
|
57
68
|
chatMapper = new ChatSemanticMapper();
|
|
58
69
|
async toChat(format, ctx) {
|
|
@@ -86,6 +97,11 @@ export class AnthropicSemanticMapper {
|
|
|
86
97
|
}
|
|
87
98
|
return metadata.extraFields;
|
|
88
99
|
};
|
|
100
|
+
const protocolState = ensureProtocolState(metadata, 'anthropic');
|
|
101
|
+
const systemBlocks = cloneAnthropicSystemBlocks(payload.system);
|
|
102
|
+
if (systemBlocks) {
|
|
103
|
+
protocolState.systemBlocks = systemBlocks;
|
|
104
|
+
}
|
|
89
105
|
if (payload.tools && Array.isArray(payload.tools) && payload.tools.length === 0) {
|
|
90
106
|
metadata.toolsFieldPresent = true;
|
|
91
107
|
resolveExtraFields().toolsFieldPresent = true;
|
|
@@ -227,6 +243,10 @@ export class AnthropicSemanticMapper {
|
|
|
227
243
|
if (chat.metadata?.toolsFieldPresent && (!Array.isArray(chat.tools) || chat.tools.length === 0)) {
|
|
228
244
|
baseRequest.tools = [];
|
|
229
245
|
}
|
|
246
|
+
const protocolState = getProtocolState(chat.metadata, 'anthropic');
|
|
247
|
+
if (protocolState?.systemBlocks !== undefined) {
|
|
248
|
+
baseRequest.system = jsonClone(protocolState.systemBlocks);
|
|
249
|
+
}
|
|
230
250
|
if (chat.metadata &&
|
|
231
251
|
typeof chat.metadata === 'object' &&
|
|
232
252
|
chat.metadata.extraFields &&
|
|
@@ -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',
|
|
@@ -93,35 +94,12 @@ function collectSystemRawBlocks(raw) {
|
|
|
93
94
|
if (!Array.isArray(raw))
|
|
94
95
|
return undefined;
|
|
95
96
|
const blocks = [];
|
|
96
|
-
const pushText = (text) => {
|
|
97
|
-
if (typeof text === 'string' && text.trim().length) {
|
|
98
|
-
blocks.push({ type: 'text', text });
|
|
99
|
-
}
|
|
100
|
-
};
|
|
101
97
|
raw.forEach((entry) => {
|
|
102
98
|
if (!isJsonObject(entry))
|
|
103
99
|
return;
|
|
104
|
-
if (entry.role !== 'system')
|
|
105
|
-
return;
|
|
106
|
-
const content = entry.content;
|
|
107
|
-
if (typeof content === 'string') {
|
|
108
|
-
pushText(content);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (Array.isArray(content)) {
|
|
112
|
-
content.forEach((part) => {
|
|
113
|
-
if (typeof part === 'string') {
|
|
114
|
-
pushText(part);
|
|
115
|
-
}
|
|
116
|
-
else if (isJsonObject(part) && typeof part.text === 'string') {
|
|
117
|
-
pushText(part.text);
|
|
118
|
-
}
|
|
119
|
-
});
|
|
100
|
+
if (String(entry.role ?? '').toLowerCase() !== 'system')
|
|
120
101
|
return;
|
|
121
|
-
|
|
122
|
-
if (isJsonObject(content) && typeof content.text === 'string') {
|
|
123
|
-
pushText(content.text);
|
|
124
|
-
}
|
|
102
|
+
blocks.push(jsonClone(entry));
|
|
125
103
|
});
|
|
126
104
|
return blocks.length ? blocks : undefined;
|
|
127
105
|
}
|
|
@@ -274,8 +252,9 @@ export class ChatSemanticMapper {
|
|
|
274
252
|
metadata.systemInstructions = normalized.systemSegments;
|
|
275
253
|
}
|
|
276
254
|
const rawSystemBlocks = collectSystemRawBlocks(payload.messages);
|
|
277
|
-
if (
|
|
278
|
-
|
|
255
|
+
if (rawSystemBlocks) {
|
|
256
|
+
const protocolState = ensureProtocolState(metadata, 'openai');
|
|
257
|
+
protocolState.systemMessages = jsonClone(rawSystemBlocks);
|
|
279
258
|
}
|
|
280
259
|
if (normalized.missingFields.length) {
|
|
281
260
|
metadata.missingFields = normalized.missingFields;
|
|
@@ -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
|
|
@@ -276,8 +275,7 @@ export class GeminiSemanticMapper {
|
|
|
276
275
|
}
|
|
277
276
|
if (payload.systemInstruction !== undefined) {
|
|
278
277
|
const rawSystem = jsonClone(payload.systemInstruction);
|
|
279
|
-
metadata.
|
|
280
|
-
metadata.rawSystem = rawSystem;
|
|
278
|
+
ensureProtocolState(metadata, 'gemini').systemInstruction = rawSystem;
|
|
281
279
|
}
|
|
282
280
|
if (payload.safetySettings) {
|
|
283
281
|
metadata.safetySettings = jsonClone(payload.safetySettings);
|
|
@@ -320,7 +318,6 @@ export class GeminiSemanticMapper {
|
|
|
320
318
|
if (providerMetadataSource) {
|
|
321
319
|
const providerMetadata = jsonClone(providerMetadataSource);
|
|
322
320
|
let toolsFieldPresent = false;
|
|
323
|
-
let rawSystemValue;
|
|
324
321
|
if (isJsonObject(providerMetadata)) {
|
|
325
322
|
delete providerMetadata.__rcc_stream;
|
|
326
323
|
if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_tools_field_present')) {
|
|
@@ -329,24 +326,12 @@ export class GeminiSemanticMapper {
|
|
|
329
326
|
delete providerMetadata.__rcc_tools_field_present;
|
|
330
327
|
}
|
|
331
328
|
if (Object.prototype.hasOwnProperty.call(providerMetadata, '__rcc_raw_system')) {
|
|
332
|
-
const rawSystemSentinel = providerMetadata.__rcc_raw_system;
|
|
333
329
|
delete providerMetadata.__rcc_raw_system;
|
|
334
|
-
if (typeof rawSystemSentinel === 'string') {
|
|
335
|
-
try {
|
|
336
|
-
rawSystemValue = JSON.parse(rawSystemSentinel);
|
|
337
|
-
}
|
|
338
|
-
catch {
|
|
339
|
-
rawSystemValue = undefined;
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
330
|
}
|
|
343
331
|
}
|
|
344
332
|
if (toolsFieldPresent) {
|
|
345
333
|
metadata.toolsFieldPresent = true;
|
|
346
334
|
}
|
|
347
|
-
if (rawSystemValue !== undefined) {
|
|
348
|
-
metadata.rawSystem = rawSystemValue;
|
|
349
|
-
}
|
|
350
335
|
metadata.providerMetadata = providerMetadata;
|
|
351
336
|
}
|
|
352
337
|
return {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsonClone } from './types/json.js';
|
|
1
|
+
import { isJsonObject, jsonClone } from './types/json.js';
|
|
2
2
|
const HUB_CAPTURE_KEY = '__hub_capture';
|
|
3
3
|
export function chatEnvelopeToStandardized(chat, options) {
|
|
4
4
|
const parameters = { ...(chat.parameters ?? {}) };
|
|
@@ -16,8 +16,8 @@ export function chatEnvelopeToStandardized(chat, options) {
|
|
|
16
16
|
if (isJsonObject(chat.metadata?.extraFields)) {
|
|
17
17
|
hubState.extraFields = chat.metadata?.extraFields;
|
|
18
18
|
}
|
|
19
|
-
if (chat.metadata?.
|
|
20
|
-
hubState.
|
|
19
|
+
if (isJsonObject(chat.metadata?.protocolState)) {
|
|
20
|
+
hubState.protocolState = chat.metadata?.protocolState;
|
|
21
21
|
}
|
|
22
22
|
hubState.context = options.adapterContext;
|
|
23
23
|
if (hubStateContextPopulated(hubState)) {
|
|
@@ -72,8 +72,8 @@ export function standardizedToChatEnvelope(request, options) {
|
|
|
72
72
|
if (hubState?.extraFields) {
|
|
73
73
|
metadata.extraFields = hubState.extraFields;
|
|
74
74
|
}
|
|
75
|
-
if (hubState?.
|
|
76
|
-
metadata.
|
|
75
|
+
if (hubState?.protocolState) {
|
|
76
|
+
metadata.protocolState = hubState.protocolState;
|
|
77
77
|
}
|
|
78
78
|
return {
|
|
79
79
|
messages,
|
|
@@ -232,9 +232,6 @@ function normalizeTools(tools) {
|
|
|
232
232
|
}
|
|
233
233
|
return normalized;
|
|
234
234
|
}
|
|
235
|
-
function isJsonObject(value) {
|
|
236
|
-
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
237
|
-
}
|
|
238
235
|
function extractHubCapture(request) {
|
|
239
236
|
const captured = request.metadata?.capturedContext;
|
|
240
237
|
if (!captured || typeof captured !== 'object') {
|
|
@@ -261,5 +258,5 @@ function hubStateContextPopulated(state) {
|
|
|
261
258
|
(state.missingFields && state.missingFields.length) ||
|
|
262
259
|
state.providerMetadata ||
|
|
263
260
|
state.extraFields ||
|
|
264
|
-
state.
|
|
261
|
+
(state.protocolState && Object.keys(state.protocolState).length));
|
|
265
262
|
}
|
|
@@ -161,7 +161,6 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
161
161
|
'response_format',
|
|
162
162
|
'user',
|
|
163
163
|
'top_p',
|
|
164
|
-
'max_output_tokens',
|
|
165
164
|
'logit_bias',
|
|
166
165
|
'seed'
|
|
167
166
|
];
|
|
@@ -169,9 +168,6 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
169
168
|
if (chat[key] !== undefined)
|
|
170
169
|
out[key] = chat[key];
|
|
171
170
|
}
|
|
172
|
-
if (typeof chat.max_tokens === 'number' && chat.max_output_tokens === undefined) {
|
|
173
|
-
out.max_output_tokens = chat.max_tokens;
|
|
174
|
-
}
|
|
175
171
|
let messages = Array.isArray(chat.messages) ? chat.messages : [];
|
|
176
172
|
let bridgeMetadata;
|
|
177
173
|
try {
|
|
@@ -32,6 +32,85 @@ function flattenAnthropicText(content) {
|
|
|
32
32
|
}
|
|
33
33
|
return '';
|
|
34
34
|
}
|
|
35
|
+
function normalizeToolResultContent(block) {
|
|
36
|
+
if (!block || typeof block !== 'object') {
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
const content = block.content;
|
|
40
|
+
if (typeof content === 'string') {
|
|
41
|
+
return content;
|
|
42
|
+
}
|
|
43
|
+
if (Array.isArray(content)) {
|
|
44
|
+
const segments = [];
|
|
45
|
+
for (const entry of content) {
|
|
46
|
+
const segment = extractToolResultSegment(entry);
|
|
47
|
+
if (segment) {
|
|
48
|
+
segments.push(segment);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (segments.length) {
|
|
52
|
+
return segments.join('\n');
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
else if (content != null) {
|
|
56
|
+
try {
|
|
57
|
+
return JSON.stringify(content);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return String(content);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
function extractToolResultSegment(entry) {
|
|
66
|
+
if (entry == null) {
|
|
67
|
+
return '';
|
|
68
|
+
}
|
|
69
|
+
if (typeof entry === 'string') {
|
|
70
|
+
return entry;
|
|
71
|
+
}
|
|
72
|
+
if (Array.isArray(entry)) {
|
|
73
|
+
return entry.map(extractToolResultSegment).filter(Boolean).join('');
|
|
74
|
+
}
|
|
75
|
+
if (typeof entry === 'object') {
|
|
76
|
+
const node = entry;
|
|
77
|
+
const type = typeof node.type === 'string' ? node.type.toLowerCase() : '';
|
|
78
|
+
if (type === 'input_text' || type === 'input_json' || type === 'tool_result_status' || type === 'status' || type === 'metadata') {
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
if (type === 'output_text' || type === 'text' || type === 'reasoning' || type === 'log') {
|
|
82
|
+
return flattenAnthropicText(entry);
|
|
83
|
+
}
|
|
84
|
+
if (type === 'output_json' || type === 'json') {
|
|
85
|
+
const payload = node.content ?? node.text ?? node.data ?? node.output;
|
|
86
|
+
if (payload === undefined) {
|
|
87
|
+
return '';
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return JSON.stringify(payload);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return String(payload ?? '');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (typeof node.text === 'string') {
|
|
97
|
+
return node.text;
|
|
98
|
+
}
|
|
99
|
+
if ('content' in node) {
|
|
100
|
+
const nested = extractToolResultSegment(node.content);
|
|
101
|
+
if (nested) {
|
|
102
|
+
return nested;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
try {
|
|
106
|
+
return JSON.stringify(entry);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return '';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return String(entry);
|
|
113
|
+
}
|
|
35
114
|
function requireTrimmedString(value, context) {
|
|
36
115
|
if (typeof value !== 'string') {
|
|
37
116
|
throw new Error(`Anthropic bridge constraint violated: ${context} must be a string`);
|
|
@@ -218,18 +297,7 @@ export function buildOpenAIChatFromAnthropic(payload) {
|
|
|
218
297
|
block.call_id ??
|
|
219
298
|
block.tool_use_id ??
|
|
220
299
|
block.id, 'tool_result.tool_use_id');
|
|
221
|
-
|
|
222
|
-
const c = block.content;
|
|
223
|
-
if (typeof c === 'string')
|
|
224
|
-
contentStr = c;
|
|
225
|
-
else if (c != null) {
|
|
226
|
-
try {
|
|
227
|
-
contentStr = JSON.stringify(c);
|
|
228
|
-
}
|
|
229
|
-
catch {
|
|
230
|
-
contentStr = String(c);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
300
|
+
const contentStr = normalizeToolResultContent(block);
|
|
233
301
|
toolResults.push({ role: 'tool', tool_call_id: callId, content: contentStr });
|
|
234
302
|
}
|
|
235
303
|
}
|
|
@@ -879,58 +879,3 @@ const metadataProviderSentinelAction = (ctx) => {
|
|
|
879
879
|
};
|
|
880
880
|
registerBridgeAction('metadata.provider-field', metadataProviderFieldAction);
|
|
881
881
|
registerBridgeAction('metadata.provider-sentinel', metadataProviderSentinelAction);
|
|
882
|
-
const metadataSystemSentinelAction = (ctx) => {
|
|
883
|
-
const sentinel = typeof ctx.descriptor.options?.sentinel === 'string'
|
|
884
|
-
? ctx.descriptor.options.sentinel
|
|
885
|
-
: undefined;
|
|
886
|
-
const targetKey = typeof ctx.descriptor.options?.target === 'string'
|
|
887
|
-
? ctx.descriptor.options.target
|
|
888
|
-
: 'rawSystem';
|
|
889
|
-
const stringifyOutbound = ctx.descriptor.options?.stringify !== false;
|
|
890
|
-
if (!sentinel)
|
|
891
|
-
return;
|
|
892
|
-
const payload = ctx.stage.startsWith('request') ? ctx.state.rawRequest :
|
|
893
|
-
ctx.stage.startsWith('response') ? ctx.state.rawResponse :
|
|
894
|
-
undefined;
|
|
895
|
-
if (!payload || typeof payload !== 'object')
|
|
896
|
-
return;
|
|
897
|
-
if (ctx.stage.endsWith('inbound')) {
|
|
898
|
-
const rawValue = payload[sentinel];
|
|
899
|
-
if (rawValue === undefined)
|
|
900
|
-
return;
|
|
901
|
-
let parsed = rawValue;
|
|
902
|
-
if (typeof rawValue === 'string') {
|
|
903
|
-
try {
|
|
904
|
-
parsed = JSON.parse(rawValue);
|
|
905
|
-
}
|
|
906
|
-
catch {
|
|
907
|
-
parsed = rawValue;
|
|
908
|
-
}
|
|
909
|
-
}
|
|
910
|
-
delete payload[sentinel];
|
|
911
|
-
const metadata = ensureMetadataRecord(ctx.state);
|
|
912
|
-
metadata[targetKey] = parsed;
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
const metadata = ctx.state.metadata;
|
|
916
|
-
if (!metadata || typeof metadata !== 'object') {
|
|
917
|
-
delete payload[sentinel];
|
|
918
|
-
return;
|
|
919
|
-
}
|
|
920
|
-
const value = metadata[targetKey];
|
|
921
|
-
if (value === undefined) {
|
|
922
|
-
delete payload[sentinel];
|
|
923
|
-
return;
|
|
924
|
-
}
|
|
925
|
-
if (stringifyOutbound) {
|
|
926
|
-
try {
|
|
927
|
-
payload[sentinel] = JSON.stringify(value);
|
|
928
|
-
}
|
|
929
|
-
catch {
|
|
930
|
-
payload[sentinel] = value;
|
|
931
|
-
}
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
payload[sentinel] = value;
|
|
935
|
-
};
|
|
936
|
-
registerBridgeAction('metadata.system-sentinel', metadataSystemSentinelAction);
|
|
@@ -136,8 +136,7 @@ const RESPONSES_POLICY = {
|
|
|
136
136
|
{ name: 'tools.ensure-placeholders' },
|
|
137
137
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
138
138
|
{ name: 'metadata.provider-field', options: { field: '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' } }
|
|
139
|
+
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
141
140
|
],
|
|
142
141
|
outbound: [
|
|
143
142
|
{ name: 'messages.normalize-history' },
|
|
@@ -149,8 +148,7 @@ const RESPONSES_POLICY = {
|
|
|
149
148
|
reasoningAction('responses_reasoning'),
|
|
150
149
|
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } },
|
|
151
150
|
{ name: 'metadata.provider-field', options: { field: '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' } }
|
|
151
|
+
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
154
152
|
]
|
|
155
153
|
},
|
|
156
154
|
response: {
|
|
@@ -158,14 +156,12 @@ const RESPONSES_POLICY = {
|
|
|
158
156
|
{ name: 'reasoning.attach-output' },
|
|
159
157
|
reasoningAction('responses_reasoning'),
|
|
160
158
|
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' } }
|
|
159
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } }
|
|
163
160
|
],
|
|
164
161
|
outbound: [
|
|
165
162
|
reasoningAction('responses_reasoning'),
|
|
166
163
|
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' } }
|
|
164
|
+
{ name: 'metadata.extra-fields', options: { allowedKeys: RESPONSES_ALLOWED_FIELDS } }
|
|
169
165
|
]
|
|
170
166
|
}
|
|
171
167
|
};
|
|
@@ -177,7 +173,6 @@ const OPENAI_CHAT_POLICY = {
|
|
|
177
173
|
reasoningAction('openai_chat_reasoning'),
|
|
178
174
|
toolCallNormalizationAction('openai_chat_tool_call'),
|
|
179
175
|
{ name: 'messages.ensure-system-instruction' },
|
|
180
|
-
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } },
|
|
181
176
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } },
|
|
182
177
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
183
178
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
@@ -190,7 +185,6 @@ const OPENAI_CHAT_POLICY = {
|
|
|
190
185
|
{ name: 'messages.ensure-output-fields', options: { toolFallback: 'Tool call completed (no output).' } },
|
|
191
186
|
{ name: 'messages.ensure-system-instruction' },
|
|
192
187
|
reasoningAction('openai_chat_reasoning'),
|
|
193
|
-
{ name: 'metadata.system-sentinel', options: { sentinel: '__rcc_raw_system', target: 'rawSystem' } },
|
|
194
188
|
{ name: 'metadata.extra-fields', options: { allowedKeys: OPENAI_CHAT_ALLOWED_FIELDS } },
|
|
195
189
|
{ name: 'metadata.provider-field', options: { field: 'metadata', target: 'providerMetadata' } },
|
|
196
190
|
{ name: 'metadata.provider-sentinel', options: { sentinel: '__rcc_provider_metadata', target: 'providerMetadata' } }
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FilterEngine, RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter
|
|
1
|
+
import { FilterEngine, RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } from '../../filters/index.js';
|
|
2
2
|
import { normalizeChatRequest } from '../index.js';
|
|
3
3
|
import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
|
|
4
4
|
import { createSnapshotWriter } from './snapshot-utils.js';
|
|
@@ -66,8 +66,6 @@ export async function runStandardChatRequestFilters(chatRequest, profile, contex
|
|
|
66
66
|
}
|
|
67
67
|
engine.registerFilter(new RequestToolCallsStringifyFilter());
|
|
68
68
|
engine.registerFilter(new RequestToolChoicePolicyFilter());
|
|
69
|
-
// 统一将 Provider 请求转为非流式 Chat;流式语义由上层 SSE 合成处理
|
|
70
|
-
engine.registerFilter(new RequestStreamingToNonStreamingFilter());
|
|
71
69
|
// FieldMap:保持与 Chat 入口一致,使用 openai-openai.fieldmap.json
|
|
72
70
|
try {
|
|
73
71
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChatEnvelope } from '../hub/types/chat-envelope.js';
|
|
2
|
+
import { type JsonObject } from '../hub/types/json.js';
|
|
3
|
+
export declare function ensureProtocolState(metadata: ChatEnvelope['metadata'], protocol: string): JsonObject;
|
|
4
|
+
export declare function getProtocolState(metadata: ChatEnvelope['metadata'] | undefined, protocol: string): JsonObject | undefined;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isJsonObject } from '../hub/types/json.js';
|
|
2
|
+
const PROTOCOL_STATE_KEY = 'protocolState';
|
|
3
|
+
export function ensureProtocolState(metadata, protocol) {
|
|
4
|
+
if (!metadata[PROTOCOL_STATE_KEY] || !isJsonObject(metadata[PROTOCOL_STATE_KEY])) {
|
|
5
|
+
metadata[PROTOCOL_STATE_KEY] = {};
|
|
6
|
+
}
|
|
7
|
+
const container = metadata[PROTOCOL_STATE_KEY];
|
|
8
|
+
if (!isJsonObject(container[protocol])) {
|
|
9
|
+
container[protocol] = {};
|
|
10
|
+
}
|
|
11
|
+
return container[protocol];
|
|
12
|
+
}
|
|
13
|
+
export function getProtocolState(metadata, protocol) {
|
|
14
|
+
if (!metadata) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const store = metadata[PROTOCOL_STATE_KEY];
|
|
18
|
+
if (!isJsonObject(store)) {
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
const node = store[protocol];
|
|
22
|
+
return isJsonObject(node) ? node : undefined;
|
|
23
|
+
}
|
|
@@ -35,12 +35,11 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
35
35
|
/* optional */
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
-
const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter
|
|
38
|
+
const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } = await import('../../filters/index.js');
|
|
39
39
|
engine.registerFilter(new RequestToolCallsStringifyFilter());
|
|
40
40
|
if (!isAnthropic) {
|
|
41
41
|
engine.registerFilter(new RequestToolChoicePolicyFilter());
|
|
42
42
|
}
|
|
43
|
-
engine.registerFilter(new RequestStreamingToNonStreamingFilter());
|
|
44
43
|
try {
|
|
45
44
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
46
45
|
if (cfg)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './engine.js';
|
|
3
|
+
export * from './builtin/whitelist-filter.js';
|
|
4
|
+
export * from './builtin/blacklist-filter.js';
|
|
5
|
+
export * from './builtin/add-fields-filter.js';
|
|
6
|
+
export * from './special/request-toolcalls-stringify.js';
|
|
7
|
+
export * from './special/request-tool-choice-policy.js';
|
|
8
|
+
export * from './special/request-tool-list-filter.js';
|
|
9
|
+
export * from './special/tool-filter-hooks.js';
|
|
10
|
+
export * from './special/response-tool-text-canonicalize.js';
|
|
11
|
+
export * from './special/response-tool-arguments-stringify.js';
|
|
12
|
+
export * from './special/response-finish-invariants.js';
|
|
13
|
+
export * from './special/request-tools-normalize.js';
|
|
14
|
+
export * from './special/response-tool-arguments-toon-decode.js';
|
|
15
|
+
export * from './special/response-tool-arguments-blacklist.js';
|
|
16
|
+
export * from './special/response-tool-arguments-schema-converge.js';
|
|
17
|
+
export * from './special/response-tool-arguments-whitelist.js';
|
|
18
|
+
export * from './special/tool-post-constraints.js';
|
package/dist/filters/index.js
CHANGED
|
@@ -9,7 +9,6 @@ export * from './special/request-toolcalls-stringify.js';
|
|
|
9
9
|
export * from './special/request-tool-choice-policy.js';
|
|
10
10
|
export * from './special/request-tool-list-filter.js';
|
|
11
11
|
export * from './special/tool-filter-hooks.js';
|
|
12
|
-
export * from './special/request-streaming-to-nonstreaming.js';
|
|
13
12
|
export * from './special/response-tool-text-canonicalize.js';
|
|
14
13
|
export * from './special/response-tool-arguments-stringify.js';
|
|
15
14
|
export * from './special/response-finish-invariants.js';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Filter, FilterContext, FilterResult, JsonObject } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* RequestStreamingToNonStreamingFilter (request_post)
|
|
4
|
+
* - Unifies provider requests to non-streaming regardless of inbound stream flag.
|
|
5
|
+
* - Preserves original intent via `originalStream` and `_originalStreamOptions`,
|
|
6
|
+
* but ensures `stream: false` is sent to providers.
|
|
7
|
+
*/
|
|
8
|
+
export declare class RequestStreamingToNonStreamingFilter implements Filter<JsonObject> {
|
|
9
|
+
readonly name = "request_streaming_to_nonstreaming";
|
|
10
|
+
readonly stage: FilterContext['stage'];
|
|
11
|
+
apply(input: JsonObject, ctx: FilterContext): FilterResult<JsonObject>;
|
|
12
|
+
private shouldPreserveStream;
|
|
13
|
+
}
|
|
@@ -7,9 +7,12 @@
|
|
|
7
7
|
export class RequestStreamingToNonStreamingFilter {
|
|
8
8
|
name = 'request_streaming_to_nonstreaming';
|
|
9
9
|
stage = 'request_post';
|
|
10
|
-
apply(input) {
|
|
10
|
+
apply(input, ctx) {
|
|
11
11
|
try {
|
|
12
12
|
const out = JSON.parse(JSON.stringify(input || {}));
|
|
13
|
+
if (this.shouldPreserveStream(out, ctx)) {
|
|
14
|
+
return { ok: true, data: out };
|
|
15
|
+
}
|
|
13
16
|
if (out.stream === true) {
|
|
14
17
|
out.originalStream = true;
|
|
15
18
|
out.stream = false;
|
|
@@ -24,4 +27,13 @@ export class RequestStreamingToNonStreamingFilter {
|
|
|
24
27
|
return { ok: true, data: input };
|
|
25
28
|
}
|
|
26
29
|
}
|
|
30
|
+
shouldPreserveStream(payload, ctx) {
|
|
31
|
+
if (ctx.stream === true) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
if (payload.stream === true) {
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
27
39
|
}
|
|
@@ -574,7 +574,30 @@ export class ResponsesResponseBuilder {
|
|
|
574
574
|
* 处理required_action事件
|
|
575
575
|
*/
|
|
576
576
|
handleRequiredAction(event) {
|
|
577
|
-
|
|
577
|
+
const payload = event.data ?? {};
|
|
578
|
+
const responsePayload = payload.response ?? payload;
|
|
579
|
+
const requiredAction = responsePayload.required_action ??
|
|
580
|
+
payload.required_action ??
|
|
581
|
+
undefined;
|
|
582
|
+
const usage = responsePayload.usage ??
|
|
583
|
+
payload.usage ??
|
|
584
|
+
this.response.usage;
|
|
585
|
+
const nextResponse = {
|
|
586
|
+
...this.response,
|
|
587
|
+
object: 'response',
|
|
588
|
+
id: responsePayload.id ?? this.response.id,
|
|
589
|
+
status: responsePayload.status ?? 'requires_action',
|
|
590
|
+
output: Array.isArray(responsePayload.output) && responsePayload.output.length
|
|
591
|
+
? responsePayload.output
|
|
592
|
+
: this.buildOutputItems(),
|
|
593
|
+
required_action: requiredAction ?? this.response.required_action,
|
|
594
|
+
usage
|
|
595
|
+
};
|
|
596
|
+
if (responsePayload.metadata) {
|
|
597
|
+
nextResponse.metadata = responsePayload.metadata;
|
|
598
|
+
}
|
|
599
|
+
this.response = nextResponse;
|
|
600
|
+
this.state = 'completed';
|
|
578
601
|
}
|
|
579
602
|
/**
|
|
580
603
|
* 处理response.done事件
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 支持OpenAI Responses API的JSON↔SSE双向转换
|
|
4
4
|
*/
|
|
5
5
|
import { BaseSseEvent, StreamDirection, type JsonObject, type JsonValue } from './core-interfaces.js';
|
|
6
|
+
import type { RequiredAction } from './sse-events.js';
|
|
6
7
|
export * from './sse-events.js';
|
|
7
8
|
export type ResponsesSseEventType = 'response.created' | 'response.in_progress' | 'response.completed' | 'response.required_action' | 'response.done' | 'response.output_item.added' | 'response.output_item.done' | 'response.content_part.added' | 'response.content_part.done' | 'response.output_text.delta' | 'response.output_text.done' | 'response.reasoning_text.delta' | 'response.reasoning_text.done' | 'response.reasoning_signature.delta' | 'response.reasoning_image.delta' | 'response.reasoning_summary_part.added' | 'response.reasoning_summary_part.done' | 'response.reasoning_summary_text.delta' | 'response.reasoning_summary_text.done' | 'response.function_call_arguments.delta' | 'response.function_call_arguments.done' | 'response.error' | 'response.cancelled' | 'response.start' | 'content_part.delta' | 'reasoning.delta' | 'function_call.start' | 'function_call.delta' | 'function_call.done' | 'output_item.start' | 'content_part.start' | 'content_part.done' | 'output_item.done' | 'reasoning.start' | 'reasoning.done' | 'required_action' | 'error';
|
|
8
9
|
export interface ResponsesSseEvent extends BaseSseEvent {
|
|
@@ -118,6 +119,7 @@ export interface ResponsesResponse {
|
|
|
118
119
|
output: ResponsesOutputItem[];
|
|
119
120
|
previous_response_id?: string;
|
|
120
121
|
usage?: ResponsesUsage;
|
|
122
|
+
required_action?: RequiredAction;
|
|
121
123
|
temperature?: number;
|
|
122
124
|
top_p?: number;
|
|
123
125
|
max_output_tokens?: number;
|