@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
@@ -1,8 +1,23 @@
1
1
  import { Readable } from 'node:stream';
2
- import { runHubInboundConversion, runHubOutboundConversion } from '../node-support.js';
3
2
  import { VirtualRouterEngine } from '../../../router/virtual-router/engine.js';
4
3
  import { defaultSseCodecRegistry } from '../../../sse/index.js';
5
- import { runHubChatProcess } from '../process/chat-process.js';
4
+ import { ResponsesFormatAdapter } from '../format-adapters/responses-format-adapter.js';
5
+ import { ResponsesSemanticMapper } from '../semantic-mappers/responses-mapper.js';
6
+ import { AnthropicFormatAdapter } from '../format-adapters/anthropic-format-adapter.js';
7
+ import { AnthropicSemanticMapper } from '../semantic-mappers/anthropic-mapper.js';
8
+ import { GeminiFormatAdapter } from '../format-adapters/gemini-format-adapter.js';
9
+ import { GeminiSemanticMapper } from '../semantic-mappers/gemini-mapper.js';
10
+ import { ChatFormatAdapter } from '../format-adapters/chat-format-adapter.js';
11
+ import { ChatSemanticMapper } from '../semantic-mappers/chat-mapper.js';
12
+ import { createSnapshotRecorder } from '../snapshot-recorder.js';
13
+ import { runReqInboundStage1FormatParse } from './stages/req_inbound/req_inbound_stage1_format_parse/index.js';
14
+ import { runReqInboundStage2SemanticMap } from './stages/req_inbound/req_inbound_stage2_semantic_map/index.js';
15
+ import { runChatContextCapture, captureResponsesContextSnapshot } from './stages/req_inbound/req_inbound_stage3_context_capture/index.js';
16
+ import { createResponsesContextCapture, createNoopContextCapture } from './stages/req_inbound/req_inbound_stage3_context_capture/context-factories.js';
17
+ import { runReqProcessStage1ToolGovernance } from './stages/req_process/req_process_stage1_tool_governance/index.js';
18
+ import { runReqProcessStage2RouteSelect } from './stages/req_process/req_process_stage2_route_select/index.js';
19
+ import { runReqOutboundStage1SemanticMap } from './stages/req_outbound/req_outbound_stage1_semantic_map/index.js';
20
+ import { runReqOutboundStage2FormatBuild } from './stages/req_outbound/req_outbound_stage2_format_build/index.js';
6
21
  export class HubPipeline {
7
22
  routerEngine;
8
23
  config;
@@ -18,23 +33,128 @@ export class HubPipeline {
18
33
  this.config.virtualRouter = nextConfig;
19
34
  this.routerEngine.initialize(nextConfig);
20
35
  }
21
- async execute(request) {
22
- const normalized = await this.normalizeRequest(request);
36
+ async executeRequestStagePipeline(normalized, hooks) {
37
+ const formatAdapter = hooks.createFormatAdapter();
38
+ const semanticMapper = hooks.createSemanticMapper();
39
+ const inboundAdapterContext = this.buildAdapterContext(normalized);
40
+ const inboundRecorder = this.maybeCreateStageRecorder(inboundAdapterContext, normalized.entryEndpoint);
41
+ const rawRequest = this.asJsonObject(normalized.payload);
42
+ const inboundStart = Date.now();
43
+ const formatEnvelope = await runReqInboundStage1FormatParse({
44
+ rawRequest,
45
+ adapterContext: inboundAdapterContext,
46
+ formatAdapter,
47
+ stageRecorder: inboundRecorder
48
+ });
49
+ const inboundStage2 = await runReqInboundStage2SemanticMap({
50
+ adapterContext: inboundAdapterContext,
51
+ formatEnvelope,
52
+ semanticMapper,
53
+ stageRecorder: inboundRecorder
54
+ });
55
+ this.captureAnthropicAliasMap(normalized, inboundAdapterContext, inboundStage2.chatEnvelope);
56
+ const contextSnapshot = await hooks.captureContext({
57
+ rawRequest,
58
+ adapterContext: inboundAdapterContext,
59
+ stageRecorder: inboundRecorder
60
+ });
61
+ const standardizedRequest = inboundStage2.standardizedRequest;
62
+ const inboundEnd = Date.now();
23
63
  const nodeResults = [];
24
- const inbound = await this.runInboundStage(normalized);
25
- nodeResults.push(inbound.nodeResult);
26
- const process = await this.runProcessStage(normalized, inbound.standardizedRequest);
27
- if (process.nodeResult) {
28
- nodeResults.push(process.nodeResult);
64
+ nodeResults.push({
65
+ id: 'req_inbound',
66
+ success: true,
67
+ metadata: {
68
+ node: 'req_inbound',
69
+ executionTime: inboundEnd - inboundStart,
70
+ startTime: inboundStart,
71
+ endTime: inboundEnd,
72
+ dataProcessed: {
73
+ messages: standardizedRequest.messages.length,
74
+ tools: standardizedRequest.tools?.length ?? 0
75
+ }
76
+ }
77
+ });
78
+ let processedRequest;
79
+ if (normalized.processMode !== 'passthrough') {
80
+ const processResult = await runReqProcessStage1ToolGovernance({
81
+ request: standardizedRequest,
82
+ rawPayload: rawRequest,
83
+ metadata: normalized.metadata,
84
+ entryEndpoint: normalized.entryEndpoint,
85
+ requestId: normalized.id,
86
+ stageRecorder: inboundRecorder
87
+ });
88
+ processedRequest = processResult.processedRequest;
89
+ if (processResult.nodeResult) {
90
+ nodeResults.push(this.convertProcessNodeResult('req_process_stage1_tool_governance', processResult.nodeResult));
91
+ }
92
+ }
93
+ const workingRequest = processedRequest ?? standardizedRequest;
94
+ const metadataInput = {
95
+ requestId: normalized.id,
96
+ entryEndpoint: normalized.entryEndpoint,
97
+ processMode: normalized.processMode,
98
+ stream: normalized.stream,
99
+ direction: normalized.direction,
100
+ providerProtocol: normalized.providerProtocol,
101
+ routeHint: normalized.routeHint,
102
+ stage: normalized.stage
103
+ };
104
+ const routing = runReqProcessStage2RouteSelect({
105
+ routerEngine: this.routerEngine,
106
+ request: workingRequest,
107
+ metadataInput,
108
+ normalizedMetadata: normalized.metadata,
109
+ stageRecorder: inboundRecorder
110
+ });
111
+ const outboundAdapterContext = this.buildAdapterContext(normalized, routing.target);
112
+ const outboundProtocol = outboundAdapterContext.providerProtocol;
113
+ const protocolSwitch = outboundProtocol !== normalized.providerProtocol;
114
+ const outboundHooks = protocolSwitch ? this.resolveProtocolHooks(outboundProtocol) : hooks;
115
+ if (!outboundHooks) {
116
+ throw new Error(`[HubPipeline] Unsupported provider protocol for hub pipeline: ${outboundProtocol}`);
29
117
  }
30
- const processedRequest = process.processedRequest;
31
- const routing = this.routeRequest(processedRequest ?? inbound.standardizedRequest, normalized);
32
- const outbound = await this.runOutboundStage(processedRequest ?? inbound.standardizedRequest, normalized, routing.target);
33
- nodeResults.push(outbound.nodeResult);
118
+ const outboundSemanticMapper = protocolSwitch ? outboundHooks.createSemanticMapper() : semanticMapper;
119
+ const outboundFormatAdapter = protocolSwitch ? outboundHooks.createFormatAdapter() : formatAdapter;
120
+ const outboundContextMetadataKey = protocolSwitch ? outboundHooks.contextMetadataKey : hooks.contextMetadataKey;
121
+ const outboundContextSnapshot = protocolSwitch ? undefined : contextSnapshot;
122
+ const outboundEndpoint = resolveEndpointForProviderProtocol(outboundAdapterContext.providerProtocol);
123
+ const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, outboundEndpoint);
124
+ const outboundStart = Date.now();
125
+ const outboundStage1 = await runReqOutboundStage1SemanticMap({
126
+ request: workingRequest,
127
+ adapterContext: outboundAdapterContext,
128
+ semanticMapper: outboundSemanticMapper,
129
+ contextSnapshot: outboundContextSnapshot,
130
+ contextMetadataKey: outboundContextMetadataKey,
131
+ stageRecorder: outboundRecorder
132
+ });
133
+ const providerPayload = await runReqOutboundStage2FormatBuild({
134
+ formatEnvelope: outboundStage1.formatEnvelope,
135
+ adapterContext: outboundAdapterContext,
136
+ formatAdapter: outboundFormatAdapter,
137
+ stageRecorder: outboundRecorder
138
+ });
139
+ const outboundEnd = Date.now();
140
+ nodeResults.push({
141
+ id: 'req_outbound',
142
+ success: true,
143
+ metadata: {
144
+ node: 'req_outbound',
145
+ executionTime: outboundEnd - outboundStart,
146
+ startTime: outboundStart,
147
+ endTime: outboundEnd,
148
+ dataProcessed: {
149
+ messages: workingRequest.messages.length,
150
+ tools: workingRequest.tools?.length ?? 0
151
+ }
152
+ }
153
+ });
34
154
  const metadata = {
35
155
  ...normalized.metadata,
36
156
  entryEndpoint: normalized.entryEndpoint,
37
- providerProtocol: normalized.providerProtocol,
157
+ providerProtocol: outboundProtocol,
38
158
  stream: normalized.stream,
39
159
  processMode: normalized.processMode,
40
160
  routeHint: normalized.routeHint,
@@ -42,8 +162,8 @@ export class HubPipeline {
42
162
  };
43
163
  return {
44
164
  requestId: normalized.id,
45
- providerPayload: outbound.payload,
46
- standardizedRequest: inbound.standardizedRequest,
165
+ providerPayload,
166
+ standardizedRequest,
47
167
  processedRequest,
48
168
  routingDecision: routing.decision,
49
169
  routingDiagnostics: routing.diagnostics,
@@ -52,181 +172,125 @@ export class HubPipeline {
52
172
  nodeResults
53
173
  };
54
174
  }
55
- async runInboundStage(normalized) {
56
- const hubContext = this.createHubNodeContext(normalized);
57
- const inbound = await runHubInboundConversion({
58
- protocol: normalized.providerProtocol,
59
- rawRequest: normalized.payload,
60
- nodeContext: hubContext,
61
- nodeId: 'hub-inbound',
62
- inputFormat: normalized.entryEndpoint,
63
- outputFormat: 'standardized-request',
64
- startTime: Date.now()
65
- });
66
- const standardized = inbound.data?.standardizedRequest;
67
- if (!standardized) {
68
- throw new Error('Hub inbound pipeline did not return a standardized request');
69
- }
70
- return {
71
- standardizedRequest: standardized,
72
- nodeResult: this.convertHubNodeResult('hub-inbound', inbound)
73
- };
74
- }
75
- async runProcessStage(normalized, standardizedRequest) {
76
- if (normalized.processMode === 'passthrough') {
77
- return {};
175
+ async execute(request) {
176
+ const normalized = await this.normalizeRequest(request);
177
+ const hooks = this.resolveProtocolHooks(normalized.providerProtocol);
178
+ if (!hooks) {
179
+ throw new Error(`Unsupported provider protocol for hub pipeline: ${normalized.providerProtocol}`);
78
180
  }
79
- const processResult = await runHubChatProcess({
80
- request: standardizedRequest,
81
- requestId: normalized.id,
82
- entryEndpoint: normalized.entryEndpoint,
83
- rawPayload: normalized.payload,
84
- metadata: normalized.metadata
85
- });
86
- return {
87
- processedRequest: processResult.processedRequest,
88
- nodeResult: this.convertProcessNodeResult('hub-chat-process', processResult.nodeResult)
89
- };
181
+ return await this.executeRequestStagePipeline(normalized, hooks);
90
182
  }
91
- routeRequest(request, normalized) {
92
- const metadata = {
93
- requestId: normalized.id,
94
- entryEndpoint: normalized.entryEndpoint,
95
- processMode: normalized.processMode,
96
- stream: normalized.stream,
97
- direction: normalized.direction,
98
- providerProtocol: normalized.providerProtocol,
99
- routeHint: normalized.routeHint,
100
- stage: normalized.stage
101
- };
102
- const previousModel = typeof request?.model === 'string'
103
- ? request.model
104
- : undefined;
105
- const result = this.routerEngine.route(request, metadata);
106
- this.applyTargetMetadata(normalized.metadata, result.target, result.decision.routeName, previousModel);
107
- this.applyTargetToSubject(request, result.target, previousModel);
108
- return {
109
- target: result.target,
110
- decision: result.decision,
111
- diagnostics: result.diagnostics
112
- };
113
- }
114
- applyTargetMetadata(metadata, target, routeName, originalModel) {
115
- if (!metadata || typeof metadata !== 'object')
183
+ captureAnthropicAliasMap(normalized, adapterContext, chatEnvelope) {
184
+ if (!this.shouldCaptureAnthropicAlias(normalized.entryEndpoint)) {
116
185
  return;
117
- metadata.routeName = routeName;
118
- metadata.pipelineId = target.providerKey;
119
- metadata.target = target;
120
- metadata.providerKey = target.providerKey;
121
- metadata.providerType = target.providerType;
122
- metadata.modelId = target.modelId;
123
- metadata.processMode = target.processMode || 'chat';
124
- if (target.responsesConfig?.toolCallIdStyle) {
125
- metadata.toolCallIdStyle = target.responsesConfig.toolCallIdStyle;
126
186
  }
127
- if (originalModel && typeof originalModel === 'string' && originalModel.trim()) {
128
- const trimmed = originalModel.trim();
129
- if (typeof metadata.originalModelId !== 'string' || !metadata.originalModelId) {
130
- metadata.originalModelId = trimmed;
131
- }
132
- if (typeof metadata.clientModelId !== 'string' || !metadata.clientModelId) {
133
- metadata.clientModelId = trimmed;
134
- }
187
+ const aliasMap = this.resolveAliasMapFromSources(adapterContext, chatEnvelope);
188
+ if (!aliasMap) {
189
+ return;
135
190
  }
136
- if (typeof target.modelId === 'string' && target.modelId.trim()) {
137
- metadata.assignedModelId = target.modelId.trim();
191
+ normalized.metadata = normalized.metadata || {};
192
+ if (!normalized.metadata.anthropicToolNameMap) {
193
+ normalized.metadata.anthropicToolNameMap = aliasMap;
138
194
  }
139
195
  }
140
- applyTargetToSubject(subject, target, originalModel) {
141
- if (!subject || typeof subject !== 'object')
142
- return;
143
- const newModel = this.extractModelFromTarget(target);
144
- if (!newModel)
145
- return;
146
- subject.model = newModel;
147
- if (subject.parameters && typeof subject.parameters === 'object') {
148
- subject.parameters = {
149
- ...subject.parameters,
150
- model: newModel
151
- };
196
+ shouldCaptureAnthropicAlias(endpoint) {
197
+ return typeof endpoint === 'string' && endpoint.toLowerCase().includes('/v1/messages');
198
+ }
199
+ resolveAliasMapFromSources(adapterContext, chatEnvelope) {
200
+ const fromContext = coerceAliasMap(adapterContext.anthropicToolNameMap);
201
+ if (fromContext) {
202
+ return fromContext;
203
+ }
204
+ const metadataNode = chatEnvelope.metadata;
205
+ const direct = metadataNode ? coerceAliasMap(metadataNode.anthropicToolNameMap) : undefined;
206
+ if (direct) {
207
+ return direct;
152
208
  }
153
- else {
154
- subject.parameters = { model: newModel };
209
+ const contextNode = metadataNode && metadataNode.context && typeof metadataNode.context === 'object'
210
+ ? metadataNode.context
211
+ : undefined;
212
+ return coerceAliasMap(contextNode?.anthropicToolNameMap);
213
+ }
214
+ resolveProtocolHooks(protocol) {
215
+ switch (protocol) {
216
+ case 'openai-chat':
217
+ return {
218
+ createFormatAdapter: () => new ChatFormatAdapter(),
219
+ createSemanticMapper: () => new ChatSemanticMapper(),
220
+ captureContext: (options) => runChatContextCapture(options),
221
+ contextMetadataKey: 'chatContext'
222
+ };
223
+ case 'openai-responses':
224
+ return {
225
+ createFormatAdapter: () => new ResponsesFormatAdapter(),
226
+ createSemanticMapper: () => new ResponsesSemanticMapper(),
227
+ captureContext: createResponsesContextCapture(captureResponsesContextSnapshot),
228
+ contextMetadataKey: 'responsesContext'
229
+ };
230
+ case 'anthropic-messages':
231
+ return {
232
+ createFormatAdapter: () => new AnthropicFormatAdapter(),
233
+ createSemanticMapper: () => new AnthropicSemanticMapper(),
234
+ captureContext: (options) => runChatContextCapture(options),
235
+ contextMetadataKey: 'anthropicContext'
236
+ };
237
+ case 'gemini-chat':
238
+ return {
239
+ createFormatAdapter: () => new GeminiFormatAdapter(),
240
+ createSemanticMapper: () => new GeminiSemanticMapper(),
241
+ captureContext: createNoopContextCapture('gemini-chat')
242
+ };
243
+ default:
244
+ return undefined;
155
245
  }
156
- subject.metadata = {
157
- ...subject.metadata,
158
- providerKey: target.providerKey,
159
- providerType: target.providerType,
160
- processMode: target.processMode || 'chat'
246
+ }
247
+ buildAdapterContext(normalized, target) {
248
+ const metadata = normalized.metadata || {};
249
+ const providerProtocol = target?.outboundProfile || normalized.providerProtocol;
250
+ const providerId = (target?.providerKey || metadata.providerKey);
251
+ const routeId = metadata.routeName;
252
+ const profileId = (target?.providerKey || metadata.pipelineId);
253
+ const streamingHint = normalized.stream === true ? 'force' : normalized.stream === false ? 'disable' : 'auto';
254
+ const toolCallIdStyle = normalizeToolCallIdStyleCandidate(metadata.toolCallIdStyle);
255
+ const adapterContext = {
256
+ requestId: normalized.id,
257
+ entryEndpoint: normalized.entryEndpoint || '/v1/chat/completions',
258
+ providerProtocol,
259
+ providerId,
260
+ routeId,
261
+ profileId,
262
+ streamingHint,
263
+ toolCallIdStyle
161
264
  };
162
- if (!subject.metadata || typeof subject.metadata !== 'object') {
163
- subject.metadata = {};
265
+ if (typeof metadata.originalModelId === 'string') {
266
+ adapterContext.originalModelId = metadata.originalModelId;
164
267
  }
165
- const subjectMeta = subject.metadata;
166
- if (originalModel && typeof originalModel === 'string' && originalModel.trim()) {
167
- const trimmed = originalModel.trim();
168
- if (typeof subjectMeta.originalModelId !== 'string' || !subjectMeta.originalModelId) {
169
- subjectMeta.originalModelId = trimmed;
170
- }
171
- if (typeof subjectMeta.clientModelId !== 'string' || !subjectMeta.clientModelId) {
172
- subjectMeta.clientModelId = trimmed;
173
- }
268
+ if (typeof metadata.clientModelId === 'string') {
269
+ adapterContext.clientModelId = metadata.clientModelId;
174
270
  }
175
- subjectMeta.assignedModelId = newModel;
176
- }
177
- extractModelFromTarget(target) {
178
- if (!target || typeof target.providerKey !== 'string') {
179
- return null;
271
+ if (typeof metadata.assignedModelId === 'string') {
272
+ adapterContext.modelId = metadata.assignedModelId;
180
273
  }
181
- if (typeof target.modelId === 'string' && target.modelId.trim()) {
182
- return target.modelId.trim();
274
+ return adapterContext;
275
+ }
276
+ maybeCreateStageRecorder(context, endpoint) {
277
+ const flag = (process.env.ROUTECODEX_HUB_SNAPSHOTS || '').trim();
278
+ if (flag === '0') {
279
+ return undefined;
183
280
  }
184
- if (typeof target.runtimeKey === 'string' && target.runtimeKey.length) {
185
- const prefix = `${target.runtimeKey}.`;
186
- if (target.providerKey.startsWith(prefix)) {
187
- const candidate = target.providerKey.slice(prefix.length).trim();
188
- if (candidate.length) {
189
- return candidate;
190
- }
191
- }
281
+ const effectiveEndpoint = endpoint || context.entryEndpoint || '/v1/chat/completions';
282
+ try {
283
+ return createSnapshotRecorder(context, effectiveEndpoint);
192
284
  }
193
- const firstDot = target.providerKey.indexOf('.');
194
- if (firstDot <= 0 || firstDot === target.providerKey.length - 1) {
195
- return null;
285
+ catch {
286
+ return undefined;
196
287
  }
197
- const fallback = target.providerKey.slice(firstDot + 1).trim();
198
- return fallback.length ? fallback : null;
199
288
  }
200
- async runOutboundStage(request, normalized, target) {
201
- const outboundProtocol = target?.outboundProfile || normalized.providerProtocol;
202
- const outboundContext = this.createHubNodeContext({
203
- ...normalized,
204
- metadata: {
205
- ...normalized.metadata,
206
- target
207
- }
208
- });
209
- const start = Date.now();
210
- const payload = await runHubOutboundConversion({
211
- protocol: outboundProtocol,
212
- request,
213
- nodeContext: outboundContext
214
- });
215
- const nodeResult = {
216
- id: 'hub-outbound',
217
- success: true,
218
- metadata: {
219
- node: 'hub-outbound',
220
- executionTime: Date.now() - start,
221
- startTime: start,
222
- endTime: Date.now(),
223
- dataProcessed: {
224
- messages: request.messages.length,
225
- tools: request.tools?.length ?? 0
226
- }
227
- }
228
- };
229
- return { payload, nodeResult };
289
+ asJsonObject(value) {
290
+ if (!value || typeof value !== 'object') {
291
+ throw new Error('Responses pipeline requires JSON object payload');
292
+ }
293
+ return value;
230
294
  }
231
295
  async normalizeRequest(request) {
232
296
  if (!request || typeof request !== 'object') {
@@ -281,25 +345,6 @@ export class HubPipeline {
281
345
  routeHint
282
346
  };
283
347
  }
284
- createHubNodeContext(normalized) {
285
- return {
286
- request: {
287
- id: normalized.id,
288
- endpoint: normalized.entryEndpoint,
289
- context: {
290
- metadata: normalized.metadata
291
- }
292
- }
293
- };
294
- }
295
- convertHubNodeResult(id, result) {
296
- return {
297
- id,
298
- success: result.success,
299
- metadata: result.metadata,
300
- error: result.error
301
- };
302
- }
303
348
  convertProcessNodeResult(id, result) {
304
349
  return {
305
350
  id,
@@ -383,6 +428,19 @@ export class HubPipeline {
383
428
  return undefined;
384
429
  }
385
430
  }
431
+ function normalizeToolCallIdStyleCandidate(value) {
432
+ if (typeof value !== 'string') {
433
+ return undefined;
434
+ }
435
+ const normalized = value.trim().toLowerCase();
436
+ if (normalized === 'fc') {
437
+ return 'fc';
438
+ }
439
+ if (normalized === 'preserve') {
440
+ return 'preserve';
441
+ }
442
+ return undefined;
443
+ }
386
444
  function normalizeEndpoint(endpoint) {
387
445
  if (!endpoint)
388
446
  return '/v1/chat/completions';
@@ -416,6 +474,21 @@ const PROVIDER_PROTOCOL_ALIASES = {
416
474
  'google-gemini': 'gemini-chat',
417
475
  'gemini-chat': 'gemini-chat'
418
476
  };
477
+ function resolveEndpointForProviderProtocol(protocol) {
478
+ if (!protocol) {
479
+ return undefined;
480
+ }
481
+ const value = protocol.toLowerCase();
482
+ if (value === 'openai-responses')
483
+ return '/v1/responses';
484
+ if (value === 'openai-chat')
485
+ return '/v1/chat/completions';
486
+ if (value === 'anthropic-messages' || value === 'anthropic')
487
+ return '/v1/messages';
488
+ if (value === 'gemini-chat' || value === 'gemini' || value === 'google-gemini')
489
+ return '/v1/responses';
490
+ return undefined;
491
+ }
419
492
  function resolveSseProtocolFromMetadata(metadata) {
420
493
  const candidate = metadata.sseProtocol ?? metadata.clientSseProtocol ?? metadata.routeSseProtocol;
421
494
  if (typeof candidate !== 'string' || !candidate.trim()) {
@@ -423,3 +496,21 @@ function resolveSseProtocolFromMetadata(metadata) {
423
496
  }
424
497
  return resolveProviderProtocol(candidate);
425
498
  }
499
+ function coerceAliasMap(candidate) {
500
+ if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
501
+ return undefined;
502
+ }
503
+ const normalized = {};
504
+ for (const [key, value] of Object.entries(candidate)) {
505
+ if (typeof key !== 'string' || typeof value !== 'string') {
506
+ continue;
507
+ }
508
+ const trimmedKey = key.trim();
509
+ const trimmedValue = value.trim();
510
+ if (!trimmedKey.length || !trimmedValue.length) {
511
+ continue;
512
+ }
513
+ normalized[trimmedKey] = trimmedValue;
514
+ }
515
+ return Object.keys(normalized).length ? normalized : undefined;
516
+ }
@@ -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 ReqInboundStage1FormatParseOptions {
6
+ rawRequest: JsonObject;
7
+ adapterContext: AdapterContext;
8
+ formatAdapter: Pick<FormatAdapter, 'parseRequest'>;
9
+ stageRecorder?: StageRecorder;
10
+ }
11
+ export declare function runReqInboundStage1FormatParse(options: ReqInboundStage1FormatParseOptions): Promise<FormatEnvelope<JsonObject>>;
@@ -0,0 +1,6 @@
1
+ import { recordStage } from '../../../stages/utils.js';
2
+ export async function runReqInboundStage1FormatParse(options) {
3
+ const envelope = (await options.formatAdapter.parseRequest(options.rawRequest, options.adapterContext));
4
+ recordStage(options.stageRecorder, 'req_inbound_stage1_format_parse', envelope);
5
+ return envelope;
6
+ }
@@ -0,0 +1,16 @@
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 { SemanticMapper, StageRecorder } from '../../../../format-adapters/index.js';
5
+ import type { StandardizedRequest } from '../../../../types/standardized.js';
6
+ export interface ReqInboundStage2SemanticMapOptions {
7
+ adapterContext: AdapterContext;
8
+ formatEnvelope: FormatEnvelope<JsonObject>;
9
+ semanticMapper: Pick<SemanticMapper, 'toChat'>;
10
+ stageRecorder?: StageRecorder;
11
+ }
12
+ export interface ReqInboundStage2SemanticMapResult {
13
+ chatEnvelope: ChatEnvelope;
14
+ standardizedRequest: StandardizedRequest;
15
+ }
16
+ export declare function runReqInboundStage2SemanticMap(options: ReqInboundStage2SemanticMapOptions): Promise<ReqInboundStage2SemanticMapResult>;
@@ -0,0 +1,17 @@
1
+ import { chatEnvelopeToStandardized } from '../../../../standardized-bridge.js';
2
+ import { validateChatEnvelope } from '../../../../../shared/chat-envelope-validator.js';
3
+ import { recordStage } from '../../../stages/utils.js';
4
+ export async function runReqInboundStage2SemanticMap(options) {
5
+ const chatEnvelope = await options.semanticMapper.toChat(options.formatEnvelope, options.adapterContext);
6
+ validateChatEnvelope(chatEnvelope, {
7
+ stage: 'req_inbound',
8
+ direction: 'request'
9
+ });
10
+ recordStage(options.stageRecorder, 'req_inbound_stage2_semantic_map', chatEnvelope);
11
+ const standardizedRequest = chatEnvelopeToStandardized(chatEnvelope, {
12
+ adapterContext: options.adapterContext,
13
+ endpoint: options.adapterContext.entryEndpoint,
14
+ requestId: options.adapterContext.requestId
15
+ });
16
+ return { chatEnvelope, standardizedRequest };
17
+ }
@@ -0,0 +1,5 @@
1
+ import type { ContextCaptureOptions, ContextCaptureHandler } from './index.js';
2
+ export type ContextSnapshot = Record<string, unknown> | undefined;
3
+ export type ContextCaptureFn = (options: ContextCaptureOptions) => Promise<ContextSnapshot> | ContextSnapshot;
4
+ export declare function createResponsesContextCapture(captureImpl: ContextCaptureHandler): ContextCaptureFn;
5
+ export declare function createNoopContextCapture(label: string): ContextCaptureFn;
@@ -0,0 +1,17 @@
1
+ import { runReqInboundStage3ContextCapture } from './index.js';
2
+ export function createResponsesContextCapture(captureImpl) {
3
+ return (options) => runReqInboundStage3ContextCapture({
4
+ rawRequest: options.rawRequest,
5
+ adapterContext: options.adapterContext,
6
+ stageRecorder: options.stageRecorder,
7
+ captureContext: captureImpl
8
+ });
9
+ }
10
+ export function createNoopContextCapture(label) {
11
+ return (options) => runReqInboundStage3ContextCapture({
12
+ rawRequest: options.rawRequest,
13
+ adapterContext: options.adapterContext,
14
+ stageRecorder: options.stageRecorder,
15
+ captureContext: () => ({ stage: label })
16
+ });
17
+ }
@@ -0,0 +1,19 @@
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
+ import type { ResponsesRequestContext } from '../../../../../responses/responses-openai-bridge.js';
5
+ export interface ContextCaptureOptions {
6
+ rawRequest: JsonObject;
7
+ adapterContext: AdapterContext;
8
+ stageRecorder?: StageRecorder;
9
+ }
10
+ export type ContextCaptureHandler = (options: ContextCaptureOptions) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
11
+ export interface ReqInboundStage3ContextCaptureOptions {
12
+ rawRequest: JsonObject;
13
+ adapterContext: AdapterContext;
14
+ captureContext?: ContextCaptureHandler;
15
+ stageRecorder?: StageRecorder;
16
+ }
17
+ export declare function runReqInboundStage3ContextCapture(options: ReqInboundStage3ContextCaptureOptions): Promise<Record<string, unknown> | undefined>;
18
+ export declare function runChatContextCapture(options: ContextCaptureOptions): Promise<Record<string, unknown> | undefined>;
19
+ export declare function captureResponsesContextSnapshot(options: ContextCaptureOptions): ResponsesRequestContext;