agentic-python-coder 3.4.2__tar.gz → 3.4.3__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 (65) hide show
  1. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/PKG-INFO +24 -18
  2. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/README.md +18 -14
  3. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/cli.py +6 -0
  4. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/kernel.py +7 -3
  5. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/llm.py +1 -1
  6. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gemini4argon.json +6 -0
  7. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gpt6astra.json +5 -0
  8. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/gpt6sol.json +5 -0
  9. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/grok47.json +5 -0
  10. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/opus55.json +5 -0
  11. agentic_python_coder-3.4.3/coder/src/agentic_python_coder/models/sonnet55.json +5 -0
  12. agentic_python_coder-3.4.3/coder/tests/test_kernel.py +212 -0
  13. agentic_python_coder-3.4.3/coder/tests/test_library_api.py +384 -0
  14. agentic_python_coder-3.4.3/coder/tests/test_llm_config.py +90 -0
  15. agentic_python_coder-3.4.3/coder/tests/test_mcp_hardening.py +275 -0
  16. agentic_python_coder-3.4.3/coder/tests/test_mcp_server.py +429 -0
  17. agentic_python_coder-3.4.3/coder/tests/test_react_loop.py +306 -0
  18. agentic_python_coder-3.4.3/coder/tests/test_todo_flag_integration.py +131 -0
  19. agentic_python_coder-3.4.3/coder/tests/test_todo_tool_availability.py +58 -0
  20. agentic_python_coder-3.4.3/coder/tests/test_tool_descriptions.py +74 -0
  21. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/pyproject.toml +5 -3
  22. agentic_python_coder-3.4.2/coder/src/agentic_python_coder/models/gemini3pro.json +0 -7
  23. agentic_python_coder-3.4.2/coder/src/agentic_python_coder/models/grok41.json +0 -6
  24. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/.gitignore +0 -0
  25. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/LICENSE +0 -0
  26. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/.gitignore +0 -0
  27. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/prompts/system.md +0 -0
  28. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/prompts/system_todo.md +0 -0
  29. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/__init__.py +0 -0
  30. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/agent.py +0 -0
  31. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/__init__.py +0 -0
  32. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/README.md +0 -0
  33. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/clingo.md +0 -0
  34. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/bird_reasoning.md +0 -0
  35. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/diagnosis.md +0 -0
  36. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/simple_coloring.md +0 -0
  37. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/stable_marriage.md +0 -0
  38. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/clingo/sample_tasks/sudoku_mini.md +0 -0
  39. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/README.md +0 -0
  40. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/cpmpy.md +0 -0
  41. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/magic_square.md +0 -0
  42. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/cpmpy/sample_tasks/n_queens.md +0 -0
  43. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/README.md +0 -0
  44. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/regex.md +0 -0
  45. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/email_extraction.md +0 -0
  46. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/phone_validation.md +0 -0
  47. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/examples/regex/sample_tasks/url_parsing.md +0 -0
  48. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/mcp_server.py +0 -0
  49. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/deepseek31.json +0 -0
  50. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini25.json +0 -0
  51. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini31.json +0 -0
  52. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gemini3flash.json +0 -0
  53. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt52.json +0 -0
  54. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt56sol.json +0 -0
  55. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/gpt56terra.json +0 -0
  56. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/opus45.json +0 -0
  57. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/opus48.json +0 -0
  58. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/qwen3.json +0 -0
  59. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet45.json +0 -0
  60. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet46.json +0 -0
  61. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/models/sonnet5.json +0 -0
  62. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/project_md.py +0 -0
  63. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/runner.py +0 -0
  64. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/coder/src/agentic_python_coder/tools.py +0 -0
  65. {agentic_python_coder-3.4.2 → agentic_python_coder-3.4.3}/examples/clingo/clingo.md +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: agentic-python-coder
3
- Version: 3.4.2
3
+ Version: 3.4.3
4
4
  Summary: A lightweight Python coding agent that writes, executes, and iterates on code through natural language instructions
5
5
  Author: Stefan Szeider
6
6
  License: Apache-2.0
@@ -11,12 +11,14 @@ Classifier: Intended Audience :: Developers
11
11
  Classifier: License :: OSI Approved :: Apache Software License
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Classifier: Programming Language :: Python :: 3.15
14
16
  Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
17
  Classifier: Topic :: Software Development :: Code Generators
16
- Requires-Python: <3.14,>=3.13
18
+ Requires-Python: >=3.13
17
19
  Requires-Dist: ipykernel>=7.3.0
18
20
  Requires-Dist: jupyter-client>=8.9.1
19
- Requires-Dist: mcp>=1.28.1
21
+ Requires-Dist: mcp<2,>=1.28.1
20
22
  Requires-Dist: openai>=2.45.0
21
23
  Requires-Dist: python-dotenv>=1.2.2
22
24
  Requires-Dist: pyyaml>=6.0.3
@@ -33,7 +35,7 @@ Description-Content-Type: text/markdown
33
35
 
34
36
  # Agentic Python Coder
35
37
 
36
- [![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/)
38
+ [![Python 3.13](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
37
39
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
38
40
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/)
39
41
  [![UV](https://img.shields.io/badge/Packaged%20with-UV-purple)](https://github.com/astral-sh/uv)
@@ -51,14 +53,14 @@ For details on architecture and constraint modelling applications, see [[Szeider
51
53
 
52
54
  ### Prerequisites
53
55
 
54
- - Python 3.13
56
+ - Python 3.13 or newer
55
57
  - UV package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
56
58
 
57
59
  ### For the Coding Agent
58
60
 
59
61
  ```bash
60
- # Install as CLI tool
61
- uv tool install agentic-python-coder
62
+ # Install as CLI tool (--python 3.14: ortools, used by cpmpy, has no 3.15 wheels yet)
63
+ uv tool install --python 3.14 agentic-python-coder
62
64
 
63
65
  # Set up OpenRouter API key
64
66
  mkdir -p ~/.config/coder
@@ -97,7 +99,7 @@ Add to your Claude Desktop MCP configuration:
97
99
  "mcpServers": {
98
100
  "ipython": {
99
101
  "command": "uvx",
100
- "args": ["--from", "agentic-python-coder", "ipython_mcp"]
102
+ "args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
101
103
  }
102
104
  }
103
105
  }
@@ -132,7 +134,7 @@ coder -i
132
134
  | `--version`, `-V` | Show version and exit |
133
135
  | `--init [TEMPLATE]` | Initialize example templates (cpmpy, clingo, regex, or all) |
134
136
  | `--task`, `-t FILE` | Load task from markdown file |
135
- | `--model MODEL` | Model name or JSON file (default: sonnet45) |
137
+ | `--model MODEL` | Model name or JSON file (default: sonnet55) |
136
138
  | `--project`, `-p FILE` | Project template for domain-specific prompts |
137
139
  | `--with PACKAGE` | Add packages dynamically (repeatable) |
138
140
  | `--dir`, `-d DIR` | Working directory |
@@ -146,21 +148,25 @@ coder -i
146
148
 
147
149
  ```bash
148
150
  # Built-in models (versioned names)
149
- coder --model sonnet45 "task" # Claude Sonnet 4.5 (default)
151
+ coder --model sonnet45 "task" # Claude Sonnet 4.5
150
152
  coder --model sonnet46 "task" # Claude Sonnet 4.6
151
153
  coder --model sonnet5 "task" # Claude Sonnet 5
152
154
  coder --model opus45 "task" # Claude Opus 4.5
153
155
  coder --model opus48 "task" # Claude Opus 4.8
156
+ coder --model sonnet55 "task" # Claude Sonnet 5.5 (default)
157
+ coder --model opus55 "task" # Claude Opus 5.5
154
158
  coder --model deepseek31 "task" # DeepSeek v3.1
155
- coder --model grok41 "task" # X.AI Grok 4.1
159
+ coder --model grok47 "task" # X.AI Grok 4.7
156
160
  coder --model qwen3 "task" # Qwen3 Coder
157
161
  coder --model gemini25 "task" # Gemini Pro 2.5
158
- coder --model gemini3pro "task" # Gemini 3 Pro Preview
159
162
  coder --model gemini31 "task" # Gemini 3.1 Pro Preview
160
163
  coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
161
164
  coder --model gpt52 "task" # GPT-5.2
162
165
  coder --model gpt56sol "task" # GPT-5.6 Sol
163
166
  coder --model gpt56terra "task" # GPT-5.6 Terra
167
+ coder --model gpt6sol "task" # GPT-6 Sol
168
+ coder --model gpt6astra "task" # GPT-6 Astra
169
+ coder --model gemini4argon "task" # Gemini 4 Argon
164
170
 
165
171
  # Custom model (JSON file)
166
172
  coder --model ./mymodel.json "task"
@@ -217,7 +223,7 @@ import agentic_python_coder as coder
217
223
  messages, stats, log_path = coder.solve_task(
218
224
  "Write a fibonacci function",
219
225
  working_directory="/tmp/workspace",
220
- model="sonnet45",
226
+ model="sonnet55",
221
227
  quiet=True,
222
228
  )
223
229
 
@@ -236,7 +242,7 @@ from agentic_python_coder import solve_task
236
242
  messages, stats, log_path = solve_task(
237
243
  task="Your task description",
238
244
  working_directory=".", # Where to run and save files
239
- model=None, # Model name: "sonnet45", "opus45", or JSON file
245
+ model=None, # Model name: "sonnet55", "opus55", or JSON file
240
246
  system_prompt=None, # Custom system prompt (string)
241
247
  system_prompt_path=None, # Path to system prompt file
242
248
  project_prompt=None, # Domain-specific context
@@ -300,9 +306,9 @@ agent = create_coding_agent(
300
306
  ```python
301
307
  from agentic_python_coder import get_openrouter_llm, list_available_models
302
308
 
303
- llm = get_openrouter_llm(model="sonnet45")
309
+ llm = get_openrouter_llm(model="sonnet55")
304
310
  print(list_available_models())
305
- # ['deepseek31', 'gemini25', 'gemini3flash', 'gemini3pro', 'gpt52', 'grok41', 'opus45', 'qwen3', 'sonnet45']
311
+ # ['deepseek31', 'gemini25', 'gemini31', 'gemini3flash', 'gpt52', ...]
306
312
  ```
307
313
 
308
314
  ---
@@ -320,7 +326,7 @@ Add to your MCP settings (e.g., `~/.claude/claude_desktop_config.json` or projec
320
326
  "mcpServers": {
321
327
  "ipython": {
322
328
  "command": "uvx",
323
- "args": ["--from", "agentic-python-coder", "ipython_mcp"]
329
+ "args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
324
330
  }
325
331
  }
326
332
  }
@@ -1,6 +1,6 @@
1
1
  # Agentic Python Coder
2
2
 
3
- [![Python 3.13](https://img.shields.io/badge/python-3.13-blue.svg)](https://www.python.org/downloads/)
3
+ [![Python 3.13](https://img.shields.io/badge/python-3.13%2B-blue.svg)](https://www.python.org/downloads/)
4
4
  [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
5
  [![MCP Compatible](https://img.shields.io/badge/MCP-Compatible-green.svg)](https://modelcontextprotocol.io/)
6
6
  [![UV](https://img.shields.io/badge/Packaged%20with-UV-purple)](https://github.com/astral-sh/uv)
@@ -18,14 +18,14 @@ For details on architecture and constraint modelling applications, see [[Szeider
18
18
 
19
19
  ### Prerequisites
20
20
 
21
- - Python 3.13
21
+ - Python 3.13 or newer
22
22
  - UV package manager: `curl -LsSf https://astral.sh/uv/install.sh | sh`
23
23
 
24
24
  ### For the Coding Agent
25
25
 
26
26
  ```bash
27
- # Install as CLI tool
28
- uv tool install agentic-python-coder
27
+ # Install as CLI tool (--python 3.14: ortools, used by cpmpy, has no 3.15 wheels yet)
28
+ uv tool install --python 3.14 agentic-python-coder
29
29
 
30
30
  # Set up OpenRouter API key
31
31
  mkdir -p ~/.config/coder
@@ -64,7 +64,7 @@ Add to your Claude Desktop MCP configuration:
64
64
  "mcpServers": {
65
65
  "ipython": {
66
66
  "command": "uvx",
67
- "args": ["--from", "agentic-python-coder", "ipython_mcp"]
67
+ "args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
68
68
  }
69
69
  }
70
70
  }
@@ -99,7 +99,7 @@ coder -i
99
99
  | `--version`, `-V` | Show version and exit |
100
100
  | `--init [TEMPLATE]` | Initialize example templates (cpmpy, clingo, regex, or all) |
101
101
  | `--task`, `-t FILE` | Load task from markdown file |
102
- | `--model MODEL` | Model name or JSON file (default: sonnet45) |
102
+ | `--model MODEL` | Model name or JSON file (default: sonnet55) |
103
103
  | `--project`, `-p FILE` | Project template for domain-specific prompts |
104
104
  | `--with PACKAGE` | Add packages dynamically (repeatable) |
105
105
  | `--dir`, `-d DIR` | Working directory |
@@ -113,21 +113,25 @@ coder -i
113
113
 
114
114
  ```bash
115
115
  # Built-in models (versioned names)
116
- coder --model sonnet45 "task" # Claude Sonnet 4.5 (default)
116
+ coder --model sonnet45 "task" # Claude Sonnet 4.5
117
117
  coder --model sonnet46 "task" # Claude Sonnet 4.6
118
118
  coder --model sonnet5 "task" # Claude Sonnet 5
119
119
  coder --model opus45 "task" # Claude Opus 4.5
120
120
  coder --model opus48 "task" # Claude Opus 4.8
121
+ coder --model sonnet55 "task" # Claude Sonnet 5.5 (default)
122
+ coder --model opus55 "task" # Claude Opus 5.5
121
123
  coder --model deepseek31 "task" # DeepSeek v3.1
122
- coder --model grok41 "task" # X.AI Grok 4.1
124
+ coder --model grok47 "task" # X.AI Grok 4.7
123
125
  coder --model qwen3 "task" # Qwen3 Coder
124
126
  coder --model gemini25 "task" # Gemini Pro 2.5
125
- coder --model gemini3pro "task" # Gemini 3 Pro Preview
126
127
  coder --model gemini31 "task" # Gemini 3.1 Pro Preview
127
128
  coder --model gemini3flash "task" # Gemini 3 Flash Preview (fast, low-cost)
128
129
  coder --model gpt52 "task" # GPT-5.2
129
130
  coder --model gpt56sol "task" # GPT-5.6 Sol
130
131
  coder --model gpt56terra "task" # GPT-5.6 Terra
132
+ coder --model gpt6sol "task" # GPT-6 Sol
133
+ coder --model gpt6astra "task" # GPT-6 Astra
134
+ coder --model gemini4argon "task" # Gemini 4 Argon
131
135
 
132
136
  # Custom model (JSON file)
133
137
  coder --model ./mymodel.json "task"
@@ -184,7 +188,7 @@ import agentic_python_coder as coder
184
188
  messages, stats, log_path = coder.solve_task(
185
189
  "Write a fibonacci function",
186
190
  working_directory="/tmp/workspace",
187
- model="sonnet45",
191
+ model="sonnet55",
188
192
  quiet=True,
189
193
  )
190
194
 
@@ -203,7 +207,7 @@ from agentic_python_coder import solve_task
203
207
  messages, stats, log_path = solve_task(
204
208
  task="Your task description",
205
209
  working_directory=".", # Where to run and save files
206
- model=None, # Model name: "sonnet45", "opus45", or JSON file
210
+ model=None, # Model name: "sonnet55", "opus55", or JSON file
207
211
  system_prompt=None, # Custom system prompt (string)
208
212
  system_prompt_path=None, # Path to system prompt file
209
213
  project_prompt=None, # Domain-specific context
@@ -267,9 +271,9 @@ agent = create_coding_agent(
267
271
  ```python
268
272
  from agentic_python_coder import get_openrouter_llm, list_available_models
269
273
 
270
- llm = get_openrouter_llm(model="sonnet45")
274
+ llm = get_openrouter_llm(model="sonnet55")
271
275
  print(list_available_models())
272
- # ['deepseek31', 'gemini25', 'gemini3flash', 'gemini3pro', 'gpt52', 'grok41', 'opus45', 'qwen3', 'sonnet45']
276
+ # ['deepseek31', 'gemini25', 'gemini31', 'gemini3flash', 'gpt52', ...]
273
277
  ```
274
278
 
275
279
  ---
@@ -287,7 +291,7 @@ Add to your MCP settings (e.g., `~/.claude/claude_desktop_config.json` or projec
287
291
  "mcpServers": {
288
292
  "ipython": {
289
293
  "command": "uvx",
290
- "args": ["--from", "agentic-python-coder", "ipython_mcp"]
294
+ "args": ["--python", "3.14", "--from", "agentic-python-coder", "ipython_mcp"]
291
295
  }
292
296
  }
293
297
  }
@@ -8,6 +8,8 @@ from pathlib import Path
8
8
  from typing import Optional, Dict, Any
9
9
  from importlib import resources
10
10
 
11
+ from openai import AuthenticationError
12
+
11
13
  from agentic_python_coder.runner import (
12
14
  solve_task,
13
15
  get_system_prompt_path,
@@ -485,6 +487,10 @@ def main():
485
487
  display_statistics(stats)
486
488
  print(f"Log saved to: {log_path}")
487
489
 
490
+ except (ValueError, AuthenticationError) as e:
491
+ # Configuration errors (missing or invalid API key): no traceback
492
+ print(f"Error: {e}")
493
+ sys.exit(1)
488
494
  except Exception as e:
489
495
  print(f"Error: {e}")
490
496
  import traceback
@@ -11,6 +11,7 @@ import os
11
11
  import re
12
12
  import shutil
13
13
  import signal
14
+ import sys
14
15
  import threading
15
16
  import time
16
17
  import uuid
@@ -101,6 +102,10 @@ class UVKernelManager(KernelManager):
101
102
  # Don't load project dependencies (create clean environment)
102
103
  uv_prefix.append("--no-project")
103
104
 
105
+ # Match the host's Python; uv's default may be a newer version for
106
+ # which packages (e.g. ortools) have no wheels yet
107
+ uv_prefix.extend(["--python", f"{sys.version_info[0]}.{sys.version_info[1]}"])
108
+
104
109
  # Add user-specified packages
105
110
  for pkg in self.with_packages:
106
111
  uv_prefix.extend(["--with", pkg])
@@ -175,9 +180,8 @@ class PythonKernel:
175
180
 
176
181
  # Wait for kernel to be ready
177
182
  logger.debug("Waiting for kernel to be ready...")
178
- self.kc.wait_for_ready(
179
- timeout=30
180
- ) # Increased timeout for UV package installation
183
+ # Generous timeout: a cold uv cache or a source build can take minutes
184
+ self.kc.wait_for_ready(timeout=180)
181
185
  logger.info("Kernel started successfully")
182
186
 
183
187
  # Configure IPython and inject startup code
@@ -11,7 +11,7 @@ from dotenv import load_dotenv
11
11
  from openai import OpenAI
12
12
 
13
13
  # Default model name (without .json)
14
- DEFAULT_MODEL = "sonnet45"
14
+ DEFAULT_MODEL = "sonnet55"
15
15
 
16
16
 
17
17
  @dataclass
@@ -0,0 +1,6 @@
1
+ {
2
+ "path": "google/gemini-4-argon",
3
+ "temperature": 0.0,
4
+ "max_tokens": 16384,
5
+ "request_timeout": 120
6
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "openai/gpt-6-astra",
3
+ "max_tokens": 16384,
4
+ "no_sampling_params": true
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "openai/gpt-6-sol",
3
+ "max_tokens": 16384,
4
+ "no_sampling_params": true
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "x-ai/grok-4.7",
3
+ "temperature": 0.0,
4
+ "max_tokens": 16384
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "anthropic/claude-opus-5.5",
3
+ "max_tokens": 32768,
4
+ "no_sampling_params": true
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "path": "anthropic/claude-sonnet-5.5",
3
+ "max_tokens": 32768,
4
+ "no_sampling_params": true
5
+ }
@@ -0,0 +1,212 @@
1
+ """Tests for the IPython kernel module."""
2
+
3
+ import pytest
4
+ from agentic_python_coder.kernel import (
5
+ PythonKernel,
6
+ get_kernel,
7
+ shutdown_kernel,
8
+ format_output,
9
+ )
10
+
11
+
12
+ @pytest.fixture
13
+ def kernel():
14
+ """Create a fresh kernel for each test."""
15
+ # Shutdown any existing global kernel
16
+ shutdown_kernel()
17
+ # Create a new kernel
18
+ k = PythonKernel()
19
+ yield k
20
+ # Cleanup
21
+ k.shutdown()
22
+ shutdown_kernel()
23
+
24
+
25
+ class TestKernelBasics:
26
+ """Test basic kernel execution."""
27
+
28
+ def test_simple_expression(self, kernel):
29
+ """Basic expression returns result."""
30
+ output = kernel.execute("2 + 2")
31
+ assert output["error"] is None
32
+ assert output["result"] == "4"
33
+
34
+ def test_print_output(self, kernel):
35
+ """Print statements captured in stdout."""
36
+ output = kernel.execute("print('hello world')")
37
+ assert output["error"] is None
38
+ assert "hello world" in output["stdout"]
39
+
40
+ def test_multiline_code(self, kernel):
41
+ """Multiline code executes correctly."""
42
+ code = """
43
+ x = 10
44
+ y = 20
45
+ x + y
46
+ """
47
+ output = kernel.execute(code)
48
+ assert output["error"] is None
49
+ assert output["result"] == "30"
50
+
51
+ def test_syntax_error(self, kernel):
52
+ """Syntax errors are captured."""
53
+ output = kernel.execute("def (")
54
+ assert output["error"] is not None
55
+ assert "SyntaxError" in output["error"]
56
+
57
+ def test_runtime_error(self, kernel):
58
+ """Runtime errors are captured."""
59
+ output = kernel.execute("1 / 0")
60
+ assert output["error"] is not None
61
+ assert "ZeroDivisionError" in output["error"]
62
+
63
+ def test_stderr_capture(self, kernel):
64
+ """Stderr is captured."""
65
+ output = kernel.execute("import sys; print('error', file=sys.stderr)")
66
+ assert "error" in output["stderr"]
67
+
68
+
69
+ class TestKernelStatePersistence:
70
+ """Test that state persists across executions."""
71
+
72
+ def test_variable_persistence(self, kernel):
73
+ """Variables persist between calls."""
74
+ kernel.execute("x = 42")
75
+ output = kernel.execute("x * 2")
76
+ assert output["result"] == "84"
77
+
78
+ def test_function_persistence(self, kernel):
79
+ """Functions persist between calls."""
80
+ kernel.execute("def double(n): return n * 2")
81
+ output = kernel.execute("double(21)")
82
+ assert output["result"] == "42"
83
+
84
+ def test_import_persistence(self, kernel):
85
+ """Imports persist between calls."""
86
+ kernel.execute("import math")
87
+ output = kernel.execute("math.pi")
88
+ assert "3.14" in output["result"]
89
+
90
+ def test_class_persistence(self, kernel):
91
+ """Classes persist between calls."""
92
+ kernel.execute("class Counter:\n def __init__(self):\n self.n = 0")
93
+ kernel.execute("c = Counter()")
94
+ kernel.execute("c.n = 5")
95
+ output = kernel.execute("c.n")
96
+ assert output["result"] == "5"
97
+
98
+
99
+ class TestMessageLoopFix:
100
+ """Test that message loop waits for idle status."""
101
+
102
+ def test_slow_print_captured(self, kernel):
103
+ """Slow output is fully captured (waits for idle, not Empty)."""
104
+ # This used to break early on Empty before the fix
105
+ code = """
106
+ import time
107
+ for i in range(5):
108
+ print(f"line {i}")
109
+ time.sleep(0.1)
110
+ print("done")
111
+ """
112
+ output = kernel.execute(code)
113
+ assert output["error"] is None
114
+ assert "line 0" in output["stdout"]
115
+ assert "line 4" in output["stdout"]
116
+ assert "done" in output["stdout"]
117
+
118
+ def test_delayed_result(self, kernel):
119
+ """Result from delayed computation is captured."""
120
+ code = """
121
+ import time
122
+ time.sleep(0.5)
123
+ 42 * 2
124
+ """
125
+ output = kernel.execute(code)
126
+ assert output["error"] is None
127
+ assert output["result"] == "84"
128
+
129
+
130
+ class TestFormatOutput:
131
+ """Test output formatting."""
132
+
133
+ def test_success_format(self):
134
+ """Successful output is formatted correctly."""
135
+ output = {"stdout": "hello", "stderr": "", "result": "42", "error": None}
136
+ result = format_output(output)
137
+ assert '"success": true' in result
138
+ assert '"stdout": "hello"' in result
139
+ assert '"result": "42"' in result
140
+
141
+ def test_error_format(self):
142
+ """Error output is formatted correctly."""
143
+ output = {
144
+ "stdout": "",
145
+ "stderr": "",
146
+ "result": None,
147
+ "error": "ValueError: bad",
148
+ }
149
+ result = format_output(output)
150
+ assert '"success": false' in result
151
+ assert "ValueError" in result
152
+
153
+ def test_none_values_removed(self):
154
+ """None values are removed from output."""
155
+ output = {"stdout": "", "stderr": None, "result": None, "error": None}
156
+ result = format_output(output)
157
+ assert "stderr" not in result
158
+ assert "result" not in result
159
+
160
+
161
+ class TestGlobalKernel:
162
+ """Test global kernel management."""
163
+
164
+ def test_get_kernel_creates_kernel(self):
165
+ """get_kernel creates a new kernel if none exists."""
166
+ shutdown_kernel() # Ensure clean state
167
+ k = get_kernel()
168
+ assert k is not None
169
+ output = k.execute("1 + 1")
170
+ assert output["result"] == "2"
171
+ shutdown_kernel()
172
+
173
+ def test_get_kernel_reuses_kernel(self):
174
+ """get_kernel reuses existing kernel with same config."""
175
+ shutdown_kernel()
176
+ k1 = get_kernel()
177
+ k1.execute("x = 123")
178
+ k2 = get_kernel()
179
+ output = k2.execute("x")
180
+ assert output["result"] == "123"
181
+ shutdown_kernel()
182
+
183
+ def test_shutdown_clears_state(self):
184
+ """shutdown_kernel clears the global kernel."""
185
+ shutdown_kernel()
186
+ k = get_kernel()
187
+ k.execute("y = 456")
188
+ shutdown_kernel()
189
+ k2 = get_kernel()
190
+ output = k2.execute("y")
191
+ assert output["error"] is not None
192
+ assert "NameError" in output["error"]
193
+ shutdown_kernel()
194
+
195
+
196
+ class TestDeadlineTimeout:
197
+ """Deadline overrun must report a timeout error, not silent success."""
198
+
199
+ def test_timeout_reports_error_and_interrupts(self, kernel):
200
+ output = kernel.execute("import time; time.sleep(30)", deadline_timeout=2)
201
+ assert output["error"] is not None
202
+ assert "timed out" in output["error"]
203
+
204
+ # The interrupt (plus grace period) must leave the kernel usable
205
+ output2 = kernel.execute("21 * 2")
206
+ assert output2["error"] is None
207
+ assert output2["result"] == "42"
208
+
209
+ def test_fast_code_unaffected_by_deadline(self, kernel):
210
+ output = kernel.execute("1 + 1", deadline_timeout=30)
211
+ assert output["error"] is None
212
+ assert output["result"] == "2"