The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .gitignore
├── .python-version
├── LICENSE
├── README.md
├── bouncing_ball_hexagon.html
├── deepseek-eng.py
├── pyproject.toml
├── requirements.txt
├── solar_cube.html
└── uv.lock


/.gitignore:
--------------------------------------------------------------------------------
 1 | # Python
 2 | __pycache__/
 3 | *.py[cod]
 4 | *$py.class
 5 | *.so
 6 | .Python
 7 | build/
 8 | develop-eggs/
 9 | dist/
10 | downloads/
11 | eggs/
12 | .eggs/
13 | lib/
14 | lib64/
15 | parts/
16 | sdist/
17 | var/
18 | wheels/
19 | *.egg-info/
20 | .installed.cfg
21 | *.egg
22 | 
23 | # Virtual Environment
24 | venv/
25 | env/
26 | ENV/
27 | .env
28 | .venv
29 | pip-log.txt
30 | pip-delete-this-directory.txt
31 | 
32 | # IDE specific files
33 | .idea/
34 | .vscode/
35 | *.swp
36 | *.swo
37 | .project
38 | .pydevproject
39 | .settings/
40 | *.sublime-workspace
41 | *.sublime-project
42 | 
43 | # OS generated files
44 | .DS_Store
45 | .DS_Store?
46 | ._*
47 | .Spotlight-V100
48 | .Trashes
49 | ehthumbs.db
50 | Thumbs.db
51 | 
52 | # Logs and databases
53 | *.log
54 | *.sqlite
55 | *.db
56 | 
57 | # Coverage reports
58 | htmlcov/
59 | .tox/
60 | .coverage
61 | .coverage.*
62 | .cache
63 | nosetests.xml
64 | coverage.xml
65 | *.cover
66 | .hypothesis/
67 | 
68 | # Jupyter Notebook
69 | .ipynb_checkpoints
70 | 
71 | # API Keys and Secrets
72 | *.pem
73 | *.key
74 | *.cert
75 | secrets.yaml
76 | secrets.yml
77 | secrets.json
78 | 
79 | # Project specific
80 | node_modules/
81 | .pytest_cache/
82 | .mypy_cache/ 


--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------
1 | 3.11
2 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | MIT License
 2 | 
 3 | Copyright (c) 2025 DeepSeek Engineer
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE. 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
  1 | # DeepSeek Engineer v2 🐋
  2 | 
  3 | ## Overview
  4 | 
  5 | DeepSeek Engineer v2 is a powerful AI-powered coding assistant that provides an interactive terminal interface for seamless code development. It integrates with DeepSeek's advanced reasoning models to offer intelligent file operations, code analysis, and development assistance through natural conversation and function calling.
  6 | 
  7 | ## 🚀 Latest Update: Function Calling Architecture
  8 | 
  9 | **Version 2.0** introduces a big upgrade from structured JSON output to native function calling, providing:
 10 | - **Natural conversations** with the AI without rigid response formats
 11 | - **Automatic file operations** through intelligent function calls
 12 | - **Real-time reasoning visibility** with Chain of Thought (CoT) capabilities
 13 | - **Enhanced reliability** and better error handling
 14 | 
 15 | ## Key Features
 16 | 
 17 | ### 🧠 **AI Capabilities**
 18 | - **Elite Software Engineering**: Decades of experience across all programming domains
 19 | - **Chain of Thought Reasoning**: Visible thought process before providing solutions
 20 | - **Code Analysis & Discussion**: Expert-level insights and optimization suggestions
 21 | - **Intelligent Problem Solving**: Automatic file reading and context understanding
 22 | 
 23 | ### 🛠️ **Function Calling Tools**
 24 | The AI can automatically execute these operations when needed:
 25 | 
 26 | #### `read_file(file_path: str)`
 27 | - Read single file content with automatic path normalization
 28 | - Built-in error handling for missing or inaccessible files
 29 | - **Automatic**: AI can read any file you mention or reference in conversation
 30 | 
 31 | #### `read_multiple_files(file_paths: List[str])`
 32 | - Batch read multiple files efficiently
 33 | - Formatted output with clear file separators
 34 | 
 35 | #### `create_file(file_path: str, content: str)`
 36 | - Create new files or overwrite existing ones
 37 | - Automatic directory creation and safety checks
 38 | 
 39 | #### `create_multiple_files(files: List[Dict])`
 40 | - Create multiple files in a single operation
 41 | - Perfect for scaffolding projects or creating related files
 42 | 
 43 | #### `edit_file(file_path: str, original_snippet: str, new_snippet: str)`
 44 | - Precise snippet-based file editing
 45 | - Safe replacement with exact matching
 46 | 
 47 | ### 📁 **File Operations**
 48 | 
 49 | #### **Automatic File Reading (Recommended)**
 50 | The AI can automatically read files you mention:
 51 | ```
 52 | You> Can you review the main.py file and suggest improvements?
 53 | → AI automatically calls read_file("main.py")
 54 | 
 55 | You> Look at src/utils.py and tests/test_utils.py
 56 | → AI automatically calls read_multiple_files(["src/utils.py", "tests/test_utils.py"])
 57 | ```
 58 | 
 59 | #### **Manual Context Addition (Optional)**
 60 | For when you want to preload files into conversation context:
 61 | - **`/add path/to/file`** - Include single file in conversation context
 62 | - **`/add path/to/folder`** - Include entire directory (with smart filtering)
 63 | 
 64 | **Note**: The `/add` command is mainly useful when you want to provide extra context upfront. The AI can read files automatically via function calls whenever needed during the conversation.
 65 | 
 66 | ### 🎨 **Rich Terminal Interface**
 67 | - **Color-coded feedback** (green for success, red for errors, yellow for warnings)
 68 | - **Real-time streaming** with visible reasoning process
 69 | - **Structured tables** for diff previews
 70 | - **Progress indicators** for long operations
 71 | 
 72 | ### 🛡️ **Security & Safety**
 73 | - **Path normalization** and validation
 74 | - **Directory traversal protection**
 75 | - **File size limits** (5MB per file)
 76 | - **Binary file detection** and exclusion
 77 | 
 78 | ## Getting Started
 79 | 
 80 | ### Prerequisites
 81 | 1. **DeepSeek API Key**: Get your API key from [DeepSeek Platform](https://platform.deepseek.com)
 82 | 2. **Python 3.11+**: Required for optimal performance
 83 | 
 84 | ### Installation
 85 | 
 86 | 1. **Clone the repository**:
 87 |    ```bash
 88 |    git clone <repository-url>
 89 |    cd deepseek-engineer
 90 |    ```
 91 | 
 92 | 2. **Set up environment**:
 93 |    ```bash
 94 |    # Create .env file
 95 |    echo "DEEPSEEK_API_KEY=your_api_key_here" > .env
 96 |    ```
 97 | 
 98 | 3. **Install dependencies** (choose one method):
 99 | 
100 |    #### Using uv (recommended - faster)
101 |    ```bash
102 |    uv venv
103 |    uv run deepseek-eng.py
104 |    ```
105 | 
106 |    #### Using pip
107 |    ```bash
108 |    pip install -r requirements.txt
109 |    python3 deepseek-eng.py
110 |    ```
111 | 
112 | ### Usage Examples
113 | 
114 | #### **Natural Conversation with Automatic File Operations**
115 | ```
116 | You> Can you read the main.py file and create a test file for it?
117 | 
118 | 💭 Reasoning: I need to first read the main.py file to understand its structure...
119 | 
120 | 🤖 Assistant> I'll read the main.py file first to understand its structure.
121 | ⚡ Executing 1 function call(s)...
122 | → read_file
123 | ✓ Read file 'main.py'
124 | 
125 | 🔄 Processing results...
126 | Now I'll create comprehensive tests based on the code structure I found.
127 | ⚡ Executing 1 function call(s)...
128 | → create_file
129 | ✓ Created/updated file at 'test_main.py'
130 | 
131 | I've analyzed main.py and created comprehensive tests covering all the main functions...
132 | ```
133 | 
134 | #### **Automatic Multi-File Analysis**
135 | ```
136 | You> Compare the implementation in utils.py with the tests in test_utils.py
137 | 
138 | 💭 Reasoning: I need to read both files to compare them...
139 | 
140 | 🤖 Assistant> I'll read both files to analyze the implementation and tests.
141 | ⚡ Executing 1 function call(s)...
142 | → read_multiple_files
143 | ✓ Read files: utils.py, test_utils.py
144 | 
145 | 🔄 Processing results...
146 | After analyzing both files, I can see several areas where the tests could be improved...
147 | ```
148 | 
149 | #### **Manual Context Loading (Optional)**
150 | ```
151 | You> /add src/
152 | 
153 | ✓ Added folder 'src/' to conversation.
154 | 📁 Added files: (15 files)
155 |   📄 src/utils.py
156 |   📄 src/models.py
157 |   ...
158 | 
159 | You> Now review this codebase structure
160 | 
161 | 🤖 Assistant> I've reviewed your codebase and found several areas for improvement:
162 | 
163 | 1. **Error Handling**: The utils.py file could benefit from more robust error handling...
164 | ```
165 | 
166 | ## Technical Details
167 | 
168 | ### **Model**: DeepSeek-Reasoner
169 | - Powered by DeepSeek-R1 with Chain-of-Thought reasoning
170 | - Real-time reasoning visibility during processing
171 | - Enhanced problem-solving capabilities
172 | 
173 | ### **Function Call Execution Flow**
174 | 1. **User Input** → Natural language request
175 | 2. **AI Reasoning** → Visible thought process (CoT)
176 | 3. **Function Calls** → Automatic tool execution
177 | 4. **Real-time Feedback** → Operation status and results
178 | 5. **Follow-up Response** → AI processes results and responds
179 | 
180 | ### **Streaming Architecture**
181 | - **Triple-stream processing**: reasoning + content + tool_calls
182 | - **Real-time tool execution** during streaming
183 | - **Automatic follow-up** responses after tool completion
184 | - **Error recovery** and graceful degradation
185 | 
186 | ## Advanced Features
187 | 
188 | ### **Intelligent Context Management**
189 | - **Automatic file detection** from user messages
190 | - **Smart conversation cleanup** to prevent token overflow
191 | - **File content preservation** across conversation history
192 | - **Tool message integration** for complete operation tracking
193 | 
194 | ### **Batch Operations**
195 | ```
196 | You> Create a complete Flask API with models, routes, and tests
197 | 
198 | 🤖 Assistant> I'll create a complete Flask API structure for you.
199 | ⚡ Executing 1 function call(s)...
200 | → create_multiple_files
201 | ✓ Created 4 files: app.py, models.py, routes.py, test_api.py
202 | ```
203 | 
204 | ### **Project Analysis**
205 | ```
206 | You> /add .
207 | You> Analyze this entire project and suggest a refactoring plan
208 | 
209 | 🤖 Assistant> ⚡ Executing 1 function call(s)...
210 | → read_multiple_files
211 | Based on my analysis of your project, here's a comprehensive refactoring plan...
212 | ```
213 | 
214 | ## File Operations Comparison
215 | 
216 | | Method | When to Use | How It Works |
217 | |--------|-------------|--------------|
218 | | **Automatic Reading** | Most cases - just mention files | AI automatically calls `read_file()` when you reference files |
219 | | **`/add` Command** | Preload context, bulk operations | Manually adds files to conversation context upfront |
220 | 
221 | **Recommendation**: Use natural conversation - the AI will automatically read files as needed. Use `/add` only when you want to provide extra context upfront.
222 | 
223 | ## Troubleshooting
224 | 
225 | ### **Common Issues**
226 | 
227 | **API Key Not Found**
228 | ```bash
229 | # Make sure .env file exists with your API key
230 | echo "DEEPSEEK_API_KEY=your_key_here" > .env
231 | ```
232 | 
233 | **Import Errors**
234 | ```bash
235 | # Install dependencies
236 | uv sync  # or pip install -r requirements.txt
237 | ```
238 | 
239 | **File Permission Errors**
240 | - Ensure you have write permissions in the working directory
241 | - Check file paths are correct and accessible
242 | 
243 | ## Contributing
244 | 
245 | This is an experimental project showcasing DeepSeek reasoning model capabilities. Contributions are welcome!
246 | 
247 | ### **Development Setup**
248 | ```bash
249 | git clone <repository-url>
250 | cd deepseek-engineer
251 | uv venv
252 | uv sync
253 | ```
254 | 
255 | ### **Run**
256 | ```bash
257 | # Run the application (preferred)
258 | uv run deepseek-eng.py
259 | ```
260 | or
261 | ```bash
262 | python3 deepseek-eng.py
263 | ```
264 | 
265 | ## License
266 | 
267 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
268 | 
269 | This project is experimental and developed for testing DeepSeek reasoning model capabilities.
270 | 
271 | ---
272 | 
273 | > **Note**: This is an experimental project developed to explore the capabilities of DeepSeek's reasoning model with function calling. The AI can automatically read files you mention in conversation, while the `/add` command is available for when you want to preload context. Use responsibly and enjoy the enhanced AI pair programming experience! 🚀
274 | 
275 | 


--------------------------------------------------------------------------------
/bouncing_ball_hexagon.html:
--------------------------------------------------------------------------------
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | <head>
  4 |     <meta charset="UTF-8">
  5 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 |     <title>Bouncing Ball in Spinning Hexagon</title>
  7 |     <style>
  8 |         body {
  9 |             margin: 0;
 10 |             padding: 0;
 11 |             display: flex;
 12 |             justify-content: center;
 13 |             align-items: center;
 14 |             height: 100vh;
 15 |             background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
 16 |             overflow: hidden;
 17 |             font-family: 'Arial', sans-serif;
 18 |         }
 19 |         
 20 |         .container {
 21 |             text-align: center;
 22 |         }
 23 |         
 24 |         h1 {
 25 |             color: white;
 26 |             text-shadow: 0 0 10px rgba(0,0,0,0.5);
 27 |             margin-bottom: 20px;
 28 |         }
 29 |         
 30 |         canvas {
 31 |             background-color: rgba(0, 0, 0, 0.7);
 32 |             border-radius: 10px;
 33 |             box-shadow: 0 0 20px rgba(0,0,0,0.5);
 34 |         }
 35 |         
 36 |         .controls {
 37 |             margin-top: 20px;
 38 |             color: white;
 39 |         }
 40 |         
 41 |         .controls label {
 42 |             margin-right: 10px;
 43 |         }
 44 |         
 45 |         .controls input {
 46 |             width: 80px;
 47 |             margin-right: 20px;
 48 |         }
 49 |     </style>
 50 | </head>
 51 | <body>
 52 |     <div class="container">
 53 |         <h1>Ball in Spinning Hexagon</h1>
 54 |         <canvas id="gameCanvas" width="600" height="600"></canvas>
 55 |         <div class="controls">
 56 |             <label>Gravity: <input type="range" id="gravitySlider" min="0" max="1" step="0.01" value="0.2"></label>
 57 |             <label>Friction: <input type="range" id="frictionSlider" min="0" max="0.1" step="0.001" value="0.01"></label>
 58 |             <label>Rotation: <input type="range" id="rotationSlider" min="0" max="0.1" step="0.001" value="0.01"></label>
 59 |         </div>
 60 |     </div>
 61 | 
 62 |     <script>
 63 |         // Canvas setup
 64 |         const canvas = document.getElementById('gameCanvas');
 65 |         const ctx = canvas.getContext('2d');
 66 |         const centerX = canvas.width / 2;
 67 |         const centerY = canvas.height / 2;
 68 |         
 69 |         // Physics parameters
 70 |         let gravity = 0.2;
 71 |         let friction = 0.01;
 72 |         let rotationSpeed = 0.01;
 73 |         
 74 |         // Ball properties
 75 |         const ball = {
 76 |             x: centerX,
 77 |             y: centerY,
 78 |             radius: 15,
 79 |             velocityX: 2,
 80 |             velocityY: 0,
 81 |             color: '#FF5252'
 82 |         };
 83 |         
 84 |         // Hexagon properties
 85 |         const hexagon = {
 86 |             radius: 200,
 87 |             rotation: 0,
 88 |             color: '#29B6F6'
 89 |         };
 90 |         
 91 |         // Slider controls
 92 |         document.getElementById('gravitySlider').addEventListener('input', (e) => {
 93 |             gravity = parseFloat(e.target.value);
 94 |         });
 95 |         
 96 |         document.getElementById('frictionSlider').addEventListener('input', (e) => {
 97 |             friction = parseFloat(e.target.value);
 98 |         });
 99 |         
100 |         document.getElementById('rotationSlider').addEventListener('input', (e) => {
101 |             rotationSpeed = parseFloat(e.target.value);
102 |         });
103 |         
104 |         // Draw hexagon
105 |         function drawHexagon() {
106 |             ctx.save();
107 |             ctx.translate(centerX, centerY);
108 |             ctx.rotate(hexagon.rotation);
109 |             
110 |             ctx.beginPath();
111 |             for (let i = 0; i < 6; i++) {
112 |                 const angle = (Math.PI / 3) * i;
113 |                 const x = hexagon.radius * Math.cos(angle);
114 |                 const y = hexagon.radius * Math.sin(angle);
115 |                 
116 |                 if (i === 0) {
117 |                     ctx.moveTo(x, y);
118 |                 } else {
119 |                     ctx.lineTo(x, y);
120 |                 }
121 |             }
122 |             
123 |             ctx.closePath();
124 |             ctx.strokeStyle = hexagon.color;
125 |             ctx.lineWidth = 4;
126 |             ctx.stroke();
127 |             
128 |             ctx.restore();
129 |         }
130 |         
131 |         // Draw ball
132 |         function drawBall() {
133 |             ctx.beginPath();
134 |             ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
135 |             ctx.fillStyle = ball.color;
136 |             
137 |             // Add gradient for 3D effect
138 |             const gradient = ctx.createRadialGradient(
139 |                 ball.x, ball.y, 0,
140 |                 ball.x, ball.y, ball.radius
141 |             );
142 |             gradient.addColorStop(0, '#FF8A80');
143 |             gradient.addColorStop(1, '#D32F2F');
144 |             ctx.fillStyle = gradient;
145 |             
146 |             ctx.fill();
147 |             ctx.closePath();
148 |         }
149 |         
150 |         // Calculate distance from point to line segment
151 |         function distanceToSegment(point, lineStart, lineEnd) {
152 |             const A = point.x - lineStart.x;
153 |             const B = point.y - lineStart.y;
154 |             const C = lineEnd.x - lineStart.x;
155 |             const D = lineEnd.y - lineStart.y;
156 |             
157 |             const dot = A * C + B * D;
158 |             const lenSq = C * C + D * D;
159 |             let param = -1;
160 |             
161 |             if (lenSq !== 0) {
162 |                 param = dot / lenSq;
163 |             }
164 |             
165 |             let xx, yy;
166 |             
167 |             if (param < 0) {
168 |                 xx = lineStart.x;
169 |                 yy = lineStart.y;
170 |             } else if (param > 1) {
171 |                 xx = lineEnd.x;
172 |                 yy = lineEnd.y;
173 |             } else {
174 |                 xx = lineStart.x + param * C;
175 |                 yy = lineStart.y + param * D;
176 |             }
177 |             
178 |             const dx = point.x - xx;
179 |             const dy = point.y - yy;
180 |             return Math.sqrt(dx * dx + dy * dy);
181 |         }
182 |         
183 |         // Get hexagon vertices
184 |         function getHexagonVertices() {
185 |             const vertices = [];
186 |             for (let i = 0; i < 6; i++) {
187 |                 const angle = (Math.PI / 3) * i + hexagon.rotation;
188 |                 vertices.push({
189 |                     x: centerX + hexagon.radius * Math.cos(angle),
190 |                     y: centerY + hexagon.radius * Math.sin(angle)
191 |                 });
192 |             }
193 |             return vertices;
194 |         }
195 |         
196 |         // Check collision and handle bounce
197 |         function checkCollision() {
198 |             const vertices = getHexagonVertices();
199 |             
200 |             for (let i = 0; i < 6; i++) {
201 |                 const start = vertices[i];
202 |                 const end = vertices[(i + 1) % 6];
203 |                 
204 |                 const distance = distanceToSegment(
205 |                     {x: ball.x, y: ball.y},
206 |                     start,
207 |                     end
208 |                 );
209 |                 
210 |                 if (distance <= ball.radius) {
211 |                     // Calculate normal vector to the wall
212 |                     const wallVec = {x: end.x - start.x, y: end.y - start.y};
213 |                     const length = Math.sqrt(wallVec.x * wallVec.x + wallVec.y * wallVec.y);
214 |                     const normal = {x: -wallVec.y / length, y: wallVec.x / length};
215 |                     
216 |                     // Calculate dot product of velocity and normal
217 |                     const dot = ball.velocityX * normal.x + ball.velocityY * normal.y;
218 |                     
219 |                     // Apply reflection
220 |                     ball.velocityX = ball.velocityX - 2 * dot * normal.x;
221 |                     ball.velocityY = ball.velocityY - 2 * dot * normal.y;
222 |                     
223 |                     // Apply energy loss (damping)
224 |                     ball.velocityX *= (1 - friction);
225 |                     ball.velocityY *= (1 - friction);
226 |                     
227 |                     // Move ball outside collision range
228 |                     const overlap = ball.radius - distance;
229 |                     ball.x += normal.x * overlap * 1.1;
230 |                     ball.y += normal.y * overlap * 1.1;
231 |                 }
232 |             }
233 |         }
234 |         
235 |         // Update physics
236 |         function updatePhysics() {
237 |             // Apply gravity
238 |             ball.velocityY += gravity;
239 |             
240 |             // Apply friction
241 |             ball.velocityX *= (1 - friction);
242 |             ball.velocityY *= (1 - friction);
243 |             
244 |             // Update position
245 |             ball.x += ball.velocityX;
246 |             ball.y += ball.velocityY;
247 |             
248 |             // Rotate hexagon
249 |             hexagon.rotation += rotationSpeed;
250 |             if (hexagon.rotation > Math.PI * 2) {
251 |                 hexagon.rotation -= Math.PI * 2;
252 |             }
253 |         }
254 |         
255 |         // Animation loop
256 |         function animate() {
257 |             // Clear canvas
258 |             ctx.clearRect(0, 0, canvas.width, canvas.height);
259 |             
260 |             // Draw and update
261 |             drawHexagon();
262 |             updatePhysics();
263 |             checkCollision();
264 |             drawBall();
265 |             
266 |             // Continue animation
267 |             requestAnimationFrame(animate);
268 |         }
269 |         
270 |         // Start animation
271 |         animate();
272 |     </script>
273 | </body>
274 | </html>


--------------------------------------------------------------------------------
/deepseek-eng.py:
--------------------------------------------------------------------------------
  1 | #!/usr/bin/env python3
  2 | 
  3 | import os
  4 | import sys
  5 | import json
  6 | from pathlib import Path
  7 | from textwrap import dedent
  8 | from typing import List, Dict, Any, Optional
  9 | from openai import OpenAI
 10 | from pydantic import BaseModel
 11 | from dotenv import load_dotenv
 12 | from rich.console import Console
 13 | from rich.table import Table
 14 | from rich.panel import Panel
 15 | from rich.style import Style
 16 | from prompt_toolkit import PromptSession
 17 | from prompt_toolkit.styles import Style as PromptStyle
 18 | import time
 19 | 
 20 | # Initialize Rich console and prompt session
 21 | console = Console()
 22 | prompt_session = PromptSession(
 23 |     style=PromptStyle.from_dict({
 24 |         'prompt': '#0066ff bold',  # Bright blue prompt
 25 |         'completion-menu.completion': 'bg:#1e3a8a fg:#ffffff',
 26 |         'completion-menu.completion.current': 'bg:#3b82f6 fg:#ffffff bold',
 27 |     })
 28 | )
 29 | 
 30 | # --------------------------------------------------------------------------------
 31 | # 1. Configure OpenAI client and load environment variables
 32 | # --------------------------------------------------------------------------------
 33 | load_dotenv()  # Load environment variables from .env file
 34 | client = OpenAI(
 35 |     api_key=os.getenv("DEEPSEEK_API_KEY"),
 36 |     base_url="https://api.deepseek.com"
 37 | )  # Configure for DeepSeek API
 38 | 
 39 | # --------------------------------------------------------------------------------
 40 | # 2. Define our schema using Pydantic for type safety
 41 | # --------------------------------------------------------------------------------
 42 | class FileToCreate(BaseModel):
 43 |     path: str
 44 |     content: str
 45 | 
 46 | class FileToEdit(BaseModel):
 47 |     path: str
 48 |     original_snippet: str
 49 |     new_snippet: str
 50 | 
 51 | # Remove AssistantResponse as we're using function calling now
 52 | 
 53 | # --------------------------------------------------------------------------------
 54 | # 2.1. Define Function Calling Tools
 55 | # --------------------------------------------------------------------------------
 56 | tools = [
 57 |     {
 58 |         "type": "function",
 59 |         "function": {
 60 |             "name": "read_file",
 61 |             "description": "Read the content of a single file from the filesystem",
 62 |             "parameters": {
 63 |                 "type": "object",
 64 |                 "properties": {
 65 |                     "file_path": {
 66 |                         "type": "string",
 67 |                         "description": "The path to the file to read (relative or absolute)",
 68 |                     }
 69 |                 },
 70 |                 "required": ["file_path"]
 71 |             },
 72 |         }
 73 |     },
 74 |     {
 75 |         "type": "function",
 76 |         "function": {
 77 |             "name": "read_multiple_files",
 78 |             "description": "Read the content of multiple files from the filesystem",
 79 |             "parameters": {
 80 |                 "type": "object",
 81 |                 "properties": {
 82 |                     "file_paths": {
 83 |                         "type": "array",
 84 |                         "items": {"type": "string"},
 85 |                         "description": "Array of file paths to read (relative or absolute)",
 86 |                     }
 87 |                 },
 88 |                 "required": ["file_paths"]
 89 |             },
 90 |         }
 91 |     },
 92 |     {
 93 |         "type": "function",
 94 |         "function": {
 95 |             "name": "create_file",
 96 |             "description": "Create a new file or overwrite an existing file with the provided content",
 97 |             "parameters": {
 98 |                 "type": "object",
 99 |                 "properties": {
100 |                     "file_path": {
101 |                         "type": "string",
102 |                         "description": "The path where the file should be created",
103 |                     },
104 |                     "content": {
105 |                         "type": "string",
106 |                         "description": "The content to write to the file",
107 |                     }
108 |                 },
109 |                 "required": ["file_path", "content"]
110 |             },
111 |         }
112 |     },
113 |     {
114 |         "type": "function",
115 |         "function": {
116 |             "name": "create_multiple_files",
117 |             "description": "Create multiple files at once",
118 |             "parameters": {
119 |                 "type": "object",
120 |                 "properties": {
121 |                     "files": {
122 |                         "type": "array",
123 |                         "items": {
124 |                             "type": "object",
125 |                             "properties": {
126 |                                 "path": {"type": "string"},
127 |                                 "content": {"type": "string"}
128 |                             },
129 |                             "required": ["path", "content"]
130 |                         },
131 |                         "description": "Array of files to create with their paths and content",
132 |                     }
133 |                 },
134 |                 "required": ["files"]
135 |             },
136 |         }
137 |     },
138 |     {
139 |         "type": "function",
140 |         "function": {
141 |             "name": "edit_file",
142 |             "description": "Edit an existing file by replacing a specific snippet with new content",
143 |             "parameters": {
144 |                 "type": "object",
145 |                 "properties": {
146 |                     "file_path": {
147 |                         "type": "string",
148 |                         "description": "The path to the file to edit",
149 |                     },
150 |                     "original_snippet": {
151 |                         "type": "string",
152 |                         "description": "The exact text snippet to find and replace",
153 |                     },
154 |                     "new_snippet": {
155 |                         "type": "string",
156 |                         "description": "The new text to replace the original snippet with",
157 |                     }
158 |                 },
159 |                 "required": ["file_path", "original_snippet", "new_snippet"]
160 |             },
161 |         }
162 |     }
163 | ]
164 | 
165 | # --------------------------------------------------------------------------------
166 | # 3. system prompt
167 | # --------------------------------------------------------------------------------
168 | system_PROMPT = dedent("""\
169 |     You are an elite software engineer called DeepSeek Engineer with decades of experience across all programming domains.
170 |     Your expertise spans system design, algorithms, testing, and best practices.
171 |     You provide thoughtful, well-structured solutions while explaining your reasoning.
172 | 
173 |     Core capabilities:
174 |     1. Code Analysis & Discussion
175 |        - Analyze code with expert-level insight
176 |        - Explain complex concepts clearly
177 |        - Suggest optimizations and best practices
178 |        - Debug issues with precision
179 | 
180 |     2. File Operations (via function calls):
181 |        - read_file: Read a single file's content
182 |        - read_multiple_files: Read multiple files at once
183 |        - create_file: Create or overwrite a single file
184 |        - create_multiple_files: Create multiple files at once
185 |        - edit_file: Make precise edits to existing files using snippet replacement
186 | 
187 |     Guidelines:
188 |     1. Provide natural, conversational responses explaining your reasoning
189 |     2. Use function calls when you need to read or modify files
190 |     3. For file operations:
191 |        - Always read files first before editing them to understand the context
192 |        - Use precise snippet matching for edits
193 |        - Explain what changes you're making and why
194 |        - Consider the impact of changes on the overall codebase
195 |     4. Follow language-specific best practices
196 |     5. Suggest tests or validation steps when appropriate
197 |     6. Be thorough in your analysis and recommendations
198 | 
199 |     IMPORTANT: In your thinking process, if you realize that something requires a tool call, cut your thinking short and proceed directly to the tool call. Don't overthink - act efficiently when file operations are needed.
200 | 
201 |     Remember: You're a senior engineer - be thoughtful, precise, and explain your reasoning clearly.
202 | """)
203 | 
204 | # --------------------------------------------------------------------------------
205 | # 4. Helper functions 
206 | # --------------------------------------------------------------------------------
207 | 
208 | def read_local_file(file_path: str) -> str:
209 |     """Return the text content of a local file."""
210 |     with open(file_path, "r", encoding="utf-8") as f:
211 |         return f.read()
212 | 
213 | def create_file(path: str, content: str):
214 |     """Create (or overwrite) a file at 'path' with the given 'content'."""
215 |     file_path = Path(path)
216 |     
217 |     # Security checks
218 |     if any(part.startswith('~') for part in file_path.parts):
219 |         raise ValueError("Home directory references not allowed")
220 |     normalized_path = normalize_path(str(file_path))
221 |     
222 |     # Validate reasonable file size for operations
223 |     if len(content) > 5_000_000:  # 5MB limit
224 |         raise ValueError("File content exceeds 5MB size limit")
225 |     
226 |     file_path.parent.mkdir(parents=True, exist_ok=True)
227 |     with open(file_path, "w", encoding="utf-8") as f:
228 |         f.write(content)
229 |     console.print(f"[bold blue]✓[/bold blue] Created/updated file at '[bright_cyan]{file_path}[/bright_cyan]'")
230 | 
231 | def show_diff_table(files_to_edit: List[FileToEdit]) -> None:
232 |     if not files_to_edit:
233 |         return
234 |     
235 |     table = Table(title="📝 Proposed Edits", show_header=True, header_style="bold bright_blue", show_lines=True, border_style="blue")
236 |     table.add_column("File Path", style="bright_cyan", no_wrap=True)
237 |     table.add_column("Original", style="red dim")
238 |     table.add_column("New", style="bright_green")
239 | 
240 |     for edit in files_to_edit:
241 |         table.add_row(edit.path, edit.original_snippet, edit.new_snippet)
242 |     
243 |     console.print(table)
244 | 
245 | def apply_diff_edit(path: str, original_snippet: str, new_snippet: str):
246 |     """Reads the file at 'path', replaces the first occurrence of 'original_snippet' with 'new_snippet', then overwrites."""
247 |     try:
248 |         content = read_local_file(path)
249 |         
250 |         # Verify we're replacing the exact intended occurrence
251 |         occurrences = content.count(original_snippet)
252 |         if occurrences == 0:
253 |             raise ValueError("Original snippet not found")
254 |         if occurrences > 1:
255 |             console.print(f"[bold yellow]⚠ Multiple matches ({occurrences}) found - requiring line numbers for safety[/bold yellow]")
256 |             console.print("[dim]Use format:\n--- original.py (lines X-Y)\n+++ modified.py[/dim]")
257 |             raise ValueError(f"Ambiguous edit: {occurrences} matches")
258 |         
259 |         updated_content = content.replace(original_snippet, new_snippet, 1)
260 |         create_file(path, updated_content)
261 |         console.print(f"[bold blue]✓[/bold blue] Applied diff edit to '[bright_cyan]{path}[/bright_cyan]'")
262 | 
263 |     except FileNotFoundError:
264 |         console.print(f"[bold red]✗[/bold red] File not found for diff editing: '[bright_cyan]{path}[/bright_cyan]'")
265 |     except ValueError as e:
266 |         console.print(f"[bold yellow]⚠[/bold yellow] {str(e)} in '[bright_cyan]{path}[/bright_cyan]'. No changes made.")
267 |         console.print("\n[bold blue]Expected snippet:[/bold blue]")
268 |         console.print(Panel(original_snippet, title="Expected", border_style="blue", title_align="left"))
269 |         console.print("\n[bold blue]Actual file content:[/bold blue]")
270 |         console.print(Panel(content, title="Actual", border_style="yellow", title_align="left"))
271 | 
272 | def try_handle_add_command(user_input: str) -> bool:
273 |     prefix = "/add "
274 |     if user_input.strip().lower().startswith(prefix):
275 |         path_to_add = user_input[len(prefix):].strip()
276 |         try:
277 |             normalized_path = normalize_path(path_to_add)
278 |             if os.path.isdir(normalized_path):
279 |                 # Handle entire directory
280 |                 add_directory_to_conversation(normalized_path)
281 |             else:
282 |                 # Handle a single file as before
283 |                 content = read_local_file(normalized_path)
284 |                 conversation_history.append({
285 |                     "role": "system",
286 |                     "content": f"Content of file '{normalized_path}':\n\n{content}"
287 |                 })
288 |                 console.print(f"[bold blue]✓[/bold blue] Added file '[bright_cyan]{normalized_path}[/bright_cyan]' to conversation.\n")
289 |         except OSError as e:
290 |             console.print(f"[bold red]✗[/bold red] Could not add path '[bright_cyan]{path_to_add}[/bright_cyan]': {e}\n")
291 |         return True
292 |     return False
293 | 
294 | def add_directory_to_conversation(directory_path: str):
295 |     with console.status("[bold bright_blue]🔍 Scanning directory...[/bold bright_blue]") as status:
296 |         excluded_files = {
297 |             # Python specific
298 |             ".DS_Store", "Thumbs.db", ".gitignore", ".python-version",
299 |             "uv.lock", ".uv", "uvenv", ".uvenv", ".venv", "venv",
300 |             "__pycache__", ".pytest_cache", ".coverage", ".mypy_cache",
301 |             # Node.js / Web specific
302 |             "node_modules", "package-lock.json", "yarn.lock", "pnpm-lock.yaml",
303 |             ".next", ".nuxt", "dist", "build", ".cache", ".parcel-cache",
304 |             ".turbo", ".vercel", ".output", ".contentlayer",
305 |             # Build outputs
306 |             "out", "coverage", ".nyc_output", "storybook-static",
307 |             # Environment and config
308 |             ".env", ".env.local", ".env.development", ".env.production",
309 |             # Misc
310 |             ".git", ".svn", ".hg", "CVS"
311 |         }
312 |         excluded_extensions = {
313 |             # Binary and media files
314 |             ".png", ".jpg", ".jpeg", ".gif", ".ico", ".svg", ".webp", ".avif",
315 |             ".mp4", ".webm", ".mov", ".mp3", ".wav", ".ogg",
316 |             ".zip", ".tar", ".gz", ".7z", ".rar",
317 |             ".exe", ".dll", ".so", ".dylib", ".bin",
318 |             # Documents
319 |             ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
320 |             # Python specific
321 |             ".pyc", ".pyo", ".pyd", ".egg", ".whl",
322 |             # UV specific
323 |             ".uv", ".uvenv",
324 |             # Database and logs
325 |             ".db", ".sqlite", ".sqlite3", ".log",
326 |             # IDE specific
327 |             ".idea", ".vscode",
328 |             # Web specific
329 |             ".map", ".chunk.js", ".chunk.css",
330 |             ".min.js", ".min.css", ".bundle.js", ".bundle.css",
331 |             # Cache and temp files
332 |             ".cache", ".tmp", ".temp",
333 |             # Font files
334 |             ".ttf", ".otf", ".woff", ".woff2", ".eot"
335 |         }
336 |         skipped_files = []
337 |         added_files = []
338 |         total_files_processed = 0
339 |         max_files = 1000  # Reasonable limit for files to process
340 |         max_file_size = 5_000_000  # 5MB limit
341 | 
342 |         for root, dirs, files in os.walk(directory_path):
343 |             if total_files_processed >= max_files:
344 |                 console.print(f"[bold yellow]⚠[/bold yellow] Reached maximum file limit ({max_files})")
345 |                 break
346 | 
347 |             status.update(f"[bold bright_blue]🔍 Scanning {root}...[/bold bright_blue]")
348 |             # Skip hidden directories and excluded directories
349 |             dirs[:] = [d for d in dirs if not d.startswith('.') and d not in excluded_files]
350 | 
351 |             for file in files:
352 |                 if total_files_processed >= max_files:
353 |                     break
354 | 
355 |                 if file.startswith('.') or file in excluded_files:
356 |                     skipped_files.append(os.path.join(root, file))
357 |                     continue
358 | 
359 |                 _, ext = os.path.splitext(file)
360 |                 if ext.lower() in excluded_extensions:
361 |                     skipped_files.append(os.path.join(root, file))
362 |                     continue
363 | 
364 |                 full_path = os.path.join(root, file)
365 | 
366 |                 try:
367 |                     # Check file size before processing
368 |                     if os.path.getsize(full_path) > max_file_size:
369 |                         skipped_files.append(f"{full_path} (exceeds size limit)")
370 |                         continue
371 | 
372 |                     # Check if it's binary
373 |                     if is_binary_file(full_path):
374 |                         skipped_files.append(full_path)
375 |                         continue
376 | 
377 |                     normalized_path = normalize_path(full_path)
378 |                     content = read_local_file(normalized_path)
379 |                     conversation_history.append({
380 |                         "role": "system",
381 |                         "content": f"Content of file '{normalized_path}':\n\n{content}"
382 |                     })
383 |                     added_files.append(normalized_path)
384 |                     total_files_processed += 1
385 | 
386 |                 except OSError:
387 |                     skipped_files.append(full_path)
388 | 
389 |         console.print(f"[bold blue]✓[/bold blue] Added folder '[bright_cyan]{directory_path}[/bright_cyan]' to conversation.")
390 |         if added_files:
391 |             console.print(f"\n[bold bright_blue]📁 Added files:[/bold bright_blue] [dim]({len(added_files)} of {total_files_processed})[/dim]")
392 |             for f in added_files:
393 |                 console.print(f"  [bright_cyan]📄 {f}[/bright_cyan]")
394 |         if skipped_files:
395 |             console.print(f"\n[bold yellow]⏭ Skipped files:[/bold yellow] [dim]({len(skipped_files)})[/dim]")
396 |             for f in skipped_files[:10]:  # Show only first 10 to avoid clutter
397 |                 console.print(f"  [yellow dim]⚠ {f}[/yellow dim]")
398 |             if len(skipped_files) > 10:
399 |                 console.print(f"  [dim]... and {len(skipped_files) - 10} more[/dim]")
400 |         console.print()
401 | 
402 | def is_binary_file(file_path: str, peek_size: int = 1024) -> bool:
403 |     try:
404 |         with open(file_path, 'rb') as f:
405 |             chunk = f.read(peek_size)
406 |         # If there is a null byte in the sample, treat it as binary
407 |         if b'\0' in chunk:
408 |             return True
409 |         return False
410 |     except Exception:
411 |         # If we fail to read, just treat it as binary to be safe
412 |         return True
413 | 
414 | def ensure_file_in_context(file_path: str) -> bool:
415 |     try:
416 |         normalized_path = normalize_path(file_path)
417 |         content = read_local_file(normalized_path)
418 |         file_marker = f"Content of file '{normalized_path}'"
419 |         if not any(file_marker in msg["content"] for msg in conversation_history):
420 |             conversation_history.append({
421 |                 "role": "system",
422 |                 "content": f"{file_marker}:\n\n{content}"
423 |             })
424 |         return True
425 |     except OSError:
426 |         console.print(f"[bold red]✗[/bold red] Could not read file '[bright_cyan]{file_path}[/bright_cyan]' for editing context")
427 |         return False
428 | 
429 | def normalize_path(path_str: str) -> str:
430 |     """Return a canonical, absolute version of the path with security checks."""
431 |     path = Path(path_str).resolve()
432 |     
433 |     # Prevent directory traversal attacks
434 |     if ".." in path.parts:
435 |         raise ValueError(f"Invalid path: {path_str} contains parent directory references")
436 |     
437 |     return str(path)
438 | 
439 | # --------------------------------------------------------------------------------
440 | # 5. Conversation state
441 | # --------------------------------------------------------------------------------
442 | conversation_history = [
443 |     {"role": "system", "content": system_PROMPT}
444 | ]
445 | 
446 | # --------------------------------------------------------------------------------
447 | # 6. OpenAI API interaction with streaming
448 | # --------------------------------------------------------------------------------
449 | 
450 | def execute_function_call_dict(tool_call_dict) -> str:
451 |     """Execute a function call from a dictionary format and return the result as a string."""
452 |     try:
453 |         function_name = tool_call_dict["function"]["name"]
454 |         arguments = json.loads(tool_call_dict["function"]["arguments"])
455 |         
456 |         if function_name == "read_file":
457 |             file_path = arguments["file_path"]
458 |             normalized_path = normalize_path(file_path)
459 |             content = read_local_file(normalized_path)
460 |             return f"Content of file '{normalized_path}':\n\n{content}"
461 |             
462 |         elif function_name == "read_multiple_files":
463 |             file_paths = arguments["file_paths"]
464 |             results = []
465 |             for file_path in file_paths:
466 |                 try:
467 |                     normalized_path = normalize_path(file_path)
468 |                     content = read_local_file(normalized_path)
469 |                     results.append(f"Content of file '{normalized_path}':\n\n{content}")
470 |                 except OSError as e:
471 |                     results.append(f"Error reading '{file_path}': {e}")
472 |             return "\n\n" + "="*50 + "\n\n".join(results)
473 |             
474 |         elif function_name == "create_file":
475 |             file_path = arguments["file_path"]
476 |             content = arguments["content"]
477 |             create_file(file_path, content)
478 |             return f"Successfully created file '{file_path}'"
479 |             
480 |         elif function_name == "create_multiple_files":
481 |             files = arguments["files"]
482 |             created_files = []
483 |             for file_info in files:
484 |                 create_file(file_info["path"], file_info["content"])
485 |                 created_files.append(file_info["path"])
486 |             return f"Successfully created {len(created_files)} files: {', '.join(created_files)}"
487 |             
488 |         elif function_name == "edit_file":
489 |             file_path = arguments["file_path"]
490 |             original_snippet = arguments["original_snippet"]
491 |             new_snippet = arguments["new_snippet"]
492 |             
493 |             # Ensure file is in context first
494 |             if not ensure_file_in_context(file_path):
495 |                 return f"Error: Could not read file '{file_path}' for editing"
496 |             
497 |             apply_diff_edit(file_path, original_snippet, new_snippet)
498 |             return f"Successfully edited file '{file_path}'"
499 |             
500 |         else:
501 |             return f"Unknown function: {function_name}"
502 |             
503 |     except Exception as e:
504 |         return f"Error executing {function_name}: {str(e)}"
505 | 
506 | def execute_function_call(tool_call) -> str:
507 |     """Execute a function call and return the result as a string."""
508 |     try:
509 |         function_name = tool_call.function.name
510 |         arguments = json.loads(tool_call.function.arguments)
511 |         
512 |         if function_name == "read_file":
513 |             file_path = arguments["file_path"]
514 |             normalized_path = normalize_path(file_path)
515 |             content = read_local_file(normalized_path)
516 |             return f"Content of file '{normalized_path}':\n\n{content}"
517 |             
518 |         elif function_name == "read_multiple_files":
519 |             file_paths = arguments["file_paths"]
520 |             results = []
521 |             for file_path in file_paths:
522 |                 try:
523 |                     normalized_path = normalize_path(file_path)
524 |                     content = read_local_file(normalized_path)
525 |                     results.append(f"Content of file '{normalized_path}':\n\n{content}")
526 |                 except OSError as e:
527 |                     results.append(f"Error reading '{file_path}': {e}")
528 |             return "\n\n" + "="*50 + "\n\n".join(results)
529 |             
530 |         elif function_name == "create_file":
531 |             file_path = arguments["file_path"]
532 |             content = arguments["content"]
533 |             create_file(file_path, content)
534 |             return f"Successfully created file '{file_path}'"
535 |             
536 |         elif function_name == "create_multiple_files":
537 |             files = arguments["files"]
538 |             created_files = []
539 |             for file_info in files:
540 |                 create_file(file_info["path"], file_info["content"])
541 |                 created_files.append(file_info["path"])
542 |             return f"Successfully created {len(created_files)} files: {', '.join(created_files)}"
543 |             
544 |         elif function_name == "edit_file":
545 |             file_path = arguments["file_path"]
546 |             original_snippet = arguments["original_snippet"]
547 |             new_snippet = arguments["new_snippet"]
548 |             
549 |             # Ensure file is in context first
550 |             if not ensure_file_in_context(file_path):
551 |                 return f"Error: Could not read file '{file_path}' for editing"
552 |             
553 |             apply_diff_edit(file_path, original_snippet, new_snippet)
554 |             return f"Successfully edited file '{file_path}'"
555 |             
556 |         else:
557 |             return f"Unknown function: {function_name}"
558 |             
559 |     except Exception as e:
560 |         return f"Error executing {function_name}: {str(e)}"
561 | 
562 | def trim_conversation_history():
563 |     """Trim conversation history to prevent token limit issues while preserving tool call sequences"""
564 |     if len(conversation_history) <= 20:  # Don't trim if conversation is still small
565 |         return
566 |         
567 |     # Always keep the system prompt
568 |     system_msgs = [msg for msg in conversation_history if msg["role"] == "system"]
569 |     other_msgs = [msg for msg in conversation_history if msg["role"] != "system"]
570 |     
571 |     # Keep only the last 15 messages to prevent token overflow
572 |     if len(other_msgs) > 15:
573 |         other_msgs = other_msgs[-15:]
574 |     
575 |     # Rebuild conversation history
576 |     conversation_history.clear()
577 |     conversation_history.extend(system_msgs + other_msgs)
578 | 
579 | def stream_openai_response(user_message: str):
580 |     # Add the user message to conversation history
581 |     conversation_history.append({"role": "user", "content": user_message})
582 |     
583 |     # Trim conversation history if it's getting too long
584 |     trim_conversation_history()
585 | 
586 |     # Remove the old file guessing logic since we'll use function calls
587 |     try:
588 |         stream = client.chat.completions.create(
589 |             model="deepseek-reasoner",
590 |             messages=conversation_history,
591 |             tools=tools,
592 |             max_completion_tokens=64000,
593 |             stream=True
594 |         )
595 | 
596 |         console.print("\n[bold bright_blue]🐋 Seeking...[/bold bright_blue]")
597 |         reasoning_started = False
598 |         reasoning_content = ""
599 |         final_content = ""
600 |         tool_calls = []
601 | 
602 |         for chunk in stream:
603 |             # Handle reasoning content if available
604 |             if hasattr(chunk.choices[0].delta, 'reasoning_content') and chunk.choices[0].delta.reasoning_content:
605 |                 if not reasoning_started:
606 |                     console.print("\n[bold blue]💭 Reasoning:[/bold blue]")
607 |                     reasoning_started = True
608 |                 console.print(chunk.choices[0].delta.reasoning_content, end="")
609 |                 reasoning_content += chunk.choices[0].delta.reasoning_content
610 |             elif chunk.choices[0].delta.content:
611 |                 if reasoning_started:
612 |                     console.print("\n")  # Add spacing after reasoning
613 |                     console.print("\n[bold bright_blue]🤖 Assistant>[/bold bright_blue] ", end="")
614 |                     reasoning_started = False
615 |                 final_content += chunk.choices[0].delta.content
616 |                 console.print(chunk.choices[0].delta.content, end="")
617 |             elif chunk.choices[0].delta.tool_calls:
618 |                 # Handle tool calls
619 |                 for tool_call_delta in chunk.choices[0].delta.tool_calls:
620 |                     if tool_call_delta.index is not None:
621 |                         # Ensure we have enough tool_calls
622 |                         while len(tool_calls) <= tool_call_delta.index:
623 |                             tool_calls.append({
624 |                                 "id": "",
625 |                                 "type": "function",
626 |                                 "function": {"name": "", "arguments": ""}
627 |                             })
628 |                         
629 |                         if tool_call_delta.id:
630 |                             tool_calls[tool_call_delta.index]["id"] = tool_call_delta.id
631 |                         if tool_call_delta.function:
632 |                             if tool_call_delta.function.name:
633 |                                 tool_calls[tool_call_delta.index]["function"]["name"] += tool_call_delta.function.name
634 |                             if tool_call_delta.function.arguments:
635 |                                 tool_calls[tool_call_delta.index]["function"]["arguments"] += tool_call_delta.function.arguments
636 | 
637 |         console.print()  # New line after streaming
638 | 
639 |         # Store the assistant's response in conversation history
640 |         assistant_message = {
641 |             "role": "assistant",
642 |             "content": final_content if final_content else None
643 |         }
644 |         
645 |         if tool_calls:
646 |             # Convert our tool_calls format to the expected format
647 |             formatted_tool_calls = []
648 |             for i, tc in enumerate(tool_calls):
649 |                 if tc["function"]["name"]:  # Only add if we have a function name
650 |                     # Ensure we have a valid tool call ID
651 |                     tool_id = tc["id"] if tc["id"] else f"call_{i}_{int(time.time() * 1000)}"
652 |                     
653 |                     formatted_tool_calls.append({
654 |                         "id": tool_id,
655 |                         "type": "function",
656 |                         "function": {
657 |                             "name": tc["function"]["name"],
658 |                             "arguments": tc["function"]["arguments"]
659 |                         }
660 |                     })
661 |             
662 |             if formatted_tool_calls:
663 |                 # Important: When there are tool calls, content should be None or empty
664 |                 if not final_content:
665 |                     assistant_message["content"] = None
666 |                     
667 |                 assistant_message["tool_calls"] = formatted_tool_calls
668 |                 conversation_history.append(assistant_message)
669 |                 
670 |                 # Execute tool calls and add results immediately
671 |                 console.print(f"\n[bold bright_cyan]⚡ Executing {len(formatted_tool_calls)} function call(s)...[/bold bright_cyan]")
672 |                 for tool_call in formatted_tool_calls:
673 |                     console.print(f"[bright_blue]→ {tool_call['function']['name']}[/bright_blue]")
674 |                     
675 |                     try:
676 |                         result = execute_function_call_dict(tool_call)
677 |                         
678 |                         # Add tool result to conversation immediately
679 |                         tool_response = {
680 |                             "role": "tool",
681 |                             "tool_call_id": tool_call["id"],
682 |                             "content": result
683 |                         }
684 |                         conversation_history.append(tool_response)
685 |                     except Exception as e:
686 |                         console.print(f"[red]Error executing {tool_call['function']['name']}: {e}[/red]")
687 |                         # Still need to add a tool response even on error
688 |                         conversation_history.append({
689 |                             "role": "tool",
690 |                             "tool_call_id": tool_call["id"],
691 |                             "content": f"Error: {str(e)}"
692 |                         })
693 |                 
694 |                 # Get follow-up response after tool execution
695 |                 console.print("\n[bold bright_blue]🔄 Processing results...[/bold bright_blue]")
696 |                 
697 |                 follow_up_stream = client.chat.completions.create(
698 |                     model="deepseek-reasoner",
699 |                     messages=conversation_history,
700 |                     tools=tools,
701 |                     max_completion_tokens=64000,
702 |                     stream=True
703 |                 )
704 |                 
705 |                 follow_up_content = ""
706 |                 reasoning_started = False
707 |                 
708 |                 for chunk in follow_up_stream:
709 |                     # Handle reasoning content if available
710 |                     if hasattr(chunk.choices[0].delta, 'reasoning_content') and chunk.choices[0].delta.reasoning_content:
711 |                         if not reasoning_started:
712 |                             console.print("\n[bold blue]💭 Reasoning:[/bold blue]")
713 |                             reasoning_started = True
714 |                         console.print(chunk.choices[0].delta.reasoning_content, end="")
715 |                     elif chunk.choices[0].delta.content:
716 |                         if reasoning_started:
717 |                             console.print("\n")
718 |                             console.print("\n[bold bright_blue]🤖 Assistant>[/bold bright_blue] ", end="")
719 |                             reasoning_started = False
720 |                         follow_up_content += chunk.choices[0].delta.content
721 |                         console.print(chunk.choices[0].delta.content, end="")
722 |                 
723 |                 console.print()
724 |                 
725 |                 # Store follow-up response
726 |                 conversation_history.append({
727 |                     "role": "assistant",
728 |                     "content": follow_up_content
729 |                 })
730 |         else:
731 |             # No tool calls, just store the regular response
732 |             conversation_history.append(assistant_message)
733 | 
734 |         return {"success": True}
735 | 
736 |     except Exception as e:
737 |         error_msg = f"DeepSeek API error: {str(e)}"
738 |         console.print(f"\n[bold red]❌ {error_msg}[/bold red]")
739 |         return {"error": error_msg}
740 | 
741 | # --------------------------------------------------------------------------------
742 | # 7. Main interactive loop
743 | # --------------------------------------------------------------------------------
744 | 
745 | def main():
746 |     # Create a beautiful gradient-style welcome panel
747 |     welcome_text = """[bold bright_blue]🐋 DeepSeek Engineer[/bold bright_blue] [bright_cyan]with Function Calling[/bright_cyan]
748 | [dim blue]Powered by DeepSeek-R1 with Chain-of-Thought Reasoning[/dim blue]"""
749 |     
750 |     console.print(Panel.fit(
751 |         welcome_text,
752 |         border_style="bright_blue",
753 |         padding=(1, 2),
754 |         title="[bold bright_cyan]🤖 AI Code Assistant[/bold bright_cyan]",
755 |         title_align="center"
756 |     ))
757 |     
758 |     # Create an elegant instruction panel
759 |     instructions = """[bold bright_blue]📁 File Operations:[/bold bright_blue]
760 |   • [bright_cyan]/add path/to/file[/bright_cyan] - Include a single file in conversation
761 |   • [bright_cyan]/add path/to/folder[/bright_cyan] - Include all files in a folder
762 |   • [dim]The AI can automatically read and create files using function calls[/dim]
763 | 
764 | [bold bright_blue]🎯 Commands:[/bold bright_blue]
765 |   • [bright_cyan]exit[/bright_cyan] or [bright_cyan]quit[/bright_cyan] - End the session
766 |   • Just ask naturally - the AI will handle file operations automatically!"""
767 |     
768 |     console.print(Panel(
769 |         instructions,
770 |         border_style="blue",
771 |         padding=(1, 2),
772 |         title="[bold blue]💡 How to Use[/bold blue]",
773 |         title_align="left"
774 |     ))
775 |     console.print()
776 | 
777 |     while True:
778 |         try:
779 |             user_input = prompt_session.prompt("🔵 You> ").strip()
780 |         except (EOFError, KeyboardInterrupt):
781 |             console.print("\n[bold yellow]👋 Exiting gracefully...[/bold yellow]")
782 |             break
783 | 
784 |         if not user_input:
785 |             continue
786 | 
787 |         if user_input.lower() in ["exit", "quit"]:
788 |             console.print("[bold bright_blue]👋 Goodbye! Happy coding![/bold bright_blue]")
789 |             break
790 | 
791 |         if try_handle_add_command(user_input):
792 |             continue
793 | 
794 |         response_data = stream_openai_response(user_input)
795 |         
796 |         if response_data.get("error"):
797 |             console.print(f"[bold red]❌ Error: {response_data['error']}[/bold red]")
798 | 
799 |     console.print("[bold blue]✨ Session finished. Thank you for using DeepSeek Engineer![/bold blue]")
800 | 
801 | if __name__ == "__main__":
802 |     main()


--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [project]
 2 | name = "deepseek-engineer"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "openai>=1.58.1",
 9 |     "prompt-toolkit>=3.0.50",
10 |     "pydantic>=2.10.4",
11 |     "python-dotenv>=1.0.1",
12 |     "rich>=13.9.4",
13 | ]
14 | 


--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | openai
2 | pydantic
3 | python-dotenv
4 | rich
5 | prompt_toolkit 


--------------------------------------------------------------------------------
/solar_cube.html:
--------------------------------------------------------------------------------
  1 | <!DOCTYPE html>
  2 | <html lang="en">
  3 | <head>
  4 |     <meta charset="UTF-8">
  5 |     <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6 |     <title>Solar System in a Rotating Cube</title>
  7 |     <style>
  8 |         body {
  9 |             margin: 0;
 10 |             overflow: hidden;
 11 |             background: #000;
 12 |             display: flex;
 13 |             justify-content: center;
 14 |             align-items: center;
 15 |             height: 100vh;
 16 |             perspective: 1200px;
 17 |         }
 18 |         
 19 |         .scene {
 20 |             position: relative;
 21 |             width: 300px;
 22 |             height: 300px;
 23 |             transform-style: preserve-3d;
 24 |             animation: rotateCube 30s infinite linear;
 25 |         }
 26 |         
 27 |         .cube {
 28 |             position: relative;
 29 |             width: 100%;
 30 |             height: 100%;
 31 |             transform-style: preserve-3d;
 32 |         }
 33 |         
 34 |         .face {
 35 |             position: absolute;
 36 |             width: 300px;
 37 |             height: 300px;
 38 |             border: 2px solid rgba(255, 255, 255, 0.2);
 39 |             box-sizing: border-box;
 40 |             display: flex;
 41 |             justify-content: center;
 42 |             align-items: center;
 43 |             color: white;
 44 |             font-size: 24px;
 45 |             opacity: 0.7;
 46 |         }
 47 |         
 48 |         .front  { transform: translateZ(150px); background: rgba(255, 0, 0, 0.1); }
 49 |         .back   { transform: rotateY(180deg) translateZ(150px); background: rgba(0, 255, 0, 0.1); }
 50 |         .right  { transform: rotateY(90deg) translateZ(150px); background: rgba(0, 0, 255, 0.1); }
 51 |         .left   { transform: rotateY(-90deg) translateZ(150px); background: rgba(255, 255, 0, 0.1); }
 52 |         .top    { transform: rotateX(90deg) translateZ(150px); background: rgba(255, 0, 255, 0.1); }
 53 |         .bottom { transform: rotateX(-90deg) translateZ(150px); background: rgba(0, 255, 255, 0.1); }
 54 |         
 55 |         .solar-system {
 56 |             position: absolute;
 57 |             top: 50%;
 58 |             left: 50%;
 59 |             transform: translate(-50%, -50%);
 60 |             width: 200px;
 61 |             height: 200px;
 62 |         }
 63 |         
 64 |         .sun {
 65 |             position: absolute;
 66 |             top: 50%;
 67 |             left: 50%;
 68 |             width: 40px;
 69 |             height: 40px;
 70 |             background: radial-gradient(circle, #ffd700, #ff8c00);
 71 |             border-radius: 50%;
 72 |             transform: translate(-50%, -50%);
 73 |             box-shadow: 0 0 20px #ff8c00;
 74 |         }
 75 |         
 76 |         .planet {
 77 |             position: absolute;
 78 |             top: 50%;
 79 |             left: 50%;
 80 |             border-radius: 50%;
 81 |             transform-origin: 0 0;
 82 |         }
 83 |         
 84 |         .orbit {
 85 |             position: absolute;
 86 |             top: 50%;
 87 |             left: 50%;
 88 |             border: 1px solid rgba(255, 255, 255, 0.1);
 89 |             border-radius: 50%;
 90 |             transform: translate(-50%, -50%);
 91 |         }
 92 |         
 93 |         .mercury { 
 94 |             width: 8px; 
 95 |             height: 8px; 
 96 |             background: #a9a9a9; 
 97 |             animation: orbit 4s infinite linear;
 98 |         }
 99 |         .venus { 
100 |             width: 12px; 
101 |             height: 12px; 
102 |             background: #ffa500; 
103 |             animation: orbit 8s infinite linear;
104 |         }
105 |         .earth { 
106 |             width: 12px; 
107 |             height: 12px; 
108 |             background: #1e90ff; 
109 |             animation: orbit 12s infinite linear;
110 |         }
111 |         .mars { 
112 |             width: 10px; 
113 |             height: 10px; 
114 |             background: #ff4500; 
115 |             animation: orbit 20s infinite linear;
116 |         }
117 |         
118 |         @keyframes rotateCube {
119 |             0% { transform: rotateX(0) rotateY(0); }
120 |             100% { transform: rotateX(360deg) rotateY(360deg); }
121 |         }
122 |         
123 |         @keyframes orbit {
124 |             0% { transform: rotate(0deg) translateX(50px) rotate(0deg); }
125 |             100% { transform: rotate(360deg) translateX(50px) rotate(-360deg); }
126 |         }
127 |     </style>
128 | </head>
129 | <body>
130 |     <div class="scene">
131 |         <div class="cube">
132 |             <div class="face front">Front</div>
133 |             <div class="face back">Back</div>
134 |             <div class="face right">Right</div>
135 |             <div class="face left">Left</div>
136 |             <div class="face top">Top</div>
137 |             <div class="face bottom">Bottom</div>
138 |             
139 |             <div class="solar-system">
140 |                 <div class="sun"></div>
141 |                 <div class="orbit" style="width: 100px; height: 100px;"></div>
142 |                 <div class="orbit" style="width: 130px; height: 130px;"></div>
143 |                 <div class="orbit" style="width: 160px; height: 160px;"></div>
144 |                 <div class="orbit" style="width: 190px; height: 190px;"></div>
145 |                 
146 |                 <div class="planet mercury"></div>
147 |                 <div class="planet venus"></div>
148 |                 <div class="planet earth"></div>
149 |                 <div class="planet mars"></div>
150 |             </div>
151 |         </div>
152 |     </div>
153 | </body>
154 | </html>


--------------------------------------------------------------------------------
/uv.lock:
--------------------------------------------------------------------------------
  1 | version = 1
  2 | requires-python = ">=3.11"
  3 | 
  4 | [[package]]
  5 | name = "annotated-types"
  6 | version = "0.7.0"
  7 | source = { registry = "https://pypi.org/simple" }
  8 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 }
  9 | wheels = [
 10 |     { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 },
 11 | ]
 12 | 
 13 | [[package]]
 14 | name = "anyio"
 15 | version = "4.7.0"
 16 | source = { registry = "https://pypi.org/simple" }
 17 | dependencies = [
 18 |     { name = "idna" },
 19 |     { name = "sniffio" },
 20 |     { name = "typing-extensions", marker = "python_full_version < '3.13'" },
 21 | ]
 22 | sdist = { url = "https://files.pythonhosted.org/packages/f6/40/318e58f669b1a9e00f5c4453910682e2d9dd594334539c7b7817dabb765f/anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48", size = 177076 }
 23 | wheels = [
 24 |     { url = "https://files.pythonhosted.org/packages/a0/7a/4daaf3b6c08ad7ceffea4634ec206faeff697526421c20f07628c7372156/anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352", size = 93052 },
 25 | ]
 26 | 
 27 | [[package]]
 28 | name = "certifi"
 29 | version = "2024.12.14"
 30 | source = { registry = "https://pypi.org/simple" }
 31 | sdist = { url = "https://files.pythonhosted.org/packages/0f/bd/1d41ee578ce09523c81a15426705dd20969f5abf006d1afe8aeff0dd776a/certifi-2024.12.14.tar.gz", hash = "sha256:b650d30f370c2b724812bee08008be0c4163b163ddaec3f2546c1caf65f191db", size = 166010 }
 32 | wheels = [
 33 |     { url = "https://files.pythonhosted.org/packages/a5/32/8f6669fc4798494966bf446c8c4a162e0b5d893dff088afddf76414f70e1/certifi-2024.12.14-py3-none-any.whl", hash = "sha256:1275f7a45be9464efc1173084eaa30f866fe2e47d389406136d332ed4967ec56", size = 164927 },
 34 | ]
 35 | 
 36 | [[package]]
 37 | name = "colorama"
 38 | version = "0.4.6"
 39 | source = { registry = "https://pypi.org/simple" }
 40 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
 41 | wheels = [
 42 |     { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
 43 | ]
 44 | 
 45 | [[package]]
 46 | name = "deepseek-engineer"
 47 | version = "0.1.0"
 48 | source = { virtual = "." }
 49 | dependencies = [
 50 |     { name = "openai" },
 51 |     { name = "prompt-toolkit" },
 52 |     { name = "pydantic" },
 53 |     { name = "python-dotenv" },
 54 |     { name = "rich" },
 55 | ]
 56 | 
 57 | [package.metadata]
 58 | requires-dist = [
 59 |     { name = "openai", specifier = ">=1.58.1" },
 60 |     { name = "prompt-toolkit", specifier = ">=3.0.50" },
 61 |     { name = "pydantic", specifier = ">=2.10.4" },
 62 |     { name = "python-dotenv", specifier = ">=1.0.1" },
 63 |     { name = "rich", specifier = ">=13.9.4" },
 64 | ]
 65 | 
 66 | [[package]]
 67 | name = "distro"
 68 | version = "1.9.0"
 69 | source = { registry = "https://pypi.org/simple" }
 70 | sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 }
 71 | wheels = [
 72 |     { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 },
 73 | ]
 74 | 
 75 | [[package]]
 76 | name = "h11"
 77 | version = "0.14.0"
 78 | source = { registry = "https://pypi.org/simple" }
 79 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
 80 | wheels = [
 81 |     { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
 82 | ]
 83 | 
 84 | [[package]]
 85 | name = "httpcore"
 86 | version = "1.0.7"
 87 | source = { registry = "https://pypi.org/simple" }
 88 | dependencies = [
 89 |     { name = "certifi" },
 90 |     { name = "h11" },
 91 | ]
 92 | sdist = { url = "https://files.pythonhosted.org/packages/6a/41/d7d0a89eb493922c37d343b607bc1b5da7f5be7e383740b4753ad8943e90/httpcore-1.0.7.tar.gz", hash = "sha256:8551cb62a169ec7162ac7be8d4817d561f60e08eaa485234898414bb5a8a0b4c", size = 85196 }
 93 | wheels = [
 94 |     { url = "https://files.pythonhosted.org/packages/87/f5/72347bc88306acb359581ac4d52f23c0ef445b57157adedb9aee0cd689d2/httpcore-1.0.7-py3-none-any.whl", hash = "sha256:a3fff8f43dc260d5bd363d9f9cf1830fa3a458b332856f34282de498ed420edd", size = 78551 },
 95 | ]
 96 | 
 97 | [[package]]
 98 | name = "httpx"
 99 | version = "0.28.1"
100 | source = { registry = "https://pypi.org/simple" }
101 | dependencies = [
102 |     { name = "anyio" },
103 |     { name = "certifi" },
104 |     { name = "httpcore" },
105 |     { name = "idna" },
106 | ]
107 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
108 | wheels = [
109 |     { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
110 | ]
111 | 
112 | [[package]]
113 | name = "idna"
114 | version = "3.10"
115 | source = { registry = "https://pypi.org/simple" }
116 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
117 | wheels = [
118 |     { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
119 | ]
120 | 
121 | [[package]]
122 | name = "jiter"
123 | version = "0.8.2"
124 | source = { registry = "https://pypi.org/simple" }
125 | sdist = { url = "https://files.pythonhosted.org/packages/f8/70/90bc7bd3932e651486861df5c8ffea4ca7c77d28e8532ddefe2abc561a53/jiter-0.8.2.tar.gz", hash = "sha256:cd73d3e740666d0e639f678adb176fad25c1bcbdae88d8d7b857e1783bb4212d", size = 163007 }
126 | wheels = [
127 |     { url = "https://files.pythonhosted.org/packages/cb/b0/c1a7caa7f9dc5f1f6cfa08722867790fe2d3645d6e7170ca280e6e52d163/jiter-0.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:2dd61c5afc88a4fda7d8b2cf03ae5947c6ac7516d32b7a15bf4b49569a5c076b", size = 303666 },
128 |     { url = "https://files.pythonhosted.org/packages/f5/97/0468bc9eeae43079aaa5feb9267964e496bf13133d469cfdc135498f8dd0/jiter-0.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a6c710d657c8d1d2adbbb5c0b0c6bfcec28fd35bd6b5f016395f9ac43e878a15", size = 311934 },
129 |     { url = "https://files.pythonhosted.org/packages/e5/69/64058e18263d9a5f1e10f90c436853616d5f047d997c37c7b2df11b085ec/jiter-0.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9584de0cd306072635fe4b89742bf26feae858a0683b399ad0c2509011b9dc0", size = 335506 },
130 |     { url = "https://files.pythonhosted.org/packages/9d/14/b747f9a77b8c0542141d77ca1e2a7523e854754af2c339ac89a8b66527d6/jiter-0.8.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5a90a923338531b7970abb063cfc087eebae6ef8ec8139762007188f6bc69a9f", size = 355849 },
131 |     { url = "https://files.pythonhosted.org/packages/53/e2/98a08161db7cc9d0e39bc385415890928ff09709034982f48eccfca40733/jiter-0.8.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21974d246ed0181558087cd9f76e84e8321091ebfb3a93d4c341479a736f099", size = 381700 },
132 |     { url = "https://files.pythonhosted.org/packages/7a/38/1674672954d35bce3b1c9af99d5849f9256ac8f5b672e020ac7821581206/jiter-0.8.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:32475a42b2ea7b344069dc1e81445cfc00b9d0e3ca837f0523072432332e9f74", size = 389710 },
133 |     { url = "https://files.pythonhosted.org/packages/f8/9b/92f9da9a9e107d019bcf883cd9125fa1690079f323f5a9d5c6986eeec3c0/jiter-0.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b9931fd36ee513c26b5bf08c940b0ac875de175341cbdd4fa3be109f0492586", size = 345553 },
134 |     { url = "https://files.pythonhosted.org/packages/44/a6/6d030003394e9659cd0d7136bbeabd82e869849ceccddc34d40abbbbb269/jiter-0.8.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ce0820f4a3a59ddced7fce696d86a096d5cc48d32a4183483a17671a61edfddc", size = 376388 },
135 |     { url = "https://files.pythonhosted.org/packages/ad/8d/87b09e648e4aca5f9af89e3ab3cfb93db2d1e633b2f2931ede8dabd9b19a/jiter-0.8.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ffc86ae5e3e6a93765d49d1ab47b6075a9c978a2b3b80f0f32628f39caa0c88", size = 511226 },
136 |     { url = "https://files.pythonhosted.org/packages/77/95/8008ebe4cdc82eac1c97864a8042ca7e383ed67e0ec17bfd03797045c727/jiter-0.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5127dc1abd809431172bc3fbe8168d6b90556a30bb10acd5ded41c3cfd6f43b6", size = 504134 },
137 |     { url = "https://files.pythonhosted.org/packages/26/0d/3056a74de13e8b2562e4d526de6dac2f65d91ace63a8234deb9284a1d24d/jiter-0.8.2-cp311-cp311-win32.whl", hash = "sha256:66227a2c7b575720c1871c8800d3a0122bb8ee94edb43a5685aa9aceb2782d44", size = 203103 },
138 |     { url = "https://files.pythonhosted.org/packages/4e/1e/7f96b798f356e531ffc0f53dd2f37185fac60fae4d6c612bbbd4639b90aa/jiter-0.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:cde031d8413842a1e7501e9129b8e676e62a657f8ec8166e18a70d94d4682855", size = 206717 },
139 |     { url = "https://files.pythonhosted.org/packages/a1/17/c8747af8ea4e045f57d6cfd6fc180752cab9bc3de0e8a0c9ca4e8af333b1/jiter-0.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e6ec2be506e7d6f9527dae9ff4b7f54e68ea44a0ef6b098256ddf895218a2f8f", size = 302027 },
140 |     { url = "https://files.pythonhosted.org/packages/3c/c1/6da849640cd35a41e91085723b76acc818d4b7d92b0b6e5111736ce1dd10/jiter-0.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:76e324da7b5da060287c54f2fabd3db5f76468006c811831f051942bf68c9d44", size = 310326 },
141 |     { url = "https://files.pythonhosted.org/packages/06/99/a2bf660d8ccffee9ad7ed46b4f860d2108a148d0ea36043fd16f4dc37e94/jiter-0.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:180a8aea058f7535d1c84183c0362c710f4750bef66630c05f40c93c2b152a0f", size = 334242 },
142 |     { url = "https://files.pythonhosted.org/packages/a7/5f/cea1c17864828731f11427b9d1ab7f24764dbd9aaf4648a7f851164d2718/jiter-0.8.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025337859077b41548bdcbabe38698bcd93cfe10b06ff66617a48ff92c9aec60", size = 356654 },
143 |     { url = "https://files.pythonhosted.org/packages/e9/13/62774b7e5e7f5d5043efe1d0f94ead66e6d0f894ae010adb56b3f788de71/jiter-0.8.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ecff0dc14f409599bbcafa7e470c00b80f17abc14d1405d38ab02e4b42e55b57", size = 379967 },
144 |     { url = "https://files.pythonhosted.org/packages/ec/fb/096b34c553bb0bd3f2289d5013dcad6074948b8d55212aa13a10d44c5326/jiter-0.8.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ffd9fee7d0775ebaba131f7ca2e2d83839a62ad65e8e02fe2bd8fc975cedeb9e", size = 389252 },
145 |     { url = "https://files.pythonhosted.org/packages/17/61/beea645c0bf398ced8b199e377b61eb999d8e46e053bb285c91c3d3eaab0/jiter-0.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14601dcac4889e0a1c75ccf6a0e4baf70dbc75041e51bcf8d0e9274519df6887", size = 345490 },
146 |     { url = "https://files.pythonhosted.org/packages/d5/df/834aa17ad5dcc3cf0118821da0a0cf1589ea7db9832589278553640366bc/jiter-0.8.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:92249669925bc1c54fcd2ec73f70f2c1d6a817928480ee1c65af5f6b81cdf12d", size = 376991 },
147 |     { url = "https://files.pythonhosted.org/packages/67/80/87d140399d382fb4ea5b3d56e7ecaa4efdca17cd7411ff904c1517855314/jiter-0.8.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e725edd0929fa79f8349ab4ec7f81c714df51dc4e991539a578e5018fa4a7152", size = 510822 },
148 |     { url = "https://files.pythonhosted.org/packages/5c/37/3394bb47bac1ad2cb0465601f86828a0518d07828a650722e55268cdb7e6/jiter-0.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bf55846c7b7a680eebaf9c3c48d630e1bf51bdf76c68a5f654b8524335b0ad29", size = 503730 },
149 |     { url = "https://files.pythonhosted.org/packages/f9/e2/253fc1fa59103bb4e3aa0665d6ceb1818df1cd7bf3eb492c4dad229b1cd4/jiter-0.8.2-cp312-cp312-win32.whl", hash = "sha256:7efe4853ecd3d6110301665a5178b9856be7e2a9485f49d91aa4d737ad2ae49e", size = 203375 },
150 |     { url = "https://files.pythonhosted.org/packages/41/69/6d4bbe66b3b3b4507e47aa1dd5d075919ad242b4b1115b3f80eecd443687/jiter-0.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:83c0efd80b29695058d0fd2fa8a556490dbce9804eac3e281f373bbc99045f6c", size = 204740 },
151 |     { url = "https://files.pythonhosted.org/packages/6c/b0/bfa1f6f2c956b948802ef5a021281978bf53b7a6ca54bb126fd88a5d014e/jiter-0.8.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:ca1f08b8e43dc3bd0594c992fb1fd2f7ce87f7bf0d44358198d6da8034afdf84", size = 301190 },
152 |     { url = "https://files.pythonhosted.org/packages/a4/8f/396ddb4e292b5ea57e45ade5dc48229556b9044bad29a3b4b2dddeaedd52/jiter-0.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5672a86d55416ccd214c778efccf3266b84f87b89063b582167d803246354be4", size = 309334 },
153 |     { url = "https://files.pythonhosted.org/packages/7f/68/805978f2f446fa6362ba0cc2e4489b945695940656edd844e110a61c98f8/jiter-0.8.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58dc9bc9767a1101f4e5e22db1b652161a225874d66f0e5cb8e2c7d1c438b587", size = 333918 },
154 |     { url = "https://files.pythonhosted.org/packages/b3/99/0f71f7be667c33403fa9706e5b50583ae5106d96fab997fa7e2f38ee8347/jiter-0.8.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:37b2998606d6dadbb5ccda959a33d6a5e853252d921fec1792fc902351bb4e2c", size = 356057 },
155 |     { url = "https://files.pythonhosted.org/packages/8d/50/a82796e421a22b699ee4d2ce527e5bcb29471a2351cbdc931819d941a167/jiter-0.8.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ab9a87f3784eb0e098f84a32670cfe4a79cb6512fd8f42ae3d0709f06405d18", size = 379790 },
156 |     { url = "https://files.pythonhosted.org/packages/3c/31/10fb012b00f6d83342ca9e2c9618869ab449f1aa78c8f1b2193a6b49647c/jiter-0.8.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79aec8172b9e3c6d05fd4b219d5de1ac616bd8da934107325a6c0d0e866a21b6", size = 388285 },
157 |     { url = "https://files.pythonhosted.org/packages/c8/81/f15ebf7de57be488aa22944bf4274962aca8092e4f7817f92ffa50d3ee46/jiter-0.8.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:711e408732d4e9a0208008e5892c2966b485c783cd2d9a681f3eb147cf36c7ef", size = 344764 },
158 |     { url = "https://files.pythonhosted.org/packages/b3/e8/0cae550d72b48829ba653eb348cdc25f3f06f8a62363723702ec18e7be9c/jiter-0.8.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:653cf462db4e8c41995e33d865965e79641ef45369d8a11f54cd30888b7e6ff1", size = 376620 },
159 |     { url = "https://files.pythonhosted.org/packages/b8/50/e5478ff9d82534a944c03b63bc217c5f37019d4a34d288db0f079b13c10b/jiter-0.8.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:9c63eaef32b7bebac8ebebf4dabebdbc6769a09c127294db6babee38e9f405b9", size = 510402 },
160 |     { url = "https://files.pythonhosted.org/packages/8e/1e/3de48bbebbc8f7025bd454cedc8c62378c0e32dd483dece5f4a814a5cb55/jiter-0.8.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:eb21aaa9a200d0a80dacc7a81038d2e476ffe473ffdd9c91eb745d623561de05", size = 503018 },
161 |     { url = "https://files.pythonhosted.org/packages/d5/cd/d5a5501d72a11fe3e5fd65c78c884e5164eefe80077680533919be22d3a3/jiter-0.8.2-cp313-cp313-win32.whl", hash = "sha256:789361ed945d8d42850f919342a8665d2dc79e7e44ca1c97cc786966a21f627a", size = 203190 },
162 |     { url = "https://files.pythonhosted.org/packages/51/bf/e5ca301245ba951447e3ad677a02a64a8845b185de2603dabd83e1e4b9c6/jiter-0.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:ab7f43235d71e03b941c1630f4b6e3055d46b6cb8728a17663eaac9d8e83a865", size = 203551 },
163 |     { url = "https://files.pythonhosted.org/packages/2f/3c/71a491952c37b87d127790dd7a0b1ebea0514c6b6ad30085b16bbe00aee6/jiter-0.8.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b426f72cd77da3fec300ed3bc990895e2dd6b49e3bfe6c438592a3ba660e41ca", size = 308347 },
164 |     { url = "https://files.pythonhosted.org/packages/a0/4c/c02408042e6a7605ec063daed138e07b982fdb98467deaaf1c90950cf2c6/jiter-0.8.2-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b2dd880785088ff2ad21ffee205e58a8c1ddabc63612444ae41e5e4b321b39c0", size = 342875 },
165 |     { url = "https://files.pythonhosted.org/packages/91/61/c80ef80ed8a0a21158e289ef70dac01e351d929a1c30cb0f49be60772547/jiter-0.8.2-cp313-cp313t-win_amd64.whl", hash = "sha256:3ac9f578c46f22405ff7f8b1f5848fb753cc4b8377fbec8470a7dc3997ca7566", size = 202374 },
166 | ]
167 | 
168 | [[package]]
169 | name = "markdown-it-py"
170 | version = "3.0.0"
171 | source = { registry = "https://pypi.org/simple" }
172 | dependencies = [
173 |     { name = "mdurl" },
174 | ]
175 | sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
176 | wheels = [
177 |     { url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
178 | ]
179 | 
180 | [[package]]
181 | name = "mdurl"
182 | version = "0.1.2"
183 | source = { registry = "https://pypi.org/simple" }
184 | sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
185 | wheels = [
186 |     { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
187 | ]
188 | 
189 | [[package]]
190 | name = "openai"
191 | version = "1.58.1"
192 | source = { registry = "https://pypi.org/simple" }
193 | dependencies = [
194 |     { name = "anyio" },
195 |     { name = "distro" },
196 |     { name = "httpx" },
197 |     { name = "jiter" },
198 |     { name = "pydantic" },
199 |     { name = "sniffio" },
200 |     { name = "tqdm" },
201 |     { name = "typing-extensions" },
202 | ]
203 | sdist = { url = "https://files.pythonhosted.org/packages/27/3c/b1ecce430ed56fa3ac1b0676966d3250aab9c70a408232b71e419ea62148/openai-1.58.1.tar.gz", hash = "sha256:f5a035fd01e141fc743f4b0e02c41ca49be8fab0866d3b67f5f29b4f4d3c0973", size = 343411 }
204 | wheels = [
205 |     { url = "https://files.pythonhosted.org/packages/8e/5a/d22cd07f1a99b9e8b3c92ee0c1959188db4318828a3d88c9daac120bdd69/openai-1.58.1-py3-none-any.whl", hash = "sha256:e2910b1170a6b7f88ef491ac3a42c387f08bd3db533411f7ee391d166571d63c", size = 454279 },
206 | ]
207 | 
208 | [[package]]
209 | name = "prompt-toolkit"
210 | version = "3.0.50"
211 | source = { registry = "https://pypi.org/simple" }
212 | dependencies = [
213 |     { name = "wcwidth" },
214 | ]
215 | sdist = { url = "https://files.pythonhosted.org/packages/a1/e1/bd15cb8ffdcfeeb2bdc215de3c3cffca11408d829e4b8416dcfe71ba8854/prompt_toolkit-3.0.50.tar.gz", hash = "sha256:544748f3860a2623ca5cd6d2795e7a14f3d0e1c3c9728359013f79877fc89bab", size = 429087 }
216 | wheels = [
217 |     { url = "https://files.pythonhosted.org/packages/e4/ea/d836f008d33151c7a1f62caf3d8dd782e4d15f6a43897f64480c2b8de2ad/prompt_toolkit-3.0.50-py3-none-any.whl", hash = "sha256:9b6427eb19e479d98acff65196a307c555eb567989e6d88ebbb1b509d9779198", size = 387816 },
218 | ]
219 | 
220 | [[package]]
221 | name = "pydantic"
222 | version = "2.10.4"
223 | source = { registry = "https://pypi.org/simple" }
224 | dependencies = [
225 |     { name = "annotated-types" },
226 |     { name = "pydantic-core" },
227 |     { name = "typing-extensions" },
228 | ]
229 | sdist = { url = "https://files.pythonhosted.org/packages/70/7e/fb60e6fee04d0ef8f15e4e01ff187a196fa976eb0f0ab524af4599e5754c/pydantic-2.10.4.tar.gz", hash = "sha256:82f12e9723da6de4fe2ba888b5971157b3be7ad914267dea8f05f82b28254f06", size = 762094 }
230 | wheels = [
231 |     { url = "https://files.pythonhosted.org/packages/f3/26/3e1bbe954fde7ee22a6e7d31582c642aad9e84ffe4b5fb61e63b87cd326f/pydantic-2.10.4-py3-none-any.whl", hash = "sha256:597e135ea68be3a37552fb524bc7d0d66dcf93d395acd93a00682f1efcb8ee3d", size = 431765 },
232 | ]
233 | 
234 | [[package]]
235 | name = "pydantic-core"
236 | version = "2.27.2"
237 | source = { registry = "https://pypi.org/simple" }
238 | dependencies = [
239 |     { name = "typing-extensions" },
240 | ]
241 | sdist = { url = "https://files.pythonhosted.org/packages/fc/01/f3e5ac5e7c25833db5eb555f7b7ab24cd6f8c322d3a3ad2d67a952dc0abc/pydantic_core-2.27.2.tar.gz", hash = "sha256:eb026e5a4c1fee05726072337ff51d1efb6f59090b7da90d30ea58625b1ffb39", size = 413443 }
242 | wheels = [
243 |     { url = "https://files.pythonhosted.org/packages/c2/89/f3450af9d09d44eea1f2c369f49e8f181d742f28220f88cc4dfaae91ea6e/pydantic_core-2.27.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:8e10c99ef58cfdf2a66fc15d66b16c4a04f62bca39db589ae8cba08bc55331bc", size = 1893421 },
244 |     { url = "https://files.pythonhosted.org/packages/9e/e3/71fe85af2021f3f386da42d291412e5baf6ce7716bd7101ea49c810eda90/pydantic_core-2.27.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:26f32e0adf166a84d0cb63be85c562ca8a6fa8de28e5f0d92250c6b7e9e2aff7", size = 1814998 },
245 |     { url = "https://files.pythonhosted.org/packages/a6/3c/724039e0d848fd69dbf5806894e26479577316c6f0f112bacaf67aa889ac/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c19d1ea0673cd13cc2f872f6c9ab42acc4e4f492a7ca9d3795ce2b112dd7e15", size = 1826167 },
246 |     { url = "https://files.pythonhosted.org/packages/2b/5b/1b29e8c1fb5f3199a9a57c1452004ff39f494bbe9bdbe9a81e18172e40d3/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e68c4446fe0810e959cdff46ab0a41ce2f2c86d227d96dc3847af0ba7def306", size = 1865071 },
247 |     { url = "https://files.pythonhosted.org/packages/89/6c/3985203863d76bb7d7266e36970d7e3b6385148c18a68cc8915fd8c84d57/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9640b0059ff4f14d1f37321b94061c6db164fbe49b334b31643e0528d100d99", size = 2036244 },
248 |     { url = "https://files.pythonhosted.org/packages/0e/41/f15316858a246b5d723f7d7f599f79e37493b2e84bfc789e58d88c209f8a/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:40d02e7d45c9f8af700f3452f329ead92da4c5f4317ca9b896de7ce7199ea459", size = 2737470 },
249 |     { url = "https://files.pythonhosted.org/packages/a8/7c/b860618c25678bbd6d1d99dbdfdf0510ccb50790099b963ff78a124b754f/pydantic_core-2.27.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c1fd185014191700554795c99b347d64f2bb637966c4cfc16998a0ca700d048", size = 1992291 },
250 |     { url = "https://files.pythonhosted.org/packages/bf/73/42c3742a391eccbeab39f15213ecda3104ae8682ba3c0c28069fbcb8c10d/pydantic_core-2.27.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d81d2068e1c1228a565af076598f9e7451712700b673de8f502f0334f281387d", size = 1994613 },
251 |     { url = "https://files.pythonhosted.org/packages/94/7a/941e89096d1175d56f59340f3a8ebaf20762fef222c298ea96d36a6328c5/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1a4207639fb02ec2dbb76227d7c751a20b1a6b4bc52850568e52260cae64ca3b", size = 2002355 },
252 |     { url = "https://files.pythonhosted.org/packages/6e/95/2359937a73d49e336a5a19848713555605d4d8d6940c3ec6c6c0ca4dcf25/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:3de3ce3c9ddc8bbd88f6e0e304dea0e66d843ec9de1b0042b0911c1663ffd474", size = 2126661 },
253 |     { url = "https://files.pythonhosted.org/packages/2b/4c/ca02b7bdb6012a1adef21a50625b14f43ed4d11f1fc237f9d7490aa5078c/pydantic_core-2.27.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:30c5f68ded0c36466acede341551106821043e9afaad516adfb6e8fa80a4e6a6", size = 2153261 },
254 |     { url = "https://files.pythonhosted.org/packages/72/9d/a241db83f973049a1092a079272ffe2e3e82e98561ef6214ab53fe53b1c7/pydantic_core-2.27.2-cp311-cp311-win32.whl", hash = "sha256:c70c26d2c99f78b125a3459f8afe1aed4d9687c24fd677c6a4436bc042e50d6c", size = 1812361 },
255 |     { url = "https://files.pythonhosted.org/packages/e8/ef/013f07248041b74abd48a385e2110aa3a9bbfef0fbd97d4e6d07d2f5b89a/pydantic_core-2.27.2-cp311-cp311-win_amd64.whl", hash = "sha256:08e125dbdc505fa69ca7d9c499639ab6407cfa909214d500897d02afb816e7cc", size = 1982484 },
256 |     { url = "https://files.pythonhosted.org/packages/10/1c/16b3a3e3398fd29dca77cea0a1d998d6bde3902fa2706985191e2313cc76/pydantic_core-2.27.2-cp311-cp311-win_arm64.whl", hash = "sha256:26f0d68d4b235a2bae0c3fc585c585b4ecc51382db0e3ba402a22cbc440915e4", size = 1867102 },
257 |     { url = "https://files.pythonhosted.org/packages/d6/74/51c8a5482ca447871c93e142d9d4a92ead74de6c8dc5e66733e22c9bba89/pydantic_core-2.27.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9e0c8cfefa0ef83b4da9588448b6d8d2a2bf1a53c3f1ae5fca39eb3061e2f0b0", size = 1893127 },
258 |     { url = "https://files.pythonhosted.org/packages/d3/f3/c97e80721735868313c58b89d2de85fa80fe8dfeeed84dc51598b92a135e/pydantic_core-2.27.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:83097677b8e3bd7eaa6775720ec8e0405f1575015a463285a92bfdfe254529ef", size = 1811340 },
259 |     { url = "https://files.pythonhosted.org/packages/9e/91/840ec1375e686dbae1bd80a9e46c26a1e0083e1186abc610efa3d9a36180/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:172fce187655fece0c90d90a678424b013f8fbb0ca8b036ac266749c09438cb7", size = 1822900 },
260 |     { url = "https://files.pythonhosted.org/packages/f6/31/4240bc96025035500c18adc149aa6ffdf1a0062a4b525c932065ceb4d868/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:519f29f5213271eeeeb3093f662ba2fd512b91c5f188f3bb7b27bc5973816934", size = 1869177 },
261 |     { url = "https://files.pythonhosted.org/packages/fa/20/02fbaadb7808be578317015c462655c317a77a7c8f0ef274bc016a784c54/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05e3a55d124407fffba0dd6b0c0cd056d10e983ceb4e5dbd10dda135c31071d6", size = 2038046 },
262 |     { url = "https://files.pythonhosted.org/packages/06/86/7f306b904e6c9eccf0668248b3f272090e49c275bc488a7b88b0823444a4/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9c3ed807c7b91de05e63930188f19e921d1fe90de6b4f5cd43ee7fcc3525cb8c", size = 2685386 },
263 |     { url = "https://files.pythonhosted.org/packages/8d/f0/49129b27c43396581a635d8710dae54a791b17dfc50c70164866bbf865e3/pydantic_core-2.27.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fb4aadc0b9a0c063206846d603b92030eb6f03069151a625667f982887153e2", size = 1997060 },
264 |     { url = "https://files.pythonhosted.org/packages/0d/0f/943b4af7cd416c477fd40b187036c4f89b416a33d3cc0ab7b82708a667aa/pydantic_core-2.27.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28ccb213807e037460326424ceb8b5245acb88f32f3d2777427476e1b32c48c4", size = 2004870 },
265 |     { url = "https://files.pythonhosted.org/packages/35/40/aea70b5b1a63911c53a4c8117c0a828d6790483f858041f47bab0b779f44/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:de3cd1899e2c279b140adde9357c4495ed9d47131b4a4eaff9052f23398076b3", size = 1999822 },
266 |     { url = "https://files.pythonhosted.org/packages/f2/b3/807b94fd337d58effc5498fd1a7a4d9d59af4133e83e32ae39a96fddec9d/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:220f892729375e2d736b97d0e51466252ad84c51857d4d15f5e9692f9ef12be4", size = 2130364 },
267 |     { url = "https://files.pythonhosted.org/packages/fc/df/791c827cd4ee6efd59248dca9369fb35e80a9484462c33c6649a8d02b565/pydantic_core-2.27.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a0fcd29cd6b4e74fe8ddd2c90330fd8edf2e30cb52acda47f06dd615ae72da57", size = 2158303 },
268 |     { url = "https://files.pythonhosted.org/packages/9b/67/4e197c300976af185b7cef4c02203e175fb127e414125916bf1128b639a9/pydantic_core-2.27.2-cp312-cp312-win32.whl", hash = "sha256:1e2cb691ed9834cd6a8be61228471d0a503731abfb42f82458ff27be7b2186fc", size = 1834064 },
269 |     { url = "https://files.pythonhosted.org/packages/1f/ea/cd7209a889163b8dcca139fe32b9687dd05249161a3edda62860430457a5/pydantic_core-2.27.2-cp312-cp312-win_amd64.whl", hash = "sha256:cc3f1a99a4f4f9dd1de4fe0312c114e740b5ddead65bb4102884b384c15d8bc9", size = 1989046 },
270 |     { url = "https://files.pythonhosted.org/packages/bc/49/c54baab2f4658c26ac633d798dab66b4c3a9bbf47cff5284e9c182f4137a/pydantic_core-2.27.2-cp312-cp312-win_arm64.whl", hash = "sha256:3911ac9284cd8a1792d3cb26a2da18f3ca26c6908cc434a18f730dc0db7bfa3b", size = 1885092 },
271 |     { url = "https://files.pythonhosted.org/packages/41/b1/9bc383f48f8002f99104e3acff6cba1231b29ef76cfa45d1506a5cad1f84/pydantic_core-2.27.2-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7d14bd329640e63852364c306f4d23eb744e0f8193148d4044dd3dacdaacbd8b", size = 1892709 },
272 |     { url = "https://files.pythonhosted.org/packages/10/6c/e62b8657b834f3eb2961b49ec8e301eb99946245e70bf42c8817350cbefc/pydantic_core-2.27.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:82f91663004eb8ed30ff478d77c4d1179b3563df6cdb15c0817cd1cdaf34d154", size = 1811273 },
273 |     { url = "https://files.pythonhosted.org/packages/ba/15/52cfe49c8c986e081b863b102d6b859d9defc63446b642ccbbb3742bf371/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71b24c7d61131bb83df10cc7e687433609963a944ccf45190cfc21e0887b08c9", size = 1823027 },
274 |     { url = "https://files.pythonhosted.org/packages/b1/1c/b6f402cfc18ec0024120602bdbcebc7bdd5b856528c013bd4d13865ca473/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fa8e459d4954f608fa26116118bb67f56b93b209c39b008277ace29937453dc9", size = 1868888 },
275 |     { url = "https://files.pythonhosted.org/packages/bd/7b/8cb75b66ac37bc2975a3b7de99f3c6f355fcc4d89820b61dffa8f1e81677/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce8918cbebc8da707ba805b7fd0b382816858728ae7fe19a942080c24e5b7cd1", size = 2037738 },
276 |     { url = "https://files.pythonhosted.org/packages/c8/f1/786d8fe78970a06f61df22cba58e365ce304bf9b9f46cc71c8c424e0c334/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:eda3f5c2a021bbc5d976107bb302e0131351c2ba54343f8a496dc8783d3d3a6a", size = 2685138 },
277 |     { url = "https://files.pythonhosted.org/packages/a6/74/d12b2cd841d8724dc8ffb13fc5cef86566a53ed358103150209ecd5d1999/pydantic_core-2.27.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8086fa684c4775c27f03f062cbb9eaa6e17f064307e86b21b9e0abc9c0f02e", size = 1997025 },
278 |     { url = "https://files.pythonhosted.org/packages/a0/6e/940bcd631bc4d9a06c9539b51f070b66e8f370ed0933f392db6ff350d873/pydantic_core-2.27.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8d9b3388db186ba0c099a6d20f0604a44eabdeef1777ddd94786cdae158729e4", size = 2004633 },
279 |     { url = "https://files.pythonhosted.org/packages/50/cc/a46b34f1708d82498c227d5d80ce615b2dd502ddcfd8376fc14a36655af1/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7a66efda2387de898c8f38c0cf7f14fca0b51a8ef0b24bfea5849f1b3c95af27", size = 1999404 },
280 |     { url = "https://files.pythonhosted.org/packages/ca/2d/c365cfa930ed23bc58c41463bae347d1005537dc8db79e998af8ba28d35e/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:18a101c168e4e092ab40dbc2503bdc0f62010e95d292b27827871dc85450d7ee", size = 2130130 },
281 |     { url = "https://files.pythonhosted.org/packages/f4/d7/eb64d015c350b7cdb371145b54d96c919d4db516817f31cd1c650cae3b21/pydantic_core-2.27.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ba5dd002f88b78a4215ed2f8ddbdf85e8513382820ba15ad5ad8955ce0ca19a1", size = 2157946 },
282 |     { url = "https://files.pythonhosted.org/packages/a4/99/bddde3ddde76c03b65dfd5a66ab436c4e58ffc42927d4ff1198ffbf96f5f/pydantic_core-2.27.2-cp313-cp313-win32.whl", hash = "sha256:1ebaf1d0481914d004a573394f4be3a7616334be70261007e47c2a6fe7e50130", size = 1834387 },
283 |     { url = "https://files.pythonhosted.org/packages/71/47/82b5e846e01b26ac6f1893d3c5f9f3a2eb6ba79be26eef0b759b4fe72946/pydantic_core-2.27.2-cp313-cp313-win_amd64.whl", hash = "sha256:953101387ecf2f5652883208769a79e48db18c6df442568a0b5ccd8c2723abee", size = 1990453 },
284 |     { url = "https://files.pythonhosted.org/packages/51/b2/b2b50d5ecf21acf870190ae5d093602d95f66c9c31f9d5de6062eb329ad1/pydantic_core-2.27.2-cp313-cp313-win_arm64.whl", hash = "sha256:ac4dbfd1691affb8f48c2c13241a2e3b60ff23247cbcf981759c768b6633cf8b", size = 1885186 },
285 | ]
286 | 
287 | [[package]]
288 | name = "pygments"
289 | version = "2.18.0"
290 | source = { registry = "https://pypi.org/simple" }
291 | sdist = { url = "https://files.pythonhosted.org/packages/8e/62/8336eff65bcbc8e4cb5d05b55faf041285951b6e80f33e2bff2024788f31/pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199", size = 4891905 }
292 | wheels = [
293 |     { url = "https://files.pythonhosted.org/packages/f7/3f/01c8b82017c199075f8f788d0d906b9ffbbc5a47dc9918a945e13d5a2bda/pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a", size = 1205513 },
294 | ]
295 | 
296 | [[package]]
297 | name = "python-dotenv"
298 | version = "1.0.1"
299 | source = { registry = "https://pypi.org/simple" }
300 | sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
301 | wheels = [
302 |     { url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
303 | ]
304 | 
305 | [[package]]
306 | name = "rich"
307 | version = "13.9.4"
308 | source = { registry = "https://pypi.org/simple" }
309 | dependencies = [
310 |     { name = "markdown-it-py" },
311 |     { name = "pygments" },
312 | ]
313 | sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
314 | wheels = [
315 |     { url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
316 | ]
317 | 
318 | [[package]]
319 | name = "sniffio"
320 | version = "1.3.1"
321 | source = { registry = "https://pypi.org/simple" }
322 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
323 | wheels = [
324 |     { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
325 | ]
326 | 
327 | [[package]]
328 | name = "tqdm"
329 | version = "4.67.1"
330 | source = { registry = "https://pypi.org/simple" }
331 | dependencies = [
332 |     { name = "colorama", marker = "platform_system == 'Windows'" },
333 | ]
334 | sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737 }
335 | wheels = [
336 |     { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540 },
337 | ]
338 | 
339 | [[package]]
340 | name = "typing-extensions"
341 | version = "4.12.2"
342 | source = { registry = "https://pypi.org/simple" }
343 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 }
344 | wheels = [
345 |     { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
346 | ]
347 | 
348 | [[package]]
349 | name = "wcwidth"
350 | version = "0.2.13"
351 | source = { registry = "https://pypi.org/simple" }
352 | sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 }
353 | wheels = [
354 |     { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
355 | ]
356 | 


--------------------------------------------------------------------------------