@jsonstudio/llms 0.6.215 → 0.6.230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/codecs/gemini-openai-codec.js +83 -1
- package/dist/conversion/compat/actions/glm-web-search.d.ts +2 -0
- package/dist/conversion/compat/actions/glm-web-search.js +66 -0
- package/dist/conversion/compat/profiles/chat-glm.json +4 -1
- package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
- package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
- package/dist/conversion/hub/pipeline/hub-pipeline.js +9 -1
- package/dist/conversion/hub/process/chat-process.js +131 -1
- package/dist/conversion/hub/response/provider-response.d.ts +22 -0
- package/dist/conversion/hub/response/provider-response.js +12 -1
- package/dist/conversion/hub/response/server-side-tools.d.ts +26 -0
- package/dist/conversion/hub/response/server-side-tools.js +326 -0
- package/dist/conversion/hub/types/standardized.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +49 -3
- package/dist/conversion/shared/tool-mapping.js +25 -2
- package/dist/router/virtual-router/bootstrap.js +273 -40
- package/dist/router/virtual-router/context-advisor.d.ts +0 -2
- package/dist/router/virtual-router/context-advisor.js +0 -12
- package/dist/router/virtual-router/engine.d.ts +7 -2
- package/dist/router/virtual-router/engine.js +161 -82
- package/dist/router/virtual-router/types.d.ts +21 -2
- package/dist/sse/json-to-sse/event-generators/responses.js +15 -3
- package/dist/sse/sse-to-json/gemini-sse-to-json-converter.js +27 -1
- package/dist/sse/types/gemini-types.d.ts +20 -1
- package/dist/sse/types/responses-types.js +1 -1
- package/package.json +1 -1
|
@@ -183,16 +183,20 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
183
183
|
const textParts = [];
|
|
184
184
|
const reasoningParts = [];
|
|
185
185
|
const toolCalls = [];
|
|
186
|
+
const toolResultTexts = [];
|
|
187
|
+
const toolOutputs = [];
|
|
186
188
|
for (const part of parts) {
|
|
187
189
|
if (!part || typeof part !== 'object')
|
|
188
190
|
continue;
|
|
189
191
|
const pObj = part;
|
|
192
|
+
// 1. Text part
|
|
190
193
|
if (typeof pObj.text === 'string') {
|
|
191
194
|
const t = pObj.text;
|
|
192
195
|
if (t && t.trim().length)
|
|
193
196
|
textParts.push(t);
|
|
194
197
|
continue;
|
|
195
198
|
}
|
|
199
|
+
// 2. Content array (nested structure)
|
|
196
200
|
if (Array.isArray(pObj.content)) {
|
|
197
201
|
for (const inner of pObj.content) {
|
|
198
202
|
if (typeof inner === 'string') {
|
|
@@ -204,10 +208,20 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
204
208
|
}
|
|
205
209
|
continue;
|
|
206
210
|
}
|
|
211
|
+
// 3. Reasoning part (channel mode)
|
|
207
212
|
if (typeof pObj.reasoning === 'string') {
|
|
208
213
|
reasoningParts.push(pObj.reasoning);
|
|
209
214
|
continue;
|
|
210
215
|
}
|
|
216
|
+
// 4. Thought part (thinking/extended thinking)
|
|
217
|
+
if (typeof pObj.thought === 'string') {
|
|
218
|
+
const thoughtText = pObj.thought.trim();
|
|
219
|
+
if (thoughtText.length) {
|
|
220
|
+
reasoningParts.push(thoughtText);
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// 5. Function call (tool call)
|
|
211
225
|
if (pObj.functionCall && typeof pObj.functionCall === 'object') {
|
|
212
226
|
const fc = pObj.functionCall;
|
|
213
227
|
const name = typeof fc.name === 'string' ? String(fc.name) : undefined;
|
|
@@ -239,8 +253,68 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
239
253
|
toolCalls.push(toolCall);
|
|
240
254
|
continue;
|
|
241
255
|
}
|
|
256
|
+
// 6. Function response (tool result)
|
|
257
|
+
if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
|
|
258
|
+
const fr = pObj.functionResponse;
|
|
259
|
+
const callId = typeof fr.id === 'string' && fr.id.trim().length ? String(fr.id) : undefined;
|
|
260
|
+
const name = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
|
|
261
|
+
const resp = fr.response;
|
|
262
|
+
let contentStr = '';
|
|
263
|
+
if (typeof resp === 'string') {
|
|
264
|
+
contentStr = resp;
|
|
265
|
+
}
|
|
266
|
+
else if (resp != null) {
|
|
267
|
+
try {
|
|
268
|
+
contentStr = JSON.stringify(resp);
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
contentStr = String(resp);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (contentStr && contentStr.trim().length) {
|
|
275
|
+
toolResultTexts.push(contentStr);
|
|
276
|
+
if (callId || name) {
|
|
277
|
+
const entry = {
|
|
278
|
+
tool_call_id: callId ?? undefined,
|
|
279
|
+
id: callId ?? undefined,
|
|
280
|
+
content: contentStr
|
|
281
|
+
};
|
|
282
|
+
if (name) {
|
|
283
|
+
entry.name = name;
|
|
284
|
+
}
|
|
285
|
+
toolOutputs.push(entry);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
// 7. Executable code (code_interpreter)
|
|
291
|
+
if (pObj.executableCode && typeof pObj.executableCode === 'object') {
|
|
292
|
+
const code = pObj.executableCode;
|
|
293
|
+
const language = typeof code.language === 'string' ? code.language : 'python';
|
|
294
|
+
const codeText = typeof code.code === 'string' ? code.code : '';
|
|
295
|
+
if (codeText.trim().length) {
|
|
296
|
+
// Append as text with code block formatting
|
|
297
|
+
textParts.push(`\`\`\`${language}\n${codeText}\n\`\`\``);
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
// 8. Code execution result
|
|
302
|
+
if (pObj.codeExecutionResult && typeof pObj.codeExecutionResult === 'object') {
|
|
303
|
+
const result = pObj.codeExecutionResult;
|
|
304
|
+
const outcome = typeof result.outcome === 'string' ? result.outcome : '';
|
|
305
|
+
const output = typeof result.output === 'string' ? result.output : '';
|
|
306
|
+
if (output.trim().length) {
|
|
307
|
+
textParts.push(`[Code Output${outcome ? ` (${outcome})` : ''}]:\n${output}`);
|
|
308
|
+
}
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
242
311
|
}
|
|
312
|
+
const hasToolCalls = toolCalls.length > 0;
|
|
243
313
|
const finish_reason = (() => {
|
|
314
|
+
// If the model is emitting tool calls, treat this turn as a tool_calls
|
|
315
|
+
// completion so downstream tool governance can continue the loop.
|
|
316
|
+
if (hasToolCalls)
|
|
317
|
+
return 'tool_calls';
|
|
244
318
|
const fr = String(primary?.finishReason || '').toUpperCase();
|
|
245
319
|
if (fr === 'MAX_TOKENS')
|
|
246
320
|
return 'length';
|
|
@@ -265,9 +339,14 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
265
339
|
usage.total_tokens = totalTokens;
|
|
266
340
|
const combinedText = textParts.join('\n');
|
|
267
341
|
const normalized = combinedText.length ? normalizeChatMessageContent(combinedText) : { contentText: undefined, reasoningText: undefined };
|
|
342
|
+
const baseContent = normalized.contentText ?? combinedText ?? '';
|
|
343
|
+
const toolResultBlock = toolResultTexts.length ? toolResultTexts.join('\n') : '';
|
|
344
|
+
const finalContent = toolResultBlock && baseContent
|
|
345
|
+
? `${baseContent}\n${toolResultBlock}`
|
|
346
|
+
: baseContent || toolResultBlock;
|
|
268
347
|
const chatMsg = {
|
|
269
348
|
role,
|
|
270
|
-
content:
|
|
349
|
+
content: finalContent
|
|
271
350
|
};
|
|
272
351
|
if (typeof normalized.reasoningText === 'string' && normalized.reasoningText.trim().length) {
|
|
273
352
|
reasoningParts.push(normalized.reasoningText.trim());
|
|
@@ -314,6 +393,9 @@ export function buildOpenAIChatFromGeminiResponse(payload) {
|
|
|
314
393
|
if (Object.keys(usage).length > 0) {
|
|
315
394
|
chatResp.usage = usage;
|
|
316
395
|
}
|
|
396
|
+
if (toolOutputs.length > 0) {
|
|
397
|
+
chatResp.tool_outputs = toolOutputs;
|
|
398
|
+
}
|
|
317
399
|
const preservedReasoning = consumeResponsesReasoning(chatResp.id);
|
|
318
400
|
if (preservedReasoning && preservedReasoning.length) {
|
|
319
401
|
chatResp.__responses_reasoning = preservedReasoning;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
2
|
+
export function applyGlmWebSearchRequestTransform(payload) {
|
|
3
|
+
const root = structuredClone(payload);
|
|
4
|
+
const webSearchRaw = root.web_search;
|
|
5
|
+
if (!isRecord(webSearchRaw)) {
|
|
6
|
+
return root;
|
|
7
|
+
}
|
|
8
|
+
const webSearch = webSearchRaw;
|
|
9
|
+
const queryValue = webSearch.query;
|
|
10
|
+
const recencyValue = webSearch.recency;
|
|
11
|
+
const countValue = webSearch.count;
|
|
12
|
+
const query = typeof queryValue === 'string' ? queryValue.trim() : '';
|
|
13
|
+
const recency = typeof recencyValue === 'string' ? recencyValue.trim() : undefined;
|
|
14
|
+
let count;
|
|
15
|
+
if (typeof countValue === 'number' && Number.isFinite(countValue)) {
|
|
16
|
+
const normalized = Math.floor(countValue);
|
|
17
|
+
if (normalized >= 1 && normalized <= 50) {
|
|
18
|
+
count = normalized;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
if (!query) {
|
|
22
|
+
// No meaningful search query, drop the helper object and passthrough.
|
|
23
|
+
delete root.web_search;
|
|
24
|
+
return root;
|
|
25
|
+
}
|
|
26
|
+
const toolsValue = root.tools;
|
|
27
|
+
const tools = Array.isArray(toolsValue) ? [...toolsValue] : [];
|
|
28
|
+
let existingIndex = -1;
|
|
29
|
+
for (let i = 0; i < tools.length; i += 1) {
|
|
30
|
+
const tool = tools[i];
|
|
31
|
+
if (isRecord(tool) && typeof tool.type === 'string' && tool.type === 'web_search') {
|
|
32
|
+
existingIndex = i;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const webSearchConfig = {
|
|
37
|
+
enable: true,
|
|
38
|
+
search_query: query
|
|
39
|
+
};
|
|
40
|
+
if (recency) {
|
|
41
|
+
webSearchConfig.search_recency_filter = recency;
|
|
42
|
+
}
|
|
43
|
+
if (typeof count === 'number') {
|
|
44
|
+
webSearchConfig.count = count;
|
|
45
|
+
}
|
|
46
|
+
const baseTool = existingIndex >= 0 && isRecord(tools[existingIndex])
|
|
47
|
+
? { ...tools[existingIndex] }
|
|
48
|
+
: {};
|
|
49
|
+
baseTool.type = 'web_search';
|
|
50
|
+
const existingWebSearch = isRecord(baseTool.web_search)
|
|
51
|
+
? baseTool.web_search
|
|
52
|
+
: {};
|
|
53
|
+
baseTool.web_search = {
|
|
54
|
+
...existingWebSearch,
|
|
55
|
+
...webSearchConfig
|
|
56
|
+
};
|
|
57
|
+
if (existingIndex >= 0) {
|
|
58
|
+
tools[existingIndex] = baseTool;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
tools.push(baseTool);
|
|
62
|
+
}
|
|
63
|
+
root.tools = tools;
|
|
64
|
+
delete root.web_search;
|
|
65
|
+
return root;
|
|
66
|
+
}
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"request": {
|
|
22
22
|
"allowTopLevel": [
|
|
23
23
|
"model", "messages", "stream", "thinking", "do_sample", "temperature", "top_p",
|
|
24
|
-
"max_tokens", "tools", "tool_choice", "stop", "response_format"
|
|
24
|
+
"max_tokens", "tools", "tool_choice", "stop", "response_format", "web_search"
|
|
25
25
|
],
|
|
26
26
|
"messages": {
|
|
27
27
|
"allowedRoles": ["system", "user", "assistant", "tool"],
|
|
@@ -157,6 +157,9 @@
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
},
|
|
160
|
+
{
|
|
161
|
+
"action": "glm_web_search_request"
|
|
162
|
+
},
|
|
160
163
|
{
|
|
161
164
|
"action": "field_map",
|
|
162
165
|
"direction": "outgoing",
|
|
@@ -10,6 +10,7 @@ import { validateResponsePayload } from '../../../compat/actions/response-valida
|
|
|
10
10
|
import { writeCompatSnapshot } from '../../../compat/actions/snapshot.js';
|
|
11
11
|
import { applyQwenRequestTransform, applyQwenResponseTransform } from '../../../compat/actions/qwen-transform.js';
|
|
12
12
|
import { extractGlmToolMarkup } from '../../../compat/actions/glm-tool-extraction.js';
|
|
13
|
+
import { applyGlmWebSearchRequestTransform } from '../../../compat/actions/glm-web-search.js';
|
|
13
14
|
const RATE_LIMIT_ERROR = 'ERR_COMPAT_RATE_LIMIT_DETECTED';
|
|
14
15
|
const INTERNAL_STATE = Symbol('compat.internal_state');
|
|
15
16
|
export function runRequestCompatPipeline(profileId, payload, options) {
|
|
@@ -157,6 +158,11 @@ function applyMapping(root, mapping, state) {
|
|
|
157
158
|
case 'qwen_response_transform':
|
|
158
159
|
replaceRoot(root, applyQwenResponseTransform(root));
|
|
159
160
|
break;
|
|
161
|
+
case 'glm_web_search_request':
|
|
162
|
+
if (state.direction === 'request') {
|
|
163
|
+
replaceRoot(root, applyGlmWebSearchRequestTransform(root));
|
|
164
|
+
}
|
|
165
|
+
break;
|
|
160
166
|
default:
|
|
161
167
|
break;
|
|
162
168
|
}
|
|
@@ -94,10 +94,18 @@ export class HubPipeline {
|
|
|
94
94
|
});
|
|
95
95
|
let processedRequest;
|
|
96
96
|
if (normalized.processMode !== 'passthrough') {
|
|
97
|
+
const processMetadata = {
|
|
98
|
+
...(normalized.metadata ?? {})
|
|
99
|
+
};
|
|
100
|
+
const webSearchConfig = this.config.virtualRouter?.webSearch;
|
|
101
|
+
if (webSearchConfig) {
|
|
102
|
+
processMetadata.webSearch = webSearchConfig;
|
|
103
|
+
}
|
|
104
|
+
normalized.metadata = processMetadata;
|
|
97
105
|
const processResult = await runReqProcessStage1ToolGovernance({
|
|
98
106
|
request: standardizedRequest,
|
|
99
107
|
rawPayload: rawRequest,
|
|
100
|
-
metadata:
|
|
108
|
+
metadata: processMetadata,
|
|
101
109
|
entryEndpoint: normalized.entryEndpoint,
|
|
102
110
|
requestId: normalized.id,
|
|
103
111
|
stageRecorder: inboundRecorder
|
|
@@ -51,7 +51,7 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
51
51
|
});
|
|
52
52
|
const governed = normalizeRecord(governedPayload);
|
|
53
53
|
const providerStreamIntent = typeof governed.stream === 'boolean' ? governed.stream : undefined;
|
|
54
|
-
|
|
54
|
+
let merged = {
|
|
55
55
|
...request,
|
|
56
56
|
messages: Array.isArray(governed.messages)
|
|
57
57
|
? governed.messages
|
|
@@ -92,6 +92,8 @@ async function applyRequestToolGovernance(request, context) {
|
|
|
92
92
|
if (typeof governed.model === 'string' && governed.model.trim()) {
|
|
93
93
|
merged.model = governed.model.trim();
|
|
94
94
|
}
|
|
95
|
+
// Server-side web_search tool injection (config-driven, best-effort).
|
|
96
|
+
merged = maybeInjectWebSearchTool(merged, metadata);
|
|
95
97
|
const { request: sanitized, summary } = toolGovernanceEngine.governRequest(merged, providerProtocol);
|
|
96
98
|
if (summary.applied) {
|
|
97
99
|
sanitized.metadata = {
|
|
@@ -274,3 +276,131 @@ function readToolChoice(value) {
|
|
|
274
276
|
function isRecord(value) {
|
|
275
277
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
276
278
|
}
|
|
279
|
+
function maybeInjectWebSearchTool(request, metadata) {
|
|
280
|
+
const rawConfig = metadata.webSearch;
|
|
281
|
+
if (!rawConfig || !Array.isArray(rawConfig.engines) || rawConfig.engines.length === 0) {
|
|
282
|
+
return request;
|
|
283
|
+
}
|
|
284
|
+
const injectPolicy = (rawConfig.injectPolicy === 'always' || rawConfig.injectPolicy === 'selective')
|
|
285
|
+
? rawConfig.injectPolicy
|
|
286
|
+
: 'selective';
|
|
287
|
+
if (injectPolicy === 'selective' && !detectWebSearchIntent(request)) {
|
|
288
|
+
return request;
|
|
289
|
+
}
|
|
290
|
+
const existingTools = Array.isArray(request.tools) ? request.tools : [];
|
|
291
|
+
const hasWebSearch = existingTools.some((tool) => {
|
|
292
|
+
if (!tool || typeof tool !== 'object')
|
|
293
|
+
return false;
|
|
294
|
+
const fn = tool.function;
|
|
295
|
+
return typeof fn?.name === 'string' && fn.name.trim() === 'web_search';
|
|
296
|
+
});
|
|
297
|
+
if (hasWebSearch) {
|
|
298
|
+
return request;
|
|
299
|
+
}
|
|
300
|
+
const engines = rawConfig.engines.filter((engine) => typeof engine?.id === 'string' && !!engine.id.trim());
|
|
301
|
+
if (!engines.length) {
|
|
302
|
+
return request;
|
|
303
|
+
}
|
|
304
|
+
const engineIds = engines.map((engine) => engine.id.trim());
|
|
305
|
+
const engineDescriptions = engines
|
|
306
|
+
.map((engine) => {
|
|
307
|
+
const id = engine.id.trim();
|
|
308
|
+
const desc = typeof engine.description === 'string' && engine.description.trim()
|
|
309
|
+
? engine.description.trim()
|
|
310
|
+
: '';
|
|
311
|
+
return desc ? `${id}: ${desc}` : id;
|
|
312
|
+
})
|
|
313
|
+
.join('; ');
|
|
314
|
+
const hasMultipleEngines = engineIds.length > 1;
|
|
315
|
+
const parameters = {
|
|
316
|
+
type: 'object',
|
|
317
|
+
properties: {
|
|
318
|
+
...(hasMultipleEngines
|
|
319
|
+
? {
|
|
320
|
+
engine: {
|
|
321
|
+
type: 'string',
|
|
322
|
+
enum: engineIds,
|
|
323
|
+
description: engineDescriptions
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
: {}),
|
|
327
|
+
query: {
|
|
328
|
+
type: 'string',
|
|
329
|
+
description: 'Search query or user question.'
|
|
330
|
+
},
|
|
331
|
+
recency: {
|
|
332
|
+
type: 'string',
|
|
333
|
+
enum: ['oneDay', 'oneWeek', 'oneMonth', 'oneYear', 'noLimit'],
|
|
334
|
+
description: 'Optional recency filter for web search results.'
|
|
335
|
+
},
|
|
336
|
+
count: {
|
|
337
|
+
type: 'integer',
|
|
338
|
+
minimum: 1,
|
|
339
|
+
maximum: 50,
|
|
340
|
+
description: 'Number of results to retrieve.'
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
required: ['query'],
|
|
344
|
+
additionalProperties: false
|
|
345
|
+
};
|
|
346
|
+
const webSearchTool = {
|
|
347
|
+
type: 'function',
|
|
348
|
+
function: {
|
|
349
|
+
name: 'web_search',
|
|
350
|
+
description: 'Perform web search using configured search engines. Use this when the user asks for up-to-date information or news.',
|
|
351
|
+
parameters,
|
|
352
|
+
strict: true
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
const nextMetadata = {
|
|
356
|
+
...(request.metadata ?? {}),
|
|
357
|
+
webSearchEnabled: true
|
|
358
|
+
};
|
|
359
|
+
return {
|
|
360
|
+
...request,
|
|
361
|
+
metadata: nextMetadata,
|
|
362
|
+
tools: [...existingTools, webSearchTool]
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function detectWebSearchIntent(request) {
|
|
366
|
+
const messages = Array.isArray(request.messages) ? request.messages : [];
|
|
367
|
+
if (!messages.length) {
|
|
368
|
+
return false;
|
|
369
|
+
}
|
|
370
|
+
const last = messages[messages.length - 1];
|
|
371
|
+
if (!last || last.role !== 'user') {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const content = typeof last.content === 'string' ? last.content : '';
|
|
375
|
+
if (!content) {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
const text = content.toLowerCase();
|
|
379
|
+
const keywords = [
|
|
380
|
+
// English
|
|
381
|
+
'web search',
|
|
382
|
+
'web_search',
|
|
383
|
+
'websearch',
|
|
384
|
+
'internet search',
|
|
385
|
+
'search the web',
|
|
386
|
+
'online search',
|
|
387
|
+
'search online',
|
|
388
|
+
'search on the internet',
|
|
389
|
+
'search the internet',
|
|
390
|
+
'web-search',
|
|
391
|
+
'online-search',
|
|
392
|
+
'internet-search',
|
|
393
|
+
// Chinese
|
|
394
|
+
'联网搜索',
|
|
395
|
+
'网络搜索',
|
|
396
|
+
'上网搜索',
|
|
397
|
+
'网上搜索',
|
|
398
|
+
'网上查',
|
|
399
|
+
'网上查找',
|
|
400
|
+
'上网查',
|
|
401
|
+
'上网搜',
|
|
402
|
+
// Command-style
|
|
403
|
+
'/search'
|
|
404
|
+
];
|
|
405
|
+
return keywords.some((keyword) => text.includes(keyword.toLowerCase()));
|
|
406
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
2
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
3
|
+
import type { JsonObject } from '../types/json.js';
|
|
4
|
+
import type { StageRecorder } from '../format-adapters/index.js';
|
|
5
|
+
import type { ProviderInvoker } from './server-side-tools.js';
|
|
6
|
+
type ProviderProtocol = 'openai-chat' | 'openai-responses' | 'anthropic-messages' | 'gemini-chat';
|
|
7
|
+
export interface ProviderResponseConversionOptions {
|
|
8
|
+
providerProtocol: ProviderProtocol;
|
|
9
|
+
providerResponse: JsonObject;
|
|
10
|
+
context: AdapterContext;
|
|
11
|
+
entryEndpoint: string;
|
|
12
|
+
wantsStream: boolean;
|
|
13
|
+
stageRecorder?: StageRecorder;
|
|
14
|
+
providerInvoker?: ProviderInvoker;
|
|
15
|
+
}
|
|
16
|
+
export interface ProviderResponseConversionResult {
|
|
17
|
+
body?: JsonObject;
|
|
18
|
+
__sse_responses?: Readable;
|
|
19
|
+
format?: string;
|
|
20
|
+
}
|
|
21
|
+
export declare function convertProviderResponse(options: ProviderResponseConversionOptions): Promise<ProviderResponseConversionResult>;
|
|
22
|
+
export {};
|
|
@@ -12,6 +12,7 @@ import { runRespProcessStage2Finalize } from '../pipeline/stages/resp_process/re
|
|
|
12
12
|
import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js';
|
|
13
13
|
import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
|
|
14
14
|
import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
|
|
15
|
+
import { runServerSideToolEngine } from './server-side-tools.js';
|
|
15
16
|
function resolveChatReasoningMode(entryEndpoint) {
|
|
16
17
|
const envRaw = (process.env.ROUTECODEX_CHAT_REASONING_MODE || process.env.RCC_CHAT_REASONING_MODE || '').trim().toLowerCase();
|
|
17
18
|
const map = {
|
|
@@ -137,8 +138,18 @@ export async function convertProviderResponse(options) {
|
|
|
137
138
|
mapper,
|
|
138
139
|
stageRecorder: options.stageRecorder
|
|
139
140
|
});
|
|
141
|
+
// Server-side tool orchestration hook (web_search, etc.).
|
|
142
|
+
const serverSideResult = await runServerSideToolEngine({
|
|
143
|
+
chatResponse,
|
|
144
|
+
adapterContext: options.context,
|
|
145
|
+
entryEndpoint: options.entryEndpoint,
|
|
146
|
+
requestId: options.context.requestId,
|
|
147
|
+
providerProtocol: options.providerProtocol,
|
|
148
|
+
providerInvoker: options.providerInvoker
|
|
149
|
+
});
|
|
150
|
+
const chatForGovernance = serverSideResult.finalChatResponse;
|
|
140
151
|
const governanceResult = await runRespProcessStage1ToolGovernance({
|
|
141
|
-
payload:
|
|
152
|
+
payload: chatForGovernance,
|
|
142
153
|
entryEndpoint: options.entryEndpoint,
|
|
143
154
|
requestId: options.context.requestId,
|
|
144
155
|
clientProtocol,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AdapterContext } from '../types/chat-envelope.js';
|
|
2
|
+
import type { JsonObject } from '../types/json.js';
|
|
3
|
+
export type ProviderInvoker = (options: {
|
|
4
|
+
providerKey: string;
|
|
5
|
+
providerType?: string;
|
|
6
|
+
modelId?: string;
|
|
7
|
+
providerProtocol: string;
|
|
8
|
+
payload: JsonObject;
|
|
9
|
+
entryEndpoint: string;
|
|
10
|
+
requestId: string;
|
|
11
|
+
}) => Promise<{
|
|
12
|
+
providerResponse: JsonObject;
|
|
13
|
+
}>;
|
|
14
|
+
export interface ServerSideToolEngineOptions {
|
|
15
|
+
chatResponse: JsonObject;
|
|
16
|
+
adapterContext: AdapterContext;
|
|
17
|
+
entryEndpoint: string;
|
|
18
|
+
requestId: string;
|
|
19
|
+
providerProtocol: string;
|
|
20
|
+
providerInvoker?: ProviderInvoker;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerSideToolEngineResult {
|
|
23
|
+
mode: 'passthrough' | 'web_search_flow';
|
|
24
|
+
finalChatResponse: JsonObject;
|
|
25
|
+
}
|
|
26
|
+
export declare function runServerSideToolEngine(options: ServerSideToolEngineOptions): Promise<ServerSideToolEngineResult>;
|