yymaxapi 1.0.66 → 1.0.68

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 (2) hide show
  1. package/bin/yymaxapi.js +130 -26
  2. package/package.json +1 -1
package/bin/yymaxapi.js CHANGED
@@ -726,6 +726,10 @@ function writeCodexConfig(baseUrl, apiKey) {
726
726
 
727
727
  function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
728
728
  const home = os.homedir();
729
+ const claudeUrl = claudeBaseUrl.replace(/\/+$/, '');
730
+ const codexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
731
+
732
+ // ---- 1. opencode.json (CLI + 桌面版) ----
729
733
  const configDir = process.platform === 'win32'
730
734
  ? path.join(process.env.APPDATA || path.join(home, 'AppData', 'Roaming'), 'opencode')
731
735
  : path.join(home, '.config', 'opencode');
@@ -736,33 +740,131 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
736
740
  if (fs.existsSync(configPath)) {
737
741
  try { existing = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { existing = {}; }
738
742
  }
739
- const cleanClaudeUrl = claudeBaseUrl.replace(/\/+$/, '');
740
- let cleanCodexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
741
743
  if (!existing.provider) existing.provider = {};
742
- existing.provider.anthropic = {
743
- options: {
744
- apiKey: apiKey,
745
- baseURL: cleanClaudeUrl
746
- }
744
+
745
+ // Claude provider (@ai-sdk/anthropic)
746
+ existing.provider['yunyi-claude'] = {
747
+ name: '云翼 Claude',
748
+ npm: '@ai-sdk/anthropic',
749
+ models: { 'claude-sonnet-4-6': { name: 'Claude Sonnet 4.6' } },
750
+ options: { apiKey, baseURL: `${claudeUrl}/v1` }
747
751
  };
748
- if (cleanCodexUrl) {
749
- existing.provider.openai = {
750
- options: {
751
- apiKey: apiKey,
752
- baseURL: cleanCodexUrl
753
- }
752
+
753
+ // Codex provider (@ai-sdk/openai)
754
+ if (codexUrl) {
755
+ existing.provider['yunyi-codex'] = {
756
+ name: '云翼 Codex',
757
+ npm: '@ai-sdk/openai',
758
+ models: { 'gpt-5.4': { name: 'GPT 5.4' } },
759
+ options: { apiKey, baseURL: codexUrl }
754
760
  };
755
761
  }
762
+
763
+ // 清理旧版写入的通用 provider 名(之前的 bug)
764
+ if (existing.provider.anthropic && existing.provider.anthropic.options
765
+ && existing.provider.anthropic.options.baseURL && existing.provider.anthropic.options.baseURL.includes('yunyi')) {
766
+ delete existing.provider.anthropic;
767
+ }
768
+
769
+ // 设置默认模型
756
770
  const rawModelId = modelId || 'claude-sonnet-4-6';
757
- existing.model = rawModelId.startsWith('anthropic/') || rawModelId.startsWith('openai/')
758
- ? rawModelId
759
- : `anthropic/${rawModelId}`;
771
+ existing.model = `yunyi-claude/${rawModelId}`;
772
+
773
+ // 从 disabled_providers 中移除我们的 provider
774
+ if (Array.isArray(existing.disabled_providers)) {
775
+ existing.disabled_providers = existing.disabled_providers.filter(p => p !== 'yunyi-claude' && p !== 'yunyi-codex');
776
+ if (existing.disabled_providers.length === 0) delete existing.disabled_providers;
777
+ }
778
+
760
779
  if (!existing.$schema) existing.$schema = 'https://opencode.ai/config.json';
761
780
  fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), 'utf8');
762
- return configPath;
763
- } catch {
764
- return null;
765
- }
781
+ } catch { /* 非关键,静默失败 */ }
782
+
783
+ // ---- 2. ~/.codex/config.toml (opencode 也读此格式) ----
784
+ const codexDir = path.join(home, '.codex');
785
+ const codexConfigPath = path.join(codexDir, 'config.toml');
786
+ try {
787
+ if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
788
+
789
+ let content = '';
790
+ if (fs.existsSync(codexConfigPath)) {
791
+ content = fs.readFileSync(codexConfigPath, 'utf8');
792
+ }
793
+
794
+ // 移除旧标记块(yunyi opencode / maxapi codex)
795
+ content = content.replace(/# >>> yunyi opencode >>>[\s\S]*?# <<< yunyi opencode <<<\n?/g, '');
796
+ content = content.replace(/# >>> maxapi codex >>>[\s\S]*?# <<< maxapi codex <<<\n?/g, '');
797
+
798
+ // 移除旧的 yunyi model_providers 段落(包括 yunyi-cli 写入的 [model_providers.yunyi])
799
+ content = content.replace(/\[model_providers\.yunyi[^\]]*\]\n(?:(?!\[)[^\n]*\n?)*/g, '');
800
+
801
+ // 替换或插入顶层设置
802
+ const topSettings = {
803
+ model_provider: '"yunyi-claude"',
804
+ model: '"claude-sonnet-4-6"',
805
+ model_context_window: '1000000',
806
+ model_auto_compact_token_limit: '900000',
807
+ model_reasoning_effort: '"xhigh"',
808
+ disable_response_storage: 'true',
809
+ };
810
+ for (const [key, value] of Object.entries(topSettings)) {
811
+ const re = new RegExp(`^${key}\\s*=\\s*[^\\n]*`, 'm');
812
+ if (re.test(content)) {
813
+ content = content.replace(re, `${key} = ${value}`);
814
+ } else {
815
+ // 插入到第一个 section 之前
816
+ const firstSection = content.search(/^\[/m);
817
+ if (firstSection > 0) {
818
+ content = content.slice(0, firstSection) + `${key} = ${value}\n` + content.slice(firstSection);
819
+ } else if (firstSection === 0) {
820
+ content = `${key} = ${value}\n` + content;
821
+ } else {
822
+ content += `${key} = ${value}\n`;
823
+ }
824
+ }
825
+ }
826
+
827
+ // 清理多余空行
828
+ content = content.replace(/\n{3,}/g, '\n\n').trim();
829
+
830
+ // 追加 model_providers 段落
831
+ const providers = [
832
+ '',
833
+ '# >>> yunyi opencode >>>',
834
+ '[model_providers.yunyi-claude]',
835
+ 'name = "云翼 Claude"',
836
+ `base_url = "${claudeUrl}"`,
837
+ 'wire_api = "anthropic"',
838
+ `experimental_bearer_token = "${apiKey}"`,
839
+ ];
840
+ if (codexUrl) {
841
+ providers.push(
842
+ '',
843
+ '[model_providers.yunyi-codex]',
844
+ 'name = "云翼 Codex"',
845
+ `base_url = "${codexUrl}"`,
846
+ 'wire_api = "responses"',
847
+ `experimental_bearer_token = "${apiKey}"`,
848
+ 'requires_openai_auth = true',
849
+ );
850
+ }
851
+ providers.push('# <<< yunyi opencode <<<');
852
+ content += '\n\n' + providers.join('\n') + '\n';
853
+
854
+ fs.writeFileSync(codexConfigPath, content, 'utf8');
855
+
856
+ // ---- 3. ~/.codex/auth.json ----
857
+ const authPath = path.join(codexDir, 'auth.json');
858
+ let auth = {};
859
+ if (fs.existsSync(authPath)) {
860
+ try { auth = JSON.parse(fs.readFileSync(authPath, 'utf8')); } catch { auth = {}; }
861
+ }
862
+ auth.ANTHROPIC_API_KEY = apiKey;
863
+ auth.OPENAI_API_KEY = apiKey;
864
+ fs.writeFileSync(authPath, JSON.stringify(auth, null, 2), 'utf8');
865
+ } catch { /* 非关键,静默失败 */ }
866
+
867
+ return configPath;
766
868
  }
767
869
 
768
870
  function syncExternalTools(type, baseUrl, apiKey, extra = {}) {
@@ -2964,13 +3066,15 @@ async function activateOpencode(paths, args = {}) {
2964
3066
  writeSpinner.succeed('Opencode 配置写入完成');
2965
3067
 
2966
3068
  console.log(chalk.green('\n✅ Opencode 配置完成!'));
2967
- console.log(chalk.cyan(` Claude: ${claudeBaseUrl}`));
2968
- console.log(chalk.cyan(` Codex: ${codexBaseUrl}`));
2969
- console.log(chalk.gray(` 模型: ${modelId}`));
3069
+ console.log(chalk.cyan(` Claude: ${claudeBaseUrl} → claude-sonnet-4-6`));
3070
+ console.log(chalk.cyan(` Codex: ${codexBaseUrl} → gpt-5.4`));
3071
+ console.log(chalk.gray(` 默认模型: yunyi-claude/${modelId}`));
2970
3072
  console.log(chalk.gray(' API Key: 已设置'));
2971
- if (configPath) {
2972
- console.log(chalk.gray(` 配置文件: ${configPath}`));
2973
- }
3073
+ console.log(chalk.gray('\n 已写入:'));
3074
+ console.log(chalk.gray(' ~/.config/opencode/opencode.json (CLI + 桌面版)'));
3075
+ console.log(chalk.gray(' • ~/.codex/config.toml (model_providers)'));
3076
+ console.log(chalk.gray(' • ~/.codex/auth.json (API Keys)'));
3077
+ console.log(chalk.yellow('\n 切换模型: 在 opencode 内使用 /model 命令切换 yunyi-claude / yunyi-codex'));
2974
3078
  }
2975
3079
 
2976
3080
  // ============ yycode 精简模式(零交互一键配置) ============
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yymaxapi",
3
- "version": "1.0.66",
3
+ "version": "1.0.68",
4
4
  "description": "跨平台 OpenClaw/Clawdbot 配置管理工具 - 管理中转地址、模型切换、API Keys、测速优化",
5
5
  "main": "bin/yymaxapi.js",
6
6
  "bin": {