yymaxapi 1.0.67 → 1.0.69
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/bin/yymaxapi.js
CHANGED
|
@@ -676,7 +676,7 @@ function writeClaudeCodeSettings(baseUrl, apiKey) {
|
|
|
676
676
|
}
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
-
function writeCodexConfig(baseUrl, apiKey) {
|
|
679
|
+
function writeCodexConfig(baseUrl, apiKey, modelId = 'gpt-5.4') {
|
|
680
680
|
const codexDir = path.join(os.homedir(), '.codex');
|
|
681
681
|
if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
|
|
682
682
|
|
|
@@ -698,7 +698,7 @@ function writeCodexConfig(baseUrl, apiKey) {
|
|
|
698
698
|
}
|
|
699
699
|
const section = [
|
|
700
700
|
marker,
|
|
701
|
-
`model = "
|
|
701
|
+
`model = "${modelId}"`,
|
|
702
702
|
`model_provider = "${providerKey}"`,
|
|
703
703
|
``,
|
|
704
704
|
`[model_providers.${providerKey}]`,
|
|
@@ -726,6 +726,8 @@ 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(/\/+$/, '');
|
|
729
731
|
|
|
730
732
|
// ---- 1. opencode.json (CLI + 桌面版) ----
|
|
731
733
|
const configDir = process.platform === 'win32'
|
|
@@ -738,21 +740,42 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
|
|
|
738
740
|
if (fs.existsSync(configPath)) {
|
|
739
741
|
try { existing = JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { existing = {}; }
|
|
740
742
|
}
|
|
741
|
-
const cleanClaudeUrl = claudeBaseUrl.replace(/\/+$/, '');
|
|
742
|
-
const cleanCodexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
|
|
743
743
|
if (!existing.provider) existing.provider = {};
|
|
744
|
-
|
|
745
|
-
|
|
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` }
|
|
746
751
|
};
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
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 }
|
|
750
760
|
};
|
|
751
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
|
+
// 设置默认模型
|
|
752
770
|
const rawModelId = modelId || 'claude-sonnet-4-6';
|
|
753
|
-
existing.model = rawModelId
|
|
754
|
-
|
|
755
|
-
|
|
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
|
+
|
|
756
779
|
if (!existing.$schema) existing.$schema = 'https://opencode.ai/config.json';
|
|
757
780
|
fs.writeFileSync(configPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
758
781
|
} catch { /* 非关键,静默失败 */ }
|
|
@@ -763,49 +786,74 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, modelId) {
|
|
|
763
786
|
try {
|
|
764
787
|
if (!fs.existsSync(codexDir)) fs.mkdirSync(codexDir, { recursive: true });
|
|
765
788
|
|
|
766
|
-
|
|
767
|
-
const codexUrl = (codexBaseUrl || '').replace(/\/+$/, '');
|
|
768
|
-
|
|
769
|
-
const marker = '# >>> yunyi opencode >>>';
|
|
770
|
-
const markerEnd = '# <<< yunyi opencode <<<';
|
|
771
|
-
let existing = '';
|
|
789
|
+
let content = '';
|
|
772
790
|
if (fs.existsSync(codexConfigPath)) {
|
|
773
|
-
|
|
774
|
-
const re = new RegExp(`${marker.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}[\\s\\S]*?${markerEnd.replace(/[.*+?^${}()|[\\]\\]/g, '\\$&')}\\n?`, 'g');
|
|
775
|
-
existing = existing.replace(re, '').trim();
|
|
791
|
+
content = fs.readFileSync(codexConfigPath, 'utf8');
|
|
776
792
|
}
|
|
777
793
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
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 = [
|
|
785
832
|
'',
|
|
786
|
-
|
|
787
|
-
|
|
833
|
+
'# >>> yunyi opencode >>>',
|
|
834
|
+
'[model_providers.yunyi-claude]',
|
|
835
|
+
'name = "云翼 Claude"',
|
|
788
836
|
`base_url = "${claudeUrl}"`,
|
|
789
|
-
|
|
790
|
-
`
|
|
837
|
+
'wire_api = "anthropic"',
|
|
838
|
+
`experimental_bearer_token = "${apiKey}"`,
|
|
791
839
|
];
|
|
792
|
-
|
|
793
840
|
if (codexUrl) {
|
|
794
|
-
|
|
841
|
+
providers.push(
|
|
795
842
|
'',
|
|
796
|
-
|
|
797
|
-
|
|
843
|
+
'[model_providers.yunyi-codex]',
|
|
844
|
+
'name = "云翼 Codex"',
|
|
798
845
|
`base_url = "${codexUrl}"`,
|
|
799
|
-
|
|
800
|
-
`
|
|
801
|
-
|
|
846
|
+
'wire_api = "responses"',
|
|
847
|
+
`experimental_bearer_token = "${apiKey}"`,
|
|
848
|
+
'requires_openai_auth = true',
|
|
802
849
|
);
|
|
803
850
|
}
|
|
851
|
+
providers.push('# <<< yunyi opencode <<<');
|
|
852
|
+
content += '\n\n' + providers.join('\n') + '\n';
|
|
804
853
|
|
|
805
|
-
|
|
806
|
-
fs.writeFileSync(codexConfigPath, existing ? `${existing}\n\n${section.join('\n')}\n` : `${section.join('\n')}\n`, 'utf8');
|
|
854
|
+
fs.writeFileSync(codexConfigPath, content, 'utf8');
|
|
807
855
|
|
|
808
|
-
// ~/.codex/auth.json
|
|
856
|
+
// ---- 3. ~/.codex/auth.json ----
|
|
809
857
|
const authPath = path.join(codexDir, 'auth.json');
|
|
810
858
|
let auth = {};
|
|
811
859
|
if (fs.existsSync(authPath)) {
|
|
@@ -3018,15 +3066,104 @@ async function activateOpencode(paths, args = {}) {
|
|
|
3018
3066
|
writeSpinner.succeed('Opencode 配置写入完成');
|
|
3019
3067
|
|
|
3020
3068
|
console.log(chalk.green('\n✅ Opencode 配置完成!'));
|
|
3021
|
-
console.log(chalk.cyan(` Claude: ${claudeBaseUrl}`));
|
|
3022
|
-
console.log(chalk.cyan(` Codex: ${codexBaseUrl}`));
|
|
3023
|
-
console.log(chalk.gray(` 默认模型:
|
|
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}`));
|
|
3024
3072
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
3025
3073
|
console.log(chalk.gray('\n 已写入:'));
|
|
3026
3074
|
console.log(chalk.gray(' • ~/.config/opencode/opencode.json (CLI + 桌面版)'));
|
|
3027
3075
|
console.log(chalk.gray(' • ~/.codex/config.toml (model_providers)'));
|
|
3028
3076
|
console.log(chalk.gray(' • ~/.codex/auth.json (API Keys)'));
|
|
3029
|
-
console.log(chalk.yellow('\n 切换模型: 在 opencode 内使用 /model 命令切换 claude/
|
|
3077
|
+
console.log(chalk.yellow('\n 切换模型: 在 opencode 内使用 /model 命令切换 yunyi-claude / yunyi-codex'));
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
// ============ 单独配置 Codex CLI ============
|
|
3081
|
+
async function activateCodex(paths, args = {}) {
|
|
3082
|
+
console.log(chalk.cyan.bold('\n🔧 配置 Codex CLI\n'));
|
|
3083
|
+
|
|
3084
|
+
// ---- 选模型 ----
|
|
3085
|
+
let modelId = CODEX_MODELS[0]?.id || 'gpt-5.4';
|
|
3086
|
+
if (CODEX_MODELS.length > 1) {
|
|
3087
|
+
const { selected } = await inquirer.prompt([{
|
|
3088
|
+
type: 'list',
|
|
3089
|
+
name: 'selected',
|
|
3090
|
+
message: '选择模型:',
|
|
3091
|
+
choices: CODEX_MODELS.map(m => ({ name: m.name, value: m.id }))
|
|
3092
|
+
}]);
|
|
3093
|
+
modelId = selected;
|
|
3094
|
+
}
|
|
3095
|
+
const modelConfig = CODEX_MODELS.find(m => m.id === modelId) || { id: modelId, name: modelId };
|
|
3096
|
+
|
|
3097
|
+
// ---- 测速选节点 ----
|
|
3098
|
+
const shouldTest = !(args['no-test'] || args.noTest);
|
|
3099
|
+
let selectedEndpoint = ENDPOINTS[0];
|
|
3100
|
+
|
|
3101
|
+
if (shouldTest) {
|
|
3102
|
+
console.log(chalk.cyan('📡 开始测速节点...\n'));
|
|
3103
|
+
const speedResult = await testAllEndpoints(ENDPOINTS, { autoFallback: true });
|
|
3104
|
+
const sorted = speedResult.ranked || [];
|
|
3105
|
+
if (sorted.length > 0) {
|
|
3106
|
+
const defaultEp = ENDPOINTS[0];
|
|
3107
|
+
const { selectedIndex } = await inquirer.prompt([{
|
|
3108
|
+
type: 'list',
|
|
3109
|
+
name: 'selectedIndex',
|
|
3110
|
+
message: '选择节点:',
|
|
3111
|
+
choices: [
|
|
3112
|
+
{ name: `* 使用默认节点 (${defaultEp.name})`, value: -1 },
|
|
3113
|
+
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
3114
|
+
...sorted.map((e, i) => ({
|
|
3115
|
+
name: `${e.name} - ${e.latency}ms (评分:${e.score})`,
|
|
3116
|
+
value: i
|
|
3117
|
+
}))
|
|
3118
|
+
]
|
|
3119
|
+
}]);
|
|
3120
|
+
selectedEndpoint = selectedIndex === -1 ? defaultEp : sorted[selectedIndex];
|
|
3121
|
+
} else {
|
|
3122
|
+
console.log(chalk.red('\n⚠️ 所有节点均不可达'));
|
|
3123
|
+
const { proceed } = await inquirer.prompt([{
|
|
3124
|
+
type: 'confirm', name: 'proceed',
|
|
3125
|
+
message: '仍要使用默认节点配置吗?', default: false
|
|
3126
|
+
}]);
|
|
3127
|
+
if (!proceed) { console.log(chalk.gray('已取消')); return; }
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
|
|
3131
|
+
// ---- API Key ----
|
|
3132
|
+
const directKey = (args['api-key'] || args.apiKey || args.key || '').toString().trim();
|
|
3133
|
+
let apiKey;
|
|
3134
|
+
if (directKey) {
|
|
3135
|
+
apiKey = directKey;
|
|
3136
|
+
} else {
|
|
3137
|
+
const envKey = process.env.OPENAI_API_KEY || '';
|
|
3138
|
+
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
3139
|
+
}
|
|
3140
|
+
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
3141
|
+
|
|
3142
|
+
// ---- 验证 ----
|
|
3143
|
+
console.log('');
|
|
3144
|
+
const validation = await validateApiKey(selectedEndpoint.url, apiKey);
|
|
3145
|
+
if (!validation.valid) {
|
|
3146
|
+
const { continueAnyway } = await inquirer.prompt([{
|
|
3147
|
+
type: 'confirm', name: 'continueAnyway',
|
|
3148
|
+
message: 'API Key 验证失败,是否仍然继续写入配置?', default: false
|
|
3149
|
+
}]);
|
|
3150
|
+
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
3151
|
+
}
|
|
3152
|
+
|
|
3153
|
+
// ---- 写入配置 ----
|
|
3154
|
+
const codexBaseUrl = buildFullUrl(selectedEndpoint.url, 'codex');
|
|
3155
|
+
const writeSpinner = ora({ text: '正在写入 Codex 配置...', spinner: 'dots' }).start();
|
|
3156
|
+
writeCodexConfig(codexBaseUrl, apiKey, modelId);
|
|
3157
|
+
writeSpinner.succeed('Codex 配置写入完成');
|
|
3158
|
+
|
|
3159
|
+
console.log(chalk.green('\n✅ Codex CLI 配置完成!'));
|
|
3160
|
+
console.log(chalk.cyan(` Base URL: ${codexBaseUrl}`));
|
|
3161
|
+
console.log(chalk.gray(` 模型: ${modelConfig.name} (${modelId})`));
|
|
3162
|
+
console.log(chalk.gray(' API Key: 已设置'));
|
|
3163
|
+
console.log(chalk.gray('\n 已写入:'));
|
|
3164
|
+
console.log(chalk.gray(' • ~/.codex/config.toml (model + model_providers)'));
|
|
3165
|
+
console.log(chalk.gray(' • ~/.codex/auth.json (OPENAI_API_KEY)'));
|
|
3166
|
+
console.log(chalk.yellow('\n 提示: 请重新打开终端使配置生效'));
|
|
3030
3167
|
}
|
|
3031
3168
|
|
|
3032
3169
|
// ============ yycode 精简模式(零交互一键配置) ============
|
|
@@ -3248,6 +3385,7 @@ async function main() {
|
|
|
3248
3385
|
{ name: ' 配置 OpenClaw(Claude + Codex)', value: 'auto_activate' },
|
|
3249
3386
|
{ name: ' 配置 Claude Code', value: 'activate_claude_code' },
|
|
3250
3387
|
{ name: ' 配置 Opencode', value: 'activate_opencode' },
|
|
3388
|
+
{ name: ' 配置 Codex CLI', value: 'activate_codex' },
|
|
3251
3389
|
new inquirer.Separator(' -- 工具 --'),
|
|
3252
3390
|
{ name: ' 切换模型', value: 'switch_model' },
|
|
3253
3391
|
{ name: ` 权限管理${getToolsProfileTag(paths)}`, value: 'tools_profile' },
|
|
@@ -3292,6 +3430,9 @@ async function main() {
|
|
|
3292
3430
|
case 'activate_opencode':
|
|
3293
3431
|
await activateOpencode(paths);
|
|
3294
3432
|
break;
|
|
3433
|
+
case 'activate_codex':
|
|
3434
|
+
await activateCodex(paths);
|
|
3435
|
+
break;
|
|
3295
3436
|
}
|
|
3296
3437
|
} catch (error) {
|
|
3297
3438
|
console.log(chalk.red(`\n错误: ${error.message}\n`));
|
|
@@ -107,3 +107,62 @@ npx yymaxapi@latest
|
|
|
107
107
|
- GPT 可用模型:`gpt-5.4`
|
|
108
108
|
- 已验证环境:腾讯云 OpenCloudOS,OpenClaw `2026.2.3-1`
|
|
109
109
|
- 参考文档:https://cloud.tencent.com/developer/article/2624003
|
|
110
|
+
|
|
111
|
+
### 腾讯云 + Claude Code CLI 一起写入(同一 Key)
|
|
112
|
+
|
|
113
|
+
云翼一张卡可同时用于 Claude 与 GPT。按下面两步做即可同时生效。
|
|
114
|
+
|
|
115
|
+
**1. 腾讯云 OpenClaw 自定义模型(添加两条)**
|
|
116
|
+
|
|
117
|
+
在腾讯云 OpenClaw 的「自定义模型」里添加**两条**,`api_key` 用同一云翼 Key 即可。
|
|
118
|
+
|
|
119
|
+
第一条 — Claude Sonnet 4.6:
|
|
120
|
+
|
|
121
|
+
```json
|
|
122
|
+
{
|
|
123
|
+
"provider": "anthropic",
|
|
124
|
+
"base_url": "https://yunyi.rdzhvip.com/claude",
|
|
125
|
+
"api": "anthropic-messages",
|
|
126
|
+
"api_key": "<你的云翼 API Key>",
|
|
127
|
+
"model": {
|
|
128
|
+
"id": "claude-sonnet-4-6",
|
|
129
|
+
"name": "Claude Sonnet 4.6"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
第二条 — GPT 5.4:
|
|
135
|
+
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"provider": "openai",
|
|
139
|
+
"base_url": "https://yunyi.rdzhvip.com/codex",
|
|
140
|
+
"api": "openai-responses",
|
|
141
|
+
"api_key": "<你的云翼 API Key>",
|
|
142
|
+
"model": {
|
|
143
|
+
"id": "gpt-5.4",
|
|
144
|
+
"name": "GPT 5.4"
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**2. Claude Code CLI(本机终端)**
|
|
150
|
+
|
|
151
|
+
在终端执行一次,或写入 `~/.zshrc` / `~/.bashrc` 后 `source`,Claude Code 即走云翼 Claude:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
export ANTHROPIC_BASE_URL="https://yunyi.rdzhvip.com/claude"
|
|
155
|
+
export ANTHROPIC_AUTH_TOKEN="<你的云翼 API Key>"
|
|
156
|
+
# 若遇证书报错可加:
|
|
157
|
+
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Windows(PowerShell,用户级环境变量):
|
|
161
|
+
|
|
162
|
+
```powershell
|
|
163
|
+
[Environment]::SetEnvironmentVariable('ANTHROPIC_BASE_URL', 'https://yunyi.rdzhvip.com/claude', 'User')
|
|
164
|
+
[Environment]::SetEnvironmentVariable('ANTHROPIC_AUTH_TOKEN', '<你的云翼 API Key>', 'User')
|
|
165
|
+
[Environment]::SetEnvironmentVariable('NODE_TLS_REJECT_UNAUTHORIZED', '0', 'User')
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
完成后:腾讯云 OpenClaw 里可选 Claude 或 GPT;本机 Claude Code CLI 使用同一 Key 走云翼 Claude。
|