lovecode-ai 0.1.3 → 0.1.4

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.
@@ -0,0 +1,366 @@
1
+ // src/ai/registry.ts
2
+ import chalk2 from "chalk";
3
+
4
+ // src/utils/logger.ts
5
+ import chalk from "chalk";
6
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
7
+ LogLevel2[LogLevel2["DEBUG"] = 0] = "DEBUG";
8
+ LogLevel2[LogLevel2["INFO"] = 1] = "INFO";
9
+ LogLevel2[LogLevel2["WARN"] = 2] = "WARN";
10
+ LogLevel2[LogLevel2["ERROR"] = 3] = "ERROR";
11
+ return LogLevel2;
12
+ })(LogLevel || {});
13
+ var LOG_LEVEL = (process.env.LOVECODE_LOG_LEVEL || "info").toUpperCase();
14
+ var currentLevel = LogLevel[LOG_LEVEL] ?? 1 /* INFO */;
15
+ var Logger = class {
16
+ static debug(...args) {
17
+ if (currentLevel <= 0 /* DEBUG */) {
18
+ console.error(chalk.dim("[debug]"), ...args);
19
+ }
20
+ }
21
+ static info(...args) {
22
+ if (currentLevel <= 1 /* INFO */) {
23
+ console.error(chalk.blue("[info]"), ...args);
24
+ }
25
+ }
26
+ static warn(...args) {
27
+ if (currentLevel <= 2 /* WARN */) {
28
+ console.error(chalk.yellow("[warn]"), ...args);
29
+ }
30
+ }
31
+ static error(...args) {
32
+ if (currentLevel <= 3 /* ERROR */) {
33
+ console.error(chalk.red("[error]"), ...args);
34
+ }
35
+ }
36
+ };
37
+
38
+ // src/ai/ollama.ts
39
+ var OllamaProvider = class {
40
+ name = "ollama";
41
+ async chat(messages, config) {
42
+ const baseUrl = config.baseUrl || "http://localhost:11434";
43
+ Logger.debug(`Ollama chat: ${config.model} (${messages.length} messages)`);
44
+ const response = await fetch(`${baseUrl}/api/chat`, {
45
+ method: "POST",
46
+ headers: { "Content-Type": "application/json" },
47
+ body: JSON.stringify({
48
+ model: config.model,
49
+ messages,
50
+ stream: false,
51
+ options: {
52
+ temperature: config.temperature ?? 0.2,
53
+ num_predict: config.maxTokens ?? 4096
54
+ }
55
+ })
56
+ });
57
+ if (!response.ok) {
58
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
59
+ }
60
+ const data = await response.json();
61
+ return data.message?.content ?? "";
62
+ }
63
+ async *stream(messages, config) {
64
+ const baseUrl = config.baseUrl || "http://localhost:11434";
65
+ const response = await fetch(`${baseUrl}/api/chat`, {
66
+ method: "POST",
67
+ headers: { "Content-Type": "application/json" },
68
+ body: JSON.stringify({
69
+ model: config.model,
70
+ messages,
71
+ stream: true,
72
+ options: {
73
+ temperature: config.temperature ?? 0.2,
74
+ num_predict: config.maxTokens ?? 4096
75
+ }
76
+ })
77
+ });
78
+ if (!response.ok) {
79
+ throw new Error(`Ollama API error: ${response.status} ${response.statusText}`);
80
+ }
81
+ const reader = response.body?.getReader();
82
+ if (!reader) return;
83
+ const decoder = new TextDecoder();
84
+ let buffer = "";
85
+ while (true) {
86
+ const { done, value } = await reader.read();
87
+ if (done) break;
88
+ buffer += decoder.decode(value, { stream: true });
89
+ const lines = buffer.split("\n");
90
+ buffer = lines.pop() ?? "";
91
+ for (const line of lines) {
92
+ if (!line.trim()) continue;
93
+ try {
94
+ const parsed = JSON.parse(line);
95
+ if (parsed.done) return;
96
+ if (parsed.message?.content) {
97
+ yield parsed.message.content;
98
+ }
99
+ } catch {
100
+ }
101
+ }
102
+ }
103
+ }
104
+ async isAvailable(baseUrl) {
105
+ try {
106
+ const res = await fetch(`${baseUrl || "http://localhost:11434"}/api/tags`);
107
+ return res.ok;
108
+ } catch {
109
+ return false;
110
+ }
111
+ }
112
+ };
113
+
114
+ // src/ai/openai-like.ts
115
+ var OpenAILikeProvider = class {
116
+ name;
117
+ baseUrl;
118
+ constructor(name, baseUrl) {
119
+ this.name = name;
120
+ this.baseUrl = baseUrl.replace(/\/+$/, "");
121
+ }
122
+ async chat(messages, config) {
123
+ const url = `${this.baseUrl}/chat/completions`;
124
+ Logger.debug(`[${this.name}] chat: ${config.model} (${messages.length} messages)`);
125
+ const apiKey = this.getApiKey();
126
+ const response = await fetch(url, {
127
+ method: "POST",
128
+ headers: {
129
+ "Content-Type": "application/json",
130
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
131
+ },
132
+ body: JSON.stringify({
133
+ model: config.model,
134
+ messages,
135
+ temperature: config.temperature ?? 0.2,
136
+ max_tokens: config.maxTokens ?? 4096,
137
+ stream: false
138
+ })
139
+ });
140
+ if (!response.ok) {
141
+ const body = await response.text().catch(() => "");
142
+ throw new Error(`[${this.name}] API error ${response.status}: ${body.slice(0, 200)}`);
143
+ }
144
+ const data = await response.json();
145
+ return data.choices?.[0]?.message?.content ?? "";
146
+ }
147
+ async *stream(messages, config) {
148
+ const url = `${this.baseUrl}/chat/completions`;
149
+ const apiKey = this.getApiKey();
150
+ const response = await fetch(url, {
151
+ method: "POST",
152
+ headers: {
153
+ "Content-Type": "application/json",
154
+ ...apiKey ? { Authorization: `Bearer ${apiKey}` } : {}
155
+ },
156
+ body: JSON.stringify({
157
+ model: config.model,
158
+ messages,
159
+ temperature: config.temperature ?? 0.2,
160
+ max_tokens: config.maxTokens ?? 4096,
161
+ stream: true
162
+ })
163
+ });
164
+ if (!response.ok) {
165
+ const body = await response.text().catch(() => "");
166
+ throw new Error(`[${this.name}] API error ${response.status}: ${body.slice(0, 200)}`);
167
+ }
168
+ const reader = response.body?.getReader();
169
+ if (!reader) return;
170
+ const decoder = new TextDecoder();
171
+ let buffer = "";
172
+ while (true) {
173
+ const { done, value } = await reader.read();
174
+ if (done) break;
175
+ buffer += decoder.decode(value, { stream: true });
176
+ const lines = buffer.split("\n");
177
+ buffer = lines.pop() ?? "";
178
+ for (const line of lines) {
179
+ const trimmed = line.trim();
180
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
181
+ const jsonStr = trimmed.slice(6).trim();
182
+ if (jsonStr === "[DONE]") return;
183
+ try {
184
+ const parsed = JSON.parse(jsonStr);
185
+ const content = parsed.choices?.[0]?.delta?.content;
186
+ if (content) yield content;
187
+ } catch {
188
+ }
189
+ }
190
+ }
191
+ }
192
+ getApiKey() {
193
+ const envVar = this.name.toUpperCase();
194
+ return process.env[`${envVar}_API_KEY`] || process.env.OPENAI_API_KEY;
195
+ }
196
+ async isAvailable() {
197
+ try {
198
+ const res = await fetch(`${this.baseUrl}/models`, {
199
+ headers: this.getApiKey() ? { Authorization: `Bearer ${this.getApiKey()}` } : {}
200
+ });
201
+ return res.ok;
202
+ } catch {
203
+ return false;
204
+ }
205
+ }
206
+ };
207
+
208
+ // src/ai/registry.ts
209
+ var registry = [
210
+ {
211
+ name: "ollama",
212
+ provider: new OllamaProvider(),
213
+ models: ["codellama", "deepseek-coder", "llama3.2", "llama3.1", "mistral", "mixtral", "phi3", "qwen2.5-coder"],
214
+ local: true,
215
+ priority: 10,
216
+ defaultModel: "codellama",
217
+ getConfig: (model) => ({
218
+ model,
219
+ baseUrl: "http://localhost:11434",
220
+ temperature: 0.2,
221
+ maxTokens: 8192
222
+ })
223
+ },
224
+ {
225
+ name: "groq",
226
+ provider: new OpenAILikeProvider("groq", "https://api.groq.com/openai/v1"),
227
+ models: ["llama3-70b-8192", "llama3-8b-8192", "mixtral-8x7b-32768", "gemma2-9b-it", "deepseek-r1-distill-llama-70b"],
228
+ local: false,
229
+ priority: 30,
230
+ defaultModel: "llama3-70b-8192",
231
+ getConfig: (model) => ({
232
+ model,
233
+ baseUrl: "https://api.groq.com/openai/v1",
234
+ temperature: 0.2,
235
+ maxTokens: 8192
236
+ })
237
+ },
238
+ {
239
+ name: "openrouter",
240
+ provider: new OpenAILikeProvider("openrouter", "https://openrouter.ai/api/v1"),
241
+ models: [
242
+ "google/gemini-2.0-flash-001",
243
+ "google/gemini-2.0-flash-lite-preview",
244
+ "mistralai/mistral-7b-instruct",
245
+ "meta-llama/llama-3.2-3b-instruct",
246
+ "deepseek/deepseek-chat",
247
+ "qwen/qwen-2.5-7b-instruct"
248
+ ],
249
+ local: false,
250
+ priority: 40,
251
+ defaultModel: "google/gemini-2.0-flash-001",
252
+ getConfig: (model) => ({
253
+ model,
254
+ baseUrl: "https://openrouter.ai/api/v1",
255
+ temperature: 0.2,
256
+ maxTokens: 8192
257
+ })
258
+ },
259
+ {
260
+ name: "together",
261
+ provider: new OpenAILikeProvider("together", "https://api.together.xyz/v1"),
262
+ models: [
263
+ "mistralai/Mixtral-8x22B-Instruct-v0.1",
264
+ "mistralai/Mistral-7B-Instruct-v0.3",
265
+ "meta-llama/Llama-3.2-3B-Instruct-Turbo",
266
+ "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo",
267
+ "deepseek-ai/deepseek-coder-33b-instruct",
268
+ "Qwen/Qwen2.5-7B-Instruct-Turbo"
269
+ ],
270
+ local: false,
271
+ priority: 50,
272
+ defaultModel: "mistralai/Mixtral-8x22B-Instruct-v0.1",
273
+ getConfig: (model) => ({
274
+ model,
275
+ baseUrl: "https://api.together.xyz/v1",
276
+ temperature: 0.2,
277
+ maxTokens: 8192
278
+ })
279
+ },
280
+ {
281
+ name: "huggingface",
282
+ provider: new OpenAILikeProvider("huggingface", "https://api-inference.huggingface.co/v1"),
283
+ models: [
284
+ "HuggingFaceH4/zephyr-7b-beta",
285
+ "mistralai/Mistral-7B-Instruct-v0.3",
286
+ "meta-llama/Meta-Llama-3-8B-Instruct",
287
+ "google/gemma-2-9b-it"
288
+ ],
289
+ local: false,
290
+ priority: 60,
291
+ defaultModel: "HuggingFaceH4/zephyr-7b-beta",
292
+ getConfig: (model) => ({
293
+ model,
294
+ baseUrl: "https://api-inference.huggingface.co/v1",
295
+ temperature: 0.2,
296
+ maxTokens: 4096
297
+ })
298
+ }
299
+ ];
300
+ function getProvider(name) {
301
+ return registry.find((p) => p.name === name);
302
+ }
303
+ function getProviderForModel(model) {
304
+ return registry.find((p) => p.models.includes(model) || p.defaultModel === model);
305
+ }
306
+ function getAllProviders() {
307
+ return [...registry];
308
+ }
309
+ function getLocalProviders() {
310
+ return registry.filter((p) => p.local);
311
+ }
312
+ function getRemoteProviders() {
313
+ return registry.filter((p) => !p.local);
314
+ }
315
+ function resolveModel(modelOrProvider) {
316
+ const byName = getProvider(modelOrProvider);
317
+ if (byName) {
318
+ return { entry: byName, model: byName.defaultModel };
319
+ }
320
+ const byModel = getProviderForModel(modelOrProvider);
321
+ if (byModel) {
322
+ return { entry: byModel, model: modelOrProvider };
323
+ }
324
+ const local = getLocalProviders()[0];
325
+ return { entry: local, model: modelOrProvider };
326
+ }
327
+ function printProviders() {
328
+ const lines = [chalk2.bold("\n Available Providers")];
329
+ for (const entry of registry) {
330
+ const tag = entry.local ? chalk2.green(" LOCAL ") : chalk2.blue(" CLOUD ");
331
+ const defaultModel = chalk2.dim(`(default: ${entry.defaultModel})`);
332
+ const models = entry.models.slice(0, 4).join(", ");
333
+ const more = entry.models.length > 4 ? chalk2.dim(` +${entry.models.length - 4} more`) : "";
334
+ lines.push(`
335
+ ${tag} ${chalk2.cyan(entry.name.padEnd(12))} ${defaultModel}`);
336
+ lines.push(` ${chalk2.dim(models)}${more}`);
337
+ }
338
+ lines.push("");
339
+ return lines.join("\n");
340
+ }
341
+ function setDefaultModel(model) {
342
+ const resolved = resolveModel(model);
343
+ resolved.entry.defaultModel = resolved.model;
344
+ return { provider: resolved.entry.name, model: resolved.model };
345
+ }
346
+ function getDefaultModel() {
347
+ const highest = [...registry].sort((a, b) => {
348
+ if (a.local && !b.local) return -1;
349
+ if (!a.local && b.local) return 1;
350
+ return a.priority - b.priority;
351
+ })[0];
352
+ return { provider: highest.name, model: highest.defaultModel };
353
+ }
354
+
355
+ export {
356
+ OllamaProvider,
357
+ getProvider,
358
+ getProviderForModel,
359
+ getAllProviders,
360
+ getLocalProviders,
361
+ getRemoteProviders,
362
+ resolveModel,
363
+ printProviders,
364
+ setDefaultModel,
365
+ getDefaultModel
366
+ };
@@ -0,0 +1,16 @@
1
+ import {
2
+ configDir,
3
+ formatConfig,
4
+ getDefaults,
5
+ loadConfig,
6
+ resetConfig,
7
+ saveConfig
8
+ } from "./chunk-LJ7HTOIK.js";
9
+ export {
10
+ configDir,
11
+ formatConfig,
12
+ getDefaults,
13
+ loadConfig,
14
+ resetConfig,
15
+ saveConfig
16
+ };
@@ -0,0 +1,14 @@
1
+ import {
2
+ KNOWN_ENV_VARS,
3
+ formatEnvStatus,
4
+ loadEnv,
5
+ saveEnv,
6
+ saveEnvExample
7
+ } from "./chunk-3AHNSXQX.js";
8
+ export {
9
+ KNOWN_ENV_VARS,
10
+ formatEnvStatus,
11
+ loadEnv,
12
+ saveEnv,
13
+ saveEnvExample
14
+ };
@@ -32,7 +32,8 @@ import {
32
32
  stageFiles,
33
33
  suggestResolutions,
34
34
  switchBranch
35
- } from "./chunk-G7VQGYJW.js";
35
+ } from "./chunk-7CT3XDH6.js";
36
+ import "./chunk-Y3HADLWO.js";
36
37
  export {
37
38
  abbreviateDiff,
38
39
  cleanupMergedBranches,