xiaozhou-chat 1.0.16 → 1.0.18
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/cli.js +35 -0
- package/lib/chat.js +29 -2
- package/lib/config.js +17 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -1,4 +1,39 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// -------------------------------------------------------------------------
|
|
4
|
+
// 🛡️ 环境净化区:在加载任何依赖前,清理可能导致冲突的环境变量
|
|
5
|
+
// -------------------------------------------------------------------------
|
|
6
|
+
(function sanitizeEnvironment() {
|
|
7
|
+
const proxyVars = [
|
|
8
|
+
'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
|
|
9
|
+
'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy'
|
|
10
|
+
];
|
|
11
|
+
|
|
12
|
+
let cleaned = [];
|
|
13
|
+
for (const key of proxyVars) {
|
|
14
|
+
if (process.env[key]) {
|
|
15
|
+
delete process.env[key];
|
|
16
|
+
cleaned.push(key);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 同时清理可能干扰的通用 API 配置(防止 .env 污染)
|
|
21
|
+
const conflictVars = ['API_KEY', 'BASE_URL', 'MODEL', 'OPENAI_API_KEY'];
|
|
22
|
+
for (const key of conflictVars) {
|
|
23
|
+
if (process.env[key]) {
|
|
24
|
+
delete process.env[key];
|
|
25
|
+
cleaned.push(key);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (cleaned.length > 0) {
|
|
30
|
+
// 仅在调试模式或检测到显式代理时提示,避免干扰正常输出
|
|
31
|
+
// 但为了响应用户需求,我们这里打印一个温和的提示
|
|
32
|
+
// console.log(`\n🛡️ 已自动屏蔽潜在冲突的环境变量: ${cleaned.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
})();
|
|
35
|
+
// -------------------------------------------------------------------------
|
|
36
|
+
|
|
2
37
|
import fs from "node:fs";
|
|
3
38
|
import path from "node:path";
|
|
4
39
|
import readline from "node:readline";
|
package/lib/chat.js
CHANGED
|
@@ -34,6 +34,27 @@ export async function requestWithRetry(url, options, maxRetries = 3) {
|
|
|
34
34
|
const text = await res.text();
|
|
35
35
|
// 4xx errors: do not retry
|
|
36
36
|
if (res.status >= 400 && res.status < 500) {
|
|
37
|
+
if (res.status === 400) {
|
|
38
|
+
console.log(`\n❌ API请求参数错误 (400)。调试信息:`);
|
|
39
|
+
console.log(`- Endpoint: ${url}`);
|
|
40
|
+
console.log(`- Model: ${options.body ? JSON.parse(options.body).model : 'unknown'}`);
|
|
41
|
+
// 尝试解析并打印更友好的错误
|
|
42
|
+
try {
|
|
43
|
+
const errJson = JSON.parse(text);
|
|
44
|
+
console.log(`- Server Message: ${errJson.error?.message || text}`);
|
|
45
|
+
} catch {
|
|
46
|
+
console.log(`- Server Response: ${text}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 提示用户检查配置
|
|
50
|
+
if (url.includes("tribiosapi")) {
|
|
51
|
+
console.log("- 提示: 默认模型 'claude-sonnet-4-5-20250929' 可能已过期或不可用。");
|
|
52
|
+
console.log(" 请尝试运行 'npx xiaozhou-chat config --model=gpt-4o' 切换模型。");
|
|
53
|
+
} else {
|
|
54
|
+
console.log("- 提示: 请检查当前模型名称是否与您的 API 提供商兼容。");
|
|
55
|
+
}
|
|
56
|
+
console.log(`- Config Source: 检查当前目录或 ~ 目录下是否存在 .newapi-chat-config.json`);
|
|
57
|
+
}
|
|
37
58
|
throw new Error(`API Error (${res.status}): ${text}`);
|
|
38
59
|
}
|
|
39
60
|
// 5xx errors: retry
|
|
@@ -128,19 +149,25 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
128
149
|
|
|
129
150
|
// 第一次尝试 (默认带 Tools)
|
|
130
151
|
try {
|
|
152
|
+
const body = createBody(true);
|
|
131
153
|
res = await requestWithRetry(requestUrl, {
|
|
132
154
|
method: "POST",
|
|
133
155
|
headers: {
|
|
134
156
|
"Content-Type": "application/json",
|
|
135
157
|
Authorization: `Bearer ${config.apiKey}`
|
|
136
158
|
},
|
|
137
|
-
body: JSON.stringify(
|
|
159
|
+
body: JSON.stringify(body),
|
|
138
160
|
signal: signal
|
|
139
161
|
}, 1); // 减少内部重试,由外层控制
|
|
140
162
|
} catch (e) {
|
|
141
163
|
// 如果是 400 错误,且我们用了 Tools,尝试降级
|
|
142
164
|
if (e.message.includes("400") || e.message.includes("Improperly formed request")) {
|
|
143
165
|
console.log("\n⚠️ API 不支持工具调用或参数格式错误,尝试自动降级为纯聊天模式...");
|
|
166
|
+
|
|
167
|
+
const body = createBody(false);
|
|
168
|
+
// 打印降级后的请求信息以供调试
|
|
169
|
+
console.log(`ℹ️ [Debug] Retrying with model: ${body.model}, max_tokens: ${body.max_tokens}`);
|
|
170
|
+
|
|
144
171
|
usedTools = false;
|
|
145
172
|
res = await requestWithRetry(requestUrl, {
|
|
146
173
|
method: "POST",
|
|
@@ -148,7 +175,7 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
148
175
|
"Content-Type": "application/json",
|
|
149
176
|
Authorization: `Bearer ${config.apiKey}`
|
|
150
177
|
},
|
|
151
|
-
body: JSON.stringify(
|
|
178
|
+
body: JSON.stringify(body),
|
|
152
179
|
signal: signal
|
|
153
180
|
});
|
|
154
181
|
} else {
|
package/lib/config.js
CHANGED
|
@@ -88,9 +88,26 @@ function loadConfigFrom(file) {
|
|
|
88
88
|
// 加载配置 (Home < Project)
|
|
89
89
|
export function loadConfig() {
|
|
90
90
|
const home = loadConfigFrom(homeConfigFile);
|
|
91
|
+
|
|
92
|
+
// 显式打印当前加载的配置文件,辅助调试
|
|
93
|
+
// 只有在存在项目级配置时才打印,避免在普通目录下显得啰嗦
|
|
94
|
+
const hasProjectConfig = fs.existsSync(projectConfigFile);
|
|
95
|
+
const hasAltConfig = fs.existsSync(projectAltConfigFile);
|
|
96
|
+
|
|
97
|
+
if (hasProjectConfig) {
|
|
98
|
+
console.log(`\n📂 [Config] 检测到项目级配置文件: ${projectConfigFile}`);
|
|
99
|
+
} else if (hasAltConfig) {
|
|
100
|
+
console.log(`\n📂 [Config] 检测到项目级配置文件: ${projectAltConfigFile}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
91
103
|
const project =
|
|
92
104
|
loadConfigFrom(projectConfigFile) ||
|
|
93
105
|
loadConfigFrom(projectAltConfigFile);
|
|
106
|
+
|
|
107
|
+
// 检查是否加载了空配置导致覆盖
|
|
108
|
+
if ((hasProjectConfig || hasAltConfig) && !project.apiKey && !project.profiles) {
|
|
109
|
+
console.log(`⚠️ [Config] 警告: 项目级配置文件似乎为空或格式不正确,可能会覆盖全局配置!`);
|
|
110
|
+
}
|
|
94
111
|
|
|
95
112
|
// Deep merge profiles if needed, but simple merge is okay for now
|
|
96
113
|
const config = { ...home, ...project };
|