@nhonh/qabot 0.2.2 → 0.3.0
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/package.json +1 -1
- package/src/ai/ai-engine.js +14 -8
- package/src/cli/commands/auth.js +70 -12
- package/src/core/constants.js +1 -1
package/package.json
CHANGED
package/src/ai/ai-engine.js
CHANGED
|
@@ -9,7 +9,7 @@ export class AIEngine {
|
|
|
9
9
|
constructor(config = {}) {
|
|
10
10
|
this.provider = config.provider || "none";
|
|
11
11
|
this.model =
|
|
12
|
-
config.model
|
|
12
|
+
config.model ?? AI_PROVIDER_DEFAULTS[this.provider]?.model ?? "";
|
|
13
13
|
this.apiKey = resolveApiKey(config);
|
|
14
14
|
this.baseUrl =
|
|
15
15
|
config.baseUrl || AI_PROVIDER_DEFAULTS[this.provider]?.baseUrl || "";
|
|
@@ -34,11 +34,15 @@ export class AIEngine {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
getProviderInfo() {
|
|
37
|
+
const preview = this.apiKey
|
|
38
|
+
? this.apiKey.slice(0, 4) + "****" + this.apiKey.slice(-4)
|
|
39
|
+
: "";
|
|
37
40
|
return {
|
|
38
41
|
provider: this.provider,
|
|
39
42
|
model: this.model,
|
|
40
43
|
baseUrl: this.baseUrl,
|
|
41
44
|
hasApiKey: !!this.apiKey,
|
|
45
|
+
apiKeyPreview: preview,
|
|
42
46
|
};
|
|
43
47
|
}
|
|
44
48
|
|
|
@@ -185,21 +189,23 @@ export class AIEngine {
|
|
|
185
189
|
async callProxy(prompt) {
|
|
186
190
|
if (!this.baseUrl) {
|
|
187
191
|
throw new Error(
|
|
188
|
-
"Proxy provider requires baseUrl. Set ai.baseUrl in qabot.config.
|
|
192
|
+
"Proxy provider requires baseUrl. Set ai.baseUrl in qabot.config.json",
|
|
189
193
|
);
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
const headers = this.buildAuthHeaders();
|
|
193
197
|
|
|
198
|
+
const body = {
|
|
199
|
+
messages: [{ role: "user", content: prompt }],
|
|
200
|
+
temperature: this.temperature,
|
|
201
|
+
max_tokens: this.maxTokens,
|
|
202
|
+
};
|
|
203
|
+
if (this.model) body.model = this.model;
|
|
204
|
+
|
|
194
205
|
const res = await fetch(this.baseUrl, {
|
|
195
206
|
method: "POST",
|
|
196
207
|
headers,
|
|
197
|
-
body: JSON.stringify(
|
|
198
|
-
model: this.model,
|
|
199
|
-
messages: [{ role: "user", content: prompt }],
|
|
200
|
-
temperature: this.temperature,
|
|
201
|
-
max_tokens: this.maxTokens,
|
|
202
|
-
}),
|
|
208
|
+
body: JSON.stringify(body),
|
|
203
209
|
});
|
|
204
210
|
if (!res.ok) {
|
|
205
211
|
const body = await res.text().catch(() => "");
|
package/src/cli/commands/auth.js
CHANGED
|
@@ -43,12 +43,43 @@ function showCurrentConfig(config) {
|
|
|
43
43
|
["Setting", "Value"],
|
|
44
44
|
[
|
|
45
45
|
["Provider", info.provider || "none"],
|
|
46
|
-
["Model", info.model || "
|
|
46
|
+
["Model", info.model || "(proxy default)"],
|
|
47
47
|
["Base URL", info.baseUrl || "(default)"],
|
|
48
|
-
[
|
|
48
|
+
[
|
|
49
|
+
"API Key",
|
|
50
|
+
info.hasApiKey ? `\u2713 ${info.apiKeyPreview}` : "\u2717 missing",
|
|
51
|
+
],
|
|
52
|
+
["Auth Header", engine.authHeader || "(none)"],
|
|
53
|
+
[
|
|
54
|
+
"Key Source",
|
|
55
|
+
ai.apiKey
|
|
56
|
+
? "config (apiKey)"
|
|
57
|
+
: ai.apiKeyEnv
|
|
58
|
+
? `env ($${ai.apiKeyEnv})`
|
|
59
|
+
: "auto-detect",
|
|
60
|
+
],
|
|
61
|
+
["Available", engine.isAvailable() ? "\u2713 yes" : "\u2717 no"],
|
|
49
62
|
],
|
|
50
63
|
);
|
|
51
64
|
logger.blank();
|
|
65
|
+
|
|
66
|
+
if (!engine.isAvailable()) {
|
|
67
|
+
logger.warn("AI is not available. To fix:");
|
|
68
|
+
if (info.provider === "proxy") {
|
|
69
|
+
logger.dim(" Option 1: Set apiKey directly in qabot.config.json:");
|
|
70
|
+
logger.dim(' "ai": { "apiKey": "your-key", ... }');
|
|
71
|
+
logger.dim(
|
|
72
|
+
` Option 2: Set env var: export ${ai.apiKeyEnv || "PROXY_API_KEY"}=your-key`,
|
|
73
|
+
);
|
|
74
|
+
logger.dim(
|
|
75
|
+
" Option 3: Remove auth requirement if proxy doesn't need it:",
|
|
76
|
+
);
|
|
77
|
+
logger.dim(' "ai": { "authHeader": null, ... }');
|
|
78
|
+
} else {
|
|
79
|
+
logger.dim(" Run: qabot auth");
|
|
80
|
+
}
|
|
81
|
+
logger.blank();
|
|
82
|
+
}
|
|
52
83
|
}
|
|
53
84
|
|
|
54
85
|
async function testConnection(config) {
|
|
@@ -60,7 +91,17 @@ async function testConnection(config) {
|
|
|
60
91
|
return;
|
|
61
92
|
}
|
|
62
93
|
|
|
94
|
+
const info = engine.getProviderInfo();
|
|
63
95
|
logger.info(`Testing connection to ${chalk.bold(engine.provider)}...`);
|
|
96
|
+
logger.blank();
|
|
97
|
+
logger.dim(` Provider: ${info.provider}`);
|
|
98
|
+
logger.dim(` Model: ${info.model || "(none)"}`);
|
|
99
|
+
logger.dim(` Base URL: ${info.baseUrl}`);
|
|
100
|
+
logger.dim(
|
|
101
|
+
` API Key: ${info.hasApiKey ? info.apiKeyPreview : "(none)"}`,
|
|
102
|
+
);
|
|
103
|
+
logger.dim(` Auth Header: ${engine.authHeader || "(none)"}`);
|
|
104
|
+
logger.blank();
|
|
64
105
|
|
|
65
106
|
try {
|
|
66
107
|
const response = await engine.complete("Reply with exactly: QABOT_OK");
|
|
@@ -76,6 +117,30 @@ async function testConnection(config) {
|
|
|
76
117
|
}
|
|
77
118
|
} catch (err) {
|
|
78
119
|
logger.error(`Connection failed: ${err.message}`);
|
|
120
|
+
logger.blank();
|
|
121
|
+
logger.info("Troubleshooting:");
|
|
122
|
+
if (err.message.includes("401") || err.message.includes("auth")) {
|
|
123
|
+
logger.dim(" 1. Check API key is correct");
|
|
124
|
+
logger.dim(
|
|
125
|
+
` 2. Verify env var: echo $${ai.apiKeyEnv || "PROXY_API_KEY"}`,
|
|
126
|
+
);
|
|
127
|
+
logger.dim(" 3. Or set apiKey directly in qabot.config.json:");
|
|
128
|
+
logger.dim(' "ai": { "apiKey": "your-key-here", ... }');
|
|
129
|
+
}
|
|
130
|
+
if (
|
|
131
|
+
err.message.includes("ECONNREFUSED") ||
|
|
132
|
+
err.message.includes("connect")
|
|
133
|
+
) {
|
|
134
|
+
logger.dim(` 1. Check server is running at ${info.baseUrl}`);
|
|
135
|
+
logger.dim(" 2. Verify URL is correct in qabot.config.json");
|
|
136
|
+
}
|
|
137
|
+
if (err.message.includes("500")) {
|
|
138
|
+
logger.dim(" 1. Server error — check proxy/LLM server logs");
|
|
139
|
+
logger.dim(" 2. Model name may be wrong — verify ai.model in config");
|
|
140
|
+
logger.dim(
|
|
141
|
+
` 3. Try: curl -X POST ${info.baseUrl} -H "Content-Type: application/json" -d '{"messages":[{"role":"user","content":"hi"}]}'`,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
79
144
|
}
|
|
80
145
|
}
|
|
81
146
|
|
|
@@ -147,7 +212,7 @@ async function interactiveSetup(projectDir, config, isEmpty) {
|
|
|
147
212
|
]);
|
|
148
213
|
|
|
149
214
|
aiConfig.baseUrl = proxyAnswers.baseUrl;
|
|
150
|
-
|
|
215
|
+
aiConfig.model = proxyAnswers.model || "";
|
|
151
216
|
|
|
152
217
|
if (proxyAnswers.authMethod === "bearer") {
|
|
153
218
|
aiConfig.authHeader = "Authorization";
|
|
@@ -168,11 +233,8 @@ async function interactiveSetup(projectDir, config, isEmpty) {
|
|
|
168
233
|
}
|
|
169
234
|
|
|
170
235
|
if (proxyAnswers.apiKey) {
|
|
236
|
+
aiConfig.apiKey = proxyAnswers.apiKey;
|
|
171
237
|
aiConfig.apiKeyEnv = "PROXY_API_KEY";
|
|
172
|
-
logger.blank();
|
|
173
|
-
logger.warn("Store your API key as an environment variable:");
|
|
174
|
-
logger.dim(` export PROXY_API_KEY="${proxyAnswers.apiKey}"`);
|
|
175
|
-
logger.dim(" Or add to .env file: PROXY_API_KEY=your-key");
|
|
176
238
|
}
|
|
177
239
|
} else if (provider === "ollama") {
|
|
178
240
|
const { baseUrl, model } = await enquirer.prompt([
|
|
@@ -218,12 +280,8 @@ async function interactiveSetup(projectDir, config, isEmpty) {
|
|
|
218
280
|
|
|
219
281
|
aiConfig.model = answers.model;
|
|
220
282
|
aiConfig.apiKeyEnv = envName;
|
|
221
|
-
|
|
222
283
|
if (answers.apiKey) {
|
|
223
|
-
|
|
224
|
-
logger.warn("Store your API key as an environment variable:");
|
|
225
|
-
logger.dim(` export ${envName}="${answers.apiKey}"`);
|
|
226
|
-
logger.dim(` Or add to .env file: ${envName}=your-key`);
|
|
284
|
+
aiConfig.apiKey = answers.apiKey;
|
|
227
285
|
}
|
|
228
286
|
}
|
|
229
287
|
|
package/src/core/constants.js
CHANGED