@voria/cli 0.0.3 → 0.0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # voria
2
2
 
3
- 🚀 **AI-Powered Bug Fixing Tool**
3
+ **AI-Powered Bug Fixing Tool**
4
4
 
5
5
  voria is a CLI tool that automatically fixes bugs and implements features in your codebase. Describe an issue or provide a GitHub issue number, and voria will generate a fix, test it, iterate on failures, and create a pull request - all automatically.
6
6
 
package/bin/voria CHANGED
@@ -131,7 +131,19 @@ function saveConfig(config, isGlobal = true) {
131
131
  fs.writeFileSync(file, JSON.stringify(config, null, 2));
132
132
  }
133
133
 
134
- // Interactive setup
134
+ /**
135
+ * Dynamically fetch models for a provider using Python backend
136
+ */
137
+ function fetchModels(provider, apiKey) {
138
+ try {
139
+ const scriptPath = path.join(__dirname, '..', 'python', 'voria', 'core', 'llm', 'model_discovery.py');
140
+ const output = execSync(`python3 "${scriptPath}" "${provider}" "${apiKey}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] });
141
+ return JSON.parse(output);
142
+ } catch (e) {
143
+ return [];
144
+ }
145
+ }
146
+
135
147
  // Interactive setup
136
148
  async function runSetup() {
137
149
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -170,8 +182,11 @@ async function runSetup() {
170
182
  return;
171
183
  }
172
184
 
185
+ console.log(`\n${colors.dim(" Fetching latest models...")}`);
186
+ const fetchedModels = fetchModels(provider, apiKey);
187
+ const models = fetchedModels.length > 0 ? fetchedModels.map(m => m.name) : (PROVIDER_MODELS[provider] || ['gpt-4o']);
188
+
173
189
  console.log(`\n${colors.bold("Select Model:")}`);
174
- const models = PROVIDER_MODELS[provider] || ['default'];
175
190
  models.forEach((model, index) => {
176
191
  console.log(` ${colors.blue((index + 1) + ")")} ${model}`);
177
192
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voria/cli",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "AI-powered CLI tool for automated bug fixing - initialize with voria --init",
5
5
  "main": "bin/voria",
6
6
  "type": "module",
@@ -5,9 +5,10 @@ Fetches available models at runtime based on API keys.
5
5
 
6
6
  import asyncio
7
7
  import httpx
8
- from dataclasses import dataclass
8
+ from dataclasses import dataclass, asdict
9
9
  from typing import List, Optional
10
10
  import logging
11
+ import json
11
12
 
12
13
  logger = logging.getLogger(__name__)
13
14
 
@@ -26,6 +27,36 @@ class ModelInfo:
26
27
  class ModelDiscovery:
27
28
  """Fetch available models from LLM providers."""
28
29
 
30
+ @staticmethod
31
+ async def fetch_generic_openai_compatible(api_key: str, base_url: str, provider_name: str) -> List[ModelInfo]:
32
+ """Fetch models from an OpenAI-compatible API."""
33
+ try:
34
+ async with httpx.AsyncClient() as client:
35
+ response = await client.get(
36
+ f"{base_url.rstrip('/')}/models",
37
+ headers={"Authorization": f"Bearer {api_key}"},
38
+ timeout=10.0,
39
+ )
40
+ if response.status_code == 200:
41
+ data = response.json()
42
+ models = []
43
+ for model in data.get("data", []):
44
+ model_id = model.get("id", "")
45
+ models.append(
46
+ ModelInfo(
47
+ name=model_id,
48
+ display_name=model_id,
49
+ description=f"{provider_name} Model",
50
+ )
51
+ )
52
+ return models
53
+ else:
54
+ logger.warning(f"{provider_name} API returned {response.status_code}")
55
+ return []
56
+ except Exception as e:
57
+ logger.warning(f"Failed to fetch {provider_name} models: {e}")
58
+ return []
59
+
29
60
  @staticmethod
30
61
  async def fetch_modal_models(api_key: str) -> List[ModelInfo]:
31
62
  """Fetch available models from Modal Z.ai API."""
@@ -39,7 +70,6 @@ class ModelDiscovery:
39
70
  if response.status_code == 200:
40
71
  data = response.json()
41
72
  models = []
42
- # Modal returns model data in "data" key
43
73
  for model in data.get("data", []):
44
74
  models.append(
45
75
  ModelInfo(
@@ -49,34 +79,16 @@ class ModelDiscovery:
49
79
  description=f"Modal Z.ai - {model.get('created', 'N/A')}",
50
80
  )
51
81
  )
52
- return (
53
- models if models else await ModelDiscovery._get_modal_fallback()
54
- )
55
- else:
56
- logger.warning(
57
- f"Modal API returned {response.status_code}, using fallback models"
58
- )
59
- return await ModelDiscovery._get_modal_fallback()
60
- except Exception as e:
61
- logger.warning(f"Failed to fetch Modal models: {e}, using fallback")
82
+ return models if models else await ModelDiscovery._get_modal_fallback()
83
+ return await ModelDiscovery._get_modal_fallback()
84
+ except Exception:
62
85
  return await ModelDiscovery._get_modal_fallback()
63
86
 
64
87
  @staticmethod
65
88
  async def _get_modal_fallback() -> List[ModelInfo]:
66
- """Fallback models for Modal when API unavailable."""
67
89
  return [
68
- ModelInfo(
69
- name="zai-org/GLM-5.1-FP8",
70
- display_name="GLM-5.1-FP8 (745B, Latest)",
71
- max_tokens=4096,
72
- description="Latest Modal Z.ai model - 745B parameters",
73
- ),
74
- ModelInfo(
75
- name="zai-org/GLM-4",
76
- display_name="GLM-4 (370B, Legacy)",
77
- max_tokens=2048,
78
- description="Previous generation Modal model",
79
- ),
90
+ ModelInfo(name="zai-org/GLM-5.1-FP8", display_name="GLM-5.1-FP8 (Latest)"),
91
+ ModelInfo(name="zai-org/GLM-4", display_name="GLM-4 (Legacy)"),
80
92
  ]
81
93
 
82
94
  @staticmethod
@@ -92,81 +104,26 @@ class ModelDiscovery:
92
104
  if response.status_code == 200:
93
105
  data = response.json()
94
106
  models = []
95
- # Filter to only gpt models suitable for text generation
96
- suitable_models = {
97
- "gpt-4o",
98
- "gpt-4-turbo",
99
- "gpt-4",
100
- "gpt-3.5-turbo",
101
- }
107
+ suitable_prefixes = {"gpt-4", "gpt-3.5", "o1-"}
102
108
  for model in data.get("data", []):
103
109
  model_id = model.get("id", "")
104
- # Match by prefix or exact name
105
- if any(
106
- model_id.startswith(prefix) for prefix in suitable_models
107
- ):
108
- models.append(
109
- ModelInfo(
110
- name=model_id,
111
- display_name=model_id,
112
- description=f"OpenAI - {model.get('owned_by', 'N/A')}",
113
- )
114
- )
115
- # Sort by recency (gpt-4o > gpt-4-turbo > gpt-4 > gpt-3.5-turbo)
116
- return (
117
- sorted(
118
- models,
119
- key=lambda x: (
120
- not x.name.startswith("gpt-4o"),
121
- not x.name.startswith("gpt-4-turbo"),
122
- not x.name.startswith("gpt-4"),
123
- ),
124
- )
125
- if models
126
- else await ModelDiscovery._get_openai_fallback()
127
- )
128
- else:
129
- logger.warning(
130
- f"OpenAI API returned {response.status_code}, using fallback models"
131
- )
132
- return await ModelDiscovery._get_openai_fallback()
133
- except Exception as e:
134
- logger.warning(f"Failed to fetch OpenAI models: {e}, using fallback")
110
+ if any(model_id.startswith(p) for p in suitable_prefixes):
111
+ models.append(ModelInfo(name=model_id, display_name=model_id))
112
+ return models if models else await ModelDiscovery._get_openai_fallback()
113
+ return await ModelDiscovery._get_openai_fallback()
114
+ except Exception:
135
115
  return await ModelDiscovery._get_openai_fallback()
136
116
 
137
117
  @staticmethod
138
118
  async def _get_openai_fallback() -> List[ModelInfo]:
139
- """Fallback models for OpenAI when API unavailable."""
140
119
  return [
141
- ModelInfo(
142
- name="gpt-5.4",
143
- display_name="GPT-5.4 (Latest Frontier)",
144
- max_tokens=128000,
145
- description="Best intelligence at scale for agentic, coding, and professional workflows. $2.50 input, $15 output per 1M tokens",
146
- ),
147
- ModelInfo(
148
- name="gpt-5.4-mini",
149
- display_name="GPT-5.4-mini (Mini Model)",
150
- max_tokens=128000,
151
- description="Strongest mini model yet for coding, computer use, and agentic tasks. $0.75 input, $4.50 output per 1M tokens",
152
- ),
153
- ModelInfo(
154
- name="gpt-5.4-nano",
155
- display_name="GPT-5.4-nano (Cheapest)",
156
- max_tokens=128000,
157
- description="Cheapest GPT-5.4-class model for simple high-volume tasks. $0.20 input, $1.25 output per 1M tokens",
158
- ),
159
- ModelInfo(
160
- name="gpt-4o",
161
- display_name="GPT-4o (Previous High Quality)",
162
- max_tokens=128000,
163
- description="Previous latest model - optimized for speed and cost",
164
- ),
120
+ ModelInfo(name="gpt-4o", display_name="GPT-4o"),
121
+ ModelInfo(name="gpt-4o-mini", display_name="GPT-4o-mini"),
122
+ ModelInfo(name="o1-preview", display_name="o1-preview"),
165
123
  ]
166
124
 
167
125
  @staticmethod
168
126
  async def fetch_gemini_models(api_key: str) -> List[ModelInfo]:
169
- """Fetch available models from Google Gemini API."""
170
127
  try:
171
128
  async with httpx.AsyncClient() as client:
172
129
  response = await client.get(
@@ -176,106 +133,33 @@ class ModelDiscovery:
176
133
  if response.status_code == 200:
177
134
  data = response.json()
178
135
  models = []
179
- # Filter to generative models
180
136
  for model in data.get("models", []):
181
- model_name = model.get("name", "").replace("models/", "")
182
- if "gemini" in model_name.lower():
183
- models.append(
184
- ModelInfo(
185
- name=model_name,
186
- display_name=model_name,
187
- description=f"Google Gemini - {model.get('displayName', 'N/A')}",
188
- )
189
- )
190
- return (
191
- models
192
- if models
193
- else await ModelDiscovery._get_gemini_fallback()
194
- )
195
- else:
196
- logger.warning(
197
- f"Gemini API returned {response.status_code}, using fallback models"
198
- )
199
- return await ModelDiscovery._get_gemini_fallback()
200
- except Exception as e:
201
- logger.warning(f"Failed to fetch Gemini models: {e}, using fallback")
137
+ name = model.get("name", "").replace("models/", "")
138
+ if "gemini" in name.lower():
139
+ models.append(ModelInfo(name=name, display_name=name))
140
+ return models if models else await ModelDiscovery._get_gemini_fallback()
141
+ return await ModelDiscovery._get_gemini_fallback()
142
+ except Exception:
202
143
  return await ModelDiscovery._get_gemini_fallback()
203
144
 
204
145
  @staticmethod
205
146
  async def _get_gemini_fallback() -> List[ModelInfo]:
206
- """Fallback models for Gemini when API unavailable."""
207
147
  return [
208
- ModelInfo(
209
- name="gemini-3.1-pro",
210
- display_name="Gemini 3.1 Pro (Latest SOTA Reasoning)",
211
- max_tokens=200000,
212
- description="Latest SOTA reasoning model with unprecedented depth and nuance. $2 input, $12 output per context window",
213
- ),
214
- ModelInfo(
215
- name="gemini-3-flash",
216
- display_name="Gemini 3 Flash (Latest, Fastest)",
217
- max_tokens=200000,
218
- description="Most intelligent model built for speed, combining frontier intelligence with superior search and grounding",
219
- ),
220
- ModelInfo(
221
- name="gemini-3.1-flash-lite",
222
- display_name="Gemini 3.1 Flash Lite (Cheapest)",
223
- max_tokens=200000,
224
- description="Most cost-efficient model, optimized for high-volume agentic tasks. $0.25 input, $1.50 output",
225
- ),
226
- ModelInfo(
227
- name="gemini-2.0-flash",
228
- display_name="Gemini 2.0 Flash (Previous)",
229
- max_tokens=2000,
230
- description="Previous generation Gemini model",
231
- ),
148
+ ModelInfo(name="gemini-1.5-pro", display_name="Gemini 1.5 Pro"),
149
+ ModelInfo(name="gemini-1.5-flash", display_name="Gemini 1.5 Flash"),
232
150
  ]
233
151
 
234
152
  @staticmethod
235
153
  async def fetch_claude_models(api_key: str) -> List[ModelInfo]:
236
- """Fetch available models from Anthropic Claude API."""
237
- try:
238
- async with httpx.AsyncClient() as client:
239
- # Claude doesn't have a public models endpoint, use documented models
240
- # Make a test call to verify API key works
241
- response = await client.get(
242
- "https://api.anthropic.com/v1/models",
243
- headers={"x-api-key": api_key},
244
- timeout=10.0,
245
- )
246
- # If we get here, API key works - return known models
247
- return await ModelDiscovery._get_claude_fallback()
248
- except Exception as e:
249
- logger.warning(f"Failed to verify Claude API: {e}, returning known models")
250
- return await ModelDiscovery._get_claude_fallback()
251
-
252
- @staticmethod
253
- async def _get_claude_fallback() -> List[ModelInfo]:
254
- """Known Claude models (Anthropic doesn't provide list endpoint)."""
154
+ # Anthropic doesn't have a models endpoint, just return hardcoded
255
155
  return [
256
- ModelInfo(
257
- name="claude-opus-4.6",
258
- display_name="Claude Opus 4.6 (Most Intelligent)",
259
- max_tokens=200000,
260
- description="Most intelligent broadly available model for complex reasoning. $5 input, $25 output per 1M tokens",
261
- ),
262
- ModelInfo(
263
- name="claude-sonnet-4.6",
264
- display_name="Claude Sonnet 4.6 (Best Value)",
265
- max_tokens=200000,
266
- description="Best balance of speed and intelligence. $3 input, $15 output per 1M tokens",
267
- ),
268
- ModelInfo(
269
- name="claude-haiku-4.5",
270
- display_name="Claude Haiku 4.5 (Fastest, Cheapest)",
271
- max_tokens=200000,
272
- description="Fast and cost-efficient for simpler tasks. $0.80 input, $4 output per 1M tokens",
273
- ),
156
+ ModelInfo(name="claude-3-5-sonnet-20240620", display_name="Claude 3.5 Sonnet"),
157
+ ModelInfo(name="claude-3-opus-20240229", display_name="Claude 3 Opus"),
158
+ ModelInfo(name="claude-3-haiku-20240307", display_name="Claude 3 Haiku"),
274
159
  ]
275
160
 
276
161
  @staticmethod
277
162
  async def discover_all(provider: str, api_key: str) -> List[ModelInfo]:
278
- """Discover all models for a given provider."""
279
163
  provider = provider.lower().strip()
280
164
  if provider == "modal":
281
165
  return await ModelDiscovery.fetch_modal_models(api_key)
@@ -285,5 +169,28 @@ class ModelDiscovery:
285
169
  return await ModelDiscovery.fetch_gemini_models(api_key)
286
170
  elif provider == "claude":
287
171
  return await ModelDiscovery.fetch_claude_models(api_key)
172
+ elif provider == "deepseek":
173
+ return await ModelDiscovery.fetch_generic_openai_compatible(api_key, "https://api.deepseek.com/v1", "DeepSeek")
174
+ elif provider == "kimi":
175
+ return await ModelDiscovery.fetch_generic_openai_compatible(api_key, "https://api.moonshot.cn/v1", "Kimi")
176
+ elif provider == "minimax":
177
+ return await ModelDiscovery.fetch_generic_openai_compatible(api_key, "https://api.minimax.chat/v1", "Minimax")
178
+ elif provider == "siliconflow":
179
+ return await ModelDiscovery.fetch_generic_openai_compatible(api_key, "https://api.siliconflow.cn/v1", "SiliconFlow")
288
180
  else:
289
- raise ValueError(f"Unknown provider: {provider}")
181
+ return []
182
+
183
+ if __name__ == "__main__":
184
+ import sys
185
+ if len(sys.argv) < 3:
186
+ print(json.dumps([]))
187
+ sys.exit(0)
188
+
189
+ provider = sys.argv[1]
190
+ api_key = sys.argv[2]
191
+
192
+ async def main():
193
+ models = await ModelDiscovery.discover_all(provider, api_key)
194
+ print(json.dumps([asdict(m) for m in models]))
195
+
196
+ asyncio.run(main())