@jsonstudio/llms 0.6.802 → 0.6.938

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 (186) hide show
  1. package/dist/bridge/routecodex-adapter.d.ts +74 -0
  2. package/dist/config-unified/enhanced-path-resolver.d.ts +5 -0
  3. package/dist/config-unified/unified-config.d.ts +26 -0
  4. package/dist/conversion/codec-registry.d.ts +10 -0
  5. package/dist/conversion/codecs/gemini-openai-codec.d.ts +16 -0
  6. package/dist/conversion/codecs/openai-openai-codec.d.ts +12 -0
  7. package/dist/conversion/codecs/responses-openai-codec.d.ts +12 -0
  8. package/dist/conversion/compat/profiles/chat-gemini.json +12 -0
  9. package/dist/conversion/config/config-manager.d.ts +212 -0
  10. package/dist/conversion/hub/config/types.d.ts +26 -0
  11. package/dist/conversion/hub/core/detour-registry.d.ts +9 -0
  12. package/dist/conversion/hub/core/hub-context.d.ts +21 -0
  13. package/dist/conversion/hub/core/index.d.ts +3 -0
  14. package/dist/conversion/hub/core/stage-driver.d.ts +30 -0
  15. package/dist/conversion/hub/format-adapters/anthropic-format-adapter.d.ts +16 -0
  16. package/dist/conversion/hub/format-adapters/chat-format-adapter.d.ts +17 -0
  17. package/dist/conversion/hub/format-adapters/gemini-format-adapter.d.ts +16 -0
  18. package/dist/conversion/hub/format-adapters/index.d.ts +21 -0
  19. package/dist/conversion/hub/hub-feature.d.ts +1 -0
  20. package/dist/conversion/hub/node-support.d.ts +19 -0
  21. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +11 -0
  22. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +3 -0
  23. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +7 -0
  24. package/dist/conversion/hub/pipeline/hub-pipeline.js +71 -14
  25. package/dist/conversion/hub/pipeline/stages/req_process/req_process_stage1_tool_governance/index.js +4 -0
  26. package/dist/conversion/hub/pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js +23 -1
  27. package/dist/conversion/hub/pipelines/inbound.d.ts +22 -0
  28. package/dist/conversion/hub/pipelines/outbound.d.ts +22 -0
  29. package/dist/conversion/hub/policy/policy-engine.d.ts +46 -0
  30. package/dist/conversion/hub/policy/policy-engine.js +176 -0
  31. package/dist/conversion/hub/policy/protocol-spec.d.ts +50 -0
  32. package/dist/conversion/hub/policy/protocol-spec.js +105 -0
  33. package/dist/conversion/hub/process/chat-process.d.ts +32 -0
  34. package/dist/conversion/hub/registry.d.ts +28 -0
  35. package/dist/conversion/hub/response/chat-response-utils.d.ts +6 -0
  36. package/dist/conversion/hub/response/provider-response.js +31 -0
  37. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +7 -0
  38. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +87 -1
  39. package/dist/conversion/hub/semantic-mappers/index.d.ts +4 -0
  40. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +21 -0
  41. package/dist/conversion/hub/standardized-bridge.d.ts +12 -0
  42. package/dist/conversion/hub/types/chat-schema.d.ts +112 -0
  43. package/dist/conversion/hub/types/errors.d.ts +5 -0
  44. package/dist/conversion/hub/types/format-envelope.d.ts +7 -0
  45. package/dist/conversion/hub/types/index.d.ts +6 -0
  46. package/dist/conversion/hub/types/json.d.ts +9 -0
  47. package/dist/conversion/hub/types/node.d.ts +31 -0
  48. package/dist/conversion/responses/responses-openai-bridge.js +263 -10
  49. package/dist/conversion/schema-validator.d.ts +7 -0
  50. package/dist/conversion/shared/args-mapping.d.ts +18 -0
  51. package/dist/conversion/shared/chat-request-filters.d.ts +9 -0
  52. package/dist/conversion/shared/errors.d.ts +1 -1
  53. package/dist/conversion/shared/gemini-tool-utils.js +61 -0
  54. package/dist/conversion/shared/jsonish.d.ts +3 -0
  55. package/dist/conversion/shared/mcp-injection.d.ts +2 -0
  56. package/dist/conversion/shared/media.d.ts +1 -0
  57. package/dist/conversion/shared/openai-message-normalize.d.ts +1 -0
  58. package/dist/conversion/shared/payload-budget.d.ts +13 -0
  59. package/dist/conversion/shared/reasoning-mapping.d.ts +5 -0
  60. package/dist/conversion/shared/responses-request-adapter.d.ts +1 -28
  61. package/dist/conversion/shared/responses-request-adapter.js +1 -430
  62. package/dist/conversion/shared/snapshot-hooks.js +58 -3
  63. package/dist/conversion/shared/tool-governor.js +8 -2
  64. package/dist/conversion/shared/tool-harvester.d.ts +31 -0
  65. package/dist/conversion/shared/tool-mapping.js +10 -29
  66. package/dist/conversion/types.d.ts +33 -0
  67. package/dist/filters/builtin/add-fields-filter.d.ts +8 -0
  68. package/dist/filters/builtin/blacklist-filter.d.ts +8 -0
  69. package/dist/filters/builtin/whitelist-filter.d.ts +8 -0
  70. package/dist/filters/engine.d.ts +16 -0
  71. package/dist/filters/special/request-tool-choice-policy.d.ts +11 -0
  72. package/dist/filters/special/response-finish-invariants.d.ts +11 -0
  73. package/dist/filters/special/response-openai-to-responses-bridge.d.ts +13 -0
  74. package/dist/filters/special/response-tool-arguments-blacklist.d.ts +12 -0
  75. package/dist/filters/special/response-tool-arguments-schema-converge.d.ts +13 -0
  76. package/dist/filters/special/response-tool-arguments-stringify.d.ts +9 -0
  77. package/dist/filters/special/response-tool-arguments-whitelist.d.ts +11 -0
  78. package/dist/filters/special/tool-filter-hooks.d.ts +19 -0
  79. package/dist/filters/special/tool-post-constraints.d.ts +31 -0
  80. package/dist/filters/types.d.ts +68 -0
  81. package/dist/filters/utils/fieldmap-loader.d.ts +2 -0
  82. package/dist/filters/utils/snapshot-writer.d.ts +10 -0
  83. package/dist/guidance/index.d.ts +3 -0
  84. package/dist/guidance/index.js +78 -83
  85. package/dist/http/sse-response.d.ts +22 -0
  86. package/dist/router/virtual-router/bootstrap.d.ts +6 -0
  87. package/dist/router/virtual-router/bootstrap.js +49 -5
  88. package/dist/router/virtual-router/classifier.d.ts +10 -0
  89. package/dist/router/virtual-router/engine-selection.js +98 -11
  90. package/dist/router/virtual-router/engine.js +177 -31
  91. package/dist/router/virtual-router/error-center.d.ts +10 -0
  92. package/dist/router/virtual-router/features.d.ts +3 -0
  93. package/dist/router/virtual-router/routing-instructions.d.ts +23 -1
  94. package/dist/router/virtual-router/routing-instructions.js +120 -30
  95. package/dist/router/virtual-router/types.d.ts +11 -0
  96. package/dist/servertool/engine.js +189 -16
  97. package/dist/servertool/handlers/apply-patch-guard.js +269 -0
  98. package/dist/servertool/handlers/exec-command-guard.js +558 -0
  99. package/dist/servertool/handlers/followup-message-trimmer.d.ts +16 -0
  100. package/dist/servertool/handlers/followup-message-trimmer.js +198 -0
  101. package/dist/servertool/handlers/followup-request-builder.d.ts +17 -0
  102. package/dist/servertool/handlers/followup-request-builder.js +122 -0
  103. package/dist/servertool/handlers/gemini-empty-reply-continue.js +252 -51
  104. package/dist/servertool/handlers/iflow-model-error-retry.js +12 -22
  105. package/dist/servertool/handlers/stop-message-auto.js +237 -75
  106. package/dist/servertool/handlers/vision.js +15 -27
  107. package/dist/servertool/handlers/web-search.js +17 -43
  108. package/dist/servertool/server-side-tools.d.ts +3 -0
  109. package/dist/servertool/server-side-tools.js +3 -0
  110. package/dist/sse/json-to-sse/anthropic-json-to-sse-converter.d.ts +2 -1
  111. package/dist/sse/json-to-sse/chat-json-to-sse-converter.d.ts +80 -0
  112. package/dist/sse/json-to-sse/event-generators/chat.d.ts +55 -0
  113. package/dist/sse/json-to-sse/event-generators/responses.d.ts +99 -0
  114. package/dist/sse/json-to-sse/gemini-json-to-sse-converter.d.ts +2 -1
  115. package/dist/sse/json-to-sse/responses-json-to-sse-converter.d.ts +80 -0
  116. package/dist/sse/json-to-sse/sequencers/anthropic-sequencer.d.ts +1 -1
  117. package/dist/sse/json-to-sse/sequencers/chat-sequencer.d.ts +2 -2
  118. package/dist/sse/json-to-sse/sequencers/gemini-sequencer.d.ts +1 -1
  119. package/dist/sse/json-to-sse/sequencers/responses-sequencer.d.ts +40 -0
  120. package/dist/sse/shared/chat-serializer.d.ts +4 -0
  121. package/dist/sse/shared/constants.d.ts +272 -0
  122. package/dist/sse/shared/serializers/anthropic-event-serializer.d.ts +1 -1
  123. package/dist/sse/shared/serializers/base-serializer.d.ts +158 -0
  124. package/dist/sse/shared/serializers/chat-event-serializer.d.ts +82 -0
  125. package/dist/sse/shared/serializers/gemini-event-serializer.d.ts +1 -1
  126. package/dist/sse/shared/serializers/index.d.ts +2 -1
  127. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +123 -0
  128. package/dist/sse/shared/serializers/types.d.ts +51 -0
  129. package/dist/sse/shared/utils.d.ts +254 -0
  130. package/dist/sse/shared/writer.d.ts +2 -2
  131. package/dist/sse/sse-to-json/anthropic-sse-to-json-converter.d.ts +1 -1
  132. package/dist/sse/sse-to-json/builders/anthropic-response-builder.d.ts +1 -1
  133. package/dist/sse/sse-to-json/builders/response-builder.d.ts +1 -1
  134. package/dist/sse/sse-to-json/chat-sse-to-json-converter.d.ts +2 -1
  135. package/dist/sse/sse-to-json/gemini-sse-to-json-converter.d.ts +2 -1
  136. package/dist/sse/sse-to-json/parsers/sse-parser.d.ts +73 -0
  137. package/dist/sse/sse-to-json/responses-sse-to-json-converter.d.ts +1 -1
  138. package/dist/sse/types/chat-types.d.ts +1 -1
  139. package/dist/sse/types/responses-types.d.ts +1 -1
  140. package/dist/tools/apply-patch/execution-capturer.d.ts +13 -0
  141. package/dist/tools/apply-patch/execution-capturer.js +158 -0
  142. package/dist/tools/apply-patch/regression-capturer.d.ts +1 -0
  143. package/dist/tools/apply-patch/regression-capturer.js +5 -4
  144. package/dist/tools/apply-patch/structured.js +109 -13
  145. package/dist/tools/apply-patch/validator.js +112 -18
  146. package/dist/tools/tool-registry.d.ts +8 -0
  147. package/dist/tools/tool-registry.js +2 -1
  148. package/package.json +4 -4
  149. package/dist/conversion/compat/actions/apply-patch-format-fixer.js +0 -233
  150. package/dist/conversion/config/compat-profiles.json +0 -38
  151. package/dist/conversion/hub/pipeline/context-limit.d.ts +0 -13
  152. package/dist/conversion/hub/pipeline/context-limit.js +0 -55
  153. package/dist/conversion/hub/response/server-side-tools.d.ts +0 -26
  154. package/dist/conversion/hub/response/server-side-tools.js +0 -383
  155. package/dist/conversion/shared/bridge-conversation-store.d.ts +0 -41
  156. package/dist/conversion/shared/bridge-conversation-store.js +0 -279
  157. package/dist/conversion/shared/bridge-request-adapter.d.ts +0 -28
  158. package/dist/conversion/shared/bridge-request-adapter.js +0 -430
  159. package/dist/conversion/shared/responses-id-utils.js +0 -42
  160. package/dist/conversion/shared/responses-instructions.js +0 -113
  161. package/dist/conversion/shared/responses-message-utils.d.ts +0 -15
  162. package/dist/conversion/shared/responses-message-utils.js +0 -206
  163. package/dist/conversion/shared/responses-metadata.js +0 -1
  164. package/dist/conversion/shared/responses-output-utils.d.ts +0 -7
  165. package/dist/conversion/shared/responses-output-utils.js +0 -108
  166. package/dist/conversion/shared/responses-types.d.ts +0 -33
  167. package/dist/conversion/shared/tool-normalizers.d.ts +0 -4
  168. package/dist/conversion/shared/tool-normalizers.js +0 -84
  169. package/dist/filters/special/request-streaming-to-nonstreaming.d.ts +0 -13
  170. package/dist/filters/special/request-streaming-to-nonstreaming.js +0 -39
  171. package/dist/filters/special/response-apply-patch-toon-decode.d.ts +0 -23
  172. package/dist/filters/special/response-apply-patch-toon-decode.js +0 -460
  173. package/dist/filters/special/response-tool-arguments-toon-decode.d.ts +0 -10
  174. package/dist/filters/special/response-tool-arguments-toon-decode.js +0 -154
  175. package/dist/servertool/flow-types.d.ts +0 -40
  176. package/dist/servertool/flow-types.js +0 -1
  177. package/dist/servertool/orchestration-types.d.ts +0 -33
  178. package/dist/servertool/orchestration-types.js +0 -1
  179. package/dist/servertool/vision-tool.d.ts +0 -2
  180. package/dist/servertool/vision-tool.js +0 -185
  181. package/dist/tools/patch-args-normalizer.d.ts +0 -15
  182. package/dist/tools/patch-args-normalizer.js +0 -472
  183. package/dist/utils/toon.d.ts +0 -4
  184. package/dist/utils/toon.js +0 -75
  185. /package/dist/{conversion/compat/actions/apply-patch-format-fixer.d.ts → servertool/handlers/apply-patch-guard.d.ts} +0 -0
  186. /package/dist/{conversion/shared/responses-types.js → servertool/handlers/exec-command-guard.d.ts} +0 -0
@@ -1,383 +0,0 @@
1
- import { buildOpenAIChatFromGeminiResponse } from '../../codecs/gemini-openai-codec.js';
2
- function asObject(value) {
3
- return value && typeof value === 'object' && !Array.isArray(value) ? value : null;
4
- }
5
- function getArray(value) {
6
- return Array.isArray(value) ? value : [];
7
- }
8
- function extractToolCalls(chatResponse) {
9
- const choices = getArray(chatResponse.choices);
10
- const calls = [];
11
- for (const choice of choices) {
12
- const choiceObj = asObject(choice);
13
- if (!choiceObj)
14
- continue;
15
- const message = asObject(choiceObj.message);
16
- if (!message)
17
- continue;
18
- const toolCalls = getArray(message.tool_calls);
19
- for (const raw of toolCalls) {
20
- const tc = asObject(raw);
21
- if (!tc)
22
- continue;
23
- const id = typeof tc.id === 'string' && tc.id.trim() ? tc.id.trim() : '';
24
- const fn = asObject(tc.function);
25
- const name = fn && typeof fn.name === 'string' && fn.name.trim() ? fn.name.trim() : '';
26
- const args = fn && typeof fn.arguments === 'string' ? fn.arguments : '';
27
- if (!id || !name)
28
- continue;
29
- calls.push({ id, name, arguments: args });
30
- }
31
- }
32
- return calls;
33
- }
34
- function extractTextFromChatLike(payload) {
35
- // 1) 解包常见包装层:data / response 节点
36
- let current = payload;
37
- const visited = new Set();
38
- while (current && typeof current === 'object' && !Array.isArray(current) && !visited.has(current)) {
39
- visited.add(current);
40
- if (Array.isArray(current.choices) || Array.isArray(current.output)) {
41
- break;
42
- }
43
- const data = current.data;
44
- if (data && typeof data === 'object' && !Array.isArray(data)) {
45
- current = data;
46
- continue;
47
- }
48
- const response = current.response;
49
- if (response && typeof response === 'object' && !Array.isArray(response)) {
50
- current = response;
51
- continue;
52
- }
53
- break;
54
- }
55
- // 2) 优先从 choices[].message.content 提取(OpenAI/GLM 兼容)
56
- const choices = getArray(current.choices);
57
- if (!choices.length)
58
- return '';
59
- const first = asObject(choices[0]);
60
- if (!first)
61
- return '';
62
- const message = asObject(first.message);
63
- if (!message)
64
- return '';
65
- const content = message.content;
66
- if (typeof content === 'string')
67
- return content.trim();
68
- const parts = getArray(content);
69
- const texts = [];
70
- for (const part of parts) {
71
- if (typeof part === 'string') {
72
- texts.push(part);
73
- }
74
- else if (part && typeof part === 'object') {
75
- const record = part;
76
- if (typeof record.text === 'string') {
77
- texts.push(record.text);
78
- }
79
- else if (typeof record.content === 'string') {
80
- texts.push(record.content);
81
- }
82
- }
83
- }
84
- const joinedFromChoices = texts.join('\n').trim();
85
- if (joinedFromChoices) {
86
- return joinedFromChoices;
87
- }
88
- // 3) 回退:从 output[].content[] 中提取(部分 Responses/自定义后端)
89
- const output = current.output;
90
- if (Array.isArray(output)) {
91
- const altTexts = [];
92
- for (const entry of output) {
93
- if (!entry || typeof entry !== 'object')
94
- continue;
95
- const blocks = entry.content;
96
- const blockArray = Array.isArray(blocks) ? blocks : [];
97
- for (const block of blockArray) {
98
- if (!block || typeof block !== 'object')
99
- continue;
100
- const record = block;
101
- if (typeof record.text === 'string') {
102
- altTexts.push(record.text);
103
- }
104
- else if (typeof record.output_text === 'string') {
105
- altTexts.push(record.output_text);
106
- }
107
- else if (typeof record.content === 'string') {
108
- altTexts.push(record.content);
109
- }
110
- }
111
- }
112
- const joined = altTexts.join('\n').trim();
113
- if (joined) {
114
- return joined;
115
- }
116
- }
117
- return '';
118
- }
119
- function getWebSearchConfig(ctx) {
120
- const raw = ctx.webSearch;
121
- const record = raw && typeof raw === 'object' && !Array.isArray(raw) ? raw : null;
122
- if (!record)
123
- return undefined;
124
- const enginesRaw = Array.isArray(record.engines) ? record.engines : [];
125
- const engines = [];
126
- for (const entry of enginesRaw) {
127
- const obj = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
128
- if (!obj)
129
- continue;
130
- const id = typeof obj.id === 'string' && obj.id.trim() ? obj.id.trim() : undefined;
131
- const providerKey = typeof obj.providerKey === 'string' && obj.providerKey.trim()
132
- ? obj.providerKey.trim()
133
- : undefined;
134
- if (!id || !providerKey)
135
- continue;
136
- engines.push({
137
- id,
138
- providerKey,
139
- description: typeof obj.description === 'string' && obj.description.trim() ? obj.description.trim() : undefined,
140
- default: obj.default === true
141
- });
142
- }
143
- if (!engines.length) {
144
- return undefined;
145
- }
146
- const config = { engines };
147
- if (typeof record.injectPolicy === 'string') {
148
- const val = String(record.injectPolicy).trim().toLowerCase();
149
- if (val === 'always' || val === 'selective') {
150
- config.injectPolicy = val;
151
- }
152
- }
153
- return config;
154
- }
155
- function resolveWebSearchEngine(config, engineId) {
156
- const trimmedId = engineId && typeof engineId === 'string' && engineId.trim() ? engineId.trim() : undefined;
157
- if (trimmedId) {
158
- const byId = config.engines.find((e) => e.id === trimmedId);
159
- if (byId) {
160
- return byId;
161
- }
162
- }
163
- const byDefault = config.engines.find((e) => e.default);
164
- if (byDefault) {
165
- return byDefault;
166
- }
167
- if (config.engines.length === 1) {
168
- return config.engines[0];
169
- }
170
- return undefined;
171
- }
172
- function isGeminiWebSearchEngine(engine) {
173
- const key = engine.providerKey.toLowerCase();
174
- return key.startsWith('gemini-cli.') || key.startsWith('antigravity.');
175
- }
176
- function logServerToolWebSearch(engine, requestId, query) {
177
- const providerAlias = engine.providerKey.split('.')[0] || engine.providerKey;
178
- const backendLabel = `${providerAlias}:${engine.id}`;
179
- const prefix = `[server-tool][web_search][${backendLabel}]`;
180
- const line = `${prefix} requestId=${requestId} query=${JSON.stringify(query)}`;
181
- // 深蓝色输出
182
- // eslint-disable-next-line no-console
183
- console.log(`\x1b[38;5;27m${line}\x1b[0m`);
184
- }
185
- function resolveEnvServerSideToolsEnabled() {
186
- const raw = (process.env.ROUTECODEX_SERVER_SIDE_TOOLS || process.env.RCC_SERVER_SIDE_TOOLS || '').trim().toLowerCase();
187
- if (!raw)
188
- return false;
189
- if (raw === '1' || raw === 'true' || raw === 'yes')
190
- return true;
191
- if (raw === 'web_search')
192
- return true;
193
- return false;
194
- }
195
- export async function runServerSideToolEngine(options) {
196
- const base = asObject(options.chatResponse);
197
- if (!base) {
198
- return { mode: 'passthrough', finalChatResponse: options.chatResponse };
199
- }
200
- // 内建 OpenAI Responses `/v1/responses` 已经支持 server-side web_search。
201
- // 仅当“入口端点为 /v1/responses 且 providerProtocol 也是 openai-responses”时保持透传,
202
- // 避免对 Responses→Responses 的链路重复执行搜索回环;
203
- // 其它场景(例如 /v1/responses → gemini/glm 后端)仍允许 server-side web_search 生效。
204
- const entry = (options.entryEndpoint || '').toLowerCase();
205
- if (options.providerProtocol === 'openai-responses' && entry.includes('/v1/responses')) {
206
- return { mode: 'passthrough', finalChatResponse: base };
207
- }
208
- // Feature flag: keep behaviour fully backwards-compatible unless explicitly enabled.
209
- const toolsEnabled = resolveEnvServerSideToolsEnabled();
210
- const toolCalls = extractToolCalls(base);
211
- const webSearchCall = toolCalls.find((tc) => tc.name === 'web_search');
212
- if (!toolsEnabled || !webSearchCall || !options.providerInvoker) {
213
- return { mode: 'passthrough', finalChatResponse: base };
214
- }
215
- const webSearchConfig = getWebSearchConfig(options.adapterContext);
216
- if (!webSearchConfig) {
217
- return { mode: 'passthrough', finalChatResponse: base };
218
- }
219
- let parsedArgs = {};
220
- if (webSearchCall.arguments && typeof webSearchCall.arguments === 'string') {
221
- try {
222
- parsedArgs = JSON.parse(webSearchCall.arguments);
223
- }
224
- catch {
225
- parsedArgs = {};
226
- }
227
- }
228
- const query = typeof parsedArgs?.query === 'string' && parsedArgs.query.trim()
229
- ? parsedArgs.query.trim()
230
- : undefined;
231
- const engineId = typeof parsedArgs?.engine === 'string' && parsedArgs.engine.trim()
232
- ? parsedArgs.engine.trim()
233
- : undefined;
234
- const recency = typeof parsedArgs?.recency === 'string' && parsedArgs.recency.trim()
235
- ? parsedArgs.recency.trim()
236
- : undefined;
237
- const count = typeof parsedArgs?.count === 'number' && Number.isFinite(parsedArgs.count)
238
- ? Math.floor(parsedArgs.count)
239
- : undefined;
240
- if (!query) {
241
- return { mode: 'passthrough', finalChatResponse: base };
242
- }
243
- const engine = resolveWebSearchEngine(webSearchConfig, engineId);
244
- if (!engine) {
245
- return { mode: 'passthrough', finalChatResponse: base };
246
- }
247
- const providerInvoker = options.providerInvoker;
248
- // 1) Call search backend (secondary request)
249
- let searchSummary = '';
250
- try {
251
- logServerToolWebSearch(engine, options.requestId, query);
252
- if (isGeminiWebSearchEngine(engine)) {
253
- const geminiPayload = {
254
- model: engine.id,
255
- contents: [
256
- {
257
- role: 'user',
258
- parts: [
259
- {
260
- text: query
261
- }
262
- ]
263
- }
264
- ],
265
- // Cloud Code search models treat googleSearch as the web search tool.
266
- tools: [
267
- {
268
- googleSearch: {}
269
- }
270
- ]
271
- };
272
- const backend = await providerInvoker({
273
- providerKey: engine.providerKey,
274
- providerType: undefined,
275
- modelId: engine.id,
276
- providerProtocol: 'gemini-chat',
277
- payload: geminiPayload,
278
- entryEndpoint: '/v1/models/gemini:generateContent',
279
- requestId: `${options.requestId}:web_search:${engine.id}`
280
- });
281
- const backendResponse = asObject(backend.providerResponse);
282
- if (backendResponse) {
283
- const chatLike = asObject(buildOpenAIChatFromGeminiResponse(backendResponse));
284
- if (chatLike) {
285
- searchSummary = extractTextFromChatLike(chatLike);
286
- }
287
- }
288
- }
289
- else {
290
- const backendPayload = {
291
- model: engine.providerKey,
292
- messages: [
293
- {
294
- role: 'system',
295
- content: 'You are a web search engine. Answer with up-to-date information based on the open internet.'
296
- },
297
- {
298
- role: 'user',
299
- content: query
300
- }
301
- ],
302
- stream: false,
303
- web_search: {
304
- query,
305
- ...(recency ? { recency } : {}),
306
- ...(typeof count === 'number' ? { count } : {}),
307
- engine: engine.id
308
- }
309
- };
310
- const backend = await providerInvoker({
311
- providerKey: engine.providerKey,
312
- providerType: undefined,
313
- modelId: undefined,
314
- providerProtocol: options.providerProtocol,
315
- payload: backendPayload,
316
- entryEndpoint: '/v1/chat/completions',
317
- requestId: `${options.requestId}:web_search:${engine.id}`
318
- });
319
- const backendResponse = asObject(backend.providerResponse);
320
- if (backendResponse) {
321
- searchSummary = extractTextFromChatLike(backendResponse);
322
- }
323
- }
324
- }
325
- catch {
326
- // fall back to passthrough if backend fails
327
- return { mode: 'passthrough', finalChatResponse: base };
328
- }
329
- if (!searchSummary) {
330
- return { mode: 'passthrough', finalChatResponse: base };
331
- }
332
- // 2) Call main provider again with synthesized prompt (third request)
333
- const ctxTarget = options.adapterContext.target;
334
- const target = ctxTarget && typeof ctxTarget === 'object' && !Array.isArray(ctxTarget)
335
- ? ctxTarget
336
- : undefined;
337
- const mainProviderKey = typeof target?.providerKey === 'string' && target.providerKey.trim()
338
- ? target.providerKey.trim()
339
- : undefined;
340
- const mainModelId = (typeof base.model === 'string' && base.model.trim()
341
- ? base.model.trim()
342
- : typeof target?.modelId === 'string' && target.modelId.trim()
343
- ? target.modelId.trim()
344
- : undefined) ?? 'gpt-4o-mini';
345
- if (!mainProviderKey) {
346
- return { mode: 'passthrough', finalChatResponse: base };
347
- }
348
- let finalResponse = base;
349
- try {
350
- const followupPayload = {
351
- model: mainModelId,
352
- messages: [
353
- {
354
- role: 'system',
355
- content: 'You are an assistant that answers using the provided web_search results. Use them as the primary source.'
356
- },
357
- {
358
- role: 'user',
359
- content: `User question: ${query}\n\nWeb search results:\n${searchSummary}`
360
- }
361
- ],
362
- stream: false
363
- };
364
- const followup = await providerInvoker({
365
- providerKey: mainProviderKey,
366
- providerType: undefined,
367
- modelId: mainModelId,
368
- providerProtocol: options.providerProtocol,
369
- payload: followupPayload,
370
- entryEndpoint: '/v1/chat/completions',
371
- requestId: `${options.requestId}:web_search_followup`
372
- });
373
- const followupResponse = asObject(followup.providerResponse);
374
- if (followupResponse) {
375
- finalResponse = followupResponse;
376
- }
377
- }
378
- catch {
379
- // If follow-up fails, keep original base response to avoid breaking clients.
380
- finalResponse = base;
381
- }
382
- return { mode: 'web_search_flow', finalChatResponse: finalResponse };
383
- }
@@ -1,41 +0,0 @@
1
- import type { ResponsesRequestContext } from './bridge-request-adapter.js';
2
- declare class BridgeConversationStore {
3
- private requestMap;
4
- private responseIndex;
5
- captureRequestContext(args: {
6
- requestId: string;
7
- payload: Record<string, unknown>;
8
- context: ResponsesRequestContext;
9
- }): void;
10
- recordResponse(args: {
11
- requestId: string;
12
- response: Record<string, unknown>;
13
- }): void;
14
- resumeConversation(responseId: string, submitPayload: Record<string, unknown>, options?: {
15
- requestId?: string;
16
- }): {
17
- payload: Record<string, unknown>;
18
- meta: Record<string, unknown>;
19
- };
20
- clearRequest(requestId: string): void;
21
- private cleanupEntry;
22
- private prune;
23
- }
24
- export declare const bridgeConversationStore: BridgeConversationStore;
25
- export declare function captureBridgeRequestContext(args: {
26
- requestId: string;
27
- payload: Record<string, unknown>;
28
- context: ResponsesRequestContext;
29
- }): void;
30
- export declare function recordBridgeResponse(args: {
31
- requestId: string;
32
- response: Record<string, unknown>;
33
- }): void;
34
- export declare function resumeBridgeConversation(responseId: string, submitPayload: Record<string, unknown>, options?: {
35
- requestId?: string;
36
- }): {
37
- payload: Record<string, unknown>;
38
- meta: Record<string, unknown>;
39
- };
40
- export declare function clearBridgeConversationByRequestId(requestId: string): void;
41
- export {};
@@ -1,279 +0,0 @@
1
- const TTL_MS = 1000 * 60 * 30; // 30 minutes safety window
2
- function cloneDeep(value) {
3
- try {
4
- if (typeof globalThis.structuredClone === 'function') {
5
- return globalThis.structuredClone(value);
6
- }
7
- }
8
- catch {
9
- /* ignore */
10
- }
11
- return JSON.parse(JSON.stringify(value ?? null));
12
- }
13
- function isRecord(value) {
14
- return Boolean(value && typeof value === 'object' && !Array.isArray(value));
15
- }
16
- function coerceInputArray(input) {
17
- if (!Array.isArray(input)) {
18
- return [];
19
- }
20
- return cloneDeep(input);
21
- }
22
- function coerceTools(tools) {
23
- if (!Array.isArray(tools)) {
24
- return undefined;
25
- }
26
- return cloneDeep(tools);
27
- }
28
- function pickPersistedFields(payload) {
29
- const fields = [
30
- 'model',
31
- 'instructions',
32
- 'metadata',
33
- 'include',
34
- 'store',
35
- 'tool_choice',
36
- 'parallel_tool_calls',
37
- 'response_format',
38
- 'temperature',
39
- 'top_p',
40
- 'max_output_tokens',
41
- 'max_tokens',
42
- 'stop',
43
- 'user',
44
- 'modal',
45
- 'truncation_strategy',
46
- 'previous_response_id',
47
- 'reasoning',
48
- 'attachments',
49
- 'input_audio',
50
- 'output_audio'
51
- ];
52
- const result = {};
53
- for (const key of fields) {
54
- if (payload[key] !== undefined) {
55
- result[key] = cloneDeep(payload[key]);
56
- }
57
- }
58
- return result;
59
- }
60
- function normalizeOutputItemToInput(item) {
61
- const type = typeof item.type === 'string' ? item.type : '';
62
- if (type === 'message' || type === 'reasoning') {
63
- const role = typeof item.role === 'string' ? item.role : 'assistant';
64
- const content = Array.isArray(item.content) ? cloneDeep(item.content) : (typeof item.text === 'string' ? [{ type: 'text', text: item.text }] : []);
65
- return {
66
- type: 'message',
67
- role,
68
- content,
69
- message: { role, content }
70
- };
71
- }
72
- if (type === 'function_call') {
73
- const callId = typeof item.call_id === 'string' ? item.call_id : (typeof item.id === 'string' ? item.id : undefined);
74
- return {
75
- type: 'function_call',
76
- role: 'assistant',
77
- id: typeof item.id === 'string' ? item.id : undefined,
78
- call_id: callId,
79
- name: typeof item.name === 'string' ? item.name : undefined,
80
- arguments: item.arguments,
81
- function: isRecord(item.function)
82
- ? cloneDeep(item.function)
83
- : (typeof item.name === 'string'
84
- ? { name: item.name, arguments: item.arguments }
85
- : undefined)
86
- };
87
- }
88
- return null;
89
- }
90
- function convertOutputToInputItems(response) {
91
- const output = Array.isArray(response.output) ? response.output : [];
92
- const items = [];
93
- for (const entry of output) {
94
- if (!entry || typeof entry !== 'object')
95
- continue;
96
- const mapped = normalizeOutputItemToInput(entry);
97
- if (mapped)
98
- items.push(mapped);
99
- }
100
- return items;
101
- }
102
- function normalizeSubmittedToolOutputs(toolOutputs) {
103
- const items = [];
104
- const submitted = [];
105
- toolOutputs.forEach((entry, index) => {
106
- if (!entry || typeof entry !== 'object')
107
- return;
108
- const rec = entry;
109
- const rawId = typeof rec.tool_call_id === 'string'
110
- ? rec.tool_call_id
111
- : typeof rec.call_id === 'string'
112
- ? rec.call_id
113
- : typeof rec.id === 'string'
114
- ? rec.id
115
- : undefined;
116
- const trimmed = typeof rawId === 'string' ? rawId.trim() : '';
117
- const callId = trimmed.length ? trimmed : `fc_resume_${index}`;
118
- const outputValue = rec.output ?? null;
119
- const normalizedOutput = typeof outputValue === 'string'
120
- ? outputValue
121
- : (() => {
122
- try {
123
- return JSON.stringify(outputValue ?? null);
124
- }
125
- catch {
126
- return String(outputValue ?? '');
127
- }
128
- })();
129
- items.push({
130
- type: 'function_call_output',
131
- id: callId,
132
- call_id: callId,
133
- output: normalizedOutput
134
- });
135
- submitted.push({ callId, originalId: rawId ?? callId, outputText: normalizedOutput });
136
- });
137
- return { items, submitted };
138
- }
139
- class BridgeConversationStore {
140
- requestMap = new Map();
141
- responseIndex = new Map();
142
- captureRequestContext(args) {
143
- const { requestId, payload, context } = args;
144
- if (!requestId || !payload)
145
- return;
146
- this.prune();
147
- const entry = {
148
- requestId,
149
- basePayload: pickPersistedFields(payload),
150
- input: coerceInputArray(context.input),
151
- tools: coerceTools(context.toolsRaw || (Array.isArray(payload.tools) ? payload.tools : undefined)),
152
- createdAt: Date.now(),
153
- updatedAt: Date.now()
154
- };
155
- if (typeof payload.model === 'string' && payload.model.trim()) {
156
- entry.basePayload.model = payload.model;
157
- }
158
- if (typeof payload.stream === 'boolean') {
159
- entry.basePayload.stream = payload.stream;
160
- }
161
- if (entry.tools) {
162
- entry.basePayload.tools = entry.tools;
163
- }
164
- this.requestMap.set(requestId, entry);
165
- }
166
- recordResponse(args) {
167
- const entry = this.requestMap.get(args.requestId);
168
- if (!entry)
169
- return;
170
- const response = args.response;
171
- const responseId = typeof response.id === 'string' ? response.id : undefined;
172
- if (!responseId)
173
- return;
174
- const assistantBlocks = convertOutputToInputItems(response);
175
- if (assistantBlocks.length) {
176
- entry.input.push(...assistantBlocks);
177
- }
178
- entry.lastResponseId = responseId;
179
- entry.updatedAt = Date.now();
180
- this.responseIndex.set(responseId, entry);
181
- }
182
- resumeConversation(responseId, submitPayload, options) {
183
- if (typeof responseId !== 'string' || !responseId.trim()) {
184
- throw new Error('Bridge conversation requires valid response_id');
185
- }
186
- this.prune();
187
- const entry = this.responseIndex.get(responseId);
188
- if (!entry) {
189
- throw new Error('Bridge conversation expired or not found');
190
- }
191
- const toolOutputs = Array.isArray(submitPayload.tool_outputs)
192
- ? submitPayload.tool_outputs
193
- : [];
194
- if (!toolOutputs.length) {
195
- throw new Error('tool_outputs array is required when submitting tool results');
196
- }
197
- const mergedInput = coerceInputArray(entry.input);
198
- const normalizedOutputs = normalizeSubmittedToolOutputs(toolOutputs);
199
- mergedInput.push(...normalizedOutputs.items);
200
- const payload = cloneDeep(entry.basePayload);
201
- payload.input = mergedInput;
202
- payload.stream = true;
203
- payload.previous_response_id = responseId;
204
- if (Array.isArray(entry.tools) && entry.tools.length) {
205
- payload.tools = cloneDeep(entry.tools);
206
- }
207
- if (typeof submitPayload.model === 'string' && submitPayload.model.trim()) {
208
- payload.model = submitPayload.model.trim();
209
- }
210
- if (submitPayload.metadata && isRecord(submitPayload.metadata)) {
211
- payload.metadata = { ...payload.metadata, ...cloneDeep(submitPayload.metadata) };
212
- }
213
- delete payload.tool_outputs;
214
- delete payload.response_id;
215
- this.cleanupEntry(entry, responseId);
216
- return {
217
- payload,
218
- meta: {
219
- restoredFromResponseId: responseId,
220
- previousRequestId: entry.requestId,
221
- toolOutputs: toolOutputs.length,
222
- toolOutputsDetailed: normalizedOutputs.submitted,
223
- requestId: options?.requestId
224
- }
225
- };
226
- }
227
- clearRequest(requestId) {
228
- const entry = this.requestMap.get(requestId);
229
- if (!entry)
230
- return;
231
- this.requestMap.delete(requestId);
232
- if (entry.lastResponseId) {
233
- this.responseIndex.delete(entry.lastResponseId);
234
- }
235
- }
236
- cleanupEntry(entry, responseId) {
237
- this.responseIndex.delete(responseId);
238
- this.requestMap.delete(entry.requestId);
239
- }
240
- prune() {
241
- const now = Date.now();
242
- for (const [requestId, entry] of this.requestMap.entries()) {
243
- if (now - entry.updatedAt > TTL_MS) {
244
- this.requestMap.delete(requestId);
245
- if (entry.lastResponseId) {
246
- this.responseIndex.delete(entry.lastResponseId);
247
- }
248
- }
249
- }
250
- for (const [respId, entry] of this.responseIndex.entries()) {
251
- if (!this.requestMap.has(entry.requestId)) {
252
- this.responseIndex.delete(respId);
253
- }
254
- }
255
- }
256
- }
257
- export const bridgeConversationStore = new BridgeConversationStore();
258
- export function captureBridgeRequestContext(args) {
259
- try {
260
- bridgeConversationStore.captureRequestContext(args);
261
- }
262
- catch {
263
- /* ignore capture failures */
264
- }
265
- }
266
- export function recordBridgeResponse(args) {
267
- try {
268
- bridgeConversationStore.recordResponse(args);
269
- }
270
- catch {
271
- /* ignore */
272
- }
273
- }
274
- export function resumeBridgeConversation(responseId, submitPayload, options) {
275
- return bridgeConversationStore.resumeConversation(responseId, submitPayload, options);
276
- }
277
- export function clearBridgeConversationByRequestId(requestId) {
278
- bridgeConversationStore.clearRequest(requestId);
279
- }