├── .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 | --------------------------------------------------------------------------------