├── .env.sample ├── .gitignore ├── AI_DOCS ├── anthropic_example.md └── computer_use.md ├── README.md ├── data └── app.db ├── images └── computer-use-thumb.png ├── pyproject.toml ├── src └── anthropic_computer_use │ ├── __init__.py │ └── main.py └── uv.lock /.env.sample: -------------------------------------------------------------------------------- 1 | ANTHROPIC_API_KEY= 2 | BASH_SYSTEM_PROMPT= 3 | EDITOR_SYSTEM_PROMPT= 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | output/ 2 | input/ 3 | 4 | # Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore 5 | 6 | # Logs 7 | 8 | logs 9 | _.log 10 | npm-debug.log_ 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | .pnpm-debug.log* 15 | 16 | # Caches 17 | 18 | .cache 19 | 20 | # Diagnostic reports (https://nodejs.org/api/report.html) 21 | 22 | report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json 23 | 24 | # Runtime data 25 | 26 | pids 27 | _.pid 28 | _.seed 29 | *.pid.lock 30 | 31 | # Directory for instrumented libs generated by jscoverage/JSCover 32 | 33 | lib-cov 34 | 35 | # Coverage directory used by tools like istanbul 36 | 37 | coverage 38 | *.lcov 39 | 40 | # nyc test coverage 41 | 42 | .nyc_output 43 | 44 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 45 | 46 | .grunt 47 | 48 | # Bower dependency directory (https://bower.io/) 49 | 50 | bower_components 51 | 52 | # node-waf configuration 53 | 54 | .lock-wscript 55 | 56 | # Compiled binary addons (https://nodejs.org/api/addons.html) 57 | 58 | build/Release 59 | 60 | # Dependency directories 61 | 62 | node_modules/ 63 | jspm_packages/ 64 | 65 | # Snowpack dependency directory (https://snowpack.dev/) 66 | 67 | web_modules/ 68 | 69 | # TypeScript cache 70 | 71 | *.tsbuildinfo 72 | 73 | # Optional npm cache directory 74 | 75 | .npm 76 | 77 | # Optional eslint cache 78 | 79 | .eslintcache 80 | 81 | # Optional stylelint cache 82 | 83 | .stylelintcache 84 | 85 | # Microbundle cache 86 | 87 | .rpt2_cache/ 88 | .rts2_cache_cjs/ 89 | .rts2_cache_es/ 90 | .rts2_cache_umd/ 91 | 92 | # Optional REPL history 93 | 94 | .node_repl_history 95 | 96 | # Output of 'npm pack' 97 | 98 | *.tgz 99 | 100 | # Yarn Integrity file 101 | 102 | .yarn-integrity 103 | 104 | # dotenv environment variable files 105 | 106 | .env 107 | .env.development.local 108 | .env.test.local 109 | .env.production.local 110 | .env.local 111 | 112 | # parcel-bundler cache (https://parceljs.org/) 113 | 114 | .parcel-cache 115 | 116 | # Next.js build output 117 | 118 | .next 119 | out 120 | 121 | # Nuxt.js build / generate output 122 | 123 | .nuxt 124 | dist 125 | 126 | # Gatsby files 127 | 128 | # Comment in the public line in if your project uses Gatsby and not Next.js 129 | 130 | # https://nextjs.org/blog/next-9-1#public-directory-support 131 | 132 | # public 133 | 134 | # vuepress build output 135 | 136 | .vuepress/dist 137 | 138 | # vuepress v2.x temp and cache directory 139 | 140 | .temp 141 | 142 | # Docusaurus cache and generated files 143 | 144 | .docusaurus 145 | 146 | # Serverless directories 147 | 148 | .serverless/ 149 | 150 | # FuseBox cache 151 | 152 | .fusebox/ 153 | 154 | # DynamoDB Local files 155 | 156 | .dynamodb/ 157 | 158 | # TernJS port file 159 | 160 | .tern-port 161 | 162 | # Stores VSCode versions used for testing VSCode extensions 163 | 164 | .vscode-test 165 | 166 | # yarn v2 167 | 168 | .yarn/cache 169 | .yarn/unplugged 170 | .yarn/build-state.yml 171 | .yarn/install-state.gz 172 | .pnp.* 173 | 174 | # IntelliJ based IDEs 175 | .idea 176 | 177 | # Finder (MacOS) folder config 178 | .DS_Store 179 | .aider* 180 | 181 | __pycache__/ 182 | 183 | .venv/ 184 | 185 | editor_dir/ 186 | sessions/ 187 | 188 | bup/ -------------------------------------------------------------------------------- /AI_DOCS/anthropic_example.md: -------------------------------------------------------------------------------- 1 | # Example anthropic computer use code 2 | 3 | ``` 4 | """ 5 | Agentic sampling loop that calls the Anthropic API and local implementation of anthropic-defined computer use tools. 6 | """ 7 | 8 | import platform 9 | from collections.abc import Callable 10 | from datetime import datetime 11 | from enum import StrEnum 12 | from typing import Any, cast 13 | 14 | import httpx 15 | from anthropic import ( 16 | Anthropic, 17 | AnthropicBedrock, 18 | AnthropicVertex, 19 | APIError, 20 | APIResponseValidationError, 21 | APIStatusError, 22 | ) 23 | from anthropic.types.beta import ( 24 | BetaCacheControlEphemeralParam, 25 | BetaContentBlockParam, 26 | BetaImageBlockParam, 27 | BetaMessage, 28 | BetaMessageParam, 29 | BetaTextBlock, 30 | BetaTextBlockParam, 31 | BetaToolResultBlockParam, 32 | BetaToolUseBlockParam, 33 | ) 34 | 35 | from .tools import BashTool, ComputerTool, EditTool, ToolCollection, ToolResult 36 | 37 | COMPUTER_USE_BETA_FLAG = "computer-use-2024-10-22" 38 | PROMPT_CACHING_BETA_FLAG = "prompt-caching-2024-07-31" 39 | 40 | 41 | class APIProvider(StrEnum): 42 | ANTHROPIC = "anthropic" 43 | BEDROCK = "bedrock" 44 | VERTEX = "vertex" 45 | 46 | 47 | PROVIDER_TO_DEFAULT_MODEL_NAME: dict[APIProvider, str] = { 48 | APIProvider.ANTHROPIC: "claude-3-5-sonnet-20241022", 49 | APIProvider.BEDROCK: "anthropic.claude-3-5-sonnet-20241022-v2:0", 50 | APIProvider.VERTEX: "claude-3-5-sonnet-v2@20241022", 51 | } 52 | 53 | 54 | # This system prompt is optimized for the Docker environment in this repository and 55 | # specific tool combinations enabled. 56 | # We encourage modifying this system prompt to ensure the model has context for the 57 | # environment it is running in, and to provide any additional information that may be 58 | # helpful for the task at hand. 59 | SYSTEM_PROMPT = f""" 60 | * You are utilising an Ubuntu virtual machine using {platform.machine()} architecture with internet access. 61 | * You can feel free to install Ubuntu applications with your bash tool. Use curl instead of wget. 62 | * To open firefox, please just click on the firefox icon. Note, firefox-esr is what is installed on your system. 63 | * Using bash tool you can start GUI applications, but you need to set export DISPLAY=:1 and use a subshell. For example "(DISPLAY=:1 xterm &)". GUI apps run with bash tool will appear within your desktop environment, but they may take some time to appear. Take a screenshot to confirm it did. 64 | * When using your bash tool with commands that are expected to output very large quantities of text, redirect into a tmp file and use str_replace_editor or `grep -n -B -A ` to confirm output. 65 | * When viewing a page it can be helpful to zoom out so that you can see everything on the page. Either that, or make sure you scroll down to see everything before deciding something isn't available. 66 | * When using your computer function calls, they take a while to run and send back to you. Where possible/feasible, try to chain multiple of these calls all into one function calls request. 67 | * The current date is {datetime.today().strftime('%A, %B %-d, %Y')}. 68 | 69 | 70 | 71 | * When using Firefox, if a startup wizard appears, IGNORE IT. Do not even click "skip this step". Instead, click on the address bar where it says "Search or enter address", and enter the appropriate search term or URL there. 72 | * If the item you are looking at is a pdf, if after taking a single screenshot of the pdf it seems that you want to read the entire document instead of trying to continue to read the pdf from your screenshots + navigation, determine the URL, use curl to download the pdf, install and use pdftotext to convert it to a text file, and then read that text file directly with your StrReplaceEditTool. 73 | """ 74 | 75 | 76 | async def sampling_loop( 77 | *, 78 | model: str, 79 | provider: APIProvider, 80 | system_prompt_suffix: str, 81 | messages: list[BetaMessageParam], 82 | output_callback: Callable[[BetaContentBlockParam], None], 83 | tool_output_callback: Callable[[ToolResult, str], None], 84 | api_response_callback: Callable[ 85 | [httpx.Request, httpx.Response | object | None, Exception | None], None 86 | ], 87 | api_key: str, 88 | only_n_most_recent_images: int | None = None, 89 | max_tokens: int = 4096, 90 | ): 91 | """ 92 | Agentic sampling loop for the assistant/tool interaction of computer use. 93 | """ 94 | tool_collection = ToolCollection( 95 | ComputerTool(), 96 | BashTool(), 97 | EditTool(), 98 | ) 99 | system = BetaTextBlockParam( 100 | type="text", 101 | text=f"{SYSTEM_PROMPT}{' ' + system_prompt_suffix if system_prompt_suffix else ''}", 102 | ) 103 | 104 | while True: 105 | enable_prompt_caching = False 106 | betas = [COMPUTER_USE_BETA_FLAG] 107 | image_truncation_threshold = 10 108 | if provider == APIProvider.ANTHROPIC: 109 | client = Anthropic(api_key=api_key) 110 | enable_prompt_caching = True 111 | elif provider == APIProvider.VERTEX: 112 | client = AnthropicVertex() 113 | elif provider == APIProvider.BEDROCK: 114 | client = AnthropicBedrock() 115 | 116 | if enable_prompt_caching: 117 | betas.append(PROMPT_CACHING_BETA_FLAG) 118 | _inject_prompt_caching(messages) 119 | # Is it ever worth it to bust the cache with prompt caching? 120 | image_truncation_threshold = 50 121 | system["cache_control"] = {"type": "ephemeral"} 122 | 123 | if only_n_most_recent_images: 124 | _maybe_filter_to_n_most_recent_images( 125 | messages, 126 | only_n_most_recent_images, 127 | min_removal_threshold=image_truncation_threshold, 128 | ) 129 | 130 | # Call the API 131 | # we use raw_response to provide debug information to streamlit. Your 132 | # implementation may be able call the SDK directly with: 133 | # `response = client.messages.create(...)` instead. 134 | try: 135 | raw_response = client.beta.messages.with_raw_response.create( 136 | max_tokens=max_tokens, 137 | messages=messages, 138 | model=model, 139 | system=[system], 140 | tools=tool_collection.to_params(), 141 | betas=betas, 142 | ) 143 | except (APIStatusError, APIResponseValidationError) as e: 144 | api_response_callback(e.request, e.response, e) 145 | return messages 146 | except APIError as e: 147 | api_response_callback(e.request, e.body, e) 148 | return messages 149 | 150 | api_response_callback( 151 | raw_response.http_response.request, raw_response.http_response, None 152 | ) 153 | 154 | response = raw_response.parse() 155 | 156 | response_params = _response_to_params(response) 157 | messages.append( 158 | { 159 | "role": "assistant", 160 | "content": response_params, 161 | } 162 | ) 163 | 164 | tool_result_content: list[BetaToolResultBlockParam] = [] 165 | for content_block in response_params: 166 | output_callback(content_block) 167 | if content_block["type"] == "tool_use": 168 | result = await tool_collection.run( 169 | name=content_block["name"], 170 | tool_input=cast(dict[str, Any], content_block["input"]), 171 | ) 172 | tool_result_content.append( 173 | _make_api_tool_result(result, content_block["id"]) 174 | ) 175 | tool_output_callback(result, content_block["id"]) 176 | 177 | if not tool_result_content: 178 | return messages 179 | 180 | messages.append({"content": tool_result_content, "role": "user"}) 181 | 182 | 183 | def _maybe_filter_to_n_most_recent_images( 184 | messages: list[BetaMessageParam], 185 | images_to_keep: int, 186 | min_removal_threshold: int, 187 | ): 188 | """ 189 | With the assumption that images are screenshots that are of diminishing value as 190 | the conversation progresses, remove all but the final `images_to_keep` tool_result 191 | images in place, with a chunk of min_removal_threshold to reduce the amount we 192 | break the implicit prompt cache. 193 | """ 194 | if images_to_keep is None: 195 | return messages 196 | 197 | tool_result_blocks = cast( 198 | list[BetaToolResultBlockParam], 199 | [ 200 | item 201 | for message in messages 202 | for item in ( 203 | message["content"] if isinstance(message["content"], list) else [] 204 | ) 205 | if isinstance(item, dict) and item.get("type") == "tool_result" 206 | ], 207 | ) 208 | 209 | total_images = sum( 210 | 1 211 | for tool_result in tool_result_blocks 212 | for content in tool_result.get("content", []) 213 | if isinstance(content, dict) and content.get("type") == "image" 214 | ) 215 | 216 | images_to_remove = total_images - images_to_keep 217 | # for better cache behavior, we want to remove in chunks 218 | images_to_remove -= images_to_remove % min_removal_threshold 219 | 220 | for tool_result in tool_result_blocks: 221 | if isinstance(tool_result.get("content"), list): 222 | new_content = [] 223 | for content in tool_result.get("content", []): 224 | if isinstance(content, dict) and content.get("type") == "image": 225 | if images_to_remove > 0: 226 | images_to_remove -= 1 227 | continue 228 | new_content.append(content) 229 | tool_result["content"] = new_content 230 | 231 | 232 | def _response_to_params( 233 | response: BetaMessage, 234 | ) -> list[BetaTextBlockParam | BetaToolUseBlockParam]: 235 | res: list[BetaTextBlockParam | BetaToolUseBlockParam] = [] 236 | for block in response.content: 237 | if isinstance(block, BetaTextBlock): 238 | res.append({"type": "text", "text": block.text}) 239 | else: 240 | res.append(cast(BetaToolUseBlockParam, block.model_dump())) 241 | return res 242 | 243 | 244 | def _inject_prompt_caching( 245 | messages: list[BetaMessageParam], 246 | ): 247 | """ 248 | Set cache breakpoints for the 3 most recent turns 249 | one cache breakpoint is left for tools/system prompt, to be shared across sessions 250 | """ 251 | 252 | breakpoints_remaining = 3 253 | for message in reversed(messages): 254 | if message["role"] == "user" and isinstance( 255 | content := message["content"], list 256 | ): 257 | if breakpoints_remaining: 258 | breakpoints_remaining -= 1 259 | content[-1]["cache_control"] = BetaCacheControlEphemeralParam( 260 | {"type": "ephemeral"} 261 | ) 262 | else: 263 | content[-1].pop("cache_control", None) 264 | # we'll only every have one extra turn per loop 265 | break 266 | 267 | 268 | def _make_api_tool_result( 269 | result: ToolResult, tool_use_id: str 270 | ) -> BetaToolResultBlockParam: 271 | """Convert an agent ToolResult to an API ToolResultBlockParam.""" 272 | tool_result_content: list[BetaTextBlockParam | BetaImageBlockParam] | str = [] 273 | is_error = False 274 | if result.error: 275 | is_error = True 276 | tool_result_content = _maybe_prepend_system_tool_result(result, result.error) 277 | else: 278 | if result.output: 279 | tool_result_content.append( 280 | { 281 | "type": "text", 282 | "text": _maybe_prepend_system_tool_result(result, result.output), 283 | } 284 | ) 285 | if result.base64_image: 286 | tool_result_content.append( 287 | { 288 | "type": "image", 289 | "source": { 290 | "type": "base64", 291 | "media_type": "image/png", 292 | "data": result.base64_image, 293 | }, 294 | } 295 | ) 296 | return { 297 | "type": "tool_result", 298 | "content": tool_result_content, 299 | "tool_use_id": tool_use_id, 300 | "is_error": is_error, 301 | } 302 | 303 | 304 | def _maybe_prepend_system_tool_result(result: ToolResult, result_text: str): 305 | if result.system: 306 | result_text = f"{result.system}\n{result_text}" 307 | return result_text 308 | ``` -------------------------------------------------------------------------------- /AI_DOCS/computer_use.md: -------------------------------------------------------------------------------- 1 | Build with Claude 2 | Computer use (beta) 3 | The upgraded Claude 3.5 Sonnet model is capable of interacting with tools that can manipulate a computer desktop environment. 4 | 5 | Computer use is a beta feature. Please be aware that computer use poses unique risks that are distinct from standard API features or chat interfaces. These risks are heightened when using computer use to interact with the internet. To minimize risks, consider taking precautions such as: 6 | 7 | Use a dedicated virtual machine or container with minimal privileges to prevent direct system attacks or accidents. 8 | Avoid giving the model access to sensitive data, such as account login information, to prevent information theft. 9 | Limit internet access to an allowlist of domains to reduce exposure to malicious content. 10 | Ask a human to confirm decisions that may result in meaningful real-world consequences as well as any tasks requiring affirmative consent, such as accepting cookies, executing financial transactions, or agreeing to terms of service. 11 | In some circumstances, Claude will follow commands found in content even if it conflicts with the user’s instructions. For example, Claude instructions on webpages or contained in images may override instructions or cause Claude to make mistakes. We suggest taking precautions to isolate Claude from sensitive data and actions to avoid risks related to prompt injection. 12 | 13 | Finally, please inform end users of relevant risks and obtain their consent prior to enabling computer use in your own products. 14 | 15 | Computer use reference implementation 16 | Get started quickly with our computer use reference implementation that includes a web interface, Docker container, example tool implementations, and an agent loop. 17 | 18 | Please use this form to provide feedback on the quality of the model responses, the API itself, or the quality of the documentation - we cannot wait to hear from you! 19 | 20 | Here’s an example of how to provide computer use tools to Claude using the Messages API: 21 | 22 | 23 | Shell 24 | 25 | Python 26 | 27 | TypeScript 28 | 29 | import anthropic 30 | 31 | client = anthropic.Anthropic() 32 | 33 | response = client.beta.messages.create( 34 | model="claude-3-5-sonnet-20241022", 35 | max_tokens=1024, 36 | tools=[ 37 | { 38 | "type": "computer_20241022", 39 | "name": "computer", 40 | "display_width_px": 1024, 41 | "display_height_px": 768, 42 | "display_number": 1, 43 | }, 44 | { 45 | "type": "text_editor_20241022", 46 | "name": "str_replace_editor" 47 | }, 48 | { 49 | "type": "bash_20241022", 50 | "name": "bash" 51 | } 52 | ], 53 | messages=[{"role": "user", "content": "Save a picture of a cat to my desktop."}], 54 | betas=["computer-use-2024-10-22"], 55 | ) 56 | print(response) 57 | ​ 58 | How computer use works 59 | 1. Provide Claude with computer use tools and a user prompt 60 | 61 | Add Anthropic-defined computer use tools to your API request. 62 | Include a user prompt that might require these tools, e.g., “Save a picture of a cat to my desktop.” 63 | 2. Claude decides to use a tool 64 | 65 | Claude loads the stored computer use tool definitions and assesses if any tools can help with the user’s query. 66 | If yes, Claude constructs a properly formatted tool use request. 67 | The API response has a stop_reason of tool_use, signaling Claude’s intent. 68 | 3. Extract tool input, evaluate the tool on a computer, and return results 69 | 70 | On your end, extract the tool name and input from Claude’s request. 71 | Use the tool on a container or Virtual Machine. 72 | Continue the conversation with a new user message containing a tool_result content block. 73 | 4. Claude continues calling computer use tools until it's completed the task 74 | 75 | Claude analyzes the tool results to determine if more tool use is needed or the task has been completed. 76 | If Claude decides it needs another tool, it responds with another tool_use stop_reason and you should return to step 3. 77 | Otherwise, it crafts a text response to the user. 78 | We refer to the repetition of steps 3 and 4 without user input as the “agent loop” - i.e., Claude responding with a tool use request and your application responding to Claude with the results of evaluating that request. 79 | 80 | ​ 81 | How to implement computer use 82 | ​ 83 | Start with our reference implementation 84 | We have built a reference implementation that includes everything you need to get started quickly with computer use: 85 | 86 | A containerized environment suitable for computer use with Claude 87 | Implementations of the computer use tools 88 | An agent loop that interacts with the Anthropic API and executes the computer use tools 89 | A web interface to interact with the container, agent loop, and tools. 90 | We recommend trying the reference implementation out before reading the rest of this documentation. 91 | 92 | ​ 93 | Optimize model performance with prompting 94 | Here are some tips on how to get the best quality outputs: 95 | 96 | Specify simple, well-defined tasks and provide explicit instructions for each step. 97 | Claude sometimes assumes outcomes of its actions without explicitly checking their results. To prevent this you can prompt Claude with After each step, take a screenshot and carefully evaluate if you have achieved the right outcome. Explicitly show your thinking: "I have evaluated step X..." If not correct, try again. Only when you confirm a step was executed correctly should you move on to the next one. 98 | Some UI elements (like dropdowns and scrollbars) might be tricky for Claude to manipulate using mouse movements. If you experience this, try prompting the model to use keyboard shortcuts. 99 | For repeatable tasks or UI interactions, include example screenshots and tool calls of successful outcomes in your prompt. 100 | If you repeatedly encounter a clear set of issues or know in advance the tasks Claude will need to complete, use the system prompt to provide Claude with explicit tips or instructions on how to do the tasks successfully. 101 | 102 | ​ 103 | System prompts 104 | When one of the Anthropic-defined tools is requested via the Anthropic API, a computer use-specific system prompt is generated. It’s similar to the tool use system prompt but starts with: 105 | 106 | You have access to a set of functions you can use to answer the user’s question. This includes access to a sandboxed computing environment. You do NOT currently have the ability to inspect files or interact with external resources, except by invoking the below functions. 107 | 108 | As with regular tool use, the user-provided system_prompt field is still respected and used in the construction of the combined system prompt. 109 | 110 | ​ 111 | Understand Anthropic-defined tools 112 | As a beta, these tool definitions are subject to change. 113 | 114 | We have provided a set of tools that enable Claude to effectively use computers. When specifying an Anthropic-defined tool, description and tool_schema fields are not necessary or allowed. 115 | 116 | Anthropic-defined tools are user executed 117 | 118 | Anthropic-defined tools are defined by Anthropic but you must explicitly evaluate the results of the tool and return the tool_results to Claude. As with any tool, the model does not automatically execute the tool. 119 | 120 | We currently provide 3 Anthropic-defined tools: 121 | 122 | { "type": "computer_20241022", "name": "computer" } 123 | { "type": "text_editor_20241022", "name": "str_replace_editor" } 124 | { "type": "bash_20241022", "name": "bash" } 125 | The type field identifies the tool and its parameters for validation purposes, the name field is the tool name exposed to the model. 126 | 127 | If you want to prompt the model to use one of these tools, you can explicitly refer the tool by the name field. The name field must be unique within the tool list; you cannot define a tool with the same name as an Anthropic-defined tool in the same API call. 128 | 129 | We do not recommend defining tools with the names of Anthropic-defined tools. While you can still redefine tools with these names (as long as the tool name is unique in your tools block), doing so may result in degraded model performance. 130 | 131 | 132 | Computer tool 133 | 134 | We do not recommend sending screenshots in resolutions above XGA/WXGA to avoid issues related to image resizing. Relying on the image resizing behavior in the API will result in lower model accuracy and slower performance than directly implementing scaling yourself. 135 | 136 | The reference repository demonstrates how to scale from higher resolutions to a suggested resolution. 137 | 138 | Type 139 | computer_20241022 140 | 141 | Parameters 142 | display_width_px: Required The width of the display being controlled by the model in pixels. 143 | display_height_px: Required The height of the display being controlled by the model in pixels. 144 | display_number: Optional The display number to control (only relevant for X11 environments). If specified, the tool will be provided a display number in the tool definition. 145 | Tool description 146 | We are providing our tool description for reference only. You should not specify this in your Anthropic-defined tool call. 147 | 148 | 149 | Use a mouse and keyboard to interact with a computer, and take screenshots. 150 | * This is an interface to a desktop GUI. You do not have access to a terminal or applications menu. You must click on desktop icons to start applications. 151 | * Some applications may take time to start or process actions, so you may need to wait and take successive screenshots to see the results of your actions. E.g. if you click on Firefox and a window doesn't open, try taking another screenshot. 152 | * The screen's resolution is {{ display_width_px }}x{{ display_height_px }}. 153 | * The display number is {{ display_number }} 154 | * Whenever you intend to move the cursor to click on an element like an icon, you should consult a screenshot to determine the coordinates of the element before moving the cursor. 155 | * If you tried clicking on a program or link but it failed to load, even after waiting, try adjusting your cursor position so that the tip of the cursor visually falls on the element that you want to click. 156 | * Make sure to click any buttons, links, icons, etc with the cursor tip in the center of the element. Don't click boxes on their edges unless asked. 157 | Tool input schema 158 | We are providing our input schema for reference only. You should not specify this in your Anthropic-defined tool call. 159 | 160 | 161 | { 162 | "properties": { 163 | "action": { 164 | "description": """The action to perform. The available actions are: 165 | * `key`: Press a key or key-combination on the keyboard. 166 | - This supports xdotool's `key` syntax. 167 | - Examples: "a", "Return", "alt+Tab", "ctrl+s", "Up", "KP_0" (for the numpad 0 key). 168 | * `type`: Type a string of text on the keyboard. 169 | * `cursor_position`: Get the current (x, y) pixel coordinate of the cursor on the screen. 170 | * `mouse_move`: Move the cursor to a specified (x, y) pixel coordinate on the screen. 171 | * `left_click`: Click the left mouse button. 172 | * `left_click_drag`: Click and drag the cursor to a specified (x, y) pixel coordinate on the screen. 173 | * `right_click`: Click the right mouse button. 174 | * `middle_click`: Click the middle mouse button. 175 | * `double_click`: Double-click the left mouse button. 176 | * `screenshot`: Take a screenshot of the screen.""", 177 | "enum": [ 178 | "key", 179 | "type", 180 | "mouse_move", 181 | "left_click", 182 | "left_click_drag", 183 | "right_click", 184 | "middle_click", 185 | "double_click", 186 | "screenshot", 187 | "cursor_position", 188 | ], 189 | "type": "string", 190 | }, 191 | "coordinate": { 192 | "description": "(x, y): The x (pixels from the left edge) and y (pixels from the top edge) coordinates to move the mouse to. Required only by `action=mouse_move` and `action=left_click_drag`.", 193 | "type": "array", 194 | }, 195 | "text": { 196 | "description": "Required only by `action=type` and `action=key`.", 197 | "type": "string", 198 | }, 199 | }, 200 | "required": ["action"], 201 | "type": "object", 202 | } 203 | 204 | Text editor tool 205 | 206 | Type 207 | text_editor_20241022 208 | 209 | Tool description 210 | We are providing our tool description for reference only. You should not specify this in your Anthropic-defined tool call. 211 | 212 | 213 | Custom editing tool for viewing, creating and editing files 214 | * State is persistent across command calls and discussions with the user 215 | * If `path` is a file, `view` displays the result of applying `cat -n`. If `path` is a directory, `view` lists non-hidden files and directories up to 2 levels deep 216 | * The `create` command cannot be used if the specified `path` already exists as a file 217 | * If a `command` generates a long output, it will be truncated and marked with `` 218 | * The `undo_edit` command will revert the last edit made to the file at `path` 219 | 220 | Notes for using the `str_replace` command: 221 | * The `old_str` parameter should match EXACTLY one or more consecutive lines from the original file. Be mindful of whitespaces! 222 | * If the `old_str` parameter is not unique in the file, the replacement will not be performed. Make sure to include enough context in `old_str` to make it unique 223 | * The `new_str` parameter should contain the edited lines that should replace the `old_str` 224 | Tool input schema 225 | We are providing our input schema for reference only. You should not specify this in your Anthropic-defined tool call. 226 | 227 | 228 | { 229 | "properties": { 230 | "command": { 231 | "description": "The commands to run. Allowed options are: `view`, `create`, `str_replace`, `insert`, `undo_edit`.", 232 | "enum": ["view", "create", "str_replace", "insert", "undo_edit"], 233 | "type": "string", 234 | }, 235 | "file_text": { 236 | "description": "Required parameter of `create` command, with the content of the file to be created.", 237 | "type": "string", 238 | }, 239 | "insert_line": { 240 | "description": "Required parameter of `insert` command. The `new_str` will be inserted AFTER the line `insert_line` of `path`.", 241 | "type": "integer", 242 | }, 243 | "new_str": { 244 | "description": "Optional parameter of `str_replace` command containing the new string (if not given, no string will be added). Required parameter of `insert` command containing the string to insert.", 245 | "type": "string", 246 | }, 247 | "old_str": { 248 | "description": "Required parameter of `str_replace` command containing the string in `path` to replace.", 249 | "type": "string", 250 | }, 251 | "path": { 252 | "description": "Absolute path to file or directory, e.g. `/repo/file.py` or `/repo`.", 253 | "type": "string", 254 | }, 255 | "view_range": { 256 | "description": "Optional parameter of `view` command when `path` points to a file. If none is given, the full file is shown. If provided, the file will be shown in the indicated line number range, e.g. [11, 12] will show lines 11 and 12. Indexing at 1 to start. Setting `[start_line, -1]` shows all lines from `start_line` to the end of the file.", 257 | "items": {"type": "integer"}, 258 | "type": "array", 259 | }, 260 | }, 261 | "required": ["command", "path"], 262 | "type": "object", 263 | } 264 | 265 | Bash tool 266 | 267 | Type 268 | bash_20241022 269 | 270 | Tool description 271 | We are providing our tool description for reference only. You should not specify this in your Anthropic-defined tool call. 272 | 273 | 274 | Run commands in a bash shell 275 | * When invoking this tool, the contents of the "command" parameter does NOT need to be XML-escaped. 276 | * You have access to a mirror of common linux and python packages via apt and pip. 277 | * State is persistent across command calls and discussions with the user. 278 | * To inspect a particular line range of a file, e.g. lines 10-25, try 'sed -n 10,25p /path/to/the/file'. 279 | * Please avoid commands that may produce a very large amount of output. 280 | * Please run long lived commands in the background, e.g. 'sleep 10 &' or start a server in the background. 281 | Tool input schema 282 | We are providing our input schema for reference only. You should not specify this in your Anthropic-defined tool call. 283 | 284 | 285 | { 286 | "properties": { 287 | "command": { 288 | "description": "The bash command to run. Required unless the tool is being restarted.", 289 | "type": "string", 290 | }, 291 | "restart": { 292 | "description": "Specifying true will restart this tool. Otherwise, leave this unspecified.", 293 | "type": "boolean", 294 | }, 295 | } 296 | } 297 | ​ 298 | Combine computer use with other tools 299 | You can combine regular tool use with the Anthropic-defined tools for computer use. 300 | 301 | 302 | Shell 303 | 304 | Python 305 | 306 | TypeScript 307 | 308 | import anthropic 309 | 310 | client = anthropic.Anthropic() 311 | 312 | response = client.beta.messages.create( 313 | model="claude-3-5-sonnet-20241022", 314 | max_tokens=1024, 315 | tools=[ 316 | { 317 | "type": "computer_20241022", 318 | "name": "computer", 319 | "display_width_px": 1024, 320 | "display_height_px": 768, 321 | "display_number": 1, 322 | }, 323 | { 324 | "type": "text_editor_20241022", 325 | "name": "str_replace_editor" 326 | }, 327 | { 328 | "type": "bash_20241022", 329 | "name": "bash" 330 | }, 331 | { 332 | "name": "get_weather", 333 | "description": "Get the current weather in a given location", 334 | "input_schema": { 335 | "type": "object", 336 | "properties": { 337 | "location": { 338 | "type": "string", 339 | "description": "The city and state, e.g. San Francisco, CA" 340 | }, 341 | "unit": { 342 | "type": "string", 343 | "enum": ["celsius", "fahrenheit"], 344 | "description": "The unit of temperature, either 'celsius' or 'fahrenheit'" 345 | } 346 | }, 347 | "required": ["location"] 348 | } 349 | }, 350 | ], 351 | messages=[{"role": "user", "content": "Find flights from San Francisco to a place with warmer weather."}], 352 | betas=["computer-use-2024-10-22"], 353 | ) 354 | print(response) 355 | ​ 356 | Build a custom computer use environment 357 | The reference implementation is meant to help you get started with computer use. It includes all of the components needed have Claude use a computer. However, you can build your own environment for computer use to suit your needs. You’ll need: 358 | 359 | A virtualized or containerized environment suitable for computer use with Claude 360 | An implementation of at least one of the Anthropic-defined computer use tools 361 | An agent loop that interacts with the Anthropic API and executes the tool_use results using your tool implementations 362 | An API or UI that allows user input to start the agent loop 363 | ​ 364 | Understand computer use limitations 365 | The computer use functionality is in beta. While Claude’s capabilities are cutting edge, developers should be aware of its limitations: 366 | 367 | Latency: the current computer use latency for human-AI interactions may be too slow compared to regular human-directed computer actions. We recommend focusing on use cases where speed isn’t critical (e.g., background information gathering, automated software testing) in trusted environments. 368 | Computer vision accuracy and reliability: Claude may make mistakes or hallucinate when outputting specific coordinates while generating actions. 369 | Tool selection accuracy and reliability: Claude may make mistakes or hallucinate when selecting tools while generating actions or take unexpected actions to solve problems. Additionally, reliability may be lower when interacting with niche applications or multiple applications at once. We recommend that users prompt the model carefully when requesting complex tasks. 370 | Scrolling reliability: Scrolling may be unreliable in the current experience, and the model may not reliably scroll to the bottom of a page. Scrolling-like behavior can be improved via keystrokes (PgUp/PgDown). 371 | Spreadsheet interaction: Mouse clicks for spreadsheet interaction are unreliable. Cell selection may not always work as expected. This can be mitigated by prompting the model to use arrow keys. 372 | Account creation and content generation on social and communications platforms: While Claude will visit websites, we are limiting its ability to create accounts or generate and share content or otherwise engage in human impersonation across social media websites and platforms. We may update this capability in the future. 373 | Vulnerabilities: Vulnerabilities like jailbreaking or prompt injection may persist across frontier AI systems, including the beta computer use API. In some circumstances, Claude will follow commands found in content, sometimes even in conflict with the user’s instructions. For example, Claude instructions on webpages or contained in images may override instructions or cause Claude to make mistakes. We recommend: a. Limiting computer use to trusted environments such as virtual machines or containers with minimal privileges b. Avoiding giving computer use access to sensitive accounts or data without strict oversight c. Informing end users of relevant risks and obtaining their consent before enabling or requesting permissions necessary for computer use features in your applications 374 | Inappropriate or illegal actions: Per Anthropic’s terms of service, you must not employ computer use to violate any laws or our Acceptable Use Policy. 375 | Always carefully review and verify Claude’s computer use actions and logs. Do not use Claude for tasks requiring perfect precision or sensitive user information without human oversight. 376 | 377 | ​ 378 | Pricing 379 | See the tool use pricing documentation for a detailed explanation of how Claude Tool Use API requests are priced. 380 | 381 | As a subset of tool use requests, computer use requests are priced the same as any other Claude API request. 382 | 383 | We also automatically include a special system prompt for the model, which enables computer use. 384 | 385 | Model Tool choice System prompt token count 386 | Claude 3.5 Sonnet (new) auto 387 | any, tool 466 tokens 388 | 499 tokens 389 | In addition to the base tokens, the following additional input tokens are needed for the Anthropic-defined tools: 390 | 391 | Tool Additional input tokens 392 | computer_20241022 683 tokens 393 | text_editor_20241022 700 tokens 394 | bash_20241022 245 tokens -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Anthropic Computer Use POC -> Bash and Text Tools 2 | > Let Claude 3.5 Sonnet NEW operate your terminal and edit your files for you. 3 | > 4 | > Proof of concept. 5 | > Fork and start building your next Gen Developer Tools. 6 | > 7 | > 🚀 Watch the walk through [video here](https://youtu.be/FxwA0DLxyrw). 8 | 9 | anthropic-computer-use 10 | 11 | ## 📌 Interesting notes 12 | > **Big takeaway: The AI Agents are coming** and for you and I, the engineer, **they're already here**. 13 | 14 | - What does a prompt + code + related tools get you: AI Agents. 15 | - `It's clear this is the setup for AI Agents.` 16 | - Anthropic has created the first set of problem specific tools (tool belt) that are good (great) enough to be useful NEARLY out of the box. 17 | - We'll see if OpenAI and Google follow suit with this pattern. 18 | - Anthropics text and bash tools are low key, very powerful. Engineers will miss this at first. 19 | - The text editor tool supports five commands (tools): `view, create, str_replace, insert, and undo_edit`. 20 | - The bash just has one tool `command`. 21 | - Upon consuming your prompt, it will generate and execute a series of these tools based on your prompt that you're code will run. 22 | - The real innovation here to me is two fold. 23 | - First, sonnet THINKS, and runs a SERIES ((`prompt chaining strikes again`)) of these tools based on your prompt. 24 | - Second, the tools execute very well, are context aware and follow instructions very well. This is likely due to the upgraded claude 3.5 sonnet being fine-tuned on these tools. 25 | - This is a game changer and a pattern we'll likely see more of from model providers: `fine-tuning on sets of tools for specific tasks`. 26 | - Logging is super important here to monitor what is happening and how the tools are being used to accomplish your goal. 27 | 28 | 29 | 30 | ## ⚙️ Setup 31 | - `brew install uv` or [install another way](https://docs.astral.sh/uv/getting-started/installation/#pypi). 32 | - `uv sync` 33 | - `cp .env.sample .env` and set your ANTHROPIC_API_KEY 34 | - (text mode) `uv run main "hi please create a file called 'ping.txt' and write 'pong' in it."` 35 | - This will create a file at `./editor_dir/ping.txt` with the text `pong`. 36 | - (bash mode) `uv run main "show me the contents of the current directory" --mode bash` 37 | 38 | ## 🚀 Usage 39 | 40 | ### Editor Tool Usage 41 | - Create and edit files with natural language: 42 | - `uv run main "hi please create a file called 'ping.txt' and write 'pong' in it."` 43 | - `uv run main "update the config.json file to add a new logging section" --mode editor` 44 | - `uv run main "create a new Python script that implements a basic web server" --mode editor` 45 | 46 | ### Bash Tool Usage 47 | - Execute shell commands through Claude: 48 | - `uv run main "show me the contents of the current directory" --mode bash` 49 | - `uv run main "show me the contents of the current directory" --mode bash--no-agi` (safe mode) 50 | - `uv run main "list tables from the data/app.db file" --mode bash` 51 | - `uv run main "read the first 3 lines of README.md and write insert them into the data/app.db sqlite database logging table" --mode bash` 52 | 53 | ## 🌟 Very cool command sequence 54 | - `uv run main "write a detailed 3 use case document for llms to a 'llm_use_cases.md' markdown file. then break that file into three going into details about the use cases."` 55 | - This will create a file at `./repo/llm_use_cases.md` with the 3 use cases. 56 | - Then it will break that file into three going into details about the use cases. 57 | - `uv run main "update the llm_use_cases.md file to add a fourth use case for data analysis and insights."` 58 | - `uv run main "read the llm_use_cases.md file and update it to contain a mermaid diagram of the use cases."` 59 | - `uv run main "update llm_use_cases.md: simplify the mermaid chart and make it lr."` 60 | 61 | ## 🔄 Application Workflow 62 | 63 | ```mermaid 64 | flowchart TD 65 | A[Start: User Input] --> B{Mode Selected?} 66 | 67 | B -- Editor --> C[Initialize EditorSession] 68 | 69 | B -- Bash --> D[Initialize BashSession] 70 | 71 | C --> E[Set up Logging] 72 | 73 | D --> E[Set up Logging] 74 | 75 | E --> F[Send Prompt to Claude API] 76 | 77 | F --> G[Model Processes Prompt] 78 | 79 | G --> H{Tool Calls Generated?} 80 | 81 | H -- Yes --> I[Execute Tool Calls] 82 | 83 | I --> J[Return Results to Model] 84 | 85 | J --> G[Model Processes Prompt] 86 | 87 | H -- No --> K[Finalize Response] 88 | 89 | K --> L[Log Token Usage and Cost] 90 | 91 | L --> M[End] 92 | ``` 93 | 94 | - The user provides a prompt to the application along with an optional mode (`editor` or `bash`). 95 | - The application generates a session ID and initializes a `SessionLogger`. 96 | - Depending on the chosen mode, either an `EditorSession` or `BashSession` is instantiated. 97 | - The session sets up logging and assigns the `SessionLogger`. 98 | - The application sends the user's prompt to the Claude model via the Anthropics API, including the appropriate system prompts and tool configurations. 99 | - The model processes the prompt and may generate tool calls (e.g., text editing commands or bash commands). 100 | - The application handles any tool calls by executing them and providing results back to the model if needed. 101 | - The application may iterate through multiple interactions based on the model's responses. 102 | - Upon completion, the application logs the total token usage and calculates the associated cost. 103 | 104 | 105 | ## 📚 Resources 106 | 107 | - https://docs.anthropic.com/en/docs/build-with-claude/computer-use 108 | - https://github.com/anthropics/anthropic-quickstarts/blob/main/computer-use-demo/computer_use_demo/loop.py 109 | -------------------------------------------------------------------------------- /data/app.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disler/anthropic-computer-use-bash-and-files/ee19ca8931f33eb75a3b3fd1caa4c8f01689d56d/data/app.db -------------------------------------------------------------------------------- /images/computer-use-thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/disler/anthropic-computer-use-bash-and-files/ee19ca8931f33eb75a3b3fd1caa4c8f01689d56d/images/computer-use-thumb.png -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "anthropic-computer-use" 3 | version = "0.1.0" 4 | description = "Add your description here" 5 | readme = "README.md" 6 | requires-python = ">=3.12" 7 | dependencies = [ 8 | "anthropic>=0.37.1", 9 | "pyyaml>=6.0.2", 10 | ] 11 | 12 | [build-system] 13 | requires = ["hatchling"] 14 | build-backend = "hatchling.build" 15 | 16 | 17 | [project.scripts] 18 | main = "anthropic_computer_use.main:main" 19 | -------------------------------------------------------------------------------- /src/anthropic_computer_use/__init__.py: -------------------------------------------------------------------------------- 1 | def hello() -> str: 2 | return "Hello from anthropic-computer-use!" 3 | -------------------------------------------------------------------------------- /src/anthropic_computer_use/main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import anthropic 3 | import argparse 4 | import yaml 5 | import subprocess 6 | from datetime import datetime 7 | import uuid 8 | from typing import Dict, Any, List, Optional, Union 9 | import traceback 10 | import sys 11 | import logging 12 | from logging.handlers import RotatingFileHandler 13 | 14 | EDITOR_DIR = os.path.join(os.getcwd(), "editor_dir") 15 | SESSIONS_DIR = os.path.join(os.getcwd(), "sessions") 16 | os.makedirs(SESSIONS_DIR, exist_ok=True) 17 | 18 | 19 | # Fetch system prompts from environment variables or use defaults 20 | BASH_SYSTEM_PROMPT = os.environ.get( 21 | "BASH_SYSTEM_PROMPT", "You are a helpful assistant that can execute bash commands." 22 | ) 23 | EDITOR_SYSTEM_PROMPT = os.environ.get( 24 | "EDITOR_SYSTEM_PROMPT", 25 | "You are a helpful assistant that helps users edit text files.", 26 | ) 27 | 28 | 29 | class SessionLogger: 30 | def __init__(self, session_id: str, sessions_dir: str): 31 | self.session_id = session_id 32 | self.sessions_dir = sessions_dir 33 | self.logger = self._setup_logging() 34 | 35 | # Initialize token counters 36 | self.total_input_tokens = 0 37 | self.total_output_tokens = 0 38 | 39 | def _setup_logging(self) -> logging.Logger: 40 | """Configure logging for the session""" 41 | log_formatter = logging.Formatter( 42 | "%(asctime)s - %(name)s - %(levelname)s - %(prefix)s - %(message)s" 43 | ) 44 | log_file = os.path.join(self.sessions_dir, f"{self.session_id}.log") 45 | 46 | file_handler = RotatingFileHandler( 47 | log_file, maxBytes=1024 * 1024, backupCount=5 48 | ) 49 | file_handler.setFormatter(log_formatter) 50 | 51 | console_handler = logging.StreamHandler() 52 | console_handler.setFormatter(log_formatter) 53 | 54 | logger = logging.getLogger(self.session_id) 55 | logger.addHandler(file_handler) 56 | logger.addHandler(console_handler) 57 | logger.setLevel(logging.DEBUG) 58 | 59 | return logger 60 | 61 | def update_token_usage(self, input_tokens: int, output_tokens: int): 62 | """Update the total token usage.""" 63 | self.total_input_tokens += input_tokens 64 | self.total_output_tokens += output_tokens 65 | 66 | def log_total_cost(self): 67 | """Calculate and log the total cost based on token usage.""" 68 | cost_per_million_input_tokens = 3.0 # $3.00 per million input tokens 69 | cost_per_million_output_tokens = 15.0 # $15.00 per million output tokens 70 | 71 | total_input_cost = ( 72 | self.total_input_tokens / 1_000_000 73 | ) * cost_per_million_input_tokens 74 | total_output_cost = ( 75 | self.total_output_tokens / 1_000_000 76 | ) * cost_per_million_output_tokens 77 | total_cost = total_input_cost + total_output_cost 78 | 79 | prefix = "📊 session" 80 | self.logger.info( 81 | f"Total input tokens: {self.total_input_tokens}", extra={"prefix": prefix} 82 | ) 83 | self.logger.info( 84 | f"Total output tokens: {self.total_output_tokens}", extra={"prefix": prefix} 85 | ) 86 | self.logger.info( 87 | f"Total input cost: ${total_input_cost:.6f}", extra={"prefix": prefix} 88 | ) 89 | self.logger.info( 90 | f"Total output cost: ${total_output_cost:.6f}", extra={"prefix": prefix} 91 | ) 92 | self.logger.info(f"Total cost: ${total_cost:.6f}", extra={"prefix": prefix}) 93 | 94 | 95 | class EditorSession: 96 | def __init__(self, session_id: Optional[str] = None): 97 | """Initialize editor session with optional existing session ID""" 98 | self.session_id = session_id or self._create_session_id() 99 | self.sessions_dir = SESSIONS_DIR 100 | self.editor_dir = EDITOR_DIR 101 | self.client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) 102 | self.messages = [] 103 | 104 | # Create editor directory if needed 105 | os.makedirs(self.editor_dir, exist_ok=True) 106 | 107 | # Initialize logger placeholder 108 | self.logger = None 109 | 110 | # Set log prefix 111 | self.log_prefix = "📝 file_editor" 112 | 113 | def set_logger(self, session_logger: SessionLogger): 114 | """Set the logger for the session and store the SessionLogger instance.""" 115 | self.session_logger = session_logger 116 | self.logger = logging.LoggerAdapter( 117 | self.session_logger.logger, {"prefix": self.log_prefix} 118 | ) 119 | 120 | def _create_session_id(self) -> str: 121 | """Create a new session ID""" 122 | timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") 123 | return f"{timestamp}-{uuid.uuid4().hex[:6]}" 124 | 125 | def _get_editor_path(self, path: str) -> str: 126 | """Convert API path to local editor directory path""" 127 | # Strip any leading /repo/ from the path 128 | clean_path = path.replace("/repo/", "", 1) 129 | # Join with editor_dir 130 | full_path = os.path.join(self.editor_dir, clean_path) 131 | # Create the directory structure if it doesn't exist 132 | os.makedirs(os.path.dirname(full_path), exist_ok=True) 133 | return full_path 134 | 135 | def _handle_view(self, path: str, _: Dict[str, Any]) -> Dict[str, Any]: 136 | """Handle view command""" 137 | editor_path = self._get_editor_path(path) 138 | if os.path.exists(editor_path): 139 | with open(editor_path, "r") as f: 140 | return {"content": f.read()} 141 | return {"error": f"File {editor_path} does not exist"} 142 | 143 | def _handle_create(self, path: str, tool_call: Dict[str, Any]) -> Dict[str, Any]: 144 | """Handle create command""" 145 | os.makedirs(os.path.dirname(path), exist_ok=True) 146 | with open(path, "w") as f: 147 | f.write(tool_call["file_text"]) 148 | return {"content": f"File created at {path}"} 149 | 150 | def _handle_str_replace( 151 | self, path: str, tool_call: Dict[str, Any] 152 | ) -> Dict[str, Any]: 153 | """Handle str_replace command""" 154 | with open(path, "r") as f: 155 | content = f.read() 156 | if tool_call["old_str"] not in content: 157 | return {"error": "old_str not found in file"} 158 | new_content = content.replace( 159 | tool_call["old_str"], tool_call.get("new_str", "") 160 | ) 161 | with open(path, "w") as f: 162 | f.write(new_content) 163 | return {"content": "File updated successfully"} 164 | 165 | def _handle_insert(self, path: str, tool_call: Dict[str, Any]) -> Dict[str, Any]: 166 | """Handle insert command""" 167 | with open(path, "r") as f: 168 | lines = f.readlines() 169 | insert_line = tool_call["insert_line"] 170 | if insert_line > len(lines): 171 | return {"error": "insert_line beyond file length"} 172 | lines.insert(insert_line, tool_call["new_str"] + "\n") 173 | with open(path, "w") as f: 174 | f.writelines(lines) 175 | return {"content": "Content inserted successfully"} 176 | 177 | def log_to_session(self, data: Dict[str, Any], section: str) -> None: 178 | """Log data to session log file""" 179 | self.logger.info(f"{section}: {data}") 180 | 181 | def handle_text_editor_tool(self, tool_call: Dict[str, Any]) -> Dict[str, Any]: 182 | """Handle text editor tool calls""" 183 | try: 184 | command = tool_call["command"] 185 | if not all(key in tool_call for key in ["command", "path"]): 186 | return {"error": "Missing required fields"} 187 | 188 | # Get path and ensure directory exists 189 | path = self._get_editor_path(tool_call["path"]) 190 | 191 | handlers = { 192 | "view": self._handle_view, 193 | "create": self._handle_create, 194 | "str_replace": self._handle_str_replace, 195 | "insert": self._handle_insert, 196 | } 197 | 198 | handler = handlers.get(command) 199 | if not handler: 200 | return {"error": f"Unknown command {command}"} 201 | 202 | return handler(path, tool_call) 203 | 204 | except Exception as e: 205 | self.logger.error(f"Error in handle_text_editor_tool: {str(e)}") 206 | return {"error": str(e)} 207 | 208 | def process_tool_calls( 209 | self, tool_calls: List[anthropic.types.ContentBlock] 210 | ) -> List[Dict[str, Any]]: 211 | """Process tool calls and return results""" 212 | results = [] 213 | 214 | for tool_call in tool_calls: 215 | if tool_call.type == "tool_use" and tool_call.name == "str_replace_editor": 216 | 217 | # Log the keys and first 20 characters of the values of the tool_call 218 | for key, value in tool_call.input.items(): 219 | truncated_value = str(value)[:20] + ( 220 | "..." if len(str(value)) > 20 else "" 221 | ) 222 | self.logger.info( 223 | f"Tool call key: {key}, Value (truncated): {truncated_value}" 224 | ) 225 | 226 | result = self.handle_text_editor_tool(tool_call.input) 227 | # Convert result to match expected tool result format 228 | is_error = False 229 | 230 | if result.get("error"): 231 | is_error = True 232 | tool_result_content = [{"type": "text", "text": result["error"]}] 233 | else: 234 | tool_result_content = [ 235 | {"type": "text", "text": result.get("content", "")} 236 | ] 237 | 238 | results.append( 239 | { 240 | "tool_call_id": tool_call.id, 241 | "output": { 242 | "type": "tool_result", 243 | "content": tool_result_content, 244 | "tool_use_id": tool_call.id, 245 | "is_error": is_error, 246 | }, 247 | } 248 | ) 249 | 250 | return results 251 | 252 | def process_edit(self, edit_prompt: str) -> None: 253 | """Main method to process editing prompts""" 254 | try: 255 | # Initial message with proper content structure 256 | api_message = { 257 | "role": "user", 258 | "content": [{"type": "text", "text": edit_prompt}], 259 | } 260 | self.messages = [api_message] 261 | 262 | self.logger.info(f"User input: {api_message}") 263 | 264 | while True: 265 | response = self.client.beta.messages.create( 266 | model="claude-3-5-sonnet-20241022", 267 | max_tokens=4096, 268 | messages=self.messages, 269 | tools=[ 270 | {"type": "text_editor_20241022", "name": "str_replace_editor"} 271 | ], 272 | system=EDITOR_SYSTEM_PROMPT, 273 | betas=["computer-use-2024-10-22"], 274 | ) 275 | 276 | # Extract token usage from the response 277 | input_tokens = getattr(response.usage, "input_tokens", 0) 278 | output_tokens = getattr(response.usage, "output_tokens", 0) 279 | self.logger.info( 280 | f"API usage: input_tokens={input_tokens}, output_tokens={output_tokens}" 281 | ) 282 | 283 | # Update token counts in SessionLogger 284 | self.session_logger.update_token_usage(input_tokens, output_tokens) 285 | 286 | self.logger.info(f"API response: {response.model_dump()}") 287 | 288 | # Convert response content to message params 289 | response_content = [] 290 | for block in response.content: 291 | if block.type == "text": 292 | response_content.append({"type": "text", "text": block.text}) 293 | else: 294 | response_content.append(block.model_dump()) 295 | 296 | # Add assistant response to messages 297 | self.messages.append({"role": "assistant", "content": response_content}) 298 | 299 | if response.stop_reason != "tool_use": 300 | print(response.content[0].text) 301 | break 302 | 303 | tool_results = self.process_tool_calls(response.content) 304 | 305 | # Add tool results as user message 306 | if tool_results: 307 | self.messages.append( 308 | {"role": "user", "content": [tool_results[0]["output"]]} 309 | ) 310 | 311 | if tool_results[0]["output"]["is_error"]: 312 | self.logger.error( 313 | f"Error: {tool_results[0]['output']['content']}" 314 | ) 315 | break 316 | 317 | # After the execution loop, log the total cost 318 | self.session_logger.log_total_cost() 319 | 320 | except Exception as e: 321 | self.logger.error(f"Error in process_edit: {str(e)}") 322 | self.logger.error(traceback.format_exc()) 323 | raise 324 | 325 | 326 | class BashSession: 327 | def __init__(self, session_id: Optional[str] = None, no_agi: bool = False): 328 | """Initialize Bash session with optional existing session ID""" 329 | self.session_id = session_id or self._create_session_id() 330 | self.sessions_dir = SESSIONS_DIR 331 | self.client = anthropic.Anthropic(api_key=os.environ.get("ANTHROPIC_API_KEY")) 332 | self.messages = [] 333 | 334 | # Initialize a persistent environment dictionary for subprocesses 335 | self.environment = os.environ.copy() 336 | 337 | # Initialize logger placeholder 338 | self.logger = None 339 | 340 | # Set log prefix 341 | self.log_prefix = "🐚 bash" 342 | 343 | # Store the no_agi flag 344 | self.no_agi = no_agi 345 | 346 | def set_logger(self, session_logger: SessionLogger): 347 | """Set the logger for the session and store the SessionLogger instance.""" 348 | self.session_logger = session_logger 349 | self.logger = logging.LoggerAdapter( 350 | session_logger.logger, {"prefix": self.log_prefix} 351 | ) 352 | 353 | def _create_session_id(self) -> str: 354 | """Create a new session ID""" 355 | timestamp = datetime.now().strftime("%Y%m%d-%H:%M:%S-%f") 356 | # return f"{timestamp}-{uuid.uuid4().hex[:6]}" 357 | return f"{timestamp}" 358 | 359 | def _handle_bash_command(self, tool_call: Dict[str, Any]) -> Dict[str, Any]: 360 | """Handle bash command execution""" 361 | try: 362 | command = tool_call.get("command") 363 | restart = tool_call.get("restart", False) 364 | 365 | if restart: 366 | self.environment = os.environ.copy() # Reset the environment 367 | self.logger.info("Bash session restarted.") 368 | return {"content": "Bash session restarted."} 369 | 370 | if not command: 371 | self.logger.error("No command provided to execute.") 372 | return {"error": "No command provided to execute."} 373 | 374 | # Check if no_agi is enabled 375 | if self.no_agi: 376 | self.logger.info(f"Mock executing bash command: {command}") 377 | return {"content": "in mock mode, command did not run"} 378 | 379 | # Log the command being executed 380 | self.logger.info(f"Executing bash command: {command}") 381 | 382 | # Execute the command in a subprocess 383 | result = subprocess.run( 384 | command, 385 | shell=True, 386 | stdout=subprocess.PIPE, 387 | stderr=subprocess.PIPE, 388 | env=self.environment, 389 | text=True, 390 | executable="/bin/bash", 391 | ) 392 | 393 | output = result.stdout.strip() 394 | error_output = result.stderr.strip() 395 | 396 | # Log the outputs 397 | if output: 398 | self.logger.info( 399 | f"Command output:\n\n```output for '{command[:20]}...'\n{output}\n```" 400 | ) 401 | if error_output: 402 | self.logger.error( 403 | f"Command error output:\n\n```error for '{command}'\n{error_output}\n```" 404 | ) 405 | 406 | if result.returncode != 0: 407 | error_message = error_output or "Command execution failed." 408 | return {"error": error_message} 409 | 410 | return {"content": output} 411 | 412 | except Exception as e: 413 | self.logger.error(f"Error in _handle_bash_command: {str(e)}") 414 | self.logger.error(traceback.format_exc()) 415 | return {"error": str(e)} 416 | 417 | def process_tool_calls( 418 | self, tool_calls: List[anthropic.types.ContentBlock] 419 | ) -> List[Dict[str, Any]]: 420 | """Process tool calls and return results""" 421 | results = [] 422 | 423 | for tool_call in tool_calls: 424 | if tool_call.type == "tool_use" and tool_call.name == "bash": 425 | self.logger.info(f"Bash tool call input: {tool_call.input}") 426 | 427 | result = self._handle_bash_command(tool_call.input) 428 | 429 | # Convert result to match expected tool result format 430 | is_error = False 431 | 432 | if result.get("error"): 433 | is_error = True 434 | tool_result_content = [{"type": "text", "text": result["error"]}] 435 | else: 436 | tool_result_content = [ 437 | {"type": "text", "text": result.get("content", "")} 438 | ] 439 | 440 | results.append( 441 | { 442 | "tool_call_id": tool_call.id, 443 | "output": { 444 | "type": "tool_result", 445 | "content": tool_result_content, 446 | "tool_use_id": tool_call.id, 447 | "is_error": is_error, 448 | }, 449 | } 450 | ) 451 | 452 | return results 453 | 454 | def process_bash_command(self, bash_prompt: str) -> None: 455 | """Main method to process bash commands via the assistant""" 456 | try: 457 | # Initial message with proper content structure 458 | api_message = { 459 | "role": "user", 460 | "content": [{"type": "text", "text": bash_prompt}], 461 | } 462 | self.messages = [api_message] 463 | 464 | self.logger.info(f"User input: {api_message}") 465 | 466 | while True: 467 | response = self.client.beta.messages.create( 468 | model="claude-3-5-sonnet-20241022", 469 | max_tokens=4096, 470 | messages=self.messages, 471 | tools=[{"type": "bash_20241022", "name": "bash"}], 472 | system=BASH_SYSTEM_PROMPT, 473 | betas=["computer-use-2024-10-22"], 474 | ) 475 | 476 | # Extract token usage from the response 477 | input_tokens = getattr(response.usage, "input_tokens", 0) 478 | output_tokens = getattr(response.usage, "output_tokens", 0) 479 | self.logger.info( 480 | f"API usage: input_tokens={input_tokens}, output_tokens={output_tokens}" 481 | ) 482 | 483 | # Update token counts in SessionLogger 484 | self.session_logger.update_token_usage(input_tokens, output_tokens) 485 | 486 | self.logger.info(f"API response: {response.model_dump()}") 487 | 488 | # Convert response content to message params 489 | response_content = [] 490 | for block in response.content: 491 | if block.type == "text": 492 | response_content.append({"type": "text", "text": block.text}) 493 | else: 494 | response_content.append(block.model_dump()) 495 | 496 | # Add assistant response to messages 497 | self.messages.append({"role": "assistant", "content": response_content}) 498 | 499 | if response.stop_reason != "tool_use": 500 | # Print the assistant's final response 501 | print(response.content[0].text) 502 | break 503 | 504 | tool_results = self.process_tool_calls(response.content) 505 | 506 | # Add tool results as user message 507 | if tool_results: 508 | self.messages.append( 509 | {"role": "user", "content": [tool_results[0]["output"]]} 510 | ) 511 | 512 | if tool_results[0]["output"]["is_error"]: 513 | self.logger.error( 514 | f"Error: {tool_results[0]['output']['content']}" 515 | ) 516 | break 517 | 518 | # After the execution loop, log the total cost 519 | self.session_logger.log_total_cost() 520 | 521 | except Exception as e: 522 | self.logger.error(f"Error in process_bash_command: {str(e)}") 523 | self.logger.error(traceback.format_exc()) 524 | raise 525 | 526 | 527 | def main(): 528 | """Main entry point""" 529 | parser = argparse.ArgumentParser() 530 | parser.add_argument("prompt", help="The prompt for Claude", nargs="?") 531 | parser.add_argument( 532 | "--mode", choices=["editor", "bash"], default="editor", help="Mode to run" 533 | ) 534 | parser.add_argument( 535 | "--no-agi", 536 | action="store_true", 537 | help="When set, commands will not be executed, but will return 'command ran'.", 538 | ) 539 | args = parser.parse_args() 540 | 541 | # Create a shared session ID 542 | session_id = datetime.now().strftime("%Y%m%d-%H%M%S") + "-" + uuid.uuid4().hex[:6] 543 | # Create a single SessionLogger instance 544 | session_logger = SessionLogger(session_id, SESSIONS_DIR) 545 | 546 | if args.mode == "editor": 547 | session = EditorSession(session_id=session_id) 548 | # Pass the logger via setter method 549 | session.set_logger(session_logger) 550 | print(f"Session ID: {session.session_id}") 551 | session.process_edit(args.prompt) 552 | elif args.mode == "bash": 553 | session = BashSession(session_id=session_id, no_agi=args.no_agi) 554 | # Pass the logger via setter method 555 | session.set_logger(session_logger) 556 | print(f"Session ID: {session.session_id}") 557 | session.process_bash_command(args.prompt) 558 | 559 | 560 | if __name__ == "__main__": 561 | main() 562 | -------------------------------------------------------------------------------- /uv.lock: -------------------------------------------------------------------------------- 1 | version = 1 2 | requires-python = ">=3.12" 3 | resolution-markers = [ 4 | "python_full_version < '3.13'", 5 | "python_full_version >= '3.13'", 6 | ] 7 | 8 | [[package]] 9 | name = "annotated-types" 10 | version = "0.7.0" 11 | source = { registry = "https://pypi.org/simple" } 12 | sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081 } 13 | wheels = [ 14 | { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643 }, 15 | ] 16 | 17 | [[package]] 18 | name = "anthropic" 19 | version = "0.37.1" 20 | source = { registry = "https://pypi.org/simple" } 21 | dependencies = [ 22 | { name = "anyio" }, 23 | { name = "distro" }, 24 | { name = "httpx" }, 25 | { name = "jiter" }, 26 | { name = "pydantic" }, 27 | { name = "sniffio" }, 28 | { name = "tokenizers" }, 29 | { name = "typing-extensions" }, 30 | ] 31 | sdist = { url = "https://files.pythonhosted.org/packages/bb/7c/4b4cc70a82b18ecbd69b13c4707281850bd9575b6c1fc74b06df231b17ca/anthropic-0.37.1.tar.gz", hash = "sha256:99f688265795daa7ba9256ee68eaf2f05d53cd99d7417f4a0c2dc292c106d00a", size = 931431 } 32 | wheels = [ 33 | { url = "https://files.pythonhosted.org/packages/4e/40/bbb252b77f7a0345aa8c759bab8280d97eab5a9acf4df49fa2251f4a3a58/anthropic-0.37.1-py3-none-any.whl", hash = "sha256:8f550f88906823752e2abf99fbe491fbc8d40bce4cb26b9663abdf7be990d721", size = 945950 }, 34 | ] 35 | 36 | [[package]] 37 | name = "anthropic-computer-use" 38 | version = "0.1.0" 39 | source = { editable = "." } 40 | dependencies = [ 41 | { name = "anthropic" }, 42 | { name = "pyyaml" }, 43 | ] 44 | 45 | [package.metadata] 46 | requires-dist = [ 47 | { name = "anthropic", specifier = ">=0.37.1" }, 48 | { name = "pyyaml", specifier = ">=6.0.2" }, 49 | ] 50 | 51 | [[package]] 52 | name = "anyio" 53 | version = "4.6.2.post1" 54 | source = { registry = "https://pypi.org/simple" } 55 | dependencies = [ 56 | { name = "idna" }, 57 | { name = "sniffio" }, 58 | ] 59 | sdist = { url = "https://files.pythonhosted.org/packages/9f/09/45b9b7a6d4e45c6bcb5bf61d19e3ab87df68e0601fa8c5293de3542546cc/anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c", size = 173422 } 60 | wheels = [ 61 | { url = "https://files.pythonhosted.org/packages/e4/f5/f2b75d2fc6f1a260f340f0e7c6a060f4dd2961cc16884ed851b0d18da06a/anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d", size = 90377 }, 62 | ] 63 | 64 | [[package]] 65 | name = "certifi" 66 | version = "2024.8.30" 67 | source = { registry = "https://pypi.org/simple" } 68 | sdist = { url = "https://files.pythonhosted.org/packages/b0/ee/9b19140fe824b367c04c5e1b369942dd754c4c5462d5674002f75c4dedc1/certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9", size = 168507 } 69 | wheels = [ 70 | { url = "https://files.pythonhosted.org/packages/12/90/3c9ff0512038035f59d279fddeb79f5f1eccd8859f06d6163c58798b9487/certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8", size = 167321 }, 71 | ] 72 | 73 | [[package]] 74 | name = "charset-normalizer" 75 | version = "3.4.0" 76 | source = { registry = "https://pypi.org/simple" } 77 | sdist = { url = "https://files.pythonhosted.org/packages/f2/4f/e1808dc01273379acc506d18f1504eb2d299bd4131743b9fc54d7be4df1e/charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e", size = 106620 } 78 | wheels = [ 79 | { url = "https://files.pythonhosted.org/packages/d3/0b/4b7a70987abf9b8196845806198975b6aab4ce016632f817ad758a5aa056/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6", size = 194445 }, 80 | { url = "https://files.pythonhosted.org/packages/50/89/354cc56cf4dd2449715bc9a0f54f3aef3dc700d2d62d1fa5bbea53b13426/charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf", size = 125275 }, 81 | { url = "https://files.pythonhosted.org/packages/fa/44/b730e2a2580110ced837ac083d8ad222343c96bb6b66e9e4e706e4d0b6df/charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db", size = 119020 }, 82 | { url = "https://files.pythonhosted.org/packages/9d/e4/9263b8240ed9472a2ae7ddc3e516e71ef46617fe40eaa51221ccd4ad9a27/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1", size = 139128 }, 83 | { url = "https://files.pythonhosted.org/packages/6b/e3/9f73e779315a54334240353eaea75854a9a690f3f580e4bd85d977cb2204/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03", size = 149277 }, 84 | { url = "https://files.pythonhosted.org/packages/1a/cf/f1f50c2f295312edb8a548d3fa56a5c923b146cd3f24114d5adb7e7be558/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284", size = 142174 }, 85 | { url = "https://files.pythonhosted.org/packages/16/92/92a76dc2ff3a12e69ba94e7e05168d37d0345fa08c87e1fe24d0c2a42223/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15", size = 143838 }, 86 | { url = "https://files.pythonhosted.org/packages/a4/01/2117ff2b1dfc61695daf2babe4a874bca328489afa85952440b59819e9d7/charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8", size = 146149 }, 87 | { url = "https://files.pythonhosted.org/packages/f6/9b/93a332b8d25b347f6839ca0a61b7f0287b0930216994e8bf67a75d050255/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2", size = 140043 }, 88 | { url = "https://files.pythonhosted.org/packages/ab/f6/7ac4a01adcdecbc7a7587767c776d53d369b8b971382b91211489535acf0/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719", size = 148229 }, 89 | { url = "https://files.pythonhosted.org/packages/9d/be/5708ad18161dee7dc6a0f7e6cf3a88ea6279c3e8484844c0590e50e803ef/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631", size = 151556 }, 90 | { url = "https://files.pythonhosted.org/packages/5a/bb/3d8bc22bacb9eb89785e83e6723f9888265f3a0de3b9ce724d66bd49884e/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b", size = 149772 }, 91 | { url = "https://files.pythonhosted.org/packages/f7/fa/d3fc622de05a86f30beea5fc4e9ac46aead4731e73fd9055496732bcc0a4/charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565", size = 144800 }, 92 | { url = "https://files.pythonhosted.org/packages/9a/65/bdb9bc496d7d190d725e96816e20e2ae3a6fa42a5cac99c3c3d6ff884118/charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7", size = 94836 }, 93 | { url = "https://files.pythonhosted.org/packages/3e/67/7b72b69d25b89c0b3cea583ee372c43aa24df15f0e0f8d3982c57804984b/charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9", size = 102187 }, 94 | { url = "https://files.pythonhosted.org/packages/f3/89/68a4c86f1a0002810a27f12e9a7b22feb198c59b2f05231349fbce5c06f4/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114", size = 194617 }, 95 | { url = "https://files.pythonhosted.org/packages/4f/cd/8947fe425e2ab0aa57aceb7807af13a0e4162cd21eee42ef5b053447edf5/charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed", size = 125310 }, 96 | { url = "https://files.pythonhosted.org/packages/5b/f0/b5263e8668a4ee9becc2b451ed909e9c27058337fda5b8c49588183c267a/charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250", size = 119126 }, 97 | { url = "https://files.pythonhosted.org/packages/ff/6e/e445afe4f7fda27a533f3234b627b3e515a1b9429bc981c9a5e2aa5d97b6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920", size = 139342 }, 98 | { url = "https://files.pythonhosted.org/packages/a1/b2/4af9993b532d93270538ad4926c8e37dc29f2111c36f9c629840c57cd9b3/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64", size = 149383 }, 99 | { url = "https://files.pythonhosted.org/packages/fb/6f/4e78c3b97686b871db9be6f31d64e9264e889f8c9d7ab33c771f847f79b7/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23", size = 142214 }, 100 | { url = "https://files.pythonhosted.org/packages/2b/c9/1c8fe3ce05d30c87eff498592c89015b19fade13df42850aafae09e94f35/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc", size = 144104 }, 101 | { url = "https://files.pythonhosted.org/packages/ee/68/efad5dcb306bf37db7db338338e7bb8ebd8cf38ee5bbd5ceaaaa46f257e6/charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d", size = 146255 }, 102 | { url = "https://files.pythonhosted.org/packages/0c/75/1ed813c3ffd200b1f3e71121c95da3f79e6d2a96120163443b3ad1057505/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88", size = 140251 }, 103 | { url = "https://files.pythonhosted.org/packages/7d/0d/6f32255c1979653b448d3c709583557a4d24ff97ac4f3a5be156b2e6a210/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90", size = 148474 }, 104 | { url = "https://files.pythonhosted.org/packages/ac/a0/c1b5298de4670d997101fef95b97ac440e8c8d8b4efa5a4d1ef44af82f0d/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b", size = 151849 }, 105 | { url = "https://files.pythonhosted.org/packages/04/4f/b3961ba0c664989ba63e30595a3ed0875d6790ff26671e2aae2fdc28a399/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d", size = 149781 }, 106 | { url = "https://files.pythonhosted.org/packages/d8/90/6af4cd042066a4adad58ae25648a12c09c879efa4849c705719ba1b23d8c/charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482", size = 144970 }, 107 | { url = "https://files.pythonhosted.org/packages/cc/67/e5e7e0cbfefc4ca79025238b43cdf8a2037854195b37d6417f3d0895c4c2/charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67", size = 94973 }, 108 | { url = "https://files.pythonhosted.org/packages/65/97/fc9bbc54ee13d33dc54a7fcf17b26368b18505500fc01e228c27b5222d80/charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b", size = 102308 }, 109 | { url = "https://files.pythonhosted.org/packages/bf/9b/08c0432272d77b04803958a4598a51e2a4b51c06640af8b8f0f908c18bf2/charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079", size = 49446 }, 110 | ] 111 | 112 | [[package]] 113 | name = "colorama" 114 | version = "0.4.6" 115 | source = { registry = "https://pypi.org/simple" } 116 | sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } 117 | wheels = [ 118 | { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, 119 | ] 120 | 121 | [[package]] 122 | name = "distro" 123 | version = "1.9.0" 124 | source = { registry = "https://pypi.org/simple" } 125 | sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722 } 126 | wheels = [ 127 | { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277 }, 128 | ] 129 | 130 | [[package]] 131 | name = "filelock" 132 | version = "3.16.1" 133 | source = { registry = "https://pypi.org/simple" } 134 | sdist = { url = "https://files.pythonhosted.org/packages/9d/db/3ef5bb276dae18d6ec2124224403d1d67bccdbefc17af4cc8f553e341ab1/filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435", size = 18037 } 135 | wheels = [ 136 | { url = "https://files.pythonhosted.org/packages/b9/f8/feced7779d755758a52d1f6635d990b8d98dc0a29fa568bbe0625f18fdf3/filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0", size = 16163 }, 137 | ] 138 | 139 | [[package]] 140 | name = "fsspec" 141 | version = "2024.10.0" 142 | source = { registry = "https://pypi.org/simple" } 143 | sdist = { url = "https://files.pythonhosted.org/packages/a0/52/f16a068ebadae42526484c31f4398e62962504e5724a8ba5dc3409483df2/fsspec-2024.10.0.tar.gz", hash = "sha256:eda2d8a4116d4f2429db8550f2457da57279247dd930bb12f821b58391359493", size = 286853 } 144 | wheels = [ 145 | { url = "https://files.pythonhosted.org/packages/c6/b2/454d6e7f0158951d8a78c2e1eb4f69ae81beb8dca5fee9809c6c99e9d0d0/fsspec-2024.10.0-py3-none-any.whl", hash = "sha256:03b9a6785766a4de40368b88906366755e2819e758b83705c88cd7cb5fe81871", size = 179641 }, 146 | ] 147 | 148 | [[package]] 149 | name = "h11" 150 | version = "0.14.0" 151 | source = { registry = "https://pypi.org/simple" } 152 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 } 153 | wheels = [ 154 | { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, 155 | ] 156 | 157 | [[package]] 158 | name = "httpcore" 159 | version = "1.0.6" 160 | source = { registry = "https://pypi.org/simple" } 161 | dependencies = [ 162 | { name = "certifi" }, 163 | { name = "h11" }, 164 | ] 165 | sdist = { url = "https://files.pythonhosted.org/packages/b6/44/ed0fa6a17845fb033bd885c03e842f08c1b9406c86a2e60ac1ae1b9206a6/httpcore-1.0.6.tar.gz", hash = "sha256:73f6dbd6eb8c21bbf7ef8efad555481853f5f6acdeaff1edb0694289269ee17f", size = 85180 } 166 | wheels = [ 167 | { url = "https://files.pythonhosted.org/packages/06/89/b161908e2f51be56568184aeb4a880fd287178d176fd1c860d2217f41106/httpcore-1.0.6-py3-none-any.whl", hash = "sha256:27b59625743b85577a8c0e10e55b50b5368a4f2cfe8cc7bcfa9cf00829c2682f", size = 78011 }, 168 | ] 169 | 170 | [[package]] 171 | name = "httpx" 172 | version = "0.27.2" 173 | source = { registry = "https://pypi.org/simple" } 174 | dependencies = [ 175 | { name = "anyio" }, 176 | { name = "certifi" }, 177 | { name = "httpcore" }, 178 | { name = "idna" }, 179 | { name = "sniffio" }, 180 | ] 181 | sdist = { url = "https://files.pythonhosted.org/packages/78/82/08f8c936781f67d9e6b9eeb8a0c8b4e406136ea4c3d1f89a5db71d42e0e6/httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2", size = 144189 } 182 | wheels = [ 183 | { url = "https://files.pythonhosted.org/packages/56/95/9377bcb415797e44274b51d46e3249eba641711cf3348050f76ee7b15ffc/httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0", size = 76395 }, 184 | ] 185 | 186 | [[package]] 187 | name = "huggingface-hub" 188 | version = "0.26.1" 189 | source = { registry = "https://pypi.org/simple" } 190 | dependencies = [ 191 | { name = "filelock" }, 192 | { name = "fsspec" }, 193 | { name = "packaging" }, 194 | { name = "pyyaml" }, 195 | { name = "requests" }, 196 | { name = "tqdm" }, 197 | { name = "typing-extensions" }, 198 | ] 199 | sdist = { url = "https://files.pythonhosted.org/packages/44/99/c8fdef6fe09a1719e5e5de24b012de5824889168c96143f5531cab5af42b/huggingface_hub-0.26.1.tar.gz", hash = "sha256:414c0d9b769eecc86c70f9d939d0f48bb28e8461dd1130021542eff0212db890", size = 375458 } 200 | wheels = [ 201 | { url = "https://files.pythonhosted.org/packages/d7/4d/017d8d7cff5100092da8ea19139bcb1965bbadcbb5ddd0480e2badc299e8/huggingface_hub-0.26.1-py3-none-any.whl", hash = "sha256:5927a8fc64ae68859cd954b7cc29d1c8390a5e15caba6d3d349c973be8fdacf3", size = 447439 }, 202 | ] 203 | 204 | [[package]] 205 | name = "idna" 206 | version = "3.10" 207 | source = { registry = "https://pypi.org/simple" } 208 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 } 209 | wheels = [ 210 | { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 }, 211 | ] 212 | 213 | [[package]] 214 | name = "jiter" 215 | version = "0.6.1" 216 | source = { registry = "https://pypi.org/simple" } 217 | sdist = { url = "https://files.pythonhosted.org/packages/26/ef/64458dfad180debd70d9dd1ca4f607e52bb6de748e5284d748556a0d5173/jiter-0.6.1.tar.gz", hash = "sha256:e19cd21221fc139fb032e4112986656cb2739e9fe6d84c13956ab30ccc7d4449", size = 161306 } 218 | wheels = [ 219 | { url = "https://files.pythonhosted.org/packages/2e/d5/fcdfbcea637f8b9b833597797d6b77fd7e22649b4794fc571674477c8520/jiter-0.6.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:1fad93654d5a7dcce0809aff66e883c98e2618b86656aeb2129db2cd6f26f867", size = 289279 }, 220 | { url = "https://files.pythonhosted.org/packages/9a/47/8e4a7704a267b8d1d3287b4353fc07f1f4a3541b27988ea3e49ccbf3164a/jiter-0.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4e6e340e8cd92edab7f6a3a904dbbc8137e7f4b347c49a27da9814015cc0420c", size = 300931 }, 221 | { url = "https://files.pythonhosted.org/packages/ea/4f/fbb1e11fcc3881d108359d3db8456715c9d30ddfce84dc5f9e0856e08e11/jiter-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:691352e5653af84ed71763c3c427cff05e4d658c508172e01e9c956dfe004aba", size = 336534 }, 222 | { url = "https://files.pythonhosted.org/packages/29/8a/4c1e1229f89127187df166de760438b2a20e5a311391ba10d2b69db0da6f/jiter-0.6.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:defee3949313c1f5b55e18be45089970cdb936eb2a0063f5020c4185db1b63c9", size = 354266 }, 223 | { url = "https://files.pythonhosted.org/packages/19/15/3f27f4b9d40bc7709a30fda99876cbe9e9f75a0ea2ef7d55f3dd4d04f927/jiter-0.6.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:26d2bdd5da097e624081c6b5d416d3ee73e5b13f1703bcdadbb1881f0caa1933", size = 370492 }, 224 | { url = "https://files.pythonhosted.org/packages/1f/9d/9ec03c07325bc3a3c5b5082840b8ecb7e7ad38f3071c149b7c6fb9e78706/jiter-0.6.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18aa9d1626b61c0734b973ed7088f8a3d690d0b7f5384a5270cd04f4d9f26c86", size = 390330 }, 225 | { url = "https://files.pythonhosted.org/packages/bd/3b/612ea6daa52d64bc0cc46f2bd2e138952c58f1edbe86b17fd89e07c33d86/jiter-0.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a3567c8228afa5ddcce950631c6b17397ed178003dc9ee7e567c4c4dcae9fa0", size = 324245 }, 226 | { url = "https://files.pythonhosted.org/packages/21/0f/f3a1ffd9f203d4014b4e5045c0ea2c67ee71a7eee8bf3408dbf11007cf07/jiter-0.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5c0507131c922defe3f04c527d6838932fcdfd69facebafd7d3574fa3395314", size = 368232 }, 227 | { url = "https://files.pythonhosted.org/packages/62/12/5d75729e0a57804852de0affc6f03b3df8518259e47ed4cd89aeeb671a71/jiter-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:540fcb224d7dc1bcf82f90f2ffb652df96f2851c031adca3c8741cb91877143b", size = 513820 }, 228 | { url = "https://files.pythonhosted.org/packages/5f/e8/e47734280e19cd465832e610e1c69367ee72947de738785c4b6fc4031e25/jiter-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e7b75436d4fa2032b2530ad989e4cb0ca74c655975e3ff49f91a1a3d7f4e1df2", size = 496023 }, 229 | { url = "https://files.pythonhosted.org/packages/52/01/5f65dd1387d39aa3fd4a98a5be1d8470e929a0cb0dd6cbfebaccd9a20ac5/jiter-0.6.1-cp312-none-win32.whl", hash = "sha256:883d2ced7c21bf06874fdeecab15014c1c6d82216765ca6deef08e335fa719e0", size = 197425 }, 230 | { url = "https://files.pythonhosted.org/packages/43/b2/bd6665030f7d7cd5d9182c62a869c3d5ceadd7bff9f1b305de9192e7dbf8/jiter-0.6.1-cp312-none-win_amd64.whl", hash = "sha256:91e63273563401aadc6c52cca64a7921c50b29372441adc104127b910e98a5b6", size = 198966 }, 231 | { url = "https://files.pythonhosted.org/packages/23/38/7b48e0149778ff4b893567c9fd997ecfcc013e290375aa7823e1f681b3d3/jiter-0.6.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:852508a54fe3228432e56019da8b69208ea622a3069458252f725d634e955b31", size = 288674 }, 232 | { url = "https://files.pythonhosted.org/packages/85/3b/96d15b483d82a637279da53a1d299dd5da6e029b9905bcd1a4e1f89b8e4f/jiter-0.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f491cc69ff44e5a1e8bc6bf2b94c1f98d179e1aaf4a554493c171a5b2316b701", size = 301531 }, 233 | { url = "https://files.pythonhosted.org/packages/cf/54/9681f112cbec4e197259e9db679bd4bc314f4bd24f74b9aa5e93073990b5/jiter-0.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc56c8f0b2a28ad4d8047f3ae62d25d0e9ae01b99940ec0283263a04724de1f3", size = 335954 }, 234 | { url = "https://files.pythonhosted.org/packages/4a/4d/f9c0ba82b154c66278e28348086086264ccf50622ae468ec215e4bbc2873/jiter-0.6.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:51b58f7a0d9e084a43b28b23da2b09fc5e8df6aa2b6a27de43f991293cab85fd", size = 353996 }, 235 | { url = "https://files.pythonhosted.org/packages/ee/be/7f26b258ef190f6d582e21c76c7dd1097753a2203bad3e1643f45392720a/jiter-0.6.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f79ce15099154c90ef900d69c6b4c686b64dfe23b0114e0971f2fecd306ec6c", size = 369733 }, 236 | { url = "https://files.pythonhosted.org/packages/5f/85/037ed5261fa622312471ef5520b2135c26b29256c83adc16c8cc55dc4108/jiter-0.6.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03a025b52009f47e53ea619175d17e4ded7c035c6fbd44935cb3ada11e1fd592", size = 389920 }, 237 | { url = "https://files.pythonhosted.org/packages/a8/f3/2e01294712faa476be9e6ceb49e424c3919e03415ded76d103378a06bb80/jiter-0.6.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74a8d93718137c021d9295248a87c2f9fdc0dcafead12d2930bc459ad40f885", size = 324138 }, 238 | { url = "https://files.pythonhosted.org/packages/00/45/50377814f21b6412c7785be27f2dace225af52e0af20be7af899a7e3f264/jiter-0.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40b03b75f903975f68199fc4ec73d546150919cb7e534f3b51e727c4d6ccca5a", size = 367610 }, 239 | { url = "https://files.pythonhosted.org/packages/af/fc/51ba30875125381bfe21a1572c176de1a7dd64a386a7498355fc100decc4/jiter-0.6.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:825651a3f04cf92a661d22cad61fc913400e33aa89b3e3ad9a6aa9dc8a1f5a71", size = 512945 }, 240 | { url = "https://files.pythonhosted.org/packages/69/60/af26168bd4916f9199ed433161e9f8a4eeda581a4e5982560d0f22dd146c/jiter-0.6.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:928bf25eb69ddb292ab8177fe69d3fbf76c7feab5fce1c09265a7dccf25d3991", size = 494963 }, 241 | { url = "https://files.pythonhosted.org/packages/f3/2f/4f3cc5c9067a6fd1020d3c4365546535a69ed77da7fba2bec24368f3662c/jiter-0.6.1-cp313-none-win32.whl", hash = "sha256:352cd24121e80d3d053fab1cc9806258cad27c53cad99b7a3cac57cf934b12e4", size = 196869 }, 242 | { url = "https://files.pythonhosted.org/packages/7a/fc/8709ee90837e94790d8b50db51c7b8a70e86e41b2c81e824c20b0ecfeba7/jiter-0.6.1-cp313-none-win_amd64.whl", hash = "sha256:be7503dd6f4bf02c2a9bacb5cc9335bc59132e7eee9d3e931b13d76fd80d7fda", size = 198919 }, 243 | ] 244 | 245 | [[package]] 246 | name = "packaging" 247 | version = "24.1" 248 | source = { registry = "https://pypi.org/simple" } 249 | sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } 250 | wheels = [ 251 | { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, 252 | ] 253 | 254 | [[package]] 255 | name = "pydantic" 256 | version = "2.9.2" 257 | source = { registry = "https://pypi.org/simple" } 258 | dependencies = [ 259 | { name = "annotated-types" }, 260 | { name = "pydantic-core" }, 261 | { name = "typing-extensions" }, 262 | ] 263 | sdist = { url = "https://files.pythonhosted.org/packages/a9/b7/d9e3f12af310e1120c21603644a1cd86f59060e040ec5c3a80b8f05fae30/pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f", size = 769917 } 264 | wheels = [ 265 | { url = "https://files.pythonhosted.org/packages/df/e4/ba44652d562cbf0bf320e0f3810206149c8a4e99cdbf66da82e97ab53a15/pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12", size = 434928 }, 266 | ] 267 | 268 | [[package]] 269 | name = "pydantic-core" 270 | version = "2.23.4" 271 | source = { registry = "https://pypi.org/simple" } 272 | dependencies = [ 273 | { name = "typing-extensions" }, 274 | ] 275 | sdist = { url = "https://files.pythonhosted.org/packages/e2/aa/6b6a9b9f8537b872f552ddd46dd3da230367754b6f707b8e1e963f515ea3/pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863", size = 402156 } 276 | wheels = [ 277 | { url = "https://files.pythonhosted.org/packages/74/7b/8e315f80666194b354966ec84b7d567da77ad927ed6323db4006cf915f3f/pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231", size = 1856459 }, 278 | { url = "https://files.pythonhosted.org/packages/14/de/866bdce10ed808323d437612aca1ec9971b981e1c52e5e42ad9b8e17a6f6/pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee", size = 1770007 }, 279 | { url = "https://files.pythonhosted.org/packages/dc/69/8edd5c3cd48bb833a3f7ef9b81d7666ccddd3c9a635225214e044b6e8281/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87", size = 1790245 }, 280 | { url = "https://files.pythonhosted.org/packages/80/33/9c24334e3af796ce80d2274940aae38dd4e5676298b4398eff103a79e02d/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8", size = 1801260 }, 281 | { url = "https://files.pythonhosted.org/packages/a5/6f/e9567fd90104b79b101ca9d120219644d3314962caa7948dd8b965e9f83e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327", size = 1996872 }, 282 | { url = "https://files.pythonhosted.org/packages/2d/ad/b5f0fe9e6cfee915dd144edbd10b6e9c9c9c9d7a56b69256d124b8ac682e/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2", size = 2661617 }, 283 | { url = "https://files.pythonhosted.org/packages/06/c8/7d4b708f8d05a5cbfda3243aad468052c6e99de7d0937c9146c24d9f12e9/pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36", size = 2071831 }, 284 | { url = "https://files.pythonhosted.org/packages/89/4d/3079d00c47f22c9a9a8220db088b309ad6e600a73d7a69473e3a8e5e3ea3/pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126", size = 1917453 }, 285 | { url = "https://files.pythonhosted.org/packages/e9/88/9df5b7ce880a4703fcc2d76c8c2d8eb9f861f79d0c56f4b8f5f2607ccec8/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e", size = 1968793 }, 286 | { url = "https://files.pythonhosted.org/packages/e3/b9/41f7efe80f6ce2ed3ee3c2dcfe10ab7adc1172f778cc9659509a79518c43/pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24", size = 2116872 }, 287 | { url = "https://files.pythonhosted.org/packages/63/08/b59b7a92e03dd25554b0436554bf23e7c29abae7cce4b1c459cd92746811/pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84", size = 1738535 }, 288 | { url = "https://files.pythonhosted.org/packages/88/8d/479293e4d39ab409747926eec4329de5b7129beaedc3786eca070605d07f/pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9", size = 1917992 }, 289 | { url = "https://files.pythonhosted.org/packages/ad/ef/16ee2df472bf0e419b6bc68c05bf0145c49247a1095e85cee1463c6a44a1/pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc", size = 1856143 }, 290 | { url = "https://files.pythonhosted.org/packages/da/fa/bc3dbb83605669a34a93308e297ab22be82dfb9dcf88c6cf4b4f264e0a42/pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd", size = 1770063 }, 291 | { url = "https://files.pythonhosted.org/packages/4e/48/e813f3bbd257a712303ebdf55c8dc46f9589ec74b384c9f652597df3288d/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05", size = 1790013 }, 292 | { url = "https://files.pythonhosted.org/packages/b4/e0/56eda3a37929a1d297fcab1966db8c339023bcca0b64c5a84896db3fcc5c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d", size = 1801077 }, 293 | { url = "https://files.pythonhosted.org/packages/04/be/5e49376769bfbf82486da6c5c1683b891809365c20d7c7e52792ce4c71f3/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510", size = 1996782 }, 294 | { url = "https://files.pythonhosted.org/packages/bc/24/e3ee6c04f1d58cc15f37bcc62f32c7478ff55142b7b3e6d42ea374ea427c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6", size = 2661375 }, 295 | { url = "https://files.pythonhosted.org/packages/c1/f8/11a9006de4e89d016b8de74ebb1db727dc100608bb1e6bbe9d56a3cbbcce/pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b", size = 2071635 }, 296 | { url = "https://files.pythonhosted.org/packages/7c/45/bdce5779b59f468bdf262a5bc9eecbae87f271c51aef628d8c073b4b4b4c/pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327", size = 1916994 }, 297 | { url = "https://files.pythonhosted.org/packages/d8/fa/c648308fe711ee1f88192cad6026ab4f925396d1293e8356de7e55be89b5/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6", size = 1968877 }, 298 | { url = "https://files.pythonhosted.org/packages/16/16/b805c74b35607d24d37103007f899abc4880923b04929547ae68d478b7f4/pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f", size = 2116814 }, 299 | { url = "https://files.pythonhosted.org/packages/d1/58/5305e723d9fcdf1c5a655e6a4cc2a07128bf644ff4b1d98daf7a9dbf57da/pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769", size = 1738360 }, 300 | { url = "https://files.pythonhosted.org/packages/a5/ae/e14b0ff8b3f48e02394d8acd911376b7b66e164535687ef7dc24ea03072f/pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5", size = 1919411 }, 301 | ] 302 | 303 | [[package]] 304 | name = "pyyaml" 305 | version = "6.0.2" 306 | source = { registry = "https://pypi.org/simple" } 307 | sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631 } 308 | wheels = [ 309 | { url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873 }, 310 | { url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302 }, 311 | { url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154 }, 312 | { url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223 }, 313 | { url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542 }, 314 | { url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164 }, 315 | { url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611 }, 316 | { url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591 }, 317 | { url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338 }, 318 | { url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309 }, 319 | { url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679 }, 320 | { url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428 }, 321 | { url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361 }, 322 | { url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523 }, 323 | { url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660 }, 324 | { url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597 }, 325 | { url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527 }, 326 | { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446 }, 327 | ] 328 | 329 | [[package]] 330 | name = "requests" 331 | version = "2.32.3" 332 | source = { registry = "https://pypi.org/simple" } 333 | dependencies = [ 334 | { name = "certifi" }, 335 | { name = "charset-normalizer" }, 336 | { name = "idna" }, 337 | { name = "urllib3" }, 338 | ] 339 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 } 340 | wheels = [ 341 | { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 }, 342 | ] 343 | 344 | [[package]] 345 | name = "sniffio" 346 | version = "1.3.1" 347 | source = { registry = "https://pypi.org/simple" } 348 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 } 349 | wheels = [ 350 | { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, 351 | ] 352 | 353 | [[package]] 354 | name = "tokenizers" 355 | version = "0.20.1" 356 | source = { registry = "https://pypi.org/simple" } 357 | dependencies = [ 358 | { name = "huggingface-hub" }, 359 | ] 360 | sdist = { url = "https://files.pythonhosted.org/packages/d7/fb/373b66ba58cbf5eda371480e4e051d8892ea1433a73f1f92c48657a699a6/tokenizers-0.20.1.tar.gz", hash = "sha256:84edcc7cdeeee45ceedb65d518fffb77aec69311c9c8e30f77ad84da3025f002", size = 339552 } 361 | wheels = [ 362 | { url = "https://files.pythonhosted.org/packages/8e/8d/a051f979f955c6717099718054d7f51fea0a92d807a7d078a48f2684e54f/tokenizers-0.20.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:407ab666b38e02228fa785e81f7cf79ef929f104bcccf68a64525a54a93ceac9", size = 2667300 }, 363 | { url = "https://files.pythonhosted.org/packages/99/c3/2132487ca51148392f0d1ed7f35c23179f67d66fd64c233ff50f091258b4/tokenizers-0.20.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2f13a2d16032ebc8bd812eb8099b035ac65887d8f0c207261472803b9633cf3e", size = 2556581 }, 364 | { url = "https://files.pythonhosted.org/packages/f4/6e/9dfd1afcfd38fcc5b3a84bca54c33025561f7cab8ea375fa88f03407adc1/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e98eee4dca22849fbb56a80acaa899eec5b72055d79637dd6aa15d5e4b8628c9", size = 2937857 }, 365 | { url = "https://files.pythonhosted.org/packages/28/51/92e3b25eb41be7fd65219c832c4ff61bf5c8cc1c3d0543e9a117d63a0876/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47c1bcdd61e61136087459cb9e0b069ff23b5568b008265e5cbc927eae3387ce", size = 2823012 }, 366 | { url = "https://files.pythonhosted.org/packages/f7/59/185ff0bb35d46d88613e87bd76b03989ef8537ebf4f39876bddf9bed2fc1/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128c1110e950534426e2274837fc06b118ab5f2fa61c3436e60e0aada0ccfd67", size = 3086473 }, 367 | { url = "https://files.pythonhosted.org/packages/a4/2a/da72c32446ad7f3e6e5cb3c625222a5b9b0bc10b50456f6cb79f6230ae1f/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2e2d47a819d2954f2c1cd0ad51bb58ffac6f53a872d5d82d65d79bf76b9896d", size = 3101655 }, 368 | { url = "https://files.pythonhosted.org/packages/cf/7d/c895f076e552cb39ea0491f62ff6551cb3e60323a7496017182bd57cc314/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bdd67a0e3503a9a7cf8bc5a4a49cdde5fa5bada09a51e4c7e1c73900297539bd", size = 3405410 }, 369 | { url = "https://files.pythonhosted.org/packages/24/59/664121cb41b4f738479e2e1271013a2a7c9160955922536fb723a9c690b7/tokenizers-0.20.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:689b93d2e26d04da337ac407acec8b5d081d8d135e3e5066a88edd5bdb5aff89", size = 2977249 }, 370 | { url = "https://files.pythonhosted.org/packages/d4/ab/ceb7bdb3394431e92b18123faef9862877009f61377bfa45ffe5135747a5/tokenizers-0.20.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0c6a796ddcd9a19ad13cf146997cd5895a421fe6aec8fd970d69f9117bddb45c", size = 8989781 }, 371 | { url = "https://files.pythonhosted.org/packages/bb/37/eaa072b848471d31ae3df6e6d5be5ae594ed5fe39ca921e65cabf193dbde/tokenizers-0.20.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:3ea919687aa7001a8ff1ba36ac64f165c4e89035f57998fa6cedcfd877be619d", size = 9304427 }, 372 | { url = "https://files.pythonhosted.org/packages/41/ff/4aeb924d09f6561209b57af9123a0a28fa69472cc71ee40415f036253203/tokenizers-0.20.1-cp312-none-win32.whl", hash = "sha256:6d3ac5c1f48358ffe20086bf065e843c0d0a9fce0d7f0f45d5f2f9fba3609ca5", size = 2195986 }, 373 | { url = "https://files.pythonhosted.org/packages/7e/ba/18bf6a7ad04f8225b71aa862b57188748d1d81e268de4a9aac1aed237246/tokenizers-0.20.1-cp312-none-win_amd64.whl", hash = "sha256:b0874481aea54a178f2bccc45aa2d0c99cd3f79143a0948af6a9a21dcc49173b", size = 2377984 }, 374 | ] 375 | 376 | [[package]] 377 | name = "tqdm" 378 | version = "4.66.5" 379 | source = { registry = "https://pypi.org/simple" } 380 | dependencies = [ 381 | { name = "colorama", marker = "platform_system == 'Windows'" }, 382 | ] 383 | sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } 384 | wheels = [ 385 | { url = "https://files.pythonhosted.org/packages/48/5d/acf5905c36149bbaec41ccf7f2b68814647347b72075ac0b1fe3022fdc73/tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd", size = 78351 }, 386 | ] 387 | 388 | [[package]] 389 | name = "typing-extensions" 390 | version = "4.12.2" 391 | source = { registry = "https://pypi.org/simple" } 392 | sdist = { url = "https://files.pythonhosted.org/packages/df/db/f35a00659bc03fec321ba8bce9420de607a1d37f8342eee1863174c69557/typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8", size = 85321 } 393 | wheels = [ 394 | { url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 }, 395 | ] 396 | 397 | [[package]] 398 | name = "urllib3" 399 | version = "2.2.3" 400 | source = { registry = "https://pypi.org/simple" } 401 | sdist = { url = "https://files.pythonhosted.org/packages/ed/63/22ba4ebfe7430b76388e7cd448d5478814d3032121827c12a2cc287e2260/urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9", size = 300677 } 402 | wheels = [ 403 | { url = "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac", size = 126338 }, 404 | ] 405 | --------------------------------------------------------------------------------