yymaxapi 1.0.112 → 1.0.117
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,20 +73,16 @@ 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.
|
|
76
|
+
"name": "默认主节点",
|
|
77
|
+
"url": "https://yunyi.yun"
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
|
-
"name": "
|
|
81
|
-
"url": "https://yunyi.
|
|
80
|
+
"name": "CDN节点1",
|
|
81
|
+
"url": "https://cdn1.yunyi.yun"
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
|
-
"name": "
|
|
85
|
-
"url": "https://
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
"name": "CF国外节点3",
|
|
89
|
-
"url": "https://cdn2.yunyi.cfd"
|
|
84
|
+
"name": "CDN节点2",
|
|
85
|
+
"url": "https://cdn2.yunyi.yun"
|
|
90
86
|
}
|
|
91
87
|
];
|
|
92
88
|
|
|
@@ -616,9 +612,73 @@ function shouldRunEndpointSpeedTest(args = {}) {
|
|
|
616
612
|
return !!raw;
|
|
617
613
|
}
|
|
618
614
|
|
|
615
|
+
function getEndpointOverrideInput(args = {}) {
|
|
616
|
+
const raw = args['endpoint-url']
|
|
617
|
+
?? args.endpointUrl
|
|
618
|
+
?? args.endpoint
|
|
619
|
+
?? args.domain
|
|
620
|
+
?? args['base-url']
|
|
621
|
+
?? args.baseUrl
|
|
622
|
+
?? '';
|
|
623
|
+
return String(raw || '').trim();
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function normalizeEndpointBaseUrl(baseUrl) {
|
|
627
|
+
let trimmed = String(baseUrl || '').trim();
|
|
628
|
+
if (!trimmed) return '';
|
|
629
|
+
|
|
630
|
+
trimmed = trimClaudeMessagesSuffix(trimmed);
|
|
631
|
+
trimmed = trimOpenAiEndpointSuffix(trimmed);
|
|
632
|
+
trimmed = stripManagedYunyiSuffix(trimmed);
|
|
633
|
+
|
|
634
|
+
try {
|
|
635
|
+
const urlObj = new URL(trimmed);
|
|
636
|
+
urlObj.search = '';
|
|
637
|
+
urlObj.hash = '';
|
|
638
|
+
const normalizedPath = urlObj.pathname.replace(/\/+$/, '');
|
|
639
|
+
return `${urlObj.origin}${normalizedPath && normalizedPath !== '/' ? normalizedPath : ''}`;
|
|
640
|
+
} catch {
|
|
641
|
+
return trimmed.replace(/\/+$/, '');
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function resolveEndpointOverride(args = {}) {
|
|
646
|
+
const raw = getEndpointOverrideInput(args);
|
|
647
|
+
if (!raw) return null;
|
|
648
|
+
|
|
649
|
+
const normalized = normalizeEndpointBaseUrl(raw);
|
|
650
|
+
if (!normalized || !isValidUrl(normalized)) {
|
|
651
|
+
throw new Error('自定义节点 URL 无效,请使用 http(s):// 域名,支持填写根域名、/claude 或 /codex 地址');
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
return { name: '自定义节点', url: normalized };
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
async function promptCustomEndpointSelection(args = {}, options = {}) {
|
|
658
|
+
const defaultInput = getEndpointOverrideInput(args);
|
|
659
|
+
const promptMessage = options.customEndpointMessage || '请输入节点域名或 Base URL(支持根域名、/claude、/codex):';
|
|
660
|
+
const { customEndpointInput } = await inquirer.prompt([{
|
|
661
|
+
type: 'input',
|
|
662
|
+
name: 'customEndpointInput',
|
|
663
|
+
message: promptMessage,
|
|
664
|
+
default: defaultInput,
|
|
665
|
+
validate: input => {
|
|
666
|
+
const normalized = normalizeEndpointBaseUrl(input);
|
|
667
|
+
return Boolean(normalized && isValidUrl(normalized)) || '请输入有效的 URL(http:// 或 https://)';
|
|
668
|
+
}
|
|
669
|
+
}]);
|
|
670
|
+
|
|
671
|
+
return {
|
|
672
|
+
name: options.customEndpointName || '自定义节点',
|
|
673
|
+
url: normalizeEndpointBaseUrl(customEndpointInput)
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
|
|
619
677
|
async function resolveEndpointSelection(args = {}, options = {}) {
|
|
620
678
|
const defaultEndpoint = options.defaultEndpoint || ENDPOINTS[0];
|
|
621
679
|
if (!defaultEndpoint) return null;
|
|
680
|
+
const directOverride = resolveEndpointOverride(args);
|
|
681
|
+
if (directOverride) return directOverride;
|
|
622
682
|
if (!shouldRunEndpointSpeedTest(args)) return defaultEndpoint;
|
|
623
683
|
|
|
624
684
|
const speedIntro = options.speedIntro || '📡 开始测速节点...\n';
|
|
@@ -636,6 +696,7 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
636
696
|
message: chooseMessage,
|
|
637
697
|
choices: [
|
|
638
698
|
{ name: `* 使用默认节点 (${defaultEndpoint.name})`, value: -1 },
|
|
699
|
+
{ name: '* 手动输入节点域名', value: '__custom__' },
|
|
639
700
|
new inquirer.Separator(' ---- 或按测速结果选择 ----'),
|
|
640
701
|
...sorted.map((endpoint, index) => ({
|
|
641
702
|
name: `${endpoint.name} - ${endpoint.latency}ms (评分:${endpoint.score})`,
|
|
@@ -644,7 +705,9 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
644
705
|
]
|
|
645
706
|
}]);
|
|
646
707
|
|
|
647
|
-
const selectedEndpoint = selectedIndex ===
|
|
708
|
+
const selectedEndpoint = selectedIndex === '__custom__'
|
|
709
|
+
? await promptCustomEndpointSelection(args, options)
|
|
710
|
+
: (selectedIndex === -1 ? defaultEndpoint : sorted[selectedIndex]);
|
|
648
711
|
if (speedResult.usedFallback) {
|
|
649
712
|
console.log(chalk.yellow('\n⚠ 当前使用备用节点\n'));
|
|
650
713
|
}
|
|
@@ -652,13 +715,21 @@ async function resolveEndpointSelection(args = {}, options = {}) {
|
|
|
652
715
|
}
|
|
653
716
|
|
|
654
717
|
console.log(chalk.red('\n⚠️ 所有节点(含备用)均不可达'));
|
|
655
|
-
const
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
718
|
+
const proceedChoiceLabel = options.proceedChoiceLabel || `${proceedMessage.replace(/[??]\s*$/, '')} (${defaultEndpoint.name})`;
|
|
719
|
+
const { nextAction } = await inquirer.prompt([{
|
|
720
|
+
type: 'list',
|
|
721
|
+
name: 'nextAction',
|
|
722
|
+
message: '测速未找到可用节点,接下来怎么做?',
|
|
723
|
+
choices: [
|
|
724
|
+
{ name: proceedChoiceLabel, value: 'default' },
|
|
725
|
+
{ name: '手动输入节点域名', value: 'custom' },
|
|
726
|
+
{ name: '取消', value: 'cancel' }
|
|
727
|
+
]
|
|
660
728
|
}]);
|
|
661
|
-
if (
|
|
729
|
+
if (nextAction === 'custom') {
|
|
730
|
+
return promptCustomEndpointSelection(args, options);
|
|
731
|
+
}
|
|
732
|
+
if (nextAction !== 'default') {
|
|
662
733
|
console.log(chalk.gray('已取消'));
|
|
663
734
|
return null;
|
|
664
735
|
}
|
|
@@ -2671,17 +2742,17 @@ function hasManagedYunyiFootprint(config) {
|
|
|
2671
2742
|
}
|
|
2672
2743
|
|
|
2673
2744
|
function inferManagedYunyiEndpointUrl(config, explicitEndpointUrl = '') {
|
|
2674
|
-
const direct =
|
|
2675
|
-
if (direct &&
|
|
2745
|
+
const direct = normalizeEndpointBaseUrl(explicitEndpointUrl);
|
|
2746
|
+
if (direct && isValidUrl(direct)) return direct;
|
|
2676
2747
|
|
|
2677
2748
|
const providers = config?.models?.providers || {};
|
|
2678
2749
|
for (const [name, provider] of Object.entries(providers)) {
|
|
2679
2750
|
if (!isYunyiProviderEntry(name, provider, 'claude') && !isYunyiProviderEntry(name, provider, 'codex')) continue;
|
|
2680
|
-
const candidate =
|
|
2681
|
-
if (candidate &&
|
|
2751
|
+
const candidate = normalizeEndpointBaseUrl(provider?.baseUrl || provider?.base_url || '');
|
|
2752
|
+
if (candidate && isValidUrl(candidate)) return candidate;
|
|
2682
2753
|
}
|
|
2683
2754
|
|
|
2684
|
-
return ENDPOINTS.find(item =>
|
|
2755
|
+
return ENDPOINTS.find(item => isValidUrl(item.url))?.url || ENDPOINTS[0]?.url || '';
|
|
2685
2756
|
}
|
|
2686
2757
|
|
|
2687
2758
|
function inferManagedYunyiApiKey(config, explicitApiKey = '') {
|
|
@@ -3450,6 +3521,10 @@ function shellQuote(value) {
|
|
|
3450
3521
|
return `"${str.replace(/(["\\$`])/g, '\\$1')}"`;
|
|
3451
3522
|
}
|
|
3452
3523
|
|
|
3524
|
+
function getShellNullInputRedirect() {
|
|
3525
|
+
return process.platform === 'win32' ? ' < NUL' : ' < /dev/null';
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3453
3528
|
function escapeSingleQuotedShell(value) {
|
|
3454
3529
|
return String(value || '').replace(/'/g, "'\\''");
|
|
3455
3530
|
}
|
|
@@ -4021,6 +4096,41 @@ function buildReadableAgentError(text, fallback = '') {
|
|
|
4021
4096
|
return (preferred || lines[0] || fallback).slice(0, 300);
|
|
4022
4097
|
}
|
|
4023
4098
|
|
|
4099
|
+
function scoreCliErrorDetail(text) {
|
|
4100
|
+
const lower = String(text || '').toLowerCase();
|
|
4101
|
+
if (!lower) return -1;
|
|
4102
|
+
let score = 0;
|
|
4103
|
+
if ([
|
|
4104
|
+
'forbidden',
|
|
4105
|
+
'unauthorized',
|
|
4106
|
+
'permission to access',
|
|
4107
|
+
'authentication_error',
|
|
4108
|
+
'invalid bearer token',
|
|
4109
|
+
'invalid api key',
|
|
4110
|
+
'all models failed',
|
|
4111
|
+
'auth issue',
|
|
4112
|
+
'auth_permanent',
|
|
4113
|
+
'unexpected status',
|
|
4114
|
+
'gateway agent failed'
|
|
4115
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4116
|
+
score += 5;
|
|
4117
|
+
}
|
|
4118
|
+
if ([
|
|
4119
|
+
'failed',
|
|
4120
|
+
'error:',
|
|
4121
|
+
'missing environment variable'
|
|
4122
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4123
|
+
score += 2;
|
|
4124
|
+
}
|
|
4125
|
+
if ([
|
|
4126
|
+
'reading additional input from stdin',
|
|
4127
|
+
'reconnecting'
|
|
4128
|
+
].some(pattern => lower.includes(pattern))) {
|
|
4129
|
+
score -= 2;
|
|
4130
|
+
}
|
|
4131
|
+
return score;
|
|
4132
|
+
}
|
|
4133
|
+
|
|
4024
4134
|
function looksLikeCliTestError(text) {
|
|
4025
4135
|
const lower = cleanCliTestOutput(text).toLowerCase();
|
|
4026
4136
|
if (!lower) return false;
|
|
@@ -4043,13 +4153,19 @@ function looksLikeCliTestError(text) {
|
|
|
4043
4153
|
|
|
4044
4154
|
function runCliTestCandidates(name, commands, env) {
|
|
4045
4155
|
let lastError = '命令执行失败';
|
|
4156
|
+
let lastErrorScore = scoreCliErrorDetail(lastError);
|
|
4046
4157
|
for (const command of commands) {
|
|
4047
4158
|
const result = safeExec(command, { timeout: 120000, env, maxBuffer: 1024 * 1024 });
|
|
4048
4159
|
const combined = cleanCliTestOutput(`${result.output || ''}\n${result.stdout || ''}\n${result.stderr || ''}`);
|
|
4049
4160
|
if (result.ok && !looksLikeCliTestError(combined)) {
|
|
4050
4161
|
return { name, status: 'success', detail: summarizeCliTestOutput(combined) || '连接成功' };
|
|
4051
4162
|
}
|
|
4052
|
-
|
|
4163
|
+
const candidateError = buildReadableAgentError(combined, result.error || lastError);
|
|
4164
|
+
const candidateScore = scoreCliErrorDetail(candidateError);
|
|
4165
|
+
if (candidateScore >= lastErrorScore) {
|
|
4166
|
+
lastError = candidateError;
|
|
4167
|
+
lastErrorScore = candidateScore;
|
|
4168
|
+
}
|
|
4053
4169
|
}
|
|
4054
4170
|
return { name, status: 'failed', detail: lastError };
|
|
4055
4171
|
}
|
|
@@ -4119,10 +4235,11 @@ function testCodexCliConnection() {
|
|
|
4119
4235
|
NODE_TLS_REJECT_UNAUTHORIZED: '0',
|
|
4120
4236
|
NODE_NO_WARNINGS: '1'
|
|
4121
4237
|
};
|
|
4238
|
+
const nullRedirect = getShellNullInputRedirect();
|
|
4122
4239
|
|
|
4123
4240
|
return runCliTestCandidates('Codex CLI', [
|
|
4124
|
-
`${shellQuote(cliBinary)} exec --skip-git-repo-check ${shellQuote('请只回复 OK')}`,
|
|
4125
|
-
`${shellQuote(cliBinary)} exec ${shellQuote('请只回复 OK')}`
|
|
4241
|
+
`${shellQuote(cliBinary)} exec --skip-git-repo-check ${shellQuote('请只回复 OK')}${nullRedirect}`,
|
|
4242
|
+
`${shellQuote(cliBinary)} exec ${shellQuote('请只回复 OK')}${nullRedirect}`
|
|
4126
4243
|
], env);
|
|
4127
4244
|
}
|
|
4128
4245
|
|
|
@@ -5178,7 +5295,11 @@ async function autoActivate(paths, args = {}) {
|
|
|
5178
5295
|
const codexApiConfig = API_CONFIG.codex;
|
|
5179
5296
|
const claudeProviderName = claudeApiConfig.providerName;
|
|
5180
5297
|
const codexProviderName = codexApiConfig.providerName;
|
|
5181
|
-
const selectedEndpoint =
|
|
5298
|
+
const selectedEndpoint = await resolveEndpointSelection(args, {
|
|
5299
|
+
speedIntro: '📡 开始测速云翼节点...\n',
|
|
5300
|
+
proceedMessage: '仍要写入默认节点配置吗?'
|
|
5301
|
+
});
|
|
5302
|
+
if (!selectedEndpoint) return;
|
|
5182
5303
|
|
|
5183
5304
|
// ---- API Key ----
|
|
5184
5305
|
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
|
```
|