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.
- package/CHANGELOG.md +599 -0
- package/LICENSE +21 -0
- package/README.md +439 -0
- package/bin/ctx.js +8 -0
- package/dist/web/assets/Analytics-DN_YsnkW.js +39 -0
- package/dist/web/assets/Analytics-DuYvId7u.css +1 -0
- package/dist/web/assets/ConfigTemplates-Bidwfdf2.css +1 -0
- package/dist/web/assets/ConfigTemplates-DpXIMy0p.js +1 -0
- package/dist/web/assets/Home-38JTUlYt.js +1 -0
- package/dist/web/assets/Home-CjupSEWE.css +1 -0
- package/dist/web/assets/PluginManager-CX2tgq2H.js +1 -0
- package/dist/web/assets/PluginManager-ROyoZ-6m.css +1 -0
- package/dist/web/assets/ProjectList-C1lDcsn6.js +1 -0
- package/dist/web/assets/ProjectList-oJIyIRkP.css +1 -0
- package/dist/web/assets/SessionList-C55tjV7i.css +1 -0
- package/dist/web/assets/SessionList-CZ7T6rVx.js +1 -0
- package/dist/web/assets/SkillManager-D7pd-d_P.css +1 -0
- package/dist/web/assets/SkillManager-DLN9f79y.js +1 -0
- package/dist/web/assets/WorkspaceManager-CrwgQgmP.css +1 -0
- package/dist/web/assets/WorkspaceManager-DxlHZkpZ.js +1 -0
- package/dist/web/assets/icons-DRrXwWZi.js +1 -0
- package/dist/web/assets/index-CetESrXw.css +1 -0
- package/dist/web/assets/index-Cfvn-2Gb.js +2 -0
- package/dist/web/assets/markdown-BfC0goYb.css +10 -0
- package/dist/web/assets/markdown-C9MYpaSi.js +1 -0
- package/dist/web/assets/naive-ui-DlpKk-8M.js +1 -0
- package/dist/web/assets/vendors-DMjSfzlv.js +7 -0
- package/dist/web/assets/vue-vendor-DET08QYg.js +45 -0
- package/dist/web/favicon.ico +0 -0
- package/dist/web/index.html +20 -0
- package/dist/web/logo.png +0 -0
- package/docs/bannel.png +0 -0
- package/docs/home.png +0 -0
- package/docs/logo.png +0 -0
- package/docs/model-redirection.md +251 -0
- package/docs/multi-channel-load-balancing.md +249 -0
- package/package.json +80 -0
- package/src/commands/channels.js +551 -0
- package/src/commands/cli-type.js +101 -0
- package/src/commands/daemon.js +365 -0
- package/src/commands/doctor.js +333 -0
- package/src/commands/export-config.js +205 -0
- package/src/commands/list.js +222 -0
- package/src/commands/logs.js +261 -0
- package/src/commands/plugin.js +585 -0
- package/src/commands/port-config.js +135 -0
- package/src/commands/proxy-control.js +264 -0
- package/src/commands/proxy.js +152 -0
- package/src/commands/resume.js +137 -0
- package/src/commands/search.js +190 -0
- package/src/commands/security.js +37 -0
- package/src/commands/stats.js +398 -0
- package/src/commands/switch.js +48 -0
- package/src/commands/toggle-proxy.js +247 -0
- package/src/commands/ui.js +99 -0
- package/src/commands/update.js +97 -0
- package/src/commands/workspace.js +454 -0
- package/src/config/default.js +69 -0
- package/src/config/loader.js +149 -0
- package/src/config/model-metadata.js +167 -0
- package/src/config/model-metadata.json +125 -0
- package/src/config/model-pricing.js +35 -0
- package/src/config/paths.js +190 -0
- package/src/index.js +680 -0
- package/src/plugins/constants.js +15 -0
- package/src/plugins/event-bus.js +54 -0
- package/src/plugins/manifest-validator.js +129 -0
- package/src/plugins/plugin-api.js +128 -0
- package/src/plugins/plugin-installer.js +601 -0
- package/src/plugins/plugin-loader.js +229 -0
- package/src/plugins/plugin-manager.js +170 -0
- package/src/plugins/registry.js +152 -0
- package/src/plugins/schema/plugin-manifest.json +115 -0
- package/src/reset-config.js +94 -0
- package/src/server/api/agents.js +826 -0
- package/src/server/api/aliases.js +36 -0
- package/src/server/api/channels.js +368 -0
- package/src/server/api/claude-hooks.js +480 -0
- package/src/server/api/codex-channels.js +417 -0
- package/src/server/api/codex-projects.js +104 -0
- package/src/server/api/codex-proxy.js +195 -0
- package/src/server/api/codex-sessions.js +483 -0
- package/src/server/api/codex-statistics.js +57 -0
- package/src/server/api/commands.js +482 -0
- package/src/server/api/config-export.js +212 -0
- package/src/server/api/config-registry.js +357 -0
- package/src/server/api/config-sync.js +155 -0
- package/src/server/api/config-templates.js +248 -0
- package/src/server/api/config.js +521 -0
- package/src/server/api/convert.js +260 -0
- package/src/server/api/dashboard.js +142 -0
- package/src/server/api/env.js +144 -0
- package/src/server/api/favorites.js +77 -0
- package/src/server/api/gemini-channels.js +366 -0
- package/src/server/api/gemini-projects.js +91 -0
- package/src/server/api/gemini-proxy.js +173 -0
- package/src/server/api/gemini-sessions.js +376 -0
- package/src/server/api/gemini-statistics.js +57 -0
- package/src/server/api/health-check.js +31 -0
- package/src/server/api/mcp.js +399 -0
- package/src/server/api/opencode-channels.js +419 -0
- package/src/server/api/opencode-projects.js +99 -0
- package/src/server/api/opencode-proxy.js +207 -0
- package/src/server/api/opencode-sessions.js +327 -0
- package/src/server/api/opencode-statistics.js +57 -0
- package/src/server/api/plugins.js +463 -0
- package/src/server/api/pm2-autostart.js +269 -0
- package/src/server/api/projects.js +124 -0
- package/src/server/api/prompts.js +279 -0
- package/src/server/api/proxy.js +306 -0
- package/src/server/api/security.js +53 -0
- package/src/server/api/sessions.js +514 -0
- package/src/server/api/settings.js +142 -0
- package/src/server/api/skills.js +570 -0
- package/src/server/api/statistics.js +238 -0
- package/src/server/api/ui-config.js +64 -0
- package/src/server/api/workspaces.js +456 -0
- package/src/server/codex-proxy-server.js +681 -0
- package/src/server/dev-server.js +26 -0
- package/src/server/gemini-proxy-server.js +610 -0
- package/src/server/index.js +422 -0
- package/src/server/opencode-proxy-server.js +4771 -0
- package/src/server/proxy-server.js +669 -0
- package/src/server/services/agents-service.js +1137 -0
- package/src/server/services/alias.js +71 -0
- package/src/server/services/channel-health.js +234 -0
- package/src/server/services/channel-scheduler.js +240 -0
- package/src/server/services/channels.js +447 -0
- package/src/server/services/codex-channels.js +705 -0
- package/src/server/services/codex-config.js +90 -0
- package/src/server/services/codex-parser.js +322 -0
- package/src/server/services/codex-sessions.js +936 -0
- package/src/server/services/codex-settings-manager.js +619 -0
- package/src/server/services/codex-speed-test-template.json +24 -0
- package/src/server/services/codex-statistics-service.js +161 -0
- package/src/server/services/commands-service.js +574 -0
- package/src/server/services/config-export-service.js +1165 -0
- package/src/server/services/config-registry-service.js +828 -0
- package/src/server/services/config-sync-manager.js +941 -0
- package/src/server/services/config-sync-service.js +504 -0
- package/src/server/services/config-templates-service.js +913 -0
- package/src/server/services/enhanced-cache.js +196 -0
- package/src/server/services/env-checker.js +409 -0
- package/src/server/services/env-manager.js +436 -0
- package/src/server/services/favorites.js +165 -0
- package/src/server/services/format-converter.js +620 -0
- package/src/server/services/gemini-channels.js +459 -0
- package/src/server/services/gemini-config.js +73 -0
- package/src/server/services/gemini-sessions.js +689 -0
- package/src/server/services/gemini-settings-manager.js +263 -0
- package/src/server/services/gemini-statistics-service.js +157 -0
- package/src/server/services/health-check.js +85 -0
- package/src/server/services/mcp-client.js +790 -0
- package/src/server/services/mcp-service.js +1732 -0
- package/src/server/services/model-detector.js +1245 -0
- package/src/server/services/network-access.js +80 -0
- package/src/server/services/opencode-channels.js +366 -0
- package/src/server/services/opencode-gateway-adapters.js +1168 -0
- package/src/server/services/opencode-gateway-converter.js +639 -0
- package/src/server/services/opencode-sessions.js +931 -0
- package/src/server/services/opencode-settings-manager.js +478 -0
- package/src/server/services/opencode-statistics-service.js +161 -0
- package/src/server/services/plugins-service.js +1268 -0
- package/src/server/services/prompts-service.js +534 -0
- package/src/server/services/proxy-runtime.js +79 -0
- package/src/server/services/repo-scanner-base.js +708 -0
- package/src/server/services/request-logger.js +130 -0
- package/src/server/services/response-decoder.js +21 -0
- package/src/server/services/security-config.js +131 -0
- package/src/server/services/session-cache.js +127 -0
- package/src/server/services/session-converter.js +577 -0
- package/src/server/services/sessions.js +900 -0
- package/src/server/services/settings-manager.js +163 -0
- package/src/server/services/skill-service.js +1482 -0
- package/src/server/services/speed-test.js +1146 -0
- package/src/server/services/statistics-service.js +1043 -0
- package/src/server/services/ui-config.js +132 -0
- package/src/server/services/workspace-service.js +830 -0
- package/src/server/utils/pricing.js +73 -0
- package/src/server/websocket-server.js +513 -0
- package/src/ui/menu.js +139 -0
- package/src/ui/prompts.js +100 -0
- package/src/utils/format.js +43 -0
- package/src/utils/port-helper.js +108 -0
- 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
|
+
};
|