coding-tool-x 3.8.1 → 3.8.3

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 (36) hide show
  1. package/CHANGELOG.md +2 -0
  2. package/dist/web/assets/{Analytics-8dRv1aYW.js → Analytics-COEUXmd4.js} +1 -1
  3. package/dist/web/assets/{ConfigTemplates-BQRXSICO.js → ConfigTemplates-DLLkS53H.js} +1 -1
  4. package/dist/web/assets/{Home-BtzWhKIa.js → Home-DCEyLzwX.js} +1 -1
  5. package/dist/web/assets/{PluginManager-BTa-Ygf6.js → PluginManager-Oe1ZHoMO.js} +1 -1
  6. package/dist/web/assets/{ProjectList-F_cB8ldA.js → ProjectList-CWjuAFof.js} +1 -1
  7. package/dist/web/assets/{SessionList-LOrbPQvu.js → SessionList-v3ehGg9U.js} +1 -1
  8. package/dist/web/assets/{SkillManager-BzI_t-yV.js → SkillManager-DUF_qP5S.js} +1 -1
  9. package/dist/web/assets/{WorkspaceManager-Db9RGiz7.js → WorkspaceManager-CjAWBosO.js} +1 -1
  10. package/dist/web/assets/index-D5hyHPj7.js +2 -0
  11. package/dist/web/index.html +1 -1
  12. package/docs/superpowers/plans/2026-08-10-codex-conversion-compatibility.md +251 -0
  13. package/docs/superpowers/plans/2026-08-11-windows-command-window-hiding.md +90 -0
  14. package/docs/superpowers/plans/2026-08-13-omp-restart-recovery.md +48 -0
  15. package/docs/superpowers/specs/2026-08-10-codex-conversion-compatibility-design.md +61 -0
  16. package/docs/superpowers/specs/2026-08-11-windows-command-window-hiding-design.md +44 -0
  17. package/docs/superpowers/specs/2026-08-13-omp-restart-recovery-design.md +22 -0
  18. package/package.json +1 -1
  19. package/src/commands/daemon.js +3 -12
  20. package/src/commands/update.js +1 -1
  21. package/src/index.js +8 -1
  22. package/src/server/omp-proxy-server.js +9 -6
  23. package/src/server/opencode-proxy-server.js +55 -1037
  24. package/src/server/services/claude-wire.js +694 -0
  25. package/src/server/services/codex-wire.js +219 -0
  26. package/src/server/services/gemini-wire.js +759 -0
  27. package/src/server/services/model-detector.js +49 -131
  28. package/src/server/services/omp-channels.js +23 -4
  29. package/src/server/services/omp-gateway.js +29 -1
  30. package/src/server/services/omp-settings-manager.js +27 -3
  31. package/src/server/services/opencode-gateway-adapters.js +22 -1147
  32. package/src/server/services/opencode-normalization.js +546 -0
  33. package/src/server/services/session-history-index.js +1 -1
  34. package/src/server/services/session-history-worker.js +2 -1
  35. package/src/server/services/speed-test.js +85 -303
  36. package/dist/web/assets/index-DIWJmyL6.js +0 -2
@@ -26,8 +26,16 @@ const {
26
26
  resolveTargetUrl,
27
27
  ensureOpenAiStreamUsage
28
28
  } = require('./services/base/proxy-utils');
29
- const { parseSSEUsage, parseNonStreamingUsage, mergeUsageIntoTokenData, createTokenData } = require('./services/base/response-usage-parser');
30
29
  const { attachServerShutdownHandling, expediteServerShutdown } = require('./services/server-shutdown');
30
+ const { buildCodexTargetUrl, createCodexRequest } = require('./services/codex-wire');
31
+ const {
32
+ parseSSEUsage,
33
+ parseNonStreamingUsage,
34
+ mergeUsageIntoTokenData,
35
+ createTokenData
36
+ } = require('./services/base/response-usage-parser');
37
+ const { createClaudeRequest, buildClaudeTargetUrl, buildClaudeCountTokensTargetUrl, buildClaudeCountTokensHeaders } = require('./services/claude-wire');
38
+ const { createGeminiRequest, buildGeminiTargetUrl, shouldUseGeminiCliFormat } = require('./services/gemini-wire');
31
39
 
32
40
  let proxyServer = null;
33
41
  let proxyApp = null;
@@ -58,45 +66,13 @@ const PRICING = {
58
66
  };
59
67
 
60
68
  const OPENCODE_BASE_PRICING = DEFAULT_CONFIG.pricing.opencode || DEFAULT_CONFIG.pricing.codex;
61
- const CLAUDE_CODE_USER_AGENT = 'claude-cli/2.1.59 (external, cli)';
62
- const CLAUDE_MESSAGES_BETA_FLAGS = Object.freeze([
63
- 'claude-code-20250219',
64
- 'interleaved-thinking-2025-05-14',
65
- 'prompt-caching-scope-2026-01-05',
66
- 'effort-2025-11-24'
67
- ]);
68
- const CLAUDE_ADVANCED_TOOL_USE_BETA = 'advanced-tool-use-2025-11-20';
69
- const CLAUDE_COUNT_TOKENS_BETA_FLAGS = Object.freeze([
70
- 'claude-code-20250219',
71
- 'token-counting-2024-11-01'
72
- ]);
73
- const DEFAULT_CLAUDE_CODE_SYSTEM_PROMPT = "You are Claude Code, Anthropic's official CLI for Claude.";
74
- const DEFAULT_CLAUDE_CODE_TOOL_NAMES = Object.freeze([
75
- 'Task',
76
- 'Bash',
77
- 'Glob',
78
- 'Grep',
79
- 'Read',
80
- 'Edit',
81
- 'Write',
82
- 'ToolSearch'
83
- ]);
84
- const CODEX_CLI_VERSION = '0.101.0';
85
- const CODEX_CLI_USER_AGENT = 'codex_cli_rs/0.101.0 (Mac OS 26.0.1; arm64) Apple_Terminal/464';
86
- const GEMINI_CLI_USER_AGENT = 'google-api-nodejs-client/9.15.1';
87
- const GEMINI_CLI_API_CLIENT = 'gl-node/22.17.0';
88
- const GEMINI_CLI_CLIENT_METADATA = 'ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI';
89
69
  const CLAUDE_SESSION_USER_ID_TTL_MS = 60 * 60 * 1000;
90
70
  const CLAUDE_SESSION_USER_ID_CACHE_MAX = 2000;
91
71
  const claudeSessionUserIdCache = new Map();
92
72
  const CLAUDE_USER_ID_ACCOUNT_RE = /^user_([0-9a-f]{64})_account__session_[a-z0-9._-]+$/i;
93
73
  const CLAUDE_USER_ID_FULL_RE = /^user_[0-9a-f]{64}_account__session_[a-z0-9._-]+$/i;
94
- const CLAUDE_TEMPLATE_SYSTEM_MIN_CHARS = 1000;
95
- const CLAUDE_TEMPLATE_CACHE_TTL_MS = 30 * 1000;
96
74
  let cachedClaudeAccountId = '';
97
75
  let cachedClaudeUserId = '';
98
- let cachedClaudeRequestTemplate = null;
99
- let cachedClaudeRequestTemplateAt = 0;
100
76
 
101
77
  // detectModelTier, redirectModel, resolveTargetUrl imported from services/base/proxy-utils
102
78
  const resolveOpenCodeTarget = resolveTargetUrl;
@@ -196,7 +172,7 @@ function resolveClaudePreferredUserId() {
196
172
  return cachedClaudeUserId;
197
173
  }
198
174
 
199
- const requestTemplate = resolveClaudeRequestTemplate();
175
+ const requestTemplate = loadClaudeRequestTemplate();
200
176
  if (requestTemplate && CLAUDE_USER_ID_FULL_RE.test(requestTemplate.userId || '')) {
201
177
  cachedClaudeUserId = requestTemplate.userId;
202
178
  return cachedClaudeUserId;
@@ -336,135 +312,6 @@ function normalizeGatewaySourceType(channel) {
336
312
  return 'codex';
337
313
  }
338
314
 
339
- function mapStainlessOs() {
340
- switch (process.platform) {
341
- case 'darwin':
342
- return 'MacOS';
343
- case 'win32':
344
- return 'Windows';
345
- case 'linux':
346
- return 'Linux';
347
- default:
348
- return `other::${process.platform}`;
349
- }
350
- }
351
-
352
- function mapStainlessArch() {
353
- switch (process.arch) {
354
- case 'x64':
355
- return 'x64';
356
- case 'arm64':
357
- return 'arm64';
358
- case 'ia32':
359
- return 'x86';
360
- default:
361
- return `other::${process.arch}`;
362
- }
363
- }
364
-
365
- function buildClaudeBetaHeader(options = {}) {
366
- const requestType = options.requestType === 'count_tokens' ? 'count_tokens' : 'messages';
367
- const hasTools = !!options.hasTools;
368
- const betaFlags = requestType === 'count_tokens'
369
- ? [...CLAUDE_COUNT_TOKENS_BETA_FLAGS]
370
- : [...CLAUDE_MESSAGES_BETA_FLAGS];
371
-
372
- if (requestType === 'messages' && hasTools) {
373
- betaFlags.push(CLAUDE_ADVANCED_TOOL_USE_BETA);
374
- }
375
-
376
- return betaFlags.join(',');
377
- }
378
-
379
-
380
- function hasExpectedClaudeToolSet(tools = []) {
381
- if (!Array.isArray(tools) || tools.length < DEFAULT_CLAUDE_CODE_TOOL_NAMES.length) {
382
- return false;
383
- }
384
- const names = new Set();
385
- tools.forEach((tool) => {
386
- if (tool && typeof tool === 'object' && typeof tool.name === 'string') {
387
- names.add(tool.name);
388
- }
389
- });
390
- return DEFAULT_CLAUDE_CODE_TOOL_NAMES.every(name => names.has(name));
391
- }
392
-
393
- function extractClaudeSystemCharCount(system = []) {
394
- if (!Array.isArray(system)) return 0;
395
- return system.reduce((sum, block) => {
396
- if (!block || typeof block !== 'object') return sum;
397
- const text = typeof block.text === 'string' ? block.text : '';
398
- return sum + text.length;
399
- }, 0);
400
- }
401
-
402
- function cloneJson(value) {
403
- try {
404
- return JSON.parse(JSON.stringify(value));
405
- } catch {
406
- return value;
407
- }
408
- }
409
-
410
- function resolveClaudeRequestTemplate() {
411
- const now = Date.now();
412
- if (cachedClaudeRequestTemplate && now - cachedClaudeRequestTemplateAt < CLAUDE_TEMPLATE_CACHE_TTL_MS) {
413
- return cachedClaudeRequestTemplate;
414
- }
415
- cachedClaudeRequestTemplate = loadClaudeRequestTemplate();
416
- cachedClaudeRequestTemplateAt = now;
417
- return cachedClaudeRequestTemplate;
418
- }
419
-
420
- function buildClaudeSystemBlocks(systemText = '', templateSystem = []) {
421
- const blocks = Array.isArray(templateSystem) && templateSystem.length > 0
422
- ? cloneJson(templateSystem)
423
- : [];
424
-
425
- const normalizedSystem = String(systemText || '').trim();
426
- if (normalizedSystem) {
427
- blocks.push({
428
- type: 'text',
429
- text: normalizedSystem
430
- });
431
- }
432
-
433
- if (blocks.length === 0) {
434
- blocks.push({
435
- type: 'text',
436
- text: DEFAULT_CLAUDE_CODE_SYSTEM_PROMPT
437
- });
438
- }
439
-
440
- return blocks;
441
- }
442
-
443
- function buildClaudeRequestHeaders(apiKey, options = {}) {
444
- return {
445
- 'x-api-key': apiKey,
446
- authorization: `Bearer ${apiKey}`,
447
- 'anthropic-version': '2023-06-01',
448
- 'anthropic-beta': buildClaudeBetaHeader(options),
449
- 'anthropic-dangerous-direct-browser-access': 'true',
450
- 'x-app': 'cli',
451
- 'x-stainless-retry-count': '0',
452
- 'x-stainless-timeout': '600',
453
- 'x-stainless-runtime-version': process.version,
454
- 'x-stainless-package-version': '0.74.0',
455
- 'x-stainless-runtime': 'node',
456
- 'x-stainless-lang': 'js',
457
- 'x-stainless-arch': mapStainlessArch(),
458
- 'x-stainless-os': mapStainlessOs(),
459
- 'content-type': 'application/json',
460
- accept: 'application/json',
461
- 'accept-encoding': 'gzip, deflate',
462
- 'accept-language': '*',
463
- 'sec-fetch-mode': 'cors',
464
- connection: 'keep-alive',
465
- 'user-agent': CLAUDE_CODE_USER_AGENT
466
- };
467
- }
468
315
 
469
316
  function getRequestPathname(urlPath = '') {
470
317
  try {
@@ -524,283 +371,12 @@ function isConverterPresetChannel(channel) {
524
371
  return presetId === 'entry_claude' || presetId === 'entry_codex' || presetId === 'entry_gemini';
525
372
  }
526
373
 
527
- function extractTextFragments(value, fragments) {
528
- if (value === null || value === undefined) return;
529
- if (typeof value === 'string') {
530
- if (value.trim()) fragments.push(value);
531
- return;
532
- }
533
- if (typeof value === 'number' || typeof value === 'boolean') {
534
- fragments.push(String(value));
535
- return;
536
- }
537
- if (Array.isArray(value)) {
538
- value.forEach(item => extractTextFragments(item, fragments));
539
- return;
540
- }
541
- if (typeof value !== 'object') return;
542
-
543
- if (typeof value.text === 'string') {
544
- extractTextFragments(value.text, fragments);
545
- return;
546
- }
547
- if (typeof value.input_text === 'string') {
548
- extractTextFragments(value.input_text, fragments);
549
- return;
550
- }
551
- if (typeof value.output_text === 'string') {
552
- extractTextFragments(value.output_text, fragments);
553
- return;
554
- }
555
- if (value.content !== undefined) {
556
- extractTextFragments(value.content, fragments);
557
- return;
558
- }
559
- if (Array.isArray(value.parts)) {
560
- extractTextFragments(value.parts, fragments);
561
- }
562
- }
563
-
564
- function extractText(value) {
565
- const fragments = [];
566
- extractTextFragments(value, fragments);
567
- return fragments.join('\n').trim();
568
- }
569
-
570
- function normalizeOpenAiRole(role) {
571
- const value = String(role || '').trim().toLowerCase();
572
- if (value === 'assistant' || value === 'model') return 'assistant';
573
- if (value === 'system') return 'system';
574
- return 'user';
575
- }
576
-
577
- function normalizeOpenAiToolsToClaude(tools = []) {
578
- if (!Array.isArray(tools)) return [];
579
-
580
- const normalized = [];
581
- for (const tool of tools) {
582
- if (!tool || typeof tool !== 'object') continue;
583
-
584
- if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
585
- const fn = tool.function;
586
- if (!fn.name) continue;
587
- normalized.push({
588
- name: fn.name,
589
- description: fn.description || '',
590
- input_schema: fn.parameters || { type: 'object', properties: {} }
591
- });
592
- continue;
593
- }
594
-
595
- if (tool.type === 'function' && tool.name) {
596
- normalized.push({
597
- name: tool.name,
598
- description: tool.description || '',
599
- input_schema: tool.parameters || { type: 'object', properties: {} }
600
- });
601
- }
602
- }
603
-
604
- return normalized;
605
- }
606
-
607
- function normalizeToolChoiceToClaude(toolChoice) {
608
- if (!toolChoice) return undefined;
609
-
610
- if (typeof toolChoice === 'string') {
611
- if (toolChoice === 'auto') return { type: 'auto' };
612
- if (toolChoice === 'required') return { type: 'any' };
613
- return undefined;
614
- }
615
-
616
- if (typeof toolChoice === 'object') {
617
- if (toolChoice.type === 'function' && toolChoice.function?.name) {
618
- return { type: 'tool', name: toolChoice.function.name };
619
- }
620
- if (toolChoice.type === 'function' && toolChoice.name) {
621
- return { type: 'tool', name: toolChoice.name };
622
- }
623
- if (toolChoice.type === 'auto') return { type: 'auto' };
624
- if (toolChoice.type === 'required') return { type: 'any' };
625
- }
626
-
627
- return undefined;
628
- }
629
374
 
630
375
  function generateToolCallId() {
631
376
  return `toolu_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
632
377
  }
633
378
 
634
- function parseToolArguments(value) {
635
- if (value && typeof value === 'object' && !Array.isArray(value)) {
636
- return value;
637
- }
638
- if (typeof value === 'string') {
639
- const trimmed = value.trim();
640
- if (!trimmed) return {};
641
- try {
642
- const parsed = JSON.parse(trimmed);
643
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
644
- return parsed;
645
- }
646
- } catch {
647
- return {};
648
- }
649
- }
650
- return {};
651
- }
652
-
653
- function normalizeToolResultContent(value) {
654
- if (typeof value === 'string') return value;
655
- if (value === null || value === undefined) return '';
656
- if (typeof value === 'number' || typeof value === 'boolean') return String(value);
657
- try {
658
- return JSON.stringify(value);
659
- } catch {
660
- return String(value);
661
- }
662
- }
663
-
664
- function buildAssistantToolUseMessageFromFunctionCall(item) {
665
- const functionPayload = (item?.function && typeof item.function === 'object')
666
- ? item.function
667
- : item;
668
- const name = functionPayload?.name || item?.name;
669
- if (!name) return null;
670
-
671
- const callId = functionPayload?.call_id || item?.call_id || functionPayload?.id || item?.id || generateToolCallId();
672
- const argumentsSource = functionPayload?.arguments ?? item?.arguments ?? functionPayload?.input ?? item?.input;
673
- const input = parseToolArguments(argumentsSource);
674
-
675
- return {
676
- role: 'assistant',
677
- content: [
678
- {
679
- type: 'tool_use',
680
- id: callId,
681
- name,
682
- input
683
- }
684
- ]
685
- };
686
- }
687
-
688
- function buildUserToolResultMessage(item) {
689
- const callId = item?.call_id || item?.tool_call_id || item?.id || generateToolCallId();
690
- const outputSource = item?.output ?? item?.content ?? '';
691
- const content = normalizeToolResultContent(outputSource);
692
-
693
- return {
694
- role: 'user',
695
- content: [
696
- {
697
- type: 'tool_result',
698
- tool_use_id: callId,
699
- content
700
- }
701
- ]
702
- };
703
- }
704
-
705
- function normalizeOpenCodeMessages(pathname, payload = {}) {
706
- const systemParts = [];
707
- const messages = [];
708
-
709
- if (isResponsesPath(pathname) && typeof payload.instructions === 'string' && payload.instructions.trim()) {
710
- systemParts.push(payload.instructions.trim());
711
- }
712
-
713
- const appendMessage = (role, content) => {
714
- const normalizedRole = normalizeOpenAiRole(role);
715
- const text = extractText(content);
716
- if (!text) return;
717
- if (normalizedRole === 'system') {
718
- systemParts.push(text);
719
- return;
720
- }
721
- messages.push({
722
- role: normalizedRole === 'assistant' ? 'assistant' : 'user',
723
- content: [{ type: 'text', text }]
724
- });
725
- };
726
-
727
- if (isResponsesPath(pathname)) {
728
- if (typeof payload.input === 'string') {
729
- appendMessage('user', payload.input);
730
- } else if (Array.isArray(payload.input)) {
731
- payload.input.forEach(item => {
732
- if (!item || typeof item !== 'object') return;
733
- if (item.type === 'function_call') {
734
- const assistantToolUse = buildAssistantToolUseMessageFromFunctionCall(item);
735
- if (assistantToolUse) {
736
- messages.push(assistantToolUse);
737
- }
738
- return;
739
- }
740
- if (item.type === 'function_call_output') {
741
- messages.push(buildUserToolResultMessage(item));
742
- return;
743
- }
744
- if (item.type === 'message' || item.role) {
745
- appendMessage(item.role, item.content);
746
- }
747
- });
748
- }
749
- }
750
-
751
- if (isChatCompletionsPath(pathname) && Array.isArray(payload.messages)) {
752
- payload.messages.forEach(message => {
753
- if (!message || typeof message !== 'object') return;
754
- if (message.role === 'tool') {
755
- messages.push(buildUserToolResultMessage(message));
756
- return;
757
- }
758
- if (message.role === 'assistant' && Array.isArray(message.tool_calls) && message.tool_calls.length > 0) {
759
- const assistantContent = [];
760
- const text = extractText(message.content);
761
- if (text) {
762
- assistantContent.push({ type: 'text', text });
763
- }
764
-
765
- message.tool_calls.forEach(toolCall => {
766
- if (!toolCall || typeof toolCall !== 'object') return;
767
- const functionPayload = (toolCall.function && typeof toolCall.function === 'object')
768
- ? toolCall.function
769
- : toolCall;
770
- const name = functionPayload.name || toolCall.name;
771
- if (!name) return;
772
- assistantContent.push({
773
- type: 'tool_use',
774
- id: toolCall.id || functionPayload.call_id || generateToolCallId(),
775
- name,
776
- input: parseToolArguments(functionPayload.arguments ?? functionPayload.input)
777
- });
778
- });
779
-
780
- if (assistantContent.length > 0) {
781
- messages.push({
782
- role: 'assistant',
783
- content: assistantContent
784
- });
785
- }
786
- return;
787
- }
788
- appendMessage(message.role, message.content);
789
- });
790
- }
791
-
792
- if (messages.length === 0) {
793
- messages.push({
794
- role: 'user',
795
- content: [{ type: 'text', text: 'Hello' }]
796
- });
797
- }
798
379
 
799
- return {
800
- system: systemParts.join('\n\n').trim(),
801
- messages
802
- };
803
- }
804
380
 
805
381
  function buildClaudeCodeUserId(sessionIdSeed = '') {
806
382
  const preferredUserId = resolveClaudePreferredUserId();
@@ -812,548 +388,8 @@ function buildClaudeCodeUserId(sessionIdSeed = '') {
812
388
  return `user_${accountId}_account__session_${sessionId}`;
813
389
  }
814
390
 
815
- function normalizeClaudeMetadata(metadata, fallbackUserId = '') {
816
- const normalized = (metadata && typeof metadata === 'object' && !Array.isArray(metadata))
817
- ? { ...metadata }
818
- : {};
819
- const userId = normalizeSessionKeyValue(normalized.user_id);
820
- const fallback = normalizeSessionKeyValue(fallbackUserId);
821
-
822
- if (CLAUDE_USER_ID_FULL_RE.test(userId)) {
823
- normalized.user_id = userId;
824
- return normalized;
825
- }
826
- if (CLAUDE_USER_ID_FULL_RE.test(fallback)) {
827
- normalized.user_id = fallback;
828
- return normalized;
829
- }
830
-
831
- normalized.user_id = buildClaudeCodeUserId(fallback);
832
- return normalized;
833
- }
834
-
835
- function convertOpenCodePayloadToClaude(pathname, payload = {}, fallbackModel = '', options = {}) {
836
- const normalized = normalizeOpenCodeMessages(pathname, payload);
837
- const maxTokens = Number(payload.max_output_tokens ?? payload.max_tokens);
838
-
839
- const converted = {
840
- model: payload.model || fallbackModel || 'claude-sonnet-4-20250514',
841
- max_tokens: Number.isFinite(maxTokens) && maxTokens > 0 ? Math.round(maxTokens) : 4096,
842
- stream: false,
843
- messages: normalized.messages
844
- };
845
-
846
- const template = resolveClaudeRequestTemplate();
847
-
848
- converted.system = buildClaudeSystemBlocks(normalized.system, template.system);
849
-
850
- const tools = normalizeOpenAiToolsToClaude(payload.tools || []);
851
- converted.tools = tools.length > 0 ? tools : template.tools;
852
-
853
- const toolChoice = normalizeToolChoiceToClaude(payload.tool_choice);
854
- if (toolChoice) {
855
- converted.tool_choice = toolChoice;
856
- }
857
-
858
- converted.temperature = Number.isFinite(Number(payload.temperature)) ? Number(payload.temperature) : 1;
859
- if (Number.isFinite(Number(payload.top_p))) {
860
- converted.top_p = Number(payload.top_p);
861
- }
862
- if (Number.isFinite(Number(payload.top_k))) {
863
- converted.top_k = Number(payload.top_k);
864
- }
865
-
866
- // 某些 Claude relay 会校验 metadata.user_id 以识别 Claude Code 请求
867
- converted.metadata = normalizeClaudeMetadata(payload.metadata, options.sessionUserId || template.userId || '');
868
-
869
- return converted;
870
- }
871
-
872
- function normalizeOpenAiToolsToGemini(tools = []) {
873
- if (!Array.isArray(tools)) return [];
874
-
875
- const functionDeclarations = [];
876
- for (const tool of tools) {
877
- if (!tool || typeof tool !== 'object') continue;
878
-
879
- if (tool.type === 'function' && tool.function && typeof tool.function === 'object') {
880
- const fn = tool.function;
881
- if (!fn.name) continue;
882
- functionDeclarations.push({
883
- name: fn.name,
884
- description: fn.description || '',
885
- parameters: fn.parameters || { type: 'object', properties: {} }
886
- });
887
- continue;
888
- }
889
-
890
- if (tool.type === 'function' && tool.name) {
891
- functionDeclarations.push({
892
- name: tool.name,
893
- description: tool.description || '',
894
- parameters: tool.parameters || { type: 'object', properties: {} }
895
- });
896
- }
897
- }
898
-
899
- if (functionDeclarations.length === 0) return [];
900
- return [{ functionDeclarations }];
901
- }
902
-
903
- function normalizeToolChoiceToGemini(toolChoice) {
904
- if (!toolChoice) return undefined;
905
391
 
906
- if (typeof toolChoice === 'string') {
907
- if (toolChoice === 'auto') {
908
- return { functionCallingConfig: { mode: 'AUTO' } };
909
- }
910
- if (toolChoice === 'required') {
911
- return { functionCallingConfig: { mode: 'ANY' } };
912
- }
913
- if (toolChoice === 'none') {
914
- return { functionCallingConfig: { mode: 'NONE' } };
915
- }
916
- return undefined;
917
- }
918
-
919
- if (typeof toolChoice === 'object') {
920
- const functionName = toolChoice.function?.name || toolChoice.name;
921
- if (toolChoice.type === 'function' && functionName) {
922
- return {
923
- functionCallingConfig: {
924
- mode: 'ANY',
925
- allowedFunctionNames: [functionName]
926
- }
927
- };
928
- }
929
- if (toolChoice.type === 'auto') {
930
- return { functionCallingConfig: { mode: 'AUTO' } };
931
- }
932
- if (toolChoice.type === 'required') {
933
- return { functionCallingConfig: { mode: 'ANY' } };
934
- }
935
- if (toolChoice.type === 'none') {
936
- return { functionCallingConfig: { mode: 'NONE' } };
937
- }
938
- }
939
-
940
- return undefined;
941
- }
942
-
943
- function normalizeStopSequences(stopValue) {
944
- if (!stopValue) return undefined;
945
- if (typeof stopValue === 'string' && stopValue.trim()) {
946
- return [stopValue];
947
- }
948
- if (Array.isArray(stopValue)) {
949
- const sequences = stopValue
950
- .filter(item => typeof item === 'string')
951
- .map(item => item.trim())
952
- .filter(Boolean);
953
- return sequences.length > 0 ? sequences : undefined;
954
- }
955
- return undefined;
956
- }
957
-
958
- function normalizeGeminiFunctionResponsePayload(value) {
959
- if (value && typeof value === 'object' && !Array.isArray(value)) {
960
- return value;
961
- }
962
- if (typeof value === 'string') {
963
- const trimmed = value.trim();
964
- if (!trimmed) return { content: '' };
965
- try {
966
- const parsed = JSON.parse(trimmed);
967
- if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
968
- return parsed;
969
- }
970
- } catch {
971
- return { content: value };
972
- }
973
- return { content: value };
974
- }
975
- return { content: normalizeToolResultContent(value) };
976
- }
977
-
978
- function buildGeminiContents(messages = []) {
979
- const contents = [];
980
- const toolNameById = new Map();
981
-
982
- for (const message of messages) {
983
- if (!message || typeof message !== 'object') continue;
984
- const role = message.role === 'assistant' ? 'model' : 'user';
985
- const contentBlocks = Array.isArray(message.content) ? message.content : [message.content];
986
- const parts = [];
987
-
988
- for (const block of contentBlocks) {
989
- if (!block || typeof block !== 'object') {
990
- const text = extractText(block);
991
- if (text) {
992
- parts.push({ text });
993
- }
994
- continue;
995
- }
996
-
997
- if (block.type === 'tool_use' && block.name) {
998
- const callId = String(block.id || generateToolCallId());
999
- const args = (block.input && typeof block.input === 'object' && !Array.isArray(block.input))
1000
- ? block.input
1001
- : {};
1002
- toolNameById.set(callId, block.name);
1003
- parts.push({
1004
- functionCall: {
1005
- name: block.name,
1006
- args
1007
- }
1008
- });
1009
- continue;
1010
- }
1011
-
1012
- if (block.type === 'tool_result') {
1013
- const toolUseId = String(block.tool_use_id || block.id || '');
1014
- const toolName = block.name || toolNameById.get(toolUseId);
1015
- if (!toolName) {
1016
- const text = normalizeToolResultContent(block.content);
1017
- if (text) {
1018
- parts.push({ text });
1019
- }
1020
- continue;
1021
- }
1022
-
1023
- parts.push({
1024
- functionResponse: {
1025
- name: toolName,
1026
- response: normalizeGeminiFunctionResponsePayload(block.content)
1027
- }
1028
- });
1029
- continue;
1030
- }
1031
-
1032
- const text = extractText(block);
1033
- if (text) {
1034
- parts.push({ text });
1035
- }
1036
- }
1037
-
1038
- if (parts.length === 0) continue;
1039
- contents.push({ role, parts });
1040
- }
1041
- return contents;
1042
- }
1043
-
1044
- function cloneJsonCompatible(value) {
1045
- try {
1046
- return JSON.parse(JSON.stringify(value));
1047
- } catch {
1048
- return value;
1049
- }
1050
- }
1051
-
1052
- function normalizeCodexResponsesInput(inputValue) {
1053
- if (typeof inputValue === 'string') {
1054
- return [
1055
- {
1056
- type: 'message',
1057
- role: 'user',
1058
- content: [
1059
- {
1060
- type: 'input_text',
1061
- text: inputValue
1062
- }
1063
- ]
1064
- }
1065
- ];
1066
- }
1067
-
1068
- if (!Array.isArray(inputValue)) return undefined;
1069
- return inputValue.map(item => {
1070
- if (!item || typeof item !== 'object') return item;
1071
- const clonedItem = cloneJsonCompatible(item);
1072
- if (String(clonedItem?.role || '').trim().toLowerCase() === 'system') {
1073
- clonedItem.role = 'developer';
1074
- }
1075
- return clonedItem;
1076
- });
1077
- }
1078
-
1079
- function convertOpenCodePayloadToCodexResponses(payload = {}, fallbackModel = '') {
1080
- const requestBody = cloneJsonCompatible((payload && typeof payload === 'object') ? payload : {});
1081
- if (requestBody.model === undefined && fallbackModel) {
1082
- requestBody.model = fallbackModel;
1083
- }
1084
-
1085
- const normalizedInput = normalizeCodexResponsesInput(requestBody.input);
1086
- if (normalizedInput !== undefined) {
1087
- requestBody.input = normalizedInput;
1088
- }
1089
-
1090
- requestBody.stream = true;
1091
- requestBody.store = false;
1092
- if (requestBody.parallel_tool_calls === undefined) {
1093
- requestBody.parallel_tool_calls = true;
1094
- }
1095
- if (typeof requestBody.instructions !== 'string') {
1096
- requestBody.instructions = '';
1097
- }
1098
-
1099
- const include = Array.isArray(requestBody.include)
1100
- ? requestBody.include.filter(item => typeof item === 'string' && item.trim())
1101
- : [];
1102
- if (!include.includes('reasoning.encrypted_content')) {
1103
- include.push('reasoning.encrypted_content');
1104
- }
1105
- requestBody.include = include;
1106
-
1107
- delete requestBody.max_output_tokens;
1108
- delete requestBody.max_completion_tokens;
1109
- delete requestBody.temperature;
1110
- delete requestBody.top_p;
1111
- delete requestBody.service_tier;
1112
- delete requestBody.user;
1113
- delete requestBody.previous_response_id;
1114
- delete requestBody.prompt_cache_retention;
1115
- delete requestBody.safety_identifier;
1116
-
1117
- return {
1118
- requestBody,
1119
- model: requestBody.model || fallbackModel || ''
1120
- };
1121
- }
1122
-
1123
- function convertOpenCodePayloadToGemini(pathname, payload = {}, fallbackModel = '') {
1124
- const normalized = normalizeOpenCodeMessages(pathname, payload);
1125
- const maxTokens = Number(payload.max_output_tokens ?? payload.max_tokens);
1126
- const stopSequences = normalizeStopSequences(payload.stop);
1127
- const tools = normalizeOpenAiToolsToGemini(payload.tools || []);
1128
- const toolConfig = normalizeToolChoiceToGemini(payload.tool_choice);
1129
-
1130
- const requestBody = {
1131
- contents: buildGeminiContents(normalized.messages)
1132
- };
1133
-
1134
- if (normalized.system) {
1135
- requestBody.systemInstruction = {
1136
- parts: [{ text: normalized.system }]
1137
- };
1138
- }
1139
-
1140
- const generationConfig = {};
1141
- if (Number.isFinite(maxTokens) && maxTokens > 0) {
1142
- generationConfig.maxOutputTokens = Math.round(maxTokens);
1143
- }
1144
- if (Number.isFinite(Number(payload.temperature))) {
1145
- generationConfig.temperature = Number(payload.temperature);
1146
- }
1147
- if (Number.isFinite(Number(payload.top_p))) {
1148
- generationConfig.topP = Number(payload.top_p);
1149
- }
1150
- if (Number.isFinite(Number(payload.top_k))) {
1151
- generationConfig.topK = Number(payload.top_k);
1152
- }
1153
- if (stopSequences) {
1154
- generationConfig.stopSequences = stopSequences;
1155
- }
1156
- if (Object.keys(generationConfig).length > 0) {
1157
- requestBody.generationConfig = generationConfig;
1158
- }
1159
392
 
1160
- if (tools.length > 0) {
1161
- requestBody.tools = tools;
1162
- }
1163
- if (toolConfig) {
1164
- requestBody.toolConfig = toolConfig;
1165
- }
1166
-
1167
- return {
1168
- model: payload.model || fallbackModel || '',
1169
- requestBody
1170
- };
1171
- }
1172
-
1173
- function buildClaudeTargetUrl(baseUrl = '') {
1174
- let targetUrl;
1175
- try {
1176
- targetUrl = new URL(String(baseUrl || '').trim() || 'https://api.anthropic.com');
1177
- } catch {
1178
- targetUrl = new URL('https://api.anthropic.com');
1179
- }
1180
-
1181
- let pathname = targetUrl.pathname.replace(/\/+$/, '');
1182
- if (!pathname || pathname === '/') {
1183
- pathname = '/v1/messages';
1184
- } else if (pathname.endsWith('/messages')) {
1185
- // noop
1186
- } else if (pathname.endsWith('/v1')) {
1187
- pathname = `${pathname}/messages`;
1188
- } else {
1189
- pathname = `${pathname}/v1/messages`;
1190
- }
1191
-
1192
- targetUrl.pathname = pathname;
1193
- targetUrl.searchParams.set('beta', 'true');
1194
- return targetUrl.toString();
1195
- }
1196
-
1197
- function buildClaudeCountTokensTargetUrl(baseUrl = '') {
1198
- let targetUrl;
1199
- try {
1200
- targetUrl = new URL(String(baseUrl || '').trim() || 'https://api.anthropic.com');
1201
- } catch {
1202
- targetUrl = new URL('https://api.anthropic.com');
1203
- }
1204
-
1205
- let pathname = targetUrl.pathname.replace(/\/+$/, '');
1206
- if (!pathname || pathname === '/') {
1207
- pathname = '/v1/messages/count_tokens';
1208
- } else if (pathname.endsWith('/messages/count_tokens')) {
1209
- // noop
1210
- } else if (pathname.endsWith('/messages')) {
1211
- pathname = `${pathname}/count_tokens`;
1212
- } else if (pathname.endsWith('/v1')) {
1213
- pathname = `${pathname}/messages/count_tokens`;
1214
- } else {
1215
- pathname = `${pathname}/v1/messages/count_tokens`;
1216
- }
1217
-
1218
- targetUrl.pathname = pathname;
1219
- targetUrl.searchParams.set('beta', 'true');
1220
- return targetUrl.toString();
1221
- }
1222
-
1223
- function shouldUseGeminiCliFormat(baseUrl = '') {
1224
- let parsedUrl;
1225
- try {
1226
- parsedUrl = new URL(String(baseUrl || '').trim() || 'https://generativelanguage.googleapis.com');
1227
- } catch {
1228
- return false;
1229
- }
1230
-
1231
- const host = String(parsedUrl.hostname || '').toLowerCase();
1232
- const pathname = parsedUrl.pathname.replace(/\/+$/, '');
1233
-
1234
- if (pathname.includes('/v1internal') || pathname.endsWith(':generateContent') || pathname.endsWith(':streamGenerateContent')) {
1235
- return true;
1236
- }
1237
- if (pathname.includes('/v1beta') || pathname.includes('/models/')) {
1238
- return false;
1239
- }
1240
- if (host.includes('cloudcode-pa.googleapis.com')) {
1241
- return true;
1242
- }
1243
- if (!pathname || pathname === '/') {
1244
- return !host.includes('generativelanguage.googleapis.com') && !host.includes('aiplatform.googleapis.com');
1245
- }
1246
- return false;
1247
- }
1248
-
1249
- function buildGeminiCliTargetPath(parsedUrl, stream = false) {
1250
- let pathname = parsedUrl.pathname.replace(/\/+$/, '');
1251
- const method = stream ? 'streamGenerateContent' : 'generateContent';
1252
-
1253
- if (!pathname || pathname === '/') {
1254
- return `/v1internal:${method}`;
1255
- }
1256
- if (pathname.endsWith(':streamGenerateContent')) {
1257
- return stream
1258
- ? pathname
1259
- : pathname.replace(/:streamGenerateContent$/, ':generateContent');
1260
- }
1261
- if (pathname.endsWith(':generateContent')) {
1262
- return stream
1263
- ? pathname.replace(/:generateContent$/, ':streamGenerateContent')
1264
- : pathname;
1265
- }
1266
- if (pathname.endsWith('/v1internal')) {
1267
- return `${pathname}:${method}`;
1268
- }
1269
- return `${pathname}/v1internal:${method}`;
1270
- }
1271
-
1272
- function buildGeminiCliTargetUrl(baseUrl = '', options = {}) {
1273
- const stream = !!options.stream;
1274
-
1275
- let targetUrl;
1276
- try {
1277
- targetUrl = new URL(String(baseUrl || '').trim() || 'https://cloudcode-pa.googleapis.com');
1278
- } catch {
1279
- targetUrl = new URL('https://cloudcode-pa.googleapis.com');
1280
- }
1281
-
1282
- targetUrl.pathname = buildGeminiCliTargetPath(targetUrl, stream);
1283
- if (stream) {
1284
- targetUrl.searchParams.set('alt', 'sse');
1285
- }
1286
- return targetUrl.toString();
1287
- }
1288
-
1289
- function buildGeminiNativeTargetUrl(baseUrl = '', model = '', apiKey = '', options = {}) {
1290
- const modelName = String(model || '').trim();
1291
- if (!modelName) return '';
1292
- const stream = !!options.stream;
1293
-
1294
- let targetUrl;
1295
- try {
1296
- targetUrl = new URL(String(baseUrl || '').trim() || 'https://generativelanguage.googleapis.com');
1297
- } catch {
1298
- targetUrl = new URL('https://generativelanguage.googleapis.com');
1299
- }
1300
-
1301
- let pathname = targetUrl.pathname.replace(/\/+$/, '');
1302
- const modelsIndex = pathname.indexOf('/models');
1303
- if (modelsIndex >= 0) {
1304
- pathname = pathname.slice(0, modelsIndex);
1305
- }
1306
-
1307
- let apiBasePath;
1308
- if (!pathname || pathname === '/') {
1309
- apiBasePath = '/v1beta';
1310
- } else if (pathname.endsWith('/v1beta') || pathname.endsWith('/v1')) {
1311
- apiBasePath = pathname;
1312
- } else {
1313
- apiBasePath = `${pathname}/v1beta`;
1314
- }
1315
-
1316
- const method = stream ? 'streamGenerateContent' : 'generateContent';
1317
- targetUrl.pathname = `${apiBasePath}/models/${encodeURIComponent(modelName)}:${method}`;
1318
- if (apiKey) {
1319
- targetUrl.searchParams.set('key', apiKey);
1320
- }
1321
- if (stream) {
1322
- targetUrl.searchParams.set('alt', 'sse');
1323
- }
1324
-
1325
- return targetUrl.toString();
1326
- }
1327
-
1328
- function buildGeminiTargetUrl(baseUrl = '', model = '', apiKey = '', options = {}) {
1329
- if (options.useCli) {
1330
- return buildGeminiCliTargetUrl(baseUrl, options);
1331
- }
1332
- return buildGeminiNativeTargetUrl(baseUrl, model, apiKey, options);
1333
- }
1334
-
1335
- function buildCodexTargetUrl(baseUrl = '') {
1336
- let targetUrl;
1337
- try {
1338
- targetUrl = new URL(String(baseUrl || '').trim());
1339
- } catch {
1340
- return '';
1341
- }
1342
-
1343
- let pathname = targetUrl.pathname.replace(/\/+$/, '');
1344
- if (!pathname || pathname === '/') {
1345
- pathname = '/responses';
1346
- } else if (pathname.endsWith('/responses') || pathname.endsWith('/v1/responses')) {
1347
- // noop
1348
- } else if (pathname.endsWith('/v1')) {
1349
- pathname = `${pathname}/responses`;
1350
- } else {
1351
- pathname = `${pathname}/responses`;
1352
- }
1353
-
1354
- targetUrl.pathname = pathname;
1355
- return targetUrl.toString();
1356
- }
1357
393
 
1358
394
  function createDecodedStream(res) {
1359
395
  const encoding = String(res.headers['content-encoding'] || '').toLowerCase();
@@ -1472,9 +508,11 @@ function buildClaudeCountTokensPayload(claudePayload = {}) {
1472
508
 
1473
509
  async function preflightClaudeCountTokens(baseUrl, apiKey, claudePayload, options = {}) {
1474
510
  const countTokensPayload = buildClaudeCountTokensPayload(claudePayload);
1475
- const countTokensHeaders = buildClaudeRequestHeaders(apiKey, {
1476
- requestType: 'count_tokens',
1477
- hasTools: options.hasTools
511
+ const countTokensHeaders = buildClaudeCountTokensHeaders(apiKey, {
512
+ baseUrl,
513
+ hasTools: !!options.hasTools,
514
+ headers: options.headers,
515
+ providerConfig: options.providerConfig
1478
516
  });
1479
517
  try {
1480
518
  await postJson(buildClaudeCountTokensTargetUrl(baseUrl), countTokensHeaders, countTokensPayload, 30000);
@@ -2916,19 +1954,24 @@ async function handleClaudeGatewayRequest(req, res, channel, effectiveKey) {
2916
1954
  : sessionKey;
2917
1955
  const preferredUserId = normalizeSessionKeyValue(originalPayload?.metadata?.user_id);
2918
1956
  const sessionUserId = resolveClaudeUserIdBySession(scopedSessionKey, preferredUserId);
2919
- const claudePayload = convertOpenCodePayloadToClaude(pathname, originalPayload, channel.model, {
2920
- sessionUserId
1957
+ const converted = createClaudeRequest(pathname, originalPayload, {
1958
+ apiKey: effectiveKey,
1959
+ baseUrl: channel.baseUrl,
1960
+ fallbackModel: channel.model,
1961
+ stream: streamResponses,
1962
+ sessionUserId,
1963
+ providerConfig: channel.providerConfig
2921
1964
  });
2922
- claudePayload.stream = streamResponses;
1965
+ const claudePayload = converted.body;
1966
+ const headers = converted.headers;
2923
1967
  const hasTools = Array.isArray(claudePayload.tools) && claudePayload.tools.length > 0;
2924
1968
 
2925
- const headers = buildClaudeRequestHeaders(effectiveKey, {
2926
- requestType: 'messages',
2927
- hasTools
1969
+ await preflightClaudeCountTokens(channel.baseUrl, effectiveKey, claudePayload, {
1970
+ hasTools,
1971
+ headers: channel.providerConfig?.headers,
1972
+ providerConfig: channel.providerConfig
2928
1973
  });
2929
1974
 
2930
- await preflightClaudeCountTokens(channel.baseUrl, effectiveKey, claudePayload, { hasTools });
2931
-
2932
1975
  if (streamResponses) {
2933
1976
  let streamUpstream;
2934
1977
  try {
@@ -3107,7 +2150,12 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
3107
2150
  const startTime = Date.now();
3108
2151
  const originalPayload = (req.body && typeof req.body === 'object') ? req.body : {};
3109
2152
  const wantsStream = !!originalPayload.stream;
3110
- const converted = convertOpenCodePayloadToCodexResponses(originalPayload, channel.model);
2153
+ const sessionId = extractSessionIdFromRequest(req, originalPayload);
2154
+ const converted = createCodexRequest(originalPayload, {
2155
+ apiKey: effectiveKey,
2156
+ fallbackModel: channel.model,
2157
+ sessionId
2158
+ });
3111
2159
  const targetModel = converted.model;
3112
2160
 
3113
2161
  if (!targetModel) {
@@ -3133,29 +2181,9 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
3133
2181
  });
3134
2182
  }
3135
2183
 
3136
- const codexSessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 15)}`;
3137
- const promptCacheKey = (typeof converted.requestBody.prompt_cache_key === 'string' && converted.requestBody.prompt_cache_key.trim())
3138
- ? converted.requestBody.prompt_cache_key.trim()
3139
- : codexSessionId;
3140
- converted.requestBody.prompt_cache_key = promptCacheKey;
3141
-
3142
- const headers = {
3143
- authorization: `Bearer ${effectiveKey}`,
3144
- 'openai-beta': 'responses=experimental',
3145
- accept: 'text/event-stream',
3146
- 'accept-encoding': 'gzip, deflate, br',
3147
- connection: 'Keep-Alive',
3148
- 'content-type': 'application/json',
3149
- Version: CODEX_CLI_VERSION,
3150
- Session_id: promptCacheKey,
3151
- Conversation_id: promptCacheKey,
3152
- Originator: 'codex_cli_rs',
3153
- 'user-agent': CODEX_CLI_USER_AGENT
3154
- };
3155
-
3156
2184
  let streamUpstream;
3157
2185
  try {
3158
- streamUpstream = await postJsonStream(targetUrl, headers, converted.requestBody, 120000);
2186
+ streamUpstream = await postJsonStream(targetUrl, converted.headers, converted.body, 120000);
3159
2187
  } catch (error) {
3160
2188
  return reportOpenCodeGatewayFailure({
3161
2189
  req,
@@ -3169,6 +2197,7 @@ async function handleCodexGatewayRequest(req, res, channel, effectiveKey) {
3169
2197
  });
3170
2198
  }
3171
2199
 
2200
+
3172
2201
  const statusCode = Number(streamUpstream.statusCode) || 500;
3173
2202
  if (statusCode < 200 || statusCode >= 300) {
3174
2203
  let rawBody = '';
@@ -3926,9 +2955,26 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
3926
2955
  const originalPayload = (req.body && typeof req.body === 'object') ? req.body : {};
3927
2956
  const wantsStream = !!originalPayload.stream;
3928
2957
  const streamResponses = wantsStream && isResponsesPath(pathname);
3929
- const converted = convertOpenCodePayloadToGemini(pathname, originalPayload, channel.model);
2958
+ // Resolve useCli by canonical providerApi precedence:
2959
+ // explicit google-gemini-cli → true; google-generative-ai/google-vertex → false;
2960
+ // missing/non-canonical → URL-based detection
2961
+ const explicitApi = String(channel?.providerApi || '').trim().toLowerCase();
2962
+ const useGeminiCli = explicitApi === 'google-gemini-cli'
2963
+ ? true
2964
+ : (explicitApi === 'google-generative-ai' || explicitApi === 'google-vertex'
2965
+ ? false
2966
+ : shouldUseGeminiCliFormat(channel.baseUrl));
2967
+
2968
+ const converted = createGeminiRequest(pathname, originalPayload, {
2969
+ apiKey: effectiveKey,
2970
+ fallbackModel: channel.model,
2971
+ stream: streamResponses,
2972
+ useCli: useGeminiCli,
2973
+ providerConfig: channel.providerConfig
2974
+ });
3930
2975
  const targetModel = converted.model;
3931
- const useGeminiCli = shouldUseGeminiCliFormat(channel.baseUrl);
2976
+ const geminiPayload = converted.body;
2977
+ const headers = converted.headers;
3932
2978
 
3933
2979
  if (!targetModel) {
3934
2980
  return reportOpenCodeGatewayFailure({
@@ -3956,34 +3002,6 @@ async function handleGeminiGatewayRequest(req, res, channel, effectiveKey) {
3956
3002
  });
3957
3003
  }
3958
3004
 
3959
- const geminiPayload = useGeminiCli
3960
- ? {
3961
- project: '',
3962
- model: targetModel,
3963
- request: converted.requestBody
3964
- }
3965
- : converted.requestBody;
3966
-
3967
- const headers = useGeminiCli
3968
- ? {
3969
- 'x-goog-api-key': effectiveKey,
3970
- 'authorization': `Bearer ${effectiveKey}`,
3971
- 'content-type': 'application/json',
3972
- 'accept': streamResponses ? 'text/event-stream' : 'application/json',
3973
- 'accept-encoding': 'gzip, deflate, br',
3974
- 'user-agent': GEMINI_CLI_USER_AGENT,
3975
- 'x-goog-api-client': GEMINI_CLI_API_CLIENT,
3976
- 'client-metadata': GEMINI_CLI_CLIENT_METADATA
3977
- }
3978
- : {
3979
- 'x-goog-api-key': effectiveKey,
3980
- 'authorization': `Bearer ${effectiveKey}`,
3981
- 'content-type': 'application/json',
3982
- 'accept': streamResponses ? 'text/event-stream' : 'application/json',
3983
- 'accept-encoding': 'gzip, deflate, br',
3984
- 'user-agent': 'google-genai-sdk/0.8.0'
3985
- };
3986
-
3987
3005
  if (streamResponses) {
3988
3006
  let streamUpstream;
3989
3007
  try {