coding-tool-x 3.2.0

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 (185) hide show
  1. package/CHANGELOG.md +599 -0
  2. package/LICENSE +21 -0
  3. package/README.md +439 -0
  4. package/bin/ctx.js +8 -0
  5. package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
  6. package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
  7. package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
  8. package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
  9. package/dist/web/assets/Home-38JTUlYt.js +1 -0
  10. package/dist/web/assets/Home-CjupSEWE.css +1 -0
  11. package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
  12. package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
  13. package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
  14. package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
  15. package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
  16. package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
  17. package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
  18. package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
  19. package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
  20. package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
  21. package/dist/web/assets/icons-DRrXwWZi.js +1 -0
  22. package/dist/web/assets/index-CetESrXw.css +1 -0
  23. package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
  24. package/dist/web/assets/markdown-BfC0goYb.css +10 -0
  25. package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
  26. package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
  27. package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
  28. package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
  29. package/dist/web/favicon.ico +0 -0
  30. package/dist/web/index.html +20 -0
  31. package/dist/web/logo.png +0 -0
  32. package/docs/bannel.png +0 -0
  33. package/docs/home.png +0 -0
  34. package/docs/logo.png +0 -0
  35. package/docs/model-redirection.md +251 -0
  36. package/docs/multi-channel-load-balancing.md +249 -0
  37. package/package.json +80 -0
  38. package/src/commands/channels.js +551 -0
  39. package/src/commands/cli-type.js +101 -0
  40. package/src/commands/daemon.js +365 -0
  41. package/src/commands/doctor.js +333 -0
  42. package/src/commands/export-config.js +205 -0
  43. package/src/commands/list.js +222 -0
  44. package/src/commands/logs.js +261 -0
  45. package/src/commands/plugin.js +585 -0
  46. package/src/commands/port-config.js +135 -0
  47. package/src/commands/proxy-control.js +264 -0
  48. package/src/commands/proxy.js +152 -0
  49. package/src/commands/resume.js +137 -0
  50. package/src/commands/search.js +190 -0
  51. package/src/commands/security.js +37 -0
  52. package/src/commands/stats.js +398 -0
  53. package/src/commands/switch.js +48 -0
  54. package/src/commands/toggle-proxy.js +247 -0
  55. package/src/commands/ui.js +99 -0
  56. package/src/commands/update.js +97 -0
  57. package/src/commands/workspace.js +454 -0
  58. package/src/config/default.js +69 -0
  59. package/src/config/loader.js +149 -0
  60. package/src/config/model-metadata.js +167 -0
  61. package/src/config/model-metadata.json +125 -0
  62. package/src/config/model-pricing.js +35 -0
  63. package/src/config/paths.js +190 -0
  64. package/src/index.js +680 -0
  65. package/src/plugins/constants.js +15 -0
  66. package/src/plugins/event-bus.js +54 -0
  67. package/src/plugins/manifest-validator.js +129 -0
  68. package/src/plugins/plugin-api.js +128 -0
  69. package/src/plugins/plugin-installer.js +601 -0
  70. package/src/plugins/plugin-loader.js +229 -0
  71. package/src/plugins/plugin-manager.js +170 -0
  72. package/src/plugins/registry.js +152 -0
  73. package/src/plugins/schema/plugin-manifest.json +115 -0
  74. package/src/reset-config.js +94 -0
  75. package/src/server/api/agents.js +826 -0
  76. package/src/server/api/aliases.js +36 -0
  77. package/src/server/api/channels.js +368 -0
  78. package/src/server/api/claude-hooks.js +480 -0
  79. package/src/server/api/codex-channels.js +417 -0
  80. package/src/server/api/codex-projects.js +104 -0
  81. package/src/server/api/codex-proxy.js +195 -0
  82. package/src/server/api/codex-sessions.js +483 -0
  83. package/src/server/api/codex-statistics.js +57 -0
  84. package/src/server/api/commands.js +482 -0
  85. package/src/server/api/config-export.js +212 -0
  86. package/src/server/api/config-registry.js +357 -0
  87. package/src/server/api/config-sync.js +155 -0
  88. package/src/server/api/config-templates.js +248 -0
  89. package/src/server/api/config.js +521 -0
  90. package/src/server/api/convert.js +260 -0
  91. package/src/server/api/dashboard.js +142 -0
  92. package/src/server/api/env.js +144 -0
  93. package/src/server/api/favorites.js +77 -0
  94. package/src/server/api/gemini-channels.js +366 -0
  95. package/src/server/api/gemini-projects.js +91 -0
  96. package/src/server/api/gemini-proxy.js +173 -0
  97. package/src/server/api/gemini-sessions.js +376 -0
  98. package/src/server/api/gemini-statistics.js +57 -0
  99. package/src/server/api/health-check.js +31 -0
  100. package/src/server/api/mcp.js +399 -0
  101. package/src/server/api/opencode-channels.js +419 -0
  102. package/src/server/api/opencode-projects.js +99 -0
  103. package/src/server/api/opencode-proxy.js +207 -0
  104. package/src/server/api/opencode-sessions.js +327 -0
  105. package/src/server/api/opencode-statistics.js +57 -0
  106. package/src/server/api/plugins.js +463 -0
  107. package/src/server/api/pm2-autostart.js +269 -0
  108. package/src/server/api/projects.js +124 -0
  109. package/src/server/api/prompts.js +279 -0
  110. package/src/server/api/proxy.js +306 -0
  111. package/src/server/api/security.js +53 -0
  112. package/src/server/api/sessions.js +514 -0
  113. package/src/server/api/settings.js +142 -0
  114. package/src/server/api/skills.js +570 -0
  115. package/src/server/api/statistics.js +238 -0
  116. package/src/server/api/ui-config.js +64 -0
  117. package/src/server/api/workspaces.js +456 -0
  118. package/src/server/codex-proxy-server.js +681 -0
  119. package/src/server/dev-server.js +26 -0
  120. package/src/server/gemini-proxy-server.js +610 -0
  121. package/src/server/index.js +422 -0
  122. package/src/server/opencode-proxy-server.js +4771 -0
  123. package/src/server/proxy-server.js +669 -0
  124. package/src/server/services/agents-service.js +1137 -0
  125. package/src/server/services/alias.js +71 -0
  126. package/src/server/services/channel-health.js +234 -0
  127. package/src/server/services/channel-scheduler.js +240 -0
  128. package/src/server/services/channels.js +447 -0
  129. package/src/server/services/codex-channels.js +705 -0
  130. package/src/server/services/codex-config.js +90 -0
  131. package/src/server/services/codex-parser.js +322 -0
  132. package/src/server/services/codex-sessions.js +936 -0
  133. package/src/server/services/codex-settings-manager.js +619 -0
  134. package/src/server/services/codex-speed-test-template.json +24 -0
  135. package/src/server/services/codex-statistics-service.js +161 -0
  136. package/src/server/services/commands-service.js +574 -0
  137. package/src/server/services/config-export-service.js +1165 -0
  138. package/src/server/services/config-registry-service.js +828 -0
  139. package/src/server/services/config-sync-manager.js +941 -0
  140. package/src/server/services/config-sync-service.js +504 -0
  141. package/src/server/services/config-templates-service.js +913 -0
  142. package/src/server/services/enhanced-cache.js +196 -0
  143. package/src/server/services/env-checker.js +409 -0
  144. package/src/server/services/env-manager.js +436 -0
  145. package/src/server/services/favorites.js +165 -0
  146. package/src/server/services/format-converter.js +620 -0
  147. package/src/server/services/gemini-channels.js +459 -0
  148. package/src/server/services/gemini-config.js +73 -0
  149. package/src/server/services/gemini-sessions.js +689 -0
  150. package/src/server/services/gemini-settings-manager.js +263 -0
  151. package/src/server/services/gemini-statistics-service.js +157 -0
  152. package/src/server/services/health-check.js +85 -0
  153. package/src/server/services/mcp-client.js +790 -0
  154. package/src/server/services/mcp-service.js +1732 -0
  155. package/src/server/services/model-detector.js +1245 -0
  156. package/src/server/services/network-access.js +80 -0
  157. package/src/server/services/opencode-channels.js +366 -0
  158. package/src/server/services/opencode-gateway-adapters.js +1168 -0
  159. package/src/server/services/opencode-gateway-converter.js +639 -0
  160. package/src/server/services/opencode-sessions.js +931 -0
  161. package/src/server/services/opencode-settings-manager.js +478 -0
  162. package/src/server/services/opencode-statistics-service.js +161 -0
  163. package/src/server/services/plugins-service.js +1268 -0
  164. package/src/server/services/prompts-service.js +534 -0
  165. package/src/server/services/proxy-runtime.js +79 -0
  166. package/src/server/services/repo-scanner-base.js +708 -0
  167. package/src/server/services/request-logger.js +130 -0
  168. package/src/server/services/response-decoder.js +21 -0
  169. package/src/server/services/security-config.js +131 -0
  170. package/src/server/services/session-cache.js +127 -0
  171. package/src/server/services/session-converter.js +577 -0
  172. package/src/server/services/sessions.js +900 -0
  173. package/src/server/services/settings-manager.js +163 -0
  174. package/src/server/services/skill-service.js +1482 -0
  175. package/src/server/services/speed-test.js +1146 -0
  176. package/src/server/services/statistics-service.js +1043 -0
  177. package/src/server/services/ui-config.js +132 -0
  178. package/src/server/services/workspace-service.js +830 -0
  179. package/src/server/utils/pricing.js +73 -0
  180. package/src/server/websocket-server.js +513 -0
  181. package/src/ui/menu.js +139 -0
  182. package/src/ui/prompts.js +100 -0
  183. package/src/utils/format.js +43 -0
  184. package/src/utils/port-helper.js +108 -0
  185. package/src/utils/session.js +240 -0
@@ -0,0 +1,1168 @@
1
+ function cloneJsonCompatible(value) {
2
+ try {
3
+ return JSON.parse(JSON.stringify(value));
4
+ } catch {
5
+ return value;
6
+ }
7
+ }
8
+
9
+ function extractTextFragments(value, fragments) {
10
+ if (value === null || value === undefined) return;
11
+ if (typeof value === 'string') {
12
+ if (value.trim()) fragments.push(value);
13
+ return;
14
+ }
15
+ if (typeof value === 'number' || typeof value === 'boolean') {
16
+ fragments.push(String(value));
17
+ return;
18
+ }
19
+ if (Array.isArray(value)) {
20
+ value.forEach(item => extractTextFragments(item, fragments));
21
+ return;
22
+ }
23
+ if (typeof value !== 'object') return;
24
+
25
+ if (typeof value.text === 'string') {
26
+ extractTextFragments(value.text, fragments);
27
+ return;
28
+ }
29
+ if (typeof value.input_text === 'string') {
30
+ extractTextFragments(value.input_text, fragments);
31
+ return;
32
+ }
33
+ if (typeof value.output_text === 'string') {
34
+ extractTextFragments(value.output_text, fragments);
35
+ return;
36
+ }
37
+ if (value.content !== undefined) {
38
+ extractTextFragments(value.content, fragments);
39
+ return;
40
+ }
41
+ if (Array.isArray(value.parts)) {
42
+ extractTextFragments(value.parts, fragments);
43
+ }
44
+ }
45
+
46
+ function extractText(value) {
47
+ const fragments = [];
48
+ extractTextFragments(value, fragments);
49
+ return fragments.join('\n').trim();
50
+ }
51
+
52
+ function parseBase64DataUrl(dataUrl = '') {
53
+ const value = typeof dataUrl === 'string' ? dataUrl.trim() : '';
54
+ if (!value) return null;
55
+ const matched = value.match(/^data:([^;,]+)?;base64,(.+)$/i);
56
+ if (!matched) return null;
57
+ return {
58
+ mediaType: String(matched[1] || '').trim(),
59
+ data: String(matched[2] || '')
60
+ };
61
+ }
62
+
63
+ function normalizeOpenAiImageBlock(value) {
64
+ let imageUrl = '';
65
+ if (typeof value === 'string') {
66
+ imageUrl = value;
67
+ } else if (value && typeof value === 'object') {
68
+ if (typeof value.url === 'string') {
69
+ imageUrl = value.url;
70
+ } else if (typeof value.image_url === 'string') {
71
+ imageUrl = value.image_url;
72
+ } else if (value.image_url && typeof value.image_url === 'object' && typeof value.image_url.url === 'string') {
73
+ imageUrl = value.image_url.url;
74
+ }
75
+ }
76
+
77
+ if (!imageUrl || typeof imageUrl !== 'string') return null;
78
+ const trimmed = imageUrl.trim();
79
+ if (!trimmed) return null;
80
+
81
+ const dataUrl = parseBase64DataUrl(trimmed);
82
+ if (dataUrl) {
83
+ return {
84
+ type: 'image',
85
+ source: {
86
+ type: 'base64',
87
+ media_type: dataUrl.mediaType || 'image/png',
88
+ data: dataUrl.data
89
+ }
90
+ };
91
+ }
92
+
93
+ return {
94
+ type: 'image',
95
+ source: {
96
+ type: 'url',
97
+ url: trimmed
98
+ }
99
+ };
100
+ }
101
+
102
+ function normalizeOpenAiFileBlock(value) {
103
+ if (!value || typeof value !== 'object') return null;
104
+
105
+ const fileNode = (value.file && typeof value.file === 'object') ? value.file : value;
106
+ const fileData = typeof fileNode.file_data === 'string' ? fileNode.file_data.trim() : '';
107
+ const fileUrl = typeof fileNode.file_url === 'string' ? fileNode.file_url.trim() : '';
108
+ const fileId = typeof fileNode.file_id === 'string' ? fileNode.file_id.trim() : '';
109
+
110
+ if (fileData) {
111
+ const dataUrl = parseBase64DataUrl(fileData);
112
+ if (dataUrl) {
113
+ return {
114
+ type: 'document',
115
+ source: {
116
+ type: 'base64',
117
+ media_type: dataUrl.mediaType || 'application/octet-stream',
118
+ data: dataUrl.data
119
+ }
120
+ };
121
+ }
122
+
123
+ return {
124
+ type: 'document',
125
+ source: {
126
+ type: 'base64',
127
+ media_type: 'application/octet-stream',
128
+ data: fileData
129
+ }
130
+ };
131
+ }
132
+
133
+ if (fileUrl) {
134
+ return {
135
+ type: 'document',
136
+ source: {
137
+ type: 'url',
138
+ url: fileUrl
139
+ }
140
+ };
141
+ }
142
+
143
+ if (fileId) {
144
+ return {
145
+ type: 'text',
146
+ text: `[input_file:${fileId}]`
147
+ };
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ function normalizeOpenAiContentItemToClaudeBlocks(item) {
154
+ if (item === null || item === undefined) return [];
155
+
156
+ if (typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
157
+ const text = String(item);
158
+ return text.trim() ? [{ type: 'text', text }] : [];
159
+ }
160
+
161
+ if (Array.isArray(item)) {
162
+ return item.flatMap(normalizeOpenAiContentItemToClaudeBlocks);
163
+ }
164
+
165
+ if (typeof item !== 'object') return [];
166
+
167
+ const itemType = String(item.type || '').trim().toLowerCase();
168
+ if (itemType === 'tool_use') {
169
+ return [item];
170
+ }
171
+ if (itemType === 'tool_result') {
172
+ return [item];
173
+ }
174
+
175
+ if (itemType === 'image' && item.source && typeof item.source === 'object') {
176
+ return [item];
177
+ }
178
+ if (itemType === 'document' && item.source && typeof item.source === 'object') {
179
+ return [item];
180
+ }
181
+
182
+ if (itemType === 'text' || itemType === 'input_text' || itemType === 'output_text') {
183
+ const text = typeof item.text === 'string' ? item.text : '';
184
+ if (!text.trim()) return [];
185
+ const block = { type: 'text', text };
186
+ if (item.cache_control && typeof item.cache_control === 'object') {
187
+ block.cache_control = item.cache_control;
188
+ }
189
+ return [block];
190
+ }
191
+
192
+ if (itemType === 'image_url' || itemType === 'input_image') {
193
+ const imageBlock = normalizeOpenAiImageBlock(item);
194
+ return imageBlock ? [imageBlock] : [];
195
+ }
196
+
197
+ if (itemType === 'file' || itemType === 'input_file') {
198
+ const fileBlock = normalizeOpenAiFileBlock(item);
199
+ return fileBlock ? [fileBlock] : [];
200
+ }
201
+
202
+ if (item.image_url !== undefined || item.url !== undefined) {
203
+ const imageBlock = normalizeOpenAiImageBlock(item);
204
+ if (imageBlock) return [imageBlock];
205
+ }
206
+
207
+ if (item.file !== undefined || item.file_data !== undefined || item.file_url !== undefined || item.file_id !== undefined) {
208
+ const fileBlock = normalizeOpenAiFileBlock(item);
209
+ if (fileBlock) return [fileBlock];
210
+ }
211
+
212
+ const fallbackText = extractText(item);
213
+ return fallbackText ? [{ type: 'text', text: fallbackText }] : [];
214
+ }
215
+
216
+ function normalizeOpenAiContentToClaudeBlocks(content) {
217
+ return normalizeOpenAiContentItemToClaudeBlocks(content);
218
+ }
219
+
220
+ function normalizeOpenAiRole(role) {
221
+ const value = String(role || '').trim().toLowerCase();
222
+ if (value === 'assistant' || value === 'model') return 'assistant';
223
+ if (value === 'system' || value === 'developer') return 'system';
224
+ if (value === 'tool') return 'tool';
225
+ return 'user';
226
+ }
227
+
228
+ function isResponsesPath(pathname) {
229
+ const normalized = String(pathname || '').trim().replace(/\/+$/, '') || '/';
230
+ return normalized.endsWith('/v1/responses') || normalized.endsWith('/responses');
231
+ }
232
+
233
+ function isChatCompletionsPath(pathname) {
234
+ const normalized = String(pathname || '').trim().replace(/\/+$/, '') || '/';
235
+ return normalized.endsWith('/v1/chat/completions') || normalized.endsWith('/chat/completions');
236
+ }
237
+
238
+ function normalizeReasoningEffortToClaude(reasoningEffort) {
239
+ const effort = String(reasoningEffort || '').trim().toLowerCase();
240
+ if (!effort) return undefined;
241
+ if (effort === 'none') return { type: 'disabled' };
242
+ if (effort === 'auto') return { type: 'enabled' };
243
+ if (effort === 'low') return { type: 'enabled', budget_tokens: 2048 };
244
+ if (effort === 'medium') return { type: 'enabled', budget_tokens: 8192 };
245
+ if (effort === 'high') return { type: 'enabled', budget_tokens: 24576 };
246
+ return undefined;
247
+ }
248
+
249
+ function generateToolCallId() {
250
+ return `toolu_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
251
+ }
252
+
253
+ function parseToolArguments(value) {
254
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
255
+ return value;
256
+ }
257
+ if (typeof value === 'string') {
258
+ const trimmed = value.trim();
259
+ if (!trimmed) return {};
260
+ try {
261
+ const parsed = JSON.parse(trimmed);
262
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
263
+ return parsed;
264
+ }
265
+ } catch {
266
+ return {};
267
+ }
268
+ }
269
+ return {};
270
+ }
271
+
272
+ function normalizeToolResultContent(value) {
273
+ if (typeof value === 'string') return value;
274
+ if (value === null || value === undefined) return '';
275
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value);
276
+ try {
277
+ return JSON.stringify(value);
278
+ } catch {
279
+ return String(value);
280
+ }
281
+ }
282
+
283
+ function ensureClaudeToolNamePrefix(name) {
284
+ const trimmed = typeof name === 'string' ? name.trim() : '';
285
+ if (!trimmed) return '';
286
+ return trimmed.startsWith('mcp_') ? trimmed : `mcp_${trimmed}`;
287
+ }
288
+
289
+ function stripClaudeToolNamePrefix(name) {
290
+ const trimmed = typeof name === 'string' ? name.trim() : '';
291
+ if (!trimmed) return '';
292
+ if (!trimmed.startsWith('mcp_')) return trimmed;
293
+ const stripped = trimmed.slice(4).trim();
294
+ return stripped || trimmed;
295
+ }
296
+
297
+ function transformIdentityTextToClaudeCode(text) {
298
+ if (typeof text !== 'string' || !text) return text;
299
+ return text.replace(/open\s*code/gi, 'Claude Code');
300
+ }
301
+
302
+ function buildAssistantToolUseMessageFromFunctionCall(item) {
303
+ const functionPayload = (item?.function && typeof item.function === 'object')
304
+ ? item.function
305
+ : item;
306
+ const rawName = functionPayload?.name || item?.name;
307
+ if (!rawName) return null;
308
+
309
+ const callId = functionPayload?.call_id || item?.call_id || functionPayload?.id || item?.id || generateToolCallId();
310
+ const argumentsSource = functionPayload?.arguments ?? item?.arguments ?? functionPayload?.input ?? item?.input;
311
+ const input = parseToolArguments(argumentsSource);
312
+
313
+ return {
314
+ role: 'assistant',
315
+ content: [
316
+ {
317
+ type: 'tool_use',
318
+ id: callId,
319
+ name: rawName,
320
+ input
321
+ }
322
+ ]
323
+ };
324
+ }
325
+
326
+ function buildUserToolResultMessage(item) {
327
+ const callId = item?.call_id || item?.tool_call_id || item?.id || generateToolCallId();
328
+ const outputSource = item?.output ?? item?.content ?? '';
329
+ const content = normalizeToolResultContent(outputSource);
330
+
331
+ return {
332
+ role: 'user',
333
+ content: [
334
+ {
335
+ type: 'tool_result',
336
+ tool_use_id: callId,
337
+ content
338
+ }
339
+ ]
340
+ };
341
+ }
342
+
343
+ function normalizeOpenCodeMessages(pathname, payload = {}) {
344
+ const systemBlocks = [];
345
+ const messages = [];
346
+
347
+ if (isResponsesPath(pathname) && typeof payload.instructions === 'string' && payload.instructions.trim()) {
348
+ systemBlocks.push({ type: 'text', text: payload.instructions.trim() });
349
+ }
350
+
351
+ const appendMessage = (role, content, topLevelCacheControl) => {
352
+ const normalizedRole = normalizeOpenAiRole(role);
353
+ const contentBlocks = normalizeOpenAiContentToClaudeBlocks(content);
354
+ if (normalizedRole === 'system') {
355
+ const blocks = contentBlocks
356
+ .filter(block => block && block.type === 'text' && typeof block.text === 'string' && block.text.trim());
357
+ blocks.forEach((block, idx) => {
358
+ const systemBlock = { type: 'text', text: block.text };
359
+ if (block.cache_control && typeof block.cache_control === 'object') {
360
+ systemBlock.cache_control = block.cache_control;
361
+ } else if (topLevelCacheControl && typeof topLevelCacheControl === 'object' && idx === blocks.length - 1) {
362
+ systemBlock.cache_control = topLevelCacheControl;
363
+ }
364
+ systemBlocks.push(systemBlock);
365
+ });
366
+ return;
367
+ }
368
+
369
+ if (!Array.isArray(contentBlocks) || contentBlocks.length === 0) return;
370
+ if (topLevelCacheControl && typeof topLevelCacheControl === 'object' && contentBlocks.length > 0) {
371
+ const lastBlock = contentBlocks[contentBlocks.length - 1];
372
+ if (!lastBlock.cache_control) lastBlock.cache_control = topLevelCacheControl;
373
+ }
374
+ messages.push({
375
+ role: normalizedRole === 'assistant' ? 'assistant' : 'user',
376
+ content: contentBlocks
377
+ });
378
+ };
379
+
380
+ if (isResponsesPath(pathname)) {
381
+ if (typeof payload.input === 'string') {
382
+ appendMessage('user', payload.input);
383
+ } else if (Array.isArray(payload.input)) {
384
+ payload.input.forEach(item => {
385
+ if (!item || typeof item !== 'object') return;
386
+ if (item.type === 'function_call') {
387
+ const assistantToolUse = buildAssistantToolUseMessageFromFunctionCall(item);
388
+ if (assistantToolUse) {
389
+ messages.push(assistantToolUse);
390
+ }
391
+ return;
392
+ }
393
+ if (item.type === 'function_call_output') {
394
+ messages.push(buildUserToolResultMessage(item));
395
+ return;
396
+ }
397
+ if (item.type === 'message' || item.role) {
398
+ appendMessage(item.role, item.content, item.cache_control);
399
+ }
400
+ });
401
+ }
402
+ }
403
+
404
+ if (isChatCompletionsPath(pathname) && Array.isArray(payload.messages)) {
405
+ payload.messages.forEach(message => {
406
+ if (!message || typeof message !== 'object') return;
407
+ if (message.role === 'tool') {
408
+ messages.push(buildUserToolResultMessage(message));
409
+ return;
410
+ }
411
+ if (message.role === 'assistant' && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
412
+ const assistantContent = normalizeOpenAiContentToClaudeBlocks(message.content);
413
+
414
+ message.tool_calls.forEach(toolCall => {
415
+ if (!toolCall || typeof toolCall !== 'object') return;
416
+ const functionPayload = (toolCall.function && typeof toolCall.function === 'object')
417
+ ? toolCall.function
418
+ : toolCall;
419
+ const rawName = functionPayload.name || toolCall.name;
420
+ if (!rawName) return;
421
+ assistantContent.push({
422
+ type: 'tool_use',
423
+ id: toolCall.id || functionPayload.call_id || generateToolCallId(),
424
+ name: rawName,
425
+ input: parseToolArguments(functionPayload.arguments ?? functionPayload.input)
426
+ });
427
+ });
428
+
429
+ if (assistantContent.length > 0) {
430
+ messages.push({
431
+ role: 'assistant',
432
+ content: assistantContent
433
+ });
434
+ }
435
+ return;
436
+ }
437
+ appendMessage(message.role, message.content, message.cache_control);
438
+ });
439
+ }
440
+
441
+ if (messages.length === 0) {
442
+ messages.push({
443
+ role: 'user',
444
+ content: [{ type: 'text', text: 'Hello' }]
445
+ });
446
+ }
447
+
448
+ return {
449
+ systemBlocks,
450
+ messages
451
+ };
452
+ }
453
+
454
+ function buildClaudeCodeUserId(seed = '') {
455
+ const normalizedSeed = normalizeClaudeSessionSeed(seed);
456
+ const sessionId = normalizedSeed || `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 12)}`;
457
+ return `user_${'0'.repeat(64)}_account__session_${sessionId}`;
458
+ }
459
+
460
+ function normalizeSessionKeyValue(value) {
461
+ const source = Array.isArray(value) ? value[0] : value;
462
+ if (typeof source !== 'string') return '';
463
+ const normalized = source.trim();
464
+ if (!normalized) return '';
465
+ const lowered = normalized.toLowerCase();
466
+ if (lowered === 'undefined' || lowered === '[undefined]' || lowered === 'null' || lowered === '[null]' || lowered === 'nan' || lowered === '[nan]') {
467
+ return '';
468
+ }
469
+ return normalized;
470
+ }
471
+
472
+ function normalizeClaudeSessionSeed(value) {
473
+ const normalized = normalizeSessionKeyValue(value);
474
+ if (!normalized) return '';
475
+ return normalized
476
+ .replace(/^user_[0-9a-f]{64}_account__session_/i, '')
477
+ .replace(/[^a-zA-Z0-9_-]/g, '')
478
+ .slice(0, 64);
479
+ }
480
+
481
+ function extractClaudeSessionSeedFromUserId(value = '') {
482
+ const normalized = normalizeSessionKeyValue(value);
483
+ if (!normalized) return '';
484
+ const matched = normalized.match(/^user_[0-9a-f]{64}_account__session_(.+)$/i);
485
+ if (!matched) return '';
486
+ return normalizeClaudeSessionSeed(matched[1] || '');
487
+ }
488
+
489
+ function getClaudeSessionSeedAllowList() {
490
+ const envSeeds = String(
491
+ process.env.OPENCODE_CLAUDE_FALLBACK_SESSION_SEEDS || 'session_test,session,sessiontest,session_'
492
+ )
493
+ .split(',')
494
+ .map(seed => normalizeClaudeSessionSeed(seed))
495
+ .filter(Boolean);
496
+ const fallbackSeed = normalizeClaudeSessionSeed(process.env.OPENCODE_CLAUDE_FALLBACK_SESSION_SEED || 'session_test');
497
+ return Array.from(new Set([
498
+ ...envSeeds,
499
+ fallbackSeed,
500
+ 'session_test',
501
+ 'session',
502
+ 'sessiontest',
503
+ 'session_'
504
+ ].filter(Boolean)));
505
+ }
506
+
507
+ function isValidClaudeCodeUserId(value = '', options = {}) {
508
+ const normalized = normalizeSessionKeyValue(value);
509
+ if (!normalized) return false;
510
+ const matched = normalized.match(/^user_[0-9a-f]{64}_account__session_([a-zA-Z0-9_-]{1,64})$/i);
511
+ if (!matched) return false;
512
+ if (!options.enforceAllowedSeed) return true;
513
+ const allowedSeeds = Array.isArray(options.allowedSeeds) && options.allowedSeeds.length > 0
514
+ ? options.allowedSeeds
515
+ : getClaudeSessionSeedAllowList();
516
+ const seed = normalizeClaudeSessionSeed(matched[1] || '');
517
+ return !!seed && allowedSeeds.includes(seed);
518
+ }
519
+
520
+ function normalizeClaudeMetadata(metadata, fallbackUserId = '') {
521
+ const normalized = (metadata && typeof metadata === 'object' && !Array.isArray(metadata))
522
+ ? { ...metadata }
523
+ : {};
524
+ const allowedSeeds = getClaudeSessionSeedAllowList();
525
+ const userId = normalizeSessionKeyValue(normalized.user_id);
526
+ const fallbackValue = normalizeSessionKeyValue(fallbackUserId);
527
+ if (isValidClaudeCodeUserId(userId, { enforceAllowedSeed: true, allowedSeeds })) {
528
+ normalized.user_id = userId;
529
+ return normalized;
530
+ }
531
+ if (isValidClaudeCodeUserId(fallbackValue, { enforceAllowedSeed: true, allowedSeeds })) {
532
+ normalized.user_id = fallbackValue;
533
+ return normalized;
534
+ }
535
+ const fallbackSeedRaw = extractClaudeSessionSeedFromUserId(fallbackValue) || normalizeClaudeSessionSeed(fallbackValue);
536
+ const fallbackSeed = allowedSeeds.includes(fallbackSeedRaw) ? fallbackSeedRaw : (allowedSeeds[0] || 'session_test');
537
+ normalized.user_id = buildClaudeCodeUserId(fallbackSeed);
538
+ return normalized;
539
+ }
540
+
541
+ function normalizeStopSequences(stopValue) {
542
+ if (!stopValue) return undefined;
543
+ if (typeof stopValue === 'string') {
544
+ const normalized = normalizeSessionKeyValue(stopValue);
545
+ if (normalized) return [normalized];
546
+ return undefined;
547
+ }
548
+ if (Array.isArray(stopValue)) {
549
+ const sequences = stopValue
550
+ .filter(item => typeof item === 'string')
551
+ .map(item => normalizeSessionKeyValue(item))
552
+ .filter(Boolean);
553
+ return sequences.length > 0 ? sequences : undefined;
554
+ }
555
+ return undefined;
556
+ }
557
+
558
+ function normalizeToolChoiceToClaude(toolChoice) {
559
+ if (!toolChoice) return undefined;
560
+
561
+ if (typeof toolChoice === 'string') {
562
+ if (toolChoice === 'auto') return { type: 'auto' };
563
+ if (toolChoice === 'required') return { type: 'any' };
564
+ return undefined;
565
+ }
566
+
567
+ if (typeof toolChoice === 'object') {
568
+ if (toolChoice.type === 'function' && toolChoice.function?.name) {
569
+ return { type: 'tool', name: ensureClaudeToolNamePrefix(toolChoice.function.name) };
570
+ }
571
+ if (toolChoice.type === 'function' && toolChoice.name) {
572
+ return { type: 'tool', name: ensureClaudeToolNamePrefix(toolChoice.name) };
573
+ }
574
+ if (toolChoice.type === 'auto') return { type: 'auto' };
575
+ if (toolChoice.type === 'required') return { type: 'any' };
576
+ }
577
+
578
+ return undefined;
579
+ }
580
+
581
+ function normalizeOpenAiToolsToClaude(tools = []) {
582
+ if (!Array.isArray(tools)) return [];
583
+
584
+ const normalized = [];
585
+ for (const tool of tools) {
586
+ if (!tool || typeof tool !== 'object') continue;
587
+
588
+ if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
589
+ const fn = tool.function;
590
+ if (!fn.name) continue;
591
+ normalized.push({
592
+ name: ensureClaudeToolNamePrefix(fn.name),
593
+ description: transformIdentityTextToClaudeCode(fn.description || ''),
594
+ input_schema: fn.parameters || { type: 'object', properties: {} }
595
+ });
596
+ continue;
597
+ }
598
+
599
+ if (tool.type === 'function' && tool.name) {
600
+ normalized.push({
601
+ name: ensureClaudeToolNamePrefix(tool.name),
602
+ description: transformIdentityTextToClaudeCode(tool.description || ''),
603
+ input_schema: tool.parameters || { type: 'object', properties: {} }
604
+ });
605
+ }
606
+ }
607
+
608
+ return normalized;
609
+ }
610
+
611
+ function applyPromptCachingToClaudePayload(converted) {
612
+ const EPHEMERAL = { type: 'ephemeral' };
613
+
614
+ let messageBreakpoints = 0;
615
+ if (Array.isArray(converted.messages)) {
616
+ converted.messages.forEach(msg => {
617
+ if (Array.isArray(msg.content)) {
618
+ msg.content.forEach(block => {
619
+ if (block.cache_control) messageBreakpoints++;
620
+ if (block.type === 'tool_result' && Array.isArray(block.content)) {
621
+ block.content.forEach(inner => {
622
+ if (inner.cache_control) messageBreakpoints++;
623
+ });
624
+ }
625
+ });
626
+ }
627
+ });
628
+ }
629
+
630
+ let systemBreakpoints = 0;
631
+ if (Array.isArray(converted.system)) {
632
+ converted.system.forEach(block => {
633
+ if (block.cache_control) systemBreakpoints++;
634
+ });
635
+ }
636
+
637
+ if (systemBreakpoints === 0 && Array.isArray(converted.system) && converted.system.length > 0) {
638
+ const last = converted.system[converted.system.length - 1];
639
+ if (!last.cache_control) last.cache_control = EPHEMERAL;
640
+ }
641
+
642
+ if (messageBreakpoints === 0 && systemBreakpoints === 0) {
643
+ if (Array.isArray(converted.messages) && converted.messages.length > 0) {
644
+ for (const msg of converted.messages.slice(-2)) {
645
+ if (Array.isArray(msg.content) && msg.content.length > 0) {
646
+ const last = msg.content[msg.content.length - 1];
647
+ if (!last.cache_control) last.cache_control = EPHEMERAL;
648
+ }
649
+ }
650
+ }
651
+ }
652
+ }
653
+
654
+ function applyClaudeToolNamePrefixToMessages(messages = []) {
655
+ if (!Array.isArray(messages)) return [];
656
+ return messages.map(message => {
657
+ if (!message || typeof message !== 'object' || !Array.isArray(message.content)) {
658
+ return message;
659
+ }
660
+
661
+ const content = message.content.map(block => {
662
+ if (!block || typeof block !== 'object') return block;
663
+ if (block.type !== 'tool_use' || !block.name) return block;
664
+ return {
665
+ ...block,
666
+ name: ensureClaudeToolNamePrefix(block.name)
667
+ };
668
+ });
669
+
670
+ return {
671
+ ...message,
672
+ content
673
+ };
674
+ });
675
+ }
676
+
677
+ function convertOpenCodePayloadToClaude(pathname, payload = {}, fallbackModel = '', options = {}) {
678
+ const normalized = normalizeOpenCodeMessages(pathname, payload);
679
+ const maxTokens = Number(payload.max_output_tokens ?? payload.max_tokens);
680
+ const stopSequences = normalizeStopSequences(payload.stop);
681
+ const thinking = normalizeReasoningEffortToClaude(payload.reasoning_effort);
682
+ const CLAUDE_CODE_IDENTITY_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
683
+
684
+ const converted = {
685
+ model: payload.model || fallbackModel || 'claude-sonnet-4-20250514',
686
+ max_tokens: Number.isFinite(maxTokens) && maxTokens > 0 ? Math.round(maxTokens) : 4096,
687
+ stream: false,
688
+ messages: applyClaudeToolNamePrefixToMessages(normalized.messages)
689
+ };
690
+
691
+ if (normalized.systemBlocks && normalized.systemBlocks.length > 0) {
692
+ converted.system = normalized.systemBlocks
693
+ .map(block => {
694
+ if (!block || typeof block !== 'object') return null;
695
+ if (block.type !== 'text') return block;
696
+ return {
697
+ ...block,
698
+ text: transformIdentityTextToClaudeCode(block.text || '')
699
+ };
700
+ })
701
+ .filter(Boolean);
702
+ }
703
+
704
+ if (!Array.isArray(converted.system) || converted.system.length === 0) {
705
+ converted.system = [{ type: 'text', text: CLAUDE_CODE_IDENTITY_PROMPT }];
706
+ }
707
+
708
+ const tools = normalizeOpenAiToolsToClaude(payload.tools || []);
709
+ if (tools.length > 0) {
710
+ converted.tools = tools;
711
+ }
712
+
713
+ const toolChoice = normalizeToolChoiceToClaude(payload.tool_choice);
714
+ if (toolChoice) {
715
+ converted.tool_choice = toolChoice;
716
+ }
717
+ if (stopSequences) {
718
+ converted.stop_sequences = stopSequences;
719
+ }
720
+ if (thinking) {
721
+ converted.thinking = thinking;
722
+ }
723
+
724
+ if (Number.isFinite(Number(payload.temperature))) {
725
+ converted.temperature = Number(payload.temperature);
726
+ }
727
+ if (Number.isFinite(Number(payload.top_p))) {
728
+ converted.top_p = Number(payload.top_p);
729
+ }
730
+ if (Number.isFinite(Number(payload.top_k))) {
731
+ converted.top_k = Number(payload.top_k);
732
+ }
733
+
734
+ converted.metadata = normalizeClaudeMetadata(payload.metadata, options.sessionUserId);
735
+
736
+ applyPromptCachingToClaudePayload(converted);
737
+
738
+ return converted;
739
+ }
740
+
741
+ function normalizeCodexResponsesInput(inputValue) {
742
+ if (typeof inputValue === 'string') {
743
+ return [
744
+ {
745
+ type: 'message',
746
+ role: 'user',
747
+ content: [
748
+ {
749
+ type: 'input_text',
750
+ text: inputValue
751
+ }
752
+ ]
753
+ }
754
+ ];
755
+ }
756
+
757
+ if (!Array.isArray(inputValue)) return undefined;
758
+ return inputValue.map(item => {
759
+ if (!item || typeof item !== 'object') return item;
760
+ const clonedItem = cloneJsonCompatible(item);
761
+ if (String(clonedItem?.role || '').trim().toLowerCase() === 'system') {
762
+ clonedItem.role = 'developer';
763
+ }
764
+ return clonedItem;
765
+ });
766
+ }
767
+
768
+ function convertOpenCodePayloadToCodexResponses(payload = {}, fallbackModel = '') {
769
+ const requestBody = cloneJsonCompatible((payload && typeof payload === 'object') ? payload : {});
770
+
771
+ if (requestBody.model === undefined && fallbackModel) {
772
+ requestBody.model = fallbackModel;
773
+ }
774
+
775
+ const normalizedInput = normalizeCodexResponsesInput(requestBody.input);
776
+ if (normalizedInput !== undefined) {
777
+ requestBody.input = normalizedInput;
778
+ }
779
+
780
+ requestBody.stream = true;
781
+ requestBody.store = false;
782
+ if (requestBody.parallel_tool_calls === undefined) {
783
+ requestBody.parallel_tool_calls = true;
784
+ }
785
+ if (typeof requestBody.instructions !== 'string') {
786
+ requestBody.instructions = '';
787
+ }
788
+
789
+ const include = Array.isArray(requestBody.include)
790
+ ? requestBody.include.filter(item => typeof item === 'string' && item.trim())
791
+ : [];
792
+ if (!include.includes('reasoning.encrypted_content')) {
793
+ include.push('reasoning.encrypted_content');
794
+ }
795
+ requestBody.include = include;
796
+
797
+ delete requestBody.max_output_tokens;
798
+ delete requestBody.max_completion_tokens;
799
+ delete requestBody.temperature;
800
+ delete requestBody.top_p;
801
+ delete requestBody.service_tier;
802
+ delete requestBody.user;
803
+ delete requestBody.previous_response_id;
804
+ delete requestBody.prompt_cache_retention;
805
+ delete requestBody.safety_identifier;
806
+
807
+ return {
808
+ requestBody,
809
+ model: requestBody.model || fallbackModel || ''
810
+ };
811
+ }
812
+
813
+ function normalizeOpenAiToolsToGemini(tools = []) {
814
+ if (!Array.isArray(tools)) return [];
815
+
816
+ const functionDeclarations = [];
817
+ const builtInTools = [];
818
+ const appendBuiltInTool = (toolNode) => {
819
+ if (!toolNode || typeof toolNode !== 'object') return;
820
+ builtInTools.push(toolNode);
821
+ };
822
+
823
+ for (const tool of tools) {
824
+ if (!tool || typeof tool !== 'object') continue;
825
+
826
+ if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
827
+ const fn = tool.function;
828
+ if (!fn.name) continue;
829
+ functionDeclarations.push({
830
+ name: fn.name,
831
+ description: fn.description || '',
832
+ parameters: fn.parameters || { type: 'object', properties: {} }
833
+ });
834
+ continue;
835
+ }
836
+
837
+ if (tool.type === 'function' && tool.name) {
838
+ functionDeclarations.push({
839
+ name: tool.name,
840
+ description: tool.description || '',
841
+ parameters: tool.parameters || { type: 'object', properties: {} }
842
+ });
843
+ continue;
844
+ }
845
+
846
+ const normalizedType = String(tool.type || '').trim().toLowerCase();
847
+
848
+ if (tool.google_search && typeof tool.google_search === 'object') {
849
+ appendBuiltInTool({ googleSearch: tool.google_search });
850
+ continue;
851
+ }
852
+ if (tool.code_execution && typeof tool.code_execution === 'object') {
853
+ appendBuiltInTool({ codeExecution: tool.code_execution });
854
+ continue;
855
+ }
856
+ if (tool.url_context && typeof tool.url_context === 'object') {
857
+ appendBuiltInTool({ urlContext: tool.url_context });
858
+ continue;
859
+ }
860
+
861
+ if (normalizedType === 'google_search' || normalizedType === 'web_search' || normalizedType === 'web_search_preview') {
862
+ const searchConfig = (tool.web_search && typeof tool.web_search === 'object')
863
+ ? tool.web_search
864
+ : ((tool.googleSearch && typeof tool.googleSearch === 'object') ? tool.googleSearch : {});
865
+ appendBuiltInTool({ googleSearch: searchConfig });
866
+ continue;
867
+ }
868
+
869
+ if (normalizedType === 'code_execution' || normalizedType === 'code_interpreter') {
870
+ const executionConfig = (tool.codeExecution && typeof tool.codeExecution === 'object')
871
+ ? tool.codeExecution
872
+ : {};
873
+ appendBuiltInTool({ codeExecution: executionConfig });
874
+ continue;
875
+ }
876
+
877
+ if (normalizedType === 'url_context') {
878
+ const urlContextConfig = (tool.urlContext && typeof tool.urlContext === 'object')
879
+ ? tool.urlContext
880
+ : {};
881
+ appendBuiltInTool({ urlContext: urlContextConfig });
882
+ }
883
+ }
884
+
885
+ const normalizedTools = [];
886
+ if (functionDeclarations.length > 0) {
887
+ normalizedTools.push({ functionDeclarations });
888
+ }
889
+ if (builtInTools.length > 0) {
890
+ normalizedTools.push(...builtInTools);
891
+ }
892
+ return normalizedTools;
893
+ }
894
+
895
+ function normalizeToolChoiceToGemini(toolChoice) {
896
+ if (!toolChoice) return undefined;
897
+
898
+ if (typeof toolChoice === 'string') {
899
+ if (toolChoice === 'auto') return { functionCallingConfig: { mode: 'AUTO' } };
900
+ if (toolChoice === 'required') return { functionCallingConfig: { mode: 'ANY' } };
901
+ if (toolChoice === 'none') return { functionCallingConfig: { mode: 'NONE' } };
902
+ return undefined;
903
+ }
904
+
905
+ if (typeof toolChoice === 'object') {
906
+ const functionName = toolChoice.function?.name || toolChoice.name;
907
+ if (toolChoice.type === 'function' && functionName) {
908
+ return {
909
+ functionCallingConfig: {
910
+ mode: 'ANY',
911
+ allowedFunctionNames: [functionName]
912
+ }
913
+ };
914
+ }
915
+ if (toolChoice.type === 'auto') return { functionCallingConfig: { mode: 'AUTO' } };
916
+ if (toolChoice.type === 'required') return { functionCallingConfig: { mode: 'ANY' } };
917
+ if (toolChoice.type === 'none') return { functionCallingConfig: { mode: 'NONE' } };
918
+ }
919
+
920
+ return undefined;
921
+ }
922
+
923
+ function normalizeReasoningEffortToGemini(reasoningEffort) {
924
+ const effort = String(reasoningEffort || '').trim().toLowerCase();
925
+ if (!effort) return undefined;
926
+ if (effort === 'none') {
927
+ return {
928
+ includeThoughts: false,
929
+ thinkingBudget: 0
930
+ };
931
+ }
932
+ if (effort === 'auto') {
933
+ return {
934
+ includeThoughts: true,
935
+ thinkingBudget: -1
936
+ };
937
+ }
938
+ if (effort === 'low' || effort === 'medium' || effort === 'high') {
939
+ return {
940
+ includeThoughts: true,
941
+ thinkingLevel: effort
942
+ };
943
+ }
944
+ return undefined;
945
+ }
946
+
947
+ function normalizeGeminiResponseModalities(modalities) {
948
+ if (!Array.isArray(modalities)) return undefined;
949
+ const mapped = modalities
950
+ .map(item => String(item || '').trim().toLowerCase())
951
+ .filter(Boolean)
952
+ .map(item => {
953
+ if (item === 'text') return 'TEXT';
954
+ if (item === 'image') return 'IMAGE';
955
+ return '';
956
+ })
957
+ .filter(Boolean);
958
+ return mapped.length > 0 ? mapped : undefined;
959
+ }
960
+
961
+ function normalizeGeminiFunctionResponsePayload(value) {
962
+ if (value && typeof value === 'object' && !Array.isArray(value)) {
963
+ return value;
964
+ }
965
+ if (typeof value === 'string') {
966
+ const trimmed = value.trim();
967
+ if (!trimmed) return { content: '' };
968
+ try {
969
+ const parsed = JSON.parse(trimmed);
970
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
971
+ return parsed;
972
+ }
973
+ } catch {
974
+ return { content: value };
975
+ }
976
+ return { content: value };
977
+ }
978
+ return { content: normalizeToolResultContent(value) };
979
+ }
980
+
981
+ function normalizeGeminiMediaType(value, fallback = 'application/octet-stream') {
982
+ const mediaType = typeof value === 'string' ? value.trim() : '';
983
+ return mediaType || fallback;
984
+ }
985
+
986
+ function buildGeminiPartFromClaudeMediaBlock(block) {
987
+ if (!block || typeof block !== 'object') return null;
988
+ const source = (block.source && typeof block.source === 'object') ? block.source : null;
989
+ if (!source) return null;
990
+
991
+ const blockType = String(block.type || '').trim().toLowerCase();
992
+ const defaultMimeType = blockType === 'image' ? 'image/png' : 'application/octet-stream';
993
+ const sourceType = String(source.type || '').trim().toLowerCase();
994
+ const mediaType = normalizeGeminiMediaType(source.media_type || source.mime_type, defaultMimeType);
995
+
996
+ if (sourceType === 'base64' && typeof source.data === 'string' && source.data.trim()) {
997
+ return {
998
+ inlineData: {
999
+ mimeType: mediaType,
1000
+ data: source.data
1001
+ }
1002
+ };
1003
+ }
1004
+
1005
+ if (sourceType === 'url' && typeof source.url === 'string' && source.url.trim()) {
1006
+ return {
1007
+ fileData: {
1008
+ mimeType: mediaType,
1009
+ fileUri: source.url.trim()
1010
+ }
1011
+ };
1012
+ }
1013
+
1014
+ return null;
1015
+ }
1016
+
1017
+ function buildGeminiContents(messages = []) {
1018
+ const contents = [];
1019
+ const toolNameById = new Map();
1020
+
1021
+ for (const message of messages) {
1022
+ if (!message || typeof message !== 'object') continue;
1023
+ const role = message.role === 'assistant' ? 'model' : 'user';
1024
+ const contentBlocks = Array.isArray(message.content) ? message.content : [message.content];
1025
+ const parts = [];
1026
+
1027
+ for (const block of contentBlocks) {
1028
+ if (!block || typeof block !== 'object') {
1029
+ const text = extractText(block);
1030
+ if (text) parts.push({ text });
1031
+ continue;
1032
+ }
1033
+
1034
+ if (block.type === 'tool_use' && block.name) {
1035
+ const callId = String(block.id || generateToolCallId());
1036
+ const args = (block.input && typeof block.input === 'object' && !Array.isArray(block.input))
1037
+ ? block.input
1038
+ : {};
1039
+ toolNameById.set(callId, block.name);
1040
+ parts.push({
1041
+ functionCall: {
1042
+ name: block.name,
1043
+ args
1044
+ }
1045
+ });
1046
+ continue;
1047
+ }
1048
+
1049
+ if (block.type === 'tool_result') {
1050
+ const toolUseId = String(block.tool_use_id || block.id || '');
1051
+ const toolName = block.name || toolNameById.get(toolUseId);
1052
+ if (!toolName) {
1053
+ const text = normalizeToolResultContent(block.content);
1054
+ if (text) parts.push({ text });
1055
+ continue;
1056
+ }
1057
+
1058
+ parts.push({
1059
+ functionResponse: {
1060
+ name: toolName,
1061
+ response: normalizeGeminiFunctionResponsePayload(block.content)
1062
+ }
1063
+ });
1064
+ continue;
1065
+ }
1066
+
1067
+ if (block.type === 'image' || block.type === 'document') {
1068
+ const mediaPart = buildGeminiPartFromClaudeMediaBlock(block);
1069
+ if (mediaPart) {
1070
+ parts.push(mediaPart);
1071
+ continue;
1072
+ }
1073
+ }
1074
+
1075
+ const text = extractText(block);
1076
+ if (text) parts.push({ text });
1077
+ }
1078
+
1079
+ if (parts.length === 0) continue;
1080
+ contents.push({ role, parts });
1081
+ }
1082
+
1083
+ return contents;
1084
+ }
1085
+
1086
+ function convertOpenCodePayloadToGemini(pathname, payload = {}, fallbackModel = '') {
1087
+ const normalized = normalizeOpenCodeMessages(pathname, payload);
1088
+ const maxTokens = Number(payload.max_output_tokens ?? payload.max_tokens);
1089
+ const stopSequences = normalizeStopSequences(payload.stop);
1090
+ const tools = normalizeOpenAiToolsToGemini(payload.tools || []);
1091
+ const toolConfig = normalizeToolChoiceToGemini(payload.tool_choice);
1092
+ const thinkingConfig = normalizeReasoningEffortToGemini(payload.reasoning_effort);
1093
+ const candidateCount = Number(payload.n);
1094
+ const responseModalities = normalizeGeminiResponseModalities(payload.modalities);
1095
+ const imageConfig = (payload.image_config && typeof payload.image_config === 'object' && !Array.isArray(payload.image_config))
1096
+ ? payload.image_config
1097
+ : null;
1098
+
1099
+ const requestBody = {
1100
+ contents: buildGeminiContents(normalized.messages)
1101
+ };
1102
+
1103
+ if (normalized.systemBlocks && normalized.systemBlocks.length > 0) {
1104
+ requestBody.systemInstruction = {
1105
+ parts: normalized.systemBlocks.map(block => ({ text: block.text || '' })).filter(p => p.text)
1106
+ };
1107
+ }
1108
+
1109
+ const generationConfig = {};
1110
+ if (Number.isFinite(maxTokens) && maxTokens > 0) {
1111
+ generationConfig.maxOutputTokens = Math.round(maxTokens);
1112
+ }
1113
+ if (Number.isFinite(Number(payload.temperature))) {
1114
+ generationConfig.temperature = Number(payload.temperature);
1115
+ }
1116
+ if (Number.isFinite(Number(payload.top_p))) {
1117
+ generationConfig.topP = Number(payload.top_p);
1118
+ }
1119
+ if (Number.isFinite(Number(payload.top_k))) {
1120
+ generationConfig.topK = Number(payload.top_k);
1121
+ }
1122
+ if (stopSequences) {
1123
+ generationConfig.stopSequences = stopSequences;
1124
+ }
1125
+ if (thinkingConfig) {
1126
+ generationConfig.thinkingConfig = thinkingConfig;
1127
+ }
1128
+ if (Number.isFinite(candidateCount) && candidateCount > 1) {
1129
+ generationConfig.candidateCount = Math.round(candidateCount);
1130
+ }
1131
+ if (responseModalities) {
1132
+ generationConfig.responseModalities = responseModalities;
1133
+ }
1134
+ if (imageConfig) {
1135
+ const mappedImageConfig = {};
1136
+ if (typeof imageConfig.aspect_ratio === 'string' && imageConfig.aspect_ratio.trim()) {
1137
+ mappedImageConfig.aspectRatio = imageConfig.aspect_ratio.trim();
1138
+ }
1139
+ if (typeof imageConfig.image_size === 'string' && imageConfig.image_size.trim()) {
1140
+ mappedImageConfig.imageSize = imageConfig.image_size.trim();
1141
+ }
1142
+ if (Object.keys(mappedImageConfig).length > 0) {
1143
+ generationConfig.imageConfig = mappedImageConfig;
1144
+ }
1145
+ }
1146
+ if (Object.keys(generationConfig).length > 0) {
1147
+ requestBody.generationConfig = generationConfig;
1148
+ }
1149
+
1150
+ if (tools.length > 0) {
1151
+ requestBody.tools = tools;
1152
+ }
1153
+ if (toolConfig) {
1154
+ requestBody.toolConfig = toolConfig;
1155
+ }
1156
+
1157
+ return {
1158
+ model: payload.model || fallbackModel || '',
1159
+ requestBody
1160
+ };
1161
+ }
1162
+
1163
+ module.exports = {
1164
+ convertOpenCodePayloadToClaude,
1165
+ convertOpenCodePayloadToCodexResponses,
1166
+ convertOpenCodePayloadToGemini,
1167
+ stripClaudeToolNamePrefix
1168
+ };