nimcode 0.8.3__tar.gz → 0.8.4__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 (56) hide show
  1. {nimcode-0.8.3 → nimcode-0.8.4}/PKG-INFO +1 -1
  2. nimcode-0.8.4/src/nimcode/__version__.py +1 -0
  3. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/agent.py +1 -1
  4. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/lenient_parser.py +22 -12
  5. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/PKG-INFO +1 -1
  6. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_lenient_parser.py +19 -4
  7. nimcode-0.8.3/src/nimcode/__version__.py +0 -1
  8. {nimcode-0.8.3 → nimcode-0.8.4}/README.md +0 -0
  9. {nimcode-0.8.3 → nimcode-0.8.4}/setup.cfg +0 -0
  10. {nimcode-0.8.3 → nimcode-0.8.4}/setup.py +0 -0
  11. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/__init__.py +0 -0
  12. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/agents/__init__.py +0 -0
  13. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/agents/qa_agent.py +0 -0
  14. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/cli.py +0 -0
  15. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/config.py +0 -0
  16. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/mcp_client.py +0 -0
  17. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/memory.py +0 -0
  18. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/model_registry.py +0 -0
  19. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/nim_client.py +0 -0
  20. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/permissions.py +0 -0
  21. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/plugin_manager.py +0 -0
  22. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/rag.py +0 -0
  23. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/repl.py +0 -0
  24. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/repo_map.py +0 -0
  25. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/task_manager.py +0 -0
  26. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/tools.py +0 -0
  27. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/updater.py +0 -0
  28. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode/watcher.py +0 -0
  29. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/SOURCES.txt +0 -0
  30. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/dependency_links.txt +0 -0
  31. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/entry_points.txt +0 -0
  32. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/requires.txt +0 -0
  33. {nimcode-0.8.3 → nimcode-0.8.4}/src/nimcode.egg-info/top_level.txt +0 -0
  34. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_advanced_blockers.py +0 -0
  35. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_agent.py +0 -0
  36. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_bash_blockers.py +0 -0
  37. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_cli.py +0 -0
  38. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_config.py +0 -0
  39. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_lazy_blockers.py +0 -0
  40. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_mcp_client.py +0 -0
  41. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_memory.py +0 -0
  42. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_model_registry.py +0 -0
  43. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_nim_client.py +0 -0
  44. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_permissions.py +0 -0
  45. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_plan_injection.py +0 -0
  46. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_plugin_manager.py +0 -0
  47. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_qa_agent.py +0 -0
  48. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_repl.py +0 -0
  49. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_repl_extra.py +0 -0
  50. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_repl_fixes.py +0 -0
  51. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_repl_trust.py +0 -0
  52. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_repo_map.py +0 -0
  53. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_task_manager.py +0 -0
  54. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_tools.py +0 -0
  55. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_updater.py +0 -0
  56. {nimcode-0.8.3 → nimcode-0.8.4}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nimcode
3
- Version: 0.8.3
3
+ Version: 0.8.4
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.4"
@@ -373,7 +373,7 @@ class Agent:
373
373
  else:
374
374
  display_text = parts[0]
375
375
 
376
- tool_tag_idx = display_text.find("<tool_call>")
376
+ tool_tag_idx = display_text.find("<tool_call")
377
377
  if tool_tag_idx != -1:
378
378
  display_text = display_text[:tool_tag_idx] + "\n\n*[dim]⚙️ Preparing tool execution...[/dim]*"
379
379
 
@@ -7,17 +7,22 @@ logger = logging.getLogger(__name__)
7
7
 
8
8
  class LenientParser:
9
9
  @staticmethod
10
- def extract_tool_calls(text: str) -> List[str]:
11
- """Extracts JSON strings from <tool_call> fenced blocks."""
12
- # The model might forget the closing tag, or put markdown fences around it.
13
- # We look for <tool_call> and extract everything until </tool_call> or EOF.
14
- pattern = re.compile(r"<tool_call>\s*(.*?)(?:</tool_call>|\Z)", re.DOTALL | re.IGNORECASE)
10
+ def extract_tool_calls(text: str) -> List[Tuple[str, str]]:
11
+ """Extracts tool names and JSON strings from <tool_call> fenced blocks."""
12
+ # We look for <tool_call> or <tool_call name="..."> and extract everything until </tool_call> or EOF.
13
+ pattern = re.compile(r"<tool_call([^>]*)>\s*(.*?)(?:</tool_call>|\Z)", re.DOTALL | re.IGNORECASE)
15
14
  matches = pattern.findall(text)
16
15
 
17
16
  # Clean up markdown code blocks if the model wrapped the JSON in them
18
17
  cleaned_matches = []
19
- for match in matches:
20
- match = match.strip()
18
+ for attr_str, inner_text in matches:
19
+ # Parse name from attributes if it exists (e.g., name="Read")
20
+ name = ""
21
+ name_match = re.search(r'name=["\'](.*?)["\']', attr_str, re.IGNORECASE)
22
+ if name_match:
23
+ name = name_match.group(1).strip()
24
+
25
+ match = inner_text.strip()
21
26
  if match.startswith("```json"):
22
27
  match = match[7:]
23
28
  elif match.startswith("```"):
@@ -26,7 +31,7 @@ class LenientParser:
26
31
  if match.endswith("```"):
27
32
  match = match[:-3]
28
33
 
29
- cleaned_matches.append(match.strip())
34
+ cleaned_matches.append((name, match.strip()))
30
35
 
31
36
  return cleaned_matches
32
37
 
@@ -86,14 +91,19 @@ class LenientParser:
86
91
  Returns (plain_text_message, list_of_tool_calls).
87
92
  """
88
93
  # First, extract the tool calls
89
- tool_call_strings = cls.extract_tool_calls(text)
94
+ tool_call_tuples = cls.extract_tool_calls(text)
90
95
 
91
96
  tool_calls = []
92
- for tc_str in tool_call_strings:
97
+ for name, tc_str in tool_call_tuples:
93
98
  if not tc_str:
94
99
  continue
95
100
  try:
96
- tc = cls.parse_tool_call(tc_str)
101
+ tc_json = cls.parse_tool_call(tc_str)
102
+ # If model used <tool_call name="..."> and provided raw arguments in JSON
103
+ if isinstance(tc_json, dict) and "tool" not in tc_json and name:
104
+ tc = {"tool": name, "args": tc_json}
105
+ else:
106
+ tc = tc_json
97
107
  tool_calls.append(tc)
98
108
  except ValueError:
99
109
  # We could append an error message or raise. We'll raise to let the agent loop handle it and re-prompt.
@@ -101,7 +111,7 @@ class LenientParser:
101
111
 
102
112
  # The plain text is whatever is not in the tool_call blocks.
103
113
  # We remove the tool call blocks from the original text to get the assistant's prose.
104
- plain_text = re.sub(r"<tool_call>\s*(.*?)(?:</tool_call>|\Z)", "", text, flags=re.DOTALL | re.IGNORECASE)
114
+ plain_text = re.sub(r"<tool_call[^>]*>\s*(.*?)(?:</tool_call>|\Z)", "", text, flags=re.DOTALL | re.IGNORECASE)
105
115
  plain_text = plain_text.strip()
106
116
 
107
117
  return plain_text, tool_calls
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nimcode
3
- Version: 0.8.3
3
+ Version: 0.8.4
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
@@ -5,7 +5,7 @@ def test_extract_tool_calls_clean():
5
5
  text = "Here is my tool call:\n<tool_call>\n{\"tool\": \"Read\", \"args\": {}}\n</tool_call>\nAnd some prose."
6
6
  calls = LenientParser.extract_tool_calls(text)
7
7
  assert len(calls) == 1
8
- assert calls[0] == "{\"tool\": \"Read\", \"args\": {}}"
8
+ assert calls[0] == ("", "{\"tool\": \"Read\", \"args\": {}}")
9
9
 
10
10
  def test_repair_truncated_json():
11
11
  # Test unclosed quote
@@ -19,19 +19,19 @@ def test_extract_tool_calls_unclosed():
19
19
  text = "Here is my tool call:\n<tool_call>\n{\"tool\": \"Read\"}"
20
20
  calls = LenientParser.extract_tool_calls(text)
21
21
  assert len(calls) == 1
22
- assert calls[0] == "{\"tool\": \"Read\"}"
22
+ assert calls[0] == ("", "{\"tool\": \"Read\"}")
23
23
 
24
24
  def test_extract_tool_calls_markdown_wrapped():
25
25
  text = "<tool_call>\n```json\n{\"tool\": \"Read\"}\n```\n</tool_call>"
26
26
  calls = LenientParser.extract_tool_calls(text)
27
27
  assert len(calls) == 1
28
- assert calls[0] == "{\"tool\": \"Read\"}"
28
+ assert calls[0] == ("", "{\"tool\": \"Read\"}")
29
29
 
30
30
  def test_extract_tool_calls_markdown_wrapped_no_lang():
31
31
  text = "<tool_call>\n```\n{\"tool\": \"Read\"}\n```\n</tool_call>"
32
32
  calls = LenientParser.extract_tool_calls(text)
33
33
  assert len(calls) == 1
34
- assert calls[0] == "{\"tool\": \"Read\"}"
34
+ assert calls[0] == ("", "{\"tool\": \"Read\"}")
35
35
 
36
36
  def test_repair_trailing_comma():
37
37
  bad_json = '{"tool": "Read", "args": {"file": "a.txt",},}'
@@ -65,3 +65,18 @@ def test_process_model_response_raises_on_invalid():
65
65
  text = "<tool_call>\n{invalid}\n</tool_call>"
66
66
  with pytest.raises(ValueError, match="Malformed tool call JSON"):
67
67
  LenientParser.process_model_response(text)
68
+
69
+ def test_extract_tool_calls_with_name_attr():
70
+ text = "Here is DeepSeek output:\n<tool_call name=\"Read\">\n{\"file_path\": \"a.txt\"}\n</tool_call>"
71
+ calls = LenientParser.extract_tool_calls(text)
72
+ assert len(calls) == 1
73
+ assert calls[0] == ("Read", "{\"file_path\": \"a.txt\"}")
74
+
75
+ def test_process_model_response_deepseek_format():
76
+ text = "Thinking... <tool_call name=\"Write\">\n{\"file_path\": \"a.txt\", \"content\": \"hello\"}\n</tool_call>"
77
+ prose, calls = LenientParser.process_model_response(text)
78
+ assert prose == "Thinking..."
79
+ assert len(calls) == 1
80
+ assert calls[0]["tool"] == "Write"
81
+ assert calls[0]["args"]["file_path"] == "a.txt"
82
+ assert calls[0]["args"]["content"] == "hello"
@@ -1 +0,0 @@
1
- __version__ = "0.8.3"
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