codeep 2.1.0 → 2.1.1
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/README.md +1 -0
- package/dist/api/index.js +6 -13
- package/dist/config/index.d.ts +21 -0
- package/dist/config/index.js +55 -1
- package/dist/config/providers.js +19 -0
- package/dist/renderer/commands.js +17 -0
- package/dist/renderer/components/Settings.js +6 -0
- package/dist/renderer/main.js +8 -0
- package/dist/utils/agentChat.js +4 -12
- package/dist/utils/taskPlanner.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -56,6 +56,7 @@ custom slash commands, lifecycle hooks, checkpoints, `/cost`,
|
|
|
56
56
|
- **MiniMax** — MiniMax M2.7, M2.5, M2.1, M2 — Coding Plan & pay-per-use API (international & China)
|
|
57
57
|
- **Ollama** — Run any model locally or on a remote server, no API key required. Models are fetched dynamically from your Ollama instance.
|
|
58
58
|
- **OpenRouter** — One key, 100+ models from Anthropic, OpenAI, Google, Meta, Mistral, DeepSeek, Qwen, xAI and more. Per-call cost reported directly by OpenRouter (matches their dashboard exactly). Use `openrouter/auto` to let OpenRouter pick the best model. Tune routing with `/openrouter prefer|ignore|fallbacks|privacy`.
|
|
59
|
+
- **Custom (OpenAI-compatible)** — Point Codeep at any self-hosted or proxied OpenAI-compatible endpoint (vLLM, LiteLLM, LM Studio, text-generation-webui). Set the base URL in `/settings` → **Custom Base URL** (config key `customBaseUrl`, e.g. `http://host:8000/v1`), then pick your model with `/model` (fetched from the server's `/models`). No API key required unless your endpoint enforces one. The `openai` provider also honors the `OPENAI_BASE_URL` env var for proxies that serve `gpt-*` model names.
|
|
59
60
|
- Switch between providers with `/provider`
|
|
60
61
|
- Configure different API keys per provider
|
|
61
62
|
- Both OpenAI-compatible and Anthropic API protocols supported
|
package/dist/api/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as http from 'node:http';
|
|
2
2
|
import * as https from 'node:https';
|
|
3
|
-
import { config, getApiKey } from '../config/index.js';
|
|
3
|
+
import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
|
|
4
4
|
import { withRetry, isNetworkError } from '../utils/retry.js';
|
|
5
5
|
import { getProvider, getProviderBaseUrl, getProviderAuthHeader, usesMaxCompletionTokens, requiresDefaultTemperature } from '../config/providers.js';
|
|
6
6
|
import { logApiRequest, logApiResponse } from '../utils/logger.js';
|
|
@@ -336,14 +336,11 @@ async function chatOpenAI(message, history, model, apiKey, onChunk, abortSignal)
|
|
|
336
336
|
const timeout = config.get('apiTimeout');
|
|
337
337
|
const temperature = config.get('temperature');
|
|
338
338
|
const maxTokens = config.get('maxTokens');
|
|
339
|
-
// Get provider-specific URL and auth
|
|
339
|
+
// Get provider-specific URL and auth. resolveBaseUrl applies user
|
|
340
|
+
// overrides: Ollama (ollamaUrl), Custom (customBaseUrl), and OpenAI
|
|
341
|
+
// (OPENAI_BASE_URL env) — so self-hosted / OpenAI-compatible endpoints work.
|
|
340
342
|
const providerId = config.get('provider');
|
|
341
|
-
let baseUrl =
|
|
342
|
-
// For Ollama, use the configured URL (can't use lazy require in ESM providers.ts)
|
|
343
|
-
if (providerId === 'ollama') {
|
|
344
|
-
const ollamaUrl = (config.get('ollamaUrl') || 'http://localhost:11434').replace(/\/$/, '');
|
|
345
|
-
baseUrl = `${ollamaUrl}/v1`;
|
|
346
|
-
}
|
|
343
|
+
let baseUrl = resolveBaseUrl(providerId, 'openai');
|
|
347
344
|
const authHeader = getProviderAuthHeader(providerId, 'openai');
|
|
348
345
|
const useCompletionTokens = usesMaxCompletionTokens(providerId);
|
|
349
346
|
const omitTemperature = requiresDefaultTemperature(providerId);
|
|
@@ -747,11 +744,7 @@ export async function validateApiKey(apiKey, providerId) {
|
|
|
747
744
|
}
|
|
748
745
|
// Determine which protocol to use for validation
|
|
749
746
|
const protocol = providerConfig.defaultProtocol;
|
|
750
|
-
|
|
751
|
-
if (provider === 'ollama' && protocol === 'openai') {
|
|
752
|
-
const ollamaUrl = (config.get('ollamaUrl') || 'http://localhost:11434').replace(/\/$/, '');
|
|
753
|
-
baseUrl = `${ollamaUrl}/v1`;
|
|
754
|
-
}
|
|
747
|
+
const baseUrl = resolveBaseUrl(provider, protocol);
|
|
755
748
|
const authHeader = getProviderAuthHeader(provider, protocol);
|
|
756
749
|
const model = providerConfig.defaultModel;
|
|
757
750
|
if (!baseUrl) {
|
package/dist/config/index.d.ts
CHANGED
|
@@ -35,6 +35,7 @@ interface ConfigSchema {
|
|
|
35
35
|
rateLimitCommands: number;
|
|
36
36
|
agentMode: AgentMode;
|
|
37
37
|
ollamaUrl: string;
|
|
38
|
+
customBaseUrl: string;
|
|
38
39
|
agentConfirmation: 'always' | 'dangerous' | 'never';
|
|
39
40
|
agentConfirmDeleteFile: boolean;
|
|
40
41
|
agentConfirmExecuteCommand: boolean;
|
|
@@ -134,6 +135,26 @@ export declare function fetchOllamaModels(baseUrl?: string): Promise<{
|
|
|
134
135
|
name: string;
|
|
135
136
|
description: string;
|
|
136
137
|
}[] | null>;
|
|
138
|
+
/**
|
|
139
|
+
* Fetch the model list from an OpenAI-compatible server's `/models`
|
|
140
|
+
* endpoint (vLLM, LiteLLM, LM Studio, etc.). `baseUrl` is the full base
|
|
141
|
+
* (e.g. http://host:8000/v1). Returns null on error.
|
|
142
|
+
*/
|
|
143
|
+
export declare function fetchOpenAiCompatibleModels(baseUrl: string, apiKey?: string): Promise<{
|
|
144
|
+
id: string;
|
|
145
|
+
name: string;
|
|
146
|
+
description: string;
|
|
147
|
+
}[] | null>;
|
|
148
|
+
/**
|
|
149
|
+
* Resolve the effective OpenAI-protocol base URL for a provider, honoring
|
|
150
|
+
* user overrides the static provider table can't express:
|
|
151
|
+
* - ollama → configured `ollamaUrl` + /v1
|
|
152
|
+
* - custom → configured `customBaseUrl` (full base, e.g. http://host:8000/v1)
|
|
153
|
+
* - openai → the OPENAI_BASE_URL env var, if set (OpenAI-SDK convention)
|
|
154
|
+
* Falls back to the provider's hardcoded base URL. Only the `openai`
|
|
155
|
+
* protocol takes overrides; the anthropic protocol uses the static table.
|
|
156
|
+
*/
|
|
157
|
+
export declare function resolveBaseUrl(providerId: string, protocol: 'openai' | 'anthropic'): string | null;
|
|
137
158
|
export { PROVIDERS } from './providers';
|
|
138
159
|
export declare function getCurrentSessionId(): string;
|
|
139
160
|
export declare function startNewSession(): string;
|
package/dist/config/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import Conf from 'conf';
|
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, unlinkSync, statSync } from 'fs';
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { randomUUID } from 'crypto';
|
|
5
|
-
import { PROVIDERS, getProvider } from './providers.js';
|
|
5
|
+
import { PROVIDERS, getProvider, getProviderBaseUrl } from './providers.js';
|
|
6
6
|
import { logSession } from '../utils/logger.js';
|
|
7
7
|
// We'll initialize GLOBAL_SESSIONS_DIR after config is created (to use config.path)
|
|
8
8
|
/**
|
|
@@ -144,6 +144,7 @@ function createConfig() {
|
|
|
144
144
|
model: 'glm-5.1',
|
|
145
145
|
agentMode: 'on',
|
|
146
146
|
ollamaUrl: 'http://localhost:11434',
|
|
147
|
+
customBaseUrl: '',
|
|
147
148
|
agentConfirmation: 'dangerous',
|
|
148
149
|
agentConfirmDeleteFile: true,
|
|
149
150
|
agentConfirmExecuteCommand: true,
|
|
@@ -531,6 +532,59 @@ export async function fetchOllamaModels(baseUrl) {
|
|
|
531
532
|
return null;
|
|
532
533
|
}
|
|
533
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* Fetch the model list from an OpenAI-compatible server's `/models`
|
|
537
|
+
* endpoint (vLLM, LiteLLM, LM Studio, etc.). `baseUrl` is the full base
|
|
538
|
+
* (e.g. http://host:8000/v1). Returns null on error.
|
|
539
|
+
*/
|
|
540
|
+
export async function fetchOpenAiCompatibleModels(baseUrl, apiKey) {
|
|
541
|
+
const base = (baseUrl || '').trim().replace(/\/+$/, '');
|
|
542
|
+
if (!base)
|
|
543
|
+
return null;
|
|
544
|
+
try {
|
|
545
|
+
const headers = {};
|
|
546
|
+
if (apiKey)
|
|
547
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
548
|
+
const res = await fetch(`${base}/models`, { headers, signal: AbortSignal.timeout(5000) });
|
|
549
|
+
if (!res.ok)
|
|
550
|
+
return null;
|
|
551
|
+
const data = await res.json();
|
|
552
|
+
if (!Array.isArray(data.data))
|
|
553
|
+
return null;
|
|
554
|
+
return data.data.map(m => ({ id: m.id, name: m.id, description: '' }));
|
|
555
|
+
}
|
|
556
|
+
catch {
|
|
557
|
+
return null;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/**
|
|
561
|
+
* Resolve the effective OpenAI-protocol base URL for a provider, honoring
|
|
562
|
+
* user overrides the static provider table can't express:
|
|
563
|
+
* - ollama → configured `ollamaUrl` + /v1
|
|
564
|
+
* - custom → configured `customBaseUrl` (full base, e.g. http://host:8000/v1)
|
|
565
|
+
* - openai → the OPENAI_BASE_URL env var, if set (OpenAI-SDK convention)
|
|
566
|
+
* Falls back to the provider's hardcoded base URL. Only the `openai`
|
|
567
|
+
* protocol takes overrides; the anthropic protocol uses the static table.
|
|
568
|
+
*/
|
|
569
|
+
export function resolveBaseUrl(providerId, protocol) {
|
|
570
|
+
const fallback = getProviderBaseUrl(providerId, protocol);
|
|
571
|
+
if (protocol !== 'openai')
|
|
572
|
+
return fallback;
|
|
573
|
+
if (providerId === 'ollama') {
|
|
574
|
+
const u = (config.get('ollamaUrl') || 'http://localhost:11434').replace(/\/+$/, '');
|
|
575
|
+
return `${u}/v1`;
|
|
576
|
+
}
|
|
577
|
+
if (providerId === 'custom') {
|
|
578
|
+
const u = (config.get('customBaseUrl') || '').trim().replace(/\/+$/, '');
|
|
579
|
+
return u || fallback;
|
|
580
|
+
}
|
|
581
|
+
if (providerId === 'openai') {
|
|
582
|
+
const env = (process.env.OPENAI_BASE_URL || '').trim().replace(/\/+$/, '');
|
|
583
|
+
if (env)
|
|
584
|
+
return env;
|
|
585
|
+
}
|
|
586
|
+
return fallback;
|
|
587
|
+
}
|
|
534
588
|
// Re-export PROVIDERS for convenience
|
|
535
589
|
export { PROVIDERS } from './providers.js';
|
|
536
590
|
// Generate unique session ID
|
package/dist/config/providers.js
CHANGED
|
@@ -331,6 +331,24 @@ export const PROVIDERS = {
|
|
|
331
331
|
groupLabel: 'Ollama (local)',
|
|
332
332
|
hint: 'Runs locally — no API key or account needed.',
|
|
333
333
|
},
|
|
334
|
+
'custom': {
|
|
335
|
+
name: 'Custom (OpenAI-compatible)',
|
|
336
|
+
description: 'Any OpenAI-compatible endpoint — vLLM, LiteLLM, LM Studio',
|
|
337
|
+
protocols: {
|
|
338
|
+
openai: {
|
|
339
|
+
baseUrl: 'http://localhost:8000/v1',
|
|
340
|
+
authHeader: 'Bearer',
|
|
341
|
+
supportsNativeTools: true,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
models: [],
|
|
345
|
+
defaultModel: '',
|
|
346
|
+
defaultProtocol: 'openai',
|
|
347
|
+
noApiKey: true, // key optional — sent as Bearer only if you set one
|
|
348
|
+
dynamicModels: true,
|
|
349
|
+
groupLabel: 'Custom',
|
|
350
|
+
hint: 'Point at any OpenAI-compatible server. Set the URL in /settings (Custom Base URL) or the OPENAI_BASE_URL env var, then pick your model with /model.',
|
|
351
|
+
},
|
|
334
352
|
};
|
|
335
353
|
export function getProvider(id) {
|
|
336
354
|
return PROVIDERS[id] || null;
|
|
@@ -354,6 +372,7 @@ const DISPLAY_ORDER = [
|
|
|
354
372
|
'minimax',
|
|
355
373
|
'minimax-api',
|
|
356
374
|
'ollama',
|
|
375
|
+
'custom',
|
|
357
376
|
'z.ai-cn',
|
|
358
377
|
'z.ai-cn-api',
|
|
359
378
|
'minimax-cn',
|
|
@@ -120,6 +120,23 @@ export async function handleCommand(command, args, ctx) {
|
|
|
120
120
|
});
|
|
121
121
|
break;
|
|
122
122
|
}
|
|
123
|
+
if (providerId === 'custom') {
|
|
124
|
+
const base = config.get('customBaseUrl') || 'http://localhost:8000/v1';
|
|
125
|
+
ctx.app.notify(`Fetching models from ${base}…`);
|
|
126
|
+
const { fetchOpenAiCompatibleModels, getApiKey: _getKey } = await import('../config/index.js');
|
|
127
|
+
const models = await fetchOpenAiCompatibleModels(base, _getKey('custom') || undefined);
|
|
128
|
+
if (!models || models.length === 0) {
|
|
129
|
+
ctx.app.notify(`Could not list models from ${base}. Set the base URL in /settings, or set the model directly (config key "model").`);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
const modelItems = models.map(m => ({ key: m.id, label: m.name, description: '' }));
|
|
133
|
+
const currentModel = config.get('model');
|
|
134
|
+
ctx.app.showSelect(`Select Model (${models.length})`, modelItems, currentModel, (item) => {
|
|
135
|
+
config.set('model', item.key);
|
|
136
|
+
ctx.app.notify(`Model: ${item.key}`);
|
|
137
|
+
});
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
123
140
|
const { fetchOllamaModels } = await import('../config/index.js');
|
|
124
141
|
ctx.app.notify('Fetching models from Ollama...');
|
|
125
142
|
const ollamaModels = await fetchOllamaModels();
|
|
@@ -131,6 +131,12 @@ export const SETTINGS = [
|
|
|
131
131
|
getValue: () => config.get('ollamaUrl') || 'http://localhost:11434',
|
|
132
132
|
type: 'text',
|
|
133
133
|
},
|
|
134
|
+
{
|
|
135
|
+
key: 'customBaseUrl',
|
|
136
|
+
label: 'Custom Base URL',
|
|
137
|
+
getValue: () => config.get('customBaseUrl') || '',
|
|
138
|
+
type: 'text',
|
|
139
|
+
},
|
|
134
140
|
{
|
|
135
141
|
key: 'agentApiTimeout',
|
|
136
142
|
label: 'Agent API Timeout (ms)',
|
package/dist/renderer/main.js
CHANGED
|
@@ -252,6 +252,14 @@ async function showLoginFlow() {
|
|
|
252
252
|
else if (event.key === 'enter') {
|
|
253
253
|
selectedProvider = providers[selectedProviderIndex];
|
|
254
254
|
setProvider(selectedProvider.id);
|
|
255
|
+
// Providers that don't need a key (Ollama, Custom OpenAI-compatible)
|
|
256
|
+
// skip the API-key prompt entirely. Configure their endpoint in
|
|
257
|
+
// /settings (Ollama URL / Custom Base URL) once inside the app.
|
|
258
|
+
if (selectedProvider.noApiKey) {
|
|
259
|
+
cleanup();
|
|
260
|
+
resolve('ollama'); // non-null sentinel so the caller proceeds
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
255
263
|
currentStep = 'apikey';
|
|
256
264
|
loginScreen = new LoginScreen(screen, input, {
|
|
257
265
|
providerName: selectedProvider.name,
|
package/dist/utils/agentChat.js
CHANGED
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
*/
|
|
14
14
|
import { existsSync, readFileSync, writeFileSync } from 'fs';
|
|
15
15
|
import { join } from 'path';
|
|
16
|
-
import { config, getApiKey } from '../config/index.js';
|
|
16
|
+
import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
|
|
17
17
|
import { loadProjectIntelligence, generateContextFromIntelligence } from './projectIntelligence.js';
|
|
18
18
|
import { syncProgress, generateProjectId } from './codeepCloud.js';
|
|
19
|
-
import {
|
|
19
|
+
import { getProviderAuthHeader, supportsNativeTools, getEffectiveMaxTokens, usesMaxCompletionTokens, requiresDefaultTemperature, isNoApiKeyProvider } from '../config/providers.js';
|
|
20
20
|
import { recordTokenUsage, extractOpenAIUsage, extractAnthropicUsage } from './tokenTracker.js';
|
|
21
21
|
import { parseOpenAIToolCalls, parseAnthropicToolCalls, parseToolCalls } from './toolParsing.js';
|
|
22
22
|
import { formatToolDefinitions, getOpenAITools, getAnthropicTools } from './tools.js';
|
|
@@ -239,11 +239,7 @@ additionalTools) {
|
|
|
239
239
|
const model = config.get('model');
|
|
240
240
|
const providerId = config.get('provider');
|
|
241
241
|
const apiKey = getApiKey() || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
242
|
-
let baseUrl =
|
|
243
|
-
if (providerId === 'ollama' && protocol === 'openai') {
|
|
244
|
-
const ollamaUrl = (config.get('ollamaUrl') || 'http://localhost:11434').replace(/\/$/, '');
|
|
245
|
-
baseUrl = `${ollamaUrl}/v1`;
|
|
246
|
-
}
|
|
242
|
+
let baseUrl = resolveBaseUrl(providerId, protocol);
|
|
247
243
|
const authHeader = getProviderAuthHeader(providerId, protocol);
|
|
248
244
|
if (!baseUrl)
|
|
249
245
|
throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
|
|
@@ -413,11 +409,7 @@ export async function agentChatFallback(messages, systemPrompt, onChunk, abortSi
|
|
|
413
409
|
const model = config.get('model');
|
|
414
410
|
const providerId = config.get('provider');
|
|
415
411
|
const apiKey = getApiKey() || (isNoApiKeyProvider(providerId) ? 'ollama' : null);
|
|
416
|
-
let baseUrl =
|
|
417
|
-
if (providerId === 'ollama' && protocol === 'openai') {
|
|
418
|
-
const ollamaUrl = (config.get('ollamaUrl') || 'http://localhost:11434').replace(/\/$/, '');
|
|
419
|
-
baseUrl = `${ollamaUrl}/v1`;
|
|
420
|
-
}
|
|
412
|
+
let baseUrl = resolveBaseUrl(providerId, protocol);
|
|
421
413
|
const authHeader = getProviderAuthHeader(providerId, protocol);
|
|
422
414
|
if (!baseUrl)
|
|
423
415
|
throw new Error(`Provider ${providerId} does not support ${protocol} protocol`);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Task Planning - breaks down complex tasks into subtasks
|
|
3
3
|
*/
|
|
4
|
-
import { config, getApiKey } from '../config/index.js';
|
|
5
|
-
import {
|
|
4
|
+
import { config, getApiKey, resolveBaseUrl } from '../config/index.js';
|
|
5
|
+
import { getProviderAuthHeader, requiresDefaultTemperature } from '../config/providers.js';
|
|
6
6
|
/**
|
|
7
7
|
* Ask AI to break down a complex task into subtasks
|
|
8
8
|
*/
|
|
@@ -43,7 +43,7 @@ Break this down into subtasks. Each task = one file or one logical unit. Respond
|
|
|
43
43
|
const protocol = config.get('protocol');
|
|
44
44
|
const provider = config.get('provider');
|
|
45
45
|
const model = config.get('model');
|
|
46
|
-
const baseUrl =
|
|
46
|
+
const baseUrl = resolveBaseUrl(provider, protocol);
|
|
47
47
|
const authHeaderType = getProviderAuthHeader(provider, protocol);
|
|
48
48
|
const messages = [
|
|
49
49
|
{ role: 'user', content: systemPrompt }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codeep",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|