@jsonstudio/llms 0.6.1399 → 0.6.1403

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 (72) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.d.ts +1 -3
  2. package/dist/conversion/codecs/gemini-openai-codec.js +4 -10
  3. package/dist/conversion/compat/actions/gemini-cli-request.d.ts +2 -0
  4. package/dist/conversion/compat/actions/gemini-cli-request.js +490 -0
  5. package/dist/conversion/compat/profiles/chat-gemini-cli.json +27 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +76 -348
  7. package/dist/conversion/hub/pipeline/compat/compat-pipeline-executor.js +6 -0
  8. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +2 -0
  9. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +95 -3
  10. package/dist/conversion/hub/pipeline/hub-pipeline.js +1365 -19
  11. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +22 -0
  12. package/dist/conversion/hub/policy/policy-engine.js +50 -3
  13. package/dist/conversion/hub/process/chat-process.js +5 -146
  14. package/dist/conversion/hub/response/provider-response.js +11 -10
  15. package/dist/conversion/hub/response/response-mappers.d.ts +1 -3
  16. package/dist/conversion/hub/response/response-mappers.js +2 -20
  17. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +2 -1
  18. package/dist/conversion/responses/responses-openai-bridge.js +4 -3
  19. package/dist/conversion/shared/gemini-tool-utils.d.ts +1 -6
  20. package/dist/conversion/shared/gemini-tool-utils.js +164 -542
  21. package/dist/conversion/shared/mcp-injection.js +29 -0
  22. package/dist/conversion/shared/openai-message-normalize.js +3 -17
  23. package/dist/filters/special/request-tool-list-filter.js +21 -13
  24. package/dist/filters/special/tool-filter-hooks.js +60 -3
  25. package/dist/router/virtual-router/bootstrap.js +8 -6
  26. package/dist/router/virtual-router/engine-health.d.ts +1 -1
  27. package/dist/router/virtual-router/engine-health.js +110 -11
  28. package/dist/router/virtual-router/engine-selection/alias-selection.d.ts +0 -15
  29. package/dist/router/virtual-router/engine-selection/alias-selection.js +4 -85
  30. package/dist/router/virtual-router/engine-selection/route-utils.js +6 -12
  31. package/dist/router/virtual-router/engine-selection/tier-selection-select.js +17 -40
  32. package/dist/router/virtual-router/engine-selection/tier-selection.js +2 -5
  33. package/dist/router/virtual-router/engine.js +6 -21
  34. package/dist/router/virtual-router/types.d.ts +1 -14
  35. package/dist/servertool/clock/config.d.ts +1 -1
  36. package/dist/servertool/clock/config.js +5 -9
  37. package/dist/servertool/engine.js +11 -88
  38. package/dist/servertool/handlers/gemini-empty-reply-continue.js +1 -2
  39. package/dist/sse/sse-to-json/builders/response-builder.js +0 -16
  40. package/package.json +1 -1
  41. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.d.ts +0 -10
  42. package/dist/conversion/hub/pipeline/hub-pipeline/adapter-context.js +0 -142
  43. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.d.ts +0 -6
  44. package/dist/conversion/hub/pipeline/hub-pipeline/anthropic-alias-map.js +0 -79
  45. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.d.ts +0 -3
  46. package/dist/conversion/hub/pipeline/hub-pipeline/apply-patch-tool-mode.js +0 -46
  47. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.d.ts +0 -8
  48. package/dist/conversion/hub/pipeline/hub-pipeline/execute-chat-process-entry.js +0 -366
  49. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.d.ts +0 -9
  50. package/dist/conversion/hub/pipeline/hub-pipeline/execute-request-stage.js +0 -390
  51. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.d.ts +0 -3
  52. package/dist/conversion/hub/pipeline/hub-pipeline/node-results.js +0 -14
  53. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.d.ts +0 -2
  54. package/dist/conversion/hub/pipeline/hub-pipeline/payload-normalize.js +0 -144
  55. package/dist/conversion/hub/pipeline/hub-pipeline/policy.d.ts +0 -4
  56. package/dist/conversion/hub/pipeline/hub-pipeline/policy.js +0 -32
  57. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.d.ts +0 -8
  58. package/dist/conversion/hub/pipeline/hub-pipeline/protocol.js +0 -63
  59. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.d.ts +0 -2
  60. package/dist/conversion/hub/pipeline/hub-pipeline/resolve-protocol-hooks.js +0 -43
  61. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.d.ts +0 -1
  62. package/dist/conversion/hub/pipeline/hub-pipeline/semantic-gate.js +0 -29
  63. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.d.ts +0 -2
  64. package/dist/conversion/hub/pipeline/hub-pipeline/servertool-runtime-config.js +0 -16
  65. package/dist/conversion/hub/pipeline/hub-pipeline/types.d.ts +0 -116
  66. package/dist/conversion/hub/pipeline/hub-pipeline/types.js +0 -1
  67. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.d.ts +0 -10
  68. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage2_thought_signature_inject/index.js +0 -172
  69. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.d.ts +0 -10
  70. package/dist/conversion/hub/pipeline/stages/resp_inbound/resp_inbound_stage3_thought_signature_capture/index.js +0 -71
  71. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.d.ts +0 -14
  72. package/dist/conversion/hub/pipeline/thought-signature/thought-signature-center.js +0 -289
@@ -2,9 +2,7 @@ import type { ConversionCodec, ConversionContext, ConversionProfile } from '../t
2
2
  export declare function buildOpenAIChatFromGeminiRequest(payload: any): {
3
3
  messages: any[];
4
4
  };
5
- export declare function buildOpenAIChatFromGeminiResponse(payload: any, options?: {
6
- aliasMap?: Record<string, string>;
7
- }): any;
5
+ export declare function buildOpenAIChatFromGeminiResponse(payload: any): any;
8
6
  export declare function buildGeminiFromOpenAIChat(chatResp: any): any;
9
7
  export declare class GeminiOpenAIConversionCodec implements ConversionCodec {
10
8
  private readonly _dependencies;
@@ -175,7 +175,7 @@ export function buildOpenAIChatFromGeminiRequest(payload) {
175
175
  }
176
176
  return { messages };
177
177
  }
178
- export function buildOpenAIChatFromGeminiResponse(payload, options) {
178
+ export function buildOpenAIChatFromGeminiResponse(payload) {
179
179
  const candidates = Array.isArray(payload?.candidates) ? payload.candidates : [];
180
180
  const primary = candidates[0] && typeof candidates[0] === 'object' ? candidates[0] : {};
181
181
  const content = primary?.content || {};
@@ -229,12 +229,9 @@ export function buildOpenAIChatFromGeminiResponse(payload, options) {
229
229
  // 5. Function call (tool call)
230
230
  if (pObj.functionCall && typeof pObj.functionCall === 'object') {
231
231
  const fc = pObj.functionCall;
232
- const nameRaw = typeof fc.name === 'string' ? String(fc.name) : undefined;
233
- if (!nameRaw)
232
+ const name = typeof fc.name === 'string' ? String(fc.name) : undefined;
233
+ if (!name)
234
234
  continue;
235
- const name = options?.aliasMap && typeof options.aliasMap[nameRaw] === 'string'
236
- ? String(options.aliasMap[nameRaw])
237
- : nameRaw;
238
235
  let id = typeof fc.id === 'string' && fc.id.trim().length ? String(fc.id).trim() : undefined;
239
236
  const argsRaw = (fc.args ?? fc.arguments);
240
237
  let argsStr;
@@ -271,10 +268,7 @@ export function buildOpenAIChatFromGeminiResponse(payload, options) {
271
268
  if (pObj.functionResponse && typeof pObj.functionResponse === 'object') {
272
269
  const fr = pObj.functionResponse;
273
270
  const callId = typeof fr.id === 'string' && fr.id.trim().length ? String(fr.id) : undefined;
274
- const nameRaw = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
275
- const name = nameRaw && options?.aliasMap && typeof options.aliasMap[nameRaw] === 'string'
276
- ? String(options.aliasMap[nameRaw])
277
- : nameRaw;
271
+ const name = typeof fr.name === 'string' && fr.name.trim().length ? String(fr.name) : undefined;
278
272
  const resp = fr.response;
279
273
  let contentStr = '';
280
274
  if (typeof resp === 'string') {
@@ -0,0 +1,2 @@
1
+ import type { JsonObject } from '../../hub/types/json.js';
2
+ export declare function wrapGeminiCliRequest(payload: JsonObject): JsonObject;
@@ -0,0 +1,490 @@
1
+ const REQUEST_FIELDS = [
2
+ 'contents',
3
+ 'systemInstruction',
4
+ 'tools',
5
+ 'toolConfig',
6
+ 'generationConfig',
7
+ 'safetySettings'
8
+ ];
9
+ const ROOT_ONLY_FIELDS = [
10
+ 'model',
11
+ 'project',
12
+ 'requestId',
13
+ 'requestType',
14
+ 'userAgent',
15
+ 'action'
16
+ ];
17
+ const TOOL_NAME_ALIASES = {
18
+ 'mcp__context7__query-docs': 'mcp__context7__query_docs',
19
+ 'mcp__context7__resolve-library-id': 'mcp__context7__resolve_library_id',
20
+ 'mcp__mcp-server-time__convert_time': 'mcp__mcp_server_time__convert_time',
21
+ 'mcp__mcp-server-time__get_current_time': 'mcp__mcp_server_time__get_current_time'
22
+ };
23
+ const TOOL_PARAM_WHITELIST = {
24
+ exec_command: ['command', 'workdir'],
25
+ write_stdin: ['session_id', 'chars', 'max_output_tokens', 'yield_time_ms', 'text'],
26
+ apply_patch: ['patch', 'input', 'instructions', 'text']
27
+ };
28
+ function isRecord(value) {
29
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
30
+ }
31
+ function normalizeSchemaTypes(value) {
32
+ if (!value || typeof value !== 'object')
33
+ return value;
34
+ if (Array.isArray(value))
35
+ return value.map((entry) => normalizeSchemaTypes(entry));
36
+ const record = value;
37
+ const out = {};
38
+ for (const [key, val] of Object.entries(record)) {
39
+ if (key === 'type' && typeof val === 'string') {
40
+ out[key] = val.toUpperCase();
41
+ continue;
42
+ }
43
+ out[key] = normalizeSchemaTypes(val);
44
+ }
45
+ return out;
46
+ }
47
+ function pickDescription(value, fallback) {
48
+ return typeof value === 'string' && value.trim().length ? value : fallback;
49
+ }
50
+ function normalizeToolDeclaration(decl) {
51
+ const rawName = typeof decl.name === 'string' ? decl.name.trim() : '';
52
+ const name = (TOOL_NAME_ALIASES[rawName] ?? rawName).trim().toLowerCase();
53
+ const params = isRecord(decl.parameters) ? { ...decl.parameters } : {};
54
+ const props = isRecord(params.properties) ? { ...params.properties } : {};
55
+ if (name === 'view_image') {
56
+ return {};
57
+ }
58
+ if (name === 'exec_command') {
59
+ const command = isRecord(props.command) ? props.command : undefined;
60
+ const cmd = isRecord(props.cmd) ? props.cmd : undefined;
61
+ const nextProps = {
62
+ command: {
63
+ type: 'STRING',
64
+ description: pickDescription(command?.description ?? cmd?.description, 'Shell command to execute.')
65
+ },
66
+ workdir: {
67
+ type: 'STRING',
68
+ description: 'Working directory.'
69
+ }
70
+ };
71
+ return {
72
+ ...decl,
73
+ name,
74
+ description: 'Run a shell command. Provide `cmd` (string) (alias: `command`) and optional `workdir` (string).',
75
+ parameters: {
76
+ type: 'OBJECT',
77
+ properties: nextProps
78
+ }
79
+ };
80
+ }
81
+ if (name === 'write_stdin') {
82
+ const nextProps = {};
83
+ for (const key of TOOL_PARAM_WHITELIST.write_stdin) {
84
+ const entry = props[key];
85
+ const fallbackType = key === 'session_id' || key.endsWith('_tokens') || key.endsWith('_ms') ? 'NUMBER' : 'STRING';
86
+ nextProps[key] = {
87
+ type: (typeof entry?.type === 'string' ? entry.type : fallbackType).toUpperCase(),
88
+ ...(entry?.description ? { description: entry.description } : {})
89
+ };
90
+ }
91
+ return {
92
+ ...decl,
93
+ name,
94
+ description: 'Write to an existing exec session. Provide `session_id` (number) and optional `chars` (string).',
95
+ parameters: {
96
+ type: 'OBJECT',
97
+ properties: nextProps
98
+ }
99
+ };
100
+ }
101
+ if (name === 'apply_patch') {
102
+ const nextProps = {};
103
+ for (const key of TOOL_PARAM_WHITELIST.apply_patch) {
104
+ const entry = props[key];
105
+ nextProps[key] = {
106
+ type: 'STRING',
107
+ ...(entry?.description ? { description: entry.description } : {})
108
+ };
109
+ }
110
+ return {
111
+ ...decl,
112
+ name,
113
+ description: 'Edit files by providing patch text in `patch` (string). Supports "*** Begin Patch" / "*** End Patch" or GNU unified diff. `input`/`instructions`/`text` are accepted as aliases.',
114
+ parameters: {
115
+ type: 'OBJECT',
116
+ properties: nextProps
117
+ }
118
+ };
119
+ }
120
+ if (name === 'list_mcp_resources') {
121
+ return {
122
+ ...decl,
123
+ name,
124
+ description: 'Lists resources provided by MCP servers. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information. Prefer resources over web search when possible.',
125
+ parameters: {
126
+ type: 'OBJECT',
127
+ properties: {
128
+ server: { type: 'STRING', minLength: 1 },
129
+ filter: { type: 'STRING' },
130
+ root: { type: 'STRING' }
131
+ }
132
+ }
133
+ };
134
+ }
135
+ if (name === 'list_mcp_resource_templates') {
136
+ return {
137
+ ...decl,
138
+ name,
139
+ description: 'Lists resource templates provided by MCP servers. Parameterized resource templates allow servers to share data that takes parameters and provides context to language models, such as files, database schemas, or application-specific information. Prefer resource templates over web search when possible.',
140
+ parameters: {
141
+ type: 'OBJECT',
142
+ properties: {
143
+ cursor: { type: 'STRING' },
144
+ server: { type: 'STRING', minLength: 1 }
145
+ }
146
+ }
147
+ };
148
+ }
149
+ if (name === 'read_mcp_resource') {
150
+ return {
151
+ ...decl,
152
+ name,
153
+ description: 'Read a specific resource from an MCP server given the server name and resource URI.',
154
+ parameters: {
155
+ type: 'OBJECT',
156
+ properties: {
157
+ server: {
158
+ type: 'STRING',
159
+ description: "MCP server name exactly as configured. Must match the 'server' field returned by list_mcp_resources."
160
+ },
161
+ uri: {
162
+ type: 'STRING',
163
+ description: 'Resource URI to read. Must be one of the URIs returned by list_mcp_resources.'
164
+ }
165
+ },
166
+ required: ['server', 'uri']
167
+ }
168
+ };
169
+ }
170
+ if (name === 'update_plan') {
171
+ return {
172
+ ...decl,
173
+ name,
174
+ description: 'Updates the task plan.\nProvide an optional explanation and a list of plan items, each with a step and status.\nAt most one step can be in_progress at a time.\n',
175
+ parameters: {
176
+ type: 'OBJECT',
177
+ properties: {
178
+ explanation: { type: 'STRING' },
179
+ plan: {
180
+ type: 'ARRAY',
181
+ items: {
182
+ type: 'OBJECT',
183
+ properties: {
184
+ status: { type: 'STRING', description: 'One of: pending, in_progress, completed' },
185
+ step: { type: 'STRING' }
186
+ },
187
+ required: ['step', 'status']
188
+ },
189
+ description: 'The list of steps'
190
+ }
191
+ },
192
+ required: ['plan']
193
+ }
194
+ };
195
+ }
196
+ if (name === 'request_user_input') {
197
+ return {
198
+ ...decl,
199
+ name,
200
+ description: 'Request user input for one to three short questions and wait for the response.',
201
+ parameters: {
202
+ type: 'OBJECT',
203
+ properties: {
204
+ questions: {
205
+ type: 'ARRAY',
206
+ items: {
207
+ type: 'OBJECT',
208
+ properties: {
209
+ header: { type: 'STRING', description: 'Short header label shown in the UI (12 or fewer chars).' },
210
+ id: { type: 'STRING', description: 'Stable identifier for mapping answers (snake_case).' },
211
+ options: {
212
+ type: 'ARRAY',
213
+ items: {
214
+ type: 'OBJECT',
215
+ properties: {
216
+ description: {
217
+ type: 'STRING',
218
+ description: 'One short sentence explaining impact/tradeoff if selected.'
219
+ },
220
+ label: { type: 'STRING', description: 'User-facing label (1-5 words).' }
221
+ },
222
+ required: ['label', 'description']
223
+ },
224
+ description: 'Optional 2-3 mutually exclusive choices. Put the recommended option first and suffix its label with "(Recommended)". Only include "Other" option if we want to include a free form option. If the question is free form in nature, please do not have any option.'
225
+ },
226
+ question: { type: 'STRING', description: 'Single-sentence prompt shown to the user.' }
227
+ },
228
+ required: ['id', 'header', 'question']
229
+ },
230
+ description: 'Questions to show the user. Prefer 1 and do not exceed 3'
231
+ }
232
+ },
233
+ required: ['questions']
234
+ }
235
+ };
236
+ }
237
+ if (name === 'mcp__context7__query_docs') {
238
+ return {
239
+ ...decl,
240
+ name,
241
+ description: "Retrieves and queries up-to-date documentation and code examples from Context7 for any programming library or framework.\n\nYou must call 'resolve-library-id' first to obtain the exact Context7-compatible library ID required to use this tool, UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nIMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best information you have.",
242
+ parameters: {
243
+ type: 'OBJECT',
244
+ properties: {
245
+ libraryId: {
246
+ type: 'STRING',
247
+ description: "Exact Context7-compatible library ID (e.g., '/mongodb/docs', '/vercel/next.js', '/supabase/supabase', '/vercel/next.js/v14.3.0-canary.87') retrieved from 'resolve-library-id' or directly from user query in the format '/org/project' or '/org/project/version'."
248
+ },
249
+ query: {
250
+ type: 'STRING',
251
+ description: "The question or task you need help with. Be specific and include relevant details. Good: 'How to set up authentication with JWT in Express.js' or 'React useEffect cleanup function examples'. Bad: 'auth' or 'hooks'. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."
252
+ }
253
+ },
254
+ required: ['libraryId', 'query']
255
+ }
256
+ };
257
+ }
258
+ if (name === 'mcp__context7__resolve_library_id') {
259
+ return {
260
+ ...decl,
261
+ name,
262
+ description: "Resolves a package/product name to a Context7-compatible library ID and returns matching libraries.\n\nYou MUST call this function before 'query-docs' to obtain a valid Context7-compatible library ID UNLESS the user explicitly provides a library ID in the format '/org/project' or '/org/project/version' in their query.\n\nSelection Process:\n1. Analyze the query to understand what library/package the user is looking for\n2. Return the most relevant match based on:\n- Name similarity to the query (exact matches prioritized)\n- Description relevance to the query's intent\n- Documentation coverage (prioritize libraries with higher Code Snippet counts)\n- Source reputation (consider libraries with High or Medium reputation more authoritative)\n- Benchmark Score: Quality indicator (100 is the highest score)\n\nResponse Format:\n- Return the selected library ID in a clearly marked section\n- Provide a brief explanation for why this library was chosen\n- If multiple good matches exist, acknowledge this but proceed with the most relevant one\n- If no good matches exist, clearly state this and suggest query refinements\n\nFor ambiguous queries, request clarification before proceeding with a best-guess match.\n\nIMPORTANT: Do not call this tool more than 3 times per question. If you cannot find what you need after 3 calls, use the best result you have.",
263
+ parameters: {
264
+ type: 'OBJECT',
265
+ properties: {
266
+ libraryName: {
267
+ type: 'STRING',
268
+ description: 'Library name to search for and retrieve a Context7-compatible library ID.'
269
+ },
270
+ query: {
271
+ type: 'STRING',
272
+ description: "The user's original question or task. This is used to rank library results by relevance to what the user is trying to accomplish. IMPORTANT: Do not include any sensitive or confidential information such as API keys, passwords, credentials, or personal data in your query."
273
+ }
274
+ },
275
+ required: ['query', 'libraryName']
276
+ }
277
+ };
278
+ }
279
+ if (name === 'mcp__mcp_server_time__convert_time') {
280
+ return {
281
+ ...decl,
282
+ name,
283
+ description: 'Convert time between timezones',
284
+ parameters: {
285
+ type: 'OBJECT',
286
+ properties: {
287
+ source_timezone: {
288
+ type: 'STRING',
289
+ description: "Source IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no source timezone provided by the user."
290
+ },
291
+ target_timezone: {
292
+ type: 'STRING',
293
+ description: "Target IANA timezone name (e.g., 'Asia/Tokyo', 'America/San_Francisco'). Use 'Asia/Shanghai' as local timezone if no target timezone provided by the user."
294
+ },
295
+ time: { type: 'STRING', description: 'Time to convert in 24-hour format (HH:MM)' }
296
+ },
297
+ required: ['source_timezone', 'time', 'target_timezone']
298
+ }
299
+ };
300
+ }
301
+ if (name === 'mcp__mcp_server_time__get_current_time') {
302
+ return {
303
+ ...decl,
304
+ name,
305
+ description: 'Get current time in a specific timezones',
306
+ parameters: {
307
+ type: 'OBJECT',
308
+ properties: {
309
+ timezone: {
310
+ type: 'STRING',
311
+ description: "IANA timezone name (e.g., 'America/New_York', 'Europe/London'). Use 'Asia/Shanghai' as local timezone if no timezone provided by the user."
312
+ }
313
+ },
314
+ required: ['timezone']
315
+ }
316
+ };
317
+ }
318
+ return {
319
+ ...decl,
320
+ name,
321
+ parameters: normalizeSchemaTypes(params)
322
+ };
323
+ }
324
+ function normalizeToolDeclarations(node) {
325
+ const tools = node.tools;
326
+ if (!Array.isArray(tools))
327
+ return;
328
+ const nextTools = tools.map((tool) => {
329
+ if (!tool || typeof tool !== 'object')
330
+ return tool;
331
+ const record = tool;
332
+ const decls = Array.isArray(record.functionDeclarations)
333
+ ? record.functionDeclarations
334
+ : undefined;
335
+ if (!decls) {
336
+ return {
337
+ ...record,
338
+ ...(record.parameters ? { parameters: normalizeSchemaTypes(record.parameters) } : {})
339
+ };
340
+ }
341
+ const nextDecls = decls
342
+ .map((decl) => normalizeToolDeclaration(decl))
343
+ .filter((decl) => Object.keys(decl).length > 0);
344
+ return { ...record, functionDeclarations: nextDecls };
345
+ });
346
+ node.tools = nextTools.filter((tool) => {
347
+ if (!tool || typeof tool !== 'object')
348
+ return true;
349
+ const record = tool;
350
+ const decls = Array.isArray(record.functionDeclarations)
351
+ ? record.functionDeclarations
352
+ : undefined;
353
+ return !decls || decls.length > 0;
354
+ });
355
+ }
356
+ function normalizeFunctionCallArgs(node) {
357
+ const contents = node.contents;
358
+ if (!Array.isArray(contents))
359
+ return;
360
+ for (const item of contents) {
361
+ if (!item || typeof item !== 'object')
362
+ continue;
363
+ const record = item;
364
+ const parts = Array.isArray(record.parts) ? record.parts : [];
365
+ for (const part of parts) {
366
+ if (!part || typeof part !== 'object')
367
+ continue;
368
+ const fnCall = part.functionCall;
369
+ if (!isRecord(fnCall))
370
+ continue;
371
+ part.thoughtSignature = 'skip_thought_signature_validator';
372
+ const rawName = typeof fnCall.name === 'string' ? fnCall.name.trim() : '';
373
+ const normalizedName = (TOOL_NAME_ALIASES[rawName] ?? rawName).trim();
374
+ fnCall.name = normalizedName;
375
+ const name = normalizedName.toLowerCase();
376
+ const args = isRecord(fnCall.args) ? { ...fnCall.args } : undefined;
377
+ if (!args)
378
+ continue;
379
+ if (name === 'exec_command') {
380
+ if (!Object.prototype.hasOwnProperty.call(args, 'command') && Object.prototype.hasOwnProperty.call(args, 'cmd')) {
381
+ args.command = args.cmd;
382
+ }
383
+ if (Object.prototype.hasOwnProperty.call(args, 'cmd')) {
384
+ delete args.cmd;
385
+ }
386
+ }
387
+ if (name === 'write_stdin') {
388
+ if (!Object.prototype.hasOwnProperty.call(args, 'chars') && Object.prototype.hasOwnProperty.call(args, 'text')) {
389
+ args.chars = args.text;
390
+ }
391
+ }
392
+ fnCall.args = args;
393
+ part.functionCall = fnCall;
394
+ }
395
+ }
396
+ }
397
+ function hasAnyRequestField(node) {
398
+ return REQUEST_FIELDS.some((key) => Object.prototype.hasOwnProperty.call(node, key));
399
+ }
400
+ function normalizeRequestNode(node) {
401
+ const base = { ...node };
402
+ const nested = base.request;
403
+ if (!isRecord(nested)) {
404
+ return base;
405
+ }
406
+ delete base.request;
407
+ const merged = { ...nested };
408
+ for (const [key, value] of Object.entries(base)) {
409
+ if (merged[key] === undefined) {
410
+ merged[key] = value;
411
+ }
412
+ }
413
+ return merged;
414
+ }
415
+ function isWebSearchToolName(value) {
416
+ if (typeof value !== 'string')
417
+ return false;
418
+ const normalized = value.trim().toLowerCase();
419
+ return normalized === 'web_search' || normalized.startsWith('web_search_');
420
+ }
421
+ function stripWebSearchTools(requestNode) {
422
+ const tools = requestNode.tools;
423
+ if (!Array.isArray(tools))
424
+ return;
425
+ const nextTools = [];
426
+ for (const tool of tools) {
427
+ if (!tool || typeof tool !== 'object') {
428
+ nextTools.push(tool);
429
+ continue;
430
+ }
431
+ const record = tool;
432
+ const fnDecls = Array.isArray(record.functionDeclarations)
433
+ ? record.functionDeclarations
434
+ : undefined;
435
+ if (fnDecls) {
436
+ const filtered = fnDecls.filter((decl) => !isWebSearchToolName(decl?.name));
437
+ if (filtered.length === 0) {
438
+ continue;
439
+ }
440
+ nextTools.push({ ...record, functionDeclarations: filtered });
441
+ continue;
442
+ }
443
+ const name = record.name;
444
+ if (isWebSearchToolName(name)) {
445
+ continue;
446
+ }
447
+ nextTools.push(record);
448
+ }
449
+ if (nextTools.length > 0) {
450
+ requestNode.tools = nextTools;
451
+ }
452
+ else {
453
+ delete requestNode.tools;
454
+ }
455
+ }
456
+ export function wrapGeminiCliRequest(payload) {
457
+ const root = { ...payload };
458
+ const existingRequest = isRecord(root.request) ? normalizeRequestNode(root.request) : undefined;
459
+ const requestNode = existingRequest ?? {};
460
+ for (const key of REQUEST_FIELDS) {
461
+ if (requestNode[key] === undefined && root[key] !== undefined) {
462
+ requestNode[key] = root[key];
463
+ }
464
+ delete root[key];
465
+ }
466
+ for (const key of ROOT_ONLY_FIELDS) {
467
+ if (requestNode[key] !== undefined) {
468
+ delete requestNode[key];
469
+ }
470
+ }
471
+ stripWebSearchTools(requestNode);
472
+ normalizeToolDeclarations(requestNode);
473
+ normalizeFunctionCallArgs(requestNode);
474
+ // Cloud Code Assist request wrapper should not carry metadata/action/web_search/stream.
475
+ delete requestNode.metadata;
476
+ delete requestNode.action;
477
+ delete requestNode.web_search;
478
+ delete requestNode.stream;
479
+ delete requestNode.sessionId;
480
+ delete root.metadata;
481
+ delete root.stream;
482
+ delete root.sessionId;
483
+ if (Object.keys(requestNode).length > 0) {
484
+ root.request = requestNode;
485
+ }
486
+ else {
487
+ delete root.request;
488
+ }
489
+ return root;
490
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "chat:gemini-cli",
3
+ "protocol": "gemini-chat",
4
+ "request": {
5
+ "mappings": [
6
+ { "action": "snapshot", "phase": "compat-pre" },
7
+ { "action": "claude_thinking_tool_schema" },
8
+ { "action": "gemini_cli_request_wrap" },
9
+ {
10
+ "action": "shallow_pick",
11
+ "allowTopLevel": [
12
+ "model",
13
+ "project",
14
+ "request",
15
+ "requestId",
16
+ "requestType",
17
+ "userAgent",
18
+ "action"
19
+ ]
20
+ },
21
+ { "action": "snapshot", "phase": "compat-post" }
22
+ ]
23
+ },
24
+ "response": {
25
+ "mappings": []
26
+ }
27
+ }