codeep 1.0.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/LICENSE +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { config, getApiKey } from '../config/index';
|
|
2
|
+
import { withRetry, isNetworkError, isTimeoutError } from '../utils/retry';
|
|
3
|
+
import { getProvider, getProviderBaseUrl, getProviderAuthHeader } from '../config/providers';
|
|
4
|
+
import { logApiRequest, logApiResponse } from '../utils/logger';
|
|
5
|
+
// Error messages by language
|
|
6
|
+
const ERROR_MESSAGES = {
|
|
7
|
+
en: {
|
|
8
|
+
noInternet: 'No internet connection. Please check your network.',
|
|
9
|
+
timeout: 'Request timed out. Please try again.',
|
|
10
|
+
retrying: 'Connection failed, retrying...',
|
|
11
|
+
apiError: 'API error',
|
|
12
|
+
},
|
|
13
|
+
hr: {
|
|
14
|
+
noInternet: 'Nema internet konekcije. Provjerite mrežu.',
|
|
15
|
+
timeout: 'Zahtjev je istekao. Pokušajte ponovo.',
|
|
16
|
+
retrying: 'Konekcija nije uspjela, pokušavam ponovo...',
|
|
17
|
+
apiError: 'API greška',
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
function getErrorMessage(key) {
|
|
21
|
+
const lang = config.get('language');
|
|
22
|
+
const messages = ERROR_MESSAGES[lang] || ERROR_MESSAGES['en'];
|
|
23
|
+
return messages[key] || ERROR_MESSAGES['en'][key];
|
|
24
|
+
}
|
|
25
|
+
// Store project context for use in system prompt
|
|
26
|
+
let currentProjectContext = null;
|
|
27
|
+
export function setProjectContext(ctx) {
|
|
28
|
+
currentProjectContext = ctx;
|
|
29
|
+
}
|
|
30
|
+
export async function chat(message, history = [], onChunk, onRetry, projectContext, abortSignal) {
|
|
31
|
+
// Update project context if provided
|
|
32
|
+
if (projectContext !== undefined) {
|
|
33
|
+
currentProjectContext = projectContext;
|
|
34
|
+
}
|
|
35
|
+
const protocol = config.get('protocol');
|
|
36
|
+
const model = config.get('model');
|
|
37
|
+
const apiKey = getApiKey();
|
|
38
|
+
const providerId = config.get('provider');
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
throw new Error('API key not configured');
|
|
41
|
+
}
|
|
42
|
+
// Log API request
|
|
43
|
+
logApiRequest(providerId, model, history.length + 1);
|
|
44
|
+
const chatFn = protocol === 'anthropic'
|
|
45
|
+
? () => chatAnthropic(message, history, model, apiKey, onChunk, abortSignal)
|
|
46
|
+
: () => chatOpenAI(message, history, model, apiKey, onChunk, abortSignal);
|
|
47
|
+
try {
|
|
48
|
+
const response = await withRetry(chatFn, {
|
|
49
|
+
maxAttempts: 3,
|
|
50
|
+
baseDelay: 1000,
|
|
51
|
+
onRetry: (attempt, error) => {
|
|
52
|
+
if (onRetry) {
|
|
53
|
+
onRetry(attempt);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
shouldRetry: (error) => {
|
|
57
|
+
// Don't retry on user abort
|
|
58
|
+
if (error.name === 'AbortError')
|
|
59
|
+
return false;
|
|
60
|
+
// Don't retry on 4xx client errors (except 429 rate limit)
|
|
61
|
+
if (error.status >= 400 && error.status < 500 && error.status !== 429)
|
|
62
|
+
return false;
|
|
63
|
+
// Retry on network errors, timeouts, 5xx, and rate limits
|
|
64
|
+
return true;
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
// Log successful response
|
|
68
|
+
logApiResponse(providerId, true, response.length);
|
|
69
|
+
return response;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const err = error;
|
|
73
|
+
// Don't log or throw if request was aborted by user
|
|
74
|
+
if (err.name === 'AbortError' || err.message?.includes('aborted')) {
|
|
75
|
+
throw error; // Re-throw abort errors silently
|
|
76
|
+
}
|
|
77
|
+
// Log error
|
|
78
|
+
logApiResponse(providerId, false, undefined, err.message);
|
|
79
|
+
// Translate errors to user-friendly messages
|
|
80
|
+
if (isNetworkError(error)) {
|
|
81
|
+
throw new Error(getErrorMessage('noInternet'));
|
|
82
|
+
}
|
|
83
|
+
if (isTimeoutError(error)) {
|
|
84
|
+
throw new Error(getErrorMessage('timeout'));
|
|
85
|
+
}
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const LANGUAGE_NAMES = {
|
|
90
|
+
'en': 'English',
|
|
91
|
+
'zh': 'Chinese (中文)',
|
|
92
|
+
'es': 'Spanish (Español)',
|
|
93
|
+
'hi': 'Hindi (हिन्दी)',
|
|
94
|
+
'ar': 'Arabic (العربية)',
|
|
95
|
+
'pt': 'Portuguese (Português)',
|
|
96
|
+
'fr': 'French (Français)',
|
|
97
|
+
'de': 'German (Deutsch)',
|
|
98
|
+
'ja': 'Japanese (日本語)',
|
|
99
|
+
'ru': 'Russian (Русский)',
|
|
100
|
+
'hr': 'Croatian (Hrvatski)',
|
|
101
|
+
};
|
|
102
|
+
function getSystemPrompt() {
|
|
103
|
+
const language = config.get('language');
|
|
104
|
+
let basePrompt;
|
|
105
|
+
if (language === 'auto') {
|
|
106
|
+
basePrompt = `You are a helpful AI coding assistant. Always respond in the same language as the user's message. Detect the language of the user's input and reply in that same language.`;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
const langName = LANGUAGE_NAMES[language] || 'English';
|
|
110
|
+
basePrompt = `You are a helpful AI coding assistant. Always respond in ${langName}, regardless of what language the user writes in.`;
|
|
111
|
+
}
|
|
112
|
+
// Important: This is CHAT mode, not agent mode
|
|
113
|
+
// The model should NOT pretend to execute tools or create files
|
|
114
|
+
basePrompt += `
|
|
115
|
+
|
|
116
|
+
IMPORTANT: You are in CHAT mode, NOT agent mode. You do NOT have the ability to:
|
|
117
|
+
- Create, edit, or delete files directly
|
|
118
|
+
- Execute shell commands
|
|
119
|
+
- Use tools or tool_calls
|
|
120
|
+
|
|
121
|
+
If the conversation history contains messages about file creation or tool execution, those were from a previous agent session. In chat mode, you can only provide advice, explanations, and code suggestions that the user must manually apply.`;
|
|
122
|
+
// Add project context if available
|
|
123
|
+
if (currentProjectContext) {
|
|
124
|
+
const writeInfo = currentProjectContext.hasWriteAccess
|
|
125
|
+
? `
|
|
126
|
+
|
|
127
|
+
**Write Access:** ENABLED - You can suggest file modifications. Format them as:
|
|
128
|
+
\`\`\`filepath:path/to/file.ts
|
|
129
|
+
// modified code here
|
|
130
|
+
\`\`\`
|
|
131
|
+
The user will review and approve changes before they are applied.`
|
|
132
|
+
: `
|
|
133
|
+
|
|
134
|
+
**Write Access:** READ-ONLY - You can analyze code but cannot suggest file modifications.`;
|
|
135
|
+
const projectInfo = `
|
|
136
|
+
|
|
137
|
+
## Project Context
|
|
138
|
+
You are working with a ${currentProjectContext.type} project called "${currentProjectContext.name}".
|
|
139
|
+
|
|
140
|
+
**Project Structure:**
|
|
141
|
+
\`\`\`
|
|
142
|
+
${currentProjectContext.structure}
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
**Key Files:** ${currentProjectContext.keyFiles.join(', ')}${writeInfo}
|
|
146
|
+
|
|
147
|
+
When the user mentions a file path, the file content will be automatically attached to their message.
|
|
148
|
+
You can analyze, explain, or suggest improvements to the code.`;
|
|
149
|
+
return basePrompt + projectInfo;
|
|
150
|
+
}
|
|
151
|
+
return basePrompt;
|
|
152
|
+
}
|
|
153
|
+
async function chatOpenAI(message, history, model, apiKey, onChunk, abortSignal) {
|
|
154
|
+
const messages = [
|
|
155
|
+
{ role: 'system', content: getSystemPrompt() },
|
|
156
|
+
...history,
|
|
157
|
+
{ role: 'user', content: message },
|
|
158
|
+
];
|
|
159
|
+
const stream = Boolean(onChunk);
|
|
160
|
+
const timeout = config.get('apiTimeout');
|
|
161
|
+
const temperature = config.get('temperature');
|
|
162
|
+
const maxTokens = config.get('maxTokens');
|
|
163
|
+
// Get provider-specific URL and auth
|
|
164
|
+
const providerId = config.get('provider');
|
|
165
|
+
const baseUrl = getProviderBaseUrl(providerId, 'openai');
|
|
166
|
+
const authHeader = getProviderAuthHeader(providerId, 'openai');
|
|
167
|
+
if (!baseUrl) {
|
|
168
|
+
throw new Error(`Provider ${providerId} does not support OpenAI protocol`);
|
|
169
|
+
}
|
|
170
|
+
// Create abort controller for timeout or use provided one
|
|
171
|
+
const controller = abortSignal ? new AbortController() : new AbortController();
|
|
172
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
173
|
+
// Listen to external abort signal if provided
|
|
174
|
+
if (abortSignal) {
|
|
175
|
+
abortSignal.addEventListener('abort', () => controller.abort());
|
|
176
|
+
}
|
|
177
|
+
// Build headers based on auth type
|
|
178
|
+
const headers = {
|
|
179
|
+
'Content-Type': 'application/json',
|
|
180
|
+
};
|
|
181
|
+
if (authHeader === 'Bearer') {
|
|
182
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
headers['x-api-key'] = apiKey;
|
|
186
|
+
}
|
|
187
|
+
try {
|
|
188
|
+
const response = await fetch(`${baseUrl}/chat/completions`, {
|
|
189
|
+
method: 'POST',
|
|
190
|
+
headers,
|
|
191
|
+
body: JSON.stringify({
|
|
192
|
+
model,
|
|
193
|
+
messages,
|
|
194
|
+
stream,
|
|
195
|
+
temperature,
|
|
196
|
+
max_tokens: maxTokens,
|
|
197
|
+
}),
|
|
198
|
+
signal: controller.signal,
|
|
199
|
+
});
|
|
200
|
+
if (!response.ok) {
|
|
201
|
+
const error = await response.text();
|
|
202
|
+
const err = new Error(`${getErrorMessage('apiError')}: ${response.status} - ${error}`);
|
|
203
|
+
err.status = response.status;
|
|
204
|
+
throw err;
|
|
205
|
+
}
|
|
206
|
+
if (stream && response.body) {
|
|
207
|
+
return handleOpenAIStream(response.body, onChunk);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const data = await response.json();
|
|
211
|
+
const content = data.choices[0]?.message?.content || '';
|
|
212
|
+
return stripThinkTags(content);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
clearTimeout(timeoutId);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
async function handleOpenAIStream(body, onChunk) {
|
|
220
|
+
const reader = body.getReader();
|
|
221
|
+
const decoder = new TextDecoder();
|
|
222
|
+
const chunks = [];
|
|
223
|
+
let buffer = '';
|
|
224
|
+
while (true) {
|
|
225
|
+
const { done, value } = await reader.read();
|
|
226
|
+
if (done)
|
|
227
|
+
break;
|
|
228
|
+
buffer += decoder.decode(value, { stream: true });
|
|
229
|
+
const lines = buffer.split('\n');
|
|
230
|
+
buffer = lines.pop() || '';
|
|
231
|
+
for (const line of lines) {
|
|
232
|
+
if (line.startsWith('data: ')) {
|
|
233
|
+
const data = line.slice(6);
|
|
234
|
+
if (data === '[DONE]')
|
|
235
|
+
continue;
|
|
236
|
+
try {
|
|
237
|
+
const parsed = JSON.parse(data);
|
|
238
|
+
const content = parsed.choices?.[0]?.delta?.content;
|
|
239
|
+
if (content) {
|
|
240
|
+
chunks.push(content);
|
|
241
|
+
onChunk(content);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
// Ignore parse errors
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// Strip <think> tags from MiniMax and other providers
|
|
251
|
+
return stripThinkTags(chunks.join(''));
|
|
252
|
+
}
|
|
253
|
+
async function chatAnthropic(message, history, model, apiKey, onChunk, abortSignal) {
|
|
254
|
+
const systemPrompt = getSystemPrompt();
|
|
255
|
+
const messages = [
|
|
256
|
+
{ role: 'user', content: systemPrompt },
|
|
257
|
+
{ role: 'assistant', content: 'Understood. I will follow your language instructions.' },
|
|
258
|
+
...history,
|
|
259
|
+
{ role: 'user', content: message },
|
|
260
|
+
];
|
|
261
|
+
const stream = Boolean(onChunk);
|
|
262
|
+
const timeout = config.get('apiTimeout');
|
|
263
|
+
const temperature = config.get('temperature');
|
|
264
|
+
const maxTokens = config.get('maxTokens');
|
|
265
|
+
// Get provider-specific URL and auth
|
|
266
|
+
const providerId = config.get('provider');
|
|
267
|
+
const baseUrl = getProviderBaseUrl(providerId, 'anthropic');
|
|
268
|
+
const authHeader = getProviderAuthHeader(providerId, 'anthropic');
|
|
269
|
+
if (!baseUrl) {
|
|
270
|
+
throw new Error(`Provider ${providerId} does not support Anthropic protocol`);
|
|
271
|
+
}
|
|
272
|
+
// Create abort controller for timeout or use provided one
|
|
273
|
+
const controller = new AbortController();
|
|
274
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
275
|
+
// Listen to external abort signal if provided
|
|
276
|
+
if (abortSignal) {
|
|
277
|
+
abortSignal.addEventListener('abort', () => controller.abort());
|
|
278
|
+
}
|
|
279
|
+
// Build headers based on auth type
|
|
280
|
+
const headers = {
|
|
281
|
+
'Content-Type': 'application/json',
|
|
282
|
+
'anthropic-version': '2023-06-01',
|
|
283
|
+
};
|
|
284
|
+
if (authHeader === 'Bearer') {
|
|
285
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
headers['x-api-key'] = apiKey;
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const response = await fetch(`${baseUrl}/v1/messages`, {
|
|
292
|
+
method: 'POST',
|
|
293
|
+
headers,
|
|
294
|
+
body: JSON.stringify({
|
|
295
|
+
model,
|
|
296
|
+
messages,
|
|
297
|
+
max_tokens: maxTokens,
|
|
298
|
+
temperature,
|
|
299
|
+
stream,
|
|
300
|
+
}),
|
|
301
|
+
signal: controller.signal,
|
|
302
|
+
});
|
|
303
|
+
if (!response.ok) {
|
|
304
|
+
const error = await response.text();
|
|
305
|
+
const err = new Error(`${getErrorMessage('apiError')}: ${response.status} - ${error}`);
|
|
306
|
+
err.status = response.status;
|
|
307
|
+
throw err;
|
|
308
|
+
}
|
|
309
|
+
if (stream && response.body) {
|
|
310
|
+
return handleAnthropicStream(response.body, onChunk);
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
const data = await response.json();
|
|
314
|
+
const content = data.content[0]?.text || '';
|
|
315
|
+
return stripThinkTags(content);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
finally {
|
|
319
|
+
clearTimeout(timeoutId);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Remove <think>...</think> tags from response
|
|
324
|
+
* Some providers (MiniMax, DeepSeek) include internal reasoning in these tags
|
|
325
|
+
* which should not be shown to users
|
|
326
|
+
*/
|
|
327
|
+
function stripThinkTags(text) {
|
|
328
|
+
return text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
|
329
|
+
}
|
|
330
|
+
async function handleAnthropicStream(body, onChunk) {
|
|
331
|
+
const reader = body.getReader();
|
|
332
|
+
const decoder = new TextDecoder();
|
|
333
|
+
const chunks = [];
|
|
334
|
+
let buffer = '';
|
|
335
|
+
while (true) {
|
|
336
|
+
const { done, value } = await reader.read();
|
|
337
|
+
if (done)
|
|
338
|
+
break;
|
|
339
|
+
buffer += decoder.decode(value, { stream: true });
|
|
340
|
+
const lines = buffer.split('\n');
|
|
341
|
+
buffer = lines.pop() || '';
|
|
342
|
+
for (const line of lines) {
|
|
343
|
+
if (line.startsWith('data: ')) {
|
|
344
|
+
const data = line.slice(6);
|
|
345
|
+
try {
|
|
346
|
+
const parsed = JSON.parse(data);
|
|
347
|
+
if (parsed.type === 'content_block_delta') {
|
|
348
|
+
const text = parsed.delta?.text;
|
|
349
|
+
if (text) {
|
|
350
|
+
chunks.push(text);
|
|
351
|
+
onChunk(text);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
catch {
|
|
356
|
+
// Ignore parse errors
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
// Strip <think> tags from MiniMax responses
|
|
362
|
+
return stripThinkTags(chunks.join(''));
|
|
363
|
+
}
|
|
364
|
+
export async function validateApiKey(apiKey, providerId) {
|
|
365
|
+
const provider = providerId || config.get('provider');
|
|
366
|
+
const providerConfig = getProvider(provider);
|
|
367
|
+
if (!providerConfig) {
|
|
368
|
+
return { valid: false, error: `Unknown provider: ${provider}` };
|
|
369
|
+
}
|
|
370
|
+
// Determine which protocol to use for validation
|
|
371
|
+
const protocol = providerConfig.defaultProtocol;
|
|
372
|
+
const baseUrl = getProviderBaseUrl(provider, protocol);
|
|
373
|
+
const authHeader = getProviderAuthHeader(provider, protocol);
|
|
374
|
+
const model = providerConfig.defaultModel;
|
|
375
|
+
if (!baseUrl) {
|
|
376
|
+
return { valid: false, error: `No endpoint configured for ${provider}` };
|
|
377
|
+
}
|
|
378
|
+
// Build headers
|
|
379
|
+
const headers = {
|
|
380
|
+
'Content-Type': 'application/json',
|
|
381
|
+
};
|
|
382
|
+
if (authHeader === 'Bearer') {
|
|
383
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
headers['x-api-key'] = apiKey;
|
|
387
|
+
}
|
|
388
|
+
if (protocol === 'anthropic') {
|
|
389
|
+
headers['anthropic-version'] = '2023-06-01';
|
|
390
|
+
}
|
|
391
|
+
// Build request based on protocol
|
|
392
|
+
const endpoint = protocol === 'openai' ? '/chat/completions' : '/v1/messages';
|
|
393
|
+
const body = protocol === 'openai'
|
|
394
|
+
? {
|
|
395
|
+
model,
|
|
396
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
397
|
+
max_tokens: 5,
|
|
398
|
+
}
|
|
399
|
+
: {
|
|
400
|
+
model,
|
|
401
|
+
messages: [{ role: 'user', content: 'hi' }],
|
|
402
|
+
max_tokens: 5,
|
|
403
|
+
};
|
|
404
|
+
try {
|
|
405
|
+
const response = await fetch(`${baseUrl}${endpoint}`, {
|
|
406
|
+
method: 'POST',
|
|
407
|
+
headers,
|
|
408
|
+
body: JSON.stringify(body),
|
|
409
|
+
});
|
|
410
|
+
if (response.ok) {
|
|
411
|
+
return { valid: true };
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
const errorText = await response.text();
|
|
415
|
+
return { valid: false, error: `${response.status}: ${errorText}` };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch (err) {
|
|
419
|
+
return { valid: false, error: err.message };
|
|
420
|
+
}
|
|
421
|
+
}
|
package/dist/app.d.ts
ADDED