@jsonstudio/rcc 0.89.333 → 0.89.524

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 (165) hide show
  1. package/dist/build-info.js +3 -3
  2. package/dist/build-info.js.map +1 -1
  3. package/dist/cli.js +62 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/token-daemon.d.ts +2 -0
  6. package/dist/commands/token-daemon.js +183 -0
  7. package/dist/commands/token-daemon.js.map +1 -0
  8. package/dist/index.js +4 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/modules/llmswitch/bridge.d.ts +1 -1
  11. package/dist/modules/llmswitch/bridge.js +3 -2
  12. package/dist/modules/llmswitch/bridge.js.map +1 -1
  13. package/dist/modules/pipeline/utils/colored-logger.js +3 -1
  14. package/dist/modules/pipeline/utils/colored-logger.js.map +1 -1
  15. package/dist/providers/auth/gemini-cli-userinfo-helper.js +12 -2
  16. package/dist/providers/auth/gemini-cli-userinfo-helper.js.map +1 -1
  17. package/dist/providers/auth/oauth-lifecycle.js +261 -22
  18. package/dist/providers/auth/oauth-lifecycle.js.map +1 -1
  19. package/dist/providers/core/config/oauth-flows.d.ts +23 -0
  20. package/dist/providers/core/config/oauth-flows.js +92 -5
  21. package/dist/providers/core/config/oauth-flows.js.map +1 -1
  22. package/dist/providers/core/config/provider-oauth-configs.js +9 -3
  23. package/dist/providers/core/config/provider-oauth-configs.js.map +1 -1
  24. package/dist/providers/core/config/service-profiles.js +18 -10
  25. package/dist/providers/core/config/service-profiles.js.map +1 -1
  26. package/dist/providers/core/runtime/gemini-cli-http-provider.js +87 -20
  27. package/dist/providers/core/runtime/gemini-cli-http-provider.js.map +1 -1
  28. package/dist/providers/core/runtime/http-request-executor.d.ts +1 -0
  29. package/dist/providers/core/runtime/http-request-executor.js +41 -1
  30. package/dist/providers/core/runtime/http-request-executor.js.map +1 -1
  31. package/dist/providers/core/runtime/http-transport-provider.d.ts +2 -0
  32. package/dist/providers/core/runtime/http-transport-provider.js +37 -2
  33. package/dist/providers/core/runtime/http-transport-provider.js.map +1 -1
  34. package/dist/providers/core/runtime/responses-provider.js +8 -3
  35. package/dist/providers/core/runtime/responses-provider.js.map +1 -1
  36. package/dist/providers/core/runtime/vision-debug-utils.d.ts +13 -0
  37. package/dist/providers/core/runtime/vision-debug-utils.js +114 -0
  38. package/dist/providers/core/runtime/vision-debug-utils.js.map +1 -0
  39. package/dist/providers/core/strategies/oauth-auth-code-flow.js +75 -26
  40. package/dist/providers/core/strategies/oauth-auth-code-flow.js.map +1 -1
  41. package/dist/providers/core/utils/http-client.js +2 -1
  42. package/dist/providers/core/utils/http-client.js.map +1 -1
  43. package/dist/providers/core/utils/snapshot-writer.d.ts +1 -1
  44. package/dist/providers/core/utils/snapshot-writer.js.map +1 -1
  45. package/dist/server/handlers/sse-dispatcher.js +22 -2
  46. package/dist/server/handlers/sse-dispatcher.js.map +1 -1
  47. package/dist/server/runtime/http-server/index.d.ts +9 -0
  48. package/dist/server/runtime/http-server/index.js +512 -144
  49. package/dist/server/runtime/http-server/index.js.map +1 -1
  50. package/dist/server/runtime/http-server/request-executor.d.ts +10 -0
  51. package/dist/server/runtime/http-server/request-executor.js +553 -159
  52. package/dist/server/runtime/http-server/request-executor.js.map +1 -1
  53. package/dist/server/runtime/http-server/routes.d.ts +5 -0
  54. package/dist/server/runtime/http-server/routes.js +69 -0
  55. package/dist/server/runtime/http-server/routes.js.map +1 -1
  56. package/dist/server/runtime/http-server/runtime-manager.js +18 -0
  57. package/dist/server/runtime/http-server/runtime-manager.js.map +1 -1
  58. package/dist/server/utils/utf8-chunk-buffer.d.ts +43 -0
  59. package/dist/server/utils/utf8-chunk-buffer.js +132 -0
  60. package/dist/server/utils/utf8-chunk-buffer.js.map +1 -0
  61. package/dist/token-daemon/index.d.ts +7 -0
  62. package/dist/token-daemon/index.js +242 -0
  63. package/dist/token-daemon/index.js.map +1 -0
  64. package/dist/token-daemon/server-utils.d.ts +33 -0
  65. package/dist/token-daemon/server-utils.js +155 -0
  66. package/dist/token-daemon/server-utils.js.map +1 -0
  67. package/dist/token-daemon/token-daemon.d.ts +20 -0
  68. package/dist/token-daemon/token-daemon.js +144 -0
  69. package/dist/token-daemon/token-daemon.js.map +1 -0
  70. package/dist/token-daemon/token-types.d.ts +44 -0
  71. package/dist/token-daemon/token-types.js +18 -0
  72. package/dist/token-daemon/token-types.js.map +1 -0
  73. package/dist/token-daemon/token-utils.d.ts +17 -0
  74. package/dist/token-daemon/token-utils.js +153 -0
  75. package/dist/token-daemon/token-utils.js.map +1 -0
  76. package/dist/tools/semantic-replay.js +7 -6
  77. package/dist/tools/semantic-replay.js.map +1 -1
  78. package/dist/utils/error-handler-registry.d.ts +36 -0
  79. package/dist/utils/error-handler-registry.js +93 -7
  80. package/dist/utils/error-handler-registry.js.map +1 -1
  81. package/node_modules/@jsonstudio/llms/README.md +2 -0
  82. package/node_modules/@jsonstudio/llms/dist/conversion/codecs/gemini-openai-codec.js +137 -5
  83. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.d.ts +17 -0
  84. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/gemini-web-search.js +68 -0
  85. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.d.ts +2 -0
  86. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-image-content.js +83 -0
  87. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.d.ts +11 -0
  88. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-vision-prompt.js +177 -0
  89. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
  90. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/glm-web-search.js +63 -0
  91. package/node_modules/@jsonstudio/llms/dist/conversion/compat/actions/universal-shape-filter.js +11 -0
  92. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-gemini.json +17 -0
  93. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-glm.json +190 -181
  94. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-iflow.json +195 -195
  95. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-lmstudio.json +43 -43
  96. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/chat-qwen.json +20 -20
  97. package/node_modules/@jsonstudio/llms/dist/conversion/compat/profiles/responses-c4m.json +42 -42
  98. package/node_modules/@jsonstudio/llms/dist/conversion/config/sample-config.json +1 -1
  99. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +24 -0
  100. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/compat/compat-types.d.ts +8 -0
  101. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/hub-pipeline.js +39 -4
  102. package/node_modules/@jsonstudio/llms/dist/conversion/hub/pipeline/target-utils.js +6 -0
  103. package/node_modules/@jsonstudio/llms/dist/conversion/hub/process/chat-process.js +213 -1
  104. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.d.ts +34 -0
  105. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/provider-response.js +84 -24
  106. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
  107. package/node_modules/@jsonstudio/llms/dist/conversion/hub/response/server-side-tools.js +383 -0
  108. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/gemini-mapper.js +241 -14
  109. package/node_modules/@jsonstudio/llms/dist/conversion/hub/semantic-mappers/responses-mapper.js +17 -1
  110. package/node_modules/@jsonstudio/llms/dist/conversion/hub/standardized-bridge.js +14 -0
  111. package/node_modules/@jsonstudio/llms/dist/conversion/hub/types/standardized.d.ts +1 -0
  112. package/node_modules/@jsonstudio/llms/dist/conversion/responses/responses-openai-bridge.js +82 -3
  113. package/node_modules/@jsonstudio/llms/dist/conversion/shared/anthropic-message-utils.js +92 -3
  114. package/node_modules/@jsonstudio/llms/dist/conversion/shared/bridge-message-utils.js +137 -10
  115. package/node_modules/@jsonstudio/llms/dist/conversion/shared/responses-output-builder.js +43 -2
  116. package/node_modules/@jsonstudio/llms/dist/conversion/shared/snapshot-utils.js +17 -47
  117. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-filter-pipeline.js +1 -0
  118. package/node_modules/@jsonstudio/llms/dist/conversion/shared/tool-mapping.js +25 -2
  119. package/node_modules/@jsonstudio/llms/dist/index.d.ts +1 -0
  120. package/node_modules/@jsonstudio/llms/dist/index.js +1 -0
  121. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/bootstrap.js +308 -43
  122. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/classifier.js +11 -17
  123. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.d.ts +0 -2
  124. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/context-advisor.js +0 -12
  125. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.d.ts +17 -2
  126. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/engine.js +332 -95
  127. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/features.js +1 -1
  128. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/message-utils.js +36 -24
  129. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/provider-registry.js +2 -1
  130. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/token-counter.js +14 -3
  131. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.d.ts +66 -2
  132. package/node_modules/@jsonstudio/llms/dist/router/virtual-router/types.js +2 -1
  133. package/node_modules/@jsonstudio/llms/dist/servertool/engine.d.ts +27 -0
  134. package/node_modules/@jsonstudio/llms/dist/servertool/engine.js +60 -0
  135. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.d.ts +40 -0
  136. package/node_modules/@jsonstudio/llms/dist/servertool/flow-types.js +1 -0
  137. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.d.ts +1 -0
  138. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/vision.js +194 -0
  139. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.d.ts +1 -0
  140. package/node_modules/@jsonstudio/llms/dist/servertool/handlers/web-search.js +638 -0
  141. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.d.ts +33 -0
  142. package/node_modules/@jsonstudio/llms/dist/servertool/orchestration-types.js +1 -0
  143. package/node_modules/@jsonstudio/llms/dist/servertool/registry.d.ts +18 -0
  144. package/node_modules/@jsonstudio/llms/dist/servertool/registry.js +27 -0
  145. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.d.ts +8 -0
  146. package/node_modules/@jsonstudio/llms/dist/servertool/server-side-tools.js +208 -0
  147. package/node_modules/@jsonstudio/llms/dist/servertool/types.d.ts +88 -0
  148. package/node_modules/@jsonstudio/llms/dist/servertool/types.js +1 -0
  149. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.d.ts +2 -0
  150. package/node_modules/@jsonstudio/llms/dist/servertool/vision-tool.js +185 -0
  151. package/node_modules/@jsonstudio/llms/dist/sse/json-to-sse/event-generators/responses.js +15 -3
  152. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/builders/response-builder.js +6 -3
  153. package/node_modules/@jsonstudio/llms/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
  154. package/node_modules/@jsonstudio/llms/dist/sse/types/gemini-types.d.ts +20 -1
  155. package/node_modules/@jsonstudio/llms/dist/sse/types/responses-types.js +1 -1
  156. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.d.ts +73 -0
  157. package/node_modules/@jsonstudio/llms/dist/telemetry/stats-center.js +280 -0
  158. package/node_modules/@jsonstudio/llms/package.json +1 -1
  159. package/package.json +2 -2
  160. package/scripts/pack-mode.mjs +2 -1
  161. package/scripts/publish-rcc.mjs +20 -4
  162. package/scripts/tests/virtual-router-health.mjs +141 -6
  163. package/dist/tools/replay-request.d.ts +0 -0
  164. package/dist/tools/replay-request.js +0 -2
  165. package/dist/tools/replay-request.js.map +0 -1
@@ -47,13 +47,13 @@ export class HubRequestExecutor {
47
47
  };
48
48
  try {
49
49
  const hubPipeline = this.ensureHubPipeline();
50
- const metadata = this.buildRequestMetadata(input);
51
- const inboundClientHeaders = this.cloneClientHeaders(metadata?.clientHeaders);
50
+ const initialMetadata = this.buildRequestMetadata(input);
51
+ const inboundClientHeaders = this.cloneClientHeaders(initialMetadata?.clientHeaders);
52
52
  const providerRequestId = input.requestId;
53
- const clientRequestId = this.resolveClientRequestId(metadata, providerRequestId);
53
+ const clientRequestId = this.resolveClientRequestId(initialMetadata, providerRequestId);
54
54
  this.logStage('request.received', providerRequestId, {
55
55
  endpoint: input.entryEndpoint,
56
- stream: metadata.stream === true
56
+ stream: initialMetadata.stream === true
57
57
  });
58
58
  try {
59
59
  const headerUa = (typeof input.headers?.['user-agent'] === 'string' && input.headers['user-agent']) ||
@@ -66,7 +66,7 @@ export class HubRequestExecutor {
66
66
  headers: asRecord(input.headers),
67
67
  body: input.body,
68
68
  metadata: {
69
- ...metadata,
69
+ ...initialMetadata,
70
70
  userAgent: headerUa,
71
71
  clientOriginator: headerOriginator
72
72
  }
@@ -76,171 +76,187 @@ export class HubRequestExecutor {
76
76
  /* snapshot failure should not block request path */
77
77
  }
78
78
  const pipelineLabel = 'hub';
79
- this.logStage(`${pipelineLabel}.start`, providerRequestId, {
80
- endpoint: input.entryEndpoint,
81
- stream: metadata.stream
82
- });
83
- const originalRequestSnapshot = this.cloneRequestPayload(input.body);
84
- const pipelineResult = await this.runHubPipeline(hubPipeline, input, metadata);
85
- const pipelineMetadata = pipelineResult.metadata ?? {};
86
- const mergedMetadata = { ...metadata, ...pipelineMetadata };
87
- const mergedClientHeaders = this.cloneClientHeaders(mergedMetadata?.clientHeaders) || inboundClientHeaders;
88
- if (mergedClientHeaders) {
89
- mergedMetadata.clientHeaders = mergedClientHeaders;
90
- }
91
- this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
92
- route: pipelineResult.routingDecision?.routeName,
93
- target: pipelineResult.target?.providerKey
94
- });
95
- const providerPayload = pipelineResult.providerPayload;
96
- const target = pipelineResult.target;
97
- if (!providerPayload || !target?.providerKey) {
98
- throw Object.assign(new Error('Virtual router did not produce a provider target'), {
99
- code: 'ERR_NO_PROVIDER_TARGET',
100
- requestId: input.requestId
101
- });
102
- }
103
- const runtimeKey = target.runtimeKey || this.deps.runtimeManager.resolveRuntimeKey(target.providerKey);
104
- if (!runtimeKey) {
105
- throw Object.assign(new Error(`Runtime for provider ${target.providerKey} not initialized`), {
106
- code: 'ERR_RUNTIME_NOT_FOUND',
107
- requestId: input.requestId
108
- });
109
- }
110
- const handle = this.deps.runtimeManager.getHandleByRuntimeKey(runtimeKey);
111
- if (!handle) {
112
- throw Object.assign(new Error(`Provider runtime ${runtimeKey} not found`), {
113
- code: 'ERR_PROVIDER_NOT_FOUND',
114
- requestId: input.requestId
115
- });
116
- }
117
- const providerProtocol = target.outboundProfile || handle.providerProtocol;
118
- const metadataModel = mergedMetadata?.target && typeof mergedMetadata.target === 'object'
119
- ? mergedMetadata.target.clientModelId
120
- : undefined;
121
- const rawModel = this.extractProviderModel(providerPayload) ||
122
- (typeof metadataModel === 'string' ? metadataModel : undefined);
123
- const providerAlias = typeof target.providerKey === 'string' && target.providerKey.includes('.')
124
- ? target.providerKey.split('.').slice(0, 2).join('.')
125
- : target.providerKey;
126
- const providerIdToken = providerAlias || handle.providerId || runtimeKey;
127
- if (!providerIdToken) {
128
- throw Object.assign(new Error('Provider identifier missing for request'), {
129
- code: 'ERR_PROVIDER_ID_MISSING',
130
- requestId: providerRequestId
79
+ let iterationMetadata = initialMetadata;
80
+ let aggregatedUsage;
81
+ let attempt = 0;
82
+ let followupTriggered = false;
83
+ while (true) {
84
+ this.logStage(`${pipelineLabel}.start`, providerRequestId, {
85
+ endpoint: input.entryEndpoint,
86
+ stream: iterationMetadata.stream,
87
+ attempt
131
88
  });
132
- }
133
- const enhancedRequestId = enhanceProviderRequestId(providerRequestId, {
134
- entryEndpoint: input.entryEndpoint,
135
- providerId: providerIdToken,
136
- model: rawModel
137
- });
138
- if (enhancedRequestId !== input.requestId) {
139
- input.requestId = enhancedRequestId;
140
- }
141
- mergedMetadata.clientRequestId = clientRequestId;
142
- const providerModel = rawModel;
143
- const providerLabel = this.buildProviderLabel(target.providerKey, providerModel);
144
- if (inboundClientHeaders) {
145
- this.ensureClientHeadersOnPayload(providerPayload, inboundClientHeaders);
146
- }
147
- this.deps.stats.bindProvider(input.requestId, {
148
- providerKey: target.providerKey,
149
- providerType: handle.providerType,
150
- model: providerModel
151
- });
152
- this.logStage('provider.prepare', input.requestId, {
153
- providerKey: target.providerKey,
154
- runtimeKey,
155
- protocol: providerProtocol,
156
- providerType: handle.providerType,
157
- providerFamily: handle.providerFamily,
158
- model: providerModel,
159
- providerLabel
160
- });
161
- attachProviderRuntimeMetadata(providerPayload, {
162
- requestId: input.requestId,
163
- providerId: handle.providerId,
164
- providerKey: target.providerKey,
165
- providerType: handle.providerType,
166
- providerFamily: handle.providerFamily,
167
- providerProtocol,
168
- pipelineId: target.providerKey,
169
- routeName: pipelineResult.routingDecision?.routeName,
170
- runtimeKey,
171
- target,
172
- metadata: mergedMetadata,
173
- compatibilityProfile: target.compatibilityProfile
174
- });
175
- this.logStage('provider.send.start', input.requestId, {
176
- providerKey: target.providerKey,
177
- runtimeKey,
178
- protocol: providerProtocol,
179
- providerType: handle.providerType,
180
- providerFamily: handle.providerFamily,
181
- model: providerModel,
182
- providerLabel
183
- });
184
- try {
185
- const providerResponse = await handle.instance.processIncoming(providerPayload);
186
- const responseStatus = this.extractResponseStatus(providerResponse);
187
- this.logStage('provider.send.completed', input.requestId, {
188
- providerKey: target.providerKey,
189
- status: responseStatus,
190
- providerType: handle.providerType,
191
- providerFamily: handle.providerFamily,
192
- model: providerModel,
193
- providerLabel
89
+ // 获取原始请求快照,用于后续响应转换(不包含任何 server-side 工具语义)。
90
+ const originalRequestSnapshot = this.cloneRequestPayload(input.body);
91
+ const pipelineResult = await this.runHubPipeline(hubPipeline, input, iterationMetadata);
92
+ const pipelineMetadata = pipelineResult.metadata ?? {};
93
+ const mergedMetadata = { ...iterationMetadata, ...pipelineMetadata };
94
+ const mergedClientHeaders = this.cloneClientHeaders(mergedMetadata?.clientHeaders) || inboundClientHeaders;
95
+ if (mergedClientHeaders) {
96
+ mergedMetadata.clientHeaders = mergedClientHeaders;
97
+ }
98
+ mergedMetadata.clientRequestId = clientRequestId;
99
+ this.logStage(`${pipelineLabel}.completed`, providerRequestId, {
100
+ route: pipelineResult.routingDecision?.routeName,
101
+ target: pipelineResult.target?.providerKey,
102
+ attempt
194
103
  });
195
- const normalized = this.normalizeProviderResponse(providerResponse);
196
- const converted = await this.convertProviderResponseIfNeeded({
104
+ const providerPayload = pipelineResult.providerPayload;
105
+ const target = pipelineResult.target;
106
+ if (!providerPayload || !target?.providerKey) {
107
+ throw Object.assign(new Error('Virtual router did not produce a provider target'), {
108
+ code: 'ERR_NO_PROVIDER_TARGET',
109
+ requestId: input.requestId
110
+ });
111
+ }
112
+ const runtimeKey = target.runtimeKey || this.deps.runtimeManager.resolveRuntimeKey(target.providerKey);
113
+ if (!runtimeKey) {
114
+ throw Object.assign(new Error(`Runtime for provider ${target.providerKey} not initialized`), {
115
+ code: 'ERR_RUNTIME_NOT_FOUND',
116
+ requestId: input.requestId
117
+ });
118
+ }
119
+ const handle = this.deps.runtimeManager.getHandleByRuntimeKey(runtimeKey);
120
+ if (!handle) {
121
+ throw Object.assign(new Error(`Provider runtime ${runtimeKey} not found`), {
122
+ code: 'ERR_PROVIDER_NOT_FOUND',
123
+ requestId: input.requestId
124
+ });
125
+ }
126
+ const providerProtocol = target.outboundProfile || handle.providerProtocol;
127
+ const metadataModel = mergedMetadata?.target && typeof mergedMetadata.target === 'object'
128
+ ? mergedMetadata.target.clientModelId
129
+ : undefined;
130
+ const rawModel = this.extractProviderModel(providerPayload) ||
131
+ (typeof metadataModel === 'string' ? metadataModel : undefined);
132
+ const providerAlias = typeof target.providerKey === 'string' && target.providerKey.includes('.')
133
+ ? target.providerKey.split('.').slice(0, 2).join('.')
134
+ : target.providerKey;
135
+ const providerIdToken = providerAlias || handle.providerId || runtimeKey;
136
+ if (!providerIdToken) {
137
+ throw Object.assign(new Error('Provider identifier missing for request'), {
138
+ code: 'ERR_PROVIDER_ID_MISSING',
139
+ requestId: providerRequestId
140
+ });
141
+ }
142
+ const enhancedRequestId = enhanceProviderRequestId(providerRequestId, {
197
143
  entryEndpoint: input.entryEndpoint,
198
- providerType: handle.providerType,
199
- requestId: input.requestId,
200
- wantsStream: Boolean(input.metadata?.inboundStream ?? input.metadata?.stream),
201
- originalRequest: originalRequestSnapshot,
202
- processMode: pipelineResult.processMode,
203
- response: normalized,
204
- pipelineMetadata: mergedMetadata
144
+ providerId: providerIdToken,
145
+ model: rawModel
205
146
  });
206
- const usage = this.extractUsageFromResult(converted, mergedMetadata);
207
- finalizeStats({ usage, error: false });
208
- this.logUsageSummary(input.requestId, {
147
+ if (enhancedRequestId !== input.requestId) {
148
+ input.requestId = enhancedRequestId;
149
+ }
150
+ mergedMetadata.clientRequestId = clientRequestId;
151
+ const providerModel = rawModel;
152
+ const providerLabel = this.buildProviderLabel(target.providerKey, providerModel);
153
+ if (inboundClientHeaders) {
154
+ this.ensureClientHeadersOnPayload(providerPayload, inboundClientHeaders);
155
+ }
156
+ this.deps.stats.bindProvider(input.requestId, {
209
157
  providerKey: target.providerKey,
210
- model: providerModel,
211
- usage,
212
- latencyMs: Date.now() - requestStartedAt
158
+ providerType: handle.providerType,
159
+ model: providerModel
213
160
  });
214
- return converted;
215
- }
216
- catch (error) {
217
- this.logStage('provider.send.error', input.requestId, {
161
+ this.logStage('provider.prepare', input.requestId, {
218
162
  providerKey: target.providerKey,
219
- message: error instanceof Error ? error.message : String(error ?? 'Unknown error'),
163
+ runtimeKey,
164
+ protocol: providerProtocol,
220
165
  providerType: handle.providerType,
221
166
  providerFamily: handle.providerFamily,
222
167
  model: providerModel,
223
- providerLabel
168
+ providerLabel,
169
+ attempt
224
170
  });
225
- const runtimeMetadata = {
171
+ attachProviderRuntimeMetadata(providerPayload, {
226
172
  requestId: input.requestId,
227
- providerKey: target.providerKey,
228
173
  providerId: handle.providerId,
174
+ providerKey: target.providerKey,
229
175
  providerType: handle.providerType,
176
+ providerFamily: handle.providerFamily,
230
177
  providerProtocol,
231
- routeName: pipelineResult.routingDecision?.routeName,
232
178
  pipelineId: target.providerKey,
179
+ routeName: pipelineResult.routingDecision?.routeName,
233
180
  runtimeKey,
234
- target
235
- };
236
- runtimeMetadata.providerFamily = handle.providerFamily;
237
- emitProviderError({
238
- error,
239
- stage: 'provider.send',
240
- runtime: runtimeMetadata,
241
- dependencies: this.deps.getModuleDependencies()
181
+ target,
182
+ metadata: mergedMetadata,
183
+ compatibilityProfile: target.compatibilityProfile
242
184
  });
243
- throw error;
185
+ this.logStage('provider.send.start', input.requestId, {
186
+ providerKey: target.providerKey,
187
+ runtimeKey,
188
+ protocol: providerProtocol,
189
+ providerType: handle.providerType,
190
+ providerFamily: handle.providerFamily,
191
+ model: providerModel,
192
+ providerLabel,
193
+ attempt
194
+ });
195
+ try {
196
+ const providerResponse = await handle.instance.processIncoming(providerPayload);
197
+ const responseStatus = this.extractResponseStatus(providerResponse);
198
+ this.logStage('provider.send.completed', input.requestId, {
199
+ providerKey: target.providerKey,
200
+ status: responseStatus,
201
+ providerType: handle.providerType,
202
+ providerFamily: handle.providerFamily,
203
+ model: providerModel,
204
+ providerLabel,
205
+ attempt
206
+ });
207
+ const wantsStreamBase = Boolean(input.metadata?.inboundStream ?? input.metadata?.stream);
208
+ const normalized = this.normalizeProviderResponse(providerResponse);
209
+ const converted = await this.convertProviderResponseIfNeeded({
210
+ entryEndpoint: input.entryEndpoint,
211
+ providerType: handle.providerType,
212
+ requestId: input.requestId,
213
+ wantsStream: wantsStreamBase,
214
+ originalRequest: originalRequestSnapshot,
215
+ processMode: pipelineResult.processMode,
216
+ response: normalized,
217
+ pipelineMetadata: mergedMetadata
218
+ });
219
+ const usage = this.extractUsageFromResult(converted, mergedMetadata);
220
+ aggregatedUsage = this.mergeUsageMetrics(aggregatedUsage, usage);
221
+ finalizeStats({ usage: aggregatedUsage, error: false });
222
+ this.logUsageSummary(input.requestId, {
223
+ providerKey: target.providerKey,
224
+ model: providerModel,
225
+ usage: aggregatedUsage,
226
+ latencyMs: Date.now() - requestStartedAt
227
+ });
228
+ return converted;
229
+ }
230
+ catch (error) {
231
+ this.logStage('provider.send.error', input.requestId, {
232
+ providerKey: target.providerKey,
233
+ message: error instanceof Error ? error.message : String(error ?? 'Unknown error'),
234
+ providerType: handle.providerType,
235
+ providerFamily: handle.providerFamily,
236
+ model: providerModel,
237
+ providerLabel,
238
+ attempt
239
+ });
240
+ const runtimeMetadata = {
241
+ requestId: input.requestId,
242
+ providerKey: target.providerKey,
243
+ providerId: handle.providerId,
244
+ providerType: handle.providerType,
245
+ providerProtocol,
246
+ routeName: pipelineResult.routingDecision?.routeName,
247
+ pipelineId: target.providerKey,
248
+ runtimeKey,
249
+ target
250
+ };
251
+ runtimeMetadata.providerFamily = handle.providerFamily;
252
+ emitProviderError({
253
+ error,
254
+ stage: 'provider.send',
255
+ runtime: runtimeMetadata,
256
+ dependencies: this.deps.getModuleDependencies()
257
+ });
258
+ throw error;
259
+ }
244
260
  }
245
261
  }
246
262
  catch (error) {
@@ -412,12 +428,17 @@ export class HubRequestExecutor {
412
428
  const metadataBag = asRecord(options.pipelineMetadata);
413
429
  const aliasMap = extractAnthropicToolAliasMap(metadataBag);
414
430
  const originalModelId = this.extractClientModelId(metadataBag, options.originalRequest);
415
- const adapterContext = {
416
- requestId: options.requestId,
417
- entryEndpoint: options.entryEndpoint || entry,
418
- providerProtocol,
419
- originalModelId
431
+ const baseContext = {
432
+ ...(metadataBag ?? {})
420
433
  };
434
+ if (typeof metadataBag?.routeName === 'string') {
435
+ baseContext.routeId = metadataBag.routeName;
436
+ }
437
+ baseContext.requestId = options.requestId;
438
+ baseContext.entryEndpoint = options.entryEndpoint || entry;
439
+ baseContext.providerProtocol = providerProtocol;
440
+ baseContext.originalModelId = originalModelId;
441
+ const adapterContext = baseContext;
421
442
  if (aliasMap) {
422
443
  adapterContext.anthropicToolNameMap = aliasMap;
423
444
  }
@@ -425,14 +446,85 @@ export class HubRequestExecutor {
425
446
  loadConvertProviderResponse(),
426
447
  loadSnapshotRecorderFactory()
427
448
  ]);
428
- const stageRecorder = createSnapshotRecorder(adapterContext, adapterContext.entryEndpoint);
449
+ const stageRecorder = createSnapshotRecorder(adapterContext, typeof adapterContext.entryEndpoint === 'string'
450
+ ? adapterContext.entryEndpoint
451
+ : options.entryEndpoint || entry);
452
+ const providerInvoker = async (invokeOptions) => {
453
+ // 将 server-side 工具的 routeHint 注入到内部 payload 的 metadata,
454
+ // 以便后续在标准 HubPipeline 中保持路由上下文一致(例如强制 web_search)。
455
+ if (invokeOptions.routeHint) {
456
+ const carrier = invokeOptions.payload;
457
+ const existingMeta = carrier.metadata && typeof carrier.metadata === 'object'
458
+ ? carrier.metadata
459
+ : {};
460
+ carrier.metadata = {
461
+ ...existingMeta,
462
+ routeHint: existingMeta.routeHint ?? invokeOptions.routeHint
463
+ };
464
+ }
465
+ // Delegate to existing runtimeManager / Provider V2 stack.
466
+ const runtimeKey = this.deps.runtimeManager.resolveRuntimeKey(invokeOptions.providerKey);
467
+ if (!runtimeKey) {
468
+ throw new Error(`Runtime for provider ${invokeOptions.providerKey} not initialized`);
469
+ }
470
+ const handle = this.deps.runtimeManager.getHandleByRuntimeKey(runtimeKey);
471
+ if (!handle) {
472
+ throw new Error(`Provider runtime ${runtimeKey} not found`);
473
+ }
474
+ const providerResponse = await handle.instance.processIncoming(invokeOptions.payload);
475
+ const normalized = this.normalizeProviderResponse(providerResponse);
476
+ const bodyPayload = normalized.body && typeof normalized.body === 'object'
477
+ ? normalized.body
478
+ : normalized;
479
+ return { providerResponse: bodyPayload };
480
+ };
481
+ const reenterPipeline = async (reenterOpts) => {
482
+ const nestedEntry = reenterOpts.entryEndpoint || options.entryEndpoint || entry;
483
+ const nestedExtra = asRecord(reenterOpts.metadata) ?? {};
484
+ const nestedEntryLower = nestedEntry.toLowerCase();
485
+ // 基于首次 HubPipeline metadata + 调用方注入的 metadata 构建新的请求 metadata。
486
+ // 不在 Host 层编码 servertool/web_search 等语义,由 llmswitch-core 负责。
487
+ const nestedMetadata = {
488
+ ...(metadataBag ?? {}),
489
+ ...nestedExtra,
490
+ entryEndpoint: nestedEntry,
491
+ direction: 'request',
492
+ stage: 'inbound'
493
+ };
494
+ // 针对 reenterPipeline 的入口端点,纠正 providerProtocol,避免沿用外层协议。
495
+ if (nestedEntryLower.includes('/v1/chat/completions')) {
496
+ nestedMetadata.providerProtocol = 'openai-chat';
497
+ }
498
+ else if (nestedEntryLower.includes('/v1/responses')) {
499
+ nestedMetadata.providerProtocol = 'openai-responses';
500
+ }
501
+ else if (nestedEntryLower.includes('/v1/messages')) {
502
+ nestedMetadata.providerProtocol = 'anthropic-messages';
503
+ }
504
+ const nestedInput = {
505
+ entryEndpoint: nestedEntry,
506
+ method: 'POST',
507
+ requestId: reenterOpts.requestId,
508
+ headers: {},
509
+ query: {},
510
+ body: reenterOpts.body,
511
+ metadata: nestedMetadata
512
+ };
513
+ const nestedResult = await this.execute(nestedInput);
514
+ const nestedBody = nestedResult.body && typeof nestedResult.body === 'object'
515
+ ? nestedResult.body
516
+ : undefined;
517
+ return { body: nestedBody };
518
+ };
429
519
  const converted = await convertProviderResponse({
430
520
  providerProtocol,
431
521
  providerResponse: body,
432
522
  context: adapterContext,
433
523
  entryEndpoint: options.entryEndpoint || entry,
434
524
  wantsStream: options.wantsStream,
435
- stageRecorder
525
+ providerInvoker,
526
+ stageRecorder,
527
+ reenterPipeline
436
528
  });
437
529
  if (converted.__sse_responses) {
438
530
  return {
@@ -446,6 +538,20 @@ export class HubRequestExecutor {
446
538
  };
447
539
  }
448
540
  catch (error) {
541
+ const err = error;
542
+ const message = err instanceof Error ? err.message : String(err ?? 'Unknown error');
543
+ const providerProtocol = mapProviderProtocol(options.providerType);
544
+ // 对于 Gemini 等基于 SSE 的 provider,如果 llmswitch-core 报告
545
+ // “Failed to convert SSE payload ...” 之类错误,说明上游流式响应已异常
546
+ // 终止(如 Cloud Code 终止/上下文超限),继续回退到原始 SSE 只会让
547
+ // 客户端挂起。因此在此类场景下直接抛出错误,让 HTTP 层返回明确的
548
+ // 5xx/4xx,而不是静默退回原始 payload。
549
+ const isSseConvertFailure = typeof message === 'string' &&
550
+ message.toLowerCase().includes('failed to convert sse payload');
551
+ if (providerProtocol === 'gemini-chat' && isSseConvertFailure) {
552
+ console.error('[RequestExecutor] Fatal SSE decode error for Gemini provider, bubbling as HTTP error', error);
553
+ throw error;
554
+ }
449
555
  console.error('[RequestExecutor] Failed to convert provider response via llmswitch-core', error);
450
556
  return options.response;
451
557
  }
@@ -579,6 +685,294 @@ export class HubRequestExecutor {
579
685
  total_tokens: total
580
686
  };
581
687
  }
688
+ mergeUsageMetrics(base, delta) {
689
+ if (!delta) {
690
+ return base;
691
+ }
692
+ if (!base) {
693
+ return { ...delta };
694
+ }
695
+ const merged = {
696
+ prompt_tokens: (base.prompt_tokens ?? 0) + (delta.prompt_tokens ?? 0),
697
+ completion_tokens: (base.completion_tokens ?? 0) + (delta.completion_tokens ?? 0)
698
+ };
699
+ const total = (base.total_tokens ?? 0) + (delta.total_tokens ?? 0);
700
+ merged.total_tokens = total || undefined;
701
+ return merged;
702
+ }
703
+ buildVisionFollowupPayload(options) {
704
+ const { originalPayload, visionResponse } = options;
705
+ if (!originalPayload || typeof originalPayload !== 'object') {
706
+ return null;
707
+ }
708
+ const clone = this.cloneRequestPayload(originalPayload) ?? { ...originalPayload };
709
+ if (!clone) {
710
+ return null;
711
+ }
712
+ const visionText = this.extractVisionDescription(visionResponse?.body);
713
+ if (!visionText) {
714
+ return null;
715
+ }
716
+ if (this.rewriteResponsesInput(clone, visionText)) {
717
+ return clone;
718
+ }
719
+ if (this.rewriteChatMessages(clone, visionText)) {
720
+ return clone;
721
+ }
722
+ return null;
723
+ }
724
+ rewriteResponsesInput(payload, visionText) {
725
+ const inputList = payload.input;
726
+ if (!Array.isArray(inputList)) {
727
+ return false;
728
+ }
729
+ for (let i = inputList.length - 1; i >= 0; i -= 1) {
730
+ const item = inputList[i];
731
+ if (!item || typeof item !== 'object') {
732
+ continue;
733
+ }
734
+ const role = typeof item.role === 'string'
735
+ ? item.role
736
+ : '';
737
+ if (role !== 'user') {
738
+ continue;
739
+ }
740
+ const contentBlocks = Array.isArray(item.content)
741
+ ? [...item.content]
742
+ : [];
743
+ const originalText = this.extractTextFromContentBlocks(contentBlocks, ['input_text', 'text']);
744
+ const textType = this.detectContentTextType(contentBlocks, 'input_text');
745
+ const composed = this.composeVisionUserText(visionText, originalText);
746
+ item.content = [
747
+ {
748
+ type: textType,
749
+ text: composed
750
+ }
751
+ ];
752
+ inputList[i] = item;
753
+ return true;
754
+ }
755
+ return false;
756
+ }
757
+ rewriteChatMessages(payload, visionText) {
758
+ const messages = payload.messages;
759
+ if (!Array.isArray(messages)) {
760
+ return false;
761
+ }
762
+ for (let i = messages.length - 1; i >= 0; i -= 1) {
763
+ const message = messages[i];
764
+ if (!message || typeof message !== 'object') {
765
+ continue;
766
+ }
767
+ const role = typeof message.role === 'string'
768
+ ? message.role
769
+ : '';
770
+ if (role !== 'user') {
771
+ continue;
772
+ }
773
+ const contentBlocks = Array.isArray(message.content)
774
+ ? [...message.content]
775
+ : [];
776
+ const originalText = this.extractTextFromContentBlocks(contentBlocks, ['text']);
777
+ const textType = this.detectContentTextType(contentBlocks, 'text');
778
+ const composed = this.composeVisionUserText(visionText, originalText);
779
+ message.content = [
780
+ {
781
+ type: textType,
782
+ text: composed
783
+ }
784
+ ];
785
+ messages[i] = message;
786
+ return true;
787
+ }
788
+ return false;
789
+ }
790
+ extractTextFromContentBlocks(content, allowedTypes) {
791
+ if (typeof content === 'string') {
792
+ return content;
793
+ }
794
+ if (!Array.isArray(content)) {
795
+ return '';
796
+ }
797
+ const collected = [];
798
+ for (const block of content) {
799
+ if (!block || typeof block !== 'object') {
800
+ continue;
801
+ }
802
+ const typeValue = typeof block.type === 'string'
803
+ ? block.type
804
+ : '';
805
+ if (allowedTypes.length && !allowedTypes.includes(typeValue)) {
806
+ continue;
807
+ }
808
+ const textValue = block.text;
809
+ if (typeof textValue === 'string' && textValue.trim()) {
810
+ collected.push(textValue.trim());
811
+ }
812
+ }
813
+ return collected.join('\n');
814
+ }
815
+ detectContentTextType(content, fallback) {
816
+ if (Array.isArray(content)) {
817
+ for (const block of content) {
818
+ if (!block || typeof block !== 'object') {
819
+ continue;
820
+ }
821
+ const typeValue = block.type;
822
+ if (typeof typeValue === 'string' && (typeValue === 'text' || typeValue === 'input_text')) {
823
+ return typeValue;
824
+ }
825
+ }
826
+ }
827
+ return fallback;
828
+ }
829
+ composeVisionUserText(visionText, originalText) {
830
+ const sections = [];
831
+ const cleanedVision = (visionText || '').trim();
832
+ if (cleanedVision) {
833
+ sections.push(`【图片分析】\n${cleanedVision}`);
834
+ }
835
+ const cleanedOriginal = (originalText || '').trim();
836
+ if (cleanedOriginal) {
837
+ sections.push(`【用户原始请求】\n${cleanedOriginal}`);
838
+ }
839
+ return sections.join('\n\n');
840
+ }
841
+ extractVisionDescription(body) {
842
+ if (!body) {
843
+ return null;
844
+ }
845
+ if (typeof body === 'string') {
846
+ const trimmed = body.trim();
847
+ return trimmed.length ? trimmed : null;
848
+ }
849
+ if (typeof body !== 'object') {
850
+ return null;
851
+ }
852
+ const record = body;
853
+ const direct = this.extractTextCandidate(record);
854
+ if (direct) {
855
+ return direct;
856
+ }
857
+ if (record.response && typeof record.response === 'object') {
858
+ const responseNode = record.response;
859
+ const nested = this.extractTextCandidate(responseNode);
860
+ if (nested) {
861
+ return nested;
862
+ }
863
+ const output = responseNode.output;
864
+ if (Array.isArray(output)) {
865
+ for (const entry of output) {
866
+ if (entry && typeof entry === 'object') {
867
+ const nestedText = this.extractTextCandidate(entry);
868
+ if (nestedText) {
869
+ return nestedText;
870
+ }
871
+ }
872
+ }
873
+ }
874
+ }
875
+ if (Array.isArray(record.output)) {
876
+ for (const entry of record.output) {
877
+ if (entry && typeof entry === 'object') {
878
+ const nested = this.extractTextCandidate(entry);
879
+ if (nested) {
880
+ return nested;
881
+ }
882
+ }
883
+ }
884
+ }
885
+ const choices = record.choices;
886
+ if (Array.isArray(choices)) {
887
+ for (const choice of choices) {
888
+ if (!choice || typeof choice !== 'object') {
889
+ continue;
890
+ }
891
+ const message = choice.message;
892
+ if (message && typeof message === 'object') {
893
+ const msg = message;
894
+ const content = msg.content;
895
+ if (typeof content === 'string' && content.trim()) {
896
+ return content.trim();
897
+ }
898
+ if (Array.isArray(content)) {
899
+ for (const part of content) {
900
+ if (part && typeof part === 'object' && typeof part.text === 'string') {
901
+ const textValue = part.text.trim();
902
+ if (textValue) {
903
+ return textValue;
904
+ }
905
+ }
906
+ }
907
+ }
908
+ }
909
+ }
910
+ }
911
+ return null;
912
+ }
913
+ extractTextCandidate(record) {
914
+ const candidates = [
915
+ { key: 'output_text', allowJson: true },
916
+ { key: 'text' },
917
+ { key: 'content' }
918
+ ];
919
+ for (const candidate of candidates) {
920
+ if (!(candidate.key in record)) {
921
+ continue;
922
+ }
923
+ const text = this.normalizeTextCandidateValue(record[candidate.key], candidate.allowJson === true);
924
+ if (text) {
925
+ return text;
926
+ }
927
+ }
928
+ return null;
929
+ }
930
+ normalizeTextCandidateValue(value, allowJsonStringify = false) {
931
+ if (!value) {
932
+ return null;
933
+ }
934
+ if (typeof value === 'string') {
935
+ const trimmed = value.trim();
936
+ return trimmed.length ? trimmed : null;
937
+ }
938
+ if (Array.isArray(value)) {
939
+ const collected = [];
940
+ for (const entry of value) {
941
+ const nested = this.normalizeTextCandidateValue(entry, allowJsonStringify);
942
+ if (nested) {
943
+ collected.push(nested);
944
+ }
945
+ }
946
+ return collected.length ? collected.join('\n') : null;
947
+ }
948
+ if (typeof value === 'object') {
949
+ const bag = value;
950
+ const textField = bag.text;
951
+ if (typeof textField === 'string' && textField.trim()) {
952
+ return textField.trim();
953
+ }
954
+ const summaryField = bag.summary;
955
+ if (typeof summaryField === 'string' && summaryField.trim()) {
956
+ return summaryField.trim();
957
+ }
958
+ if ('content' in bag) {
959
+ const nested = this.normalizeTextCandidateValue(bag.content, allowJsonStringify);
960
+ if (nested) {
961
+ return nested;
962
+ }
963
+ }
964
+ if (allowJsonStringify) {
965
+ try {
966
+ const serialized = JSON.stringify(value, null, 2);
967
+ return serialized.trim() || null;
968
+ }
969
+ catch {
970
+ return null;
971
+ }
972
+ }
973
+ }
974
+ return null;
975
+ }
582
976
  }
583
977
  export function createRequestExecutor(deps) {
584
978
  return new HubRequestExecutor(deps);