xiaozhou-chat 1.0.6 → 1.0.8
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 +9 -2
- package/lib/chat.js +48 -1
- package/lib/config.js +31 -0
- package/package.json +1 -1
package/bin/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import minimist from "minimist";
|
|
|
6
6
|
import { MCPClient } from "../lib/mcp-lite.js";
|
|
7
7
|
import {
|
|
8
8
|
initConfigFile,
|
|
9
|
+
initProjectConfigFile,
|
|
9
10
|
loadConfig,
|
|
10
11
|
updateConfig,
|
|
11
12
|
getActiveConfig,
|
|
@@ -27,13 +28,19 @@ import {
|
|
|
27
28
|
} from "../lib/tools.js";
|
|
28
29
|
import { estimateTokens } from "../lib/utils.js";
|
|
29
30
|
|
|
31
|
+
const args = minimist(process.argv.slice(2));
|
|
32
|
+
|
|
33
|
+
// 处理 init 命令:直接在当前目录生成配置文件并退出
|
|
34
|
+
if (args._.includes("init")) {
|
|
35
|
+
initProjectConfigFile();
|
|
36
|
+
process.exit(0);
|
|
37
|
+
}
|
|
38
|
+
|
|
30
39
|
// --- 初始化 ---
|
|
31
40
|
initConfigFile();
|
|
32
41
|
let config = loadConfig();
|
|
33
42
|
let activeConfig = getActiveConfig(config);
|
|
34
43
|
|
|
35
|
-
const args = minimist(process.argv.slice(2));
|
|
36
|
-
|
|
37
44
|
// 覆盖配置 (命令行参数 > 配置文件)
|
|
38
45
|
if (args["api-key"]) activeConfig.apiKey = args["api-key"];
|
|
39
46
|
if (args["base-url"]) activeConfig.baseUrl = args["base-url"];
|
package/lib/chat.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { Spinner, StreamPrinter } from "./ui.js";
|
|
3
3
|
import { sleep } from "./utils.js";
|
|
4
|
+
import { updateConfig, setProfileValue } from "./config.js";
|
|
4
5
|
|
|
5
6
|
// 尝试加载 Markdown 渲染库
|
|
6
7
|
let marked;
|
|
@@ -98,8 +99,13 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
98
99
|
|
|
99
100
|
const printer = new StreamPrinter();
|
|
100
101
|
|
|
102
|
+
let requestUrl = `${config.baseUrl}/chat/completions`;
|
|
103
|
+
|
|
104
|
+
// 自动尝试逻辑
|
|
105
|
+
let shouldRetryWithV1 = false;
|
|
106
|
+
|
|
101
107
|
try {
|
|
102
|
-
|
|
108
|
+
let res = await requestWithRetry(requestUrl, {
|
|
103
109
|
method: "POST",
|
|
104
110
|
headers: {
|
|
105
111
|
"Content-Type": "application/json",
|
|
@@ -109,9 +115,50 @@ export async function chatStream(context, userInput = null, options = {}) {
|
|
|
109
115
|
signal: signal
|
|
110
116
|
});
|
|
111
117
|
|
|
118
|
+
// 智能检测:如果返回的是 HTML (通常是 404 页或首页),且 URL 没带 v1,可能是用户配错了
|
|
119
|
+
const contentType = res.headers.get("content-type");
|
|
120
|
+
if (contentType && contentType.includes("text/html")) {
|
|
121
|
+
if (!config.baseUrl.endsWith("/v1") && !config.baseUrl.endsWith("/v1/")) {
|
|
122
|
+
// 静默重试,不打扰用户
|
|
123
|
+
requestUrl = `${config.baseUrl}/v1/chat/completions`;
|
|
124
|
+
shouldRetryWithV1 = true;
|
|
125
|
+
|
|
126
|
+
// 重试请求
|
|
127
|
+
res = await requestWithRetry(requestUrl, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
Authorization: `Bearer ${config.apiKey}`
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify(body),
|
|
134
|
+
signal: signal
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
112
139
|
// 停止 Spinner,准备流式输出
|
|
113
140
|
spinner.stop();
|
|
114
141
|
|
|
142
|
+
if (shouldRetryWithV1 && res.ok && !res.headers.get("content-type")?.includes("text/html")) {
|
|
143
|
+
// 静默保存配置
|
|
144
|
+
try {
|
|
145
|
+
const correctBaseUrl = config.baseUrl.endsWith("/")
|
|
146
|
+
? config.baseUrl + "v1"
|
|
147
|
+
: config.baseUrl + "/v1";
|
|
148
|
+
|
|
149
|
+
// 1. 更新内存配置
|
|
150
|
+
config.baseUrl = correctBaseUrl;
|
|
151
|
+
|
|
152
|
+
// 2. 永久保存配置 (同时更新顶层和当前 Profile)
|
|
153
|
+
updateConfig("baseUrl", correctBaseUrl);
|
|
154
|
+
if (config.currentProfile) {
|
|
155
|
+
setProfileValue(config.currentProfile, "baseUrl", correctBaseUrl);
|
|
156
|
+
}
|
|
157
|
+
} catch (e) {
|
|
158
|
+
// ignore save error
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
115
162
|
if (!res.body) throw new Error("Response body is empty");
|
|
116
163
|
|
|
117
164
|
const reader = res.body.getReader();
|
package/lib/config.js
CHANGED
|
@@ -45,6 +45,37 @@ export function initConfigFile() {
|
|
|
45
45
|
console.log("⚠️ 请务必编辑该文件,填入你的 apiKey (sk-...),否则无法使用。");
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
// 在当前目录初始化项目级配置文件
|
|
49
|
+
export function initProjectConfigFile() {
|
|
50
|
+
const targetFile = path.join(process.cwd(), ".newapi-chat-config.json");
|
|
51
|
+
|
|
52
|
+
if (fs.existsSync(targetFile)) {
|
|
53
|
+
console.log(`⚠️ 配置文件已存在: ${targetFile}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const defaultConfig = {
|
|
58
|
+
apiKey: "", // 用户需手动填入
|
|
59
|
+
baseUrl: "https://paid.tribiosapi.top/v1",
|
|
60
|
+
model: "claude-sonnet-4-5-20250929",
|
|
61
|
+
profiles: {
|
|
62
|
+
default: {
|
|
63
|
+
apiKey: "",
|
|
64
|
+
baseUrl: "https://paid.tribiosapi.top/v1",
|
|
65
|
+
model: "claude-sonnet-4-5-20250929"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
currentProfile: "default"
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
fs.writeFileSync(targetFile, JSON.stringify(defaultConfig, null, 2), "utf-8");
|
|
72
|
+
console.log(`✅ 已在当前目录创建配置文件: ${targetFile}`);
|
|
73
|
+
console.log("📝 请编辑此文件填入你的 API Key。");
|
|
74
|
+
|
|
75
|
+
// 提示用户添加到 .gitignore
|
|
76
|
+
console.log("🔒 建议将 .newapi-chat-config.json 添加到 .gitignore 以防泄露。");
|
|
77
|
+
}
|
|
78
|
+
|
|
48
79
|
function loadConfigFrom(file) {
|
|
49
80
|
if (!fs.existsSync(file)) return {};
|
|
50
81
|
try {
|