yymaxapi 1.0.112 → 1.0.116
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/README.md
CHANGED
package/bin/yymaxapi.js
CHANGED
|
@@ -73,19 +73,11 @@ if (!process.__yymaxapiWarningFilterInstalled && typeof process.emitWarning ===
|
|
|
73
73
|
// ============ 预设 (由 build.js 从 provider config 生成) ============
|
|
74
74
|
const DEFAULT_ENDPOINTS = [
|
|
75
75
|
{
|
|
76
|
-
"name": "
|
|
77
|
-
"url": "https://yunyi.rdzhvip.com"
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
"name": "CF国外节点1",
|
|
76
|
+
"name": "默认主节点",
|
|
81
77
|
"url": "https://yunyi.cfd"
|
|
82
78
|
},
|
|
83
79
|
{
|
|
84
|
-
"name": "CF国外节点
|
|
85
|
-
"url": "https://cdn1.yunyi.cfd"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"name": "CF国外节点3",
|
|
80
|
+
"name": "CF国外节点1",
|
|
89
81
|
"url": "https://cdn2.yunyi.cfd"
|
|
90
82
|
}
|
|
91
83
|
];
|
|
@@ -616,9 +608,73 @@ function shouldRunEndpointSpeedTest(args = {}) {
|
|
|
616
608
|
return !!raw;
|
|
617
609
|
}
|
|
618
610
|
|
|
611
|
+
function getEndpointOverrideInput(args = {}) {
|
|
612
|
+
const raw = args['endpoint-url']
|
|
613
|
+
?? args.endpointUrl
|
|
614
|
+
?? args.endpoint
|
|
615
|
+
?? args.domain
|
|
616
|
+
?? args['base-url']
|
|
617
|
+
?? args.baseUrl
|
|
618
|
+
?? '';
|
|
619
|
+
return String(raw || '').trim();
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function normalizeEndpointBaseUrl(baseUrl) {
|
|
623
|
+
let trimmed = String(baseUrl || '').trim();
|
|
624
|
+
if (!trimmed) return '';
|
|
625
|
+
|
|
626
|
+
trimmed = trimClaudeMessagesSuffix(trimmed);
|
|
627
|
+
trimmed = trimOpenAiEndpointSuffix(trimmed);
|
|
628
|
+
trimmed = stripManagedYunyiSuffix(trimmed);
|
|
629
|
+
|
|
630
|
+
try {
|
|
631
|
+
const urlObj = new URL(trimmed);
|
|
632
|
+
urlObj.search = '';
|
|
633
|
+
urlObj.hash = '';
|
|
634
|
+
const normalizedPath = urlObj.pathname.replace(/\/+$/, '');
|
|
635
|
+
return `${urlObj.origin}${normalizedPath && normalizedPath !== '/' ? normalizedPath : ''}`;
|
|
636
|
+
} catch {
|
|
637
|
+
return trimmed.replace(/\/+$/, '');
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
function resolveEndpointOverride(args = {}) {
|
|
642
|
+
const raw = getEndpointOverrideInput(args);
|
|
643
|
+
if (!raw) return null;
|
|
644
|
+
|
|
645
|
+
const normalized = normalizeEndpointBaseUrl(raw);
|
|
646
|
+
if (!normalized || !isValidUrl(normalized)) {
|
|
647
|
+
throw new Error('自定义节点 URL 无效,请使用 http(s):// 域名,支持填写根域名、/claude 或 /codex 地址');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return { name: '自定义节点', url: normalized };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function promptCustomEndpointSelection(args = {}, options = {}) {
|
|
654
|
+
const defaultInput = getEndpointOverrideInput(args);
|
|
655
|
+
const promptMessage = options.customEndpointMessage || '请输入节点域名或 Base URL(支持根域名、/claude、/codex):';
|
|
656
|
+
const { customEndpointInput } = await inquirer.prompt([{
|
|
657
|
+
type: 'input',
|
|
658
|
+
name: 'customEndpointInput',
|
|
659
|
+
message: promptMessage,
|
|
660
|
+
default: defaultInput,
|
|
661
|
+
validate: input => {
|
|
662
|
+
const normalized = normalizeEndpointBaseUrl(input);
|
|
663
|
+
return Boolean(normalized && isValidUrl(normalized)) || '请输入有效的 URL(http:// 或 https://)';
|
|
664
|
+
}
|
|
665
|
+
}]);
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
name: options.customEndpointName || '自定义节点',
|
|
669
|
+
url: normalizeEndpointBaseUrl(customEndpointInput)
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
|
|
619
673
|
async function resolveEndpointSelection(args = {}, options = {}) {
|
|
620
674
|
const defaultEndpoint = options.defaultEndpoint || ENDPOINTS[0];
|
|
621
675
|
if (!defaultEndpoint) return null;
|
|
676
|
+
const directOverride = resolveEndpointOverride(args);
|
|
677
|
+
if (directOverride) return directOverride;
|
|
622
678
|
if (!shouldRunEndpointSpeedTest(args)) return defaultEndpoint;
|
|
623
679
|
|
|
624
680
|
const speedIntro = options.speedIntro || '📡 开始测速节点...\n';
|
|
@@ -636,6 +692,7 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
636
692
|
message: chooseMessage,
|
|
637
693
|
choices: [
|
|
638
694
|
{ name: `* 使用默认节点 (${defaultEndpoint.name})`, value: -1 },
|
|
695
|
+
{ name: '* 手动输入节点域名', value: '__custom__' },
|
|
639
696
|
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
640
697
|
...sorted.map((endpoint, index) => ({
|
|
641
698
|
name: `${endpoint.name} - ${endpoint.latency}ms (评分:${endpoint.score})`,
|
|
@@ -644,7 +701,9 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
644
701
|
]
|
|
645
702
|
}]);
|
|
646
703
|
|
|
647
|
-
const selectedEndpoint = selectedIndex ===
|
|
704
|
+
const selectedEndpoint = selectedIndex === '__custom__'
|
|
705
|
+
? await promptCustomEndpointSelection(args, options)
|
|
706
|
+
: (selectedIndex === -1 ? defaultEndpoint : sorted[selectedIndex]);
|
|
648
707
|
if (speedResult.usedFallback) {
|
|
649
708
|
console.log(chalk.yellow('\n⚠ 当前使用备用节点\n'));
|
|
650
709
|
}
|
|
@@ -652,13 +711,21 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
652
711
|
}
|
|
653
712
|
|
|
654
713
|
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
714
|
+
const proceedChoiceLabel = options.proceedChoiceLabel || `${proceedMessage.replace(/[??]\s*$/, '')} (${defaultEndpoint.name})`;
|
|
715
|
+
const { nextAction } = await inquirer.prompt([{
|
|
716
|
+
type: 'list',
|
|
717
|
+
name: 'nextAction',
|
|
718
|
+
message: '测速未找到可用节点,接下来怎么做?',
|
|
719
|
+
choices: [
|
|
720
|
+
{ name: proceedChoiceLabel, value: 'default' },
|
|
721
|
+
{ name: '手动输入节点域名', value: 'custom' },
|
|
722
|
+
{ name: '取消', value: 'cancel' }
|
|
723
|
+
]
|
|
660
724
|
}]);
|
|
661
|
-
if (
|
|
725
|
+
if (nextAction === 'custom') {
|
|
726
|
+
return promptCustomEndpointSelection(args, options);
|
|
727
|
+
}
|
|
728
|
+
if (nextAction !== 'default') {
|
|
662
729
|
console.log(chalk.gray('已取消'));
|
|
663
730
|
return null;
|
|
664
731
|
}
|
|
@@ -2671,17 +2738,17 @@ function hasManagedYunyiFootprint(config) {
|
|
|
2671
2738
|
}
|
|
2672
2739
|
|
|
2673
2740
|
function inferManagedYunyiEndpointUrl(config, explicitEndpointUrl = '') {
|
|
2674
|
-
const direct =
|
|
2675
|
-
if (direct &&
|
|
2741
|
+
const direct = normalizeEndpointBaseUrl(explicitEndpointUrl);
|
|
2742
|
+
if (direct && isValidUrl(direct)) return direct;
|
|
2676
2743
|
|
|
2677
2744
|
const providers = config?.models?.providers || {};
|
|
2678
2745
|
for (const [name, provider] of Object.entries(providers)) {
|
|
2679
2746
|
if (!isYunyiProviderEntry(name, provider, 'claude') && !isYunyiProviderEntry(name, provider, 'codex')) continue;
|
|
2680
|
-
const candidate =
|
|
2681
|
-
if (candidate &&
|
|
2747
|
+
const candidate = normalizeEndpointBaseUrl(provider?.baseUrl || provider?.base_url || '');
|
|
2748
|
+
if (candidate && isValidUrl(candidate)) return candidate;
|
|
2682
2749
|
}
|
|
2683
2750
|
|
|
2684
|
-
return ENDPOINTS.find(item =>
|
|
2751
|
+
return ENDPOINTS.find(item => isValidUrl(item.url))?.url || ENDPOINTS[0]?.url || '';
|
|
2685
2752
|
}
|
|
2686
2753
|
|
|
2687
2754
|
function inferManagedYunyiApiKey(config, explicitApiKey = '') {
|
|
@@ -3450,6 +3517,10 @@ function shellQuote(value) {
|
|
|
3450
3517
|
return `"${str.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
3451
3518
|
}
|
|
3452
3519
|
|
|
3520
|
+
function getShellNullInputRedirect() {
|
|
3521
|
+
return process.platform === 'win32' ? ' < NUL' : ' < /dev/null';
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3453
3524
|
function escapeSingleQuotedShell(value) {
|
|
3454
3525
|
return String(value || '').replace(/'/g, "'\\''");
|
|
3455
3526
|
}
|
|
@@ -4021,6 +4092,41 @@ function buildReadableAgentError(text, fallback = '') {
|
|
|
4021
4092
|
return (preferred || lines[0] || fallback).slice(0, 300);
|
|
4022
4093
|
}
|
|
4023
4094
|
|
|
4095
|
+
function scoreCliErrorDetail(text) {
|
|
4096
|
+
const lower = String(text || '').toLowerCase();
|
|
4097
|
+
if (!lower) return -1;
|
|
4098
|
+
let score = 0;
|
|
4099
|
+
if ([
|
|
4100
|
+
'forbidden',
|
|
4101
|
+
'unauthorized',
|
|
4102
|
+
'permission to access',
|
|
4103
|
+
'authentication_error',
|
|
4104
|
+
'invalid bearer token',
|
|
4105
|
+
'invalid api key',
|
|
4106
|
+
'all models failed',
|
|
4107
|
+
'auth issue',
|
|
4108
|
+
'auth_permanent',
|
|
4109
|
+
'unexpected status',
|
|
4110
|
+
'gateway agent failed'
|
|
4111
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4112
|
+
score += 5;
|
|
4113
|
+
}
|
|
4114
|
+
if ([
|
|
4115
|
+
'failed',
|
|
4116
|
+
'error:',
|
|
4117
|
+
'missing environment variable'
|
|
4118
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4119
|
+
score += 2;
|
|
4120
|
+
}
|
|
4121
|
+
if ([
|
|
4122
|
+
'reading additional input from stdin',
|
|
4123
|
+
'reconnecting'
|
|
4124
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4125
|
+
score -= 2;
|
|
4126
|
+
}
|
|
4127
|
+
return score;
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4024
4130
|
function looksLikeCliTestError(text) {
|
|
4025
4131
|
const lower = cleanCliTestOutput(text).toLowerCase();
|
|
4026
4132
|
if (!lower) return false;
|
|
@@ -4043,13 +4149,19 @@ function looksLikeCliTestError(text) {
|
|
|
4043
4149
|
|
|
4044
4150
|
function runCliTestCandidates(name, commands, env) {
|
|
4045
4151
|
let lastError = '命令执行失败';
|
|
4152
|
+
let lastErrorScore = scoreCliErrorDetail(lastError);
|
|
4046
4153
|
for (const command of commands) {
|
|
4047
4154
|
const result = safeExec(command, { timeout: 120000, env, maxBuffer: 1024 * 1024 });
|
|
4048
4155
|
const combined = cleanCliTestOutput(`${result.output || ''}\n${result.stdout || ''}\n${result.stderr || ''}`);
|
|
4049
4156
|
if (result.ok && !looksLikeCliTestError(combined)) {
|
|
4050
4157
|
return { name, status: 'success', detail: summarizeCliTestOutput(combined) || '连接成功' };
|
|
4051
4158
|
}
|
|
4052
|
-
|
|
4159
|
+
const candidateError = buildReadableAgentError(combined, result.error || lastError);
|
|
4160
|
+
const candidateScore = scoreCliErrorDetail(candidateError);
|
|
4161
|
+
if (candidateScore >= lastErrorScore) {
|
|
4162
|
+
lastError = candidateError;
|
|
4163
|
+
lastErrorScore = candidateScore;
|
|
4164
|
+
}
|
|
4053
4165
|
}
|
|
4054
4166
|
return { name, status: 'failed', detail: lastError };
|
|
4055
4167
|
}
|
|
@@ -4119,10 +4231,11 @@ function testCodexCliConnection() {
|
|
|
4119
4231
|
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
4120
4232
|
NODE_NO_WARNINGS: '1'
|
|
4121
4233
|
};
|
|
4234
|
+
const nullRedirect = getShellNullInputRedirect();
|
|
4122
4235
|
|
|
4123
4236
|
return runCliTestCandidates('Codex CLI', [
|
|
4124
|
-
`${shellQuote(cliBinary)} exec --skip-git-repo-check ${shellQuote('请只回复 OK')}`,
|
|
4125
|
-
`${shellQuote(cliBinary)} exec ${shellQuote('请只回复 OK')}`
|
|
4237
|
+
`${shellQuote(cliBinary)} exec --skip-git-repo-check ${shellQuote('请只回复 OK')}${nullRedirect}`,
|
|
4238
|
+
`${shellQuote(cliBinary)} exec ${shellQuote('请只回复 OK')}${nullRedirect}`
|
|
4126
4239
|
], env);
|
|
4127
4240
|
}
|
|
4128
4241
|
|
|
@@ -5178,7 +5291,11 @@ async function autoActivate(paths, args = {}) {
|
|
|
5178
5291
|
const codexApiConfig = API_CONFIG.codex;
|
|
5179
5292
|
const claudeProviderName = claudeApiConfig.providerName;
|
|
5180
5293
|
const codexProviderName = codexApiConfig.providerName;
|
|
5181
|
-
const selectedEndpoint =
|
|
5294
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5295
|
+
speedIntro: '📡 开始测速云翼节点...\n',
|
|
5296
|
+
proceedMessage: '仍要写入默认节点配置吗?'
|
|
5297
|
+
});
|
|
5298
|
+
if (!selectedEndpoint) return;
|
|
5182
5299
|
|
|
5183
5300
|
// ---- API Key ----
|
|
5184
5301
|
const apiKeyEnvFallbacks = [
|
|
@@ -13,10 +13,8 @@
|
|
|
13
13
|
```json5
|
|
14
14
|
{
|
|
15
15
|
"endpoints": [
|
|
16
|
-
{ "name": "
|
|
17
|
-
{ "name": "CF国外节点1", "url": "https://yunyi.cfd" }
|
|
18
|
-
{ "name": "CF国外节点2", "url": "https://cdn1.yunyi.cfd" },
|
|
19
|
-
{ "name": "CF国外节点3", "url": "https://cdn2.yunyi.cfd" }
|
|
16
|
+
{ "name": "默认主节点", "url": "https://yunyi.cfd" },
|
|
17
|
+
{ "name": "CF国外节点1", "url": "https://cdn2.yunyi.cfd" }
|
|
20
18
|
],
|
|
21
19
|
"fallbackEndpoints": [
|
|
22
20
|
{ "name": "备用节点1", "url": "http://47.99.42.193" },
|
|
@@ -77,7 +75,7 @@ npx yymaxapi@latest
|
|
|
77
75
|
```json
|
|
78
76
|
{
|
|
79
77
|
"provider": "anthropic",
|
|
80
|
-
"base_url": "https://yunyi.
|
|
78
|
+
"base_url": "https://yunyi.cfd/claude",
|
|
81
79
|
"api": "anthropic-messages",
|
|
82
80
|
"api_key": "<你的云翼 API Key>",
|
|
83
81
|
"model": {
|
|
@@ -92,7 +90,7 @@ npx yymaxapi@latest
|
|
|
92
90
|
```json
|
|
93
91
|
{
|
|
94
92
|
"provider": "openai",
|
|
95
|
-
"base_url": "https://yunyi.
|
|
93
|
+
"base_url": "https://yunyi.cfd/codex",
|
|
96
94
|
"api": "openai-completions",
|
|
97
95
|
"api_key": "<你的云翼 API Key>",
|
|
98
96
|
"model": {
|
|
@@ -104,6 +102,7 @@ npx yymaxapi@latest
|
|
|
104
102
|
|
|
105
103
|
**注意事项:**
|
|
106
104
|
- `base_url` 不要加 `/v1`,平台会自动拼接路径
|
|
105
|
+
- 如需临时切到其他云翼域名,可直接把域名部分替换掉;`yymaxapi` 也支持在预设/一键激活命令中传 `--endpoint-url https://你的域名` 或 `--base-url https://你的域名/claude`
|
|
107
106
|
- Claude 可用模型:`claude-sonnet-4-6`、`claude-opus-4-7`、`claude-haiku-4-5`
|
|
108
107
|
- GPT 可用模型:`gpt-5.4`
|
|
109
108
|
- 已验证环境:腾讯云 OpenCloudOS,OpenClaw `2026.2.3-1`
|
|
@@ -122,7 +121,7 @@ npx yymaxapi@latest
|
|
|
122
121
|
```json
|
|
123
122
|
{
|
|
124
123
|
"provider": "anthropic",
|
|
125
|
-
"base_url": "https://yunyi.
|
|
124
|
+
"base_url": "https://yunyi.cfd/claude",
|
|
126
125
|
"api": "anthropic-messages",
|
|
127
126
|
"api_key": "<你的云翼 API Key>",
|
|
128
127
|
"model": {
|
|
@@ -137,7 +136,7 @@ npx yymaxapi@latest
|
|
|
137
136
|
```json
|
|
138
137
|
{
|
|
139
138
|
"provider": "openai",
|
|
140
|
-
"base_url": "https://yunyi.
|
|
139
|
+
"base_url": "https://yunyi.cfd/codex",
|
|
141
140
|
"api": "openai-completions",
|
|
142
141
|
"api_key": "<你的云翼 API Key>",
|
|
143
142
|
"model": {
|
|
@@ -153,10 +152,10 @@ npx yymaxapi@latest
|
|
|
153
152
|
|
|
154
153
|
```json
|
|
155
154
|
{
|
|
156
|
-
"apiBaseUrl": "https://yunyi.
|
|
155
|
+
"apiBaseUrl": "https://yunyi.cfd/claude",
|
|
157
156
|
"env": {
|
|
158
157
|
"ANTHROPIC_API_KEY": "<你的云翼 API Key>",
|
|
159
|
-
"ANTHROPIC_BASE_URL": "https://yunyi.
|
|
158
|
+
"ANTHROPIC_BASE_URL": "https://yunyi.cfd/claude"
|
|
160
159
|
}
|
|
161
160
|
}
|
|
162
161
|
```
|