yymaxapi 1.0.103 → 1.0.104
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 +245 -43
- package/package.json +1 -1
package/bin/yymaxapi.js
CHANGED
|
@@ -321,6 +321,90 @@ async function promptClaudeModelSelection(args = {}, message = '选择 Claude
|
|
|
321
321
|
return CLAUDE_MODELS.find(model => model.id === selected) || fallback;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
function normalizeHermesModelType(value) {
|
|
325
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
326
|
+
if (!normalized) return '';
|
|
327
|
+
if (['gpt', 'codex', 'openai'].includes(normalized)) return 'codex';
|
|
328
|
+
if (['claude', 'anthropic'].includes(normalized)) return 'claude';
|
|
329
|
+
return '';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function resolveHermesModelType(modelId, preferredType = '') {
|
|
333
|
+
const requestedType = normalizeHermesModelType(preferredType);
|
|
334
|
+
const inClaude = CLAUDE_MODELS.some(model => model.id === modelId);
|
|
335
|
+
const inCodex = CODEX_MODELS.some(model => model.id === modelId);
|
|
336
|
+
|
|
337
|
+
if (requestedType === 'claude' && (inClaude || !inCodex)) return 'claude';
|
|
338
|
+
if (requestedType === 'codex' && (inCodex || !inClaude)) return 'codex';
|
|
339
|
+
if (inCodex && !inClaude) return 'codex';
|
|
340
|
+
if (inClaude && !inCodex) return 'claude';
|
|
341
|
+
return requestedType || 'claude';
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
async function promptHermesModelSelection(args = {}, message = '选择 Hermes 默认模型:') {
|
|
345
|
+
const requestedModel = (args['model-id'] || args.model || '').toString().trim();
|
|
346
|
+
const requestedType = normalizeHermesModelType(
|
|
347
|
+
args.primary || args.type || args.protocol || args.provider || args.runtime
|
|
348
|
+
);
|
|
349
|
+
const explicitClaudeModel = (args['claude-model'] || '').toString().trim();
|
|
350
|
+
const explicitCodexModel = (args['codex-model'] || '').toString().trim();
|
|
351
|
+
|
|
352
|
+
let selectedType = requestedType;
|
|
353
|
+
if (!selectedType && explicitClaudeModel) selectedType = 'claude';
|
|
354
|
+
if (!selectedType && explicitCodexModel) selectedType = 'codex';
|
|
355
|
+
if (!selectedType && requestedModel) {
|
|
356
|
+
selectedType = resolveHermesModelType(requestedModel);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!selectedType) {
|
|
360
|
+
if (CLAUDE_MODELS.length > 0 && CODEX_MODELS.length === 0) {
|
|
361
|
+
selectedType = 'claude';
|
|
362
|
+
} else if (CODEX_MODELS.length > 0 && CLAUDE_MODELS.length === 0) {
|
|
363
|
+
selectedType = 'codex';
|
|
364
|
+
} else {
|
|
365
|
+
const { selectedType: pickedType } = await inquirer.prompt([{
|
|
366
|
+
type: 'list',
|
|
367
|
+
name: 'selectedType',
|
|
368
|
+
message: '选择 Hermes 模型类型:',
|
|
369
|
+
choices: [
|
|
370
|
+
{ name: 'Claude', value: 'claude' },
|
|
371
|
+
{ name: 'GPT', value: 'codex' }
|
|
372
|
+
],
|
|
373
|
+
default: 'claude'
|
|
374
|
+
}]);
|
|
375
|
+
selectedType = pickedType;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const modelList = selectedType === 'codex' ? CODEX_MODELS : CLAUDE_MODELS;
|
|
380
|
+
const fallbackModel = modelList[0] || (selectedType === 'codex' ? getDefaultCodexModel() : getDefaultClaudeModel());
|
|
381
|
+
const explicitModelId = selectedType === 'codex'
|
|
382
|
+
? (explicitCodexModel || requestedModel)
|
|
383
|
+
: (explicitClaudeModel || requestedModel);
|
|
384
|
+
|
|
385
|
+
if (explicitModelId) {
|
|
386
|
+
const model = modelList.find(item => item.id === explicitModelId) || { id: explicitModelId, name: explicitModelId };
|
|
387
|
+
return { type: selectedType, model };
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (modelList.length <= 1) {
|
|
391
|
+
return { type: selectedType, model: fallbackModel };
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const { selected } = await inquirer.prompt([{
|
|
395
|
+
type: 'list',
|
|
396
|
+
name: 'selected',
|
|
397
|
+
message,
|
|
398
|
+
choices: modelList.map(model => ({ name: model.name, value: model.id })),
|
|
399
|
+
default: fallbackModel.id
|
|
400
|
+
}]);
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
type: selectedType,
|
|
404
|
+
model: modelList.find(model => model.id === selected) || fallbackModel
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
324
408
|
async function promptOpencodeDefaultModelSelection(args = {}, message = '选择 Opencode 默认模型:') {
|
|
325
409
|
const fallbackClaude = getDefaultClaudeModel();
|
|
326
410
|
const fallbackCodex = getDefaultCodexModel();
|
|
@@ -1075,12 +1159,16 @@ function writeOpencodeConfig(claudeBaseUrl, codexBaseUrl, apiKey, defaultModelKe
|
|
|
1075
1159
|
return configPath;
|
|
1076
1160
|
}
|
|
1077
1161
|
|
|
1078
|
-
function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id) {
|
|
1162
|
+
function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id, options = {}) {
|
|
1079
1163
|
const dataDir = getHermesDataDir();
|
|
1080
1164
|
const configPath = path.join(dataDir, 'config.yaml');
|
|
1081
1165
|
const envPath = getHermesEnvPath();
|
|
1082
1166
|
const existingConfigPath = getHermesConfigPath();
|
|
1083
1167
|
const wslMirror = getHermesWslMirrorInfo();
|
|
1168
|
+
const modelType = options.type || options.provider || '';
|
|
1169
|
+
const resolvedType = resolveHermesModelType(modelId, modelType);
|
|
1170
|
+
const runtimeProvider = resolvedType === 'codex' ? 'custom' : 'anthropic';
|
|
1171
|
+
const apiMode = resolvedType === 'codex' ? 'codex_responses' : 'anthropic_messages';
|
|
1084
1172
|
|
|
1085
1173
|
let existingConfigRaw = readTextIfExists(existingConfigPath);
|
|
1086
1174
|
if (!existingConfigRaw && wslMirror.sourceConfigPath) {
|
|
@@ -1092,15 +1180,25 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
|
|
|
1092
1180
|
existingEnvRaw = readWslTextFile(wslMirror.envPath);
|
|
1093
1181
|
}
|
|
1094
1182
|
|
|
1095
|
-
const normalizedBaseUrl =
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1183
|
+
const normalizedBaseUrl = (
|
|
1184
|
+
resolvedType === 'codex'
|
|
1185
|
+
? trimOpenAiEndpointSuffix(String(baseUrl || '').trim())
|
|
1186
|
+
: trimClaudeMessagesSuffix(String(baseUrl || '').trim())
|
|
1187
|
+
).replace(/\/+$/, '');
|
|
1188
|
+
const existingConfig = parseSimpleYamlConfig(existingConfigRaw);
|
|
1189
|
+
const existingModelConfig = existingConfig.model && !Array.isArray(existingConfig.model) && typeof existingConfig.model === 'object'
|
|
1190
|
+
? existingConfig.model
|
|
1191
|
+
: {};
|
|
1192
|
+
const nextConfigRaw = upsertHermesModelConfig(existingConfigRaw, {
|
|
1193
|
+
...existingModelConfig,
|
|
1194
|
+
default: modelId,
|
|
1195
|
+
provider: runtimeProvider,
|
|
1196
|
+
base_url: normalizedBaseUrl,
|
|
1197
|
+
api_mode: apiMode
|
|
1103
1198
|
});
|
|
1199
|
+
const envEntries = {};
|
|
1200
|
+
envEntries[resolvedType === 'codex' ? 'OPENAI_API_KEY' : 'ANTHROPIC_API_KEY'] = String(apiKey || '').trim();
|
|
1201
|
+
const nextEnvRaw = upsertEnvFile(existingEnvRaw, envEntries);
|
|
1104
1202
|
|
|
1105
1203
|
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
|
1106
1204
|
fs.writeFileSync(configPath, nextConfigRaw, 'utf8');
|
|
@@ -1120,6 +1218,9 @@ function writeHermesConfig(baseUrl, apiKey, modelId = getDefaultClaudeModel().id
|
|
|
1120
1218
|
wslConfigPath: wslMirror.configPath,
|
|
1121
1219
|
wslEnvPath: wslMirror.envPath,
|
|
1122
1220
|
modelId,
|
|
1221
|
+
modelType: resolvedType,
|
|
1222
|
+
provider: runtimeProvider,
|
|
1223
|
+
apiMode,
|
|
1123
1224
|
baseUrl: normalizedBaseUrl
|
|
1124
1225
|
};
|
|
1125
1226
|
}
|
|
@@ -3508,24 +3609,58 @@ function readWslTextFile(filePath) {
|
|
|
3508
3609
|
return result.ok ? (result.output || result.stdout || '') : '';
|
|
3509
3610
|
}
|
|
3510
3611
|
|
|
3612
|
+
function parseYamlScalar(value) {
|
|
3613
|
+
let normalized = String(value ?? '').trim();
|
|
3614
|
+
if (
|
|
3615
|
+
(normalized.startsWith('"') && normalized.endsWith('"'))
|
|
3616
|
+
|| (normalized.startsWith('\'') && normalized.endsWith('\''))
|
|
3617
|
+
) {
|
|
3618
|
+
try {
|
|
3619
|
+
normalized = JSON.parse(normalized);
|
|
3620
|
+
} catch {
|
|
3621
|
+
normalized = normalized.slice(1, -1);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
return normalized;
|
|
3625
|
+
}
|
|
3626
|
+
|
|
3511
3627
|
function parseSimpleYamlConfig(text) {
|
|
3512
3628
|
const config = {};
|
|
3629
|
+
let currentObjectKey = '';
|
|
3630
|
+
|
|
3513
3631
|
for (const line of String(text || '').split(/\r?\n/)) {
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
if (
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3632
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
3633
|
+
|
|
3634
|
+
const topLevelMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);
|
|
3635
|
+
if (topLevelMatch && !line.startsWith(' ')) {
|
|
3636
|
+
const key = topLevelMatch[1];
|
|
3637
|
+
const rawValue = topLevelMatch[2];
|
|
3638
|
+
if (rawValue === '') {
|
|
3639
|
+
if (!config[key] || typeof config[key] !== 'object' || Array.isArray(config[key])) {
|
|
3640
|
+
config[key] = {};
|
|
3641
|
+
}
|
|
3642
|
+
currentObjectKey = key;
|
|
3643
|
+
} else {
|
|
3644
|
+
config[key] = parseYamlScalar(rawValue);
|
|
3645
|
+
currentObjectKey = '';
|
|
3525
3646
|
}
|
|
3647
|
+
continue;
|
|
3526
3648
|
}
|
|
3527
|
-
|
|
3649
|
+
|
|
3650
|
+
if (currentObjectKey) {
|
|
3651
|
+
const nestedMatch = line.match(/^\s{2}([A-Za-z0-9_-]+)\s*:\s*(.*?)\s*$/);
|
|
3652
|
+
if (nestedMatch) {
|
|
3653
|
+
if (!config[currentObjectKey] || typeof config[currentObjectKey] !== 'object' || Array.isArray(config[currentObjectKey])) {
|
|
3654
|
+
config[currentObjectKey] = {};
|
|
3655
|
+
}
|
|
3656
|
+
config[currentObjectKey][nestedMatch[1]] = parseYamlScalar(nestedMatch[2]);
|
|
3657
|
+
continue;
|
|
3658
|
+
}
|
|
3659
|
+
}
|
|
3660
|
+
|
|
3661
|
+
currentObjectKey = '';
|
|
3528
3662
|
}
|
|
3663
|
+
|
|
3529
3664
|
return config;
|
|
3530
3665
|
}
|
|
3531
3666
|
|
|
@@ -3533,31 +3668,71 @@ function serializeYamlScalar(value) {
|
|
|
3533
3668
|
return JSON.stringify(String(value ?? ''));
|
|
3534
3669
|
}
|
|
3535
3670
|
|
|
3536
|
-
function
|
|
3537
|
-
const
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3671
|
+
function buildHermesModelConfigBlock(entries) {
|
|
3672
|
+
const ordered = {};
|
|
3673
|
+
for (const key of ['default', 'provider', 'base_url', 'api_mode']) {
|
|
3674
|
+
if (Object.prototype.hasOwnProperty.call(entries, key)) {
|
|
3675
|
+
ordered[key] = entries[key];
|
|
3676
|
+
}
|
|
3677
|
+
}
|
|
3678
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
3679
|
+
if (!Object.prototype.hasOwnProperty.call(ordered, key)) {
|
|
3680
|
+
ordered[key] = value;
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3540
3683
|
|
|
3541
|
-
|
|
3542
|
-
|
|
3543
|
-
if (
|
|
3544
|
-
|
|
3545
|
-
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
|
|
3546
|
-
normalized[i] = `${key}: ${serializeYamlScalar(entries[key])}`;
|
|
3547
|
-
seen.add(key);
|
|
3684
|
+
const lines = ['model:'];
|
|
3685
|
+
for (const [key, value] of Object.entries(ordered)) {
|
|
3686
|
+
if (value === undefined || value === null || value === '') continue;
|
|
3687
|
+
lines.push(` ${key}: ${serializeYamlScalar(value)}`);
|
|
3548
3688
|
}
|
|
3689
|
+
return lines.join('\n');
|
|
3690
|
+
}
|
|
3549
3691
|
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3692
|
+
function upsertHermesModelConfig(text, modelEntries) {
|
|
3693
|
+
let normalized = String(text || '').replace(/\r\n/g, '\n');
|
|
3694
|
+
normalized = normalized.replace(/^(provider|base_url|api_mode):\s*.*\n?/gm, '');
|
|
3695
|
+
|
|
3696
|
+
const lines = normalized.split('\n');
|
|
3697
|
+
const output = [];
|
|
3698
|
+
let replaced = false;
|
|
3699
|
+
|
|
3700
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
3701
|
+
const line = lines[i];
|
|
3702
|
+
if (/^model:\s*(.*)$/.test(line)) {
|
|
3703
|
+
replaced = true;
|
|
3704
|
+
if (output.length > 0 && output[output.length - 1] !== '') {
|
|
3705
|
+
output.push('');
|
|
3706
|
+
}
|
|
3707
|
+
output.push(buildHermesModelConfigBlock(modelEntries));
|
|
3708
|
+
|
|
3709
|
+
i += 1;
|
|
3710
|
+
while (i < lines.length) {
|
|
3711
|
+
const nextLine = lines[i];
|
|
3712
|
+
if (/^\s{2,}/.test(nextLine)) {
|
|
3713
|
+
i += 1;
|
|
3714
|
+
continue;
|
|
3715
|
+
}
|
|
3716
|
+
if (nextLine === '') {
|
|
3717
|
+
break;
|
|
3718
|
+
}
|
|
3719
|
+
i -= 1;
|
|
3720
|
+
break;
|
|
3721
|
+
}
|
|
3722
|
+
continue;
|
|
3553
3723
|
}
|
|
3724
|
+
output.push(line);
|
|
3554
3725
|
}
|
|
3555
3726
|
|
|
3556
|
-
while (
|
|
3557
|
-
|
|
3727
|
+
while (output.length > 0 && output[output.length - 1] === '') {
|
|
3728
|
+
output.pop();
|
|
3729
|
+
}
|
|
3730
|
+
if (!replaced) {
|
|
3731
|
+
if (output.length > 0) output.push('');
|
|
3732
|
+
output.push(buildHermesModelConfigBlock(modelEntries));
|
|
3558
3733
|
}
|
|
3559
3734
|
|
|
3560
|
-
return
|
|
3735
|
+
return output.join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n';
|
|
3561
3736
|
}
|
|
3562
3737
|
|
|
3563
3738
|
function parseEnvFile(text) {
|
|
@@ -4332,6 +4507,27 @@ function trimClaudeMessagesSuffix(baseUrl) {
|
|
|
4332
4507
|
return trimmed;
|
|
4333
4508
|
}
|
|
4334
4509
|
|
|
4510
|
+
function trimOpenAiEndpointSuffix(baseUrl) {
|
|
4511
|
+
const trimmed = baseUrl.trim();
|
|
4512
|
+
const suffixes = [
|
|
4513
|
+
'/v1/responses',
|
|
4514
|
+
'/responses',
|
|
4515
|
+
'/v1/chat/completions',
|
|
4516
|
+
'/chat/completions',
|
|
4517
|
+
'/v1/models',
|
|
4518
|
+
'/models',
|
|
4519
|
+
'/v1'
|
|
4520
|
+
];
|
|
4521
|
+
|
|
4522
|
+
for (const suffix of suffixes) {
|
|
4523
|
+
if (trimmed.endsWith(suffix)) {
|
|
4524
|
+
return trimmed.slice(0, -suffix.length);
|
|
4525
|
+
}
|
|
4526
|
+
}
|
|
4527
|
+
|
|
4528
|
+
return trimmed;
|
|
4529
|
+
}
|
|
4530
|
+
|
|
4335
4531
|
async function quickSetup(paths, args = {}) {
|
|
4336
4532
|
console.log(chalk.cyan.bold('\n🚀 快速配置向导\n'));
|
|
4337
4533
|
|
|
@@ -5319,7 +5515,9 @@ async function activateCodex(paths, args = {}) {
|
|
|
5319
5515
|
// ============ 单独配置 Hermes ============
|
|
5320
5516
|
async function activateHermes(paths, args = {}) {
|
|
5321
5517
|
console.log(chalk.cyan.bold('\n🔧 配置 Hermes\n'));
|
|
5322
|
-
const
|
|
5518
|
+
const selection = await promptHermesModelSelection(args, '选择 Hermes 默认模型:');
|
|
5519
|
+
const selectedModel = selection.model;
|
|
5520
|
+
const selectedType = selection.type;
|
|
5323
5521
|
|
|
5324
5522
|
const shouldTest = !(args['no-test'] || args.noTest);
|
|
5325
5523
|
let selectedEndpoint = ENDPOINTS[0];
|
|
@@ -5359,7 +5557,9 @@ async function activateHermes(paths, args = {}) {
|
|
|
5359
5557
|
if (directKey) {
|
|
5360
5558
|
apiKey = directKey;
|
|
5361
5559
|
} else {
|
|
5362
|
-
const envKey =
|
|
5560
|
+
const envKey = selectedType === 'codex'
|
|
5561
|
+
? (process.env.OPENAI_API_KEY || process.env.OPENAI_AUTH_TOKEN || '')
|
|
5562
|
+
: (process.env.ANTHROPIC_API_KEY || process.env.ANTHROPIC_AUTH_TOKEN || process.env.CLAUDE_API_KEY || '');
|
|
5363
5563
|
apiKey = await promptApiKey('请输入 API Key:', envKey);
|
|
5364
5564
|
}
|
|
5365
5565
|
if (!apiKey) { console.log(chalk.gray('已取消')); return; }
|
|
@@ -5374,15 +5574,17 @@ async function activateHermes(paths, args = {}) {
|
|
|
5374
5574
|
if (!continueAnyway) { console.log(chalk.gray('已取消')); return; }
|
|
5375
5575
|
}
|
|
5376
5576
|
|
|
5377
|
-
const
|
|
5577
|
+
const typeLabel = selectedType === 'codex' ? 'GPT' : 'Claude';
|
|
5578
|
+
const hermesBaseUrl = buildFullUrl(selectedEndpoint.url, selectedType === 'codex' ? 'codex' : 'claude');
|
|
5378
5579
|
const writeSpinner = ora({ text: '正在写入 Hermes 配置...', spinner: 'dots' }).start();
|
|
5379
|
-
const hermesPaths = writeHermesConfig(
|
|
5580
|
+
const hermesPaths = writeHermesConfig(hermesBaseUrl, apiKey, selectedModel.id, { type: selectedType });
|
|
5380
5581
|
writeSpinner.succeed('Hermes 配置写入完成');
|
|
5381
5582
|
|
|
5382
5583
|
console.log(chalk.green('\n✅ Hermes 配置完成!'));
|
|
5383
5584
|
console.log(chalk.cyan(` Base URL: ${hermesPaths.baseUrl}`));
|
|
5384
5585
|
console.log(chalk.gray(` 模型: ${selectedModel.name} (${selectedModel.id})`));
|
|
5385
|
-
console.log(chalk.gray(
|
|
5586
|
+
console.log(chalk.gray(` 协议: ${typeLabel}`));
|
|
5587
|
+
console.log(chalk.gray(` Provider: ${hermesPaths.provider}`));
|
|
5386
5588
|
console.log(chalk.gray(' API Key: 已设置'));
|
|
5387
5589
|
console.log(chalk.gray('\n 已写入:'));
|
|
5388
5590
|
console.log(chalk.gray(` • ${hermesPaths.configPath}`));
|