nimcode 0.8.7__tar.gz → 0.8.8__tar.gz

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.
Files changed (58) hide show
  1. {nimcode-0.8.7 → nimcode-0.8.8}/PKG-INFO +1 -1
  2. nimcode-0.8.8/src/nimcode/__version__.py +1 -0
  3. nimcode-0.8.8/src/nimcode/model_registry.py +129 -0
  4. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/PKG-INFO +1 -1
  5. nimcode-0.8.8/tests/test_model_registry.py +90 -0
  6. nimcode-0.8.7/src/nimcode/__version__.py +0 -1
  7. nimcode-0.8.7/src/nimcode/model_registry.py +0 -55
  8. nimcode-0.8.7/tests/test_model_registry.py +0 -38
  9. {nimcode-0.8.7 → nimcode-0.8.8}/README.md +0 -0
  10. {nimcode-0.8.7 → nimcode-0.8.8}/setup.cfg +0 -0
  11. {nimcode-0.8.7 → nimcode-0.8.8}/setup.py +0 -0
  12. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/__init__.py +0 -0
  13. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/agent.py +0 -0
  14. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/agents/__init__.py +0 -0
  15. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/agents/qa_agent.py +0 -0
  16. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/cli.py +0 -0
  17. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/config.py +0 -0
  18. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/lenient_parser.py +0 -0
  19. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/mcp_client.py +0 -0
  20. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/memory.py +0 -0
  21. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/nim_client.py +0 -0
  22. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/permissions.py +0 -0
  23. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/plugin_manager.py +0 -0
  24. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/rag.py +0 -0
  25. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/repl.py +0 -0
  26. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/repo_map.py +0 -0
  27. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/task_manager.py +0 -0
  28. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/tools.py +0 -0
  29. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/updater.py +0 -0
  30. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode/watcher.py +0 -0
  31. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/SOURCES.txt +0 -0
  32. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/dependency_links.txt +0 -0
  33. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/entry_points.txt +0 -0
  34. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/requires.txt +0 -0
  35. {nimcode-0.8.7 → nimcode-0.8.8}/src/nimcode.egg-info/top_level.txt +0 -0
  36. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_advanced_blockers.py +0 -0
  37. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_agent.py +0 -0
  38. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_bash_blockers.py +0 -0
  39. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_cli.py +0 -0
  40. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_config.py +0 -0
  41. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_lazy_blockers.py +0 -0
  42. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_lenient_parser.py +0 -0
  43. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_mcp_client.py +0 -0
  44. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_memory.py +0 -0
  45. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_nim_client.py +0 -0
  46. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_permissions.py +0 -0
  47. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_plan_injection.py +0 -0
  48. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_plugin_manager.py +0 -0
  49. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_qa_agent.py +0 -0
  50. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_repl.py +0 -0
  51. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_repl_extra.py +0 -0
  52. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_repl_fixes.py +0 -0
  53. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_repl_trust.py +0 -0
  54. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_repo_map.py +0 -0
  55. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_task_manager.py +0 -0
  56. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_tools.py +0 -0
  57. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_updater.py +0 -0
  58. {nimcode-0.8.7 → nimcode-0.8.8}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nimcode
3
- Version: 0.8.7
3
+ Version: 0.8.8
4
4
  Summary: A standalone, robust coding agent for NVIDIA NIM models.
5
5
  Home-page: https://github.com/Batunash/nimcode
6
6
  Author: Autonomous Agent
@@ -0,0 +1 @@
1
+ __version__ = "0.8.8"
@@ -0,0 +1,129 @@
1
+ import logging
2
+ import os
3
+ import json
4
+ import urllib.request
5
+ import threading
6
+ import time
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Known NVIDIA NIM models and their context window sizes (in tokens)
11
+ # This acts as a fallback + enrichment layer when API is unavailable
12
+ MODEL_CONTEXT_WINDOWS = {
13
+ # Llama 3.1 family
14
+ "meta/llama-3.1-8b-instruct": 128000,
15
+ "meta/llama-3.3-70b-instruct": 128000,
16
+ "meta/llama-3.1-405b-instruct": 128000,
17
+ # Llama 3.2 family
18
+ "meta/llama-3.2-1b-instruct": 128000,
19
+ "meta/llama-3.2-3b-instruct": 128000,
20
+ "meta/llama-3.2-11b-vision-instruct": 128000,
21
+ "meta/llama-3.2-90b-vision-instruct": 128000,
22
+ # Llama 3.3 family
23
+ "meta/llama-3.3-70b-instruct": 128000,
24
+ # Nemotron
25
+ "nvidia/nemotron-4-340b-instruct": 4096,
26
+ "nvidia/llama-3.1-nemotron-70b-instruct": 128000,
27
+ # Mistral
28
+ "mistralai/mixtral-8x22b-instruct-v0.1": 65536,
29
+ "mistralai/mistral-large-2-instruct": 128000,
30
+ # DeepSeek
31
+ "deepseek-ai/deepseek-r1": 128000,
32
+ "deepseek-ai/deepseek-v4-pro": 128000,
33
+ # Qwen
34
+ "qwen/qwen2.5-72b-instruct": 128000,
35
+ "qwen/qwq-32b": 128000,
36
+ }
37
+
38
+ # Default context window for unknown models
39
+ DEFAULT_CONTEXT_WINDOW = 8192
40
+
41
+ # Hardcoded fallback model list (used when API is unreachable)
42
+ FALLBACK_MODELS = [
43
+ "meta/llama-3.3-70b-instruct",
44
+ "meta/llama-3.1-8b-instruct",
45
+ "meta/llama-3.1-405b-instruct",
46
+ "meta/llama-3.3-70b-instruct",
47
+ "nvidia/llama-3.1-nemotron-70b-instruct",
48
+ "deepseek-ai/deepseek-r1",
49
+ "mistralai/mixtral-8x22b-instruct-v0.1",
50
+ "qwen/qwen2.5-72b-instruct",
51
+ ]
52
+
53
+ _CACHE_FILE = os.path.expanduser("~/.nimcode/model_contexts.json")
54
+ _CACHE_MAX_AGE_DAYS = 7
55
+ _DYNAMIC_CACHE = {}
56
+ _CACHE_LOADED = False
57
+ _UPDATE_THREAD_STARTED = False
58
+
59
+ def _fetch_and_cache_models():
60
+ """Background task to fetch latest model info from LiteLLM."""
61
+ url = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"
62
+ try:
63
+ req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0 Nimcode/1.0'})
64
+ with urllib.request.urlopen(req, timeout=15) as response:
65
+ data = json.loads(response.read().decode('utf-8'))
66
+
67
+ processed = {}
68
+ for k, v in data.items():
69
+ if isinstance(v, dict) and "max_tokens" in v and isinstance(v["max_tokens"], int):
70
+ processed[k] = v["max_tokens"]
71
+ basename = k.split("/")[-1]
72
+ if basename not in processed or processed[basename] < v["max_tokens"]:
73
+ processed[basename] = v["max_tokens"]
74
+
75
+ os.makedirs(os.path.dirname(_CACHE_FILE), exist_ok=True)
76
+ with open(_CACHE_FILE, "w", encoding="utf-8") as f:
77
+ json.dump(processed, f, indent=4)
78
+
79
+ global _DYNAMIC_CACHE
80
+ _DYNAMIC_CACHE.update(processed)
81
+ logger.debug(f"Successfully cached {len(processed)} models from LiteLLM.")
82
+ except Exception as e:
83
+ logger.debug(f"Failed to fetch model contexts: {e}")
84
+
85
+ def _load_or_update_cache():
86
+ """Loads cache from disk, spawns background update if old/missing."""
87
+ global _CACHE_LOADED, _DYNAMIC_CACHE, _UPDATE_THREAD_STARTED
88
+ if _CACHE_LOADED:
89
+ return
90
+
91
+ _CACHE_LOADED = True
92
+ needs_update = True
93
+
94
+ if os.path.exists(_CACHE_FILE):
95
+ try:
96
+ with open(_CACHE_FILE, "r", encoding="utf-8") as f:
97
+ _DYNAMIC_CACHE = json.load(f)
98
+
99
+ # Check age
100
+ mtime = os.path.getmtime(_CACHE_FILE)
101
+ age_days = (time.time() - mtime) / (24 * 3600)
102
+ if age_days < _CACHE_MAX_AGE_DAYS:
103
+ needs_update = False
104
+ except Exception as e:
105
+ logger.debug(f"Error reading cache file: {e}")
106
+
107
+ if needs_update and not _UPDATE_THREAD_STARTED:
108
+ _UPDATE_THREAD_STARTED = True
109
+ t = threading.Thread(target=_fetch_and_cache_models, daemon=True)
110
+ t.start()
111
+
112
+ def get_context_window(model_id: str) -> int:
113
+ """Returns the context window size for a given model ID."""
114
+ _load_or_update_cache()
115
+
116
+ # 1. Check hardcoded exact match
117
+ if model_id in MODEL_CONTEXT_WINDOWS:
118
+ return MODEL_CONTEXT_WINDOWS[model_id]
119
+
120
+ # 2. Check dynamic cache exact match
121
+ if model_id in _DYNAMIC_CACHE:
122
+ return _DYNAMIC_CACHE[model_id]
123
+
124
+ # 3. Check dynamic cache basename match
125
+ basename = model_id.split("/")[-1]
126
+ if basename in _DYNAMIC_CACHE:
127
+ return _DYNAMIC_CACHE[basename]
128
+
129
+ return DEFAULT_CONTEXT_WINDOW
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nimcode
3
- Version: 0.8.7
3
+ Version: 0.8.8
4
4
  Summary: A standalone, robust coding agent for NVIDIA NIM models.
5
5
  Home-page: https://github.com/Batunash/nimcode
6
6
  Author: Autonomous Agent
@@ -0,0 +1,90 @@
1
+ import os
2
+ import json
3
+ import time
4
+ import pytest
5
+ from unittest.mock import patch, mock_open, MagicMock
6
+
7
+ import nimcode.model_registry as mr
8
+
9
+ @pytest.fixture(autouse=True)
10
+ def reset_registry_state():
11
+ """Reset the module-level state before each test."""
12
+ mr._CACHE_LOADED = False
13
+ mr._UPDATE_THREAD_STARTED = False
14
+ mr._DYNAMIC_CACHE = {}
15
+ yield
16
+
17
+ def test_get_context_window_hardcoded():
18
+ # Known model in hardcoded dictionary
19
+ assert mr.get_context_window("meta/llama-3.1-8b-instruct") == 128000
20
+
21
+ def test_get_context_window_unknown():
22
+ # Completely unknown model
23
+ assert mr.get_context_window("unknown/model-123") == mr.DEFAULT_CONTEXT_WINDOW
24
+
25
+ @patch("os.path.exists")
26
+ @patch("os.path.getmtime")
27
+ @patch("builtins.open", new_callable=mock_open, read_data='{"provider/test-model": 64000, "test-model": 64000}')
28
+ def test_get_context_window_from_cache(mock_file, mock_getmtime, mock_exists):
29
+ mock_exists.return_value = True
30
+ mock_getmtime.return_value = time.time() # Very recent cache
31
+
32
+ # Force reload
33
+ mr._load_or_update_cache()
34
+
35
+ assert mr.get_context_window("provider/test-model") == 64000
36
+ assert mr.get_context_window("other_provider/test-model") == 64000
37
+ assert mr._UPDATE_THREAD_STARTED is False # Cache is fresh, no thread spawned
38
+
39
+ @patch("os.path.exists")
40
+ @patch("threading.Thread")
41
+ def test_get_context_window_spawns_thread_if_no_cache(mock_thread, mock_exists):
42
+ mock_exists.return_value = False
43
+
44
+ # Should spawn thread to fetch cache
45
+ mr.get_context_window("some-model")
46
+
47
+ assert mr._UPDATE_THREAD_STARTED is True
48
+ mock_thread.assert_called_once()
49
+ mock_thread.return_value.start.assert_called_once()
50
+
51
+ @patch("os.path.exists")
52
+ @patch("os.path.getmtime")
53
+ @patch("threading.Thread")
54
+ @patch("builtins.open", new_callable=mock_open, read_data='{"old-model": 1000}')
55
+ def test_get_context_window_spawns_thread_if_old_cache(mock_file, mock_thread, mock_getmtime, mock_exists):
56
+ mock_exists.return_value = True
57
+ mock_getmtime.return_value = time.time() - (8 * 24 * 3600) # 8 days old
58
+
59
+ mr.get_context_window("old-model")
60
+
61
+ assert mr._UPDATE_THREAD_STARTED is True
62
+ mock_thread.assert_called_once()
63
+ mock_thread.return_value.start.assert_called_once()
64
+
65
+ @patch("urllib.request.urlopen")
66
+ def test_fetch_and_cache_models(mock_urlopen, tmp_path):
67
+ mock_response = MagicMock()
68
+ mock_response.read.return_value = b'''
69
+ {
70
+ "provider/model-A": {"max_tokens": 1234},
71
+ "provider/model-B": {"max_tokens": 5678, "other": "ignored"},
72
+ "provider/model-C": {"ignored": 10}
73
+ }
74
+ '''
75
+ mock_response.__enter__.return_value = mock_response
76
+ mock_urlopen.return_value = mock_response
77
+
78
+ with patch("nimcode.model_registry._CACHE_FILE", str(tmp_path / "cache.json")):
79
+ mr._fetch_and_cache_models()
80
+
81
+ assert "provider/model-A" in mr._DYNAMIC_CACHE
82
+ assert mr._DYNAMIC_CACHE["provider/model-A"] == 1234
83
+ assert mr._DYNAMIC_CACHE["model-A"] == 1234
84
+ assert "provider/model-C" not in mr._DYNAMIC_CACHE
85
+
86
+ # Verify file is written
87
+ with open(str(tmp_path / "cache.json"), "r") as f:
88
+ data = json.load(f)
89
+ assert data["provider/model-A"] == 1234
90
+ assert data["model-A"] == 1234
@@ -1 +0,0 @@
1
- __version__ = "0.8.7"
@@ -1,55 +0,0 @@
1
- """
2
- Model registry with context window sizes and metadata.
3
- Used for auto-compact thresholds and dynamic model listing.
4
- """
5
- import logging
6
-
7
- logger = logging.getLogger(__name__)
8
-
9
- # Known NVIDIA NIM models and their context window sizes (in tokens)
10
- # This acts as a fallback + enrichment layer when API is unavailable
11
- MODEL_CONTEXT_WINDOWS = {
12
- # Llama 3.1 family
13
- "meta/llama-3.1-8b-instruct": 128000,
14
- "meta/llama-3.3-70b-instruct": 128000,
15
- "meta/llama-3.1-405b-instruct": 128000,
16
- # Llama 3.2 family
17
- "meta/llama-3.2-1b-instruct": 128000,
18
- "meta/llama-3.2-3b-instruct": 128000,
19
- "meta/llama-3.2-11b-vision-instruct": 128000,
20
- "meta/llama-3.2-90b-vision-instruct": 128000,
21
- # Llama 3.3 family
22
- "meta/llama-3.3-70b-instruct": 128000,
23
- # Nemotron
24
- "nvidia/nemotron-4-340b-instruct": 4096,
25
- "nvidia/llama-3.1-nemotron-70b-instruct": 128000,
26
- # Mistral
27
- "mistralai/mixtral-8x22b-instruct-v0.1": 65536,
28
- "mistralai/mistral-large-2-instruct": 128000,
29
- # DeepSeek
30
- "deepseek-ai/deepseek-r1": 128000,
31
- "deepseek-ai/deepseek-v4-pro": 128000,
32
- # Qwen
33
- "qwen/qwen2.5-72b-instruct": 128000,
34
- "qwen/qwq-32b": 128000,
35
- }
36
-
37
- # Default context window for unknown models
38
- DEFAULT_CONTEXT_WINDOW = 8192
39
-
40
- # Hardcoded fallback model list (used when API is unreachable)
41
- FALLBACK_MODELS = [
42
- "meta/llama-3.3-70b-instruct",
43
- "meta/llama-3.1-8b-instruct",
44
- "meta/llama-3.1-405b-instruct",
45
- "meta/llama-3.3-70b-instruct",
46
- "nvidia/llama-3.1-nemotron-70b-instruct",
47
- "deepseek-ai/deepseek-r1",
48
- "mistralai/mixtral-8x22b-instruct-v0.1",
49
- "qwen/qwen2.5-72b-instruct",
50
- ]
51
-
52
-
53
- def get_context_window(model_id: str) -> int:
54
- """Returns the context window size for a given model ID."""
55
- return MODEL_CONTEXT_WINDOWS.get(model_id, DEFAULT_CONTEXT_WINDOW)
@@ -1,38 +0,0 @@
1
- """Tests for the model_registry module."""
2
- import pytest
3
- from nimcode.model_registry import (
4
- MODEL_CONTEXT_WINDOWS,
5
- FALLBACK_MODELS,
6
- DEFAULT_CONTEXT_WINDOW,
7
- get_context_window,
8
- )
9
-
10
-
11
- def test_get_context_window_known_model():
12
- """Known models should return their specific context window size."""
13
- assert get_context_window("meta/llama-3.3-70b-instruct") == 128000
14
- assert get_context_window("nvidia/nemotron-4-340b-instruct") == 4096
15
- assert get_context_window("mistralai/mixtral-8x22b-instruct-v0.1") == 65536
16
-
17
-
18
- def test_get_context_window_unknown_model():
19
- """Unknown models should return the default context window."""
20
- assert get_context_window("some/unknown-model") == DEFAULT_CONTEXT_WINDOW
21
- assert get_context_window("") == DEFAULT_CONTEXT_WINDOW
22
-
23
-
24
- def test_fallback_models_not_empty():
25
- """Fallback model list should have entries."""
26
- assert len(FALLBACK_MODELS) > 0
27
-
28
-
29
- def test_fallback_models_all_in_context_windows():
30
- """All fallback models should have a known context window entry."""
31
- for model in FALLBACK_MODELS:
32
- assert model in MODEL_CONTEXT_WINDOWS, f"Fallback model {model} missing from context windows"
33
-
34
-
35
- def test_context_windows_positive():
36
- """All context window values should be positive integers."""
37
- for model, ctx in MODEL_CONTEXT_WINDOWS.items():
38
- assert isinstance(ctx, int) and ctx > 0, f"Invalid context window for {model}: {ctx}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes