tamfis-code 0.2.3__py3-none-any.whl
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.
- tamfis_code/__init__.py +58 -0
- tamfis_code/__main__.py +4 -0
- tamfis_code/agents.py +371 -0
- tamfis_code/api_client.py +461 -0
- tamfis_code/cli.py +2351 -0
- tamfis_code/completion.py +137 -0
- tamfis_code/config.py +199 -0
- tamfis_code/doctor.py +173 -0
- tamfis_code/enforcer.py +318 -0
- tamfis_code/indexer.py +567 -0
- tamfis_code/instructions.py +121 -0
- tamfis_code/interactive.py +985 -0
- tamfis_code/local_chat.py +117 -0
- tamfis_code/local_tools.py +93 -0
- tamfis_code/mcp.py +538 -0
- tamfis_code/metrics.py +105 -0
- tamfis_code/providers.py +423 -0
- tamfis_code/references.py +336 -0
- tamfis_code/render.py +575 -0
- tamfis_code/runner.py +635 -0
- tamfis_code/runner_local.py +364 -0
- tamfis_code/safety.py +164 -0
- tamfis_code/screenshot.py +382 -0
- tamfis_code/sessions.py +253 -0
- tamfis_code/state.py +449 -0
- tamfis_code/tasks.py +42 -0
- tamfis_code/workspace.py +329 -0
- tamfis_code-0.2.3.dist-info/METADATA +78 -0
- tamfis_code-0.2.3.dist-info/RECORD +32 -0
- tamfis_code-0.2.3.dist-info/WHEEL +5 -0
- tamfis_code-0.2.3.dist-info/entry_points.txt +4 -0
- tamfis_code-0.2.3.dist-info/top_level.txt +1 -0
tamfis_code/providers.py
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""Provider integrations for TAMFIS-CODE - HF, NVIDIA NIM, OpenRouter, and Ollama"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import json
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Optional, Dict, Any, AsyncIterator, List
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
import httpx
|
|
10
|
+
from openai import AsyncOpenAI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ProviderType(Enum):
|
|
14
|
+
HF = "hf"
|
|
15
|
+
NVIDIA = "nvidia"
|
|
16
|
+
OPENROUTER = "openrouter"
|
|
17
|
+
OLLAMA = "ollama"
|
|
18
|
+
LOCAL = "local"
|
|
19
|
+
AUTO = "auto"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class ProviderConfig:
|
|
24
|
+
"""Configuration for a provider"""
|
|
25
|
+
name: str
|
|
26
|
+
base_url: str
|
|
27
|
+
api_key_env: str
|
|
28
|
+
default_model: str
|
|
29
|
+
models: List[str] = field(default_factory=list)
|
|
30
|
+
weight: int = 1
|
|
31
|
+
reasoning_supported: bool = False
|
|
32
|
+
vision_supported: bool = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ProviderManager:
|
|
36
|
+
"""Manages multiple AI providers with vision support"""
|
|
37
|
+
|
|
38
|
+
PROVIDERS = {
|
|
39
|
+
# NOTE: provider catalogs drift as vendors ship/retire models --
|
|
40
|
+
# these lists are a reasonable, current-as-of-writing starting point
|
|
41
|
+
# for local/offline mode, not a live catalog feed. Review periodically.
|
|
42
|
+
ProviderType.HF: ProviderConfig(
|
|
43
|
+
name="Hugging Face",
|
|
44
|
+
base_url="https://router.huggingface.co/v1",
|
|
45
|
+
api_key_env="HF_TOKEN",
|
|
46
|
+
# meta-llama/Llama-3.2-3B-Instruct is no longer served by any
|
|
47
|
+
# provider HF's router enables for a standard token (confirmed
|
|
48
|
+
# live: 400 model_not_supported); Qwen2.5-7B-Instruct responds
|
|
49
|
+
# normally, so it's the default instead.
|
|
50
|
+
default_model="Qwen/Qwen2.5-7B-Instruct",
|
|
51
|
+
models=[
|
|
52
|
+
# Text models
|
|
53
|
+
"Qwen/Qwen2.5-7B-Instruct",
|
|
54
|
+
"meta-llama/Llama-3.2-3B-Instruct",
|
|
55
|
+
"mistralai/Mistral-7B-Instruct-v0.3",
|
|
56
|
+
# Vision models
|
|
57
|
+
"microsoft/Phi-3.5-vision-instruct",
|
|
58
|
+
"meta-llama/Llama-3.2-11B-Vision-Instruct",
|
|
59
|
+
"Qwen/Qwen2-VL-7B-Instruct",
|
|
60
|
+
],
|
|
61
|
+
weight=2,
|
|
62
|
+
vision_supported=True
|
|
63
|
+
),
|
|
64
|
+
ProviderType.NVIDIA: ProviderConfig(
|
|
65
|
+
name="NVIDIA NIM (API Catalog)",
|
|
66
|
+
base_url="https://integrate.api.nvidia.com/v1",
|
|
67
|
+
api_key_env="NVIDIA_API_KEY",
|
|
68
|
+
default_model="nvidia/nemotron-3-super-120b-a12b",
|
|
69
|
+
models=[
|
|
70
|
+
# Text models
|
|
71
|
+
"nvidia/nemotron-3-super-120b-a12b",
|
|
72
|
+
"nvidia/nemotron-3-ultra-550b-a55b",
|
|
73
|
+
"moonshotai/kimi-k2.6",
|
|
74
|
+
"meta/llama-3.1-405b-instruct",
|
|
75
|
+
"meta/llama-3.1-70b-instruct",
|
|
76
|
+
"mistralai/mistral-large-2-123b",
|
|
77
|
+
"google/gemma-2-27b-it",
|
|
78
|
+
"microsoft/phi-3-medium-128k-instruct",
|
|
79
|
+
],
|
|
80
|
+
weight=3,
|
|
81
|
+
reasoning_supported=True,
|
|
82
|
+
vision_supported=False
|
|
83
|
+
),
|
|
84
|
+
ProviderType.OPENROUTER: ProviderConfig(
|
|
85
|
+
name="OpenRouter",
|
|
86
|
+
base_url="https://openrouter.ai/api/v1",
|
|
87
|
+
api_key_env="OPENROUTER_API_KEY",
|
|
88
|
+
default_model="openai/gpt-4o-mini",
|
|
89
|
+
models=[
|
|
90
|
+
# Text models
|
|
91
|
+
"openai/gpt-4o-mini",
|
|
92
|
+
"google/gemini-2.5-flash",
|
|
93
|
+
"meta-llama/llama-3.3-70b-instruct:free",
|
|
94
|
+
"mistralai/mistral-7b-instruct:free",
|
|
95
|
+
# Vision models
|
|
96
|
+
"google/gemini-2.5-flash",
|
|
97
|
+
"openai/gpt-4o-mini",
|
|
98
|
+
],
|
|
99
|
+
weight=1,
|
|
100
|
+
vision_supported=True
|
|
101
|
+
),
|
|
102
|
+
ProviderType.OLLAMA: ProviderConfig(
|
|
103
|
+
name="Ollama (Local)",
|
|
104
|
+
base_url=os.environ.get("OLLAMA_BASE_URL", "http://localhost:11434/v1"),
|
|
105
|
+
api_key_env="OLLAMA_API_KEY",
|
|
106
|
+
default_model="llama3.2:3b",
|
|
107
|
+
# Deliberately the lowest weight of any provider (below OpenRouter's 1):
|
|
108
|
+
# Ollama needs no API key, so it's "available" merely by being installed
|
|
109
|
+
# and running. If it outranked keyed cloud providers, `auto` would pick
|
|
110
|
+
# a 3B local model over a properly configured HF/NVIDIA/OpenRouter
|
|
111
|
+
# account whenever Ollama happened to be running -- it should only be
|
|
112
|
+
# chosen when nothing else is configured, per the fallback in
|
|
113
|
+
# _select_best_provider below.
|
|
114
|
+
models=[
|
|
115
|
+
"llama3.2:3b",
|
|
116
|
+
"llama3.2:1b",
|
|
117
|
+
"phi3:mini",
|
|
118
|
+
"qwen2.5:3b",
|
|
119
|
+
"qwen2.5:7b",
|
|
120
|
+
"codellama:7b",
|
|
121
|
+
"codellama:13b",
|
|
122
|
+
"mistral:7b",
|
|
123
|
+
"mixtral:8x7b",
|
|
124
|
+
"tamgpt-fast-1:latest",
|
|
125
|
+
"tamgpt-r1-elite:latest",
|
|
126
|
+
"tamgpt-vision-pro:latest",
|
|
127
|
+
"deepseek-coder:6.7b",
|
|
128
|
+
"gemma2:9b",
|
|
129
|
+
"nomic-embed-text:latest",
|
|
130
|
+
# Vision-capable (if available)
|
|
131
|
+
"llava:7b",
|
|
132
|
+
"llava:13b",
|
|
133
|
+
"bakllava:7b",
|
|
134
|
+
],
|
|
135
|
+
weight=0,
|
|
136
|
+
reasoning_supported=True,
|
|
137
|
+
vision_supported=True
|
|
138
|
+
),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
def __init__(self):
|
|
142
|
+
self.clients: Dict[ProviderType, AsyncOpenAI] = {}
|
|
143
|
+
self.config = self._load_config()
|
|
144
|
+
self._init_clients()
|
|
145
|
+
|
|
146
|
+
def _load_config(self) -> Dict[str, Any]:
|
|
147
|
+
"""Load provider configuration from environment"""
|
|
148
|
+
config = {}
|
|
149
|
+
for provider in ProviderType:
|
|
150
|
+
if provider == ProviderType.AUTO:
|
|
151
|
+
continue
|
|
152
|
+
env_var = f"TAMFIS_PROVIDER_{provider.value.upper()}_ENABLED"
|
|
153
|
+
config[provider.value] = os.environ.get(env_var, "true").lower() == "true"
|
|
154
|
+
return config
|
|
155
|
+
|
|
156
|
+
def _get_api_key(self, provider_type: ProviderType) -> Optional[str]:
|
|
157
|
+
"""Get API key for a provider from environment"""
|
|
158
|
+
config = self.PROVIDERS.get(provider_type)
|
|
159
|
+
if not config:
|
|
160
|
+
return None
|
|
161
|
+
|
|
162
|
+
# Ollama doesn't need a key
|
|
163
|
+
if provider_type == ProviderType.OLLAMA:
|
|
164
|
+
return "ollama"
|
|
165
|
+
|
|
166
|
+
key = os.environ.get(config.api_key_env, "")
|
|
167
|
+
return key if key else None
|
|
168
|
+
|
|
169
|
+
def _has_valid_api_key(self, provider_type: ProviderType) -> bool:
|
|
170
|
+
"""Check if a provider has a valid API key set"""
|
|
171
|
+
if provider_type == ProviderType.AUTO:
|
|
172
|
+
return False
|
|
173
|
+
|
|
174
|
+
# Ollama always available if the service is running
|
|
175
|
+
if provider_type == ProviderType.OLLAMA:
|
|
176
|
+
return self._check_ollama_available()
|
|
177
|
+
|
|
178
|
+
config = self.PROVIDERS.get(provider_type)
|
|
179
|
+
if not config:
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
key = os.environ.get(config.api_key_env, "")
|
|
183
|
+
if not key:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
# Check if it's a placeholder
|
|
187
|
+
placeholder_patterns = ["your_", "_key", "_token", "YOUR_", "_API_KEY"]
|
|
188
|
+
if any(p in key for p in placeholder_patterns):
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
# Valid key must be at least 8 characters
|
|
192
|
+
if len(key) < 8:
|
|
193
|
+
return False
|
|
194
|
+
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
def _check_ollama_available(self) -> bool:
|
|
198
|
+
"""Check if Ollama service is running and accessible"""
|
|
199
|
+
try:
|
|
200
|
+
result = subprocess.run(
|
|
201
|
+
["ollama", "list"],
|
|
202
|
+
capture_output=True,
|
|
203
|
+
text=True,
|
|
204
|
+
timeout=5
|
|
205
|
+
)
|
|
206
|
+
return result.returncode == 0 and "NAME" in result.stdout
|
|
207
|
+
except:
|
|
208
|
+
return False
|
|
209
|
+
|
|
210
|
+
def _get_ollama_models(self) -> List[str]:
|
|
211
|
+
"""Get list of available Ollama models"""
|
|
212
|
+
try:
|
|
213
|
+
result = subprocess.run(
|
|
214
|
+
["ollama", "list"],
|
|
215
|
+
capture_output=True,
|
|
216
|
+
text=True,
|
|
217
|
+
timeout=5
|
|
218
|
+
)
|
|
219
|
+
if result.returncode == 0:
|
|
220
|
+
models = []
|
|
221
|
+
for line in result.stdout.split('\n')[1:]: # Skip header
|
|
222
|
+
if line.strip():
|
|
223
|
+
parts = line.split()
|
|
224
|
+
if parts:
|
|
225
|
+
models.append(parts[0])
|
|
226
|
+
return models
|
|
227
|
+
except:
|
|
228
|
+
pass
|
|
229
|
+
return []
|
|
230
|
+
|
|
231
|
+
def _init_clients(self):
|
|
232
|
+
"""Initialize OpenAI-compatible clients for each provider"""
|
|
233
|
+
for provider_type, config in self.PROVIDERS.items():
|
|
234
|
+
if provider_type == ProviderType.AUTO:
|
|
235
|
+
continue
|
|
236
|
+
if not self.config.get(provider_type.value, True):
|
|
237
|
+
continue
|
|
238
|
+
|
|
239
|
+
# Skip if no valid API key (except Ollama)
|
|
240
|
+
if not self._has_valid_api_key(provider_type) and provider_type != ProviderType.OLLAMA:
|
|
241
|
+
continue
|
|
242
|
+
|
|
243
|
+
api_key = self._get_api_key(provider_type)
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
self.clients[provider_type] = AsyncOpenAI(
|
|
247
|
+
base_url=config.base_url,
|
|
248
|
+
api_key=api_key or "dummy",
|
|
249
|
+
timeout=120.0,
|
|
250
|
+
max_retries=2,
|
|
251
|
+
)
|
|
252
|
+
except Exception as e:
|
|
253
|
+
print(f"Failed to initialize {config.name}: {e}")
|
|
254
|
+
|
|
255
|
+
def get_client(self, provider: ProviderType) -> Optional[AsyncOpenAI]:
|
|
256
|
+
"""Get a client for a specific provider"""
|
|
257
|
+
if provider == ProviderType.AUTO:
|
|
258
|
+
provider = self._select_best_provider()
|
|
259
|
+
return self.clients.get(provider)
|
|
260
|
+
|
|
261
|
+
def _select_best_provider(self) -> ProviderType:
|
|
262
|
+
"""Select the best available provider based on weight and availability"""
|
|
263
|
+
available = []
|
|
264
|
+
for p, client in self.clients.items():
|
|
265
|
+
if client and self._has_valid_api_key(p):
|
|
266
|
+
available.append((p, self.PROVIDERS[p].weight))
|
|
267
|
+
|
|
268
|
+
if not available:
|
|
269
|
+
if self._check_ollama_available():
|
|
270
|
+
return ProviderType.OLLAMA
|
|
271
|
+
return ProviderType.NVIDIA
|
|
272
|
+
|
|
273
|
+
available.sort(key=lambda x: x[1], reverse=True)
|
|
274
|
+
return available[0][0]
|
|
275
|
+
|
|
276
|
+
def list_available_providers(self) -> List[Dict[str, Any]]:
|
|
277
|
+
"""List all available providers with their status"""
|
|
278
|
+
result = []
|
|
279
|
+
ollama_models = self._get_ollama_models() if self._check_ollama_available() else []
|
|
280
|
+
|
|
281
|
+
for provider_type, client in self.clients.items():
|
|
282
|
+
config = self.PROVIDERS.get(provider_type)
|
|
283
|
+
if config:
|
|
284
|
+
valid_key = self._has_valid_api_key(provider_type)
|
|
285
|
+
available = client is not None and valid_key
|
|
286
|
+
|
|
287
|
+
if provider_type == ProviderType.OLLAMA:
|
|
288
|
+
available = self._check_ollama_available()
|
|
289
|
+
valid_key = available
|
|
290
|
+
|
|
291
|
+
result.append({
|
|
292
|
+
"name": config.name,
|
|
293
|
+
"type": provider_type.value,
|
|
294
|
+
"available": available,
|
|
295
|
+
"default_model": config.default_model,
|
|
296
|
+
"models": ollama_models if provider_type == ProviderType.OLLAMA else config.models,
|
|
297
|
+
"weight": config.weight,
|
|
298
|
+
"api_key_set": valid_key,
|
|
299
|
+
"reasoning_supported": config.reasoning_supported,
|
|
300
|
+
"vision_supported": config.vision_supported,
|
|
301
|
+
"key_preview": self._get_api_key(provider_type)[:8] + "..." if self._get_api_key(provider_type) and provider_type != ProviderType.OLLAMA else "local"
|
|
302
|
+
})
|
|
303
|
+
return result
|
|
304
|
+
|
|
305
|
+
async def chat_completion(
|
|
306
|
+
self,
|
|
307
|
+
provider: ProviderType,
|
|
308
|
+
messages: List[Dict[str, str]],
|
|
309
|
+
model: Optional[str] = None,
|
|
310
|
+
stream: bool = True,
|
|
311
|
+
temperature: float = 0.10,
|
|
312
|
+
max_tokens: int = 16384,
|
|
313
|
+
reasoning_effort: Optional[str] = "high",
|
|
314
|
+
**kwargs
|
|
315
|
+
) -> AsyncIterator[str]:
|
|
316
|
+
"""Stream a chat completion from a provider"""
|
|
317
|
+
client = self.get_client(provider)
|
|
318
|
+
if not client:
|
|
319
|
+
raise ValueError(f"Provider {provider} not available")
|
|
320
|
+
|
|
321
|
+
config = self.PROVIDERS.get(provider)
|
|
322
|
+
if not config:
|
|
323
|
+
raise ValueError(f"Unknown provider: {provider}")
|
|
324
|
+
|
|
325
|
+
model = model or config.default_model
|
|
326
|
+
|
|
327
|
+
# For Ollama, use a valid model name
|
|
328
|
+
if provider == ProviderType.OLLAMA:
|
|
329
|
+
ollama_models = self._get_ollama_models()
|
|
330
|
+
if model not in ollama_models and ollama_models:
|
|
331
|
+
model = ollama_models[0]
|
|
332
|
+
|
|
333
|
+
try:
|
|
334
|
+
response = await client.chat.completions.create(
|
|
335
|
+
model=model,
|
|
336
|
+
messages=messages,
|
|
337
|
+
stream=stream,
|
|
338
|
+
temperature=temperature,
|
|
339
|
+
max_tokens=max_tokens,
|
|
340
|
+
**kwargs
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
if stream:
|
|
344
|
+
async for chunk in response:
|
|
345
|
+
if chunk.choices and chunk.choices[0].delta.content:
|
|
346
|
+
yield chunk.choices[0].delta.content
|
|
347
|
+
else:
|
|
348
|
+
if response.choices and response.choices[0].message.content:
|
|
349
|
+
yield response.choices[0].message.content
|
|
350
|
+
|
|
351
|
+
except Exception as e:
|
|
352
|
+
print(f"[{config.name} Error] {e}")
|
|
353
|
+
fallback_chain = [ProviderType.NVIDIA, ProviderType.HF, ProviderType.OPENROUTER, ProviderType.OLLAMA]
|
|
354
|
+
for fallback in fallback_chain:
|
|
355
|
+
if fallback != provider and self.get_client(fallback):
|
|
356
|
+
try:
|
|
357
|
+
async for chunk in self.chat_completion(
|
|
358
|
+
fallback, messages, model, stream, temperature, max_tokens, **kwargs
|
|
359
|
+
):
|
|
360
|
+
yield chunk
|
|
361
|
+
return
|
|
362
|
+
except:
|
|
363
|
+
continue
|
|
364
|
+
raise
|
|
365
|
+
|
|
366
|
+
async def chat_completion_sync(
|
|
367
|
+
self,
|
|
368
|
+
provider: ProviderType,
|
|
369
|
+
messages: List[Dict[str, str]],
|
|
370
|
+
model: Optional[str] = None,
|
|
371
|
+
temperature: float = 0.10,
|
|
372
|
+
max_tokens: int = 16384,
|
|
373
|
+
**kwargs
|
|
374
|
+
) -> str:
|
|
375
|
+
"""Get a single chat completion (non-streaming)"""
|
|
376
|
+
result = []
|
|
377
|
+
async for chunk in self.chat_completion(
|
|
378
|
+
provider, messages, model, False, temperature, max_tokens, **kwargs
|
|
379
|
+
):
|
|
380
|
+
result.append(chunk)
|
|
381
|
+
return ''.join(result)
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
# Convenience functions
|
|
385
|
+
async def chat_with_hf(messages: List[Dict[str, str]], **kwargs) -> AsyncIterator[str]:
|
|
386
|
+
manager = ProviderManager()
|
|
387
|
+
async for chunk in manager.chat_completion(ProviderType.HF, messages, **kwargs):
|
|
388
|
+
yield chunk
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
async def chat_with_nvidia(messages: List[Dict[str, str]], **kwargs) -> AsyncIterator[str]:
|
|
392
|
+
manager = ProviderManager()
|
|
393
|
+
async for chunk in manager.chat_completion(ProviderType.NVIDIA, messages, **kwargs):
|
|
394
|
+
yield chunk
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
async def chat_with_openrouter(messages: List[Dict[str, str]], **kwargs) -> AsyncIterator[str]:
|
|
398
|
+
manager = ProviderManager()
|
|
399
|
+
async for chunk in manager.chat_completion(ProviderType.OPENROUTER, messages, **kwargs):
|
|
400
|
+
yield chunk
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
async def chat_with_ollama(messages: List[Dict[str, str]], **kwargs) -> AsyncIterator[str]:
|
|
404
|
+
manager = ProviderManager()
|
|
405
|
+
async for chunk in manager.chat_completion(ProviderType.OLLAMA, messages, **kwargs):
|
|
406
|
+
yield chunk
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def get_provider_status() -> Dict[str, Any]:
|
|
410
|
+
manager = ProviderManager()
|
|
411
|
+
return {
|
|
412
|
+
"available": manager.list_available_providers(),
|
|
413
|
+
"default": manager._select_best_provider().value if manager.clients else "none",
|
|
414
|
+
"config": {
|
|
415
|
+
p.value: {
|
|
416
|
+
"enabled": manager.config.get(p.value, True),
|
|
417
|
+
"api_key_set": manager._has_valid_api_key(p),
|
|
418
|
+
"key_preview": manager._get_api_key(p)[:8] + "..." if manager._get_api_key(p) else "Not set"
|
|
419
|
+
}
|
|
420
|
+
for p in ProviderType
|
|
421
|
+
if p != ProviderType.AUTO and p in manager.PROVIDERS
|
|
422
|
+
}
|
|
423
|
+
}
|