@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.
Files changed (99) hide show
  1. package/dist/conversion/codecs/anthropic-openai-codec.js +28 -2
  2. package/dist/conversion/codecs/gemini-openai-codec.js +23 -0
  3. package/dist/conversion/codecs/responses-openai-codec.js +8 -1
  4. package/dist/conversion/hub/node-support.js +14 -1
  5. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +66 -0
  6. package/dist/conversion/hub/pipeline/hub-pipeline.js +284 -193
  7. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.d.ts +11 -0
  8. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage1_format_parse/index.js +6 -0
  9. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.d.ts +16 -0
  10. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +17 -0
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.d.ts +5 -0
  12. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js +17 -0
  13. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.d.ts +19 -0
  14. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +269 -0
  15. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.d.ts +18 -0
  16. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +141 -0
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.d.ts +11 -0
  18. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_format_build/index.js +29 -0
  19. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.d.ts +16 -0
  20. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +15 -0
  21. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.d.ts +17 -0
  22. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage2_route_select/index.js +18 -0
  23. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.d.ts +17 -0
  24. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js +63 -0
  25. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.d.ts +11 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js +6 -0
  27. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.d.ts +12 -0
  28. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js +6 -0
  29. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.d.ts +13 -0
  30. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +43 -0
  31. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.d.ts +17 -0
  32. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js +22 -0
  33. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.d.ts +16 -0
  34. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js +19 -0
  35. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.d.ts +17 -0
  36. package/dist/conversion/hub/pipeline/stages/resp_process/resp_process_stage2_finalize/index.js +19 -0
  37. package/dist/conversion/hub/pipeline/stages/utils.d.ts +2 -0
  38. package/dist/conversion/hub/pipeline/stages/utils.js +11 -0
  39. package/dist/conversion/hub/pipeline/target-utils.d.ts +5 -0
  40. package/dist/conversion/hub/pipeline/target-utils.js +87 -0
  41. package/dist/conversion/hub/process/chat-process.js +23 -17
  42. package/dist/conversion/hub/response/provider-response.js +69 -122
  43. package/dist/conversion/hub/response/response-mappers.d.ts +19 -0
  44. package/dist/conversion/hub/response/response-mappers.js +22 -2
  45. package/dist/conversion/hub/response/response-runtime.d.ts +8 -0
  46. package/dist/conversion/hub/response/response-runtime.js +239 -6
  47. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +8 -0
  48. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +135 -55
  49. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +80 -40
  50. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +5 -29
  51. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +16 -13
  52. package/dist/conversion/hub/snapshot-recorder.d.ts +13 -0
  53. package/dist/conversion/hub/snapshot-recorder.js +90 -50
  54. package/dist/conversion/hub/standardized-bridge.js +49 -38
  55. package/dist/conversion/hub/types/chat-envelope.d.ts +68 -0
  56. package/dist/conversion/hub/types/standardized.d.ts +97 -0
  57. package/dist/conversion/pipeline/codecs/v2/anthropic-openai-pipeline.js +29 -2
  58. package/dist/conversion/pipeline/codecs/v2/responses-openai-pipeline.js +68 -1
  59. package/dist/conversion/responses/responses-openai-bridge.d.ts +6 -1
  60. package/dist/conversion/responses/responses-openai-bridge.js +132 -10
  61. package/dist/conversion/shared/anthropic-message-utils.d.ts +9 -1
  62. package/dist/conversion/shared/anthropic-message-utils.js +414 -26
  63. package/dist/conversion/shared/bridge-actions.js +267 -95
  64. package/dist/conversion/shared/bridge-message-utils.js +54 -8
  65. package/dist/conversion/shared/bridge-policies.js +21 -2
  66. package/dist/conversion/shared/chat-envelope-validator.d.ts +8 -0
  67. package/dist/conversion/shared/chat-envelope-validator.js +128 -0
  68. package/dist/conversion/shared/chat-request-filters.js +109 -28
  69. package/dist/conversion/shared/mcp-injection.js +41 -20
  70. package/dist/conversion/shared/openai-finalizer.d.ts +11 -0
  71. package/dist/conversion/shared/openai-finalizer.js +73 -0
  72. package/dist/conversion/shared/openai-message-normalize.js +32 -31
  73. package/dist/conversion/shared/protocol-state.d.ts +4 -0
  74. package/dist/conversion/shared/protocol-state.js +23 -0
  75. package/dist/conversion/shared/reasoning-normalizer.d.ts +1 -0
  76. package/dist/conversion/shared/reasoning-normalizer.js +50 -18
  77. package/dist/conversion/shared/responses-output-builder.d.ts +1 -1
  78. package/dist/conversion/shared/responses-output-builder.js +76 -25
  79. package/dist/conversion/shared/responses-reasoning-registry.d.ts +8 -0
  80. package/dist/conversion/shared/responses-reasoning-registry.js +61 -0
  81. package/dist/conversion/shared/responses-response-utils.js +32 -2
  82. package/dist/conversion/shared/responses-tool-utils.js +28 -2
  83. package/dist/conversion/shared/snapshot-hooks.d.ts +9 -0
  84. package/dist/conversion/shared/snapshot-hooks.js +60 -6
  85. package/dist/conversion/shared/snapshot-utils.d.ts +16 -0
  86. package/dist/conversion/shared/snapshot-utils.js +84 -0
  87. package/dist/conversion/shared/tool-filter-pipeline.js +46 -7
  88. package/dist/conversion/shared/tool-mapping.js +13 -2
  89. package/dist/filters/index.d.ts +18 -0
  90. package/dist/filters/index.js +0 -1
  91. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +13 -0
  92. package/dist/filters/special/request-streaming-to-nonstreaming.js +13 -1
  93. package/dist/filters/special/request-tool-choice-policy.js +3 -1
  94. package/dist/filters/special/request-tool-list-filter.d.ts +11 -0
  95. package/dist/filters/special/request-tool-list-filter.js +20 -7
  96. package/dist/sse/shared/responses-output-normalizer.js +5 -4
  97. package/dist/sse/sse-to-json/builders/response-builder.js +24 -1
  98. package/dist/sse/types/responses-types.d.ts +2 -0
  99. package/package.json +1 -1
@@ -0,0 +1,269 @@
1
+ import { captureResponsesContext, buildChatRequestFromResponses } from '../../../../../responses/responses-openai-bridge.js';
2
+ import { recordStage } from '../../../stages/utils.js';
3
+ export async function runReqInboundStage3ContextCapture(options) {
4
+ let context;
5
+ if (options.captureContext) {
6
+ try {
7
+ context = await options.captureContext({
8
+ rawRequest: options.rawRequest,
9
+ adapterContext: options.adapterContext
10
+ });
11
+ }
12
+ catch {
13
+ context = undefined;
14
+ }
15
+ }
16
+ const fallbackSnapshot = buildToolOutputSnapshot(options.rawRequest, options.adapterContext.providerProtocol);
17
+ const snapshot = context
18
+ ? augmentContextSnapshot(context, fallbackSnapshot)
19
+ : fallbackSnapshot;
20
+ recordStage(options.stageRecorder, 'req_inbound_stage3_context_capture', snapshot);
21
+ return context ?? snapshot;
22
+ }
23
+ export function runChatContextCapture(options) {
24
+ return runReqInboundStage3ContextCapture({
25
+ rawRequest: options.rawRequest,
26
+ adapterContext: options.adapterContext,
27
+ stageRecorder: options.stageRecorder,
28
+ captureContext: captureChatContextSnapshot
29
+ });
30
+ }
31
+ export function captureResponsesContextSnapshot(options) {
32
+ const context = captureResponsesContext(options.rawRequest, {
33
+ route: { requestId: options.adapterContext.requestId }
34
+ });
35
+ const style = normalizeToolCallIdStyleCandidate(options.adapterContext.toolCallIdStyle);
36
+ if (style) {
37
+ context.toolCallIdStyle = style;
38
+ if (!context.metadata || typeof context.metadata !== 'object') {
39
+ context.metadata = {};
40
+ }
41
+ context.metadata.toolCallIdStyle = style;
42
+ }
43
+ try {
44
+ const built = buildChatRequestFromResponses(options.rawRequest, context);
45
+ if (built.toolsNormalized) {
46
+ context.toolsNormalized = built.toolsNormalized;
47
+ }
48
+ const captured = collectToolOutputs(options.rawRequest);
49
+ if (captured.length) {
50
+ context.__captured_tool_results = captured;
51
+ }
52
+ }
53
+ catch {
54
+ // best-effort context capture
55
+ }
56
+ return context;
57
+ }
58
+ function captureChatContextSnapshot(options) {
59
+ return buildToolOutputSnapshot(options.rawRequest, options.adapterContext.providerProtocol);
60
+ }
61
+ function normalizeToolCallIdStyleCandidate(value) {
62
+ if (typeof value !== 'string') {
63
+ return undefined;
64
+ }
65
+ const normalized = value.trim().toLowerCase();
66
+ if (normalized === 'fc') {
67
+ return 'fc';
68
+ }
69
+ if (normalized === 'preserve') {
70
+ return 'preserve';
71
+ }
72
+ return undefined;
73
+ }
74
+ function augmentContextSnapshot(context, fallback) {
75
+ if (!fallback.tool_outputs || Array.isArray(context.tool_outputs)) {
76
+ return context;
77
+ }
78
+ if (!Array.isArray(context.tool_outputs) && Array.isArray(fallback.tool_outputs)) {
79
+ context.tool_outputs = fallback.tool_outputs;
80
+ }
81
+ return context;
82
+ }
83
+ function buildToolOutputSnapshot(payload, providerProtocol) {
84
+ const snapshot = {
85
+ providerProtocol: providerProtocol ?? 'unknown'
86
+ };
87
+ const toolOutputs = collectToolOutputs(payload);
88
+ if (toolOutputs.length) {
89
+ snapshot.tool_outputs = toolOutputs;
90
+ }
91
+ return snapshot;
92
+ }
93
+ function collectToolOutputs(payload) {
94
+ const aggregated = [];
95
+ const seen = new Set();
96
+ const append = (entry) => {
97
+ const id = (entry.tool_call_id || entry.call_id || '').trim();
98
+ if (!id) {
99
+ return;
100
+ }
101
+ if (seen.has(id)) {
102
+ return;
103
+ }
104
+ seen.add(id);
105
+ aggregated.push({
106
+ tool_call_id: entry.tool_call_id ?? entry.call_id,
107
+ call_id: entry.call_id ?? entry.tool_call_id,
108
+ output: entry.output,
109
+ name: entry.name
110
+ });
111
+ };
112
+ for (const record of readArray(payload, 'tool_outputs')) {
113
+ append(record);
114
+ }
115
+ for (const record of readRequiredActionOutputs(payload)) {
116
+ append(record);
117
+ }
118
+ for (const record of readMessageToolOutputs(payload)) {
119
+ append(record);
120
+ }
121
+ for (const record of readMessageContentToolOutputs(payload)) {
122
+ append(record);
123
+ }
124
+ for (const record of readResponsesInputToolOutputs(payload)) {
125
+ append(record);
126
+ }
127
+ return aggregated;
128
+ }
129
+ function readArray(payload, field) {
130
+ const raw = payload[field];
131
+ if (!Array.isArray(raw)) {
132
+ return [];
133
+ }
134
+ return raw
135
+ .map((entry) => normalizeToolOutputEntry(entry))
136
+ .filter((entry) => Boolean(entry));
137
+ }
138
+ function readRequiredActionOutputs(payload) {
139
+ const required = payload.required_action;
140
+ if (!required || typeof required !== 'object') {
141
+ return [];
142
+ }
143
+ const submit = required.submit_tool_outputs;
144
+ if (!submit || typeof submit !== 'object') {
145
+ return [];
146
+ }
147
+ return readArray(submit, 'tool_outputs');
148
+ }
149
+ function readMessageToolOutputs(payload) {
150
+ const messages = payload.messages;
151
+ if (!Array.isArray(messages)) {
152
+ return [];
153
+ }
154
+ return messages
155
+ .map((entry) => {
156
+ if (!entry || typeof entry !== 'object') {
157
+ return undefined;
158
+ }
159
+ const record = entry;
160
+ const role = String(record.role || '').toLowerCase();
161
+ if (role !== 'tool') {
162
+ return undefined;
163
+ }
164
+ return normalizeToolOutputEntry({
165
+ tool_call_id: record.tool_call_id ?? record.call_id ?? record.id,
166
+ call_id: record.call_id ?? record.tool_call_id ?? record.id,
167
+ name: typeof record.name === 'string' ? record.name : undefined,
168
+ output: record.content ?? record.output
169
+ });
170
+ })
171
+ .filter((entry) => Boolean(entry));
172
+ }
173
+ function readMessageContentToolOutputs(payload) {
174
+ const messages = payload.messages;
175
+ if (!Array.isArray(messages)) {
176
+ return [];
177
+ }
178
+ const outputs = [];
179
+ for (const entry of messages) {
180
+ if (!entry || typeof entry !== 'object') {
181
+ continue;
182
+ }
183
+ const record = entry;
184
+ const contentList = record.content;
185
+ if (!Array.isArray(contentList)) {
186
+ continue;
187
+ }
188
+ for (const block of contentList) {
189
+ if (!block || typeof block !== 'object') {
190
+ continue;
191
+ }
192
+ const blockRecord = block;
193
+ const type = typeof blockRecord.type === 'string' ? blockRecord.type.toLowerCase() : '';
194
+ if (type !== 'tool_result' && type !== 'function_call_output' && type !== 'tool_message') {
195
+ continue;
196
+ }
197
+ const normalized = normalizeToolOutputEntry({
198
+ tool_call_id: blockRecord.tool_use_id ?? blockRecord.tool_call_id ?? blockRecord.call_id ?? blockRecord.id,
199
+ call_id: blockRecord.tool_use_id ?? blockRecord.tool_call_id ?? blockRecord.call_id ?? blockRecord.id,
200
+ name: typeof blockRecord.name === 'string' ? blockRecord.name : undefined,
201
+ output: blockRecord.content ?? blockRecord.output
202
+ });
203
+ if (normalized) {
204
+ outputs.push(normalized);
205
+ }
206
+ }
207
+ }
208
+ return outputs;
209
+ }
210
+ function readResponsesInputToolOutputs(payload) {
211
+ const input = payload.input;
212
+ if (!Array.isArray(input)) {
213
+ return [];
214
+ }
215
+ return input
216
+ .map((entry) => {
217
+ if (!entry || typeof entry !== 'object') {
218
+ return undefined;
219
+ }
220
+ const record = entry;
221
+ const type = String(record.type || '').toLowerCase();
222
+ if (type !== 'tool_result' && type !== 'tool_message' && type !== 'function_call_output') {
223
+ return undefined;
224
+ }
225
+ return normalizeToolOutputEntry({
226
+ tool_call_id: record.tool_call_id ?? record.call_id ?? record.tool_use_id,
227
+ call_id: record.call_id ?? record.tool_call_id ?? record.tool_use_id,
228
+ name: typeof record.name === 'string' ? record.name : undefined,
229
+ output: record.output
230
+ });
231
+ })
232
+ .filter((entry) => Boolean(entry));
233
+ }
234
+ function normalizeToolOutputEntry(entry) {
235
+ if (!entry || typeof entry !== 'object') {
236
+ return undefined;
237
+ }
238
+ const record = entry;
239
+ const tool_call_id = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
240
+ ? record.tool_call_id.trim()
241
+ : undefined;
242
+ const call_id = typeof record.call_id === 'string' && record.call_id.trim().length
243
+ ? record.call_id.trim()
244
+ : undefined;
245
+ const id = tool_call_id ?? call_id;
246
+ if (!id) {
247
+ return undefined;
248
+ }
249
+ let output;
250
+ const rawOutput = 'output' in record ? record.output : record.content;
251
+ if (typeof rawOutput === 'string') {
252
+ output = rawOutput;
253
+ }
254
+ else if (rawOutput !== undefined) {
255
+ try {
256
+ output = JSON.stringify(rawOutput);
257
+ }
258
+ catch {
259
+ output = String(rawOutput);
260
+ }
261
+ }
262
+ const name = typeof record.name === 'string' && record.name.trim().length ? record.name.trim() : undefined;
263
+ return {
264
+ tool_call_id,
265
+ call_id,
266
+ output,
267
+ name
268
+ };
269
+ }
@@ -0,0 +1,18 @@
1
+ import type { AdapterContext, ChatEnvelope } from '../../../../types/chat-envelope.js';
2
+ import type { FormatEnvelope } from '../../../../types/format-envelope.js';
3
+ import type { JsonObject } from '../../../../types/json.js';
4
+ import type { StageRecorder, SemanticMapper } from '../../../../format-adapters/index.js';
5
+ import type { ProcessedRequest, StandardizedRequest } from '../../../../types/standardized.js';
6
+ export interface ReqOutboundStage1SemanticMapOptions {
7
+ request: StandardizedRequest | ProcessedRequest;
8
+ adapterContext: AdapterContext;
9
+ semanticMapper: Pick<SemanticMapper, 'fromChat'>;
10
+ contextSnapshot?: Record<string, unknown>;
11
+ contextMetadataKey?: string;
12
+ stageRecorder?: StageRecorder;
13
+ }
14
+ export interface ReqOutboundStage1SemanticMapResult {
15
+ chatEnvelope: ChatEnvelope;
16
+ formatEnvelope: FormatEnvelope<JsonObject>;
17
+ }
18
+ export declare function runReqOutboundStage1SemanticMap(options: ReqOutboundStage1SemanticMapOptions): Promise<ReqOutboundStage1SemanticMapResult>;
@@ -0,0 +1,141 @@
1
+ import { standardizedToChatEnvelope } from '../../../../standardized-bridge.js';
2
+ import { validateChatEnvelope } from '../../../../../shared/chat-envelope-validator.js';
3
+ import { recordStage } from '../../../stages/utils.js';
4
+ export async function runReqOutboundStage1SemanticMap(options) {
5
+ const chatEnvelope = standardizedToChatEnvelope(options.request, {
6
+ adapterContext: options.adapterContext
7
+ });
8
+ applyToolCallIdStyleMetadata(chatEnvelope, options.adapterContext, options.contextSnapshot);
9
+ if (options.contextSnapshot && options.contextMetadataKey) {
10
+ const snapshot = options.contextSnapshot;
11
+ chatEnvelope.metadata[options.contextMetadataKey] = snapshot;
12
+ attachToolOutputsFromContext(chatEnvelope, snapshot);
13
+ attachToolsFromContext(chatEnvelope, snapshot);
14
+ }
15
+ validateChatEnvelope(chatEnvelope, {
16
+ stage: 'req_outbound',
17
+ direction: 'request'
18
+ });
19
+ const formatEnvelope = (await options.semanticMapper.fromChat(chatEnvelope, options.adapterContext));
20
+ recordStage(options.stageRecorder, 'req_outbound_stage1_semantic_map', chatEnvelope);
21
+ return { chatEnvelope, formatEnvelope };
22
+ }
23
+ function attachToolOutputsFromContext(chatEnvelope, snapshot) {
24
+ const outputsRaw = snapshot?.tool_outputs;
25
+ if (!Array.isArray(outputsRaw) || !outputsRaw.length) {
26
+ return;
27
+ }
28
+ const normalized = outputsRaw
29
+ .map((entry) => normalizeToolOutput(entry))
30
+ .filter((entry) => Boolean(entry));
31
+ if (!normalized.length) {
32
+ return;
33
+ }
34
+ const existing = Array.isArray(chatEnvelope.toolOutputs) ? chatEnvelope.toolOutputs.slice() : [];
35
+ const known = new Set(existing.map((entry) => entry.tool_call_id).filter(Boolean));
36
+ for (const entry of normalized) {
37
+ if (entry.tool_call_id && !known.has(entry.tool_call_id)) {
38
+ existing.push(entry);
39
+ known.add(entry.tool_call_id);
40
+ }
41
+ }
42
+ if (existing.length) {
43
+ chatEnvelope.toolOutputs = existing;
44
+ }
45
+ }
46
+ function attachToolsFromContext(chatEnvelope, snapshot) {
47
+ if (Array.isArray(chatEnvelope.tools) && chatEnvelope.tools.length) {
48
+ return;
49
+ }
50
+ const normalized = snapshot?.toolsNormalized ?? snapshot?.tools;
51
+ if (!Array.isArray(normalized) || !normalized.length) {
52
+ return;
53
+ }
54
+ const tools = normalized
55
+ .map((entry) => normalizeToolDefinition(entry))
56
+ .filter((entry) => Boolean(entry));
57
+ if (tools.length) {
58
+ chatEnvelope.tools = tools;
59
+ }
60
+ }
61
+ function normalizeToolDefinition(entry) {
62
+ if (!entry || typeof entry !== 'object') {
63
+ return undefined;
64
+ }
65
+ const record = entry;
66
+ if (record.type !== 'function') {
67
+ return undefined;
68
+ }
69
+ const fn = record.function;
70
+ if (!fn || typeof fn !== 'object') {
71
+ return undefined;
72
+ }
73
+ const fnRecord = fn;
74
+ const name = typeof fnRecord.name === 'string' && fnRecord.name.trim().length ? fnRecord.name.trim() : undefined;
75
+ if (!name) {
76
+ return undefined;
77
+ }
78
+ return {
79
+ type: 'function',
80
+ function: {
81
+ name,
82
+ description: typeof fnRecord.description === 'string' ? fnRecord.description : undefined,
83
+ parameters: (fnRecord.parameters ?? { type: 'object', properties: {} }),
84
+ strict: typeof fnRecord.strict === 'boolean' ? fnRecord.strict : undefined
85
+ }
86
+ };
87
+ }
88
+ function normalizeToolOutput(entry) {
89
+ if (!entry || typeof entry !== 'object') {
90
+ return undefined;
91
+ }
92
+ const record = entry;
93
+ const toolCallId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
94
+ ? record.tool_call_id.trim()
95
+ : typeof record.call_id === 'string' && record.call_id.trim().length
96
+ ? record.call_id.trim()
97
+ : undefined;
98
+ if (!toolCallId) {
99
+ return undefined;
100
+ }
101
+ let content = '';
102
+ const rawOutput = 'output' in record ? record.output : record.content;
103
+ if (typeof rawOutput === 'string') {
104
+ content = rawOutput;
105
+ }
106
+ else if (rawOutput !== undefined) {
107
+ try {
108
+ content = JSON.stringify(rawOutput);
109
+ }
110
+ catch {
111
+ content = String(rawOutput);
112
+ }
113
+ }
114
+ const name = typeof record.name === 'string' && record.name.trim().length ? record.name.trim() : undefined;
115
+ return {
116
+ tool_call_id: toolCallId,
117
+ content,
118
+ name
119
+ };
120
+ }
121
+ function applyToolCallIdStyleMetadata(chatEnvelope, adapterContext, snapshot) {
122
+ const metadata = chatEnvelope.metadata || (chatEnvelope.metadata = { context: adapterContext });
123
+ const hasStyle = typeof metadata.toolCallIdStyle === 'string'
124
+ && String(metadata.toolCallIdStyle).trim().length > 0;
125
+ if (hasStyle) {
126
+ return;
127
+ }
128
+ const resolved = selectToolCallIdStyle(adapterContext, snapshot);
129
+ if (resolved) {
130
+ metadata.toolCallIdStyle = resolved;
131
+ }
132
+ }
133
+ function selectToolCallIdStyle(adapterContext, snapshot) {
134
+ if (adapterContext && typeof adapterContext.toolCallIdStyle === 'string' && adapterContext.toolCallIdStyle.trim().length) {
135
+ return adapterContext.toolCallIdStyle;
136
+ }
137
+ if (snapshot && typeof snapshot.toolCallIdStyle === 'string' && snapshot.toolCallIdStyle.trim().length) {
138
+ return String(snapshot.toolCallIdStyle);
139
+ }
140
+ return undefined;
141
+ }
@@ -0,0 +1,11 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { FormatEnvelope } from '../../../../types/format-envelope.js';
3
+ import type { JsonObject } from '../../../../types/json.js';
4
+ import type { FormatAdapter, StageRecorder } from '../../../../format-adapters/index.js';
5
+ export interface ReqOutboundStage2FormatBuildOptions {
6
+ formatEnvelope: FormatEnvelope<JsonObject>;
7
+ adapterContext: AdapterContext;
8
+ formatAdapter: Pick<FormatAdapter, 'buildResponse'>;
9
+ stageRecorder?: StageRecorder;
10
+ }
11
+ export declare function runReqOutboundStage2FormatBuild(options: ReqOutboundStage2FormatBuildOptions): Promise<JsonObject>;
@@ -0,0 +1,29 @@
1
+ import { recordStage } from '../../../stages/utils.js';
2
+ const PRIVATE_FIELD_PREFIX = '__rcc_';
3
+ export async function runReqOutboundStage2FormatBuild(options) {
4
+ const payload = (await options.formatAdapter.buildResponse(options.formatEnvelope, options.adapterContext));
5
+ stripPrivateFields(payload);
6
+ recordStage(options.stageRecorder, 'req_outbound_stage2_format_build', payload);
7
+ return payload;
8
+ }
9
+ function stripPrivateFields(value) {
10
+ if (!value) {
11
+ return;
12
+ }
13
+ if (Array.isArray(value)) {
14
+ value.forEach((entry) => stripPrivateFields(entry));
15
+ return;
16
+ }
17
+ if (typeof value !== 'object') {
18
+ return;
19
+ }
20
+ const record = value;
21
+ for (const key of Object.keys(record)) {
22
+ const child = record[key];
23
+ if (typeof key === 'string' && key.startsWith(PRIVATE_FIELD_PREFIX)) {
24
+ delete record[key];
25
+ continue;
26
+ }
27
+ stripPrivateFields(child);
28
+ }
29
+ }
@@ -0,0 +1,16 @@
1
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
2
+ import type { ProcessedRequest, StandardizedRequest } from '../../../../types/standardized.js';
3
+ import { runHubChatProcess } from '../../../../process/chat-process.js';
4
+ export interface ReqProcessStage1ToolGovernanceOptions {
5
+ request: StandardizedRequest;
6
+ rawPayload: Record<string, unknown>;
7
+ metadata: Record<string, unknown>;
8
+ entryEndpoint: string;
9
+ requestId: string;
10
+ stageRecorder?: StageRecorder;
11
+ }
12
+ export interface ReqProcessStage1ToolGovernanceResult {
13
+ processedRequest?: ProcessedRequest;
14
+ nodeResult?: Awaited<ReturnType<typeof runHubChatProcess>>['nodeResult'];
15
+ }
16
+ export declare function runReqProcessStage1ToolGovernance(options: ReqProcessStage1ToolGovernanceOptions): Promise<ReqProcessStage1ToolGovernanceResult>;
@@ -0,0 +1,15 @@
1
+ import { runHubChatProcess } from '../../../../process/chat-process.js';
2
+ import { recordStage } from '../../../stages/utils.js';
3
+ export async function runReqProcessStage1ToolGovernance(options) {
4
+ const result = await runHubChatProcess({
5
+ request: options.request,
6
+ rawPayload: options.rawPayload,
7
+ metadata: options.metadata,
8
+ entryEndpoint: options.entryEndpoint,
9
+ requestId: options.requestId
10
+ });
11
+ if (result.processedRequest) {
12
+ recordStage(options.stageRecorder, 'req_process_stage1_tool_governance', result.processedRequest);
13
+ }
14
+ return result;
15
+ }
@@ -0,0 +1,17 @@
1
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
2
+ import type { ProcessedRequest, StandardizedRequest } from '../../../../types/standardized.js';
3
+ import type { RouterMetadataInput, RoutingDecision, RoutingDiagnostics, TargetMetadata } from '../../../../../../router/virtual-router/types.js';
4
+ import { VirtualRouterEngine } from '../../../../../../router/virtual-router/engine.js';
5
+ export interface ReqProcessStage2RouteSelectOptions {
6
+ routerEngine: VirtualRouterEngine;
7
+ request: StandardizedRequest | ProcessedRequest;
8
+ metadataInput: RouterMetadataInput;
9
+ normalizedMetadata: Record<string, unknown>;
10
+ stageRecorder?: StageRecorder;
11
+ }
12
+ export interface ReqProcessStage2RouteSelectResult {
13
+ target: TargetMetadata;
14
+ decision: RoutingDecision;
15
+ diagnostics: RoutingDiagnostics;
16
+ }
17
+ export declare function runReqProcessStage2RouteSelect(options: ReqProcessStage2RouteSelectOptions): ReqProcessStage2RouteSelectResult;
@@ -0,0 +1,18 @@
1
+ import { recordStage } from '../../../stages/utils.js';
2
+ import { applyTargetMetadata, applyTargetToSubject } from '../../../target-utils.js';
3
+ export function runReqProcessStage2RouteSelect(options) {
4
+ const previousModel = typeof options.request.model === 'string' ? options.request.model : undefined;
5
+ const result = options.routerEngine.route(options.request, options.metadataInput);
6
+ applyTargetMetadata(options.normalizedMetadata, result.target, result.decision.routeName, previousModel);
7
+ applyTargetToSubject(options.request, result.target, previousModel);
8
+ recordStage(options.stageRecorder, 'req_process_stage2_route_select', {
9
+ target: result.target,
10
+ decision: result.decision,
11
+ diagnostics: result.diagnostics
12
+ });
13
+ return {
14
+ target: result.target,
15
+ decision: result.decision,
16
+ diagnostics: result.diagnostics
17
+ };
18
+ }
@@ -0,0 +1,17 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { JsonObject } from '../../../../types/json.js';
3
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
4
+ type ProviderProtocol = 'openai-chat' | 'openai-responses' | 'anthropic-messages' | 'gemini-chat';
5
+ export interface RespInboundStage1SseDecodeOptions {
6
+ providerProtocol: ProviderProtocol;
7
+ payload: JsonObject;
8
+ adapterContext: AdapterContext;
9
+ wantsStream: boolean;
10
+ stageRecorder?: StageRecorder;
11
+ }
12
+ export interface RespInboundStage1SseDecodeResult {
13
+ payload: JsonObject;
14
+ decodedFromSse: boolean;
15
+ }
16
+ export declare function runRespInboundStage1SseDecode(options: RespInboundStage1SseDecodeOptions): Promise<RespInboundStage1SseDecodeResult>;
17
+ export {};
@@ -0,0 +1,63 @@
1
+ import { defaultSseCodecRegistry } from '../../../../../../sse/index.js';
2
+ import { recordStage } from '../../../stages/utils.js';
3
+ export async function runRespInboundStage1SseDecode(options) {
4
+ const stream = extractSseStream(options.payload);
5
+ if (!stream) {
6
+ recordStage(options.stageRecorder, 'resp_inbound_stage1_sse_decode', {
7
+ streamDetected: false
8
+ });
9
+ return { payload: options.payload, decodedFromSse: false };
10
+ }
11
+ if (!supportsSseProtocol(options.providerProtocol)) {
12
+ recordStage(options.stageRecorder, 'resp_inbound_stage1_sse_decode', {
13
+ streamDetected: true,
14
+ decoded: false,
15
+ reason: 'protocol_unsupported',
16
+ protocol: options.providerProtocol
17
+ });
18
+ throw new Error(`[resp_inbound_stage1_sse_decode] Protocol ${options.providerProtocol} does not support SSE decoding`);
19
+ }
20
+ try {
21
+ const codec = defaultSseCodecRegistry.get(options.providerProtocol);
22
+ const decoded = (await codec.convertSseToJson(stream, {
23
+ requestId: options.adapterContext.requestId,
24
+ model: options.adapterContext.modelId
25
+ }));
26
+ recordStage(options.stageRecorder, 'resp_inbound_stage1_sse_decode', {
27
+ streamDetected: true,
28
+ decoded: true,
29
+ protocol: options.providerProtocol
30
+ });
31
+ return { payload: decoded, decodedFromSse: true };
32
+ }
33
+ catch (error) {
34
+ const message = error instanceof Error ? error.message : String(error);
35
+ recordStage(options.stageRecorder, 'resp_inbound_stage1_sse_decode', {
36
+ streamDetected: true,
37
+ decoded: false,
38
+ protocol: options.providerProtocol,
39
+ error: message
40
+ });
41
+ throw new Error(`[resp_inbound_stage1_sse_decode] Failed to decode SSE payload for protocol ${options.providerProtocol}: ${message}`);
42
+ }
43
+ }
44
+ function supportsSseProtocol(protocol) {
45
+ return protocol === 'openai-chat' || protocol === 'openai-responses' || protocol === 'anthropic-messages' || protocol === 'gemini-chat';
46
+ }
47
+ function extractSseStream(payload) {
48
+ if (!payload || typeof payload !== 'object') {
49
+ return undefined;
50
+ }
51
+ const direct = payload.__sse_responses || payload.__sse_stream;
52
+ if (direct && typeof direct.pipe === 'function') {
53
+ return direct;
54
+ }
55
+ const nested = payload.data;
56
+ if (nested && typeof nested === 'object') {
57
+ const inner = nested.__sse_responses || nested.__sse_stream;
58
+ if (inner && typeof inner.pipe === 'function') {
59
+ return inner;
60
+ }
61
+ }
62
+ return undefined;
63
+ }
@@ -0,0 +1,11 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { FormatEnvelope } from '../../../../types/format-envelope.js';
3
+ import type { JsonObject } from '../../../../types/json.js';
4
+ import type { FormatAdapter, StageRecorder } from '../../../../format-adapters/index.js';
5
+ export interface RespInboundStage2FormatParseOptions {
6
+ adapterContext: AdapterContext;
7
+ payload: JsonObject;
8
+ formatAdapter: Pick<FormatAdapter, 'parseResponse'>;
9
+ stageRecorder?: StageRecorder;
10
+ }
11
+ export declare function runRespInboundStage2FormatParse(options: RespInboundStage2FormatParseOptions): Promise<FormatEnvelope<JsonObject>>;
@@ -0,0 +1,6 @@
1
+ import { recordStage } from '../../../stages/utils.js';
2
+ export async function runRespInboundStage2FormatParse(options) {
3
+ const envelope = (await options.formatAdapter.parseResponse(options.payload, options.adapterContext));
4
+ recordStage(options.stageRecorder, 'resp_inbound_stage2_format_parse', envelope);
5
+ return envelope;
6
+ }
@@ -0,0 +1,12 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { FormatEnvelope } from '../../../../types/format-envelope.js';
3
+ import type { JsonObject } from '../../../../types/json.js';
4
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
5
+ import type { ResponseMapper, ChatCompletionLike } from '../../../../response/response-mappers.js';
6
+ export interface RespInboundStage3SemanticMapOptions {
7
+ adapterContext: AdapterContext;
8
+ formatEnvelope: FormatEnvelope<JsonObject>;
9
+ mapper: ResponseMapper;
10
+ stageRecorder?: StageRecorder;
11
+ }
12
+ export declare function runRespInboundStage3SemanticMap(options: RespInboundStage3SemanticMapOptions): Promise<ChatCompletionLike>;
@@ -0,0 +1,6 @@
1
+ import { recordStage } from '../../../stages/utils.js';
2
+ export async function runRespInboundStage3SemanticMap(options) {
3
+ const chatResponse = await options.mapper.toChatCompletion(options.formatEnvelope, options.adapterContext);
4
+ recordStage(options.stageRecorder, 'resp_inbound_stage3_semantic_map', chatResponse);
5
+ return chatResponse;
6
+ }