diff --git a/devbox.lock b/devbox.lock index 4ae8f00..125bb29 100644 --- a/devbox.lock +++ b/devbox.lock @@ -74,8 +74,8 @@ } }, "github:NixOS/nixpkgs/nixpkgs-unstable": { - "last_modified": "2025-10-23T16:27:14Z", - "resolved": "github:NixOS/nixpkgs/d5faa84122bc0a1fd5d378492efce4e289f8eac1?lastModified=1761236834&narHash=sha256-%2Bpthv6hrL5VLW2UqPdISGuLiUZ6SnAXdd2DdUE%2BfV2Q%3D" + "last_modified": "2025-12-01T11:04:22Z", + "resolved": "github:NixOS/nixpkgs/c1cb7d097cb250f6e1904aacd5f2ba5ffd8a49ce?lastModified=1764587062&narHash=sha256-hdFa0TAVQAQLDF31cEW3enWmBP%2Bb592OvHs6WVe3D8k%3D" }, "nodejs@23.6.1": { "last_modified": "2025-01-22T14:55:48Z", @@ -143,56 +143,56 @@ } }, "pnpm@latest": { - "last_modified": "2025-10-10T13:35:32Z", - "resolved": "github:NixOS/nixpkgs/870493f9a8cb0b074ae5b411b2f232015db19a65#pnpm", + "last_modified": "2025-11-28T17:56:46Z", + "resolved": "github:NixOS/nixpkgs/24e915b36ca87d32777d766da3a3f4e3ce22cc98#pnpm", "source": "devbox-search", - "version": "10.18.2", + "version": "10.24.0", "systems": { "aarch64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/1a1h3bn5wfyc8r8xllzfibri2wh7npnw-pnpm-10.18.2", + "path": "/nix/store/09p3ygf7zdspqfyqn7z25k2q832hfklr-pnpm-10.24.0", "default": true } ], - "store_path": "/nix/store/1a1h3bn5wfyc8r8xllzfibri2wh7npnw-pnpm-10.18.2" + "store_path": "/nix/store/09p3ygf7zdspqfyqn7z25k2q832hfklr-pnpm-10.24.0" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/6mg938xvsavdcf0792imsfnzx0i0wic1-pnpm-10.18.2", + "path": "/nix/store/nkfhs7q2gk8nns4cr4r8gmvdi1dx99lg-pnpm-10.24.0", "default": true } ], - "store_path": "/nix/store/6mg938xvsavdcf0792imsfnzx0i0wic1-pnpm-10.18.2" + "store_path": "/nix/store/nkfhs7q2gk8nns4cr4r8gmvdi1dx99lg-pnpm-10.24.0" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/l46bk2n002bfd2giqiibizczihnxw771-pnpm-10.18.2", + "path": "/nix/store/qwp6r0zxrm5h4qa55wym2k9dy84rsaiq-pnpm-10.24.0", "default": true } ], - "store_path": "/nix/store/l46bk2n002bfd2giqiibizczihnxw771-pnpm-10.18.2" + "store_path": "/nix/store/qwp6r0zxrm5h4qa55wym2k9dy84rsaiq-pnpm-10.24.0" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/n5j11an7wlcr98k8mc9h1kz4bm3jqrag-pnpm-10.18.2", + "path": "/nix/store/x8brlvxzdkgpl75s9p7srnm2b5py8vnd-pnpm-10.24.0", "default": true } ], - "store_path": "/nix/store/n5j11an7wlcr98k8mc9h1kz4bm3jqrag-pnpm-10.18.2" + "store_path": "/nix/store/x8brlvxzdkgpl75s9p7srnm2b5py8vnd-pnpm-10.24.0" } } }, "poetry@latest": { - "last_modified": "2025-10-07T08:41:47Z", - "resolved": "github:NixOS/nixpkgs/bce5fe2bb998488d8e7e7856315f90496723793c#poetry", + "last_modified": "2025-11-26T06:22:50Z", + "resolved": "github:NixOS/nixpkgs/bb813de6d2241bcb1b5af2d3059f560c66329967#poetry", "source": "devbox-search", "version": "2.2.1", "systems": { @@ -200,57 +200,57 @@ "outputs": [ { "name": "out", - "path": "/nix/store/r9hx24sgm1j6fgc7k3iimzvs578x74ni-python3.13-poetry-2.2.1", + "path": "/nix/store/sfsxq068qvwpq7vwb0743m7isk7mbhwv-python3.13-poetry-2.2.1", "default": true }, { "name": "dist", - "path": "/nix/store/z5mlnbjvi194jinh1954cgw9pnma6rjc-python3.13-poetry-2.2.1-dist" + "path": "/nix/store/4yfah728gxi2p5gmn5mb5civh5cd3pi7-python3.13-poetry-2.2.1-dist" } ], - "store_path": "/nix/store/r9hx24sgm1j6fgc7k3iimzvs578x74ni-python3.13-poetry-2.2.1" + "store_path": "/nix/store/sfsxq068qvwpq7vwb0743m7isk7mbhwv-python3.13-poetry-2.2.1" }, "aarch64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/x81km926mwl1cw4g69hp3xa650yh6x2h-python3.13-poetry-2.2.1", + "path": "/nix/store/jaah5kv576z0i6sl8zcjxwnd1nf8xhqm-python3.13-poetry-2.2.1", "default": true }, { "name": "dist", - "path": "/nix/store/dvzd51ciyvcmkw89y4cib6nn395ij2r6-python3.13-poetry-2.2.1-dist" + "path": "/nix/store/jn3mq4ccbvpnhgiki3n9q2pl3wsllhzr-python3.13-poetry-2.2.1-dist" } ], - "store_path": "/nix/store/x81km926mwl1cw4g69hp3xa650yh6x2h-python3.13-poetry-2.2.1" + "store_path": "/nix/store/jaah5kv576z0i6sl8zcjxwnd1nf8xhqm-python3.13-poetry-2.2.1" }, "x86_64-darwin": { "outputs": [ { "name": "out", - "path": "/nix/store/pvsmkshnzd892pw6yfjwswahycd7z2if-python3.13-poetry-2.2.1", + "path": "/nix/store/aj5kr9p7d7skbacqkk949yk1s9axxka2-python3.13-poetry-2.2.1", "default": true }, { "name": "dist", - "path": "/nix/store/b7g8rjx6bj24jfd9vbxvwhl2zdswa0iz-python3.13-poetry-2.2.1-dist" + "path": "/nix/store/w6ipm3dpg44xcfks78n2iqdg5mq51n4d-python3.13-poetry-2.2.1-dist" } ], - "store_path": "/nix/store/pvsmkshnzd892pw6yfjwswahycd7z2if-python3.13-poetry-2.2.1" + "store_path": "/nix/store/aj5kr9p7d7skbacqkk949yk1s9axxka2-python3.13-poetry-2.2.1" }, "x86_64-linux": { "outputs": [ { "name": "out", - "path": "/nix/store/7fv57d1mglcr01mfzla5jhkbjfh0r0ki-python3.13-poetry-2.2.1", + "path": "/nix/store/jgi0jzxlm08qk7vnx1w3wmhyn4cchf47-python3.13-poetry-2.2.1", "default": true }, { "name": "dist", - "path": "/nix/store/9n8kp052wwcjr8w17phr0r3ci2yad9f3-python3.13-poetry-2.2.1-dist" + "path": "/nix/store/mmhq14nsa1vqkw4rqr7lcbhvih3fm4nk-python3.13-poetry-2.2.1-dist" } ], - "store_path": "/nix/store/7fv57d1mglcr01mfzla5jhkbjfh0r0ki-python3.13-poetry-2.2.1" + "store_path": "/nix/store/jgi0jzxlm08qk7vnx1w3wmhyn4cchf47-python3.13-poetry-2.2.1" } } }, diff --git a/docker-compose.yml b/docker-compose.yml index 14f120b..09a87b1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -56,11 +56,10 @@ services: volumes: - ./src/backend/var/playwright-output:/tmp/playwright-output playwright: - image: mcp/playwright:latest + image: mcp/playwright@sha256:5f0f926783f81f36ea72cae4b783b01cdd04d26edff08f3976a6f82e436e306e ports: - "8931:8931" - entrypoint: "node" - command: ["cli.js","--headless","--browser=chromium","--no-sandbox","--port=8931", "--viewport-size=1280x720","--allowed-hosts=playwright:8931,localhost:8931"] + command: ["--port=8931", "--host=0.0.0.0", "--viewport-size=1280x720", "--allowed-hosts=playwright:8931,localhost:8931", "--output-dir=/tmp/playwright-output"] volumes: - ./src/backend/var/playwright-output:/tmp/playwright-output volumes: diff --git a/docs/guide/tools/browser.md b/docs/guide/tools/browser.md index 211b5a8..8e678d8 100644 --- a/docs/guide/tools/browser.md +++ b/docs/guide/tools/browser.md @@ -1,8 +1,16 @@ ### Browser -::: info -This section is comming soon -::: +Automate real browser interactions using Playwright. Unlike the Web tool which only reads static content, the Browser tool can interact with live web pages — useful for apps that require login, dynamic content, or multi-step navigation. + +The agent can: + +- **Navigate** — open URLs, follow links, handle redirects and page transitions +- **Interact** — click buttons, fill forms, select dropdowns, check checkboxes, upload files +- **Read** — extract text, read table data, capture the current state of dynamic pages +- **Screenshot** — take screenshots to capture the current state of a page +- **Wait** — wait for elements to appear or conditions to be met before acting + +This tool requires no configuration by the agent creator. ::: warning Concurrent browser sessions Tero currently only supports 1 active browser session at a time. If multiple users try to use it at the same time they may get a notification about this. diff --git a/docs/guide/tools/docs.md b/docs/guide/tools/docs.md new file mode 100644 index 0000000..52be6d2 --- /dev/null +++ b/docs/guide/tools/docs.md @@ -0,0 +1,20 @@ +### Docs + +Upload files to your agent so it can use their content when answering questions. The agent searches the uploaded files using semantic similarity and grounds its responses in what the documents actually say, citing the source files. + +This is useful for agents that need to reason over technical documentation, test plans, specifications, runbooks, or any domain-specific knowledge that isn't in the model's training data. + +The agent can: + +- **Answer questions** from uploaded documents using semantic search across all files +- **Cite sources** — responses reference the specific file sections they were drawn from +- **Reason across multiple files** — the agent considers all uploaded files together when answering + +Supported file types include PDF, plain text, Markdown, and common document formats. + +::: tip Basic vs Advanced PDF processing + +**Basic** processing is faster and works well for most PDFs. **Advanced** processing (when available) handles complex PDFs with tables, multi-column layouts, and embedded images more accurately, but it costs more and takes longer. + +Use Advanced only when the agent consistently misses or misreads content from your PDFs. +::: diff --git a/docs/guide/tools/documents.md b/docs/guide/tools/documents.md deleted file mode 100644 index 7145d5e..0000000 --- a/docs/guide/tools/documents.md +++ /dev/null @@ -1,9 +0,0 @@ -### Documents - -::: info -This section is comming soon -::: - -::: tip Basic vs Advance PDF file processing - -::: diff --git a/docs/guide/tools/github.md b/docs/guide/tools/github.md new file mode 100644 index 0000000..984f504 --- /dev/null +++ b/docs/guide/tools/github.md @@ -0,0 +1,30 @@ +### GitHub + +Search code, manage repositories, review pull requests, and track issues — all through the official GitHub MCP server. + +The agent can help with: + +- **Issues** — create, read, update, comment on, and label issues +- **Pull requests** — create, review, merge, and comment on PRs; request reviews +- **Repositories** — browse files and commits, create branches, browse releases and tags +- **Users** — look up user profiles and organization members +- **Context** — access information about the authenticated user and their teams + +::: info +Only the GitHub core tools are currently supported. Advanced toolsets such as Actions, Projects, Security, Discussions, and Notifications are not supported. +::: + +#### Authentication + +This tool uses a GitHub Personal Access Token. The token is used for all requests on behalf of the agent. + +**Steps to get a token:** + +1. Go to GitHub → **Settings** → **Developer settings** → **Personal access tokens**. +2. Create a new token (classic or fine-grained). +3. Grant the scopes your agent needs — for example `repo` for full repository access, `read:org` for organization data. +4. Copy the token and paste it into the tool configuration. + +::: tip Fine-grained tokens +Fine-grained tokens let you limit access to specific repositories and permissions, which is a safer choice for shared agents. +::: diff --git a/docs/guide/tools/index.md b/docs/guide/tools/index.md index 23e95ac..2f5b9b6 100644 --- a/docs/guide/tools/index.md +++ b/docs/guide/tools/index.md @@ -1,5 +1,9 @@ -@include: ./documents.md +@include: ./docs.md @include: ./web.md @include: ./browser.md @include: ./jira.md +@include: ./github.md +@include: ./youtrack.md +@include: ./practitest.md +@include: ./redmine.md @include: ./mcp.md diff --git a/docs/guide/tools/jira.md b/docs/guide/tools/jira.md index f798685..aa76866 100644 --- a/docs/guide/tools/jira.md +++ b/docs/guide/tools/jira.md @@ -1,7 +1,28 @@ ### Jira -::: info -This section is comming soon -::: +Manage issues and track project activity in Jira Cloud. The agent can work across the full issue lifecycle and project structure. + +The agent can help with: + +- **Issues** — create, read, update, and delete issues; add comments and attachments +- **Projects** — browse projects and retrieve project details and statuses +- **Search** — use JQL (Jira Query Language) to find issues across projects +- **Users** — look up user information #### Authentication + +This tool uses OAuth 2.0 (3LO). You need to create an OAuth app in the Atlassian developer console and provide its credentials. + +**Steps to set up:** + +1. Go to [developer.atlassian.com](https://developer.atlassian.com) and sign in. +2. Open **Developer console** and create a new app. +3. In the left menu, go to **Authorization** → click **Configure** next to **OAuth 2.0 (3LO)**. +4. Enter the **Callback URL** provided by your Tero instance and save. +5. Go to **Permissions** and add the **Jira API**. Enable the scopes you need: + - `read:jira-work` — read issues, projects, and boards + - `write:jira-work` — create and update issues + - `read:jira-user` — read user information +6. Copy the **Client ID** and **Client secret** from the app settings. + +Enter the Client ID, Client secret, and selected scopes in the tool configuration. After saving, each user will be prompted to authorize the connection with their Atlassian account. diff --git a/docs/guide/tools/mcp.md b/docs/guide/tools/mcp.md index ede0830..3389f21 100644 --- a/docs/guide/tools/mcp.md +++ b/docs/guide/tools/mcp.md @@ -1,7 +1,19 @@ ### MCP -::: info -This section is comming soon -::: +Connect to any [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server and expose its tools to the agent. This lets you integrate any service that provides an MCP interface without needing a dedicated Tero tool. + +Once connected, the agent automatically discovers and can use all tools exposed by the MCP server — whatever those tools do (querying APIs, managing resources, reading data) becomes available in the agent's context. #### Authentication + +Two authentication modes are supported: + +**OAuth** — the agent creator provides the server URL and each user authorizes via the OAuth flow when they first use the tool. + +**Bearer Token** — the agent creator provides the server URL and a static bearer token used for all requests. + +Some servers also require custom HTTP headers (for example, a workspace ID or API version header). Use the **Custom headers** field to add any additional headers the server needs. + +::: tip Transport +The tool supports both SSE (`/sse` endpoint) and streamable HTTP transports. The correct transport is selected automatically based on the server URL. +::: diff --git a/docs/guide/tools/practitest.md b/docs/guide/tools/practitest.md new file mode 100644 index 0000000..9ef15a9 --- /dev/null +++ b/docs/guide/tools/practitest.md @@ -0,0 +1,25 @@ +### PractiTest + +Manage tests, test sets, and requirements in PractiTest. The agent connects directly to your project data, making it useful for QA workflows that need to bridge AI-generated content with your test management system. + +The agent can help with: + +- **Tests** — create, read, and update test cases +- **Test sets & runs** — manage test sets and track execution results +- **Requirements** — read requirements and analyze test coverage +- **Coverage analysis** — identify gaps between requirements and existing tests, then create missing tests and link them back to requirements + +#### Authentication + +This tool uses a personal API token and connects to the PractiTest MCP server. + +::: info Availability +The PractiTest MCP server is available for Corporate accounts. +::: + +**Steps to set up:** + +1. In PractiTest, go to your account settings and generate a personal API token. +2. In the tool configuration, enter: + - **Server URL**: your PractiTest MCP server URL + - **Token**: the personal API token diff --git a/docs/guide/tools/redmine.md b/docs/guide/tools/redmine.md new file mode 100644 index 0000000..82529a4 --- /dev/null +++ b/docs/guide/tools/redmine.md @@ -0,0 +1,30 @@ +### Redmine + +Manage issues, projects, and time tracking in Redmine. + +::: warning Enterprise Edition +This tool is only available in the Tero Enterprise Edition. +::: + +The agent can help with: + +- **Issues** — create, read, update, and delete issues; add notes; change status and assignee +- **Time tracking** — log and review time entries on issues and projects +- **Projects** — browse projects and versions +- **Search** — find issues across projects using filters + +#### Authentication + +This tool uses a Redmine API key for authentication. + +::: tip Prerequisite +A Redmine administrator must first enable the REST API under **Administration → Settings → API → Enable REST API**. +::: + +**Steps to set up:** + +1. In Redmine, click your account name in the top-right → **My account**. +2. In the right-hand pane, find your **API access key** (generate one if it doesn't exist yet). +3. In the tool configuration, enter: + - **URL**: your Redmine instance URL, e.g. `https://redmine.example.com` + - **API Key**: the key from your Redmine account diff --git a/docs/guide/tools/web.md b/docs/guide/tools/web.md index f6531ad..c26002b 100644 --- a/docs/guide/tools/web.md +++ b/docs/guide/tools/web.md @@ -1,5 +1,14 @@ ### Web -::: info -This section is comming soon +Search the web and extract text content from public URLs. The agent can look up current information beyond its training data or read specific pages as part of answering a request. + +The agent can: + +- **Search** — query the web and retrieve a ranked list of relevant results with snippets +- **Extract** — read the full text content of public URLs, including documentation sites, blog posts, changelogs, and issue trackers + +This tool requires no configuration by the agent creator. It is available when a Tero admin has configured a web search API key (Tavily or Google Custom Search) for the instance. + +::: tip Search vs Extract +The agent uses **search** to find relevant pages for a query, and **extract** to read the full content of a specific URL. You can ask the agent to visit a URL directly, or let it search and pick the best result. ::: diff --git a/docs/guide/tools/youtrack.md b/docs/guide/tools/youtrack.md new file mode 100644 index 0000000..82ab304 --- /dev/null +++ b/docs/guide/tools/youtrack.md @@ -0,0 +1,25 @@ +### YouTrack + +Report issues, log work time, and manage knowledge base articles in JetBrains YouTrack. The agent connects to the YouTrack MCP server built into your instance (requires YouTrack 2025.3 or later). + +The agent can help with: + +- **Issues** — search using YouTrack query language, create, update, assign, comment on, tag, and link issues +- **Time tracking** — log work time directly on issues +- **Knowledge Base** — search, read, create, and update articles +- **Projects** — find and inspect project configurations and custom field schemas +- **Users & groups** — look up users by name or email, list group members + +#### Authentication + +This tool uses a YouTrack permanent token and connects to the YouTrack MCP server built into your instance. + +**Steps to set up:** + +1. In YouTrack, click your **avatar** → **Profile** → **Account Security** tab. +2. In the **Tokens** section, click **New token**. +3. Enter a name and select the **YouTrack** scope, then click **Create token**. +4. Copy the token immediately — it is only shown once. +5. In the tool configuration, enter: + - **Server URL**: your YouTrack MCP endpoint, e.g. `https://your-instance.youtrack.cloud/mcp` + - **Token**: the permanent token you created diff --git a/scripts/vllm.sh b/scripts/vllm.sh old mode 100644 new mode 100755 diff --git a/src/backend/alembic/versions/20260226-3170980dd69c-protected_agents.py b/src/backend/alembic/versions/20260226-3170980dd69c-protected_agents.py new file mode 100644 index 0000000..d088ade --- /dev/null +++ b/src/backend/alembic/versions/20260226-3170980dd69c-protected_agents.py @@ -0,0 +1,25 @@ +"""is_protected_agents + +Revision ID: 3170980dd69c +Revises: 8e9d8ca4dd0c +Create Date: 2026-02-26 13:37:10.458412 + +""" +import sqlalchemy as sa +from typing import Sequence, Union +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = '3170980dd69c' +down_revision: Union[str, None] = '8e9d8ca4dd0c' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column('agent', sa.Column('is_protected', sa.Boolean(), nullable=False, server_default=sa.false())) + + +def downgrade() -> None: + op.drop_column('agent', 'is_protected') diff --git a/src/backend/alembic/versions/20260309-a1b2c3d4e5f6-add_claude_sonnet_4_6_and_opus_4_6.py b/src/backend/alembic/versions/20260309-a1b2c3d4e5f6-add_claude_sonnet_4_6_and_opus_4_6.py new file mode 100644 index 0000000..eb2d76a --- /dev/null +++ b/src/backend/alembic/versions/20260309-a1b2c3d4e5f6-add_claude_sonnet_4_6_and_opus_4_6.py @@ -0,0 +1,29 @@ +"""add-claude-sonnet-4-6-and-opus-4-6 + +Revision ID: a1b2c3d4e5f6 +Revises: 3170980dd69c +Create Date: 2026-03-09 + +""" +from typing import Sequence, Union +from alembic import op + + +# revision identifiers, used by Alembic. +revision: str = 'a1b2c3d4e5f6' +down_revision: Union[str, None] = '3170980dd69c' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute(""" + INSERT INTO llm_model (id, name, model_type, description, model_vendor, token_limit, output_token_limit, prompt_1k_token_usd, completion_1k_token_usd) + VALUES + ('claude-sonnet-4-6', 'Claude Sonnet 4.6', 'CHAT', 'This is an improved version of Claude Sonnet 4 with better reasoning. Good for complex analysis, coding, and creative writing. Similar pricing to Claude Sonnet 4.', 'ANTHROPIC', 200000, 64000, 0.003, 0.015), + ('claude-opus-4-6', 'Claude Opus 4.6', 'CHAT', 'This is a more capable model than Claude Sonnet 4 for highly complex tasks. Best for demanding reasoning and analysis. Higher cost than Claude Sonnet models.', 'ANTHROPIC', 200000, 64000, 0.005, 0.025) + """) + + +def downgrade() -> None: + op.execute("DELETE FROM llm_model WHERE id IN ('claude-sonnet-4-6', 'claude-opus-4-6')") diff --git a/src/backend/alembic/versions/20260310-b8c4d2e6f1a3-migrate_4_models_to_5.py b/src/backend/alembic/versions/20260310-b8c4d2e6f1a3-migrate_4_models_to_5.py new file mode 100644 index 0000000..2b787c0 --- /dev/null +++ b/src/backend/alembic/versions/20260310-b8c4d2e6f1a3-migrate_4_models_to_5.py @@ -0,0 +1,91 @@ +"""migrate_4_models_to_5 + +Revision ID: b8c4d2e6f1a3 +Revises: a1b2c3d4e5f6 +Create Date: 2026-03-10 + +""" +from typing import Sequence, Union +from alembic import op + + +revision: str = 'b8c4d2e6f1a3' +down_revision: Union[str, None] = 'a1b2c3d4e5f6' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute(""" + UPDATE agent + SET team_id = NULL + WHERE model_id IN ('gpt-4o', 'gpt-4o-mini') + AND user_id IS NULL + AND team_id = 1 + """) + + op.execute(""" + UPDATE agent + SET model_id = CASE model_id + WHEN 'gpt-4o' THEN 'gpt-5' + WHEN 'gpt-4.1' THEN 'gpt-5' + WHEN 'o4-mini' THEN 'gpt-5-mini' + WHEN 'gpt-4o-mini' THEN 'gpt-5-mini' + WHEN 'gpt-4.1-nano' THEN 'gpt-5-nano' + END, + reasoning_effort = 'MEDIUM' + WHERE model_id IN ('gpt-4o', 'gpt-4.1', 'o4-mini', 'gpt-4o-mini', 'gpt-4.1-nano') + """) + + op.execute(""" + UPDATE evaluator + SET model_id = CASE model_id + WHEN 'gpt-4o' THEN 'gpt-5' + WHEN 'gpt-4.1' THEN 'gpt-5' + WHEN 'o4-mini' THEN 'gpt-5-mini' + WHEN 'gpt-4o-mini' THEN 'gpt-5-mini' + WHEN 'gpt-4.1-nano' THEN 'gpt-5-nano' + END + WHERE model_id IN ('gpt-4o', 'gpt-4.1', 'o4-mini', 'gpt-4o-mini', 'gpt-4.1-nano') + """) + + op.execute(""" + DELETE FROM llm_model + WHERE id IN ('gpt-4o', 'gpt-4o-mini', 'gpt-4.1', 'gpt-4.1-nano', 'o4-mini') + """) + + op.execute(""" + UPDATE llm_model + SET description = CASE id + WHEN 'gemini-2.5-pro' THEN 'This is an advanced reasoning model, outperforming GPT-5 Mini with a larger context while being more affordable.' + WHEN 'gemini-2.5-flash' THEN 'This is a fast and efficient model, comparable to GPT-5 Nano, optimized for speed while maintaining high quality responses.' + WHEN 'gpt-5' THEN 'This is the best reasoning model for coding and complex agentic tasks from OpenAI. Aligned with GPT-5 Mini for most use cases.' + WHEN 'gpt-5-mini' THEN 'This is a new reasoning model with a good balance between cost and intelligence. Suited for most agent tasks.' + WHEN 'gpt-5-nano' THEN 'This is a new reasoning model for simpler tasks. Lower cost than GPT-5 Mini.' + END + WHERE id IN ('gemini-2.5-pro', 'gemini-2.5-flash', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano') + """) + + +def downgrade() -> None: + op.execute(""" + UPDATE llm_model + SET description = CASE id + WHEN 'gemini-2.5-pro' THEN 'This is an advanced reasoning model, outperforming GPT-4o with a larger context while being more affordable.' + WHEN 'gemini-2.5-flash' THEN 'This is a fast and efficient model, comparable to GPT-4.1 Nano, optimized for speed while maintaining high quality responses.' + WHEN 'gpt-5' THEN 'This is the best reasoning model for coding and complex agentic tasks from OpenAI. It will replace GPT-4o and GPT-4.1 in the short term.' + WHEN 'gpt-5-mini' THEN 'This is a new reasoning model that will replace GPT-4o Mini in the short term. It has a good balance between cost and intelligence.' + WHEN 'gpt-5-nano' THEN 'This is a new reasoning model that will replace GPT-4.1 Nano in the short term.' + END + WHERE id IN ('gemini-2.5-pro', 'gemini-2.5-flash', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano') + """) + + op.execute(""" + INSERT INTO llm_model (id, name, model_type, description, model_vendor, token_limit, output_token_limit, prompt_1k_token_usd, completion_1k_token_usd) + VALUES + ('gpt-4o', 'GPT-4o', 'CHAT', 'This is the best model for complex tasks. This is more powerful than GPT-4o Mini, but slower and more expensive.', 'OPENAI', 128000, 4096, 0.005, 0.015), + ('gpt-4o-mini', 'GPT-4o Mini', 'CHAT', 'This is a good model for simple tasks like summaries, simple questions, etc.', 'OPENAI', 128000, 16384, 0.00015, 0.0006), + ('gpt-4.1', 'GPT-4.1', 'CHAT', 'This is a new model that will replace GPT-4o in the short term.', 'OPENAI', 1047576, 32768, 0.002, 0.008), + ('gpt-4.1-nano', 'GPT-4.1 Nano', 'CHAT', 'This is a new model that will replace GPT-4o Mini in the short term.', 'OPENAI', 1047576, 32768, 0.0001, 0.0004), + ('o4-mini', 'O4 Mini', 'REASONING', 'This is a reasoning model that is good for coding, math and some complex tasks.', 'OPENAI', 200000, 100000, 0.0011, 0.0044) + """) diff --git a/src/backend/alembic/versions/20260313-c4d5e6f7a8b9-add_gpt_5_4_and_codex.py b/src/backend/alembic/versions/20260313-c4d5e6f7a8b9-add_gpt_5_4_and_codex.py new file mode 100644 index 0000000..67224f5 --- /dev/null +++ b/src/backend/alembic/versions/20260313-c4d5e6f7a8b9-add_gpt_5_4_and_codex.py @@ -0,0 +1,29 @@ +"""add-gpt-5-4-and-codex + +Revision ID: c4d5e6f7a8b9 +Revises: b8c4d2e6f1a3 +Create Date: 2026-03-13 + +""" + +from typing import Sequence, Union +from alembic import op + + +revision: str = 'c4d5e6f7a8b9' +down_revision: Union[str, None] = 'b8c4d2e6f1a3' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute(""" + INSERT INTO llm_model (id, name, model_type, description, model_vendor, token_limit, output_token_limit, prompt_1k_token_usd, completion_1k_token_usd) + VALUES + ('gpt-5.1-codex-max', 'GPT-5.1 Codex Max', 'REASONING', 'This is the most capable Codex model for long-running agentic coding. Best for complex codebases and multi-step tasks. Similar pricing to GPT-5.1 Codex.', 'OPENAI', 400000, 128000, 0.00125, 0.01), + ('gpt-5.4', 'GPT-5.4', 'REASONING', 'This is the most capable reasoning model from OpenAI for professional work. Best for demanding analysis and complex agentic tasks. Higher cost than GPT-5.', 'OPENAI', 1050000, 128000, 0.0025, 0.015) + """) + + +def downgrade() -> None: + op.execute("DELETE FROM llm_model WHERE id IN ('gpt-5.1-codex-max', 'gpt-5.4')") diff --git a/src/backend/alembic/versions/20260406-d5e6f7a8b9c0-remove_cost_from_model_descriptions.py b/src/backend/alembic/versions/20260406-d5e6f7a8b9c0-remove_cost_from_model_descriptions.py new file mode 100644 index 0000000..221a106 --- /dev/null +++ b/src/backend/alembic/versions/20260406-d5e6f7a8b9c0-remove_cost_from_model_descriptions.py @@ -0,0 +1,48 @@ +"""remove-cost-from-model-descriptions + +Revision ID: d5e6f7a8b9c0 +Revises: c4d5e6f7a8b9 +Create Date: 2026-04-06 + +""" + +from typing import Sequence, Union +from alembic import op + + +revision: str = 'd5e6f7a8b9c0' +down_revision: Union[str, None] = 'c4d5e6f7a8b9' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.execute(""" + UPDATE llm_model + SET description = CASE id + WHEN 'gemini-2.5-pro' THEN 'This is an advanced reasoning model, outperforming GPT-5 Mini with a larger context.' + WHEN 'gpt-5-mini' THEN 'This is a fast reasoning model with good performance, suited for most agent tasks like research, drafting, and data analysis.' + WHEN 'gpt-5-nano' THEN 'This is a very fast reasoning model for simple tasks, such as summarization, and extraction.' + WHEN 'claude-sonnet-4-6' THEN 'This is an improved version of Claude Sonnet 4 with better reasoning. Good for complex analysis, coding, and creative writing.' + WHEN 'claude-opus-4-6' THEN 'This is a more capable model than Claude Sonnet 4 for highly complex tasks. Best for demanding reasoning and analysis.' + WHEN 'gpt-5.4' THEN 'This is the most capable reasoning model from OpenAI. Best for demanding analysis and complex agentic tasks.' + WHEN 'gpt-5.1-codex-max' THEN 'This is the most capable Codex model for coding. Best for complex codebases and multi-step tasks.' + END + WHERE id IN ('gemini-2.5-pro', 'gpt-5-mini', 'gpt-5-nano', 'claude-sonnet-4-6', 'claude-opus-4-6', 'gpt-5.4', 'gpt-5.1-codex-max') + """) + + +def downgrade() -> None: + op.execute(""" + UPDATE llm_model + SET description = CASE id + WHEN 'gemini-2.5-pro' THEN 'This is an advanced reasoning model, outperforming GPT-5 Mini with a larger context while being more affordable.' + WHEN 'gpt-5-mini' THEN 'This is a new reasoning model with a good balance between cost and intelligence. Suited for most agent tasks.' + WHEN 'gpt-5-nano' THEN 'This is a new reasoning model for simpler tasks. Lower cost than GPT-5 Mini.' + WHEN 'claude-sonnet-4-6' THEN 'This is an improved version of Claude Sonnet 4 with better reasoning. Good for complex analysis, coding, and creative writing. Similar pricing to Claude Sonnet 4.' + WHEN 'claude-opus-4-6' THEN 'This is a more capable model than Claude Sonnet 4 for highly complex tasks. Best for demanding reasoning and analysis. Higher cost than Claude Sonnet models.' + WHEN 'gpt-5.4' THEN 'This is the most capable reasoning model from OpenAI for professional work. Best for demanding analysis and complex agentic tasks. Higher cost than GPT-5.' + WHEN 'gpt-5.1-codex-max' THEN 'This is the most capable Codex model for long-running agentic coding. Best for complex codebases and multi-step tasks. Similar pricing to GPT-5.1 Codex.' + END + WHERE id IN ('gemini-2.5-pro', 'gpt-5-mini', 'gpt-5-nano', 'claude-sonnet-4-6', 'claude-opus-4-6', 'gpt-5.4', 'gpt-5.1-codex-max') + """) diff --git a/src/backend/alembic/versions/20260422-e1f2a3b4c5d6-add_error_code_to_test_case_result.py b/src/backend/alembic/versions/20260422-e1f2a3b4c5d6-add_error_code_to_test_case_result.py new file mode 100644 index 0000000..e33dd76 --- /dev/null +++ b/src/backend/alembic/versions/20260422-e1f2a3b4c5d6-add_error_code_to_test_case_result.py @@ -0,0 +1,25 @@ +"""add-error-code-to-test-case-result + +Revision ID: e1f2a3b4c5d6 +Revises: d5e6f7a8b9c0 +Create Date: 2026-04-22 + +""" + +from typing import Sequence, Union +import sqlalchemy as sa +from alembic import op + + +revision: str = 'e1f2a3b4c5d6' +down_revision: Union[str, None] = 'd5e6f7a8b9c0' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + op.add_column('test_case_result', sa.Column('error_code', sa.String(), nullable=True)) + + +def downgrade() -> None: + op.drop_column('test_case_result', 'error_code') diff --git a/src/backend/poetry.lock b/src/backend/poetry.lock index e7c231e..e30920f 100644 --- a/src/backend/poetry.lock +++ b/src/backend/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.2 and should not be changed by hand. [[package]] name = "aiofiles" @@ -253,6 +253,18 @@ files = [ alembic = ">=1.7" SQLAlchemy = ">=1.4" +[[package]] +name = "annotated-doc" +version = "0.0.4" +description = "Document parameters, class attributes, return types, and variables inline, with Annotated." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320"}, + {file = "annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4"}, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -267,23 +279,34 @@ files = [ [[package]] name = "anyio" -version = "4.11.0" +version = "4.12.0" description = "High-level concurrency and networking framework on top of asyncio or Trio" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "anyio-4.11.0-py3-none-any.whl", hash = "sha256:0287e96f4d26d4149305414d4e3bc32f0dcd0862365a4bddea19d7a1ec38c4fc"}, - {file = "anyio-4.11.0.tar.gz", hash = "sha256:82a8d0b81e318cc5ce71a5f1f8b5c4e63619620b63141ef8c995fa0db95a57c4"}, + {file = "anyio-4.12.0-py3-none-any.whl", hash = "sha256:dad2376a628f98eeca4881fc56cd06affd18f659b17a747d3ff0307ced94b1bb"}, + {file = "anyio-4.12.0.tar.gz", hash = "sha256:73c693b567b0c55130c104d0b43a9baf3aa6a31fc6110116509f27bf75e21ec0"}, ] [package.dependencies] idna = ">=2.8" -sniffio = ">=1.1" typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -trio = ["trio (>=0.31.0)"] +trio = ["trio (>=0.31.0) ; python_version < \"3.10\"", "trio (>=0.32.0) ; python_version >= \"3.10\""] + +[[package]] +name = "async-property" +version = "0.2.2" +description = "Python decorator for async properties." +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "async_property-0.2.2-py2.py3-none-any.whl", hash = "sha256:8924d792b5843994537f8ed411165700b27b2bd966cefc4daeefc1253442a9d7"}, + {file = "async_property-0.2.2.tar.gz", hash = "sha256:17d9bd6ca67e27915a75d92549df64b5c7174e9dc806b30a3934dc4ff0506380"}, +] [[package]] name = "asyncpg" @@ -406,18 +429,18 @@ tracing = ["opentelemetry-api (>=1.26,<2.0)"] [[package]] name = "beautifulsoup4" -version = "4.14.2" +version = "4.14.3" description = "Screen-scraping library" optional = false python-versions = ">=3.7.0" groups = ["main"] files = [ - {file = "beautifulsoup4-4.14.2-py3-none-any.whl", hash = "sha256:5ef6fa3a8cbece8488d66985560f97ed091e22bbc4e9c2338508a9d5de6d4515"}, - {file = "beautifulsoup4-4.14.2.tar.gz", hash = "sha256:2a98ab9f944a11acee9cc848508ec28d9228abfd522ef0fad6a02a72e0ded69e"}, + {file = "beautifulsoup4-4.14.3-py3-none-any.whl", hash = "sha256:0918bfe44902e6ad8d57732ba310582e98da931428d231a5ecb9e7c703a735bb"}, + {file = "beautifulsoup4-4.14.3.tar.gz", hash = "sha256:6292b1c5186d356bba669ef9f7f051757099565ad9ada5dd630bd9de5fa7fb86"}, ] [package.dependencies] -soupsieve = ">1.2" +soupsieve = ">=1.6.1" typing-extensions = ">=4.0.0" [package.extras] @@ -429,34 +452,34 @@ lxml = ["lxml"] [[package]] name = "boto3" -version = "1.40.63" +version = "1.42.13" description = "The AWS SDK for Python" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "boto3-1.40.63-py3-none-any.whl", hash = "sha256:f15d4abf1a6283887c336f660cdfc2162a210d2d8f4d98dbcbcef983371c284d"}, - {file = "boto3-1.40.63.tar.gz", hash = "sha256:3bf4b034900c87a6a9b3b3b44c4aec26e96fc73bff2505f0766224b7295178ce"}, + {file = "boto3-1.42.13-py3-none-any.whl", hash = "sha256:9d6aad3fa8b90567006bf7b32efa26489fc306fbe63946eaf57b72356a45761d"}, + {file = "boto3-1.42.13.tar.gz", hash = "sha256:4c9a62dcb5c3f905630fe99fb4b81131da84c5c92eedcc81a89cbd924c1c524f"}, ] [package.dependencies] -botocore = ">=1.40.63,<1.41.0" +botocore = ">=1.42.13,<1.43.0" jmespath = ">=0.7.1,<2.0.0" -s3transfer = ">=0.14.0,<0.15.0" +s3transfer = ">=0.16.0,<0.17.0" [package.extras] crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.40.63" +version = "1.42.13" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "botocore-1.40.63-py3-none-any.whl", hash = "sha256:83657b3ee487268fccc9ba022cba572ba657b9ece8cddd1fa241e2c6a49c8c14"}, - {file = "botocore-1.40.63.tar.gz", hash = "sha256:0324552c3c800e258cbcb8c22b495a2e2e0260a7408d08016196e46fa0d1b587"}, + {file = "botocore-1.42.13-py3-none-any.whl", hash = "sha256:b750b2de4a2478db9718a02395cb9da8698901ba02378d60037d6369ecb6bb88"}, + {file = "botocore-1.42.13.tar.gz", hash = "sha256:7e4cf14bd5719b60600fb45d2bb3ae140feb3c182a863b93093aafce7f93cfee"}, ] [package.dependencies] @@ -465,30 +488,30 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.27.6)"] +crt = ["awscrt (==0.29.2)"] [[package]] name = "cachetools" -version = "6.2.1" +version = "6.2.4" description = "Extensible memoizing collections and decorators" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "cachetools-6.2.1-py3-none-any.whl", hash = "sha256:09868944b6dde876dfd44e1d47e18484541eaf12f26f29b7af91b26cc892d701"}, - {file = "cachetools-6.2.1.tar.gz", hash = "sha256:3f391e4bd8f8bf0931169baf7456cc822705f4e2a31f840d218f445b9a854201"}, + {file = "cachetools-6.2.4-py3-none-any.whl", hash = "sha256:69a7a52634fed8b8bf6e24a050fb60bff1c9bd8f6d24572b99c32d4e71e62a51"}, + {file = "cachetools-6.2.4.tar.gz", hash = "sha256:82c5c05585e70b6ba2d3ae09ea60b79548872185d2f24ae1f2709d37299fd607"}, ] [[package]] name = "certifi" -version = "2025.10.5" +version = "2025.11.12" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" groups = ["main", "dev"] files = [ - {file = "certifi-2025.10.5-py3-none-any.whl", hash = "sha256:0f212c2744a9bb6de0c56639a6f68afe01ecd92d91f14ae897c4fe7bbeeef0de"}, - {file = "certifi-2025.10.5.tar.gz", hash = "sha256:47c09d31ccf2acf0be3f701ea53595ee7e0b8fa08801c6624be771df09ae7b43"}, + {file = "certifi-2025.11.12-py3-none-any.whl", hash = "sha256:97de8790030bbd5c2d96b7ec782fc2f7820ef8dba6db909ccf95449f2d062d4b"}, + {file = "certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316"}, ] [[package]] @@ -726,14 +749,14 @@ files = [ [[package]] name = "click" -version = "8.3.0" +version = "8.3.1" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "click-8.3.0-py3-none-any.whl", hash = "sha256:9b9f285302c6e3064f4330c05f05b81945b2a39544279343e6e7c5f27a9baddc"}, - {file = "click-8.3.0.tar.gz", hash = "sha256:e7b8232224eba16f4ebe410c25ced9f7875cb5f3263ffc93cc3e8da705e229c4"}, + {file = "click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6"}, + {file = "click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a"}, ] [package.dependencies] @@ -845,6 +868,21 @@ files = [ marshmallow = ">=3.18.0,<4.0.0" typing-inspect = ">=0.4.0,<1" +[[package]] +name = "deprecation" +version = "2.1.0" +description = "A library to handle automated deprecations" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a"}, + {file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff"}, +] + +[package.dependencies] +packaging = "*" + [[package]] name = "distro" version = "1.9.0" @@ -913,24 +951,26 @@ files = [ [[package]] name = "fastapi" -version = "0.115.14" +version = "0.121.3" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" groups = ["main"] files = [ - {file = "fastapi-0.115.14-py3-none-any.whl", hash = "sha256:6c0c8bf9420bd58f565e585036d971872472b4f7d3f6c73b698e10cffdefb3ca"}, - {file = "fastapi-0.115.14.tar.gz", hash = "sha256:b1de15cdc1c499a4da47914db35d0e4ef8f1ce62b624e94e0e5824421df99739"}, + {file = "fastapi-0.121.3-py3-none-any.whl", hash = "sha256:0c78fc87587fcd910ca1bbf5bc8ba37b80e119b388a7206b39f0ecc95ebf53e9"}, + {file = "fastapi-0.121.3.tar.gz", hash = "sha256:0055bc24fe53e56a40e9e0ad1ae2baa81622c406e548e501e717634e2dfbc40b"}, ] [package.dependencies] +annotated-doc = ">=0.0.2" pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0" -starlette = ">=0.40.0,<0.47.0" +starlette = ">=0.40.0,<0.51.0" typing-extensions = ">=4.8.0" [package.extras] -all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] -standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.5)", "httpx (>=0.23.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +all = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=3.1.5)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.18)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"] +standard = ["email-validator (>=2.0.0)", "fastapi-cli[standard] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] +standard-no-fastapi-cloud-cli = ["email-validator (>=2.0.0)", "fastapi-cli[standard-no-fastapi-cloud-cli] (>=0.0.8)", "httpx (>=0.23.0,<1.0.0)", "jinja2 (>=3.1.5)", "python-multipart (>=0.0.18)", "uvicorn[standard] (>=0.12.0)"] [[package]] name = "fastapi-mcp" @@ -1137,14 +1177,14 @@ files = [ [[package]] name = "fsspec" -version = "2025.10.0" +version = "2025.12.0" description = "File-system specification" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "fsspec-2025.10.0-py3-none-any.whl", hash = "sha256:7c7712353ae7d875407f97715f0e1ffcc21e33d5b24556cb1e090ae9409ec61d"}, - {file = "fsspec-2025.10.0.tar.gz", hash = "sha256:b6789427626f068f9a83ca4e8a3cc050850b6c0f71f99ddb4f542b8266a26a59"}, + {file = "fsspec-2025.12.0-py3-none-any.whl", hash = "sha256:8bf1fe301b7d8acfa6e8571e3b1c3d158f909666642431cc78a1b7b4dbc5ec5b"}, + {file = "fsspec-2025.12.0.tar.gz", hash = "sha256:c505de011584597b1060ff778bb664c1bc022e87921b0e4f10cc9c44f9635973"}, ] [package.extras] @@ -1229,14 +1269,14 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.0)"] [[package]] name = "google-api-python-client" -version = "2.186.0" +version = "2.187.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_api_python_client-2.186.0-py3-none-any.whl", hash = "sha256:2ea4beba93e193d3a632c7bf865b6ccace42b0017269a964566e39b7e1f3cf79"}, - {file = "google_api_python_client-2.186.0.tar.gz", hash = "sha256:01b8ff446adbc10f495188400a9f7c3e88e5e75741663a25822f41e788475333"}, + {file = "google_api_python_client-2.187.0-py3-none-any.whl", hash = "sha256:d8d0f6d85d7d1d10bdab32e642312ed572bdc98919f72f831b44b9a9cebba32f"}, + {file = "google_api_python_client-2.187.0.tar.gz", hash = "sha256:e98e8e8f49e1b5048c2f8276473d6485febc76c9c47892a8b4d1afa2c9ec8278"}, ] [package.dependencies] @@ -1248,14 +1288,14 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.42.1" +version = "2.45.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "google_auth-2.42.1-py2.py3-none-any.whl", hash = "sha256:eb73d71c91fc95dbd221a2eb87477c278a355e7367a35c0d84e6b0e5f9b4ad11"}, - {file = "google_auth-2.42.1.tar.gz", hash = "sha256:30178b7a21aa50bffbdc1ffcb34ff770a2f65c712170ecd5446c4bef4dc2b94e"}, + {file = "google_auth-2.45.0-py2.py3-none-any.whl", hash = "sha256:82344e86dc00410ef5382d99be677c6043d72e502b625aa4f4afa0bdacca0f36"}, + {file = "google_auth-2.45.0.tar.gz", hash = "sha256:90d3f41b6b72ea72dd9811e765699ee491ab24139f34ebf1ca2b9cc0c38708f3"}, ] [package.dependencies] @@ -1265,6 +1305,7 @@ rsa = ">=3.1.4,<5" [package.extras] aiohttp = ["aiohttp (>=3.6.2,<4.0.0)", "requests (>=2.20.0,<3.0.0)"] +cryptography = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)"] enterprise-cert = ["cryptography", "pyopenssl"] pyjwt = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyjwt (>=2.0)"] pyopenssl = ["cryptography (<39.0.0) ; python_version < \"3.8\"", "cryptography (>=38.0.3)", "pyopenssl (>=20.0.0)"] @@ -1332,14 +1373,14 @@ protobuf = ">=3.20.2,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4 [[package]] name = "googleapis-common-protos" -version = "1.71.0" +version = "1.72.0" description = "Common protobufs used in Google APIs" optional = false python-versions = ">=3.7" groups = ["main"] files = [ - {file = "googleapis_common_protos-1.71.0-py3-none-any.whl", hash = "sha256:59034a1d849dc4d18971997a72ac56246570afdd17f9369a0ff68218d50ab78c"}, - {file = "googleapis_common_protos-1.71.0.tar.gz", hash = "sha256:1aec01e574e29da63c80ba9f7bbf1ccfaacf1da877f23609fe236ca7c72a2e2e"}, + {file = "googleapis_common_protos-1.72.0-py3-none-any.whl", hash = "sha256:4299c5a82d5ae1a9702ada957347726b167f9f8d1fc352477702a1e851ff4038"}, + {file = "googleapis_common_protos-1.72.0.tar.gz", hash = "sha256:e55a601c1b32b52d7a3e65f43563e2aa61bcd737998ee672ac9b951cd49319f5"}, ] [package.dependencies] @@ -1350,66 +1391,60 @@ grpc = ["grpcio (>=1.44.0,<2.0.0)"] [[package]] name = "greenlet" -version = "3.2.4" +version = "3.3.0" description = "Lightweight in-process concurrent programming" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main", "dev"] files = [ - {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f10fd42b5ee276335863712fa3da6608e93f70629c631bf77145021600abc23c"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c8c9e331e58180d0d83c5b7999255721b725913ff6bc6cf39fa2a45841a4fd4b"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:58b97143c9cc7b86fc458f215bd0932f1757ce649e05b640fea2e79b54cedb31"}, - {file = "greenlet-3.2.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c2ca18a03a8cfb5b25bc1cbe20f3d9a4c80d8c3b13ba3df49ac3961af0b1018d"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9fe0a28a7b952a21e2c062cd5756d34354117796c6d9215a87f55e38d15402c5"}, - {file = "greenlet-3.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8854167e06950ca75b898b104b63cc646573aa5fef1353d4508ecdd1ee76254f"}, - {file = "greenlet-3.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:73f49b5368b5359d04e18d15828eecc1806033db5233397748f4ca813ff1056c"}, - {file = "greenlet-3.2.4-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:96378df1de302bc38e99c3a9aa311967b7dc80ced1dcc6f171e99842987882a2"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1ee8fae0519a337f2329cb78bd7a8e128ec0f881073d43f023c7b8d4831d5246"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:94abf90142c2a18151632371140b3dba4dee031633fe614cb592dbb6c9e17bc3"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:4d1378601b85e2e5171b99be8d2dc85f594c79967599328f95c1dc1a40f1c633"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0db5594dce18db94f7d1650d7489909b57afde4c580806b8d9203b6e79cdc079"}, - {file = "greenlet-3.2.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2523e5246274f54fdadbce8494458a2ebdcdbc7b802318466ac5606d3cded1f8"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:1987de92fec508535687fb807a5cea1560f6196285a4cde35c100b8cd632cc52"}, - {file = "greenlet-3.2.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:55e9c5affaa6775e2c6b67659f3a71684de4c549b3dd9afca3bc773533d284fa"}, - {file = "greenlet-3.2.4-cp311-cp311-win_amd64.whl", hash = "sha256:9c40adce87eaa9ddb593ccb0fa6a07caf34015a29bf8d344811665b573138db9"}, - {file = "greenlet-3.2.4-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:3b67ca49f54cede0186854a008109d6ee71f66bd57bb36abd6d0a0267b540cdd"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ddf9164e7a5b08e9d22511526865780a576f19ddd00d62f8a665949327fde8bb"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f28588772bb5fb869a8eb331374ec06f24a83a9c25bfa1f38b6993afe9c1e968"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:5c9320971821a7cb77cfab8d956fa8e39cd07ca44b6070db358ceb7f8797c8c9"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c60a6d84229b271d44b70fb6e5fa23781abb5d742af7b808ae3f6efd7c9c60f6"}, - {file = "greenlet-3.2.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3b3812d8d0c9579967815af437d96623f45c0f2ae5f04e366de62a12d83a8fb0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:abbf57b5a870d30c4675928c37278493044d7c14378350b3aa5d484fa65575f0"}, - {file = "greenlet-3.2.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:20fb936b4652b6e307b8f347665e2c615540d4b42b3b4c8a321d8286da7e520f"}, - {file = "greenlet-3.2.4-cp312-cp312-win_amd64.whl", hash = "sha256:a7d4e128405eea3814a12cc2605e0e6aedb4035bf32697f72deca74de4105e02"}, - {file = "greenlet-3.2.4-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:1a921e542453fe531144e91e1feedf12e07351b1cf6c9e8a3325ea600a715a31"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd3c8e693bff0fff6ba55f140bf390fa92c994083f838fece0f63be121334945"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:710638eb93b1fa52823aa91bf75326f9ecdfd5e0466f00789246a5280f4ba0fc"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:c5111ccdc9c88f423426df3fd1811bfc40ed66264d35aa373420a34377efc98a"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d76383238584e9711e20ebe14db6c88ddcedc1829a9ad31a584389463b5aa504"}, - {file = "greenlet-3.2.4-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23768528f2911bcd7e475210822ffb5254ed10d71f4028387e5a99b4c6699671"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:00fadb3fedccc447f517ee0d3fd8fe49eae949e1cd0f6a611818f4f6fb7dc83b"}, - {file = "greenlet-3.2.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:d25c5091190f2dc0eaa3f950252122edbbadbb682aa7b1ef2f8af0f8c0afefae"}, - {file = "greenlet-3.2.4-cp313-cp313-win_amd64.whl", hash = "sha256:554b03b6e73aaabec3745364d6239e9e012d64c68ccd0b8430c64ccc14939a8b"}, - {file = "greenlet-3.2.4-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:49a30d5fda2507ae77be16479bdb62a660fa51b1eb4928b524975b3bde77b3c0"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:299fd615cd8fc86267b47597123e3f43ad79c9d8a22bebdce535e53550763e2f"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:c17b6b34111ea72fc5a4e4beec9711d2226285f0386ea83477cbb97c30a3f3a5"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b4a1870c51720687af7fa3e7cda6d08d801dae660f75a76f3845b642b4da6ee1"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:061dc4cf2c34852b052a8620d40f36324554bc192be474b9e9770e8c042fd735"}, - {file = "greenlet-3.2.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44358b9bf66c8576a9f57a590d5f5d6e72fa4228b763d0e43fee6d3b06d3a337"}, - {file = "greenlet-3.2.4-cp314-cp314-win_amd64.whl", hash = "sha256:e37ab26028f12dbb0ff65f29a8d3d44a765c61e729647bf2ddfbbed621726f01"}, - {file = "greenlet-3.2.4-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:b6a7c19cf0d2742d0809a4c05975db036fdff50cd294a93632d6a310bf9ac02c"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:27890167f55d2387576d1f41d9487ef171849ea0359ce1510ca6e06c8bece11d"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:18d9260df2b5fbf41ae5139e1be4e796d99655f023a636cd0e11e6406cca7d58"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:671df96c1f23c4a0d4077a325483c1503c96a1b7d9db26592ae770daa41233d4"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:16458c245a38991aa19676900d48bd1a6f2ce3e16595051a4db9d012154e8433"}, - {file = "greenlet-3.2.4-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9913f1a30e4526f432991f89ae263459b1c64d1608c0d22a5c79c287b3c70df"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b90654e092f928f110e0007f572007c9727b5265f7632c2fa7415b4689351594"}, - {file = "greenlet-3.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:81701fd84f26330f0d5f4944d4e92e61afe6319dcd9775e39396e39d7c3e5f98"}, - {file = "greenlet-3.2.4-cp39-cp39-win32.whl", hash = "sha256:65458b409c1ed459ea899e939f0e1cdb14f58dbc803f2f93c5eab5694d32671b"}, - {file = "greenlet-3.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:d2e685ade4dafd447ede19c31277a224a239a0a1a4eca4e6390efedf20260cfb"}, - {file = "greenlet-3.2.4.tar.gz", hash = "sha256:0dca0d95ff849f9a364385f36ab49f50065d76964944638be9691e1832e9f86d"}, + {file = "greenlet-3.3.0-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:6f8496d434d5cb2dce025773ba5597f71f5410ae499d5dd9533e0653258cdb3d"}, + {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b96dc7eef78fd404e022e165ec55327f935b9b52ff355b067eb4a0267fc1cffb"}, + {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:73631cd5cccbcfe63e3f9492aaa664d278fda0ce5c3d43aeda8e77317e38efbd"}, + {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b299a0cb979f5d7197442dccc3aee67fce53500cd88951b7e6c35575701c980b"}, + {file = "greenlet-3.3.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dee147740789a4632cace364816046e43310b59ff8fb79833ab043aefa72fd5"}, + {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:39b28e339fc3c348427560494e28d8a6f3561c8d2bcf7d706e1c624ed8d822b9"}, + {file = "greenlet-3.3.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b3c374782c2935cc63b2a27ba8708471de4ad1abaa862ffdb1ef45a643ddbb7d"}, + {file = "greenlet-3.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:b49e7ed51876b459bd645d83db257f0180e345d3f768a35a85437a24d5a49082"}, + {file = "greenlet-3.3.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e29f3018580e8412d6aaf5641bb7745d38c85228dacf51a73bd4e26ddf2a6a8e"}, + {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a687205fb22794e838f947e2194c0566d3812966b41c78709554aa883183fb62"}, + {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4243050a88ba61842186cb9e63c7dfa677ec146160b0efd73b855a3d9c7fcf32"}, + {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:670d0f94cd302d81796e37299bcd04b95d62403883b24225c6b5271466612f45"}, + {file = "greenlet-3.3.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cb3a8ec3db4a3b0eb8a3c25436c2d49e3505821802074969db017b87bc6a948"}, + {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2de5a0b09eab81fc6a382791b995b1ccf2b172a9fec934747a7a23d2ff291794"}, + {file = "greenlet-3.3.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4449a736606bd30f27f8e1ff4678ee193bc47f6ca810d705981cfffd6ce0d8c5"}, + {file = "greenlet-3.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:7652ee180d16d447a683c04e4c5f6441bae7ba7b17ffd9f6b3aff4605e9e6f71"}, + {file = "greenlet-3.3.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:b01548f6e0b9e9784a2c99c5651e5dc89ffcbe870bc5fb2e5ef864e9cc6b5dcb"}, + {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:349345b770dc88f81506c6861d22a6ccd422207829d2c854ae2af8025af303e3"}, + {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:e8e18ed6995e9e2c0b4ed264d2cf89260ab3ac7e13555b8032b25a74c6d18655"}, + {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c024b1e5696626890038e34f76140ed1daf858e37496d33f2af57f06189e70d7"}, + {file = "greenlet-3.3.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:047ab3df20ede6a57c35c14bf5200fcf04039d50f908270d3f9a7a82064f543b"}, + {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:2d9ad37fc657b1102ec880e637cccf20191581f75c64087a549e66c57e1ceb53"}, + {file = "greenlet-3.3.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83cd0e36932e0e7f36a64b732a6f60c2fc2df28c351bae79fbaf4f8092fe7614"}, + {file = "greenlet-3.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:a7a34b13d43a6b78abf828a6d0e87d3385680eaf830cd60d20d52f249faabf39"}, + {file = "greenlet-3.3.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:a1e41a81c7e2825822f4e068c48cb2196002362619e2d70b148f20a831c00739"}, + {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9f515a47d02da4d30caaa85b69474cec77b7929b2e936ff7fb853d42f4bf8808"}, + {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7d2d9fd66bfadf230b385fdc90426fcd6eb64db54b40c495b72ac0feb5766c54"}, + {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30a6e28487a790417d036088b3bcb3f3ac7d8babaa7d0139edbaddebf3af9492"}, + {file = "greenlet-3.3.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:087ea5e004437321508a8d6f20efc4cfec5e3c30118e1417ea96ed1d93950527"}, + {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ab97cf74045343f6c60a39913fa59710e4bd26a536ce7ab2397adf8b27e67c39"}, + {file = "greenlet-3.3.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5375d2e23184629112ca1ea89a53389dddbffcf417dad40125713d88eb5f96e8"}, + {file = "greenlet-3.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:9ee1942ea19550094033c35d25d20726e4f1c40d59545815e1128ac58d416d38"}, + {file = "greenlet-3.3.0-cp314-cp314-macosx_11_0_universal2.whl", hash = "sha256:60c2ef0f578afb3c8d92ea07ad327f9a062547137afe91f38408f08aacab667f"}, + {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a5d554d0712ba1de0a6c94c640f7aeba3f85b3a6e1f2899c11c2c0428da9365"}, + {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3a898b1e9c5f7307ebbde4102908e6cbfcb9ea16284a3abe15cab996bee8b9b3"}, + {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:dcd2bdbd444ff340e8d6bdf54d2f206ccddbb3ccfdcd3c25bf4afaa7b8f0cf45"}, + {file = "greenlet-3.3.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5773edda4dc00e173820722711d043799d3adb4f01731f40619e07ea2750b955"}, + {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ac0549373982b36d5fd5d30beb8a7a33ee541ff98d2b502714a09f1169f31b55"}, + {file = "greenlet-3.3.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d198d2d977460358c3b3a4dc844f875d1adb33817f0613f663a656f463764ccc"}, + {file = "greenlet-3.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:73f51dd0e0bdb596fb0417e475fa3c5e32d4c83638296e560086b8d7da7c4170"}, + {file = "greenlet-3.3.0-cp314-cp314t-macosx_11_0_universal2.whl", hash = "sha256:d6ed6f85fae6cdfdb9ce04c9bf7a08d666cfcfb914e7d006f44f840b46741931"}, + {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d9125050fcf24554e69c4cacb086b87b3b55dc395a8b3ebe6487b045b2614388"}, + {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:87e63ccfa13c0a0f6234ed0add552af24cc67dd886731f2261e46e241608bee3"}, + {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2662433acbca297c9153a4023fe2161c8dcfdcc91f10433171cf7e7d94ba2221"}, + {file = "greenlet-3.3.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c6e9b9c1527a78520357de498b0e709fb9e2f49c3a513afd5a249007261911b"}, + {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:286d093f95ec98fdd92fcb955003b8a3d054b4e2cab3e2707a5039e7b50520fd"}, + {file = "greenlet-3.3.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c10513330af5b8ae16f023e8ddbfb486ab355d04467c4679c5cfe4659975dd9"}, + {file = "greenlet-3.3.0.tar.gz", hash = "sha256:a82bb225a4e9e4d653dd2fb7b8b2d36e4fb25bc0165422a11e48b88e9e6f78fb"}, ] markers = {dev = "python_version == \"3.12\""} @@ -1731,114 +1766,114 @@ i18n = ["Babel (>=2.7)"] [[package]] name = "jiter" -version = "0.11.1" +version = "0.12.0" description = "Fast iterable JSON parser." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "jiter-0.11.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:ed58841a491bbbf3f7c55a6b68fff568439ab73b2cce27ace0e169057b5851df"}, - {file = "jiter-0.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:499beb9b2d7e51d61095a8de39ebcab1d1778f2a74085f8305a969f6cee9f3e4"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b87b2821795e28cc990939b68ce7a038edea680a24910bd68a79d54ff3f03c02"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:83f6fa494d8bba14ab100417c80e70d32d737e805cb85be2052d771c76fcd1f8"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fbc6aea1daa2ec6f5ed465f0c5e7b0607175062ceebbea5ca70dd5ddab58083"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:302288e2edc43174bb2db838e94688d724f9aad26c5fb9a74f7a5fb427452a6a"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85db563fe3b367bb568af5d29dea4d4066d923b8e01f3417d25ebecd958de815"}, - {file = "jiter-0.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f1c1ba2b6b22f775444ef53bc2d5778396d3520abc7b2e1da8eb0c27cb3ffb10"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:523be464b14f8fd0cc78da6964b87b5515a056427a2579f9085ce30197a1b54a"}, - {file = "jiter-0.11.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:25b99b3f04cd2a38fefb22e822e35eb203a2cd37d680dbbc0c0ba966918af336"}, - {file = "jiter-0.11.1-cp310-cp310-win32.whl", hash = "sha256:47a79e90545a596bb9104109777894033347b11180d4751a216afef14072dbe7"}, - {file = "jiter-0.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:cace75621ae9bd66878bf69fbd4dfc1a28ef8661e0c2d0eb72d3d6f1268eddf5"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9b0088ff3c374ce8ce0168523ec8e97122ebb788f950cf7bb8e39c7dc6a876a2"}, - {file = "jiter-0.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74433962dd3c3090655e02e461267095d6c84f0741c7827de11022ef8d7ff661"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6d98030e345e6546df2cc2c08309c502466c66c4747b043f1a0d415fada862b8"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1d6db0b2e788db46bec2cf729a88b6dd36959af2abd9fa2312dfba5acdd96dcb"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:55678fbbda261eafe7289165dd2ddd0e922df5f9a1ae46d7c79a5a15242bd7d1"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a6b74fae8e40497653b52ce6ca0f1b13457af769af6fb9c1113efc8b5b4d9be"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a55a453f8b035eb4f7852a79a065d616b7971a17f5e37a9296b4b38d3b619e4"}, - {file = "jiter-0.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2638148099022e6bdb3f42904289cd2e403609356fb06eb36ddec2d50958bc29"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:252490567a5d990986f83b95a5f1ca1bf205ebd27b3e9e93bb7c2592380e29b9"}, - {file = "jiter-0.11.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d431d52b0ca2436eea6195f0f48528202100c7deda354cb7aac0a302167594d5"}, - {file = "jiter-0.11.1-cp311-cp311-win32.whl", hash = "sha256:db6f41e40f8bae20c86cb574b48c4fd9f28ee1c71cb044e9ec12e78ab757ba3a"}, - {file = "jiter-0.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:0cc407b8e6cdff01b06bb80f61225c8b090c3df108ebade5e0c3c10993735b19"}, - {file = "jiter-0.11.1-cp311-cp311-win_arm64.whl", hash = "sha256:fe04ea475392a91896d1936367854d346724a1045a247e5d1c196410473b8869"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c92148eec91052538ce6823dfca9525f5cfc8b622d7f07e9891a280f61b8c96c"}, - {file = "jiter-0.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ecd4da91b5415f183a6be8f7158d127bdd9e6a3174138293c0d48d6ea2f2009d"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7e3ac25c00b9275684d47aa42febaa90a9958e19fd1726c4ecf755fbe5e553b"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:57d7305c0a841858f866cd459cd9303f73883fb5e097257f3d4a3920722c69d4"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e86fa10e117dce22c547f31dd6d2a9a222707d54853d8de4e9a2279d2c97f239"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae5ef1d48aec7e01ee8420155d901bb1d192998fa811a65ebb82c043ee186711"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb68e7bf65c990531ad8715e57d50195daf7c8e6f1509e617b4e692af1108939"}, - {file = "jiter-0.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:43b30c8154ded5845fa454ef954ee67bfccce629b2dea7d01f795b42bc2bda54"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:586cafbd9dd1f3ce6a22b4a085eaa6be578e47ba9b18e198d4333e598a91db2d"}, - {file = "jiter-0.11.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:677cc2517d437a83bb30019fd4cf7cad74b465914c56ecac3440d597ac135250"}, - {file = "jiter-0.11.1-cp312-cp312-win32.whl", hash = "sha256:fa992af648fcee2b850a3286a35f62bbbaeddbb6dbda19a00d8fbc846a947b6e"}, - {file = "jiter-0.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:88b5cae9fa51efeb3d4bd4e52bfd4c85ccc9cac44282e2a9640893a042ba4d87"}, - {file = "jiter-0.11.1-cp312-cp312-win_arm64.whl", hash = "sha256:9a6cae1ab335551917f882f2c3c1efe7617b71b4c02381e4382a8fc80a02588c"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:71b6a920a5550f057d49d0e8bcc60945a8da998019e83f01adf110e226267663"}, - {file = "jiter-0.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b3de72e925388453a5171be83379549300db01284f04d2a6f244d1d8de36f94"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc19dd65a2bd3d9c044c5b4ebf657ca1e6003a97c0fc10f555aa4f7fb9821c00"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d58faaa936743cd1464540562f60b7ce4fd927e695e8bc31b3da5b914baa9abd"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:902640c3103625317291cb73773413b4d71847cdf9383ba65528745ff89f1d14"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:30405f726e4c2ed487b176c09f8b877a957f535d60c1bf194abb8dadedb5836f"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3217f61728b0baadd2551844870f65219ac4a1285d5e1a4abddff3d51fdabe96"}, - {file = "jiter-0.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b1364cc90c03a8196f35f396f84029f12abe925415049204446db86598c8b72c"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:53a54bf8e873820ab186b2dca9f6c3303f00d65ae5e7b7d6bda1b95aa472d646"}, - {file = "jiter-0.11.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:7e29aca023627b0e0c2392d4248f6414d566ff3974fa08ff2ac8dbb96dfee92a"}, - {file = "jiter-0.11.1-cp313-cp313-win32.whl", hash = "sha256:f153e31d8bca11363751e875c0a70b3d25160ecbaee7b51e457f14498fb39d8b"}, - {file = "jiter-0.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:f773f84080b667c69c4ea0403fc67bb08b07e2b7ce1ef335dea5868451e60fed"}, - {file = "jiter-0.11.1-cp313-cp313-win_arm64.whl", hash = "sha256:635ecd45c04e4c340d2187bcb1cea204c7cc9d32c1364d251564bf42e0e39c2d"}, - {file = "jiter-0.11.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d892b184da4d94d94ddb4031296931c74ec8b325513a541ebfd6dfb9ae89904b"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aa22c223a3041dacb2fcd37c70dfd648b44662b4a48e242592f95bda5ab09d58"}, - {file = "jiter-0.11.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:330e8e6a11ad4980cd66a0f4a3e0e2e0f646c911ce047014f984841924729789"}, - {file = "jiter-0.11.1-cp313-cp313t-win_amd64.whl", hash = "sha256:09e2e386ebf298547ca3a3704b729471f7ec666c2906c5c26c1a915ea24741ec"}, - {file = "jiter-0.11.1-cp313-cp313t-win_arm64.whl", hash = "sha256:fe4a431c291157e11cee7c34627990ea75e8d153894365a3bc84b7a959d23ca8"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:0fa1f70da7a8a9713ff8e5f75ec3f90c0c870be6d526aa95e7c906f6a1c8c676"}, - {file = "jiter-0.11.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:569ee559e5046a42feb6828c55307cf20fe43308e3ae0d8e9e4f8d8634d99944"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f69955fa1d92e81987f092b233f0be49d4c937da107b7f7dcf56306f1d3fcce9"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:090f4c9d4a825e0fcbd0a2647c9a88a0f366b75654d982d95a9590745ff0c48d"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bbf3d8cedf9e9d825233e0dcac28ff15c47b7c5512fdfe2e25fd5bbb6e6b0cee"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa9b1958f9c30d3d1a558b75f0626733c60eb9b7774a86b34d88060be1e67fe"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e42d1ca16590b768c5e7d723055acd2633908baacb3628dd430842e2e035aa90"}, - {file = "jiter-0.11.1-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5db4c2486a023820b701a17aec9c5a6173c5ba4393f26662f032f2de9c848b0f"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:4573b78777ccfac954859a6eff45cbd9d281d80c8af049d0f1a3d9fc323d5c3a"}, - {file = "jiter-0.11.1-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:7593ac6f40831d7961cb67633c39b9fef6689a211d7919e958f45710504f52d3"}, - {file = "jiter-0.11.1-cp314-cp314-win32.whl", hash = "sha256:87202ec6ff9626ff5f9351507def98fcf0df60e9a146308e8ab221432228f4ea"}, - {file = "jiter-0.11.1-cp314-cp314-win_amd64.whl", hash = "sha256:a5dd268f6531a182c89d0dd9a3f8848e86e92dfff4201b77a18e6b98aa59798c"}, - {file = "jiter-0.11.1-cp314-cp314-win_arm64.whl", hash = "sha256:5d761f863f912a44748a21b5c4979c04252588ded8d1d2760976d2e42cd8d991"}, - {file = "jiter-0.11.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2cc5a3965285ddc33e0cab933e96b640bc9ba5940cea27ebbbf6695e72d6511c"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b572b3636a784c2768b2342f36a23078c8d3aa6d8a30745398b1bab58a6f1a8"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad93e3d67a981f96596d65d2298fe8d1aa649deb5374a2fb6a434410ee11915e"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83097ce379e202dcc3fe3fc71a16d523d1ee9192c8e4e854158f96b3efe3f2f"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7042c51e7fbeca65631eb0c332f90c0c082eab04334e7ccc28a8588e8e2804d9"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a68d679c0e47649a61df591660507608adc2652442de7ec8276538ac46abe08"}, - {file = "jiter-0.11.1-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a1b0da75dbf4b6ec0b3c9e604d1ee8beaf15bc046fff7180f7d89e3cdbd3bb51"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:69dd514bf0fa31c62147d6002e5ca2b3e7ef5894f5ac6f0a19752385f4e89437"}, - {file = "jiter-0.11.1-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:bb31ac0b339efa24c0ca606febd8b77ef11c58d09af1b5f2be4c99e907b11111"}, - {file = "jiter-0.11.1-cp314-cp314t-win32.whl", hash = "sha256:b2ce0d6156a1d3ad41da3eec63b17e03e296b78b0e0da660876fccfada86d2f7"}, - {file = "jiter-0.11.1-cp314-cp314t-win_amd64.whl", hash = "sha256:f4db07d127b54c4a2d43b4cf05ff0193e4f73e0dd90c74037e16df0b29f666e1"}, - {file = "jiter-0.11.1-cp314-cp314t-win_arm64.whl", hash = "sha256:28e4fdf2d7ebfc935523e50d1efa3970043cfaa161674fe66f9642409d001dfe"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:baa99c8db49467527658bb479857344daf0a14dff909b7f6714579ac439d1253"}, - {file = "jiter-0.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:860fe55fa3b01ad0edf2adde1098247ff5c303d0121f9ce028c03d4f88c69502"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:173dd349d99b6feaf5a25a6fbcaf3489a6f947708d808240587a23df711c67db"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:14ac1dca837514cc946a6ac2c4995d9695303ecc754af70a3163d057d1a444ab"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:69af47de5f93a231d5b85f7372d3284a5be8edb4cc758f006ec5a1406965ac5e"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:685f8b3abd3bbd3e06e4dfe2429ff87fd5d7a782701151af99b1fcbd80e31b2b"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d04afa2d4e5526e54ae8a58feea953b1844bf6e3526bc589f9de68e86d0ea01"}, - {file = "jiter-0.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1e92b927259035b50d8e11a8fdfe0ebd014d883e4552d37881643fa289a4bcf1"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e7bd8be4fad8d4c5558b7801770cd2da6c072919c6f247cc5336edb143f25304"}, - {file = "jiter-0.11.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:121381a77a3c85987f3eba0d30ceaca9116f7463bedeec2fa79b2e7286b89b60"}, - {file = "jiter-0.11.1-cp39-cp39-win32.whl", hash = "sha256:160225407f6dfabdf9be1b44e22f06bc293a78a28ffa4347054698bd712dad06"}, - {file = "jiter-0.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:028e0d59bcdfa1079f8df886cdaefc6f515c27a5288dec956999260c7e4a7cfd"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:e642b5270e61dd02265866398707f90e365b5db2eb65a4f30c789d826682e1f6"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:464ba6d000585e4e2fd1e891f31f1231f497273414f5019e27c00a4b8f7a24ad"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:055568693ab35e0bf3a171b03bb40b2dcb10352359e0ab9b5ed0da2bf1eb6f6f"}, - {file = "jiter-0.11.1-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0c69ea798d08a915ba4478113efa9e694971e410056392f4526d796f136d3fa"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:0d4d6993edc83cf75e8c6828a8d6ce40a09ee87e38c7bfba6924f39e1337e21d"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f78d151c83a87a6cf5461d5ee55bc730dd9ae227377ac6f115b922989b95f838"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9022974781155cd5521d5cb10997a03ee5e31e8454c9d999dcdccd253f2353f"}, - {file = "jiter-0.11.1-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18c77aaa9117510d5bdc6a946baf21b1f0cfa58ef04d31c8d016f206f2118960"}, - {file = "jiter-0.11.1.tar.gz", hash = "sha256:849dcfc76481c0ea0099391235b7ca97d7279e0fa4c86005457ac7c88e8b76dc"}, + {file = "jiter-0.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:e7acbaba9703d5de82a2c98ae6a0f59ab9770ab5af5fa35e43a303aee962cf65"}, + {file = "jiter-0.12.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:364f1a7294c91281260364222f535bc427f56d4de1d8ffd718162d21fbbd602e"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85ee4d25805d4fb23f0a5167a962ef8e002dbfb29c0989378488e32cf2744b62"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:796f466b7942107eb889c08433b6e31b9a7ed31daceaecf8af1be26fb26c0ca8"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:35506cb71f47dba416694e67af996bbdefb8e3608f1f78799c2e1f9058b01ceb"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:726c764a90c9218ec9e4f99a33d6bf5ec169163f2ca0fc21b654e88c2abc0abc"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa47810c5565274810b726b0dc86d18dce5fd17b190ebdc3890851d7b2a0e74"}, + {file = "jiter-0.12.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ec0259d3f26c62aed4d73b198c53e316ae11f0f69c8fbe6682c6dcfa0fcce2"}, + {file = "jiter-0.12.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:79307d74ea83465b0152fa23e5e297149506435535282f979f18b9033c0bb025"}, + {file = "jiter-0.12.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cf6e6dd18927121fec86739f1a8906944703941d000f0639f3eb6281cc601dca"}, + {file = "jiter-0.12.0-cp310-cp310-win32.whl", hash = "sha256:b6ae2aec8217327d872cbfb2c1694489057b9433afce447955763e6ab015b4c4"}, + {file = "jiter-0.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:c7f49ce90a71e44f7e1aa9e7ec415b9686bbc6a5961e57eab511015e6759bc11"}, + {file = "jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9"}, + {file = "jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6"}, + {file = "jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725"}, + {file = "jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6"}, + {file = "jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e"}, + {file = "jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c"}, + {file = "jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f"}, + {file = "jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5"}, + {file = "jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37"}, + {file = "jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403"}, + {file = "jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126"}, + {file = "jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9"}, + {file = "jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86"}, + {file = "jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44"}, + {file = "jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb"}, + {file = "jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789"}, + {file = "jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e"}, + {file = "jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed"}, + {file = "jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9"}, + {file = "jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626"}, + {file = "jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c"}, + {file = "jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de"}, + {file = "jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a"}, + {file = "jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60"}, + {file = "jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6"}, + {file = "jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4"}, + {file = "jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb"}, + {file = "jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7"}, + {file = "jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3"}, + {file = "jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525"}, + {file = "jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a"}, + {file = "jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a"}, + {file = "jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67"}, + {file = "jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b"}, + {file = "jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42"}, + {file = "jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf"}, + {file = "jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451"}, + {file = "jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f"}, + {file = "jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783"}, + {file = "jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b"}, + {file = "jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6"}, + {file = "jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183"}, + {file = "jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873"}, + {file = "jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473"}, + {file = "jiter-0.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c9d28b218d5f9e5f69a0787a196322a5056540cb378cac8ff542b4fa7219966c"}, + {file = "jiter-0.12.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0ee12028daf8cfcf880dd492349a122a64f42c059b6c62a2b0c96a83a8da820"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b135ebe757a82d67ed2821526e72d0acf87dd61f6013e20d3c45b8048af927b"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:15d7fafb81af8a9e3039fc305529a61cd933eecee33b4251878a1c89859552a3"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92d1f41211d8a8fe412faad962d424d334764c01dac6691c44691c2e4d3eedaf"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a64a48d7c917b8f32f25c176df8749ecf08cec17c466114727efe7441e17f6d"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122046f3b3710b85de99d9aa2f3f0492a8233a2f54a64902b096efc27ea747b5"}, + {file = "jiter-0.12.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:27ec39225e03c32c6b863ba879deb427882f243ae46f0d82d68b695fa5b48b40"}, + {file = "jiter-0.12.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:26b9e155ddc132225a39b1995b3b9f0fe0f79a6d5cbbeacf103271e7d309b404"}, + {file = "jiter-0.12.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ab05b7c58e29bb9e60b70c2e0094c98df79a1e42e397b9bb6eaa989b7a66dd0"}, + {file = "jiter-0.12.0-cp39-cp39-win32.whl", hash = "sha256:59f9f9df87ed499136db1c2b6c9efb902f964bed42a582ab7af413b6a293e7b0"}, + {file = "jiter-0.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:d3719596a1ebe7a48a498e8d5d0c4bf7553321d4c3eee1d620628d51351a3928"}, + {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8"}, + {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3"}, + {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e"}, + {file = "jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d"}, + {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb"}, + {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b"}, + {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f"}, + {file = "jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c"}, + {file = "jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b"}, ] [[package]] @@ -1855,14 +1890,14 @@ files = [ [[package]] name = "json-schema-to-pydantic" -version = "0.4.6" +version = "0.4.7" description = "A Python library for automatically generating Pydantic v2 models from JSON Schema definitions" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "json_schema_to_pydantic-0.4.6-py3-none-any.whl", hash = "sha256:e27faa9b886d3a55a9dfb32f9273efadd15d7400ccea26c82eefe1f3c5c4c944"}, - {file = "json_schema_to_pydantic-0.4.6.tar.gz", hash = "sha256:5cbca5b02cb52435f5698981dc050acaf684a757434ec4393b81a402a4a9a08a"}, + {file = "json_schema_to_pydantic-0.4.7-py3-none-any.whl", hash = "sha256:e329506e42b63f9a0ae0a17f7082ec7fc7cb3326138ec5c24904e0af5a660e63"}, + {file = "json_schema_to_pydantic-0.4.7.tar.gz", hash = "sha256:a6384825fee7609715641a1e5095ddf41aa3bdd06cd91d4a6b6d88a3dfcad920"}, ] [package.dependencies] @@ -1935,20 +1970,36 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "jwcrypto" +version = "1.5.6" +description = "Implementation of JOSE Web standards" +optional = false +python-versions = ">= 3.8" +groups = ["main"] +files = [ + {file = "jwcrypto-1.5.6-py3-none-any.whl", hash = "sha256:150d2b0ebbdb8f40b77f543fb44ffd2baeff48788be71f67f03566692fd55789"}, + {file = "jwcrypto-1.5.6.tar.gz", hash = "sha256:771a87762a0c081ae6166958a954f80848820b2ab066937dc8b8379d65b1b039"}, +] + +[package.dependencies] +cryptography = ">=3.4" +typing-extensions = ">=4.5.0" + [[package]] name = "langchain" -version = "1.1.0" +version = "1.2.0" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain-1.1.0-py3-none-any.whl", hash = "sha256:af080f3a4a779bfa5925de7aacb6dfab83249d4aab9a08f7aa7b9bec3766d8ea"}, - {file = "langchain-1.1.0.tar.gz", hash = "sha256:583c892f59873c0329dbe04169fb3234ac794c50780e7c6fb62a61c7b86a981b"}, + {file = "langchain-1.2.0-py3-none-any.whl", hash = "sha256:82f0d17aa4fbb11560b30e1e7d4aeb75e3ad71ce09b85c90ab208b181a24ffac"}, + {file = "langchain-1.2.0.tar.gz", hash = "sha256:a087d1e2b2969819e29a91a6d5f98302aafe31bd49ba377ecee3bf5a5dcfe14a"}, ] [package.dependencies] -langchain-core = ">=1.1.0,<2.0.0" +langchain-core = ">=1.2.1,<2.0.0" langgraph = ">=1.0.2,<1.1.0" pydantic = ">=2.7.4,<3.0.0" @@ -2058,14 +2109,14 @@ tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" [[package]] name = "langchain-core" -version = "1.1.0" +version = "1.2.3" description = "Building applications with LLMs through composability" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_core-1.1.0-py3-none-any.whl", hash = "sha256:2c9f27dadc6d21ed4aa46506a37a56e6a7e2d2f9141922dc5c251ba921822ee6"}, - {file = "langchain_core-1.1.0.tar.gz", hash = "sha256:2b76a82d427922c8bc51c08404af4fc2a29e9f161dfe2297cb05091e810201e7"}, + {file = "langchain_core-1.2.3-py3-none-any.whl", hash = "sha256:c3501cf0219daf67a0ae23f6d6bdf3b41ab695efd8f0f3070a566e368b8c3dc7"}, + {file = "langchain_core-1.2.3.tar.gz", hash = "sha256:61f5197aa101cd5605879ef37f2b0ac56c079974d94d347849b8d4fe18949746"}, ] [package.dependencies] @@ -2076,6 +2127,7 @@ pydantic = ">=2.7.4,<3.0.0" pyyaml = ">=5.3.0,<7.0.0" tenacity = ">=8.1.0,<8.4.0 || >8.4.0,<10.0.0" typing-extensions = ">=4.7.0,<5.0.0" +uuid-utils = ">=0.12.0,<1.0" [[package]] name = "langchain-google-community" @@ -2132,14 +2184,14 @@ pydantic = ">=2.0.0,<3.0.0" [[package]] name = "langchain-mcp-adapters" -version = "0.1.12" +version = "0.1.14" description = "Make Anthropic Model Context Protocol (MCP) tools compatible with LangChain and LangGraph agents." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langchain_mcp_adapters-0.1.12-py3-none-any.whl", hash = "sha256:ea9894ed8baf83dce485cf4fb64d7afb74c34b95e2e627fde6bf307eaa49ea1c"}, - {file = "langchain_mcp_adapters-0.1.12.tar.gz", hash = "sha256:0c7baa974278e44148b36fe6cb04173e9bf3c540619017a8f1bb602f90a24c1f"}, + {file = "langchain_mcp_adapters-0.1.14-py3-none-any.whl", hash = "sha256:b900d37d1a82261e408e663b7176a1a74cf0072e17ae9e4241c068093912394a"}, + {file = "langchain_mcp_adapters-0.1.14.tar.gz", hash = "sha256:36f8131d0b3b5ca28df03440be4dc2a3636e2f73e3e4a0a266d606ffaa12adda"}, ] [package.dependencies] @@ -2149,18 +2201,18 @@ typing-extensions = ">=4.14.0" [[package]] name = "langchain-openai" -version = "1.1.0" +version = "1.1.6" description = "An integration package connecting OpenAI and LangChain" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_openai-1.1.0-py3-none-any.whl", hash = "sha256:243bb345d0260ea1326c2b6ac2237ec29f082ab457c59e9306bac349df4577e8"}, - {file = "langchain_openai-1.1.0.tar.gz", hash = "sha256:9a33280c2e8315d013d64e6b15e583be347beb0d0f281755c335ae504ad0c184"}, + {file = "langchain_openai-1.1.6-py3-none-any.whl", hash = "sha256:c42d04a67a85cee1d994afe400800d2b09ebf714721345f0b651eb06a02c3948"}, + {file = "langchain_openai-1.1.6.tar.gz", hash = "sha256:e306612654330ae36fb6bbe36db91c98534312afade19e140c3061fe4208dac8"}, ] [package.dependencies] -langchain-core = ">=1.1.0,<2.0.0" +langchain-core = ">=1.2.2,<2.0.0" openai = ">=1.109.1,<3.0.0" tiktoken = ">=0.7.0,<1.0.0" @@ -2187,14 +2239,14 @@ sqlalchemy = {version = ">=2,<3", extras = ["asyncio"]} [[package]] name = "langchain-tavily" -version = "0.2.12" +version = "0.2.15" description = "An integration package connecting Tavily and LangChain" optional = false python-versions = "<4.0,>=3.10" groups = ["main"] files = [ - {file = "langchain_tavily-0.2.12-py3-none-any.whl", hash = "sha256:d243a53f60048a18b55f5a7efd5e2dcdde6a9dbc9f84c45e5f349210303e95a3"}, - {file = "langchain_tavily-0.2.12.tar.gz", hash = "sha256:e73ef2149b7a4e751fce75c47003d04628222b12d073dae4af2dc7f58d589e30"}, + {file = "langchain_tavily-0.2.15-py3-none-any.whl", hash = "sha256:e7eb0dd956cad3cfb44f2bac5eddeb150845d7a2dbf369a2e5698144e79cdb14"}, + {file = "langchain_tavily-0.2.15.tar.gz", hash = "sha256:4c3fefe7789d460c9724c68ac631e446e2a0d98a20285add81e7d810f5301397"}, ] [package.dependencies] @@ -2205,18 +2257,18 @@ requests = ">=2.32.3,<3.0.0" [[package]] name = "langchain-text-splitters" -version = "1.0.0" +version = "1.1.0" description = "LangChain text splitting utilities" optional = false python-versions = "<4.0.0,>=3.10.0" groups = ["main"] files = [ - {file = "langchain_text_splitters-1.0.0-py3-none-any.whl", hash = "sha256:f00c8219d3468f2c5bd951b708b6a7dd9bc3c62d0cfb83124c377f7170f33b2e"}, - {file = "langchain_text_splitters-1.0.0.tar.gz", hash = "sha256:d8580a20ad7ed10b432feb273e5758b2cc0902d094919629cec0e1ad691a6744"}, + {file = "langchain_text_splitters-1.1.0-py3-none-any.whl", hash = "sha256:f00341fe883358786104a5f881375ac830a4dd40253ecd42b4c10536c6e4693f"}, + {file = "langchain_text_splitters-1.1.0.tar.gz", hash = "sha256:75e58acb7585dc9508f3cd9d9809cb14751283226c2d6e21fb3a9ae57582ca22"}, ] [package.dependencies] -langchain-core = ">=1.0.0,<2.0.0" +langchain-core = ">=1.2.0,<2.0.0" [[package]] name = "langgraph" @@ -2240,19 +2292,19 @@ xxhash = ">=3.5.0" [[package]] name = "langgraph-checkpoint" -version = "2.1.2" +version = "3.0.1" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langgraph_checkpoint-2.1.2-py3-none-any.whl", hash = "sha256:911ebffb069fd01775d4b5184c04aaafc2962fcdf50cf49d524cd4367c4d0c60"}, - {file = "langgraph_checkpoint-2.1.2.tar.gz", hash = "sha256:112e9d067a6eff8937caf198421b1ffba8d9207193f14ac6f89930c1260c06f9"}, + {file = "langgraph_checkpoint-3.0.1-py3-none-any.whl", hash = "sha256:9b04a8d0edc0474ce4eaf30c5d731cee38f11ddff50a6177eead95b5c4e4220b"}, + {file = "langgraph_checkpoint-3.0.1.tar.gz", hash = "sha256:59222f875f85186a22c494aedc65c4e985a3df27e696e5016ba0b98a5ed2cee0"}, ] [package.dependencies] langchain-core = ">=0.2.38" -ormsgpack = ">=1.10.0" +ormsgpack = ">=1.12.0" [[package]] name = "langgraph-prebuilt" @@ -2272,14 +2324,14 @@ langgraph-checkpoint = ">=2.1.0,<4.0.0" [[package]] name = "langgraph-sdk" -version = "0.3.6" +version = "0.3.1" description = "SDK for interacting with LangGraph API" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langgraph_sdk-0.3.6-py3-none-any.whl", hash = "sha256:7df2fd552ad7262d0baf8e1f849dce1d62186e76dcdd36db9dc5bdfa5c3fc20f"}, - {file = "langgraph_sdk-0.3.6.tar.gz", hash = "sha256:7650f607f89c1586db5bee391b1a8754cbe1fc83b721ff2f1450f8906e790bd7"}, + {file = "langgraph_sdk-0.3.1-py3-none-any.whl", hash = "sha256:0b856923bfd20bf3441ce9d03bef488aa333fb610e972618799a9d584436acad"}, + {file = "langgraph_sdk-0.3.1.tar.gz", hash = "sha256:f6dadfd2444eeff3e01405a9005c95fb3a028d4bd954ebec80ea6150084f92bb"}, ] [package.dependencies] @@ -2288,14 +2340,14 @@ orjson = ">=3.10.1" [[package]] name = "langsmith" -version = "0.4.38" +version = "0.5.0" description = "Client library to connect to the LangSmith Observability and Evaluation Platform." optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "langsmith-0.4.38-py3-none-any.whl", hash = "sha256:326232a24b1c6dd308a3188557cc023adf8fb14144263b2982c115a6be5141e7"}, - {file = "langsmith-0.4.38.tar.gz", hash = "sha256:3aa57f9c16a5880256cd1eab0452533c1fb5ee14ec5250e23ed919cc2b07f6d3"}, + {file = "langsmith-0.5.0-py3-none-any.whl", hash = "sha256:a83750cb3dccb33148d4ffe005e3e03080fad13e01671efbb74c9a68813bfef8"}, + {file = "langsmith-0.5.0.tar.gz", hash = "sha256:5cadf1ddd30e838cf61679f4a776aaef638d4b02ffbceba9f73283caebd39e1b"}, ] [package.dependencies] @@ -2305,6 +2357,7 @@ packaging = ">=23.2" pydantic = ">=1,<3" requests = ">=2.0.0" requests-toolbelt = ">=1.0.0" +uuid-utils = ">=0.12.0,<1.0" zstandard = ">=0.23.0" [package.extras] @@ -2705,98 +2758,98 @@ files = [ [[package]] name = "numpy" -version = "2.3.4" +version = "2.3.5" description = "Fundamental package for array computing in Python" optional = false python-versions = ">=3.11" groups = ["main"] files = [ - {file = "numpy-2.3.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:e78aecd2800b32e8347ce49316d3eaf04aed849cd5b38e0af39f829a4e59f5eb"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fd09cc5d65bda1e79432859c40978010622112e9194e581e3415a3eccc7f43f"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:1b219560ae2c1de48ead517d085bc2d05b9433f8e49d0955c82e8cd37bd7bf36"}, - {file = "numpy-2.3.4-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:bafa7d87d4c99752d07815ed7a2c0964f8ab311eb8168f41b910bd01d15b6032"}, - {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:36dc13af226aeab72b7abad501d370d606326a0029b9f435eacb3b8c94b8a8b7"}, - {file = "numpy-2.3.4-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a7b2f9a18b5ff9824a6af80de4f37f4ec3c2aab05ef08f51c77a093f5b89adda"}, - {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:9984bd645a8db6ca15d850ff996856d8762c51a2239225288f08f9050ca240a0"}, - {file = "numpy-2.3.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:64c5825affc76942973a70acf438a8ab618dbd692b84cd5ec40a0a0509edc09a"}, - {file = "numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1"}, - {file = "numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996"}, - {file = "numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ef1b5a3e808bc40827b5fa2c8196151a4c5abe110e1726949d7abddfe5c7ae11"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2f91f496a87235c6aaf6d3f3d89b17dba64996abadccb289f48456cff931ca9"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:f77e5b3d3da652b474cc80a14084927a5e86a5eccf54ca8ca5cbd697bf7f2667"}, - {file = "numpy-2.3.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:8ab1c5f5ee40d6e01cbe96de5863e39b215a4d24e7d007cad56c7184fdf4aeef"}, - {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:77b84453f3adcb994ddbd0d1c5d11db2d6bda1a2b7fd5ac5bd4649d6f5dc682e"}, - {file = "numpy-2.3.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4121c5beb58a7f9e6dfdee612cb24f4df5cd4db6e8261d7f4d7450a997a65d6a"}, - {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:65611ecbb00ac9846efe04db15cbe6186f562f6bb7e5e05f077e53a599225d16"}, - {file = "numpy-2.3.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:dabc42f9c6577bcc13001b8810d300fe814b4cfbe8a92c873f269484594f9786"}, - {file = "numpy-2.3.4-cp312-cp312-win32.whl", hash = "sha256:a49d797192a8d950ca59ee2d0337a4d804f713bb5c3c50e8db26d49666e351dc"}, - {file = "numpy-2.3.4-cp312-cp312-win_amd64.whl", hash = "sha256:985f1e46358f06c2a09921e8921e2c98168ed4ae12ccd6e5e87a4f1857923f32"}, - {file = "numpy-2.3.4-cp312-cp312-win_arm64.whl", hash = "sha256:4635239814149e06e2cb9db3dd584b2fa64316c96f10656983b8026a82e6e4db"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c090d4860032b857d94144d1a9976b8e36709e40386db289aaf6672de2a81966"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a13fc473b6db0be619e45f11f9e81260f7302f8d180c49a22b6e6120022596b3"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:3634093d0b428e6c32c3a69b78e554f0cd20ee420dcad5a9f3b2a63762ce4197"}, - {file = "numpy-2.3.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:043885b4f7e6e232d7df4f51ffdef8c36320ee9d5f227b380ea636722c7ed12e"}, - {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4ee6a571d1e4f0ea6d5f22d6e5fbd6ed1dc2b18542848e1e7301bd190500c9d7"}, - {file = "numpy-2.3.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fc8a63918b04b8571789688b2780ab2b4a33ab44bfe8ccea36d3eba51228c953"}, - {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:40cc556d5abbc54aabe2b1ae287042d7bdb80c08edede19f0c0afb36ae586f37"}, - {file = "numpy-2.3.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ecb63014bb7f4ce653f8be7f1df8cbc6093a5a2811211770f6606cc92b5a78fd"}, - {file = "numpy-2.3.4-cp313-cp313-win32.whl", hash = "sha256:e8370eb6925bb8c1c4264fec52b0384b44f675f191df91cbe0140ec9f0955646"}, - {file = "numpy-2.3.4-cp313-cp313-win_amd64.whl", hash = "sha256:56209416e81a7893036eea03abcb91c130643eb14233b2515c90dcac963fe99d"}, - {file = "numpy-2.3.4-cp313-cp313-win_arm64.whl", hash = "sha256:a700a4031bc0fd6936e78a752eefb79092cecad2599ea9c8039c548bc097f9bc"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:86966db35c4040fdca64f0816a1c1dd8dbd027d90fca5a57e00e1ca4cd41b879"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:838f045478638b26c375ee96ea89464d38428c69170360b23a1a50fa4baa3562"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:d7315ed1dab0286adca467377c8381cd748f3dc92235f22a7dfc42745644a96a"}, - {file = "numpy-2.3.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:84f01a4d18b2cc4ade1814a08e5f3c907b079c847051d720fad15ce37aa930b6"}, - {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:817e719a868f0dacde4abdfc5c1910b301877970195db9ab6a5e2c4bd5b121f7"}, - {file = "numpy-2.3.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85e071da78d92a214212cacea81c6da557cab307f2c34b5f85b628e94803f9c0"}, - {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:2ec646892819370cf3558f518797f16597b4e4669894a2ba712caccc9da53f1f"}, - {file = "numpy-2.3.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:035796aaaddfe2f9664b9a9372f089cfc88bd795a67bd1bfe15e6e770934cf64"}, - {file = "numpy-2.3.4-cp313-cp313t-win32.whl", hash = "sha256:fea80f4f4cf83b54c3a051f2f727870ee51e22f0248d3114b8e755d160b38cfb"}, - {file = "numpy-2.3.4-cp313-cp313t-win_amd64.whl", hash = "sha256:15eea9f306b98e0be91eb344a94c0e630689ef302e10c2ce5f7e11905c704f9c"}, - {file = "numpy-2.3.4-cp313-cp313t-win_arm64.whl", hash = "sha256:b6c231c9c2fadbae4011ca5e7e83e12dc4a5072f1a1d85a0a7b3ed754d145a40"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:81c3e6d8c97295a7360d367f9f8553973651b76907988bb6066376bc2252f24e"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7c26b0b2bf58009ed1f38a641f3db4be8d960a417ca96d14e5b06df1506d41ff"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:62b2198c438058a20b6704351b35a1d7db881812d8512d67a69c9de1f18ca05f"}, - {file = "numpy-2.3.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:9d729d60f8d53a7361707f4b68a9663c968882dd4f09e0d58c044c8bf5faee7b"}, - {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd0c630cf256b0a7fd9d0a11c9413b42fef5101219ce6ed5a09624f5a65392c7"}, - {file = "numpy-2.3.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d5e081bc082825f8b139f9e9fe42942cb4054524598aaeb177ff476cc76d09d2"}, - {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:15fb27364ed84114438fff8aaf998c9e19adbeba08c0b75409f8c452a8692c52"}, - {file = "numpy-2.3.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:85d9fb2d8cd998c84d13a79a09cc0c1091648e848e4e6249b0ccd7f6b487fa26"}, - {file = "numpy-2.3.4-cp314-cp314-win32.whl", hash = "sha256:e73d63fd04e3a9d6bc187f5455d81abfad05660b212c8804bf3b407e984cd2bc"}, - {file = "numpy-2.3.4-cp314-cp314-win_amd64.whl", hash = "sha256:3da3491cee49cf16157e70f607c03a217ea6647b1cea4819c4f48e53d49139b9"}, - {file = "numpy-2.3.4-cp314-cp314-win_arm64.whl", hash = "sha256:6d9cd732068e8288dbe2717177320723ccec4fb064123f0caf9bbd90ab5be868"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:22758999b256b595cf0b1d102b133bb61866ba5ceecf15f759623b64c020c9ec"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9cb177bc55b010b19798dc5497d540dea67fd13a8d9e882b2dae71de0cf09eb3"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:0f2bcc76f1e05e5ab58893407c63d90b2029908fa41f9f1cc51eecce936c3365"}, - {file = "numpy-2.3.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:8dc20bde86802df2ed8397a08d793da0ad7a5fd4ea3ac85d757bf5dd4ad7c252"}, - {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5e199c087e2aa71c8f9ce1cb7a8e10677dc12457e7cc1be4798632da37c3e86e"}, - {file = "numpy-2.3.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85597b2d25ddf655495e2363fe044b0ae999b75bc4d630dc0d886484b03a5eb0"}, - {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04a69abe45b49c5955923cf2c407843d1c85013b424ae8a560bba16c92fe44a0"}, - {file = "numpy-2.3.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e1708fac43ef8b419c975926ce1eaf793b0c13b7356cfab6ab0dc34c0a02ac0f"}, - {file = "numpy-2.3.4-cp314-cp314t-win32.whl", hash = "sha256:863e3b5f4d9915aaf1b8ec79ae560ad21f0b8d5e3adc31e73126491bb86dee1d"}, - {file = "numpy-2.3.4-cp314-cp314t-win_amd64.whl", hash = "sha256:962064de37b9aef801d33bc579690f8bfe6c5e70e29b61783f60bcba838a14d6"}, - {file = "numpy-2.3.4-cp314-cp314t-win_arm64.whl", hash = "sha256:8b5a9a39c45d852b62693d9b3f3e0fe052541f804296ff401a72a1b60edafb29"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d"}, - {file = "numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f"}, - {file = "numpy-2.3.4.tar.gz", hash = "sha256:a7d018bfedb375a8d979ac758b120ba846a7fe764911a64465fd87b8729f4a6a"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:de5672f4a7b200c15a4127042170a694d4df43c992948f5e1af57f0174beed10"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:acfd89508504a19ed06ef963ad544ec6664518c863436306153e13e94605c218"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:ffe22d2b05504f786c867c8395de703937f934272eb67586817b46188b4ded6d"}, + {file = "numpy-2.3.5-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:872a5cf366aec6bb1147336480fef14c9164b154aeb6542327de4970282cd2f5"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3095bdb8dd297e5920b010e96134ed91d852d81d490e787beca7e35ae1d89cf7"}, + {file = "numpy-2.3.5-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8cba086a43d54ca804ce711b2a940b16e452807acebe7852ff327f1ecd49b0d4"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6cf9b429b21df6b99f4dee7a1218b8b7ffbbe7df8764dc0bd60ce8a0708fed1e"}, + {file = "numpy-2.3.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:396084a36abdb603546b119d96528c2f6263921c50df3c8fd7cb28873a237748"}, + {file = "numpy-2.3.5-cp311-cp311-win32.whl", hash = "sha256:b0c7088a73aef3d687c4deef8452a3ac7c1be4e29ed8bf3b366c8111128ac60c"}, + {file = "numpy-2.3.5-cp311-cp311-win_amd64.whl", hash = "sha256:a414504bef8945eae5f2d7cb7be2d4af77c5d1cb5e20b296c2c25b61dff2900c"}, + {file = "numpy-2.3.5-cp311-cp311-win_arm64.whl", hash = "sha256:0cd00b7b36e35398fa2d16af7b907b65304ef8bb4817a550e06e5012929830fa"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:74ae7b798248fe62021dbf3c914245ad45d1a6b0cb4a29ecb4b31d0bfbc4cc3e"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee3888d9ff7c14604052b2ca5535a30216aa0a58e948cdd3eeb8d3415f638769"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:612a95a17655e213502f60cfb9bf9408efdc9eb1d5f50535cc6eb365d11b42b5"}, + {file = "numpy-2.3.5-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:3101e5177d114a593d79dd79658650fe28b5a0d8abeb8ce6f437c0e6df5be1a4"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8b973c57ff8e184109db042c842423ff4f60446239bd585a5131cc47f06f789d"}, + {file = "numpy-2.3.5-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0d8163f43acde9a73c2a33605353a4f1bc4798745a8b1d73183b28e5b435ae28"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:51c1e14eb1e154ebd80e860722f9e6ed6ec89714ad2db2d3aa33c31d7c12179b"}, + {file = "numpy-2.3.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b46b4ec24f7293f23adcd2d146960559aaf8020213de8ad1909dba6c013bf89c"}, + {file = "numpy-2.3.5-cp312-cp312-win32.whl", hash = "sha256:3997b5b3c9a771e157f9aae01dd579ee35ad7109be18db0e85dbdbe1de06e952"}, + {file = "numpy-2.3.5-cp312-cp312-win_amd64.whl", hash = "sha256:86945f2ee6d10cdfd67bcb4069c1662dd711f7e2a4343db5cecec06b87cf31aa"}, + {file = "numpy-2.3.5-cp312-cp312-win_arm64.whl", hash = "sha256:f28620fe26bee16243be2b7b874da327312240a7cdc38b769a697578d2100013"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d0f23b44f57077c1ede8c5f26b30f706498b4862d3ff0a7298b8411dd2f043ff"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:aa5bc7c5d59d831d9773d1170acac7893ce3a5e130540605770ade83280e7188"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:ccc933afd4d20aad3c00bcef049cb40049f7f196e0397f1109dba6fed63267b0"}, + {file = "numpy-2.3.5-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:afaffc4393205524af9dfa400fa250143a6c3bc646c08c9f5e25a9f4b4d6a903"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c75442b2209b8470d6d5d8b1c25714270686f14c749028d2199c54e29f20b4d"}, + {file = "numpy-2.3.5-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11e06aa0af8c0f05104d56450d6093ee639e15f24ecf62d417329d06e522e017"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ed89927b86296067b4f81f108a2271d8926467a8868e554eaf370fc27fa3ccaf"}, + {file = "numpy-2.3.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:51c55fe3451421f3a6ef9a9c1439e82101c57a2c9eab9feb196a62b1a10b58ce"}, + {file = "numpy-2.3.5-cp313-cp313-win32.whl", hash = "sha256:1978155dd49972084bd6ef388d66ab70f0c323ddee6f693d539376498720fb7e"}, + {file = "numpy-2.3.5-cp313-cp313-win_amd64.whl", hash = "sha256:00dc4e846108a382c5869e77c6ed514394bdeb3403461d25a829711041217d5b"}, + {file = "numpy-2.3.5-cp313-cp313-win_arm64.whl", hash = "sha256:0472f11f6ec23a74a906a00b48a4dcf3849209696dff7c189714511268d103ae"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:414802f3b97f3c1eef41e530aaba3b3c1620649871d8cb38c6eaff034c2e16bd"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5ee6609ac3604fa7780e30a03e5e241a7956f8e2fcfe547d51e3afa5247ac47f"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:86d835afea1eaa143012a2d7a3f45a3adce2d7adc8b4961f0b362214d800846a"}, + {file = "numpy-2.3.5-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:30bc11310e8153ca664b14c5f1b73e94bd0503681fcf136a163de856f3a50139"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1062fde1dcf469571705945b0f221b73928f34a20c904ffb45db101907c3454e"}, + {file = "numpy-2.3.5-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ce581db493ea1a96c0556360ede6607496e8bf9b3a8efa66e06477267bc831e9"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:cc8920d2ec5fa99875b670bb86ddeb21e295cb07aa331810d9e486e0b969d946"}, + {file = "numpy-2.3.5-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:9ee2197ef8c4f0dfe405d835f3b6a14f5fee7782b5de51ba06fb65fc9b36e9f1"}, + {file = "numpy-2.3.5-cp313-cp313t-win32.whl", hash = "sha256:70b37199913c1bd300ff6e2693316c6f869c7ee16378faf10e4f5e3275b299c3"}, + {file = "numpy-2.3.5-cp313-cp313t-win_amd64.whl", hash = "sha256:b501b5fa195cc9e24fe102f21ec0a44dffc231d2af79950b451e0d99cea02234"}, + {file = "numpy-2.3.5-cp313-cp313t-win_arm64.whl", hash = "sha256:a80afd79f45f3c4a7d341f13acbe058d1ca8ac017c165d3fa0d3de6bc1a079d7"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:bf06bc2af43fa8d32d30fae16ad965663e966b1a3202ed407b84c989c3221e82"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:052e8c42e0c49d2575621c158934920524f6c5da05a1d3b9bab5d8e259e045f0"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:1ed1ec893cff7040a02c8aa1c8611b94d395590d553f6b53629a4461dc7f7b63"}, + {file = "numpy-2.3.5-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:2dcd0808a421a482a080f89859a18beb0b3d1e905b81e617a188bd80422d62e9"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:727fd05b57df37dc0bcf1a27767a3d9a78cbbc92822445f32cc3436ba797337b"}, + {file = "numpy-2.3.5-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fffe29a1ef00883599d1dc2c51aa2e5d80afe49523c261a74933df395c15c520"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:8f7f0e05112916223d3f438f293abf0727e1181b5983f413dfa2fefc4098245c"}, + {file = "numpy-2.3.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2e2eb32ddb9ccb817d620ac1d8dae7c3f641c1e5f55f531a33e8ab97960a75b8"}, + {file = "numpy-2.3.5-cp314-cp314-win32.whl", hash = "sha256:66f85ce62c70b843bab1fb14a05d5737741e74e28c7b8b5a064de10142fad248"}, + {file = "numpy-2.3.5-cp314-cp314-win_amd64.whl", hash = "sha256:e6a0bc88393d65807d751a614207b7129a310ca4fe76a74e5c7da5fa5671417e"}, + {file = "numpy-2.3.5-cp314-cp314-win_arm64.whl", hash = "sha256:aeffcab3d4b43712bb7a60b65f6044d444e75e563ff6180af8f98dd4b905dfd2"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:17531366a2e3a9e30762c000f2c43a9aaa05728712e25c11ce1dbe700c53ad41"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d21644de1b609825ede2f48be98dfde4656aefc713654eeee280e37cadc4e0ad"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:c804e3a5aba5460c73955c955bdbd5c08c354954e9270a2c1565f62e866bdc39"}, + {file = "numpy-2.3.5-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:cc0a57f895b96ec78969c34f682c602bf8da1a0270b09bc65673df2e7638ec20"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:900218e456384ea676e24ea6a0417f030a3b07306d29d7ad843957b40a9d8d52"}, + {file = "numpy-2.3.5-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:09a1bea522b25109bf8e6f3027bd810f7c1085c64a0c7ce050c1676ad0ba010b"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:04822c00b5fd0323c8166d66c701dc31b7fbd252c100acd708c48f763968d6a3"}, + {file = "numpy-2.3.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:d6889ec4ec662a1a37eb4b4fb26b6100841804dac55bd9df579e326cdc146227"}, + {file = "numpy-2.3.5-cp314-cp314t-win32.whl", hash = "sha256:93eebbcf1aafdf7e2ddd44c2923e2672e1010bddc014138b229e49725b4d6be5"}, + {file = "numpy-2.3.5-cp314-cp314t-win_amd64.whl", hash = "sha256:c8a9958e88b65c3b27e22ca2a076311636850b612d6bbfb76e8d156aacde2aaf"}, + {file = "numpy-2.3.5-cp314-cp314t-win_arm64.whl", hash = "sha256:6203fdf9f3dc5bdaed7319ad8698e685c7a3be10819f41d32a0723e611733b42"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:f0963b55cdd70fad460fa4c1341f12f976bb26cb66021a5580329bd498988310"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f4255143f5160d0de972d28c8f9665d882b5f61309d8362fdd3e103cf7bf010c"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:a4b9159734b326535f4dd01d947f919c6eefd2d9827466a696c44ced82dfbc18"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:2feae0d2c91d46e59fcd62784a3a83b3fb677fead592ce51b5a6fbb4f95965ff"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ffac52f28a7849ad7576293c0cb7b9f08304e8f7d738a8cb8a90ec4c55a998eb"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63c0e9e7eea69588479ebf4a8a270d5ac22763cc5854e9a7eae952a3908103f7"}, + {file = "numpy-2.3.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:f16417ec91f12f814b10bafe79ef77e70113a2f5f7018640e7425ff979253425"}, + {file = "numpy-2.3.5.tar.gz", hash = "sha256:784db1dcdab56bf0517743e746dfb0f885fc68d948aba86eeec2cba234bdf1c0"}, ] [[package]] name = "openai" -version = "2.8.1" +version = "2.14.0" description = "The official Python library for the openai API" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "openai-2.8.1-py3-none-any.whl", hash = "sha256:c6c3b5a04994734386e8dad3c00a393f56d3b68a27cd2e8acae91a59e4122463"}, - {file = "openai-2.8.1.tar.gz", hash = "sha256:cb1b79eef6e809f6da326a7ef6038719e35aa944c42d081807bfa1be8060f15f"}, + {file = "openai-2.14.0-py3-none-any.whl", hash = "sha256:7ea40aca4ffc4c4a776e77679021b47eec1160e341f42ae086ba949c9dcc9183"}, + {file = "openai-2.14.0.tar.gz", hash = "sha256:419357bedde9402d23bf8f2ee372fca1985a73348debba94bddff06f19459952"}, ] [package.dependencies] @@ -2817,14 +2870,14 @@ voice-helpers = ["numpy (>=2.0.2)", "sounddevice (>=0.5.1)"] [[package]] name = "openevals" -version = "0.1.0" +version = "0.1.3" description = "Open-source evaluators for LLM applications" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "openevals-0.1.0-py3-none-any.whl", hash = "sha256:214b53197b1becff74279ea063c8752f8887a81afda700477639c19a0d683647"}, - {file = "openevals-0.1.0.tar.gz", hash = "sha256:3dbb7cc4251ab7f6fcd5cbc8939b8538c5bf333e88ad01e5d8ac287f509891a4"}, + {file = "openevals-0.1.3-py3-none-any.whl", hash = "sha256:aed448df0cfdded732e24cda026eda065435a71ffb8c406a3ce73e590156d9f9"}, + {file = "openevals-0.1.3.tar.gz", hash = "sha256:9b00df1a7738464676aa887d4d950b77d3ef7024f6e8a54be3a83c82f485ea65"}, ] [package.dependencies] @@ -2853,165 +2906,157 @@ et-xmlfile = "*" [[package]] name = "orjson" -version = "3.11.4" +version = "3.11.5" description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "orjson-3.11.4-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e3aa2118a3ece0d25489cbe48498de8a5d580e42e8d9979f65bf47900a15aba1"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a69ab657a4e6733133a3dca82768f2f8b884043714e8d2b9ba9f52b6efef5c44"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3740bffd9816fc0326ddc406098a3a8f387e42223f5f455f2a02a9f834ead80c"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:65fd2f5730b1bf7f350c6dc896173d3460d235c4be007af73986d7cd9a2acd23"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fdc3ae730541086158d549c97852e2eea6820665d4faf0f41bf99df41bc11ea"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e10b4d65901da88845516ce9f7f9736f9638d19a1d483b3883dc0182e6e5edba"}, - {file = "orjson-3.11.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb6a03a678085f64b97f9d4a9ae69376ce91a3a9e9b56a82b1580d8e1d501aff"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:2c82e4f0b1c712477317434761fbc28b044c838b6b1240d895607441412371ac"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:d58c166a18f44cc9e2bad03a327dc2d1a3d2e85b847133cfbafd6bfc6719bd79"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:94f206766bf1ea30e1382e4890f763bd1eefddc580e08fec1ccdc20ddd95c827"}, - {file = "orjson-3.11.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:41bf25fb39a34cf8edb4398818523277ee7096689db352036a9e8437f2f3ee6b"}, - {file = "orjson-3.11.4-cp310-cp310-win32.whl", hash = "sha256:fa9627eba4e82f99ca6d29bc967f09aba446ee2b5a1ea728949ede73d313f5d3"}, - {file = "orjson-3.11.4-cp310-cp310-win_amd64.whl", hash = "sha256:23ef7abc7fca96632d8174ac115e668c1e931b8fe4dde586e92a500bf1914dcc"}, - {file = "orjson-3.11.4-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:5e59d23cd93ada23ec59a96f215139753fbfe3a4d989549bcb390f8c00370b39"}, - {file = "orjson-3.11.4-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:5c3aedecfc1beb988c27c79d52ebefab93b6c3921dbec361167e6559aba2d36d"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da9e5301f1c2caa2a9a4a303480d79c9ad73560b2e7761de742ab39fe59d9175"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8873812c164a90a79f65368f8f96817e59e35d0cc02786a5356f0e2abed78040"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5d7feb0741ebb15204e748f26c9638e6665a5fa93c37a2c73d64f1669b0ddc63"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:01ee5487fefee21e6910da4c2ee9eef005bee568a0879834df86f888d2ffbdd9"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d40d46f348c0321df01507f92b95a377240c4ec31985225a6668f10e2676f9a"}, - {file = "orjson-3.11.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95713e5fc8af84d8edc75b785d2386f653b63d62b16d681687746734b4dfc0be"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ad73ede24f9083614d6c4ca9a85fe70e33be7bf047ec586ee2363bc7418fe4d7"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:842289889de515421f3f224ef9c1f1efb199a32d76d8d2ca2706fa8afe749549"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:3b2427ed5791619851c52a1261b45c233930977e7de8cf36de05636c708fa905"}, - {file = "orjson-3.11.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c36e524af1d29982e9b190573677ea02781456b2e537d5840e4538a5ec41907"}, - {file = "orjson-3.11.4-cp311-cp311-win32.whl", hash = "sha256:87255b88756eab4a68ec61837ca754e5d10fa8bc47dc57f75cedfeaec358d54c"}, - {file = "orjson-3.11.4-cp311-cp311-win_amd64.whl", hash = "sha256:e2d5d5d798aba9a0e1fede8d853fa899ce2cb930ec0857365f700dffc2c7af6a"}, - {file = "orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045"}, - {file = "orjson-3.11.4-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:d4371de39319d05d3f482f372720b841c841b52f5385bd99c61ed69d55d9ab50"}, - {file = "orjson-3.11.4-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:e41fd3b3cac850eaae78232f37325ed7d7436e11c471246b87b2cd294ec94853"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:600e0e9ca042878c7fdf189cf1b028fe2c1418cc9195f6cb9824eb6ed99cb938"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7bbf9b333f1568ef5da42bc96e18bf30fd7f8d54e9ae066d711056add508e415"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4806363144bb6e7297b8e95870e78d30a649fdc4e23fc84daa80c8ebd366ce44"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad355e8308493f527d41154e9053b86a5be892b3b359a5c6d5d95cda23601cb2"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c8a7517482667fb9f0ff1b2f16fe5829296ed7a655d04d68cd9711a4d8a4e708"}, - {file = "orjson-3.11.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97eb5942c7395a171cbfecc4ef6701fc3c403e762194683772df4c54cfbb2210"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:149d95d5e018bdd822e3f38c103b1a7c91f88d38a88aada5c4e9b3a73a244241"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:624f3951181eb46fc47dea3d221554e98784c823e7069edb5dbd0dc826ac909b"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:03bfa548cf35e3f8b3a96c4e8e41f753c686ff3d8e182ce275b1751deddab58c"}, - {file = "orjson-3.11.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:525021896afef44a68148f6ed8a8bf8375553d6066c7f48537657f64823565b9"}, - {file = "orjson-3.11.4-cp312-cp312-win32.whl", hash = "sha256:b58430396687ce0f7d9eeb3dd47761ca7d8fda8e9eb92b3077a7a353a75efefa"}, - {file = "orjson-3.11.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6dbf422894e1e3c80a177133c0dda260f81428f9de16d61041949f6a2e5c140"}, - {file = "orjson-3.11.4-cp312-cp312-win_arm64.whl", hash = "sha256:d38d2bc06d6415852224fcc9c0bfa834c25431e466dc319f0edd56cca81aa96e"}, - {file = "orjson-3.11.4-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:2d6737d0e616a6e053c8b4acc9eccea6b6cce078533666f32d140e4f85002534"}, - {file = "orjson-3.11.4-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:afb14052690aa328cc118a8e09f07c651d301a72e44920b887c519b313d892ff"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38aa9e65c591febb1b0aed8da4d469eba239d434c218562df179885c94e1a3ad"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f2cf4dfaf9163b0728d061bebc1e08631875c51cd30bf47cb9e3293bfbd7dcd5"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:89216ff3dfdde0e4070932e126320a1752c9d9a758d6a32ec54b3b9334991a6a"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9daa26ca8e97fae0ce8aa5d80606ef8f7914e9b129b6b5df9104266f764ce436"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c8b2769dc31883c44a9cd126560327767f848eb95f99c36c9932f51090bfce9"}, - {file = "orjson-3.11.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1469d254b9884f984026bd9b0fa5bbab477a4bfe558bba6848086f6d43eb5e73"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:68e44722541983614e37117209a194e8c3ad07838ccb3127d96863c95ec7f1e0"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:8e7805fda9672c12be2f22ae124dcd7b03928d6c197544fe12174b86553f3196"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:04b69c14615fb4434ab867bf6f38b2d649f6f300af30a6705397e895f7aec67a"}, - {file = "orjson-3.11.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:639c3735b8ae7f970066930e58cf0ed39a852d417c24acd4a25fc0b3da3c39a6"}, - {file = "orjson-3.11.4-cp313-cp313-win32.whl", hash = "sha256:6c13879c0d2964335491463302a6ca5ad98105fc5db3565499dcb80b1b4bd839"}, - {file = "orjson-3.11.4-cp313-cp313-win_amd64.whl", hash = "sha256:09bf242a4af98732db9f9a1ec57ca2604848e16f132e3f72edfd3c5c96de009a"}, - {file = "orjson-3.11.4-cp313-cp313-win_arm64.whl", hash = "sha256:a85f0adf63319d6c1ba06fb0dbf997fced64a01179cf17939a6caca662bf92de"}, - {file = "orjson-3.11.4-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:42d43a1f552be1a112af0b21c10a5f553983c2a0938d2bbb8ecd8bc9fb572803"}, - {file = "orjson-3.11.4-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:26a20f3fbc6c7ff2cb8e89c4c5897762c9d88cf37330c6a117312365d6781d54"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3f20be9048941c7ffa8fc523ccbd17f82e24df1549d1d1fe9317712d19938e"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aac364c758dc87a52e68e349924d7e4ded348dedff553889e4d9f22f74785316"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d5c54a6d76e3d741dcc3f2707f8eeb9ba2a791d3adbf18f900219b62942803b1"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f28485bdca8617b79d44627f5fb04336897041dfd9fa66d383a49d09d86798bc"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bfc2a484cad3585e4ba61985a6062a4c2ed5c7925db6d39f1fa267c9d166487f"}, - {file = "orjson-3.11.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e34dbd508cb91c54f9c9788923daca129fe5b55c5b4eebe713bf5ed3791280cf"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b13c478fa413d4b4ee606ec8e11c3b2e52683a640b006bb586b3041c2ca5f606"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:724ca721ecc8a831b319dcd72cfa370cc380db0bf94537f08f7edd0a7d4e1780"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:977c393f2e44845ce1b540e19a786e9643221b3323dae190668a98672d43fb23"}, - {file = "orjson-3.11.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:1e539e382cf46edec157ad66b0b0872a90d829a6b71f17cb633d6c160a223155"}, - {file = "orjson-3.11.4-cp314-cp314-win32.whl", hash = "sha256:d63076d625babab9db5e7836118bdfa086e60f37d8a174194ae720161eb12394"}, - {file = "orjson-3.11.4-cp314-cp314-win_amd64.whl", hash = "sha256:0a54d6635fa3aaa438ae32e8570b9f0de36f3f6562c308d2a2a452e8b0592db1"}, - {file = "orjson-3.11.4-cp314-cp314-win_arm64.whl", hash = "sha256:78b999999039db3cf58f6d230f524f04f75f129ba3d1ca2ed121f8657e575d3d"}, - {file = "orjson-3.11.4-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:405261b0a8c62bcbd8e2931c26fdc08714faf7025f45531541e2b29e544b545b"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af02ff34059ee9199a3546f123a6ab4c86caf1708c79042caf0820dc290a6d4f"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0b2eba969ea4203c177c7b38b36c69519e6067ee68c34dc37081fac74c796e10"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0baa0ea43cfa5b008a28d3c07705cf3ada40e5d347f0f44994a64b1b7b4b5350"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80fd082f5dcc0e94657c144f1b2a3a6479c44ad50be216cf0c244e567f5eae19"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1e3704d35e47d5bee811fb1cbd8599f0b4009b14d451c4c57be5a7e25eb89a13"}, - {file = "orjson-3.11.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:caa447f2b5356779d914658519c874cf3b7629e99e63391ed519c28c8aea4919"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bba5118143373a86f91dadb8df41d9457498226698ebdf8e11cbb54d5b0e802d"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:622463ab81d19ef3e06868b576551587de8e4d518892d1afab71e0fbc1f9cffc"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:3e0a700c4b82144b72946b6629968df9762552ee1344bfdb767fecdd634fbd5a"}, - {file = "orjson-3.11.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6e18a5c15e764e5f3fc569b47872450b4bcea24f2a6354c0a0e95ad21045d5a9"}, - {file = "orjson-3.11.4-cp39-cp39-win32.whl", hash = "sha256:fb1c37c71cad991ef4d89c7a634b5ffb4447dbd7ae3ae13e8f5ee7f1775e7ab1"}, - {file = "orjson-3.11.4-cp39-cp39-win_amd64.whl", hash = "sha256:e2985ce8b8c42d00492d0ed79f2bd2b6460d00f2fa671dfde4bf2e02f49bf5c6"}, - {file = "orjson-3.11.4.tar.gz", hash = "sha256:39485f4ab4c9b30a3943cfe99e1a213c4776fb69e8abd68f66b83d5a0b0fdc6d"}, + {file = "orjson-3.11.5-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:df9eadb2a6386d5ea2bfd81309c505e125cfc9ba2b1b99a97e60985b0b3665d1"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccc70da619744467d8f1f49a8cadae5ec7bbe054e5232d95f92ed8737f8c5870"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:073aab025294c2f6fc0807201c76fdaed86f8fc4be52c440fb78fbb759a1ac09"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:835f26fa24ba0bb8c53ae2a9328d1706135b74ec653ed933869b74b6909e63fd"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667c132f1f3651c14522a119e4dd631fad98761fa960c55e8e7430bb2a1ba4ac"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42e8961196af655bb5e63ce6c60d25e8798cd4dfbc04f4203457fa3869322c2e"}, + {file = "orjson-3.11.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75412ca06e20904c19170f8a24486c4e6c7887dea591ba18a1ab572f1300ee9f"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:6af8680328c69e15324b5af3ae38abbfcf9cbec37b5346ebfd52339c3d7e8a18"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a86fe4ff4ea523eac8f4b57fdac319faf037d3c1be12405e6a7e86b3fbc4756a"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e607b49b1a106ee2086633167033afbd63f76f2999e9236f638b06b112b24ea7"}, + {file = "orjson-3.11.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7339f41c244d0eea251637727f016b3d20050636695bc78345cce9029b189401"}, + {file = "orjson-3.11.5-cp310-cp310-win32.whl", hash = "sha256:8be318da8413cdbbce77b8c5fac8d13f6eb0f0db41b30bb598631412619572e8"}, + {file = "orjson-3.11.5-cp310-cp310-win_amd64.whl", hash = "sha256:b9f86d69ae822cabc2a0f6c099b43e8733dda788405cba2665595b7e8dd8d167"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:9c8494625ad60a923af6b2b0bd74107146efe9b55099e20d7740d995f338fcd8"}, + {file = "orjson-3.11.5-cp311-cp311-macosx_15_0_arm64.whl", hash = "sha256:7bb2ce0b82bc9fd1168a513ddae7a857994b780b2945a8c51db4ab1c4b751ebc"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:67394d3becd50b954c4ecd24ac90b5051ee7c903d167459f93e77fc6f5b4c968"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:298d2451f375e5f17b897794bcc3e7b821c0f32b4788b9bcae47ada24d7f3cf7"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa5e4244063db8e1d87e0f54c3f7522f14b2dc937e65d5241ef0076a096409fd"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1db2088b490761976c1b2e956d5d4e6409f3732e9d79cfa69f876c5248d1baf9"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c2ed66358f32c24e10ceea518e16eb3549e34f33a9d51f99ce23b0251776a1ef"}, + {file = "orjson-3.11.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2021afda46c1ed64d74b555065dbd4c2558d510d8cec5ea6a53001b3e5e82a9"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b42ffbed9128e547a1647a3e50bc88ab28ae9daa61713962e0d3dd35e820c125"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:8d5f16195bb671a5dd3d1dbea758918bada8f6cc27de72bd64adfbd748770814"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:c0e5d9f7a0227df2927d343a6e3859bebf9208b427c79bd31949abcc2fa32fa5"}, + {file = "orjson-3.11.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:23d04c4543e78f724c4dfe656b3791b5f98e4c9253e13b2636f1af5d90e4a880"}, + {file = "orjson-3.11.5-cp311-cp311-win32.whl", hash = "sha256:c404603df4865f8e0afe981aa3c4b62b406e6d06049564d58934860b62b7f91d"}, + {file = "orjson-3.11.5-cp311-cp311-win_amd64.whl", hash = "sha256:9645ef655735a74da4990c24ffbd6894828fbfa117bc97c1edd98c282ecb52e1"}, + {file = "orjson-3.11.5-cp311-cp311-win_arm64.whl", hash = "sha256:1cbf2735722623fcdee8e712cbaaab9e372bbcb0c7924ad711b261c2eccf4a5c"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:334e5b4bff9ad101237c2d799d9fd45737752929753bf4faf4b207335a416b7d"}, + {file = "orjson-3.11.5-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:ff770589960a86eae279f5d8aa536196ebda8273a2a07db2a54e82b93bc86626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed24250e55efbcb0b35bed7caaec8cedf858ab2f9f2201f17b8938c618c8ca6f"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a66d7769e98a08a12a139049aac2f0ca3adae989817f8c43337455fbc7669b85"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:86cfc555bfd5794d24c6a1903e558b50644e5e68e6471d66502ce5cb5fdef3f9"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a230065027bc2a025e944f9d4714976a81e7ecfa940923283bca7bbc1f10f626"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b29d36b60e606df01959c4b982729c8845c69d1963f88686608be9ced96dbfaa"}, + {file = "orjson-3.11.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c74099c6b230d4261fdc3169d50efc09abf38ace1a42ea2f9994b1d79153d477"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e697d06ad57dd0c7a737771d470eedc18e68dfdefcdd3b7de7f33dfda5b6212e"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:e08ca8a6c851e95aaecc32bc44a5aa75d0ad26af8cdac7c77e4ed93acf3d5b69"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e8b5f96c05fce7d0218df3fdfeb962d6b8cfff7e3e20264306b46dd8b217c0f3"}, + {file = "orjson-3.11.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ddbfdb5099b3e6ba6d6ea818f61997bb66de14b411357d24c4612cf1ebad08ca"}, + {file = "orjson-3.11.5-cp312-cp312-win32.whl", hash = "sha256:9172578c4eb09dbfcf1657d43198de59b6cef4054de385365060ed50c458ac98"}, + {file = "orjson-3.11.5-cp312-cp312-win_amd64.whl", hash = "sha256:2b91126e7b470ff2e75746f6f6ee32b9ab67b7a93c8ba1d15d3a0caaf16ec875"}, + {file = "orjson-3.11.5-cp312-cp312-win_arm64.whl", hash = "sha256:acbc5fac7e06777555b0722b8ad5f574739e99ffe99467ed63da98f97f9ca0fe"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:3b01799262081a4c47c035dd77c1301d40f568f77cc7ec1bb7db5d63b0a01629"}, + {file = "orjson-3.11.5-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:61de247948108484779f57a9f406e4c84d636fa5a59e411e6352484985e8a7c3"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:894aea2e63d4f24a7f04a1908307c738d0dce992e9249e744b8f4e8dd9197f39"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ddc21521598dbe369d83d4d40338e23d4101dad21dae0e79fa20465dbace019f"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7cce16ae2f5fb2c53c3eafdd1706cb7b6530a67cc1c17abe8ec747f5cd7c0c51"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e46c762d9f0e1cfb4ccc8515de7f349abbc95b59cb5a2bd68df5973fdef913f8"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d7345c759276b798ccd6d77a87136029e71e66a8bbf2d2755cbdde1d82e78706"}, + {file = "orjson-3.11.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75bc2e59e6a2ac1dd28901d07115abdebc4563b5b07dd612bf64260a201b1c7f"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:54aae9b654554c3b4edd61896b978568c6daa16af96fa4681c9b5babd469f863"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:4bdd8d164a871c4ec773f9de0f6fe8769c2d6727879c37a9666ba4183b7f8228"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a261fef929bcf98a60713bf5e95ad067cea16ae345d9a35034e73c3990e927d2"}, + {file = "orjson-3.11.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c028a394c766693c5c9909dec76b24f37e6a1b91999e8d0c0d5feecbe93c3e05"}, + {file = "orjson-3.11.5-cp313-cp313-win32.whl", hash = "sha256:2cc79aaad1dfabe1bd2d50ee09814a1253164b3da4c00a78c458d82d04b3bdef"}, + {file = "orjson-3.11.5-cp313-cp313-win_amd64.whl", hash = "sha256:ff7877d376add4e16b274e35a3f58b7f37b362abf4aa31863dadacdd20e3a583"}, + {file = "orjson-3.11.5-cp313-cp313-win_arm64.whl", hash = "sha256:59ac72ea775c88b163ba8d21b0177628bd015c5dd060647bbab6e22da3aad287"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e446a8ea0a4c366ceafc7d97067bfd55292969143b57e3c846d87fc701e797a0"}, + {file = "orjson-3.11.5-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:53deb5addae9c22bbe3739298f5f2196afa881ea75944e7720681c7080909a81"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82cd00d49d6063d2b8791da5d4f9d20539c5951f965e45ccf4e96d33505ce68f"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3fd15f9fc8c203aeceff4fda211157fad114dde66e92e24097b3647a08f4ee9e"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df95000fbe6777bf9820ae82ab7578e8662051bb5f83d71a28992f539d2cda7"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a8d676748fca47ade5bc3da7430ed7767afe51b2f8100e3cd65e151c0eaceb"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa0f513be38b40234c77975e68805506cad5d57b3dfd8fe3baa7f4f4051e15b4"}, + {file = "orjson-3.11.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa1863e75b92891f553b7922ce4ee10ed06db061e104f2b7815de80cdcb135ad"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d4be86b58e9ea262617b8ca6251a2f0d63cc132a6da4b5fcc8e0a4128782c829"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:b923c1c13fa02084eb38c9c065afd860a5cff58026813319a06949c3af5732ac"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:1b6bd351202b2cd987f35a13b5e16471cf4d952b42a73c391cc537974c43ef6d"}, + {file = "orjson-3.11.5-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bb150d529637d541e6af06bbe3d02f5498d628b7f98267ff87647584293ab439"}, + {file = "orjson-3.11.5-cp314-cp314-win32.whl", hash = "sha256:9cc1e55c884921434a84a0c3dd2699eb9f92e7b441d7f53f3941079ec6ce7499"}, + {file = "orjson-3.11.5-cp314-cp314-win_amd64.whl", hash = "sha256:a4f3cb2d874e03bc7767c8f88adaa1a9a05cecea3712649c3b58589ec7317310"}, + {file = "orjson-3.11.5-cp314-cp314-win_arm64.whl", hash = "sha256:38b22f476c351f9a1c43e5b07d8b5a02eb24a6ab8e75f700f7d479d4568346a5"}, + {file = "orjson-3.11.5-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1b280e2d2d284a6713b0cfec7b08918ebe57df23e3f76b27586197afca3cb1e9"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c8d8a112b274fae8c5f0f01954cb0480137072c271f3f4958127b010dfefaec"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f0a2ae6f09ac7bd47d2d5a5305c1d9ed08ac057cda55bb0a49fa506f0d2da00"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c0d87bd1896faac0d10b4f849016db81a63e4ec5df38757ffae84d45ab38aa71"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:801a821e8e6099b8c459ac7540b3c32dba6013437c57fdcaec205b169754f38c"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a0f6ac618c98c74b7fbc8c0172ba86f9e01dbf9f62aa0b1776c2231a7bffe5"}, + {file = "orjson-3.11.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fea7339bdd22e6f1060c55ac31b6a755d86a5b2ad3657f2669ec243f8e3b2bdb"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:4dad582bc93cef8f26513e12771e76385a7e6187fd713157e971c784112aad56"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:0522003e9f7fba91982e83a97fec0708f5a714c96c4209db7104e6b9d132f111"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:7403851e430a478440ecc1258bcbacbfbd8175f9ac1e39031a7121dd0de05ff8"}, + {file = "orjson-3.11.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5f691263425d3177977c8d1dd896cde7b98d93cbf390b2544a090675e83a6a0a"}, + {file = "orjson-3.11.5-cp39-cp39-win32.whl", hash = "sha256:61026196a1c4b968e1b1e540563e277843082e9e97d78afa03eb89315af531f1"}, + {file = "orjson-3.11.5-cp39-cp39-win_amd64.whl", hash = "sha256:09b94b947ac08586af635ef922d69dc9bc63321527a3a04647f4986a73f4bd30"}, + {file = "orjson-3.11.5.tar.gz", hash = "sha256:82393ab47b4fe44ffd0a7659fa9cfaacc717eb617c93cde83795f14af5c2e9d5"}, ] [[package]] name = "ormsgpack" -version = "1.11.0" -description = "" +version = "1.12.1" +description = "Fast, correct Python msgpack library supporting dataclasses, datetimes, and numpy" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "ormsgpack-1.11.0-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:03d4e658dd6e1882a552ce1d13cc7b49157414e7d56a4091fbe7823225b08cba"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1bb67eb913c2b703f0ed39607fc56e50724dd41f92ce080a586b4d6149eb3fe4"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1e54175b92411f73a238e5653a998627f6660de3def37d9dd7213e0fd264ca56"}, - {file = "ormsgpack-1.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca2b197f4556e1823d1319869d4c5dc278be335286d2308b0ed88b59a5afcc25"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc62388262f58c792fe1e450e1d9dbcc174ed2fb0b43db1675dd7c5ff2319d6a"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c48bc10af74adfbc9113f3fb160dc07c61ad9239ef264c17e449eba3de343dc2"}, - {file = "ormsgpack-1.11.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:a608d3a1d4fa4acdc5082168a54513cff91f47764cef435e81a483452f5f7647"}, - {file = "ormsgpack-1.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:97217b4f7f599ba45916b9c4c4b1d5656e8e2a4d91e2e191d72a7569d3c30923"}, - {file = "ormsgpack-1.11.0-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:c7be823f47d8e36648d4bc90634b93f02b7d7cc7480081195f34767e86f181fb"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68accf15d1b013812755c0eb7a30e1fc2f81eb603a1a143bf0cda1b301cfa797"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:805d06fb277d9a4e503c0c707545b49cde66cbb2f84e5cf7c58d81dfc20d8658"}, - {file = "ormsgpack-1.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1e57cdf003e77acc43643bda151dc01f97147a64b11cdee1380bb9698a7601c"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:37fc05bdaabd994097c62e2f3e08f66b03f856a640ede6dc5ea340bd15b77f4d"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:a6e9db6c73eb46b2e4d97bdffd1368a66f54e6806b563a997b19c004ef165e1d"}, - {file = "ormsgpack-1.11.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e9c44eae5ac0196ffc8b5ed497c75511056508f2303fa4d36b208eb820cf209e"}, - {file = "ormsgpack-1.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:11d0dfaf40ae7c6de4f7dbd1e4892e2e6a55d911ab1774357c481158d17371e4"}, - {file = "ormsgpack-1.11.0-cp311-cp311-win_arm64.whl", hash = "sha256:0c63a3f7199a3099c90398a1bdf0cb577b06651a442dc5efe67f2882665e5b02"}, - {file = "ormsgpack-1.11.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3434d0c8d67de27d9010222de07fb6810fb9af3bb7372354ffa19257ac0eb83b"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2da5bd097e8dbfa4eb0d4ccfe79acd6f538dee4493579e2debfe4fc8f4ca89b"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fdbaa0a5a8606a486960b60c24f2d5235d30ac7a8b98eeaea9854bffef14dc3d"}, - {file = "ormsgpack-1.11.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3682f24f800c1837017ee90ce321086b2cbaef88db7d4cdbbda1582aa6508159"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fcca21202bb05ccbf3e0e92f560ee59b9331182e4c09c965a28155efbb134993"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:c30e5c4655ba46152d722ec7468e8302195e6db362ec1ae2c206bc64f6030e43"}, - {file = "ormsgpack-1.11.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7138a341f9e2c08c59368f03d3be25e8b87b3baaf10d30fb1f6f6b52f3d47944"}, - {file = "ormsgpack-1.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:d4bd8589b78a11026d47f4edf13c1ceab9088bb12451f34396afe6497db28a27"}, - {file = "ormsgpack-1.11.0-cp312-cp312-win_arm64.whl", hash = "sha256:e5e746a1223e70f111d4001dab9585ac8639eee8979ca0c8db37f646bf2961da"}, - {file = "ormsgpack-1.11.0-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0e7b36ab7b45cb95217ae1f05f1318b14a3e5ef73cb00804c0f06233f81a14e8"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43402d67e03a9a35cc147c8c03f0c377cad016624479e1ee5b879b8425551484"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:64fd992f932764d6306b70ddc755c1bc3405c4c6a69f77a36acf7af1c8f5ada4"}, - {file = "ormsgpack-1.11.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0362fb7fe4a29c046c8ea799303079a09372653a1ce5a5a588f3bbb8088368d0"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:de2f7a65a9d178ed57be49eba3d0fc9b833c32beaa19dbd4ba56014d3c20b152"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:f38cfae95461466055af966fc922d06db4e1654966385cda2828653096db34da"}, - {file = "ormsgpack-1.11.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c88396189d238f183cea7831b07a305ab5c90d6d29b53288ae11200bd956357b"}, - {file = "ormsgpack-1.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:5403d1a945dd7c81044cebeca3f00a28a0f4248b33242a5d2d82111628043725"}, - {file = "ormsgpack-1.11.0-cp313-cp313-win_arm64.whl", hash = "sha256:c57357b8d43b49722b876edf317bdad9e6d52071b523fdd7394c30cd1c67d5a0"}, - {file = "ormsgpack-1.11.0-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:d390907d90fd0c908211592c485054d7a80990697ef4dff4e436ac18e1aab98a"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6153c2e92e789509098e04c9aa116b16673bd88ec78fbe0031deeb34ab642d10"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c2b2c2a065a94d742212b2018e1fecd8f8d72f3c50b53a97d1f407418093446d"}, - {file = "ormsgpack-1.11.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:110e65b5340f3d7ef8b0009deae3c6b169437e6b43ad5a57fd1748085d29d2ac"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c27e186fca96ab34662723e65b420919910acbbc50fc8e1a44e08f26268cb0e0"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:d56b1f877c13d499052d37a3db2378a97d5e1588d264f5040b3412aee23d742c"}, - {file = "ormsgpack-1.11.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c88e28cd567c0a3269f624b4ade28142d5e502c8e826115093c572007af5be0a"}, - {file = "ormsgpack-1.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:8811160573dc0a65f62f7e0792c4ca6b7108dfa50771edb93f9b84e2d45a08ae"}, - {file = "ormsgpack-1.11.0-cp314-cp314-win_arm64.whl", hash = "sha256:23e30a8d3c17484cf74e75e6134322255bd08bc2b5b295cc9c442f4bae5f3c2d"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:2905816502adfaf8386a01dd85f936cd378d243f4f5ee2ff46f67f6298dc90d5"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c04402fb9a0a9b9f18fbafd6d5f8398ee99b3ec619fb63952d3a954bc9d47daa"}, - {file = "ormsgpack-1.11.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a025ec07ac52056ecfd9e57b5cbc6fff163f62cb9805012b56cda599157f8ef2"}, - {file = "ormsgpack-1.11.0-cp39-cp39-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:354c6a5039faf63b63d8f42ec7915583a4a56e10b319284370a5a89c4382d985"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7058c85cc13dd329bc7b528e38626c6babcd0066d6e9163330a1509fe0aa4707"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4e15b634be324fb18dab7aa82ab929a0d57d42c12650ae3dedd07d8d31b17733"}, - {file = "ormsgpack-1.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6329e6eae9dfe600962739a6e060ea82885ec58b8338875c5ac35080da970f94"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:b27546c28f92b9eb757620f7f1ed89fb7b07be3b9f4ba1b7de75761ec1c4bcc8"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:26a17919d9144b4ac7112dbbadef07927abbe436be2cf99a703a19afe7dd5c8b"}, - {file = "ormsgpack-1.11.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5352868ee4cdc00656bf216b56bc654f72ac3008eb36e12561f6337bb7104b45"}, - {file = "ormsgpack-1.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:2ffe36f1f441a40949e8587f5aa3d3fc9f100576925aab667117403eab494338"}, - {file = "ormsgpack-1.11.0.tar.gz", hash = "sha256:7c9988e78fedba3292541eb3bb274fa63044ef4da2ddb47259ea70c05dee4206"}, + {file = "ormsgpack-1.12.1-cp310-cp310-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:62e3614cab63fa5aa42f5f0ca3cd12899f0bfc5eb8a5a0ebab09d571c89d427d"}, + {file = "ormsgpack-1.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:86d9fbf85c05c69c33c229d2eba7c8c3500a56596cd8348131c918acd040d6af"}, + {file = "ormsgpack-1.12.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8d246e66f09d8e0f96e770829149ee83206e90ed12f5987998bb7be84aec99fe"}, + {file = "ormsgpack-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfc2c830a1ed2d00de713d08c9e62efa699e8fd29beafa626aaebe466f583ebb"}, + {file = "ormsgpack-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:bc892757d8f9eea5208268a527cf93c98409802f6a9f7c8d71a7b8f9ba5cb944"}, + {file = "ormsgpack-1.12.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:0de1dbcf11ea739ac4a882b43d5c2055e6d99ce64e8d6502e25d6d881700c017"}, + {file = "ormsgpack-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d5065dfb9ec4db93241c60847624d9aeef4ccb449c26a018c216b55c69be83c0"}, + {file = "ormsgpack-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:7d17103c4726181d7000c61b751c881f1b6f401d146df12da028fc730227df19"}, + {file = "ormsgpack-1.12.1-cp311-cp311-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4038f59ae0e19dac5e5d9aae4ec17ff84a79e046342ee73ccdecf3547ecf0d34"}, + {file = "ormsgpack-1.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16c63b0c5a3eec467e4bb33a14dabba076b7d934dff62898297b5c0b5f7c3cb3"}, + {file = "ormsgpack-1.12.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74fd6a8e037eb310dda865298e8d122540af00fe5658ec18b97a1d34f4012e4d"}, + {file = "ormsgpack-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58ad60308e233dd824a1859eabb5fe092e123e885eafa4ad5789322329c80fb5"}, + {file = "ormsgpack-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:35127464c941c1219acbe1a220e48d55e7933373d12257202f4042f7044b4c90"}, + {file = "ormsgpack-1.12.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:c48d1c50794692d1e6e3f8c3bb65f5c3acfaae9347e506484a65d60b3d91fb50"}, + {file = "ormsgpack-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b512b2ad6feaaefdc26e05431ed2843e42483041e354e167c53401afaa83d919"}, + {file = "ormsgpack-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:93f30db95e101a9616323bfc50807ad00e7f6197cea2216d2d24af42afc77d88"}, + {file = "ormsgpack-1.12.1-cp311-cp311-win_arm64.whl", hash = "sha256:d75b5fa14f6abffce2c392ee03b4731199d8a964c81ee8645c4c79af0e80fd50"}, + {file = "ormsgpack-1.12.1-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:4d7fb0e1b6fbc701d75269f7405a4f79230a6ce0063fb1092e4f6577e312f86d"}, + {file = "ormsgpack-1.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:43a9353e2db5b024c91a47d864ef15eaa62d81824cfc7740fed4cef7db738694"}, + {file = "ormsgpack-1.12.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fc8fe866b7706fc25af0adf1f600bc06ece5b15ca44e34641327198b821e5c3c"}, + {file = "ormsgpack-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:813755b5f598a78242042e05dfd1ada4e769e94b98c9ab82554550f97ff4d641"}, + {file = "ormsgpack-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8eea2a13536fae45d78f93f2cc846c9765c7160c85f19cfefecc20873c137cdd"}, + {file = "ormsgpack-1.12.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:7a02ebda1a863cbc604740e76faca8eee1add322db2dcbe6cf32669fffdff65c"}, + {file = "ormsgpack-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c0bd63897c439931cdf29348e5e6e8c330d529830e848d10767615c0f3d1b82"}, + {file = "ormsgpack-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:362f2e812f8d7035dc25a009171e09d7cc97cb30d3c9e75a16aeae00ca3c1dcf"}, + {file = "ormsgpack-1.12.1-cp312-cp312-win_arm64.whl", hash = "sha256:6190281e381db2ed0045052208f47a995ccf61eed48f1215ae3cce3fbccd59c5"}, + {file = "ormsgpack-1.12.1-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:9663d6b3ecc917c063d61a99169ce196a80f3852e541ae404206836749459279"}, + {file = "ormsgpack-1.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:32e85cfbaf01a94a92520e7fe7851cfcfe21a5698299c28ab86194895f9b9233"}, + {file = "ormsgpack-1.12.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:dabfd2c24b59c7c69870a5ecee480dfae914a42a0c2e7c9d971cf531e2ba471a"}, + {file = "ormsgpack-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51bbf2b64afeded34ccd8e25402e4bca038757913931fa0d693078d75563f6f9"}, + {file = "ormsgpack-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:9959a71dde1bd0ced84af17facc06a8afada495a34e9cb1bad8e9b20d4c59cef"}, + {file = "ormsgpack-1.12.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:e9be0e3b62d758f21f5b20e0e06b3a240ec546c4a327bf771f5825462aa74714"}, + {file = "ormsgpack-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a29d49ab7fdd77ea787818e60cb4ef491708105b9c4c9b0f919201625eb036b5"}, + {file = "ormsgpack-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:c418390b47a1d367e803f6c187f77e4d67c7ae07ba962e3a4a019001f4b0291a"}, + {file = "ormsgpack-1.12.1-cp313-cp313-win_arm64.whl", hash = "sha256:cfa22c91cffc10a7fbd43729baff2de7d9c28cef2509085a704168ae31f02568"}, + {file = "ormsgpack-1.12.1-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:b93c91efb1a70751a1902a5b43b27bd8fd38e0ca0365cf2cde2716423c15c3a6"}, + {file = "ormsgpack-1.12.1-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cf0ea0389167b5fa8d2933dd3f33e887ec4ba68f89c25214d7eec4afd746d22"}, + {file = "ormsgpack-1.12.1-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f4c29af837f35af3375070689e781161e7cf019eb2f7cd641734ae45cd001c0d"}, + {file = "ormsgpack-1.12.1-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336fc65aa0fe65896a3dabaae31e332a0a98b4a00ad7b0afde21a7505fd23ff3"}, + {file = "ormsgpack-1.12.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:940f60aabfefe71dd6b82cb33f4ff10b2e7f5fcfa5f103cdb0a23b6aae4c713c"}, + {file = "ormsgpack-1.12.1-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:596ad9e1b6d4c95595c54aaf49b1392609ca68f562ce06f4f74a5bc4053bcda4"}, + {file = "ormsgpack-1.12.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:575210e8fcbc7b0375026ba040a5eef223e9f66a4453d9623fc23282ae09c3c8"}, + {file = "ormsgpack-1.12.1-cp314-cp314-win_amd64.whl", hash = "sha256:647daa3718572280893456be44c60aea6690b7f2edc54c55648ee66e8f06550f"}, + {file = "ormsgpack-1.12.1-cp314-cp314-win_arm64.whl", hash = "sha256:a8b3ab762a6deaf1b6490ab46dda0c51528cf8037e0246c40875c6fe9e37b699"}, + {file = "ormsgpack-1.12.1-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:12087214e436c1f6c28491949571abea759a63111908c4f7266586d78144d7a8"}, + {file = "ormsgpack-1.12.1-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e6d54c14cf86ef13f10ccade94d1e7de146aa9b17d371e18b16e95f329393b7"}, + {file = "ormsgpack-1.12.1-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3584d07882b7ea2a1a589f795a3af97fe4c2932b739408e6d1d9d286cad862"}, + {file = "ormsgpack-1.12.1.tar.gz", hash = "sha256:a3877fde1e4f27a39f92681a0aab6385af3a41d0c25375d33590ae20410ea2ac"}, ] [[package]] @@ -3318,137 +3363,133 @@ testing = ["google-api-core (>=1.31.5)"] [[package]] name = "protobuf" -version = "6.33.0" +version = "6.33.2" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "protobuf-6.33.0-cp310-abi3-win32.whl", hash = "sha256:d6101ded078042a8f17959eccd9236fb7a9ca20d3b0098bbcb91533a5680d035"}, - {file = "protobuf-6.33.0-cp310-abi3-win_amd64.whl", hash = "sha256:9a031d10f703f03768f2743a1c403af050b6ae1f3480e9c140f39c45f81b13ee"}, - {file = "protobuf-6.33.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:905b07a65f1a4b72412314082c7dbfae91a9e8b68a0cc1577515f8df58ecf455"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:e0697ece353e6239b90ee43a9231318302ad8353c70e6e45499fa52396debf90"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:e0a1715e4f27355afd9570f3ea369735afc853a6c3951a6afe1f80d8569ad298"}, - {file = "protobuf-6.33.0-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:35be49fd3f4fefa4e6e2aacc35e8b837d6703c37a2168a55ac21e9b1bc7559ef"}, - {file = "protobuf-6.33.0-cp39-cp39-win32.whl", hash = "sha256:cd33a8e38ea3e39df66e1bbc462b076d6e5ba3a4ebbde58219d777223a7873d3"}, - {file = "protobuf-6.33.0-cp39-cp39-win_amd64.whl", hash = "sha256:c963e86c3655af3a917962c9619e1a6b9670540351d7af9439d06064e3317cc9"}, - {file = "protobuf-6.33.0-py3-none-any.whl", hash = "sha256:25c9e1963c6734448ea2d308cfa610e692b801304ba0908d7bfa564ac5132995"}, - {file = "protobuf-6.33.0.tar.gz", hash = "sha256:140303d5c8d2037730c548f8c7b93b20bb1dc301be280c378b82b8894589c954"}, + {file = "protobuf-6.33.2-cp310-abi3-win32.whl", hash = "sha256:87eb388bd2d0f78febd8f4c8779c79247b26a5befad525008e49a6955787ff3d"}, + {file = "protobuf-6.33.2-cp310-abi3-win_amd64.whl", hash = "sha256:fc2a0e8b05b180e5fc0dd1559fe8ebdae21a27e81ac77728fb6c42b12c7419b4"}, + {file = "protobuf-6.33.2-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:d9b19771ca75935b3a4422957bc518b0cecb978b31d1dd12037b088f6bcc0e43"}, + {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_aarch64.whl", hash = "sha256:b5d3b5625192214066d99b2b605f5783483575656784de223f00a8d00754fc0e"}, + {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_s390x.whl", hash = "sha256:8cd7640aee0b7828b6d03ae518b5b4806fdfc1afe8de82f79c3454f8aef29872"}, + {file = "protobuf-6.33.2-cp39-abi3-manylinux2014_x86_64.whl", hash = "sha256:1f8017c48c07ec5859106533b682260ba3d7c5567b1ca1f24297ce03384d1b4f"}, + {file = "protobuf-6.33.2-cp39-cp39-win32.whl", hash = "sha256:7109dcc38a680d033ffb8bf896727423528db9163be1b6a02d6a49606dcadbfe"}, + {file = "protobuf-6.33.2-cp39-cp39-win_amd64.whl", hash = "sha256:2981c58f582f44b6b13173e12bb8656711189c2a70250845f264b877f00b1913"}, + {file = "protobuf-6.33.2-py3-none-any.whl", hash = "sha256:7636aad9bb01768870266de5dc009de2d1b936771b38a793f73cbbf279c91c5c"}, + {file = "protobuf-6.33.2.tar.gz", hash = "sha256:56dc370c91fbb8ac85bc13582c9e373569668a290aa2e66a590c2a0d35ddb9e4"}, ] [[package]] name = "psycopg" -version = "3.2.12" +version = "3.3.2" description = "PostgreSQL database adapter for Python" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "psycopg-3.2.12-py3-none-any.whl", hash = "sha256:8a1611a2d4c16ae37eada46438be9029a35bb959bb50b3d0e1e93c0f3d54c9ee"}, - {file = "psycopg-3.2.12.tar.gz", hash = "sha256:85c08d6f6e2a897b16280e0ff6406bef29b1327c045db06d21f364d7cd5da90b"}, + {file = "psycopg-3.3.2-py3-none-any.whl", hash = "sha256:3e94bc5f4690247d734599af56e51bae8e0db8e4311ea413f801fef82b14a99b"}, + {file = "psycopg-3.3.2.tar.gz", hash = "sha256:707a67975ee214d200511177a6a80e56e654754c9afca06a7194ea6bbfde9ca7"}, ] [package.dependencies] -psycopg-binary = {version = "3.2.12", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} +psycopg-binary = {version = "3.3.2", optional = true, markers = "implementation_name != \"pypy\" and extra == \"binary\""} typing-extensions = {version = ">=4.6", markers = "python_version < \"3.13\""} tzdata = {version = "*", markers = "sys_platform == \"win32\""} [package.extras] -binary = ["psycopg-binary (==3.2.12) ; implementation_name != \"pypy\""] -c = ["psycopg-c (==3.2.12) ; implementation_name != \"pypy\""] -dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.14)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] +binary = ["psycopg-binary (==3.3.2) ; implementation_name != \"pypy\""] +c = ["psycopg-c (==3.3.2) ; implementation_name != \"pypy\""] +dev = ["ast-comments (>=1.1.2)", "black (>=24.1.0)", "codespell (>=2.2)", "cython-lint (>=0.16)", "dnspython (>=2.1)", "flake8 (>=4.0)", "isort-psycopg", "isort[colors] (>=6.0)", "mypy (>=1.19.0)", "pre-commit (>=4.0.1)", "types-setuptools (>=57.4)", "types-shapely (>=2.0)", "wheel (>=0.37)"] docs = ["Sphinx (>=5.0)", "furo (==2022.6.21)", "sphinx-autobuild (>=2021.3.14)", "sphinx-autodoc-typehints (>=1.12)"] pool = ["psycopg-pool"] -test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] +test = ["anyio (>=4.0)", "mypy (>=1.19.0) ; implementation_name != \"pypy\"", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] [[package]] name = "psycopg-binary" -version = "3.2.12" +version = "3.3.2" description = "PostgreSQL database adapter for Python -- C optimisation distribution" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] markers = "implementation_name != \"pypy\"" files = [ - {file = "psycopg_binary-3.2.12-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:13cd057f406d2c8063ae8b489395b089a7f23c39aff223b5ea39f0c4dd640550"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ef92d5ba6213de060d1390b1f71f5c3b2fbb00b4d55edee39f3b07234538b64a"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:95f2806097a49bfd57e0c6a178f77b99487c53c157d9d507aee9c40dd58efdb4"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:ce68839da386f137bc8d814fdbeede8f89916b8605e3593a85b504a859243af9"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:940ac69ef6e89c17b3d30f3297a2ad03efdd06a4b1857f81bc533a9108a90eb9"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:310c95a68a9b948b89d6d187622757d57b6c26cece3c3f7c2cbb645ee36531b2"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f7c81bc60560be9eb3c23601237765069ebfa9881097ce19ca6b5ea17c5faa8f"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1c1dbeb8e97d00a33dfa9987776ce3d1c1e4cc251dfbd663b8f9e173f5c89d17"}, - {file = "psycopg_binary-3.2.12-cp310-cp310-win_amd64.whl", hash = "sha256:8335d989a4e94df2ccd8a1acbba9d03c4157ea8d73b65b79d447c6dc10b001d8"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:16db2549a31ccd4887bef05570d95036813ce25fd9810b523ba1c16b0f6cfd90"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b9a99ded7d19b24d3b6fa632b58e52bbdecde7e1f866c3b23d0c27b092af4e3"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:385c7b5cfffac115f413b8e32c941c85ea0960e0b94a6ef43bb260f774c54893"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9c674887d1e0d4384c06c822bc7fcfede4952742e232ec1e76b5a6ae39a3ddd4"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:72fd979e410ba7805462817ef8ed6f37dd75f9f4ae109bdb8503e013ccecb80b"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec82fa5134517af44e28a30c38f34384773a0422ffd545fd298433ea9f2cc5a9"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:100fdfee763d701f6da694bde711e264aca4c2bc84fb81e1669fb491ce11d219"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:802bd01fb18a0acb0dea491f69a9a2da6034f33329a62876ab5b558a1fb66b45"}, - {file = "psycopg_binary-3.2.12-cp311-cp311-win_amd64.whl", hash = "sha256:f33c9e12ed05e579b7fb3c8fdb10a165f41459394b8eb113e7c377b2bd027f61"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:ea9751310b840186379c949ede5a5129b31439acdb929f3003a8685372117ed8"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9fdf3a0c24822401c60c93640da69b3dfd4d9f29c3a8d797244fe22bfe592823"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:49582c3b6d578bdaab2932b59f70b1bd93351ed4d594b2c97cea1611633c9de1"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:5b6e505618cb376a7a7d6af86833a8f289833fe4cc97541d7100745081dc31bd"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6a898717ab560db393355c6ecf39b8c534f252afc3131480db1251e061090d3a"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bfd632f7038c76b0921f6d5621f5ba9ecabfad3042fa40e5875db11771d2a5de"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3e9c9e64fb7cda688e9488402611c0be2c81083664117edcc709d15f37faa30f"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3c1e38b1eda54910628f68448598139a9818973755abf77950057372c1fe89a6"}, - {file = "psycopg_binary-3.2.12-cp312-cp312-win_amd64.whl", hash = "sha256:77690f0bf08356ca00fc357f50a5980c7a25f076c2c1f37d9d775a278234fefd"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:442f20153415f374ae5753ca618637611a41a3c58c56d16ce55f845d76a3cf7b"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:79de3cc5adbf51677009a8fda35ac9e9e3686d5595ab4b0c43ec7099ece6aeb5"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:095ccda59042a1239ac2fefe693a336cb5cecf8944a8d9e98b07f07e94e2b78d"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:efab679a2c7d1bf7d0ec0e1ecb47fe764945eff75bb4321f2e699b30a12db9b3"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d369e79ad9647fc8217cbb51bbbf11f9a1ffca450be31d005340157ffe8e91b3"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eedc410f82007038030650aa58f620f9fe0009b9d6b04c3dc71cbd3bae5b2675"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:f3bae4be7f6781bf6c9576eedcd5e1bb74468126fa6de991e47cdb1a8ea3a42a"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8ffe75fe6be902dadd439adf4228c98138a992088e073ede6dd34e7235f4e03e"}, - {file = "psycopg_binary-3.2.12-cp313-cp313-win_amd64.whl", hash = "sha256:2598d0e4f2f258da13df0560187b3f1dfc9b8688c46b9d90176360ae5212c3fc"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:dc68094e00a5a7e8c20de1d3a0d5e404a27f522e18f8eb62bbbc9f865c3c81ef"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2d55009eeddbef54c711093c986daaf361d2c4210aaa1ee905075a3b97a62441"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:66a031f22e4418016990446d3e38143826f03ad811b9f78f58e2afbc1d343f7a"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:58ed30d33c25d7dc8d2f06285e88493147c2a660cc94713e4b563a99efb80a1f"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e0b5ccd03ca4749b8f66f38608ccbcb415cbd130d02de5eda80d042b83bee90e"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:909de94de7dd4d6086098a5755562207114c9638ec42c52d84c8a440c45fe084"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:7130effd0517881f3a852eff98729d51034128f0737f64f0d1c7ea8343d77bd7"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:89b3c5201ca616d69ca0c3c0003ca18f7170a679c445c7e386ebfb4f29aa738e"}, - {file = "psycopg_binary-3.2.12-cp314-cp314-win_amd64.whl", hash = "sha256:48a8e29f3e38fcf8d393b8fe460d83e39c107ad7e5e61cd3858a7569e0554a39"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2aa80ca8d17266507bef853cecefa7d632ffd087883ee7ca92b8a7ea14a1e581"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:deeb06b7141f3a577c3aa8562307e2747580ae43d705a0482603a2c1f110d046"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:32b3e12d9441508f9c4e1424f4478b1a518a90a087cd54be3754e74954934194"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d7cedecbe0bb60a2e72b1613fba4072a184a6472d6cc9aa99e540217f544e3e"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:ea049c8d33c4f4e6b030d5a68123c0ccd2ffb77d4035f073db97187b49b6422f"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f821e0c8a8fdfddfa71acb4f462d7a4c5aae1655f3f5e078970dbe9f19027386"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ef40601b959cc1440deaf4d53472ab54fa51036c37189cf3fe5500559ac25347"}, - {file = "psycopg_binary-3.2.12-cp38-cp38-win_amd64.whl", hash = "sha256:0afb71a99871a41dd677d207c6a988d978edde5d6a018bafaed4f9da45357055"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f8107968a9eadb451cfa6cf86036006fdde32a83cd39c26c9ca46765e653b547"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:15e226f0d8af85cc8b2435b2e9bc6f0d40febc79eef76cf20fceac4d902a6a7b"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f6ba1fe35fd215813dac4544a5ffc90f13713b29dd26e9e5be97ba53482bf6d6"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:26b5927b5880b396231ab6190ee5c8fb47ed3f459b53504ed5419faaf16d3bfb"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ab02b7d138768fd6ac4230e45b073f7b9fd688d88c04f24c34df4a250a94d066"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:acb1811219a4144539f0baee224a11a2aa323a739c349799cf52f191eb87bc52"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:356b4266e5cde7b5bbcf232f549dedf7fbed4983daa556042bdec397780e044d"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:489b154891f1c995355adeb1077ee3479e9c9bada721b93270c20243bbad6542"}, - {file = "psycopg_binary-3.2.12-cp39-cp39-win_amd64.whl", hash = "sha256:294f08b014f08dfd3c9b72408f5e1a0fd187bd86d7a85ead651e32dbd47aa038"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0768c5f32934bb52a5df098317eca9bdcf411de627c5dca2ee57662b64b54b41"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:09b3014013f05cd89828640d3a1db5f829cc24ad8fa81b6e42b2c04685a0c9d4"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3789d452a9d17a841c7f4f97bbcba51a21f957ea35641a4c98507520e6b6a068"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:44e89938d36acc4495735af70a886d206a5bfdc80258f95b69b52f68b2968d9e"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:90ed9da805e52985b0202aed4f352842c907c6b4fc6c7c109c6e646c32e2f43b"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c3a9ccdfee4ae59cf9bf1822777e763bc097ed208f4901e21537fca1070e1391"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:de9173f8cc0efd88ac2a89b3b6c287a9a0011cdc2f53b2a12c28d6fd55f9f81c"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0611f4822674f3269e507a307236efb62ae5a828fcfc923ac85fe22ca19fd7c8"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:522b79c7db547767ca923e441c19b97a2157f2f494272a119c854bba4804e186"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:1ea41c0229f3f5a3844ad0857a83a9f869aa7b840448fa0c200e6bcf85d33d19"}, + {file = "psycopg_binary-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:8ea05b499278790a8fa0ff9854ab0de2542aca02d661ddff94e830df971ff640"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:94503b79f7da0b65c80d0dbb2f81dd78b300319ec2435d5e6dcf9622160bc2fa"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07a5f030e0902ec3e27d0506ceb01238c0aecbc73ecd7fa0ee55f86134600b5b"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e09d0d93d35c134704a2cb2b15f81ffc8174fd602f3e08f7b1a3d8896156cf0"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:649c1d33bedda431e0c1df646985fbbeb9274afa964e1aef4be053c0f23a2924"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c5774272f754605059521ff037a86e680342e3847498b0aa86b0f3560c70963c"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d391b70c9cc23f6e1142729772a011f364199d2c5ddc0d596f5f43316fbf982d"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f3f601f32244a677c7b029ec39412db2772ad04a28bc2cbb4b1f0931ed0ffad7"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0ae60e910531cfcc364a8f615a7941cac89efeb3f0fffe0c4824a6d11461eef7"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:7c43a773dd1a481dbb2fe64576aa303d80f328cce0eae5e3e4894947c41d1da7"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:5a327327f1188b3fbecac41bf1973a60b86b2eb237db10dc945bd3dc97ec39e4"}, + {file = "psycopg_binary-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:136c43f185244893a527540307167f5d3ef4e08786508afe45d6f146228f5aa9"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:a9387ab615f929e71ef0f4a8a51e986fa06236ccfa9f3ec98a88f60fbf230634"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3ff7489df5e06c12d1829544eaec64970fe27fe300f7cf04c8495fe682064688"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:9742580ecc8e1ac45164e98d32ca6df90da509c2d3ff26be245d94c430f92db4"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d45acedcaa58619355f18e0f42af542fcad3fd84ace4b8355d3a5dea23318578"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d88f32ff8c47cb7f4e7e7a9d1747dcee6f3baa19ed9afa9e5694fd2fb32b61ed"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:59d0163c4617a2c577cb34afbed93d7a45b8c8364e54b2bd2020ff25d5f5f860"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e750afe74e6c17b2c7046d2c3e3173b5a3f6080084671c8aa327215323df155b"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f26f113013c4dcfbfe9ced57b5bad2035dda1a7349f64bf726021968f9bccad3"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8309ee4569dced5e81df5aa2dcd48c7340c8dee603a66430f042dfbd2878edca"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c6464150e25b68ae3cb04c4e57496ea11ebfaae4d98126aea2f4702dd43e3c12"}, + {file = "psycopg_binary-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:716a586f99bbe4f710dc58b40069fcb33c7627e95cc6fc936f73c9235e07f9cf"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:fc5a189e89cbfff174588665bb18d28d2d0428366cc9dae5864afcaa2e57380b"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:083c2e182be433f290dc2c516fd72b9b47054fcd305cce791e0a50d9e93e06f2"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:ac230e3643d1c436a2dfb59ca84357dfc6862c9f372fc5dbd96bafecae581f9f"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d8c899a540f6c7585cee53cddc929dd4d2db90fd828e37f5d4017b63acbc1a5d"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50ff10ab8c0abdb5a5451b9315538865b50ba64c907742a1385fdf5f5772b73e"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:23d2594af848c1fd3d874a9364bef50730124e72df7bb145a20cb45e728c50ed"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ea4fe6b4ead3bbbe27244ea224fcd1f53cb119afc38b71a2f3ce570149a03e30"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:742ce48cde825b8e52fb1a658253d6d1ff66d152081cbc76aa45e2986534858d"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:e22bf6b54df994aff37ab52695d635f1ef73155e781eee1f5fa75bc08b58c8da"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8db9034cde3bcdafc66980f0130813f5c5d19e74b3f2a19fb3cfbc25ad113121"}, + {file = "psycopg_binary-3.3.2-cp313-cp313-win_amd64.whl", hash = "sha256:df65174c7cf6b05ea273ce955927d3270b3a6e27b0b12762b009ce6082b8d3fc"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9ca24062cd9b2270e4d77576042e9cc2b1d543f09da5aba1f1a3d016cea28390"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c749770da0947bc972e512f35366dd4950c0e34afad89e60b9787a37e97cb443"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:03b7cd73fb8c45d272a34ae7249713e32492891492681e3cf11dff9531cf37e9"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:43b130e3b6edcb5ee856c7167ccb8561b473308c870ed83978ae478613764f1c"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7c1feba5a8c617922321aef945865334e468337b8fc5c73074f5e63143013b5a"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cabb2a554d9a0a6bf84037d86ca91782f087dfff2a61298d0b00c19c0bc43f6d"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:74bc306c4b4df35b09bc8cecf806b271e1c5d708f7900145e4e54a2e5dedfed0"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:d79b0093f0fbf7a962d6a46ae292dc056c65d16a8ee9361f3cfbafd4c197ab14"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:1586e220be05547c77afc326741dd41cc7fba38a81f9931f616ae98865439678"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:458696a5fa5dad5b6fb5d5862c22454434ce4fe1cf66ca6c0de5f904cbc1ae3e"}, + {file = "psycopg_binary-3.3.2-cp314-cp314-win_amd64.whl", hash = "sha256:04bb2de4ba69d6f8395b446ede795e8884c040ec71d01dd07ac2b2d18d4153d1"}, ] [[package]] name = "psycopg-pool" -version = "3.2.7" +version = "3.3.0" description = "Connection Pool for Psycopg" optional = false -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "psycopg_pool-3.2.7-py3-none-any.whl", hash = "sha256:4b47bb59d887ef5da522eb63746b9f70e2faf967d34aac4f56ffc65e9606728f"}, - {file = "psycopg_pool-3.2.7.tar.gz", hash = "sha256:a77d531bfca238e49e5fb5832d65b98e69f2c62bfda3d2d4d833696bdc9ca54b"}, + {file = "psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063"}, + {file = "psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5"}, ] [package.dependencies] typing-extensions = ">=4.6" +[package.extras] +test = ["anyio (>=4.0)", "mypy (>=1.14)", "pproxy (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0)", "pytest-randomly (>=3.5)"] + [[package]] name = "pyasn1" version = "0.6.1" @@ -3491,19 +3532,19 @@ files = [ [[package]] name = "pydantic" -version = "2.12.3" +version = "2.12.5" description = "Data validation using Python type hints" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic-2.12.3-py3-none-any.whl", hash = "sha256:6986454a854bc3bc6e5443e1369e06a3a456af9d339eda45510f517d9ea5c6bf"}, - {file = "pydantic-2.12.3.tar.gz", hash = "sha256:1da1c82b0fc140bb0103bc1441ffe062154c8d38491189751ee00fd8ca65ce74"}, + {file = "pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d"}, + {file = "pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.41.4" +pydantic-core = "2.41.5" typing-extensions = ">=4.14.1" typing-inspection = ">=0.4.2" @@ -3513,129 +3554,133 @@ timezone = ["tzdata ; python_version >= \"3.9\" and platform_system == \"Windows [[package]] name = "pydantic-core" -version = "2.41.4" +version = "2.41.5" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:2442d9a4d38f3411f22eb9dd0912b7cbf4b7d5b6c92c4173b75d3e1ccd84e36e"}, - {file = "pydantic_core-2.41.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:30a9876226dda131a741afeab2702e2d127209bde3c65a2b8133f428bc5d006b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d55bbac04711e2980645af68b97d445cdbcce70e5216de444a6c4b6943ebcccd"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1d778fb7849a42d0ee5927ab0f7453bf9f85eef8887a546ec87db5ddb178945"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b65077a4693a98b90ec5ad8f203ad65802a1b9b6d4a7e48066925a7e1606706"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:62637c769dee16eddb7686bf421be48dfc2fae93832c25e25bc7242e698361ba"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dfe3aa529c8f501babf6e502936b9e8d4698502b2cfab41e17a028d91b1ac7b"}, - {file = "pydantic_core-2.41.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ca2322da745bf2eeb581fc9ea3bbb31147702163ccbcbf12a3bb630e4bf05e1d"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e8cd3577c796be7231dcf80badcf2e0835a46665eaafd8ace124d886bab4d700"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:1cae8851e174c83633f0833e90636832857297900133705ee158cf79d40f03e6"}, - {file = "pydantic_core-2.41.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a26d950449aae348afe1ac8be5525a00ae4235309b729ad4d3399623125b43c9"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win32.whl", hash = "sha256:0cf2a1f599efe57fa0051312774280ee0f650e11152325e41dfd3018ef2c1b57"}, - {file = "pydantic_core-2.41.4-cp310-cp310-win_amd64.whl", hash = "sha256:a8c2e340d7e454dc3340d3d2e8f23558ebe78c98aa8f68851b04dcb7bc37abdc"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:28ff11666443a1a8cf2a044d6a545ebffa8382b5f7973f22c36109205e65dc80"}, - {file = "pydantic_core-2.41.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:61760c3925d4633290292bad462e0f737b840508b4f722247d8729684f6539ae"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae547b7315d055b0de2ec3965643b0ab82ad0106a7ffd29615ee9f266a02827"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef9ee5471edd58d1fcce1c80ffc8783a650e3e3a193fe90d52e43bb4d87bff1f"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:15dd504af121caaf2c95cb90c0ebf71603c53de98305621b94da0f967e572def"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3a926768ea49a8af4d36abd6a8968b8790f7f76dd7cbd5a4c180db2b4ac9a3a2"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916b9b7d134bff5440098a4deb80e4cb623e68974a87883299de9124126c2a8"}, - {file = "pydantic_core-2.41.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5cf90535979089df02e6f17ffd076f07237efa55b7343d98760bde8743c4b265"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7533c76fa647fade2d7ec75ac5cc079ab3f34879626dae5689b27790a6cf5a5c"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:37e516bca9264cbf29612539801ca3cd5d1be465f940417b002905e6ed79d38a"}, - {file = "pydantic_core-2.41.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0c19cb355224037c83642429b8ce261ae108e1c5fbf5c028bac63c77b0f8646e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win32.whl", hash = "sha256:09c2a60e55b357284b5f31f5ab275ba9f7f70b7525e18a132ec1f9160b4f1f03"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_amd64.whl", hash = "sha256:711156b6afb5cb1cb7c14a2cc2c4a8b4c717b69046f13c6b332d8a0a8f41ca3e"}, - {file = "pydantic_core-2.41.4-cp311-cp311-win_arm64.whl", hash = "sha256:6cb9cf7e761f4f8a8589a45e49ed3c0d92d1d696a45a6feaee8c904b26efc2db"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:ab06d77e053d660a6faaf04894446df7b0a7e7aba70c2797465a0a1af00fc887"}, - {file = "pydantic_core-2.41.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c53ff33e603a9c1179a9364b0a24694f183717b2e0da2b5ad43c316c956901b2"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:304c54176af2c143bd181d82e77c15c41cbacea8872a2225dd37e6544dce9999"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:025ba34a4cf4fb32f917d5d188ab5e702223d3ba603be4d8aca2f82bede432a4"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9f5f30c402ed58f90c70e12eff65547d3ab74685ffe8283c719e6bead8ef53f"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dd96e5d15385d301733113bcaa324c8bcf111275b7675a9c6e88bfb19fc05e3b"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98f348cbb44fae6e9653c1055db7e29de67ea6a9ca03a5fa2c2e11a47cff0e47"}, - {file = "pydantic_core-2.41.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec22626a2d14620a83ca583c6f5a4080fa3155282718b6055c2ea48d3ef35970"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a95d4590b1f1a43bf33ca6d647b990a88f4a3824a8c4572c708f0b45a5290ed"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:f9672ab4d398e1b602feadcffcdd3af44d5f5e6ddc15bc7d15d376d47e8e19f8"}, - {file = "pydantic_core-2.41.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:84d8854db5f55fead3b579f04bda9a36461dab0730c5d570e1526483e7bb8431"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win32.whl", hash = "sha256:9be1c01adb2ecc4e464392c36d17f97e9110fbbc906bcbe1c943b5b87a74aabd"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_amd64.whl", hash = "sha256:d682cf1d22bab22a5be08539dca3d1593488a99998f9f412137bc323179067ff"}, - {file = "pydantic_core-2.41.4-cp312-cp312-win_arm64.whl", hash = "sha256:833eebfd75a26d17470b58768c1834dfc90141b7afc6eb0429c21fc5a21dcfb8"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:85e050ad9e5f6fe1004eec65c914332e52f429bc0ae12d6fa2092407a462c746"}, - {file = "pydantic_core-2.41.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7393f1d64792763a48924ba31d1e44c2cfbc05e3b1c2c9abb4ceeadd912cced"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94dab0940b0d1fb28bcab847adf887c66a27a40291eedf0b473be58761c9799a"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:de7c42f897e689ee6f9e93c4bec72b99ae3b32a2ade1c7e4798e690ff5246e02"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:664b3199193262277b8b3cd1e754fb07f2c6023289c815a1e1e8fb415cb247b1"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d95b253b88f7d308b1c0b417c4624f44553ba4762816f94e6986819b9c273fb2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1351f5bbdbbabc689727cb91649a00cb9ee7203e0a6e54e9f5ba9e22e384b84"}, - {file = "pydantic_core-2.41.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1affa4798520b148d7182da0615d648e752de4ab1a9566b7471bc803d88a062d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7b74e18052fea4aa8dea2fb7dbc23d15439695da6cbe6cfc1b694af1115df09d"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:285b643d75c0e30abda9dc1077395624f314a37e3c09ca402d4015ef5979f1a2"}, - {file = "pydantic_core-2.41.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f52679ff4218d713b3b33f88c89ccbf3a5c2c12ba665fb80ccc4192b4608dbab"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win32.whl", hash = "sha256:ecde6dedd6fff127c273c76821bb754d793be1024bc33314a120f83a3c69460c"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_amd64.whl", hash = "sha256:d081a1f3800f05409ed868ebb2d74ac39dd0c1ff6c035b5162356d76030736d4"}, - {file = "pydantic_core-2.41.4-cp313-cp313-win_arm64.whl", hash = "sha256:f8e49c9c364a7edcbe2a310f12733aad95b022495ef2a8d653f645e5d20c1564"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:ed97fd56a561f5eb5706cebe94f1ad7c13b84d98312a05546f2ad036bafe87f4"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a870c307bf1ee91fc58a9a61338ff780d01bfae45922624816878dce784095d2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25e97bc1f5f8f7985bdc2335ef9e73843bb561eb1fa6831fdfc295c1c2061cf"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_amd64.whl", hash = "sha256:d405d14bea042f166512add3091c1af40437c2e7f86988f3915fabd27b1e9cd2"}, - {file = "pydantic_core-2.41.4-cp313-cp313t-win_arm64.whl", hash = "sha256:19f3684868309db5263a11bace3c45d93f6f24afa2ffe75a647583df22a2ff89"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:e9205d97ed08a82ebb9a307e92914bb30e18cdf6f6b12ca4bedadb1588a0bfe1"}, - {file = "pydantic_core-2.41.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:82df1f432b37d832709fbcc0e24394bba04a01b6ecf1ee87578145c19cde12ac"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc3b4cc4539e055cfa39a3763c939f9d409eb40e85813257dcd761985a108554"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b1eb1754fce47c63d2ff57fdb88c351a6c0150995890088b33767a10218eaa4e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e6ab5ab30ef325b443f379ddb575a34969c333004fca5a1daa0133a6ffaad616"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:31a41030b1d9ca497634092b46481b937ff9397a86f9f51bd41c4767b6fc04af"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a44ac1738591472c3d020f61c6df1e4015180d6262ebd39bf2aeb52571b60f12"}, - {file = "pydantic_core-2.41.4-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d72f2b5e6e82ab8f94ea7d0d42f83c487dc159c5240d8f83beae684472864e2d"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c4d1e854aaf044487d31143f541f7aafe7b482ae72a022c664b2de2e466ed0ad"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:b568af94267729d76e6ee5ececda4e283d07bbb28e8148bb17adad93d025d25a"}, - {file = "pydantic_core-2.41.4-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6d55fb8b1e8929b341cc313a81a26e0d48aa3b519c1dbaadec3a6a2b4fcad025"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win32.whl", hash = "sha256:5b66584e549e2e32a1398df11da2e0a7eff45d5c2d9db9d5667c5e6ac764d77e"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_amd64.whl", hash = "sha256:557a0aab88664cc552285316809cab897716a372afaf8efdbef756f8b890e894"}, - {file = "pydantic_core-2.41.4-cp314-cp314-win_arm64.whl", hash = "sha256:3f1ea6f48a045745d0d9f325989d8abd3f1eaf47dd00485912d1a3a63c623a8d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6c1fe4c5404c448b13188dd8bd2ebc2bdd7e6727fa61ff481bcc2cca894018da"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:523e7da4d43b113bf8e7b49fa4ec0c35bf4fe66b2230bfc5c13cc498f12c6c3e"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5729225de81fb65b70fdb1907fcf08c75d498f4a6f15af005aabb1fdadc19dfa"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_amd64.whl", hash = "sha256:de2cfbb09e88f0f795fd90cf955858fc2c691df65b1f21f0aa00b99f3fbc661d"}, - {file = "pydantic_core-2.41.4-cp314-cp314t-win_arm64.whl", hash = "sha256:d34f950ae05a83e0ede899c595f312ca976023ea1db100cd5aa188f7005e3ab0"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:646e76293345954acea6966149683047b7b2ace793011922208c8e9da12b0062"}, - {file = "pydantic_core-2.41.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cc8e85a63085a137d286e2791037f5fdfff0aabb8b899483ca9c496dd5797338"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:692c622c8f859a17c156492783902d8370ac7e121a611bd6fe92cc71acf9ee8d"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d1e2906efb1031a532600679b424ef1d95d9f9fb507f813951f23320903adbd7"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04e2f7f8916ad3ddd417a7abdd295276a0bf216993d9318a5d61cc058209166"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df649916b81822543d1c8e0e1d079235f68acdc7d270c911e8425045a8cfc57e"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:66c529f862fdba70558061bb936fe00ddbaaa0c647fd26e4a4356ef1d6561891"}, - {file = "pydantic_core-2.41.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fc3b4c5a1fd3a311563ed866c2c9b62da06cb6398bee186484ce95c820db71cb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6e0fc40d84448f941df9b3334c4b78fe42f36e3bf631ad54c3047a0cdddc2514"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:44e7625332683b6c1c8b980461475cde9595eff94447500e80716db89b0da005"}, - {file = "pydantic_core-2.41.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:170ee6835f6c71081d031ef1c3b4dc4a12b9efa6a9540f93f95b82f3c7571ae8"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win32.whl", hash = "sha256:3adf61415efa6ce977041ba9745183c0e1f637ca849773afa93833e04b163feb"}, - {file = "pydantic_core-2.41.4-cp39-cp39-win_amd64.whl", hash = "sha256:a238dd3feee263eeaeb7dc44aea4ba1364682c4f9f9467e6af5596ba322c2332"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:a1b2cfec3879afb742a7b0bcfa53e4f22ba96571c9e54d6a3afe1052d17d843b"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:d175600d975b7c244af6eb9c9041f10059f20b8bbffec9e33fdd5ee3f67cdc42"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f184d657fa4947ae5ec9c47bd7e917730fa1cbb78195037e32dcbab50aca5ee"}, - {file = "pydantic_core-2.41.4-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ed810568aeffed3edc78910af32af911c835cc39ebbfacd1f0ab5dd53028e5c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:4f5d640aeebb438517150fdeec097739614421900e4a08db4a3ef38898798537"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:4a9ab037b71927babc6d9e7fc01aea9e66dc2a4a34dff06ef0724a4049629f94"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4dab9484ec605c3016df9ad4fd4f9a390bc5d816a3b10c6550f8424bb80b18c"}, - {file = "pydantic_core-2.41.4-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd8a5028425820731d8c6c098ab642d7b8b999758e24acae03ed38a66eca8335"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:1e5ab4fc177dd41536b3c32b2ea11380dd3d4619a385860621478ac2d25ceb00"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:3d88d0054d3fa11ce936184896bed3c1c5441d6fa483b498fac6a5d0dd6f64a9"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b2a054a8725f05b4b6503357e0ac1c4e8234ad3b0c2ac130d6ffc66f0e170e2"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0d9db5a161c99375a0c68c058e227bee1d89303300802601d76a3d01f74e258"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:6273ea2c8ffdac7b7fda2653c49682db815aebf4a89243a6feccf5e36c18c347"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:4c973add636efc61de22530b2ef83a65f39b6d6f656df97f678720e20de26caa"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b69d1973354758007f46cf2d44a4f3d0933f10b6dc9bf15cf1356e037f6f731a"}, - {file = "pydantic_core-2.41.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:3619320641fd212aaf5997b6ca505e97540b7e16418f4a241f44cdf108ffb50d"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:491535d45cd7ad7e4a2af4a5169b0d07bebf1adfd164b0368da8aa41e19907a5"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:54d86c0cada6aba4ec4c047d0e348cbad7063b87ae0f005d9f8c9ad04d4a92a2"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eca1124aced216b2500dc2609eade086d718e8249cb9696660ab447d50a758bd"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6c9024169becccf0cb470ada03ee578d7348c119a0d42af3dcf9eda96e3a247c"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:26895a4268ae5a2849269f4991cdc97236e4b9c010e51137becf25182daac405"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:ca4df25762cf71308c446e33c9b1fdca2923a3f13de616e2a949f38bf21ff5a8"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:5a28fcedd762349519276c36634e71853b4541079cab4acaaac60c4421827308"}, - {file = "pydantic_core-2.41.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c173ddcd86afd2535e2b695217e82191580663a1d1928239f877f5a1649ef39f"}, - {file = "pydantic_core-2.41.4.tar.gz", hash = "sha256:70e47929a9d4a1905a67e4b687d5946026390568a8e952b92824118063cee4d5"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:77b63866ca88d804225eaa4af3e664c5faf3568cea95360d21f4725ab6e07146"}, + {file = "pydantic_core-2.41.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dfa8a0c812ac681395907e71e1274819dec685fec28273a28905df579ef137e2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5921a4d3ca3aee735d9fd163808f5e8dd6c6972101e4adbda9a4667908849b97"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e25c479382d26a2a41b7ebea1043564a937db462816ea07afa8a44c0866d52f9"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f547144f2966e1e16ae626d8ce72b4cfa0caedc7fa28052001c94fb2fcaa1c52"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f52298fbd394f9ed112d56f3d11aabd0d5bd27beb3084cc3d8ad069483b8941"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:100baa204bb412b74fe285fb0f3a385256dad1d1879f0a5cb1499ed2e83d132a"}, + {file = "pydantic_core-2.41.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05a2c8852530ad2812cb7914dc61a1125dc4e06252ee98e5638a12da6cc6fb6c"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:29452c56df2ed968d18d7e21f4ab0ac55e71dc59524872f6fc57dcf4a3249ed2"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:d5160812ea7a8a2ffbe233d8da666880cad0cbaf5d4de74ae15c313213d62556"}, + {file = "pydantic_core-2.41.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:df3959765b553b9440adfd3c795617c352154e497a4eaf3752555cfb5da8fc49"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win32.whl", hash = "sha256:1f8d33a7f4d5a7889e60dc39856d76d09333d8a6ed0f5f1190635cbec70ec4ba"}, + {file = "pydantic_core-2.41.5-cp310-cp310-win_amd64.whl", hash = "sha256:62de39db01b8d593e45871af2af9e497295db8d73b085f6bfd0b18c83c70a8f9"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6"}, + {file = "pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594"}, + {file = "pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe"}, + {file = "pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7"}, + {file = "pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294"}, + {file = "pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815"}, + {file = "pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9"}, + {file = "pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586"}, + {file = "pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e"}, + {file = "pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11"}, + {file = "pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a"}, + {file = "pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375"}, + {file = "pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07"}, + {file = "pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf"}, + {file = "pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c"}, + {file = "pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:8bfeaf8735be79f225f3fefab7f941c712aaca36f1128c9d7e2352ee1aa87bdf"}, + {file = "pydantic_core-2.41.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:346285d28e4c8017da95144c7f3acd42740d637ff41946af5ce6e5e420502dd5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a75dafbf87d6276ddc5b2bf6fae5254e3d0876b626eb24969a574fff9149ee5d"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:7b93a4d08587e2b7e7882de461e82b6ed76d9026ce91ca7915e740ecc7855f60"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e8465ab91a4bd96d36dde3263f06caa6a8a6019e4113f24dc753d79a8b3a3f82"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:299e0a22e7ae2b85c1a57f104538b2656e8ab1873511fd718a1c1c6f149b77b5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:707625ef0983fcfb461acfaf14de2067c5942c6bb0f3b4c99158bed6fedd3cf3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f41eb9797986d6ebac5e8edff36d5cef9de40def462311b3eb3eeded1431e425"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0384e2e1021894b1ff5a786dbf94771e2986ebe2869533874d7e43bc79c6f504"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:f0cd744688278965817fd0839c4a4116add48d23890d468bc436f78beb28abf5"}, + {file = "pydantic_core-2.41.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:753e230374206729bf0a807954bcc6c150d3743928a73faffee51ac6557a03c3"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win32.whl", hash = "sha256:873e0d5b4fb9b89ef7c2d2a963ea7d02879d9da0da8d9d4933dee8ee86a8b460"}, + {file = "pydantic_core-2.41.5-cp39-cp39-win_amd64.whl", hash = "sha256:e4f4a984405e91527a0d62649ee21138f8e3d0ef103be488c1dc11a80d7f184b"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2"}, + {file = "pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56"}, + {file = "pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b5819cd790dbf0c5eb9f82c73c16b39a65dd6dd4d1439dcdea7816ec9adddab8"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:5a4e67afbc95fa5c34cf27d9089bca7fcab4e51e57278d710320a70b956d1b9a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ece5c59f0ce7d001e017643d8d24da587ea1f74f6993467d85ae8a5ef9d4f42b"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:16f80f7abe3351f8ea6858914ddc8c77e02578544a0ebc15b4c2e1a0e813b0b2"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:33cb885e759a705b426baada1fe68cbb0a2e68e34c5d0d0289a364cf01709093"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:c8d8b4eb992936023be7dee581270af5c6e0697a8559895f527f5b7105ecd36a"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:242a206cd0318f95cd21bdacff3fcc3aab23e79bba5cac3db5a841c9ef9c6963"}, + {file = "pydantic_core-2.41.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:d3a978c4f57a597908b7e697229d996d77a6d3c94901e9edee593adada95ce1a"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f"}, + {file = "pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51"}, + {file = "pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e"}, ] [package.dependencies] @@ -3643,14 +3688,14 @@ typing-extensions = ">=4.14.1" [[package]] name = "pydantic-settings" -version = "2.11.0" +version = "2.12.0" description = "Settings management using Pydantic" optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "pydantic_settings-2.11.0-py3-none-any.whl", hash = "sha256:fe2cea3413b9530d10f3a5875adffb17ada5c1e1bab0b2885546d7310415207c"}, - {file = "pydantic_settings-2.11.0.tar.gz", hash = "sha256:d0e87a1c7d33593beb7194adb8470fc426e95ba02af83a0f23474a04c9a08180"}, + {file = "pydantic_settings-2.12.0-py3-none-any.whl", hash = "sha256:fddb9fd99a5b18da837b29710391e945b1e30c135477f484084ee513adb93809"}, + {file = "pydantic_settings-2.12.0.tar.gz", hash = "sha256:005538ef951e3c2a68e1c08b292b5f2e71490def8589d4221b95dab00dafcfd0"}, ] [package.dependencies] @@ -3718,14 +3763,14 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pypdf" -version = "6.4.0" +version = "6.4.2" description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "pypdf-6.4.0-py3-none-any.whl", hash = "sha256:55ab9837ed97fd7fcc5c131d52fcc2223bc5c6b8a1488bbf7c0e27f1f0023a79"}, - {file = "pypdf-6.4.0.tar.gz", hash = "sha256:4769d471f8ddc3341193ecc5d6560fa44cf8cd0abfabf21af4e195cc0c224072"}, + {file = "pypdf-6.4.2-py3-none-any.whl", hash = "sha256:014dcff867fd99fc0b6fc90ed1f7e1347ef2317ae038a489c2caa64106d268f4"}, + {file = "pypdf-6.4.2.tar.gz", hash = "sha256:c466ff1272ffb4712c2348d2bbc3019bc93f1c62ccfaf50808e3b9f13c3dc527"}, ] [package.extras] @@ -3738,25 +3783,34 @@ image = ["Pillow (>=8.0.0)"] [[package]] name = "pypdfium2" -version = "5.0.0" +version = "5.2.0" description = "Python bindings to PDFium" optional = false python-versions = ">=3.6" groups = ["main"] files = [ - {file = "pypdfium2-5.0.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c477d68a0f32a22d6477d9aa9c5c2afae6512af1d5455a9ea561a224908f16ae"}, - {file = "pypdfium2-5.0.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:753954aeb8e130507cb3b408da68f66a25c4b7e510bdfaf5458975ab8c8285c4"}, - {file = "pypdfium2-5.0.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1e50b08bde1c6c93685022ac72746ff099a7543f178d35e0a834f7e36bf401d"}, - {file = "pypdfium2-5.0.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:baf715937b3bc78312c2d07ab2b06684f57156adadc8e849f5892724f892648e"}, - {file = "pypdfium2-5.0.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f216423de641187c4e322992f3a97afc5ffa63b72d4ad30f8189cfa783c9d781"}, - {file = "pypdfium2-5.0.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4445d83ae3c6688667feba568b7b390b948c4a06ab94e576ad3b029b5567b44c"}, - {file = "pypdfium2-5.0.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3b1cbc217a6accfab005806b53e467044f83fe61133df01a4fde94334e4655ac"}, - {file = "pypdfium2-5.0.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:2f050cca56c4d85c24dcb572344cf5e54ebcd0a0dd351fcf6b5117e72474382c"}, - {file = "pypdfium2-5.0.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:4ee80e08a5c93a8e0f9e26a1978d4e0a31f0122a33351c260a6e436300d95075"}, - {file = "pypdfium2-5.0.0-py3-none-win32.whl", hash = "sha256:aafb55d57f03c8cf482557ed421d40aed943cd563628a3df8515f301725f8e49"}, - {file = "pypdfium2-5.0.0-py3-none-win_amd64.whl", hash = "sha256:de2201d4e9e423779d2e3b2c2368591d6826153a009146eaa105b501a213b299"}, - {file = "pypdfium2-5.0.0-py3-none-win_arm64.whl", hash = "sha256:0a2a473fe95802e7a5f4140f25e5cd036cf17f060f27ee2d28c3977206add763"}, - {file = "pypdfium2-5.0.0.tar.gz", hash = "sha256:666f66e8170f5502feac3b31c5c05a3697989c10e65e1a8503bf8dff8936b125"}, + {file = "pypdfium2-5.2.0-py3-none-android_23_arm64_v8a.whl", hash = "sha256:1ba4187a45ce4cf08f2a8c7e0f8970c36b9aa1770c8a3412a70781c1d80fb145"}, + {file = "pypdfium2-5.2.0-py3-none-android_23_armeabi_v7a.whl", hash = "sha256:80c55e10a8c9242f0901d35a9a306dd09accce8e497507bb23fcec017d45fe2e"}, + {file = "pypdfium2-5.2.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:73523ae69cd95c084c1342096893b2143ea73c36fdde35494780ba431e6a7d6e"}, + {file = "pypdfium2-5.2.0-py3-none-macosx_11_0_x86_64.whl", hash = "sha256:19c501d22ef5eb98e42416d22cc3ac66d4808b436e3d06686392f24d8d9f708d"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ed15a3f58d6ee4905f0d0a731e30b381b457c30689512589c7f57950b0cdcec"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:329cd1e9f068e8729e0d0b79a070d6126f52bc48ff1e40505cb207a5e20ce0ba"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:325259759886e66619504df4721fef3b8deabf8a233e4f4a66e0c32ebae60c2f"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5683e8f08ab38ed05e0e59e611451ec74332803d4e78f8c45658ea1d372a17af"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da4815426a5adcf03bf4d2c5f26c0ff8109dbfaf2c3415984689931bc6006ef9"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_27_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64bf5c039b2c314dab1fd158bfff99db96299a5b5c6d96fc056071166056f1de"}, + {file = "pypdfium2-5.2.0-py3-none-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:76b42a17748ac7dc04d5ef04d0561c6a0a4b546d113ec1d101d59650c6a340f7"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:9d4367d471439fae846f0aba91ff9e8d66e524edcf3c8d6e02fe96fa306e13b9"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:613f6bb2b47d76b66c0bf2ca581c7c33e3dd9dcb29d65d8c34fef4135f933149"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:c03fad3f2fa68d358f5dd4deb07e438482fa26fae439c49d127576d969769ca1"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_ppc64le.whl", hash = "sha256:f10be1900ae21879d02d9f4d58c2d2db3a2e6da611736a8e9decc22d1fb02909"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_riscv64.whl", hash = "sha256:97c1a126d30378726872f94866e38c055740cae80313638dafd1cd448d05e7c0"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_s390x.whl", hash = "sha256:c369f183a90781b788af9a357a877bc8caddc24801e8346d0bf23f3295f89f3a"}, + {file = "pypdfium2-5.2.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b391f1cceb454934b612a05b54e90f98aafeffe5e73830d71700b17f0812226b"}, + {file = "pypdfium2-5.2.0-py3-none-win32.whl", hash = "sha256:c68067938f617c37e4d17b18de7cac231fc7ce0eb7b6653b7283ebe8764d4999"}, + {file = "pypdfium2-5.2.0-py3-none-win_amd64.whl", hash = "sha256:eb0591b720e8aaeab9475c66d653655ec1be0464b946f3f48a53922e843f0f3b"}, + {file = "pypdfium2-5.2.0-py3-none-win_arm64.whl", hash = "sha256:5dd1ef579f19fa3719aee4959b28bda44b1072405756708b5e83df8806a19521"}, + {file = "pypdfium2-5.2.0.tar.gz", hash = "sha256:43863625231ce999c1ebbed6721a88de818b2ab4d909c1de558d413b9a400256"}, ] [[package]] @@ -3782,14 +3836,14 @@ nodejs = ["nodejs-wheel-binaries"] [[package]] name = "pytest" -version = "9.0.1" +version = "9.0.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.10" groups = ["dev"] files = [ - {file = "pytest-9.0.1-py3-none-any.whl", hash = "sha256:67be0030d194df2dfa7b556f2e56fb3c3315bd5c8822c6951162b92b32ce7dad"}, - {file = "pytest-9.0.1.tar.gz", hash = "sha256:3e9c069ea73583e255c3b21cf46b8d3c56f6e3a1a8f6da94ccb0fcf57b9d73c8"}, + {file = "pytest-9.0.2-py3-none-any.whl", hash = "sha256:711ffd45bf766d5264d487b917733b453d917afd2b0ad65223959f59089f875b"}, + {file = "pytest-9.0.2.tar.gz", hash = "sha256:75186651a92bd89611d1d9fc20f0b4345fd827c41ccd5c299a868a05d70edf11"}, ] [package.dependencies] @@ -3875,6 +3929,27 @@ pycrypto = ["pycrypto (>=2.6.0,<2.7.0)"] pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)"] test = ["pytest", "pytest-cov"] +[[package]] +name = "python-keycloak" +version = "5.8.1" +description = "python-keycloak is a Python package providing access to the Keycloak API." +optional = false +python-versions = "<4.0,>=3.9" +groups = ["main"] +files = [ + {file = "python_keycloak-5.8.1-py3-none-any.whl", hash = "sha256:f80accf3e63b6c907f0f873ffac7a07705bd89d935520ba235259ba81b9ed864"}, + {file = "python_keycloak-5.8.1.tar.gz", hash = "sha256:b99a1efc7eb8715c3a7d915005728f8ba2ee03c81cdf12210c65ce794cd148ad"}, +] + +[package.dependencies] +aiofiles = ">=24.1.0" +async-property = ">=0.2.2" +deprecation = ">=2.1.0" +httpx = ">=0.23.2" +jwcrypto = ">=1.5.4" +requests = ">=2.20.0" +requests-toolbelt = ">=0.6.0" + [[package]] name = "python-multipart" version = "0.0.20" @@ -4038,127 +4113,127 @@ typing-extensions = {version = ">=4.4.0", markers = "python_version < \"3.13\""} [[package]] name = "regex" -version = "2025.10.23" +version = "2025.11.3" description = "Alternative regular expression module, to replace re." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:17bbcde374bef1c5fad9b131f0e28a6a24856dd90368d8c0201e2b5a69533daa"}, - {file = "regex-2025.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b4e10434279cc8567f99ca6e018e9025d14f2fded2a603380b6be2090f476426"}, - {file = "regex-2025.10.23-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c9bb421cbe7012c744a5a56cf4d6c80829c72edb1a2991677299c988d6339c8"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:275cd1c2ed8c4a78ebfa489618d7aee762e8b4732da73573c3e38236ec5f65de"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7b426ae7952f3dc1e73a86056d520bd4e5f021397484a6835902fc5648bcacce"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:c5cdaf5b6d37c7da1967dbe729d819461aab6a98a072feef65bbcff0a6e60649"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bfeff0b08f296ab28b4332a7e03ca31c437ee78b541ebc874bbf540e5932f8d"}, - {file = "regex-2025.10.23-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f97236a67307b775f30a74ef722b64b38b7ab7ba3bb4a2508518a5de545459c"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:be19e7de499940cd72475fb8e46ab2ecb1cf5906bebdd18a89f9329afb1df82f"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:883df76ee42d9ecb82b37ff8d01caea5895b3f49630a64d21111078bbf8ef64c"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2e9117d1d35fc2addae6281019ecc70dc21c30014b0004f657558b91c6a8f1a7"}, - {file = "regex-2025.10.23-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0ff1307f531a5d8cf5c20ea517254551ff0a8dc722193aab66c656c5a900ea68"}, - {file = "regex-2025.10.23-cp310-cp310-win32.whl", hash = "sha256:7888475787cbfee4a7cd32998eeffe9a28129fa44ae0f691b96cb3939183ef41"}, - {file = "regex-2025.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ec41a905908496ce4906dab20fb103c814558db1d69afc12c2f384549c17936a"}, - {file = "regex-2025.10.23-cp310-cp310-win_arm64.whl", hash = "sha256:b2b7f19a764d5e966d5a62bf2c28a8b4093cc864c6734510bdb4aeb840aec5e6"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6c531155bf9179345e85032052a1e5fe1a696a6abf9cea54b97e8baefff970fd"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:912e9df4e89d383681268d38ad8f5780d7cccd94ba0e9aa09ca7ab7ab4f8e7eb"}, - {file = "regex-2025.10.23-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4f375c61bfc3138b13e762fe0ae76e3bdca92497816936534a0177201666f44f"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e248cc9446081119128ed002a3801f8031e0c219b5d3c64d3cc627da29ac0a33"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b52bf9282fdf401e4f4e721f0f61fc4b159b1307244517789702407dd74e38ca"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5c084889ab2c59765a0d5ac602fd1c3c244f9b3fcc9a65fdc7ba6b74c5287490"}, - {file = "regex-2025.10.23-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d80e8eb79009bdb0936658c44ca06e2fbbca67792013e3818eea3f5f228971c2"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b6f259118ba87b814a8ec475380aee5f5ae97a75852a3507cf31d055b01b5b40"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:9b8c72a242683dcc72d37595c4f1278dfd7642b769e46700a8df11eab19dfd82"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a8d7b7a0a3df9952f9965342159e0c1f05384c0f056a47ce8b61034f8cecbe83"}, - {file = "regex-2025.10.23-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:413bfea20a484c524858125e92b9ce6ffdd0a4b97d4ff96b5859aa119b0f1bdd"}, - {file = "regex-2025.10.23-cp311-cp311-win32.whl", hash = "sha256:f76deef1f1019a17dad98f408b8f7afc4bd007cbe835ae77b737e8c7f19ae575"}, - {file = "regex-2025.10.23-cp311-cp311-win_amd64.whl", hash = "sha256:59bba9f7125536f23fdab5deeea08da0c287a64c1d3acc1c7e99515809824de8"}, - {file = "regex-2025.10.23-cp311-cp311-win_arm64.whl", hash = "sha256:b103a752b6f1632ca420225718d6ed83f6a6ced3016dd0a4ab9a6825312de566"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7a44d9c00f7a0a02d3b777429281376370f3d13d2c75ae74eb94e11ebcf4a7fc"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b83601f84fde939ae3478bb32a3aef36f61b58c3208d825c7e8ce1a735f143f2"}, - {file = "regex-2025.10.23-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ec13647907bb9d15fd192bbfe89ff06612e098a5709e7d6ecabbdd8f7908fc45"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78d76dd2957d62501084e7012ddafc5fcd406dd982b7a9ca1ea76e8eaaf73e7e"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:8668e5f067e31a47699ebb354f43aeb9c0ef136f915bd864243098524482ac43"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a32433fe3deb4b2d8eda88790d2808fed0dc097e84f5e683b4cd4f42edef6cca"}, - {file = "regex-2025.10.23-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d97d73818c642c938db14c0668167f8d39520ca9d983604575ade3fda193afcc"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bca7feecc72ee33579e9f6ddf8babbe473045717a0e7dbc347099530f96e8b9a"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7e24af51e907d7457cc4a72691ec458320b9ae67dc492f63209f01eecb09de32"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:d10bcde58bbdf18146f3a69ec46dd03233b94a4a5632af97aa5378da3a47d288"}, - {file = "regex-2025.10.23-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:44383bc0c933388516c2692c9a7503e1f4a67e982f20b9a29d2fb70c6494f147"}, - {file = "regex-2025.10.23-cp312-cp312-win32.whl", hash = "sha256:6040a86f95438a0114bba16e51dfe27f1bc004fd29fe725f54a586f6d522b079"}, - {file = "regex-2025.10.23-cp312-cp312-win_amd64.whl", hash = "sha256:436b4c4352fe0762e3bfa34a5567079baa2ef22aa9c37cf4d128979ccfcad842"}, - {file = "regex-2025.10.23-cp312-cp312-win_arm64.whl", hash = "sha256:f4b1b1991617055b46aff6f6db24888c1f05f4db9801349d23f09ed0714a9335"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:b7690f95404a1293923a296981fd943cca12c31a41af9c21ba3edd06398fc193"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1a32d77aeaea58a13230100dd8797ac1a84c457f3af2fdf0d81ea689d5a9105b"}, - {file = "regex-2025.10.23-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b24b29402f264f70a3c81f45974323b41764ff7159655360543b7cabb73e7d2f"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:563824a08c7c03d96856d84b46fdb3bbb7cfbdf79da7ef68725cda2ce169c72a"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a0ec8bdd88d2e2659c3518087ee34b37e20bd169419ffead4240a7004e8ed03b"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b577601bfe1d33913fcd9276d7607bbac827c4798d9e14d04bf37d417a6c41cb"}, - {file = "regex-2025.10.23-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7c9f2c68ac6cb3de94eea08a437a75eaa2bd33f9e97c84836ca0b610a5804368"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:89f8b9ea3830c79468e26b0e21c3585f69f105157c2154a36f6b7839f8afb351"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:98fd84c4e4ea185b3bb5bf065261ab45867d8875032f358a435647285c722673"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:1e11d3e5887b8b096f96b4154dfb902f29c723a9556639586cd140e77e28b313"}, - {file = "regex-2025.10.23-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f13450328a6634348d47a88367e06b64c9d84980ef6a748f717b13f8ce64e87"}, - {file = "regex-2025.10.23-cp313-cp313-win32.whl", hash = "sha256:37be9296598a30c6a20236248cb8b2c07ffd54d095b75d3a2a2ee5babdc51df1"}, - {file = "regex-2025.10.23-cp313-cp313-win_amd64.whl", hash = "sha256:ea7a3c283ce0f06fe789365841e9174ba05f8db16e2fd6ae00a02df9572c04c0"}, - {file = "regex-2025.10.23-cp313-cp313-win_arm64.whl", hash = "sha256:d9a4953575f300a7bab71afa4cd4ac061c7697c89590a2902b536783eeb49a4f"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:7d6606524fa77b3912c9ef52a42ef63c6cfbfc1077e9dc6296cd5da0da286044"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:c037aadf4d64bdc38af7db3dbd34877a057ce6524eefcb2914d6d41c56f968cc"}, - {file = "regex-2025.10.23-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:99018c331fb2529084a0c9b4c713dfa49fafb47c7712422e49467c13a636c656"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd8aba965604d70306eb90a35528f776e59112a7114a5162824d43b76fa27f58"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:238e67264b4013e74136c49f883734f68656adf8257bfa13b515626b31b20f8e"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b2eb48bd9848d66fd04826382f5e8491ae633de3233a3d64d58ceb4ecfa2113a"}, - {file = "regex-2025.10.23-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d36591ce06d047d0c0fe2fc5f14bfbd5b4525d08a7b6a279379085e13f0e3d0e"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5d4ece8628d6e364302006366cea3ee887db397faebacc5dacf8ef19e064cf8"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:39a7e8083959cb1c4ff74e483eecb5a65d3b3e1d821b256e54baf61782c906c6"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:842d449a8fefe546f311656cf8c0d6729b08c09a185f1cad94c756210286d6a8"}, - {file = "regex-2025.10.23-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:d614986dc68506be8f00474f4f6960e03e4ca9883f7df47744800e7d7c08a494"}, - {file = "regex-2025.10.23-cp313-cp313t-win32.whl", hash = "sha256:a5b7a26b51a9df473ec16a1934d117443a775ceb7b39b78670b2e21893c330c9"}, - {file = "regex-2025.10.23-cp313-cp313t-win_amd64.whl", hash = "sha256:ce81c5544a5453f61cb6f548ed358cfb111e3b23f3cd42d250a4077a6be2a7b6"}, - {file = "regex-2025.10.23-cp313-cp313t-win_arm64.whl", hash = "sha256:e9bf7f6699f490e4e43c44757aa179dab24d1960999c84ab5c3d5377714ed473"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:5b5cb5b6344c4c4c24b2dc87b0bfee78202b07ef7633385df70da7fcf6f7cec6"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a6ce7973384c37bdf0f371a843f95a6e6f4e1489e10e0cf57330198df72959c5"}, - {file = "regex-2025.10.23-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2ee3663f2c334959016b56e3bd0dd187cbc73f948e3a3af14c3caaa0c3035d10"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2003cc82a579107e70d013482acce8ba773293f2db534fb532738395c557ff34"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:182c452279365a93a9f45874f7f191ec1c51e1f1eb41bf2b16563f1a40c1da3a"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b1249e9ff581c5b658c8f0437f883b01f1edcf424a16388591e7c05e5e9e8b0c"}, - {file = "regex-2025.10.23-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2b841698f93db3ccc36caa1900d2a3be281d9539b822dc012f08fc80b46a3224"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:956d89e0c92d471e8f7eee73f73fdff5ed345886378c45a43175a77538a1ffe4"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5c259cb363299a0d90d63b5c0d7568ee98419861618a95ee9d91a41cb9954462"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:185d2b18c062820b3a40d8fefa223a83f10b20a674bf6e8c4a432e8dfd844627"}, - {file = "regex-2025.10.23-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:281d87fa790049c2b7c1b4253121edd80b392b19b5a3d28dc2a77579cb2a58ec"}, - {file = "regex-2025.10.23-cp314-cp314-win32.whl", hash = "sha256:63b81eef3656072e4ca87c58084c7a9c2b81d41a300b157be635a8a675aacfb8"}, - {file = "regex-2025.10.23-cp314-cp314-win_amd64.whl", hash = "sha256:0967c5b86f274800a34a4ed862dfab56928144d03cb18821c5153f8777947796"}, - {file = "regex-2025.10.23-cp314-cp314-win_arm64.whl", hash = "sha256:c70dfe58b0a00b36aa04cdb0f798bf3e0adc31747641f69e191109fd8572c9a9"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:1f5799ea1787aa6de6c150377d11afad39a38afd033f0c5247aecb997978c422"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:a9639ab7540cfea45ef57d16dcbea2e22de351998d614c3ad2f9778fa3bdd788"}, - {file = "regex-2025.10.23-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:08f52122c352eb44c3421dab78b9b73a8a77a282cc8314ae576fcaa92b780d10"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ebf1baebef1c4088ad5a5623decec6b52950f0e4d7a0ae4d48f0a99f8c9cb7d7"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:16b0f1c2e2d566c562d5c384c2b492646be0a19798532fdc1fdedacc66e3223f"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7ada5d9dceafaab92646aa00c10a9efd9b09942dd9b0d7c5a4b73db92cc7e61"}, - {file = "regex-2025.10.23-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3a36b4005770044bf08edecc798f0e41a75795b9e7c9c12fe29da8d792ef870c"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:af7b2661dcc032da1fae82069b5ebf2ac1dfcd5359ef8b35e1367bfc92181432"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:1cb976810ac1416a67562c2e5ba0accf6f928932320fef302e08100ed681b38e"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:1a56a54be3897d62f54290190fbcd754bff6932934529fbf5b29933da28fcd43"}, - {file = "regex-2025.10.23-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8f3e6d202fb52c2153f532043bbcf618fd177df47b0b306741eb9b60ba96edc3"}, - {file = "regex-2025.10.23-cp314-cp314t-win32.whl", hash = "sha256:1fa1186966b2621b1769fd467c7b22e317e6ba2d2cdcecc42ea3089ef04a8521"}, - {file = "regex-2025.10.23-cp314-cp314t-win_amd64.whl", hash = "sha256:08a15d40ce28362eac3e78e83d75475147869c1ff86bc93285f43b4f4431a741"}, - {file = "regex-2025.10.23-cp314-cp314t-win_arm64.whl", hash = "sha256:a93e97338e1c8ea2649e130dcfbe8cd69bba5e1e163834752ab64dcb4de6d5ed"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d8d286760ee5b77fd21cf6b33cc45e0bffd1deeda59ca65b9be996f590a9828a"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9e72e3b84b170fec02193d32620a0a7060a22e52c46e45957dcd14742e0d28fb"}, - {file = "regex-2025.10.23-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ec506e8114fa12d21616deb44800f536d6bf2e1a69253dbf611f69af92395c99"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d7e481f9710e8e24228ce2c77b41db7662a3f68853395da86a292b49dadca2aa"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4663ff2fc367735ae7b90b4f0e05b25554446df4addafc76fdaacaaa0ba852b5"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0879dd3251a42d2e9b938e1e03b1e9f60de90b4d153015193f5077a376a18439"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:651c58aecbab7e97bdf8ec76298a28d2bf2b6238c099ec6bf32e6d41e2f9a9cb"}, - {file = "regex-2025.10.23-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ceabc62a0e879169cd1bf066063bd6991c3e41e437628936a2ce66e0e2071c32"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:bfdf4e9aa3e7b7d02fda97509b4ceeed34542361694ecc0a81db1688373ecfbd"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:92f565ff9beb9f51bc7cc8c578a7e92eb5c4576b69043a4c58cd05d73fda83c5"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:abbea548b1076eaf8635caf1071c9d86efdf0fa74abe71fca26c05a2d64cda80"}, - {file = "regex-2025.10.23-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:33535dcf34f47821381e341f7b715cbd027deda4223af4d3932adcd371d3192a"}, - {file = "regex-2025.10.23-cp39-cp39-win32.whl", hash = "sha256:345c9df49a15bf6460534b104b336581bc5f35c286cac526416e7a63d389b09b"}, - {file = "regex-2025.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:f668fe1fd3358c5423355a289a4a003e58005ce829d217b828f80bd605a90145"}, - {file = "regex-2025.10.23-cp39-cp39-win_arm64.whl", hash = "sha256:07a3fd25d9074923e4d7258b551ae35ab6bdfe01904b8f0d5341c7d8b20eb18d"}, - {file = "regex-2025.10.23.tar.gz", hash = "sha256:8cbaf8ceb88f96ae2356d01b9adf5e6306fa42fa6f7eab6b97794e37c959ac26"}, + {file = "regex-2025.11.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2b441a4ae2c8049106e8b39973bfbddfb25a179dda2bdb99b0eeb60c40a6a3af"}, + {file = "regex-2025.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2fa2eed3f76677777345d2f81ee89f5de2f5745910e805f7af7386a920fa7313"}, + {file = "regex-2025.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8b4a27eebd684319bdf473d39f1d79eed36bf2cd34bd4465cdb4618d82b3d56"}, + {file = "regex-2025.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cf77eac15bd264986c4a2c63353212c095b40f3affb2bc6b4ef80c4776c1a28"}, + {file = "regex-2025.11.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b7f9ee819f94c6abfa56ec7b1dbab586f41ebbdc0a57e6524bd5e7f487a878c7"}, + {file = "regex-2025.11.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:838441333bc90b829406d4a03cb4b8bf7656231b84358628b0406d803931ef32"}, + {file = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cfe6d3f0c9e3b7e8c0c694b24d25e677776f5ca26dce46fd6b0489f9c8339391"}, + {file = "regex-2025.11.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2ab815eb8a96379a27c3b6157fcb127c8f59c36f043c1678110cea492868f1d5"}, + {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:728a9d2d173a65b62bdc380b7932dd8e74ed4295279a8fe1021204ce210803e7"}, + {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:509dc827f89c15c66a0c216331260d777dd6c81e9a4e4f830e662b0bb296c313"}, + {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:849202cd789e5f3cf5dcc7822c34b502181b4824a65ff20ce82da5524e45e8e9"}, + {file = "regex-2025.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:b6f78f98741dcc89607c16b1e9426ee46ce4bf31ac5e6b0d40e81c89f3481ea5"}, + {file = "regex-2025.11.3-cp310-cp310-win32.whl", hash = "sha256:149eb0bba95231fb4f6d37c8f760ec9fa6fabf65bab555e128dde5f2475193ec"}, + {file = "regex-2025.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:ee3a83ce492074c35a74cc76cf8235d49e77b757193a5365ff86e3f2f93db9fd"}, + {file = "regex-2025.11.3-cp310-cp310-win_arm64.whl", hash = "sha256:38af559ad934a7b35147716655d4a2f79fcef2d695ddfe06a06ba40ae631fa7e"}, + {file = "regex-2025.11.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eadade04221641516fa25139273505a1c19f9bf97589a05bc4cfcd8b4a618031"}, + {file = "regex-2025.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:feff9e54ec0dd3833d659257f5c3f5322a12eee58ffa360984b716f8b92983f4"}, + {file = "regex-2025.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3b30bc921d50365775c09a7ed446359e5c0179e9e2512beec4a60cbcef6ddd50"}, + {file = "regex-2025.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f99be08cfead2020c7ca6e396c13543baea32343b7a9a5780c462e323bd8872f"}, + {file = "regex-2025.11.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6dd329a1b61c0ee95ba95385fb0c07ea0d3fe1a21e1349fa2bec272636217118"}, + {file = "regex-2025.11.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4c5238d32f3c5269d9e87be0cf096437b7622b6920f5eac4fd202468aaeb34d2"}, + {file = "regex-2025.11.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10483eefbfb0adb18ee9474498c9a32fcf4e594fbca0543bb94c48bac6183e2e"}, + {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:78c2d02bb6e1da0720eedc0bad578049cad3f71050ef8cd065ecc87691bed2b0"}, + {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:e6b49cd2aad93a1790ce9cffb18964f6d3a4b0b3dbdbd5de094b65296fce6e58"}, + {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:885b26aa3ee56433b630502dc3d36ba78d186a00cc535d3806e6bfd9ed3c70ab"}, + {file = "regex-2025.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:ddd76a9f58e6a00f8772e72cff8ebcff78e022be95edf018766707c730593e1e"}, + {file = "regex-2025.11.3-cp311-cp311-win32.whl", hash = "sha256:3e816cc9aac1cd3cc9a4ec4d860f06d40f994b5c7b4d03b93345f44e08cc68bf"}, + {file = "regex-2025.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:087511f5c8b7dfbe3a03f5d5ad0c2a33861b1fc387f21f6f60825a44865a385a"}, + {file = "regex-2025.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:1ff0d190c7f68ae7769cd0313fe45820ba07ffebfddfaa89cc1eb70827ba0ddc"}, + {file = "regex-2025.11.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:bc8ab71e2e31b16e40868a40a69007bc305e1109bd4658eb6cad007e0bf67c41"}, + {file = "regex-2025.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22b29dda7e1f7062a52359fca6e58e548e28c6686f205e780b02ad8ef710de36"}, + {file = "regex-2025.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3a91e4a29938bc1a082cc28fdea44be420bf2bebe2665343029723892eb073e1"}, + {file = "regex-2025.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:08b884f4226602ad40c5d55f52bf91a9df30f513864e0054bad40c0e9cf1afb7"}, + {file = "regex-2025.11.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3e0b11b2b2433d1c39c7c7a30e3f3d0aeeea44c2a8d0bae28f6b95f639927a69"}, + {file = "regex-2025.11.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:87eb52a81ef58c7ba4d45c3ca74e12aa4b4e77816f72ca25258a85b3ea96cb48"}, + {file = "regex-2025.11.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a12ab1f5c29b4e93db518f5e3872116b7e9b1646c9f9f426f777b50d44a09e8c"}, + {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7521684c8c7c4f6e88e35ec89680ee1aa8358d3f09d27dfbdf62c446f5d4c695"}, + {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:7fe6e5440584e94cc4b3f5f4d98a25e29ca12dccf8873679a635638349831b98"}, + {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:8e026094aa12b43f4fd74576714e987803a315c76edb6b098b9809db5de58f74"}, + {file = "regex-2025.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:435bbad13e57eb5606a68443af62bed3556de2f46deb9f7d4237bc2f1c9fb3a0"}, + {file = "regex-2025.11.3-cp312-cp312-win32.whl", hash = "sha256:3839967cf4dc4b985e1570fd8d91078f0c519f30491c60f9ac42a8db039be204"}, + {file = "regex-2025.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:e721d1b46e25c481dc5ded6f4b3f66c897c58d2e8cfdf77bbced84339108b0b9"}, + {file = "regex-2025.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:64350685ff08b1d3a6fff33f45a9ca183dc1d58bbfe4981604e70ec9801bbc26"}, + {file = "regex-2025.11.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c1e448051717a334891f2b9a620fe36776ebf3dd8ec46a0b877c8ae69575feb4"}, + {file = "regex-2025.11.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9b5aca4d5dfd7fbfbfbdaf44850fcc7709a01146a797536a8f84952e940cca76"}, + {file = "regex-2025.11.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:04d2765516395cf7dda331a244a3282c0f5ae96075f728629287dfa6f76ba70a"}, + {file = "regex-2025.11.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d9903ca42bfeec4cebedba8022a7c97ad2aab22e09573ce9976ba01b65e4361"}, + {file = "regex-2025.11.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:639431bdc89d6429f6721625e8129413980ccd62e9d3f496be618a41d205f160"}, + {file = "regex-2025.11.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f117efad42068f9715677c8523ed2be1518116d1c49b1dd17987716695181efe"}, + {file = "regex-2025.11.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4aecb6f461316adf9f1f0f6a4a1a3d79e045f9b71ec76055a791affa3b285850"}, + {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3b3a5f320136873cc5561098dfab677eea139521cb9a9e8db98b7e64aef44cbc"}, + {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:75fa6f0056e7efb1f42a1c34e58be24072cb9e61a601340cc1196ae92326a4f9"}, + {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:dbe6095001465294f13f1adcd3311e50dd84e5a71525f20a10bd16689c61ce0b"}, + {file = "regex-2025.11.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:454d9b4ae7881afbc25015b8627c16d88a597479b9dea82b8c6e7e2e07240dc7"}, + {file = "regex-2025.11.3-cp313-cp313-win32.whl", hash = "sha256:28ba4d69171fc6e9896337d4fc63a43660002b7da53fc15ac992abcf3410917c"}, + {file = "regex-2025.11.3-cp313-cp313-win_amd64.whl", hash = "sha256:bac4200befe50c670c405dc33af26dad5a3b6b255dd6c000d92fe4629f9ed6a5"}, + {file = "regex-2025.11.3-cp313-cp313-win_arm64.whl", hash = "sha256:2292cd5a90dab247f9abe892ac584cb24f0f54680c73fcb4a7493c66c2bf2467"}, + {file = "regex-2025.11.3-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:1eb1ebf6822b756c723e09f5186473d93236c06c579d2cc0671a722d2ab14281"}, + {file = "regex-2025.11.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1e00ec2970aab10dc5db34af535f21fcf32b4a31d99e34963419636e2f85ae39"}, + {file = "regex-2025.11.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:a4cb042b615245d5ff9b3794f56be4138b5adc35a4166014d31d1814744148c7"}, + {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:44f264d4bf02f3176467d90b294d59bf1db9fe53c141ff772f27a8b456b2a9ed"}, + {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7be0277469bf3bd7a34a9c57c1b6a724532a0d235cd0dc4e7f4316f982c28b19"}, + {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0d31e08426ff4b5b650f68839f5af51a92a5b51abd8554a60c2fbc7c71f25d0b"}, + {file = "regex-2025.11.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e43586ce5bd28f9f285a6e729466841368c4a0353f6fd08d4ce4630843d3648a"}, + {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:0f9397d561a4c16829d4e6ff75202c1c08b68a3bdbfe29dbfcdb31c9830907c6"}, + {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:dd16e78eb18ffdb25ee33a0682d17912e8cc8a770e885aeee95020046128f1ce"}, + {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:ffcca5b9efe948ba0661e9df0fa50d2bc4b097c70b9810212d6b62f05d83b2dd"}, + {file = "regex-2025.11.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:c56b4d162ca2b43318ac671c65bd4d563e841a694ac70e1a976ac38fcf4ca1d2"}, + {file = "regex-2025.11.3-cp313-cp313t-win32.whl", hash = "sha256:9ddc42e68114e161e51e272f667d640f97e84a2b9ef14b7477c53aac20c2d59a"}, + {file = "regex-2025.11.3-cp313-cp313t-win_amd64.whl", hash = "sha256:7a7c7fdf755032ffdd72c77e3d8096bdcb0eb92e89e17571a196f03d88b11b3c"}, + {file = "regex-2025.11.3-cp313-cp313t-win_arm64.whl", hash = "sha256:df9eb838c44f570283712e7cff14c16329a9f0fb19ca492d21d4b7528ee6821e"}, + {file = "regex-2025.11.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9697a52e57576c83139d7c6f213d64485d3df5bf84807c35fa409e6c970801c6"}, + {file = "regex-2025.11.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e18bc3f73bd41243c9b38a6d9f2366cd0e0137a9aebe2d8ff76c5b67d4c0a3f4"}, + {file = "regex-2025.11.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:61a08bcb0ec14ff4e0ed2044aad948d0659604f824cbd50b55e30b0ec6f09c73"}, + {file = "regex-2025.11.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9c30003b9347c24bcc210958c5d167b9e4f9be786cb380a7d32f14f9b84674f"}, + {file = "regex-2025.11.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4e1e592789704459900728d88d41a46fe3969b82ab62945560a31732ffc19a6d"}, + {file = "regex-2025.11.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6538241f45eb5a25aa575dbba1069ad786f68a4f2773a29a2bd3dd1f9de787be"}, + {file = "regex-2025.11.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bce22519c989bb72a7e6b36a199384c53db7722fe669ba891da75907fe3587db"}, + {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:66d559b21d3640203ab9075797a55165d79017520685fb407b9234d72ab63c62"}, + {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:669dcfb2e38f9e8c69507bace46f4889e3abbfd9b0c29719202883c0a603598f"}, + {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:32f74f35ff0f25a5021373ac61442edcb150731fbaa28286bbc8bb1582c89d02"}, + {file = "regex-2025.11.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e6c7a21dffba883234baefe91bc3388e629779582038f75d2a5be918e250f0ed"}, + {file = "regex-2025.11.3-cp314-cp314-win32.whl", hash = "sha256:795ea137b1d809eb6836b43748b12634291c0ed55ad50a7d72d21edf1cd565c4"}, + {file = "regex-2025.11.3-cp314-cp314-win_amd64.whl", hash = "sha256:9f95fbaa0ee1610ec0fc6b26668e9917a582ba80c52cc6d9ada15e30aa9ab9ad"}, + {file = "regex-2025.11.3-cp314-cp314-win_arm64.whl", hash = "sha256:dfec44d532be4c07088c3de2876130ff0fbeeacaa89a137decbbb5f665855a0f"}, + {file = "regex-2025.11.3-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:ba0d8a5d7f04f73ee7d01d974d47c5834f8a1b0224390e4fe7c12a3a92a78ecc"}, + {file = "regex-2025.11.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:442d86cf1cfe4faabf97db7d901ef58347efd004934da045c745e7b5bd57ac49"}, + {file = "regex-2025.11.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:fd0a5e563c756de210bb964789b5abe4f114dacae9104a47e1a649b910361536"}, + {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bf3490bcbb985a1ae97b2ce9ad1c0f06a852d5b19dde9b07bdf25bf224248c95"}, + {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3809988f0a8b8c9dcc0f92478d6501fac7200b9ec56aecf0ec21f4a2ec4b6009"}, + {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f4ff94e58e84aedb9c9fce66d4ef9f27a190285b451420f297c9a09f2b9abee9"}, + {file = "regex-2025.11.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7eb542fd347ce61e1321b0a6b945d5701528dca0cd9759c2e3bb8bd57e47964d"}, + {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:d6c2d5919075a1f2e413c00b056ea0c2f065b3f5fe83c3d07d325ab92dce51d6"}, + {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:3f8bf11a4827cc7ce5a53d4ef6cddd5ad25595d3c1435ef08f76825851343154"}, + {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:22c12d837298651e5550ac1d964e4ff57c3f56965fc1812c90c9fb2028eaf267"}, + {file = "regex-2025.11.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:62ba394a3dda9ad41c7c780f60f6e4a70988741415ae96f6d1bf6c239cf01379"}, + {file = "regex-2025.11.3-cp314-cp314t-win32.whl", hash = "sha256:4bf146dca15cdd53224a1bf46d628bd7590e4a07fbb69e720d561aea43a32b38"}, + {file = "regex-2025.11.3-cp314-cp314t-win_amd64.whl", hash = "sha256:adad1a1bcf1c9e76346e091d22d23ac54ef28e1365117d99521631078dfec9de"}, + {file = "regex-2025.11.3-cp314-cp314t-win_arm64.whl", hash = "sha256:c54f768482cef41e219720013cd05933b6f971d9562544d691c68699bf2b6801"}, + {file = "regex-2025.11.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:81519e25707fc076978c6143b81ea3dc853f176895af05bf7ec51effe818aeec"}, + {file = "regex-2025.11.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3bf28b1873a8af8bbb58c26cc56ea6e534d80053b41fb511a35795b6de507e6a"}, + {file = "regex-2025.11.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:856a25c73b697f2ce2a24e7968285579e62577a048526161a2c0f53090bea9f9"}, + {file = "regex-2025.11.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a3d571bd95fade53c86c0517f859477ff3a93c3fde10c9e669086f038e0f207"}, + {file = "regex-2025.11.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:732aea6de26051af97b94bc98ed86448821f839d058e5d259c72bf6d73ad0fc0"}, + {file = "regex-2025.11.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:51c1c1847128238f54930edb8805b660305dca164645a9fd29243f5610beea34"}, + {file = "regex-2025.11.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:22dd622a402aad4558277305350699b2be14bc59f64d64ae1d928ce7d072dced"}, + {file = "regex-2025.11.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f3b5a391c7597ffa96b41bd5cbd2ed0305f515fcbb367dfa72735679d5502364"}, + {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cc4076a5b4f36d849fd709284b4a3b112326652f3b0466f04002a6c15a0c96c1"}, + {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:a295ca2bba5c1c885826ce3125fa0b9f702a1be547d821c01d65f199e10c01e2"}, + {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:b4774ff32f18e0504bfc4e59a3e71e18d83bc1e171a3c8ed75013958a03b2f14"}, + {file = "regex-2025.11.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e7d1cdfa88ef33a2ae6aa0d707f9255eb286ffbd90045f1088246833223aee"}, + {file = "regex-2025.11.3-cp39-cp39-win32.whl", hash = "sha256:74d04244852ff73b32eeede4f76f51c5bcf44bc3c207bc3e6cf1c5c45b890708"}, + {file = "regex-2025.11.3-cp39-cp39-win_amd64.whl", hash = "sha256:7a50cd39f73faa34ec18d6720ee25ef10c4c1839514186fcda658a06c06057a2"}, + {file = "regex-2025.11.3-cp39-cp39-win_arm64.whl", hash = "sha256:43b4fb020e779ca81c1b5255015fe2b82816c76ec982354534ad9ec09ad7c9e3"}, + {file = "regex-2025.11.3.tar.gz", hash = "sha256:1fedc720f9bb2494ce31a58a1631f9c82df6a09b49c19517ea5cc280b4541e01"}, ] [[package]] @@ -4219,127 +4294,127 @@ jupyter = ["ipywidgets (>=7.5.1,<9)"] [[package]] name = "rpds-py" -version = "0.28.0" +version = "0.30.0" description = "Python bindings to Rust's persistent data structures (rpds)" optional = false python-versions = ">=3.10" groups = ["main"] files = [ - {file = "rpds_py-0.28.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:7b6013db815417eeb56b2d9d7324e64fcd4fa289caeee6e7a78b2e11fc9b438a"}, - {file = "rpds_py-0.28.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:1a4c6b05c685c0c03f80dabaeb73e74218c49deea965ca63f76a752807397207"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4794c6c3fbe8f9ac87699b131a1f26e7b4abcf6d828da46a3a52648c7930eba"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2e8456b6ee5527112ff2354dd9087b030e3429e43a74f480d4a5ca79d269fd85"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:beb880a9ca0a117415f241f66d56025c02037f7c4efc6fe59b5b8454f1eaa50d"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6897bebb118c44b38c9cb62a178e09f1593c949391b9a1a6fe777ccab5934ee7"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1b553dd06e875249fd43efd727785efb57a53180e0fde321468222eabbeaafa"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:f0b2044fdddeea5b05df832e50d2a06fe61023acb44d76978e1b060206a8a476"}, - {file = "rpds_py-0.28.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:05cf1e74900e8da73fa08cc76c74a03345e5a3e37691d07cfe2092d7d8e27b04"}, - {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:efd489fec7c311dae25e94fe7eeda4b3d06be71c68f2cf2e8ef990ffcd2cd7e8"}, - {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:ada7754a10faacd4f26067e62de52d6af93b6d9542f0df73c57b9771eb3ba9c4"}, - {file = "rpds_py-0.28.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c2a34fd26588949e1e7977cfcbb17a9a42c948c100cab890c6d8d823f0586457"}, - {file = "rpds_py-0.28.0-cp310-cp310-win32.whl", hash = "sha256:f9174471d6920cbc5e82a7822de8dfd4dcea86eb828b04fc8c6519a77b0ee51e"}, - {file = "rpds_py-0.28.0-cp310-cp310-win_amd64.whl", hash = "sha256:6e32dd207e2c4f8475257a3540ab8a93eff997abfa0a3fdb287cae0d6cd874b8"}, - {file = "rpds_py-0.28.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:03065002fd2e287725d95fbc69688e0c6daf6c6314ba38bdbaa3895418e09296"}, - {file = "rpds_py-0.28.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:28ea02215f262b6d078daec0b45344c89e161eab9526b0d898221d96fdda5f27"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25dbade8fbf30bcc551cb352376c0ad64b067e4fc56f90e22ba70c3ce205988c"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3c03002f54cc855860bfdc3442928ffdca9081e73b5b382ed0b9e8efe6e5e205"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b9699fa7990368b22032baf2b2dce1f634388e4ffc03dfefaaac79f4695edc95"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b9b06fe1a75e05e0713f06ea0c89ecb6452210fd60e2f1b6ddc1067b990e08d9"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac9f83e7b326a3f9ec3ef84cda98fb0a74c7159f33e692032233046e7fd15da2"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:0d3259ea9ad8743a75a43eb7819324cdab393263c91be86e2d1901ee65c314e0"}, - {file = "rpds_py-0.28.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a7548b345f66f6695943b4ef6afe33ccd3f1b638bd9afd0f730dd255c249c9e"}, - {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c9a40040aa388b037eb39416710fbcce9443498d2eaab0b9b45ae988b53f5c67"}, - {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8f60c7ea34e78c199acd0d3cda37a99be2c861dd2b8cf67399784f70c9f8e57d"}, - {file = "rpds_py-0.28.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1571ae4292649100d743b26d5f9c63503bb1fedf538a8f29a98dce2d5ba6b4e6"}, - {file = "rpds_py-0.28.0-cp311-cp311-win32.whl", hash = "sha256:5cfa9af45e7c1140af7321fa0bef25b386ee9faa8928c80dc3a5360971a29e8c"}, - {file = "rpds_py-0.28.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd8d86b5d29d1b74100982424ba53e56033dc47720a6de9ba0259cf81d7cecaa"}, - {file = "rpds_py-0.28.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e27d3a5709cc2b3e013bf93679a849213c79ae0573f9b894b284b55e729e120"}, - {file = "rpds_py-0.28.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:6b4f28583a4f247ff60cd7bdda83db8c3f5b05a7a82ff20dd4b078571747708f"}, - {file = "rpds_py-0.28.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d678e91b610c29c4b3d52a2c148b641df2b4676ffe47c59f6388d58b99cdc424"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e819e0e37a44a78e1383bf1970076e2ccc4dc8c2bbaa2f9bd1dc987e9afff628"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5ee514e0f0523db5d3fb171f397c54875dbbd69760a414dccf9d4d7ad628b5bd"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5f3fa06d27fdcee47f07a39e02862da0100cb4982508f5ead53ec533cd5fe55e"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46959ef2e64f9e4a41fc89aa20dbca2b85531f9a72c21099a3360f35d10b0d5a"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8455933b4bcd6e83fde3fefc987a023389c4b13f9a58c8d23e4b3f6d13f78c84"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:ad50614a02c8c2962feebe6012b52f9802deec4263946cddea37aaf28dd25a66"}, - {file = "rpds_py-0.28.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e5deca01b271492553fdb6c7fd974659dce736a15bae5dad7ab8b93555bceb28"}, - {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:735f8495a13159ce6a0d533f01e8674cec0c57038c920495f87dcb20b3ddb48a"}, - {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:961ca621ff10d198bbe6ba4957decca61aa2a0c56695384c1d6b79bf61436df5"}, - {file = "rpds_py-0.28.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2374e16cc9131022e7d9a8f8d65d261d9ba55048c78f3b6e017971a4f5e6353c"}, - {file = "rpds_py-0.28.0-cp312-cp312-win32.whl", hash = "sha256:d15431e334fba488b081d47f30f091e5d03c18527c325386091f31718952fe08"}, - {file = "rpds_py-0.28.0-cp312-cp312-win_amd64.whl", hash = "sha256:a410542d61fc54710f750d3764380b53bf09e8c4edbf2f9141a82aa774a04f7c"}, - {file = "rpds_py-0.28.0-cp312-cp312-win_arm64.whl", hash = "sha256:1f0cfd1c69e2d14f8c892b893997fa9a60d890a0c8a603e88dca4955f26d1edd"}, - {file = "rpds_py-0.28.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:e9e184408a0297086f880556b6168fa927d677716f83d3472ea333b42171ee3b"}, - {file = "rpds_py-0.28.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:edd267266a9b0448f33dc465a97cfc5d467594b600fe28e7fa2f36450e03053a"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:85beb8b3f45e4e32f6802fb6cd6b17f615ef6c6a52f265371fb916fae02814aa"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d2412be8d00a1b895f8ad827cc2116455196e20ed994bb704bf138fe91a42724"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf128350d384b777da0e68796afdcebc2e9f63f0e9f242217754e647f6d32491"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a2036d09b363aa36695d1cc1a97b36865597f4478470b0697b5ee9403f4fe399"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8e1e9be4fa6305a16be628959188e4fd5cd6f1b0e724d63c6d8b2a8adf74ea6"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:0a403460c9dd91a7f23fc3188de6d8977f1d9603a351d5db6cf20aaea95b538d"}, - {file = "rpds_py-0.28.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d7366b6553cdc805abcc512b849a519167db8f5e5c3472010cd1228b224265cb"}, - {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5b43c6a3726efd50f18d8120ec0551241c38785b68952d240c45ea553912ac41"}, - {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:0cb7203c7bc69d7c1585ebb33a2e6074492d2fc21ad28a7b9d40457ac2a51ab7"}, - {file = "rpds_py-0.28.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a52a5169c664dfb495882adc75c304ae1d50df552fbd68e100fdc719dee4ff9"}, - {file = "rpds_py-0.28.0-cp313-cp313-win32.whl", hash = "sha256:2e42456917b6687215b3e606ab46aa6bca040c77af7df9a08a6dcfe8a4d10ca5"}, - {file = "rpds_py-0.28.0-cp313-cp313-win_amd64.whl", hash = "sha256:e0a0311caedc8069d68fc2bf4c9019b58a2d5ce3cd7cb656c845f1615b577e1e"}, - {file = "rpds_py-0.28.0-cp313-cp313-win_arm64.whl", hash = "sha256:04c1b207ab8b581108801528d59ad80aa83bb170b35b0ddffb29c20e411acdc1"}, - {file = "rpds_py-0.28.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:f296ea3054e11fc58ad42e850e8b75c62d9a93a9f981ad04b2e5ae7d2186ff9c"}, - {file = "rpds_py-0.28.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5a7306c19b19005ad98468fcefeb7100b19c79fc23a5f24a12e06d91181193fa"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5d9b86aa501fed9862a443c5c3116f6ead8bc9296185f369277c42542bd646b"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e5bbc701eff140ba0e872691d573b3d5d30059ea26e5785acba9132d10c8c31d"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a5690671cd672a45aa8616d7374fdf334a1b9c04a0cac3c854b1136e92374fe"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9f1d92ecea4fa12f978a367c32a5375a1982834649cdb96539dcdc12e609ab1a"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d252db6b1a78d0a3928b6190156042d54c93660ce4d98290d7b16b5296fb7cc"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:d61b355c3275acb825f8777d6c4505f42b5007e357af500939d4a35b19177259"}, - {file = "rpds_py-0.28.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:acbe5e8b1026c0c580d0321c8aae4b0a1e1676861d48d6e8c6586625055b606a"}, - {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8aa23b6f0fc59b85b4c7d89ba2965af274346f738e8d9fc2455763602e62fd5f"}, - {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:7b14b0c680286958817c22d76fcbca4800ddacef6f678f3a7c79a1fe7067fe37"}, - {file = "rpds_py-0.28.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:bcf1d210dfee61a6c86551d67ee1031899c0fdbae88b2d44a569995d43797712"}, - {file = "rpds_py-0.28.0-cp313-cp313t-win32.whl", hash = "sha256:3aa4dc0fdab4a7029ac63959a3ccf4ed605fee048ba67ce89ca3168da34a1342"}, - {file = "rpds_py-0.28.0-cp313-cp313t-win_amd64.whl", hash = "sha256:7b7d9d83c942855e4fdcfa75d4f96f6b9e272d42fffcb72cd4bb2577db2e2907"}, - {file = "rpds_py-0.28.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:dcdcb890b3ada98a03f9f2bb108489cdc7580176cb73b4f2d789e9a1dac1d472"}, - {file = "rpds_py-0.28.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f274f56a926ba2dc02976ca5b11c32855cbd5925534e57cfe1fda64e04d1add2"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4fe0438ac4a29a520ea94c8c7f1754cdd8feb1bc490dfda1bfd990072363d527"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a358a32dd3ae50e933347889b6af9a1bdf207ba5d1a3f34e1a38cd3540e6733"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e80848a71c78aa328fefaba9c244d588a342c8e03bda518447b624ea64d1ff56"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f586db2e209d54fe177e58e0bc4946bea5fb0102f150b1b2f13de03e1f0976f8"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ae8ee156d6b586e4292491e885d41483136ab994e719a13458055bec14cf370"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:a805e9b3973f7e27f7cab63a6b4f61d90f2e5557cff73b6e97cd5b8540276d3d"}, - {file = "rpds_py-0.28.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5d3fd16b6dc89c73a4da0b4ac8b12a7ecc75b2864b95c9e5afed8003cb50a728"}, - {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:6796079e5d24fdaba6d49bda28e2c47347e89834678f2bc2c1b4fc1489c0fb01"}, - {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:76500820c2af232435cbe215e3324c75b950a027134e044423f59f5b9a1ba515"}, - {file = "rpds_py-0.28.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:bbdc5640900a7dbf9dd707fe6388972f5bbd883633eb68b76591044cfe346f7e"}, - {file = "rpds_py-0.28.0-cp314-cp314-win32.whl", hash = "sha256:adc8aa88486857d2b35d75f0640b949759f79dc105f50aa2c27816b2e0dd749f"}, - {file = "rpds_py-0.28.0-cp314-cp314-win_amd64.whl", hash = "sha256:66e6fa8e075b58946e76a78e69e1a124a21d9a48a5b4766d15ba5b06869d1fa1"}, - {file = "rpds_py-0.28.0-cp314-cp314-win_arm64.whl", hash = "sha256:a6fe887c2c5c59413353b7c0caff25d0e566623501ccfff88957fa438a69377d"}, - {file = "rpds_py-0.28.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7a69df082db13c7070f7b8b1f155fa9e687f1d6aefb7b0e3f7231653b79a067b"}, - {file = "rpds_py-0.28.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b1cde22f2c30ebb049a9e74c5374994157b9b70a16147d332f89c99c5960737a"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5338742f6ba7a51012ea470bd4dc600a8c713c0c72adaa0977a1b1f4327d6592"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e1460ebde1bcf6d496d80b191d854adedcc619f84ff17dc1c6d550f58c9efbba"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3eb248f2feba84c692579257a043a7699e28a77d86c77b032c1d9fbb3f0219c"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3bbba5def70b16cd1c1d7255666aad3b290fbf8d0fe7f9f91abafb73611a91"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3114f4db69ac5a1f32e7e4d1cbbe7c8f9cf8217f78e6e002cedf2d54c2a548ed"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:4b0cb8a906b1a0196b863d460c0222fb8ad0f34041568da5620f9799b83ccf0b"}, - {file = "rpds_py-0.28.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:cf681ac76a60b667106141e11a92a3330890257e6f559ca995fbb5265160b56e"}, - {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1e8ee6413cfc677ce8898d9cde18cc3a60fc2ba756b0dec5b71eb6eb21c49fa1"}, - {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:b3072b16904d0b5572a15eb9d31c1954e0d3227a585fc1351aa9878729099d6c"}, - {file = "rpds_py-0.28.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b670c30fd87a6aec281c3c9896d3bae4b205fd75d79d06dc87c2503717e46092"}, - {file = "rpds_py-0.28.0-cp314-cp314t-win32.whl", hash = "sha256:8014045a15b4d2b3476f0a287fcc93d4f823472d7d1308d47884ecac9e612be3"}, - {file = "rpds_py-0.28.0-cp314-cp314t-win_amd64.whl", hash = "sha256:7a4e59c90d9c27c561eb3160323634a9ff50b04e4f7820600a2beb0ac90db578"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f5e7101145427087e493b9c9b959da68d357c28c562792300dd21a095118ed16"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:31eb671150b9c62409a888850aaa8e6533635704fe2b78335f9aaf7ff81eec4d"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48b55c1f64482f7d8bd39942f376bfdf2f6aec637ee8c805b5041e14eeb771db"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:24743a7b372e9a76171f6b69c01aedf927e8ac3e16c474d9fe20d552a8cb45c7"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:389c29045ee8bbb1627ea190b4976a310a295559eaf9f1464a1a6f2bf84dde78"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23690b5827e643150cf7b49569679ec13fe9a610a15949ed48b85eb7f98f34ec"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f0c9266c26580e7243ad0d72fc3e01d6b33866cfab5084a6da7576bcf1c4f72"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:4c6c4db5d73d179746951486df97fd25e92396be07fc29ee8ff9a8f5afbdfb27"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a3b695a8fa799dd2cfdb4804b37096c5f6dba1ac7f48a7fbf6d0485bcd060316"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:6aa1bfce3f83baf00d9c5fcdbba93a3ab79958b4c7d7d1f55e7fe68c20e63912"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:7b0f9dceb221792b3ee6acb5438eb1f02b0cb2c247796a72b016dcc92c6de829"}, - {file = "rpds_py-0.28.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5d0145edba8abd3db0ab22b5300c99dc152f5c9021fab861be0f0544dc3cbc5f"}, - {file = "rpds_py-0.28.0.tar.gz", hash = "sha256:abd4df20485a0983e2ca334a216249b6186d6e3c1627e106651943dbdb791aea"}, + {file = "rpds_py-0.30.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:679ae98e00c0e8d68a7fda324e16b90fd5260945b45d3b824c892cec9eea3288"}, + {file = "rpds_py-0.30.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4cc2206b76b4f576934f0ed374b10d7ca5f457858b157ca52064bdfc26b9fc00"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:389a2d49eded1896c3d48b0136ead37c48e221b391c052fba3f4055c367f60a6"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:32c8528634e1bf7121f3de08fa85b138f4e0dc47657866630611b03967f041d7"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f207f69853edd6f6700b86efb84999651baf3789e78a466431df1331608e5324"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:67b02ec25ba7a9e8fa74c63b6ca44cf5707f2fbfadae3ee8e7494297d56aa9df"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0e95f6819a19965ff420f65578bacb0b00f251fefe2c8b23347c37174271f3"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_31_riscv64.whl", hash = "sha256:a452763cc5198f2f98898eb98f7569649fe5da666c2dc6b5ddb10fde5a574221"}, + {file = "rpds_py-0.30.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e0b65193a413ccc930671c55153a03ee57cecb49e6227204b04fae512eb657a7"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:858738e9c32147f78b3ac24dc0edb6610000e56dc0f700fd5f651d0a0f0eb9ff"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:da279aa314f00acbb803da1e76fa18666778e8a8f83484fba94526da5de2cba7"}, + {file = "rpds_py-0.30.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7c64d38fb49b6cdeda16ab49e35fe0da2e1e9b34bc38bd78386530f218b37139"}, + {file = "rpds_py-0.30.0-cp310-cp310-win32.whl", hash = "sha256:6de2a32a1665b93233cde140ff8b3467bdb9e2af2b91079f0333a0974d12d464"}, + {file = "rpds_py-0.30.0-cp310-cp310-win_amd64.whl", hash = "sha256:1726859cd0de969f88dc8673bdd954185b9104e05806be64bcd87badbe313169"}, + {file = "rpds_py-0.30.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a2bffea6a4ca9f01b3f8e548302470306689684e61602aa3d141e34da06cf425"}, + {file = "rpds_py-0.30.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dc4f992dfe1e2bc3ebc7444f6c7051b4bc13cd8e33e43511e8ffd13bf407010d"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:422c3cb9856d80b09d30d2eb255d0754b23e090034e1deb4083f8004bd0761e4"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:07ae8a593e1c3c6b82ca3292efbe73c30b61332fd612e05abee07c79359f292f"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:12f90dd7557b6bd57f40abe7747e81e0c0b119bef015ea7726e69fe550e394a4"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99b47d6ad9a6da00bec6aabe5a6279ecd3c06a329d4aa4771034a21e335c3a97"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33f559f3104504506a44bb666b93a33f5d33133765b0c216a5bf2f1e1503af89"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:946fe926af6e44f3697abbc305ea168c2c31d3e3ef1058cf68f379bf0335a78d"}, + {file = "rpds_py-0.30.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:495aeca4b93d465efde585977365187149e75383ad2684f81519f504f5c13038"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:d9a0ca5da0386dee0655b4ccdf46119df60e0f10da268d04fe7cc87886872ba7"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8d6d1cc13664ec13c1b84241204ff3b12f9bb82464b8ad6e7a5d3486975c2eed"}, + {file = "rpds_py-0.30.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3896fa1be39912cf0757753826bc8bdc8ca331a28a7c4ae46b7a21280b06bb85"}, + {file = "rpds_py-0.30.0-cp311-cp311-win32.whl", hash = "sha256:55f66022632205940f1827effeff17c4fa7ae1953d2b74a8581baaefb7d16f8c"}, + {file = "rpds_py-0.30.0-cp311-cp311-win_amd64.whl", hash = "sha256:a51033ff701fca756439d641c0ad09a41d9242fa69121c7d8769604a0a629825"}, + {file = "rpds_py-0.30.0-cp311-cp311-win_arm64.whl", hash = "sha256:47b0ef6231c58f506ef0b74d44e330405caa8428e770fec25329ed2cb971a229"}, + {file = "rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad"}, + {file = "rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6"}, + {file = "rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e"}, + {file = "rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394"}, + {file = "rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf"}, + {file = "rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b"}, + {file = "rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e"}, + {file = "rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2"}, + {file = "rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e"}, + {file = "rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31"}, + {file = "rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95"}, + {file = "rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d"}, + {file = "rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15"}, + {file = "rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1"}, + {file = "rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a"}, + {file = "rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9"}, + {file = "rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08"}, + {file = "rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27"}, + {file = "rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6"}, + {file = "rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d"}, + {file = "rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0"}, + {file = "rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07"}, + {file = "rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f"}, + {file = "rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53"}, + {file = "rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed"}, + {file = "rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950"}, + {file = "rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6"}, + {file = "rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb"}, + {file = "rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8"}, + {file = "rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856"}, + {file = "rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40"}, + {file = "rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0"}, + {file = "rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:c2262bdba0ad4fc6fb5545660673925c2d2a5d9e2e0fb603aad545427be0fc58"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:ee6af14263f25eedc3bb918a3c04245106a42dfd4f5c2285ea6f997b1fc3f89a"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3adbb8179ce342d235c31ab8ec511e66c73faa27a47e076ccc92421add53e2bb"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:250fa00e9543ac9b97ac258bd37367ff5256666122c2d0f2bc97577c60a1818c"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9854cf4f488b3d57b9aaeb105f06d78e5529d3145b1e4a41750167e8c213c6d3"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:993914b8e560023bc0a8bf742c5f303551992dcb85e247b1e5c7f4a7d145bda5"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58edca431fb9b29950807e301826586e5bbf24163677732429770a697ffe6738"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_31_riscv64.whl", hash = "sha256:dea5b552272a944763b34394d04577cf0f9bd013207bc32323b5a89a53cf9c2f"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ba3af48635eb83d03f6c9735dfb21785303e73d22ad03d489e88adae6eab8877"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:dff13836529b921e22f15cb099751209a60009731a68519630a24d61f0b1b30a"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_i686.whl", hash = "sha256:1b151685b23929ab7beec71080a8889d4d6d9fa9a983d213f07121205d48e2c4"}, + {file = "rpds_py-0.30.0-pp311-pypy311_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:ac37f9f516c51e5753f27dfdef11a88330f04de2d564be3991384b2f3535d02e"}, + {file = "rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84"}, ] [[package]] @@ -4359,14 +4434,14 @@ pyasn1 = ">=0.1.3" [[package]] name = "s3transfer" -version = "0.14.0" +version = "0.16.0" description = "An Amazon S3 Transfer Manager" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456"}, - {file = "s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125"}, + {file = "s3transfer-0.16.0-py3-none-any.whl", hash = "sha256:18e25d66fed509e3868dc1572b3f427ff947dd2c56f844a5bf09481ad3f3b2fe"}, + {file = "s3transfer-0.16.0.tar.gz", hash = "sha256:8e990f13268025792229cd52fa10cb7163744bf56e719e0b9cb925ab79abf920"}, ] [package.dependencies] @@ -4377,27 +4452,35 @@ crt = ["botocore[crt] (>=1.37.4,<2.0a.0)"] [[package]] name = "safetensors" -version = "0.6.2" +version = "0.7.0" description = "" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "safetensors-0.6.2-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:9c85ede8ec58f120bad982ec47746981e210492a6db876882aa021446af8ffba"}, - {file = "safetensors-0.6.2-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d6675cf4b39c98dbd7d940598028f3742e0375a6b4d4277e76beb0c35f4b843b"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d2d2b3ce1e2509c68932ca03ab8f20570920cd9754b05063d4368ee52833ecd"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:93de35a18f46b0f5a6a1f9e26d91b442094f2df02e9fd7acf224cfec4238821a"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89a89b505f335640f9120fac65ddeb83e40f1fd081cb8ed88b505bdccec8d0a1"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fc4d0d0b937e04bdf2ae6f70cd3ad51328635fe0e6214aa1fc811f3b576b3bda"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8045db2c872db8f4cbe3faa0495932d89c38c899c603f21e9b6486951a5ecb8f"}, - {file = "safetensors-0.6.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:81e67e8bab9878bb568cffbc5f5e655adb38d2418351dc0859ccac158f753e19"}, - {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:b0e4d029ab0a0e0e4fdf142b194514695b1d7d3735503ba700cf36d0fc7136ce"}, - {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:fa48268185c52bfe8771e46325a1e21d317207bcabcb72e65c6e28e9ffeb29c7"}, - {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:d83c20c12c2d2f465997c51b7ecb00e407e5f94d7dec3ea0cc11d86f60d3fde5"}, - {file = "safetensors-0.6.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:d944cea65fad0ead848b6ec2c37cc0b197194bec228f8020054742190e9312ac"}, - {file = "safetensors-0.6.2-cp38-abi3-win32.whl", hash = "sha256:cab75ca7c064d3911411461151cb69380c9225798a20e712b102edda2542ddb1"}, - {file = "safetensors-0.6.2-cp38-abi3-win_amd64.whl", hash = "sha256:c7b214870df923cbc1593c3faee16bec59ea462758699bd3fee399d00aac072c"}, - {file = "safetensors-0.6.2.tar.gz", hash = "sha256:43ff2aa0e6fa2dc3ea5524ac7ad93a9839256b8703761e76e2d0b2a3fa4f15d9"}, + {file = "safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517"}, + {file = "safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48"}, + {file = "safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981"}, + {file = "safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b"}, + {file = "safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85"}, + {file = "safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0"}, + {file = "safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4"}, + {file = "safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba"}, + {file = "safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755"}, + {file = "safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737"}, + {file = "safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd"}, + {file = "safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2"}, + {file = "safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3"}, + {file = "safetensors-0.7.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7b95a3fa7b3abb9b5b0e07668e808364d0d40f6bbbf9ae0faa8b5b210c97b140"}, + {file = "safetensors-0.7.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cfdead2f57330d76aa7234051dadfa7d4eedc0e5a27fd08e6f96714a92b00f09"}, + {file = "safetensors-0.7.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc92bc2db7b45bda4510e4f51c59b00fe80b2d6be88928346e4294ce1c2abe7c"}, + {file = "safetensors-0.7.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6999421eb8ba9df4450a16d9184fcb7bef26240b9f98e95401f17af6c2210b71"}, + {file = "safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0"}, ] [package.extras] @@ -4412,7 +4495,7 @@ quality = ["ruff"] tensorflow = ["safetensors[numpy]", "tensorflow (>=2.11.0)"] testing = ["h5py (>=3.7.0)", "huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] testingfree = ["huggingface-hub (>=0.12.1)", "hypothesis (>=6.70.2)", "pytest (>=7.2.0)", "pytest-benchmark (>=4.0.0)", "safetensors[numpy]", "setuptools-rust (>=1.5.2)"] -torch = ["safetensors[numpy]", "torch (>=1.10)"] +torch = ["packaging", "safetensors[numpy]", "torch (>=1.10)"] [[package]] name = "shellingham" @@ -4571,55 +4654,57 @@ SQLAlchemy = ">=2.0.14,<2.1.0" [[package]] name = "sqlparse" -version = "0.5.3" +version = "0.5.5" description = "A non-validating SQL parser." optional = false python-versions = ">=3.8" groups = ["dev"] files = [ - {file = "sqlparse-0.5.3-py3-none-any.whl", hash = "sha256:cf2196ed3418f3ba5de6af7e82c694a9fbdbfecccdfc72e281548517081f16ca"}, - {file = "sqlparse-0.5.3.tar.gz", hash = "sha256:09f67787f56a0b16ecdbde1bfc7f5d9c3371ca683cfeaa8e6ff60b4807ec9272"}, + {file = "sqlparse-0.5.5-py3-none-any.whl", hash = "sha256:12a08b3bf3eec877c519589833aed092e2444e68240a3577e8e26148acc7b1ba"}, + {file = "sqlparse-0.5.5.tar.gz", hash = "sha256:e20d4a9b0b8585fdf63b10d30066c7c94c5d7a7ec47c889a2d83a3caa93ff28e"}, ] [package.extras] -dev = ["build", "hatch"] +dev = ["build"] doc = ["sphinx"] [[package]] name = "sse-starlette" -version = "3.0.3" +version = "3.0.4" description = "SSE plugin for Starlette" optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "sse_starlette-3.0.3-py3-none-any.whl", hash = "sha256:af5bf5a6f3933df1d9c7f8539633dc8444ca6a97ab2e2a7cd3b6e431ac03a431"}, - {file = "sse_starlette-3.0.3.tar.gz", hash = "sha256:88cfb08747e16200ea990c8ca876b03910a23b547ab3bd764c0d8eb81019b971"}, + {file = "sse_starlette-3.0.4-py3-none-any.whl", hash = "sha256:32c80ef0d04506ced4b0b6ab8fe300925edc37d26f666afb1874c754895f5dc3"}, + {file = "sse_starlette-3.0.4.tar.gz", hash = "sha256:5e34286862e96ead0eb70f5ddd0bd21ab1f6473a8f44419dd267f431611383dd"}, ] [package.dependencies] anyio = ">=4.7.0" +starlette = ">=0.49.1" [package.extras] daphne = ["daphne (>=4.2.0)"] -examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio] (>=2.0.41)", "starlette (>=0.49.1)", "uvicorn (>=0.34.0)"] +examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio] (>=2.0.41)", "uvicorn (>=0.34.0)"] granian = ["granian (>=2.3.1)"] uvicorn = ["uvicorn (>=0.34.0)"] [[package]] name = "starlette" -version = "0.46.2" +version = "0.50.0" description = "The little ASGI library that shines." optional = false -python-versions = ">=3.9" +python-versions = ">=3.10" groups = ["main"] files = [ - {file = "starlette-0.46.2-py3-none-any.whl", hash = "sha256:595633ce89f8ffa71a015caed34a5b2dc1c0cdb3f0f1fbd1e69339cf2abeec35"}, - {file = "starlette-0.46.2.tar.gz", hash = "sha256:7f7361f34eed179294600af672f565727419830b54b7b084efe44bb82d2fccd5"}, + {file = "starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca"}, + {file = "starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca"}, ] [package.dependencies] anyio = ">=3.6.2,<5" +typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} [package.extras] full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] @@ -4904,14 +4989,14 @@ telegram = ["requests"] [[package]] name = "transformers" -version = "4.57.1" +version = "4.57.3" description = "State-of-the-art Machine Learning for JAX, PyTorch and TensorFlow" optional = false python-versions = ">=3.9.0" groups = ["main"] files = [ - {file = "transformers-4.57.1-py3-none-any.whl", hash = "sha256:b10d05da8fa67dc41644dbbf9bc45a44cb86ae33da6f9295f5fbf5b7890bd267"}, - {file = "transformers-4.57.1.tar.gz", hash = "sha256:f06c837959196c75039809636cd964b959f6604b75b8eeec6fdfc0440b89cc55"}, + {file = "transformers-4.57.3-py3-none-any.whl", hash = "sha256:c77d353a4851b1880191603d36acb313411d3577f6e2897814f333841f7003f4"}, + {file = "transformers-4.57.3.tar.gz", hash = "sha256:df4945029aaddd7c09eec5cad851f30662f8bd1746721b34cc031d70c65afebc"}, ] [package.dependencies] @@ -5065,32 +5150,64 @@ files = [ [[package]] name = "urllib3" -version = "2.5.0" +version = "2.6.2" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" groups = ["main", "dev"] files = [ - {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, - {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, + {file = "urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd"}, + {file = "urllib3-2.6.2.tar.gz", hash = "sha256:016f9c98bb7e98085cb2b4b17b87d2c702975664e4f060c6532e64d1c1a5e797"}, ] [package.extras] -brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] +brotli = ["brotli (>=1.2.0) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=1.2.0.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] -zstd = ["zstandard (>=0.18.0)"] +zstd = ["backports-zstd (>=1.0.0) ; python_version < \"3.14\""] + +[[package]] +name = "uuid-utils" +version = "0.12.0" +description = "Drop-in replacement for Python UUID with bindings in Rust" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:3b9b30707659292f207b98f294b0e081f6d77e1fbc760ba5b41331a39045f514"}, + {file = "uuid_utils-0.12.0-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:add3d820c7ec14ed37317375bea30249699c5d08ff4ae4dbee9fc9bce3bfbf65"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b8fce83ecb3b16af29c7809669056c4b6e7cc912cab8c6d07361645de12dd79"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ec921769afcb905035d785582b0791d02304a7850fbd6ce924c1a8976380dfc6"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6f3b060330f5899a92d5c723547dc6a95adef42433e9748f14c66859a7396664"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:908dfef7f0bfcf98d406e5dc570c25d2f2473e49b376de41792b6e96c1d5d291"}, + {file = "uuid_utils-0.12.0-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4c6a24148926bd0ca63e8a2dabf4cc9dc329a62325b3ad6578ecd60fbf926506"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:64a91e632669f059ef605f1771d28490b1d310c26198e46f754e8846dddf12f4"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:93c082212470bb4603ca3975916c205a9d7ef1443c0acde8fbd1e0f5b36673c7"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:431b1fb7283ba974811b22abd365f2726f8f821ab33f0f715be389640e18d039"}, + {file = "uuid_utils-0.12.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2ffd7838c40149100299fa37cbd8bab5ee382372e8e65a148002a37d380df7c8"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win32.whl", hash = "sha256:487f17c0fee6cbc1d8b90fe811874174a9b1b5683bf2251549e302906a50fed3"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win_amd64.whl", hash = "sha256:9598e7c9da40357ae8fffc5d6938b1a7017f09a1acbcc95e14af8c65d48c655a"}, + {file = "uuid_utils-0.12.0-cp39-abi3-win_arm64.whl", hash = "sha256:c9bea7c5b2aa6f57937ebebeee4d4ef2baad10f86f1b97b58a3f6f34c14b4e84"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:e2209d361f2996966ab7114f49919eb6aaeabc6041672abbbbf4fdbb8ec1acc0"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d9636bcdbd6cfcad2b549c352b669412d0d1eb09be72044a2f13e498974863cd"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8cd8543a3419251fb78e703ce3b15fdfafe1b7c542cf40caf0775e01db7e7674"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e98db2d8977c052cb307ae1cb5cc37a21715e8d415dbc65863b039397495a013"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8f2bdf5e4ffeb259ef6d15edae92aed60a1d6f07cbfab465d836f6b12b48da8"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c3ec53c0cb15e1835870c139317cc5ec06e35aa22843e3ed7d9c74f23f23898"}, + {file = "uuid_utils-0.12.0-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:84e5c0eba209356f7f389946a3a47b2cc2effd711b3fc7c7f155ad9f7d45e8a3"}, + {file = "uuid_utils-0.12.0.tar.gz", hash = "sha256:252bd3d311b5d6b7f5dfce7a5857e27bb4458f222586bb439463231e5a9cbd64"}, +] [[package]] name = "uvicorn" -version = "0.34.3" +version = "0.38.0" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.9" groups = ["main"] files = [ - {file = "uvicorn-0.34.3-py3-none-any.whl", hash = "sha256:16246631db62bdfbf069b0645177d6e8a77ba950cfedbfd093acef9444e4d885"}, - {file = "uvicorn-0.34.3.tar.gz", hash = "sha256:35919a9a979d7a59334b6b10e05d77c1d0d574c50e0fc98b8b1a0f165708b55a"}, + {file = "uvicorn-0.38.0-py3-none-any.whl", hash = "sha256:48c0afd214ceb59340075b4a052ea1ee91c16fbc2a9b1469cca0e54566977b02"}, + {file = "uvicorn-0.38.0.tar.gz", hash = "sha256:fd97093bdd120a2609fc0d3afe931d4d4ad688b6e75f0f929fde1bc36fe0e91d"}, ] [package.dependencies] @@ -5618,4 +5735,4 @@ cffi = ["cffi (>=1.17,<2.0) ; platform_python_implementation != \"PyPy\" and pyt [metadata] lock-version = "2.1" python-versions = ">=3.12,<3.14" -content-hash = "609f6891fea6189ee96070c32c845667fe5950964ecbf452f09ddc3292ef6a26" +content-hash = "bd8e9f709f072e76b7818454af6e10705a1855a2df5389f76ef7261f4262ea2e" diff --git a/src/backend/pyproject.toml b/src/backend/pyproject.toml index 2e8e2a6..f97fcb6 100644 --- a/src/backend/pyproject.toml +++ b/src/backend/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ "langchain-google-genai (>=3.1.0,<4.0.0)", "fastapi-mcp (>=0.4.0,<1.0.0)", "tokenizers (>=0.21.0,<1.0.0)", + "python-keycloak (>=5.8.1,<6.0.0)", "jinja2 (>=3.1.6,<4.0.0)", "aiosmtplib (>=5.0.0,<6.0.0)", "xlrd (>=2.0.2,<3.0.0)", diff --git a/src/backend/pytest.ini b/src/backend/pytest.ini index c578f17..046c5fd 100644 --- a/src/backend/pytest.ini +++ b/src/backend/pytest.ini @@ -1,8 +1,12 @@ [pytest] +markers = + no_stub_estimate_minutes_saved: do not autouse-patch estimate_minutes_saved in this test + no_stub_build_thread_name: do not autouse-patch build_thread_name in this test +addopts = --durations=0 asyncio_default_fixture_loop_scope = function asyncio_mode = auto log_cli = true -log_cli_level = INFO +log_cli_level = DEBUG log_cli_format = %(asctime)s %(levelname)s [%(name)s] %(message)s log_cli_date_format = %Y-%m-%d %H:%M:%S filterwarnings = diff --git a/src/backend/tero/agents/api.py b/src/backend/tero/agents/api.py index 29cc3cf..b057d61 100644 --- a/src/backend/tero/agents/api.py +++ b/src/backend/tero/agents/api.py @@ -24,6 +24,7 @@ from ..users.domain import User from ..users.repos import UserRepository from . import field_generation, distribution +from .distribution import AgentImportResult, UnsupportedFileStructureError, MissingRequiredConfigurationError from .domain import AgentListItem, Agent, AgentUpdate, AgentToolConfig, AutomaticAgentField, PublicAgent from .evaluators.repos import EvaluatorRepository from .prompts.repos import AgentPromptRepository @@ -98,6 +99,11 @@ async def find_agent_by_id(agent_id: int, user: User, db: AsyncSession) -> Agent return ret +def _require_editor_for_protected(agent: Agent, user: User, detail: str) -> None: + if agent.is_protected and not agent.is_editable_by(user): + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail=f"Editors access required to {detail}") + + @router.delete(AGENT_PIN_PATH, status_code=status.HTTP_204_NO_CONTENT) async def remove_user_agent(agent_id: int, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]): @@ -120,6 +126,12 @@ async def update_agent(agent_id: int, updated: AgentUpdate, user: Annotated[User db: Annotated[AsyncSession, Depends(get_db)]) -> PublicAgent: agent = await find_editable_agent(agent_id, user, db) + if updated.is_protected is not None and updated.is_protected != agent.is_protected and not user.is_global_owner(): + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="Global owner access required to manage protected configuration" + ) + if updated.team_id == GLOBAL_TEAM_ID and env.disable_publish_global and not any(tr.role in [Role.TEAM_OWNER, Role.TEAM_EDITOR] and tr.team_id == GLOBAL_TEAM_ID for tr in user.team_roles): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, @@ -201,7 +213,8 @@ async def _save_tool_config(agent_id: int, tool: AgentTool, prev_tool_id: str, d @router.get(AGENT_TOOLS_PATH) async def find_agent_tools_configs(agent_id: int, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]) -> List[AgentToolConfig]: - await find_agent_by_id(agent_id, user, db) + agent = await find_agent_by_id(agent_id, user, db) + _require_editor_for_protected(agent, user, "view this agent's tool configs") return await AgentToolConfigRepository(db).find_by_agent_id(agent_id) @@ -344,6 +357,7 @@ async def delete_agent_tool_file(agent_id: int, tool_id: str, file_id: int, @router.post(f"{AGENT_PATH}/clone", status_code=status.HTTP_201_CREATED) async def clone_agent(agent_id: int, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]) -> PublicAgent: agent = await find_agent_by_id(agent_id, user, db) + _require_editor_for_protected(agent, user, "clone this protected agent") cloned_agent = await AgentRepository(db).add(agent.clone(user_id=user.id)) await _clone_agent_prompts(agent_id, cloned_agent.id, user.id, db) @@ -406,15 +420,31 @@ async def _clone_agent_evaluator(agent: Agent, cloned_agent: Agent, db: AsyncSes @router.get(f"{AGENT_PATH}/dist") async def download_agent_distribution(agent_id: int, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]) -> StreamingResponse: agent = await find_agent_by_id(agent_id, user, db) + _require_editor_for_protected(agent, user, "export this protected agent") return build_file_download_response(await distribution.generate_agent_zip(agent, user.id,db)) @router.put(f"{AGENT_PATH}/dist") async def update_agent_from_distribution(agent_id: int, file: UploadFile, user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)], - background_tasks: BackgroundTasks): + background_tasks: BackgroundTasks) -> AgentImportResult: agent = await find_editable_agent(agent_id, user, db) try: - await distribution.update_agent_from_zip(agent, await file.read(), user, db, background_tasks) - except (BadZipFile, ValueError): + return await distribution.update_agent_from_zip(agent, await file.read(), user, db, background_tasks) + except BadZipFile: logger.error(f"Error updating agent {agent_id} from distribution", exc_info=True) - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Error updating agent from distribution") + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="invalidFileFormat" + ) + except UnsupportedFileStructureError: + logger.error(f"Error updating agent {agent_id} from distribution", exc_info=True) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="unsupportedFileStructure" + ) + except MissingRequiredConfigurationError: + logger.error(f"Error updating agent {agent_id} from distribution", exc_info=True) + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="missingRequiredConfiguration" + ) diff --git a/src/backend/tero/agents/distribution.py b/src/backend/tero/agents/distribution.py index 5d5e16d..0297212 100644 --- a/src/backend/tero/agents/distribution.py +++ b/src/backend/tero/agents/distribution.py @@ -1,5 +1,6 @@ import base64 -from io import BytesIO +import json +from io import BytesIO import logging import mimetypes import re @@ -17,6 +18,8 @@ from ..ai_models.domain import LlmModelType, LlmModel from ..ai_models.repos import AiModelRepository from ..core.assets import solve_asset_path +from ..core.env import env +from ..core.domain import CamelCaseModel from ..files.domain import File, FileStatus from ..threads.domain import Thread, ThreadMessage, ThreadMessageOrigin from ..threads.repos import ThreadRepository, ThreadMessageRepository @@ -39,6 +42,20 @@ logger = logging.getLogger(__name__) +class AgentImportResult(CamelCaseModel): + unavailable_tools: List[str] = [] + tools_requiring_authentication: List[str] = [] + unavailable_model: Optional[str] = None + default_model: Optional[str] = None + + +class UnsupportedFileStructureError(Exception): + pass + + +class MissingRequiredConfigurationError(Exception): + pass + class ToolInfo(BaseModel): id: str @@ -121,7 +138,11 @@ def _format_tool_config_key(key: str) -> str: def _format_tool_config_value(value: Any) -> Any: - return ",".join(value) if isinstance(value, list) else value + if isinstance(value, list): + return ",".join(value) if all(isinstance(item, str) for item in value) else json.dumps(value) + if isinstance(value, dict): + return json.dumps(value) + return value async def _format_test(test_case: TestCase, db: AsyncSession) -> dict: @@ -154,32 +175,39 @@ async def _format_evaluator(evaluator: Optional[Evaluator], db: AsyncSession) -> } -async def update_agent_from_zip(agent: Agent, zip_content: bytes, user: User, db: AsyncSession, background_tasks: BackgroundTasks) -> Agent: +async def update_agent_from_zip(agent: Agent, zip_content: bytes, user: User, db: AsyncSession, background_tasks: BackgroundTasks) -> AgentImportResult: with _open_zip_file(zip_content) as zip_file: - found_root_folder = [ name.rsplit('/', 1)[0] for name in zip_file.namelist() if name.endswith('/agent.md') ] - # supporting zip without root folder in case users zip the folder contents and not the folder itself - root_folder = f"{found_root_folder[0]}/" if found_root_folder else "" - - if not f"{root_folder}agent.md" in zip_file.namelist(): - raise ValueError("Agent markdown file not found") - agent_md = zip_file.read(f"{root_folder}agent.md").decode('utf-8') - async with aiofiles.open(solve_asset_path("agent-template.md", __file__)) as template_file: - parsed = JinjaTemplateParser(_build_jinja_env()).parse(agent_md, await template_file.read()) - - # we find tools before updating the agent to avoid leaving some things changed and others not when tools are invalid - parsed_tools = parsed.get('tools', []) - tools = await _find_tools(parsed_tools) - await _update_agent(agent, parsed, zip_file, root_folder, db) - await _update_prompts(agent.id, parsed.get('conversation_starters', []), parsed.get('user_prompts', []), user.id, db) - await _update_tools(agent, parsed_tools, tools, zip_file, root_folder, user, db, background_tasks) - await _update_tests(agent.id, parsed.get('tests', []), user.id, db) - return agent + try: + found_root_folder = [ name.rsplit('/', 1)[0] for name in zip_file.namelist() if name.endswith('/agent.md') ] + # supporting zip without root folder in case users zip the folder contents and not the folder itself + root_folder = f"{found_root_folder[0]}/" if found_root_folder else "" + + if not f"{root_folder}agent.md" in zip_file.namelist(): + raise UnsupportedFileStructureError() + agent_md = zip_file.read(f"{root_folder}agent.md").decode('utf-8') + async with aiofiles.open(solve_asset_path("agent-template.md", __file__)) as template_file: + parsed = JinjaTemplateParser(_build_jinja_env()).parse(agent_md, await template_file.read()) + + parsed_tools = parsed.get('tools', []) + tools, unavailable_tools = await _find_tools(parsed_tools) + unavailable_model, default_model = await _update_agent(agent, parsed, zip_file, root_folder, db) + await _update_prompts(agent.id, parsed.get('conversation_starters', []), parsed.get('user_prompts', []), user.id, db) + auth_required_tools = await _update_tools(agent, parsed_tools, tools, zip_file, root_folder, user, db, background_tasks) + await _update_tests(agent.id, parsed.get('tests', []), user.id, db) + return AgentImportResult( + unavailable_tools=unavailable_tools, + tools_requiring_authentication=auth_required_tools, + unavailable_model=unavailable_model, + default_model=default_model if unavailable_model else None + ) + except ValueError as e: + raise MissingRequiredConfigurationError() from e def _open_zip_file(zip_content: bytes) -> ZipFile: zip_bytes = BytesIO(zip_content) try: - # we test with utf-8 encoding in case the file was zipped in mac since python zip encoding auto detection does not + # we test with utf-8 encoding in case the file was zipped in mac since python zip encoding auto detection does not # work when zip contains files with special characters (like ñ) on their names ret = ZipFile(zip_bytes, metadata_encoding='utf-8') ret.namelist() # Test if metadata can be decoded @@ -190,32 +218,42 @@ def _open_zip_file(zip_content: bytes) -> ZipFile: return ZipFile(zip_bytes) -async def _find_tools(parsed_tools: List[Dict[str, Any]]) -> Dict[str, AgentTool]: - ret = {} +async def _find_tools(parsed_tools: List[Dict[str, Any]]) -> tuple[Dict[str, AgentTool], List[str]]: + found = {} + unavailable = [] for parsed in parsed_tools: tool_id = _parse_tool_id(parsed) tool = ToolRepository().find_by_id(tool_id) - if not tool: - raise ValueError(f"Tool {tool_id} not found") - ret[tool_id] = tool - return ret + if tool: + found[tool_id] = tool + else: + unavailable.append(tool_id) + return found, unavailable def _parse_tool_id(tool: Dict[str, Any]) -> str: return tool['name'].lower().replace(' ', '-') -async def _update_agent(agent: Agent, parsed: Dict[str, Any], zip_file: ZipFile, root_folder: str, db: AsyncSession): +async def _update_agent(agent: Agent, parsed: Dict[str, Any], zip_file: ZipFile, root_folder: str, db: AsyncSession) -> tuple[Optional[str], Optional[str]]: update = AgentUpdate() update.name = parsed['name'] update.description = parsed['description'] update.system_prompt = parsed['system_prompt'] - model = await _find_model_by_name(parsed['model_name'], db) - update.model_id = model.id + unavailable_model: Optional[str] = None + default_model: Optional[str] = None + try: + model = await _find_model_by_name(parsed['model_name'], db) + except ValueError: + model = cast(LlmModel, await AiModelRepository(db).find_by_id(cast(str, env.agent_default_model))) + unavailable_model = parsed['model_name'] + default_model = model.name + update.temperature = LlmTemperature[parsed['model_config']['Temperature'].upper()] if model.model_type == LlmModelType.CHAT else None update.reasoning_effort = ReasoningEffort[parsed['model_config']['Reasoning'].upper()] if model.model_type == LlmModelType.REASONING else None - + update.model_id = model.id + icon_path = f"{root_folder}icon.png" if icon_path in zip_file.namelist(): update.icon = base64.b64encode(zip_file.read(icon_path)).decode('utf-8') @@ -225,6 +263,7 @@ async def _update_agent(agent: Agent, parsed: Dict[str, Any], zip_file: ZipFile, agent.update_with(update) agent = await AgentRepository(db).update(agent) + return unavailable_model, default_model async def _create_new_evaluator(evaluator_dict: Dict[str, Any], db: AsyncSession) -> int: @@ -253,7 +292,7 @@ async def _update_prompts(agent_id: int, conversation_starters: List[Dict[str, A for p in user_prompts: await _add_prompt(agent_id, user_id, p, db, shared=p["visibility"] == "Public") - + async def _add_prompt(agent_id: int, user_id: int, p: Dict[str, Any], db: AsyncSession, shared: bool=False, starter: bool=False): await AgentPromptRepository(db).add(AgentPrompt( agent_id=agent_id, @@ -265,21 +304,26 @@ async def _add_prompt(agent_id: int, user_id: int, p: Dict[str, Any], db: AsyncS )) -async def _update_tools(agent: Agent, parsed_tools: List[Dict[str, Any]], tools: Dict[str, AgentTool], zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks): - tools_dict = {_parse_tool_id(tool): tool for tool in parsed_tools} +async def _update_tools(agent: Agent, parsed_tools: List[Dict[str, Any]], tools: Dict[str, AgentTool], zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks) -> List[str]: + tools_dict = {_parse_tool_id(t): t for t in parsed_tools if _parse_tool_id(t) in tools} tool_config_repo = AgentToolConfigRepository(db) await tool_config_repo.delete_drafts(agent.id) existing_tools = {tc.tool_id: tc for tc in await tool_config_repo.find_by_agent_id(agent.id)} + auth_required_tools = [] for tc in existing_tools.values(): if not tc.tool_id in tools_dict: await _remove_tool(tc, user.id, db) else: - await _update_tool(tc, tools_dict[tc.tool_id], tools[tc.tool_id], zip_file, root_folder, user, db, background_tasks) - + if await _update_tool(tc, tools_dict[tc.tool_id], tools[tc.tool_id], zip_file, root_folder, user, db, background_tasks): + auth_required_tools.append(tc.tool_id) + for tool_id, config in tools_dict.items(): if not tool_id in existing_tools: - await _configure_new_tool(tool_id, config, agent, tools[tool_id], zip_file, root_folder, user, db, background_tasks) + if await _configure_new_tool(tool_id, config, agent, tools[tool_id], zip_file, root_folder, user, db, background_tasks): + auth_required_tools.append(tool_id) + + return auth_required_tools async def _remove_tool(tc: AgentToolConfig, user_id: int, db: AsyncSession): @@ -290,8 +334,8 @@ async def _remove_tool(tc: AgentToolConfig, user_id: int, db: AsyncSession): await AgentToolConfigRepository(db).delete(tc.agent_id, tc.tool_id) -async def _update_tool(tc: AgentToolConfig, new_config: Dict[str, Any], tool: AgentTool, zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks): - await _configure_parsed_tool(tc.tool_id, new_config, tc.agent, tc, tool, user, db) +async def _update_tool(tc: AgentToolConfig, new_config: Dict[str, Any], tool: AgentTool, zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks) -> bool: + auth_required = await _configure_parsed_tool(tc.tool_id, new_config, tc.agent, tc, tool, user, db) existing_files = {f.name: f for f in await AgentToolConfigFileRepository(db).find_by_agent_id_and_tool_id(tc.agent_id, tc.tool_id)} new_files = _parse_new_files(tc.tool_id, zip_file, root_folder, user) @@ -300,24 +344,34 @@ async def _update_tool(tc: AgentToolConfig, new_config: Dict[str, Any], tool: Ag await _remove_tool_file(file, tc, db) else: await _update_tool_file(file, new_files[file_name], tc, tool, user, db, background_tasks) - + for file_name, new_file in new_files.items(): if not file_name in existing_files: await upload_tool_file(new_file, tool, tc.agent_id, user, db, background_tasks) + return auth_required -async def _configure_parsed_tool(tool_id: str, new_config: Dict[str, Any], agent: Agent, tc: Optional[AgentToolConfig], tool: AgentTool, user: User, db: AsyncSession): +async def _configure_parsed_tool(tool_id: str, new_config: Dict[str, Any], agent: Agent, tc: Optional[AgentToolConfig], tool: AgentTool, user: User, db: AsyncSession) -> bool: config = _parse_tool_config(new_config.get('config', {}), tool, tool_id) tool.configure(agent, user.id, config, db) try: await tool.setup(prev_config=tc) - except ToolAuthRequestException as e: + except ToolAuthRequestException: logger.error(f"Tool OAuth required by tool {tool_id} imported by user {user.id} but still not supported in imports", exc_info=True) - await AgentToolConfigRepository(db).add(AgentToolConfig( - agent_id=agent.id, - tool_id=tool_id, - config=config - )) + await AgentToolConfigRepository(db).add(AgentToolConfig(agent_id=agent.id, tool_id=tool_id, config=config)) + return True + except Exception: + logger.warning(f"Tool {tool_id} setup failed during import by user {user.id}", exc_info=True) + raise ValueError(f"Tool '{tool_id}' setup failed while importing config") + await AgentToolConfigRepository(db).add(AgentToolConfig(agent_id=agent.id, tool_id=tool_id, config=config)) + return _has_secret_config(tool, config) + + +def _has_secret_config(tool: AgentTool, config: Dict[str, Any]) -> bool: + props = tool.config_schema.get("properties", {}) + has_secret_config = any(key in props for key in ("apiKey", "bearerToken", "token", "clientSecret")) + is_oauth_only = "authType" in props and (config.get("authType") or "oauth") == "oauth" + return has_secret_config and not is_oauth_only def _parse_tool_config(config: Dict[str, Any], tool: AgentTool, tool_id: str) -> Any: @@ -345,9 +399,11 @@ def _parse_config_value(value: Any, schema: dict, key: str, tool_id: str) -> Any return value.lower() == "true" elif schema_type == "array": item_type = schema["items"]["type"] - if not item_type == "string": - raise ValueError(f"Invalid array item type '{item_type}' while parsing tool '{tool_id}' config '{key}'") - return value.split(",") + if item_type == "string": + return value.split(",") + if item_type == "object": + return json.loads(value) if isinstance(value, str) else value + raise ValueError(f"Invalid array item type '{item_type}' while parsing tool '{tool_id}' config '{key}'") else: raise ValueError(f"Invalid type '{schema_type}' while parsing tool '{tool_id}' config '{key}'") @@ -378,11 +434,12 @@ async def _update_tool_file(file: File, new_file: File, tc: AgentToolConfig, too await upload_tool_file(new_file, tool, tc.agent_id, user, db, background_tasks) -async def _configure_new_tool(tool_id: str, new_config: Dict[str, Any], agent: Agent,tool: AgentTool, zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks): - await _configure_parsed_tool(tool_id, new_config, agent, None, tool, user, db) +async def _configure_new_tool(tool_id: str, new_config: Dict[str, Any], agent: Agent,tool: AgentTool, zip_file: ZipFile, root_folder: str, user: User, db: AsyncSession, background_tasks: BackgroundTasks) -> bool: + auth_required = await _configure_parsed_tool(tool_id, new_config, agent, None, tool, user, db) files = _parse_new_files(tool_id, zip_file, root_folder, user) for file in files.values(): await upload_tool_file(file, tool, agent.id, user, db, background_tasks) + return auth_required async def _update_tests(agent_id: int, tests: List[Dict[str, Any]], user_id: int, db: AsyncSession): @@ -398,7 +455,7 @@ async def _delete_existing_tests(agent_id: int, db: AsyncSession): await TestCaseRepository(db).delete(test) -async def _add_new_test(agent_id: int, test: Dict[str, Any], user_id: int, db: AsyncSession): +async def _add_new_test(agent_id: int, test: Dict[str, Any], user_id: int, db: AsyncSession): thread = await ThreadRepository(db).add(Thread( agent_id=agent_id, user_id=user_id, diff --git a/src/backend/tero/agents/domain.py b/src/backend/tero/agents/domain.py index bcfb913..bcf04a3 100644 --- a/src/backend/tero/agents/domain.py +++ b/src/backend/tero/agents/domain.py @@ -10,7 +10,6 @@ from sqlmodel import SQLModel, Column, Text, JSON from ..ai_models.domain import LlmModel, LlmModelType, LlmTemperature, ReasoningEffort -from ..core.env import env from ..core.domain import CamelCaseModel from ..teams.domain import Role, Team from ..users.domain import User, UserListItem @@ -41,6 +40,7 @@ class AgentUpdate(CamelCaseModel): recursion_limit: Optional[int] = None publish_prompts: Optional[bool] = None team_id: Optional[int] = None + is_protected: Optional[bool] = None class Agent(BaseAgent, table=True): @@ -60,6 +60,7 @@ class Agent(BaseAgent, table=True): team_id: Optional[int] = Field(default=None, foreign_key="team.id") team: Optional[Team] = Relationship() evaluator_id: Optional[int] = Field(default=None, foreign_key="evaluator.id") + is_protected: bool = Field(default=False) def update_with(self, update: AgentUpdate): update_dict = update.model_dump(exclude_none=True) @@ -110,6 +111,7 @@ class AgentListItem(BaseAgent): icon: Optional[str] = None active_users: Optional[int] = None can_edit: bool + is_protected: bool = False user: Optional[UserListItem] team: Optional[Team] = None @@ -120,6 +122,7 @@ def from_agent(a: Agent, can_edit: bool, active_users: Optional[int] = None) -> agent_dict["user"] = UserListItem.from_user(a.user) agent_dict["active_users"] = active_users agent_dict["can_edit"] = can_edit + agent_dict["is_protected"] = a.is_protected agent_dict["team"] = a.team return AgentListItem.model_validate(agent_dict) @@ -128,11 +131,12 @@ class PublicAgent(BaseAgent): icon: Optional[str] user_id: Optional[int] can_edit: bool - model_id: str - system_prompt: str - temperature: LlmTemperature - reasoning_effort: ReasoningEffort - recursion_limit: int + is_protected: bool = False + model_id: Optional[str] = None + system_prompt: str = "" + temperature: Optional[LlmTemperature] = None + reasoning_effort: Optional[ReasoningEffort] = None + recursion_limit: Optional[int] = None user: Optional[UserListItem] = None team: Optional[Team] = None @@ -141,8 +145,17 @@ def from_agent(a: Agent, can_edit: bool) -> 'PublicAgent': agent_dict = a.model_dump() agent_dict["icon"] = base64.b64encode(agent_dict["icon"]).decode("utf-8") if agent_dict.get("icon") else None agent_dict["can_edit"] = can_edit + agent_dict["is_protected"] = a.is_protected agent_dict["user"] = UserListItem.from_user(a.user) if a.user else None agent_dict["team"] = a.team + + if a.is_protected and not can_edit: + agent_dict["system_prompt"] = "" + agent_dict["model_id"] = None + agent_dict["temperature"] = None + agent_dict["reasoning_effort"] = None + agent_dict["recursion_limit"] = None + return PublicAgent.model_validate(agent_dict) diff --git a/src/backend/tero/agents/field_generation.py b/src/backend/tero/agents/field_generation.py index 30ed01c..86e961e 100644 --- a/src/backend/tero/agents/field_generation.py +++ b/src/backend/tero/agents/field_generation.py @@ -17,7 +17,8 @@ async def generate_agent_field(agent: Agent, field: AutomaticAgentField, user_id message_usage = MessageUsage(user_id=user_id, agent_id=agent.id, model_id=model.id) try: prompt = _build_prompt(agent, field) - llm = ai_factory.build_chat_model(model.id, env.internal_generator_temperature) + llm = ai_factory.build_chat_model( + model.id, env.internal_generator_temperature, env.internal_generator_reasoning_effort) response = await llm.ainvoke([HumanMessage(prompt)]) response = cast(AIMessage, response) message_usage.increment_with_metadata(response.usage_metadata, model) diff --git a/src/backend/tero/agents/mcp_tools.py b/src/backend/tero/agents/mcp_tools.py new file mode 100644 index 0000000..75f4f1e --- /dev/null +++ b/src/backend/tero/agents/mcp_tools.py @@ -0,0 +1,37 @@ +from typing import Annotated, List, Optional + +from fastapi import APIRouter, Depends, Query +from pydantic import BaseModel +from sqlmodel.ext.asyncio.session import AsyncSession + +from ..core.api import MCP_PATH +from ..core.auth import get_current_user +from ..core.repos import get_db +from ..users.domain import User +from .repos import AgentRepository + + +class McpAgent(BaseModel): + id: int + name: Optional[str] + description: Optional[str] + + +router = APIRouter() + + +@router.post(f"{MCP_PATH}/list-agents", operation_id="list-agents") +async def list_agents( + user: Annotated[User, Depends(get_current_user)], + db: Annotated[AsyncSession, Depends(get_db)], + text: Annotated[Optional[str], Query(description="Optional text to filter agents by name or description")] = None, +) -> List[McpAgent]: + """ + List available AI agents. + + Returns the agents accessible to the current user, optionally filtered by + a text search on name or description. + """ + repo = AgentRepository(db) + agents = await repo.find_user_agents(text, user) + return [McpAgent(id=a.id, name=a.name, description=a.description) for a in agents] diff --git a/src/backend/tero/agents/test_cases/domain.py b/src/backend/tero/agents/test_cases/domain.py index 3c94fe0..390ac04 100644 --- a/src/backend/tero/agents/test_cases/domain.py +++ b/src/backend/tero/agents/test_cases/domain.py @@ -101,6 +101,7 @@ class TestCaseResult(CamelCaseModel, table=True): evaluator_analysis: Optional[str] = Field(default=None, sa_column=Column(Text)) executed_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc)) test_case_name: Optional[str] = Field(default=None) + error_code: Optional[str] = Field(default=None) class PublicTestCase(CamelCaseModel): diff --git a/src/backend/tero/agents/test_cases/name_generation.py b/src/backend/tero/agents/test_cases/name_generation.py index 7b9dcdb..23d6d13 100644 --- a/src/backend/tero/agents/test_cases/name_generation.py +++ b/src/backend/tero/agents/test_cases/name_generation.py @@ -41,7 +41,8 @@ async def generate_test_case_name( message_usage = MessageUsage(user_id=user_id, agent_id=agent.id, model_id=model.id) try: - llm = ai_factory.build_chat_model(model.id, env.internal_generator_temperature) + llm = ai_factory.build_chat_model( + model.id, env.internal_generator_temperature, env.internal_generator_reasoning_effort) trimmed_user_message, trimmed_agent_message = _trim_messages_to_fit_model( user_message, agent_message, model, llm, agent ) diff --git a/src/backend/tero/agents/test_cases/repos.py b/src/backend/tero/agents/test_cases/repos.py index 0331b20..8d4ad42 100644 --- a/src/backend/tero/agents/test_cases/repos.py +++ b/src/backend/tero/agents/test_cases/repos.py @@ -184,7 +184,7 @@ async def find_by_suite_run(self, suite_run_id: int, after_id: Optional[int] = N ) if after_id: stmt = stmt.where(TestSuiteRunEvent.id > after_id) - + stmt = stmt.order_by(col(TestSuiteRunEvent.id).asc()) ret = await self._db.exec(stmt) return list(ret.all()) @@ -197,7 +197,7 @@ async def find_current_test_events(self, suite_run_id: int) -> List[TestSuiteRun ) last_start_id_result = await self._db.exec(subq) last_start_id = last_start_id_result.one_or_none() - + if not last_start_id: return [] diff --git a/src/backend/tero/agents/test_cases/runner.py b/src/backend/tero/agents/test_cases/runner.py index 557abb4..9aeb45d 100644 --- a/src/backend/tero/agents/test_cases/runner.py +++ b/src/backend/tero/agents/test_cases/runner.py @@ -5,6 +5,7 @@ from typing import List, cast, Any, AsyncIterator, Tuple, Optional, Dict, Coroutine from langchain_core.callbacks import AsyncCallbackHandler +from langgraph.errors import GraphRecursionError from langchain_core.messages.ai import UsageMetadata from langchain_core.outputs import LLMResult from langchain_core.prompts import ChatPromptTemplate @@ -91,8 +92,8 @@ async def on_llm_end(self, response: LLMResult, **kwargs) -> None: class BackgroundTestSuiteRunner: - async def _broadcast_event(self, db: AsyncSession, suite_run_id: int, event_type: str, data: Dict[str, Any]) -> None: - repo = TestSuiteRunEventRepository(db) + async def _broadcast_event(self, event_db: AsyncSession, suite_run_id: int, event_type: str, data: Dict[str, Any]) -> None: + repo = TestSuiteRunEventRepository(event_db) await repo.add(TestSuiteRunEvent( test_suite_run_id=suite_run_id, type=event_type, @@ -210,16 +211,24 @@ async def _run_test_case_stream(self, db: AsyncSession, test_case: TestCase, age } }) - except Exception: - logger.exception(f"Unexpected error running test case {test_case.thread_id}") + except Exception as exc: + error_code = None + if isinstance(exc, GraphRecursionError) or ( + isinstance(exc, BaseExceptionGroup) and exc.subgroup(GraphRecursionError) is not None + ): + error_code = "RECURSION_LIMIT_EXCEEDED" + else: + logger.exception(f"Unexpected error running test case {test_case.thread_id}") try: result.status = TestCaseResultStatus.ERROR + result.error_code = error_code await results_repo.save(result) except Exception: pass yield (TestCaseEventType.PHASE, { "phase": "completed", "status": TestCaseResultStatus.ERROR.value, + "errorCode": error_code, "evaluation": None }) @@ -253,21 +262,25 @@ async def _execute_agent_with_input_stream(self, db: AsyncSession, agent: Agent, }) messages_for_engine = previous_messages + [input_message] + status_updates: List[AgentActionEvent] = [] complete_response = "" - async for event in engine.answer(messages_for_engine, input_message_usage, stop_event): - if isinstance(event, AgentActionEvent): - yield (TestCaseEventType.EXECUTION_STATUS, event) - elif isinstance(event, AgentMessageEvent): - complete_response += event.content - yield (TestCaseEventType.AGENT_MESSAGE_CHUNK, { - "id": response_message.id, - "chunk": event.content - }) - - response_message.text = complete_response - response_message.timestamp = datetime.now(timezone.utc) - await thread_message_repo.update(response_message) - await UsageRepository(db).add(input_message_usage) + try: + async for event in engine.answer(messages_for_engine, input_message_usage, stop_event): + if isinstance(event, AgentActionEvent): + status_updates.append(event) + yield (TestCaseEventType.EXECUTION_STATUS, event) + elif isinstance(event, AgentMessageEvent): + complete_response += event.content + yield (TestCaseEventType.AGENT_MESSAGE_CHUNK, { + "id": response_message.id, + "chunk": event.content + }) + finally: + response_message.text = complete_response + response_message.status_updates = [e.model_dump(mode="json", by_alias=True) for e in status_updates] or None + response_message.timestamp = datetime.now(timezone.utc) + await thread_message_repo.update(response_message) + await UsageRepository(db).add(input_message_usage) yield (TestCaseEventType.AGENT_MESSAGE_COMPLETE, { "id": response_message.id, @@ -358,7 +371,14 @@ async def run( suite_run_id: int, stop_event: asyncio.Event, ) -> None: - async with AsyncSession(repos_module.engine, expire_on_commit=False) as db: + # Event writes use a dedicated session, separate from the main DB session. + # Tools like docs add Usage objects to the main session; SQLAlchemy's + # autoflush may begin flushing them mid-transaction. If _broadcast_event + # commits on the same session during that flush, it poisons the session + # and causes cascading failures (e.g. duplicate key on Usage re-insert + # attempts). + async with AsyncSession(repos_module.engine, expire_on_commit=False) as event_db, \ + AsyncSession(repos_module.engine, expire_on_commit=False) as db: try: agent_repo = AgentRepository(db) suite_repo = TestSuiteRunRepository(db) @@ -404,11 +424,11 @@ async def run( ): if counts_as_skip: skipped += 1 - await self._broadcast_event(db, suite_run.id, event_type, event_data) + await self._broadcast_event(event_db, suite_run.id, event_type, event_data) break result = pending_results[test_case.thread_id] - await self._broadcast_event(db, suite_run.id, TestSuiteEventType.TEST_START.value, { + await self._broadcast_event(event_db, suite_run.id, TestSuiteEventType.TEST_START.value, { "testCaseId": test_case.thread_id, "resultId": result.id }) @@ -429,14 +449,15 @@ async def run( else: skipped += 1 - await self._broadcast_event(db, suite_run.id, f"suite.test.{event_type.value}", + await self._broadcast_event(event_db, suite_run.id, f"suite.test.{event_type.value}", content.model_dump() if event_type == TestCaseEventType.EXECUTION_STATUS else content ) - await self._broadcast_event(db, suite_run.id, TestSuiteEventType.TEST_COMPLETE.value, { + await self._broadcast_event(event_db, suite_run.id, TestSuiteEventType.TEST_COMPLETE.value, { "testCaseId": test_case.thread_id, "resultId": result.id, "status": result.status.value, + "errorCode": result.error_code, "evaluation": { "analysis": result.evaluator_analysis } @@ -452,7 +473,7 @@ async def run( suite_run = await suite_repo.save(suite_run) - await self._broadcast_event(db, suite_run.id, TestSuiteEventType.COMPLETE.value, { + await self._broadcast_event(event_db, suite_run.id, TestSuiteEventType.COMPLETE.value, { "suiteRunId": suite_run.id, "status": suite_run.status.value, "totalTests": suite_run.total_tests, @@ -464,10 +485,14 @@ async def run( except Exception: logger.exception(f"Error running test suite for agent {agent_id}") + try: + await db.rollback() + except Exception: + pass await self._cancel_suite_run(suite_run_id, agent_id, db) - await self._broadcast_event(db, suite_run_id, TestSuiteEventType.ERROR.value, {}) + await self._broadcast_event(event_db, suite_run_id, TestSuiteEventType.ERROR.value, {}) finally: - await TestSuiteRunEventRepository(db).delete_by_suite_run_id(suite_run_id) + await TestSuiteRunEventRepository(event_db).delete_by_suite_run_id(suite_run_id) async def _skip_remaining_tests_background( self, diff --git a/src/backend/tero/ai_models/api.py b/src/backend/tero/ai_models/api.py index 0ecd275..1eeb3c2 100644 --- a/src/backend/tero/ai_models/api.py +++ b/src/backend/tero/ai_models/api.py @@ -3,10 +3,11 @@ from fastapi import APIRouter, Depends from sqlmodel.ext.asyncio.session import AsyncSession -from .domain import LlmModel +from .domain import LlmModel, LlmModelResponse from .repos import AiModelRepository from ..core.api import BASE_PATH from ..core.auth import get_current_user +from ..core.env import env from ..core.repos import get_db from ..users.domain import User @@ -15,5 +16,8 @@ @router.get(f"{BASE_PATH}/models") # user is added to contract just to require authentication to get the available agent tools async def find_models(_: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)]) \ - -> List[LlmModel]: - return await AiModelRepository(db).find_all() + -> List[LlmModelResponse]: + models = await AiModelRepository(db).find_all() + base_model_id = env.agent_base_cost_model + base_cost = next((m.completion_1k_token_usd for m in models if m.id == base_model_id), None) if base_model_id else None + return [LlmModelResponse.from_model(m, base_cost, base_model_id) for m in models] diff --git a/src/backend/tero/ai_models/aws_provider.py b/src/backend/tero/ai_models/aws_provider.py index ca499df..c42a507 100644 --- a/src/backend/tero/ai_models/aws_provider.py +++ b/src/backend/tero/ai_models/aws_provider.py @@ -1,6 +1,7 @@ from typing import Optional import boto3 +from botocore.exceptions import ClientError from langchain_aws import ChatBedrockConverse from langchain_core.language_models.chat_models import BaseChatModel @@ -52,3 +53,8 @@ def _get_model_arn(self, model: str) -> str: def _get_model_provider(self, model: str) -> str: return model.split(".")[0] + + def is_rate_limit_error(self, exc: Exception) -> bool: + return isinstance(exc, ClientError) and exc.response['Error']['Code'] in ( + 'ThrottlingException', 'TooManyRequestsException' + ) diff --git a/src/backend/tero/ai_models/azure_provider.py b/src/backend/tero/ai_models/azure_provider.py index ea3833f..b6c931f 100644 --- a/src/backend/tero/ai_models/azure_provider.py +++ b/src/backend/tero/ai_models/azure_provider.py @@ -1,15 +1,17 @@ import io -from typing import Callable, Iterable, Optional, cast +from collections.abc import Sequence +from typing import Callable, Iterable, Optional, Any, cast +from langchain_core.messages import BaseMessage from langchain_core.language_models.chat_models import BaseChatModel from langchain_openai import AzureChatOpenAI, AzureOpenAIEmbeddings -from openai import AsyncAzureOpenAI +from openai import AsyncAzureOpenAI, RateLimitError from pydantic import SecretStr import tiktoken from ..core.env import env from .domain import AiModelProvider -from .openai_provider import get_encoding_model, count_tokens +from .openai_provider import get_encoding_model, count_tokens, get_num_tokens_from_messages_sanitizing_unsupported_blocks class AzureProvider(AiModelProvider): @@ -25,11 +27,16 @@ def _build_chat_model(self, model: str, temperature: Optional[float], reasoning_ temperature=temperature, reasoning_effort=reasoning_effort, streaming=streaming, - stream_usage=True) + stream_usage=True, + # use responses api for codex models because they are not supported by completion endpoint + use_responses_api="-codex" in model) def supports_model(self, model: str) -> bool: return model in env.azure_model_deployments + def is_rate_limit_error(self, exc: Exception) -> bool: + return isinstance(exc, RateLimitError) + async def transcribe_audio(self, file: io.BytesIO, model: str) -> str: deployment = env.azure_model_deployments[model] client = AsyncAzureOpenAI( @@ -62,6 +69,13 @@ class ReasoningTokenCountingAzureChatOpenAI(AzureChatOpenAI): def _get_encoding_model(self) -> tuple[str, tiktoken.Encoding]: return get_encoding_model(self.model_name, lambda: AzureChatOpenAI._get_encoding_model(self)) + def get_num_tokens_from_messages(self, messages: Sequence[BaseMessage], tools: Optional[Sequence[Any]] = None) -> int: + return get_num_tokens_from_messages_sanitizing_unsupported_blocks( + token_counter=super().get_num_tokens_from_messages, + messages=messages, + tools=tools, + ) + class UsageTrackingAzureOpenAIEmbeddings(AzureOpenAIEmbeddings): usage_tracker: Callable[[int], None] diff --git a/src/backend/tero/ai_models/domain.py b/src/backend/tero/ai_models/domain.py index 98da44f..3720ea5 100644 --- a/src/backend/tero/ai_models/domain.py +++ b/src/backend/tero/ai_models/domain.py @@ -28,8 +28,7 @@ class LlmModelVendor(Enum): -class LlmModel(CamelCaseModel, table=True): - __tablename__ : Any = "llm_model" +class LlmModelBase(CamelCaseModel): id: str = Field(primary_key=True, max_length=20) name: str = Field(max_length=30) description: str = Field(max_length=200) @@ -40,12 +39,27 @@ class LlmModel(CamelCaseModel, table=True): prompt_1k_token_usd: float completion_1k_token_usd: float + +class LlmModel(LlmModelBase, table=True): + __tablename__: Any = "llm_model" + @computed_field @property def is_basic(self) -> bool: return self.id in env.agent_basic_models +class LlmModelResponse(LlmModelBase): + is_basic: bool + cost_multiplier: Optional[float] = None + is_base_cost_model: bool = False + + @staticmethod + def from_model(model: LlmModel, base_completion_cost: Optional[float], base_model_id: Optional[str] = None) -> 'LlmModelResponse': + multiplier = round(model.completion_1k_token_usd / base_completion_cost, 2) if base_completion_cost else None + return LlmModelResponse.model_validate({**model.model_dump(), 'cost_multiplier': multiplier, 'is_base_cost_model': model.id == base_model_id}) + + class LlmTemperature(Enum): CREATIVE = 'CREATIVE' NEUTRAL = 'NEUTRAL' @@ -92,3 +106,6 @@ def build_embedding(self, model: str, usage_tracker: Callable[[int], None]) -> E def count_tokens(self, txt: str, model: str) -> int: raise NotImplementedError("Counting tokens is not yet supported by this provider") + + def is_rate_limit_error(self, exc: Exception) -> bool: + return False diff --git a/src/backend/tero/ai_models/google_provider.py b/src/backend/tero/ai_models/google_provider.py index e93ffaf..f7a61c7 100644 --- a/src/backend/tero/ai_models/google_provider.py +++ b/src/backend/tero/ai_models/google_provider.py @@ -1,5 +1,6 @@ from typing import Optional +from google.api_core.exceptions import ResourceExhausted from langchain_core.language_models.chat_models import BaseChatModel from langchain_google_genai import ChatGoogleGenerativeAI @@ -18,3 +19,6 @@ def _build_chat_model(self, model: str, temperature: Optional[float], reasoning_ def supports_model(self, model: str) -> bool: return model in env.google_model_id_mapping + + def is_rate_limit_error(self, exc: Exception) -> bool: + return isinstance(exc, ResourceExhausted) diff --git a/src/backend/tero/ai_models/openai_provider.py b/src/backend/tero/ai_models/openai_provider.py index e6ab979..cbb303d 100644 --- a/src/backend/tero/ai_models/openai_provider.py +++ b/src/backend/tero/ai_models/openai_provider.py @@ -1,10 +1,12 @@ import io -from typing import Callable, Iterable, Optional, cast +from collections.abc import Sequence +from typing import Callable, Iterable, Optional, Any, cast +from langchain_core.messages import BaseMessage from langchain_core.embeddings import Embeddings from langchain_core.language_models.chat_models import BaseChatModel from langchain_openai import ChatOpenAI, OpenAIEmbeddings -from openai import AsyncOpenAI +from openai import AsyncOpenAI, RateLimitError from pydantic import SecretStr import tiktoken @@ -20,11 +22,16 @@ def _build_chat_model(self, model: str, temperature: Optional[float], reasoning_ api_key=env.openai_api_key, model=openai_model_id, temperature=temperature, - streaming=streaming) + streaming=streaming, + # use responses api for codex models because they are not supported by completion endpoint + use_responses_api="-codex" in model) def supports_model(self, model: str) -> bool: return model in env.openai_model_id_mapping + def is_rate_limit_error(self, exc: Exception) -> bool: + return isinstance(exc, RateLimitError) + async def transcribe_audio(self, file: io.BytesIO, model: str) -> str: client = AsyncOpenAI(api_key=cast(SecretStr, env.openai_api_key).get_secret_value()) response = await client.audio.transcriptions.create( @@ -54,6 +61,13 @@ class ReasoningTokenCountingChatOpenAI(ChatOpenAI): def _get_encoding_model(self) -> tuple[str, tiktoken.Encoding]: return get_encoding_model(self.model_name, lambda: ChatOpenAI._get_encoding_model(self)) + def get_num_tokens_from_messages(self, messages: Sequence[BaseMessage], tools: Optional[Sequence[Any]] = None) -> int: + return get_num_tokens_from_messages_sanitizing_unsupported_blocks( + token_counter=super().get_num_tokens_from_messages, + messages=messages, + tools=tools, + ) + def get_encoding_model(model_name: Optional[str], default: Callable[[], tuple[str, tiktoken.Encoding]]) -> tuple[str, tiktoken.Encoding]: if model_name and model_name.startswith("o"): @@ -62,6 +76,50 @@ def get_encoding_model(model_name: Optional[str], default: Callable[[], tuple[st return default() +_UNSUPPORTED_TOKEN_COUNT_BLOCK_TYPES = {"reasoning", "function_call"} + + +def get_num_tokens_from_messages_sanitizing_unsupported_blocks( + token_counter: Callable[[Sequence[BaseMessage], Optional[Sequence[Any]]], int], + messages: Sequence[BaseMessage], + tools: Optional[Sequence[Any]], +) -> int: + if _has_unsupported_blocks(messages): + messages = sanitize_messages_for_token_count(messages) + return token_counter(messages, tools) + + +def _has_unsupported_blocks(messages: Sequence[BaseMessage]) -> bool: + return any( + isinstance(message.content, list) + and any( + isinstance(item, dict) and item.get("type") in _UNSUPPORTED_TOKEN_COUNT_BLOCK_TYPES + for item in message.content + ) + for message in messages + ) + + +def sanitize_messages_for_token_count(messages: Sequence[BaseMessage]) -> list[BaseMessage]: + sanitized_messages: list[BaseMessage] = [] + for message in messages: + content = message.content + if not isinstance(content, list): + sanitized_messages.append(message) + continue + + cleaned_content = [ + item + for item in content + if not (isinstance(item, dict) and item.get("type") in _UNSUPPORTED_TOKEN_COUNT_BLOCK_TYPES) + ] + + sanitized_messages.append( + message.model_copy(update={"content": cleaned_content}) + ) + return sanitized_messages + + class UsageTrackingOpenAIEmbeddings(OpenAIEmbeddings): usage_tracker: Callable[[int], None] diff --git a/src/backend/tero/ai_models/vllm_provider.py b/src/backend/tero/ai_models/vllm_provider.py index ece798d..e47d0a4 100644 --- a/src/backend/tero/ai_models/vllm_provider.py +++ b/src/backend/tero/ai_models/vllm_provider.py @@ -8,6 +8,7 @@ from langchain_core.messages import BaseMessage from langchain_core.tools import BaseTool from langchain_openai import ChatOpenAI +from openai import RateLimitError from langchain_core.utils.function_calling import convert_to_openai_tool from tokenizers import Tokenizer @@ -36,6 +37,9 @@ def _find_vllm_model(self, model: str) -> tuple[int, str]: def supports_model(self, model: str) -> bool: return model in env.vllm_model_id_mapping + def is_rate_limit_error(self, exc: Exception) -> bool: + return isinstance(exc, RateLimitError) + def build_embedding(self, model: str, usage_tracker: Callable[[int], None]) -> Embeddings: index, model_id = self._find_vllm_model(model) print(f"Building embedding model {model_id} with context limit {env.embedding_context_limit}") diff --git a/src/backend/tero/core/env.py b/src/backend/tero/core/env.py index b2f0cbb..a443fb3 100644 --- a/src/backend/tero/core/env.py +++ b/src/backend/tero/core/env.py @@ -17,7 +17,7 @@ class AzureModelDeployment(BaseModel): class Settings(BaseSettings): model_config = SettingsConfigDict(enable_decoding=False, extra="allow") - + db_url : str secret_encryption_key : SecretStr frontend_url : str @@ -41,9 +41,11 @@ class Settings(BaseSettings): monthly_usd_limit_default : int internal_generator_model : str internal_generator_temperature : float + internal_generator_reasoning_effort : str internal_evaluator_model : Optional[str] = None agent_default_model : Optional[str] = None agent_basic_models : List[str] + agent_base_cost_model : Optional[str] = None default_agent_name : str embedding_model : str embedding_context_limit : int = 8191 @@ -75,14 +77,14 @@ class Settings(BaseSettings): web_tool_google_cost_per_1k_searches_usd : float browser_tool_playwright_mcp_url : str browser_tool_playwright_output_dir : str - + def is_local_env(self) -> bool: found = re.search('@([^/]+)(?:\\d+)?/', self.db_url) if not found: return False db_host = found.group(1) return db_host == 'localhost' or db_host == 'postgres' - + @field_validator('azure_model_deployments', mode='before') @classmethod def decode_model_deployments(cls, v: str) -> dict[str, AzureModelDeployment]: @@ -92,17 +94,17 @@ def decode_model_deployments(cls, v: str) -> dict[str, AzureModelDeployment]: deployment_parts = deployment.split('@', 1) ret[model_id] = AzureModelDeployment(deployment_name=deployment_parts[0], endpoint_index=int(deployment_parts[1]) if len(deployment_parts) > 1 else 0) return ret - + @field_validator('aws_model_id_mapping', 'google_model_id_mapping', 'openai_model_id_mapping', 'vllm_model_id_mapping', mode='before') @classmethod def decode_model_id_mapping(cls, v: str) -> dict[str, str]: return {k: v for k, v in (pair.split(':', 1) for pair in v.split(','))} if v else {} - + @field_validator('temperatures', mode='before') @classmethod def decode_temperatures(cls, v: str) -> dict[str, float]: return {k: float(v) for k, v in (pair.split(':', 1) for pair in v.split(','))} if v else {} - + @field_validator('allowed_users', 'azure_endpoints', 'azure_api_keys', 'agent_basic_models', 'vllm_urls', 'vllm_api_keys', mode='before') @classmethod def decode_list(cls, v: str) -> list[str]: @@ -112,6 +114,7 @@ def decode_list(cls, v: str) -> list[str]: def set_defaults(self): self.agent_default_model = self.agent_default_model or self.internal_generator_model self.internal_evaluator_model = self.internal_evaluator_model or self.internal_generator_model + self.agent_base_cost_model = self.agent_base_cost_model or self.agent_default_model return self @model_validator(mode="after") @@ -127,5 +130,5 @@ def _find_env_file() -> str: return path raise FileNotFoundError('No .env file found') - + env = Settings(_env_file=_find_env_file(), _env_file_encoding='utf-8') # type: ignore diff --git a/src/backend/tero/core/repos.py b/src/backend/tero/core/repos.py index cbb0f9d..c2a57cf 100644 --- a/src/backend/tero/core/repos.py +++ b/src/backend/tero/core/repos.py @@ -1,4 +1,4 @@ -from typing import AsyncGenerator, Any, Optional, cast +from typing import Any, AsyncGenerator, Optional, cast from cryptography.fernet import Fernet from sqlalchemy import Dialect diff --git a/src/backend/tero/files/processors/__init__.py b/src/backend/tero/files/processors/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/backend/tero/mcp_server.py b/src/backend/tero/mcp_server.py index 3dafadf..3159829 100644 --- a/src/backend/tero/mcp_server.py +++ b/src/backend/tero/mcp_server.py @@ -3,29 +3,40 @@ from fastapi_mcp import AuthConfig, FastApiMCP import httpx +from .agents.mcp_tools import router as agents_mcp_tools_router from .core.auth import get_current_user from .core.env import env from .external_agents.mcp_tools import router as external_agents_mcp_tools_router +from .threads.mcp_tools import router as threads_mcp_tools_router -FRONTEND_OPENID_URL = env.frontend_openid_url or env.openid_url +OPENID_SCOPES = ["openid", "profile", "email", "offline_access"] mcp_server_router = APIRouter() @mcp_server_router.get("/.well-known/oauth-protected-resource") -async def oauth_protected_resource_forward(): - return RedirectResponse(url=f"{FRONTEND_OPENID_URL}/.well-known/oauth-protected-resource") +async def oauth_protected_resource_metadata(): + base_url = env.frontend_url.rstrip("/") + return { + "resource": f"{base_url}/mcp", + "authorization_servers": [base_url], + "scopes_supported": OPENID_SCOPES, + "bearer_methods_supported": ["header"], + } @mcp_server_router.get("/.well-known/oauth-authorization-server") async def oauth_authorization_server_forward(): - return RedirectResponse(url=f"{FRONTEND_OPENID_URL}/.well-known/oauth-authorization-server") + frontend_openid_url = env.frontend_openid_url or env.openid_url + return RedirectResponse(url=f"{frontend_openid_url}/.well-known/oauth-authorization-server") # This is a workaround to allow the required-action page to load the css and js files @mcp_server_router.get("/resources/{path:path}") async def resources_forward(path: str): - target_url = f"{env.openid_url.replace("/realms/tero", "")}/resources/{path}" + openid_url = env.openid_url + keycloak_base = openid_url[:openid_url.index("/realms/")] + target_url = f"{keycloak_base}/resources/{path}" async with httpx.AsyncClient() as client: response = await client.get(target_url) return Response(content=response.content, status_code=response.status_code) @@ -34,21 +45,18 @@ async def resources_forward(path: str): def setup_mcp_server(app: FastAPI): if not env.openid_url: return - - with httpx.Client() as client: - response = client.get(f"{env.openid_url}/.well-known/openid-configuration") - custom_oauth_metadata = response.json() - + mcp = FastApiMCP( app, auth_config=AuthConfig( - custom_oauth_metadata=custom_oauth_metadata, dependencies=[Depends(get_current_user)], - ) + ), + headers=["authorization", "host"], ) mcp.mount_http() + app.include_router(agents_mcp_tools_router) + app.include_router(threads_mcp_tools_router) app.include_router(external_agents_mcp_tools_router) mcp.setup_server() # include the mcp server router after the mcp server is setup to avoid capturing well-known or other non-tool endpoints as tools app.include_router(mcp_server_router) - diff --git a/src/backend/tero/threads/api.py b/src/backend/tero/threads/api.py index 7aa2fdd..e68e235 100644 --- a/src/backend/tero/threads/api.py +++ b/src/backend/tero/threads/api.py @@ -31,7 +31,7 @@ from ..users.domain import User from .domain import ThreadListItem, Thread, ThreadMessage, ThreadMessageOrigin, ThreadUpdate,\ ThreadMessagePublic, ThreadMessageFile, ThreadMessageUpdate, AgentActionEvent, AgentFileEvent,\ - AgentMessageEvent, ThreadTranscriptionResult + AgentMessageEvent, ThreadTranscriptionResult, ModelRateLimitError from .engine import build_thread_name, AgentEngine from .repos import ThreadRepository, ThreadMessageRepository, ThreadMessageFileRepository from .time_saved_estimation import estimate_minutes_saved @@ -57,17 +57,21 @@ class ThreadCreateApi(CamelCaseModel): agent_id: int -@router.post(THREADS_PATH, status_code=status.HTTP_201_CREATED) -async def start_thread(thread: ThreadCreateApi, user: Annotated[User, Depends(get_current_user)], - db: Annotated[AsyncSession, Depends(get_db)]) -> ThreadListItem: - agent = await AgentRepository(db).find_by_id(thread.agent_id) +async def find_or_create_thread(agent_id: int, user: User, db: AsyncSession) -> Thread: + agent = await AgentRepository(db).find_by_id(agent_id) if not agent or not agent.is_visible_by(user): raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Agent not found") repo = ThreadRepository(db) - empty_thread = await repo.find_empty_thread(thread.agent_id, user.id) + empty_thread = await repo.find_empty_thread(agent_id, user.id) if empty_thread: - return ThreadListItem.from_thread(empty_thread) - ret = await repo.add(Thread(agent_id=thread.agent_id, user_id=user.id)) + return empty_thread + return await repo.add(Thread(agent_id=agent_id, user_id=user.id)) + + +@router.post(THREADS_PATH, status_code=status.HTTP_201_CREATED) +async def start_thread(thread: ThreadCreateApi, user: Annotated[User, Depends(get_current_user)], + db: Annotated[AsyncSession, Depends(get_db)]) -> ThreadListItem: + ret = await find_or_create_thread(thread.agent_id, user, db) return ThreadListItem.from_thread(ret) @@ -297,6 +301,15 @@ async def _agent_response(message: ThreadMessage, thread: Thread, user_id: int, status_updates=_dump_status_updates(status_updates) )) yield ServerSentEvent(event="error", data="recursionLimitExceeded").encode() + except* ModelRateLimitError: + await repo.add(ThreadMessage( + thread_id=thread.id, + text="ERROR_MODEL_RATE_LIMIT", + origin=ThreadMessageOrigin.SYSTEM, + parent_id=message.id, + status_updates=_dump_status_updates(status_updates) + )) + yield ServerSentEvent(event="error", data="modelRateLimitExceeded").encode() except* Exception: logger.exception(f"Problem answering message in thread {thread.id}") await repo.add(ThreadMessage( diff --git a/src/backend/tero/threads/domain.py b/src/backend/tero/threads/domain.py index 7e3d3fd..576b8ef 100644 --- a/src/backend/tero/threads/domain.py +++ b/src/backend/tero/threads/domain.py @@ -171,3 +171,7 @@ class AgentMessageEvent(AgentEvent): class ThreadTranscriptionResult(CamelCaseModel): transcription: str + + +class ModelRateLimitError(Exception): + pass diff --git a/src/backend/tero/threads/engine.py b/src/backend/tero/threads/engine.py index 8691bc3..e7d1d9a 100644 --- a/src/backend/tero/threads/engine.py +++ b/src/backend/tero/threads/engine.py @@ -30,7 +30,7 @@ from ..tools.core import AgentTool, AgentToolMetadata from ..tools.repos import ToolRepository from ..usage.domain import MessageUsage -from .domain import ThreadMessage, ThreadMessageOrigin, MAX_THREAD_NAME_LENGTH, AgentEvent, AgentActionEvent, AgentFileEvent, AgentMessageEvent, AgentAction +from .domain import ThreadMessage, ThreadMessageOrigin, MAX_THREAD_NAME_LENGTH, AgentEvent, AgentActionEvent, AgentFileEvent, AgentMessageEvent, AgentAction, ModelRateLimitError # adding this tool because we are going to add more tools in the future and right now @@ -64,7 +64,8 @@ async def load_tools(self, stack: AsyncExitStack, thread_id: Optional[int] = Non return ret async def answer(self, messages: List[ThreadMessage], message_usage: MessageUsage, stop_event: asyncio.Event) -> AsyncIterator[AgentEvent]: - llm = ai_factory.build_streaming_chat_model(self._agent.model.id, self._agent.model_temperature, self._agent.model_reasoning_effort) + provider = ai_factory.get_provider(self._agent.model.id) + llm = provider.build_streaming_chat_model(self._agent.model.id, self._agent.model_temperature, self._agent.model_reasoning_effort) async with AsyncExitStack() as stack: agent_tools = await self.load_tools(stack, thread_id=messages[0].thread_id) tools = [ lt for t in agent_tools for lt in await t.build_langchain_tools() ] @@ -84,31 +85,36 @@ async def answer(self, messages: List[ThreadMessage], message_usage: MessageUsag }, stream_mode=["updates", "messages", "custom"], ) - async for mode, content in stream: - if stop_event.is_set(): - break - if mode == "updates": - async for status_update in self._process_updates(content): - yield status_update - elif mode == "custom": - yield cast(AgentActionEvent, content) - elif mode == "messages": - msg, metadata = content - metadata = cast(dict, metadata) - # we need to filter AI messages since AI messages from tools are also returned - if ((isinstance(msg, AIMessage) and metadata.get("langgraph_node") != "tools") \ - or (isinstance(msg, ToolMessage) and msg.response_metadata.get("return_direct"))) \ - and msg.content: - content = self._get_content(msg.content) - generated_content += content - yield AgentMessageEvent(content=content) - if isinstance(msg, AIMessage): - message_usage.increment_with_metadata(msg.usage_metadata, self._agent.model) - elif isinstance(msg, ToolMessage): - agent_tool_metadata = AgentToolMetadata.model_validate(msg.response_metadata) - message_usage.increment_tool_usage(agent_tool_metadata.tool_usage) - if agent_tool_metadata.file: - yield AgentFileEvent(file=agent_tool_metadata.file) + try: + async for mode, content in stream: + if stop_event.is_set(): + break + if mode == "updates": + async for status_update in self._process_updates(content): + yield status_update + elif mode == "custom": + yield cast(AgentActionEvent, content) + elif mode == "messages": + msg, metadata = content + metadata = cast(dict, metadata) + # we need to filter AI messages since AI messages from tools are also returned + if ((isinstance(msg, AIMessage) and metadata.get("langgraph_node") != "tools") \ + or (isinstance(msg, ToolMessage) and msg.response_metadata.get("return_direct"))) \ + and msg.content: + content = self._get_content(msg.content) + generated_content += content + yield AgentMessageEvent(content=content) + if isinstance(msg, AIMessage): + message_usage.increment_with_metadata(msg.usage_metadata, self._agent.model) + elif isinstance(msg, ToolMessage): + agent_tool_metadata = AgentToolMetadata.model_validate(msg.response_metadata) + message_usage.increment_tool_usage(agent_tool_metadata.tool_usage) + if agent_tool_metadata.file: + yield AgentFileEvent(file=agent_tool_metadata.file) + except* Exception as eg: + if any(provider.is_rate_limit_error(e) for e in eg.exceptions): + raise ModelRateLimitError() + raise # If the response was stopped, approximate the token usage if stop_event.is_set(): @@ -252,7 +258,8 @@ async def build_thread_name(first_thread_message: str, message_usage: MessageUsa model = await AiModelRepository(db).find_by_id(env.internal_generator_model) if not model: raise ValueError("Internal generator model not found") - llm = ai_factory.build_chat_model(model.id, env.internal_generator_temperature) + llm = ai_factory.build_chat_model( + model.id, env.internal_generator_temperature, env.internal_generator_reasoning_effort) system_prompt = "From the following user message generate a short (less than 80 characters) title for the chat. Do not include quoting or any special characters." # invoke the llm using the prompt as system prompt and the first thread message as user message response = await llm.ainvoke( diff --git a/src/backend/tero/threads/mcp_tools.py b/src/backend/tero/threads/mcp_tools.py new file mode 100644 index 0000000..c4f112e --- /dev/null +++ b/src/backend/tero/threads/mcp_tools.py @@ -0,0 +1,149 @@ +import asyncio +import logging +from contextlib import AsyncExitStack +from typing import Annotated, Optional + +from fastapi import APIRouter, Depends, HTTPException, Query, status +from langgraph.errors import GraphRecursionError +from pydantic import BaseModel, Field +from sqlmodel.ext.asyncio.session import AsyncSession + +from ..core.api import MCP_PATH +from ..core.auth import get_current_user +from ..core.repos import get_db +from ..tools.auth import ToolAuthRequestException +from ..usage.domain import MessageUsage +from ..usage.repos import UsageRepository +from ..users.domain import User +from .api import find_or_create_thread, _find_thread +from .domain import ThreadMessage, ThreadMessageOrigin, AgentMessageEvent +from .engine import build_thread_name, AgentEngine +from .repos import ThreadRepository, ThreadMessageRepository + +logger = logging.getLogger(__name__) + +router = APIRouter() + + +class McpThread(BaseModel): + thread_id: int + thread_name: Optional[str] + + +class McpSendMessageRequest(BaseModel): + message: str = Field(description="The message text to send to the agent") + + +class McpMessageResponse(BaseModel): + response: str + thread_id: int + + +@router.post(f"{MCP_PATH}/start-conversation", operation_id="start-conversation") +async def start_conversation( + agent_id: Annotated[int, Query(description="The ID of the agent to start a conversation with")], + user: Annotated[User, Depends(get_current_user)], + db: Annotated[AsyncSession, Depends(get_db)], +) -> McpThread: + """ + Start a new conversation with an agent. + + Creates a new conversation thread for the specified agent, or returns + an existing empty thread if one already exists. + """ + thread = await find_or_create_thread(agent_id, user, db) + return McpThread(thread_id=thread.id, thread_name=thread.name) + + +@router.post(f"{MCP_PATH}/send-message", operation_id="send-message") +async def send_message( + thread_id: Annotated[int, Query(description="The ID of the conversation thread")], + body: McpSendMessageRequest, + user: Annotated[User, Depends(get_current_user)], + db: Annotated[AsyncSession, Depends(get_db)], +) -> McpMessageResponse: + """ + Send a message in a conversation and get the agent's full response. + + Sends a text message to the agent in the specified thread and returns + the complete response. This is a non-streaming call that waits for the + full agent response before returning. + """ + thread = await _find_thread(thread_id, user.id, db) + + current_usage = await UsageRepository(db).find_current_month_user_usage_usd(user.id) + if current_usage >= user.monthly_usd_limit: + raise HTTPException(status_code=status.HTTP_429_TOO_MANY_REQUESTS, detail="Monthly usage quota exceeded") + + try: + engine = AgentEngine(thread.agent, user.id, db) + async with AsyncExitStack() as stack: + await engine.load_tools(stack) + except ToolAuthRequestException: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="Agent tools require authentication that cannot be completed via MCP" + ) + + repo = ThreadMessageRepository(db) + user_message = await repo.add(ThreadMessage( + thread_id=thread.id, + text=body.message, + origin=ThreadMessageOrigin.USER, + )) + + message_usage = MessageUsage( + user_id=user.id, agent_id=thread.agent_id, + model_id=thread.agent.model_id, message_id=user_message.id, + ) + + thread_messages = await repo.find_previous_messages(user_message) + if len(thread_messages) == 0: + thread.name = await build_thread_name(body.message, message_usage, db) + await ThreadRepository(db).update(thread) + + complete_answer = "" + try: + stop_event = asyncio.Event() + answer_stream = AgentEngine(thread.agent, user.id, db).answer( + [*thread_messages, user_message], message_usage, stop_event, + ) + async for event in answer_stream: + if isinstance(event, AgentMessageEvent): + complete_answer += event.content + + except* GraphRecursionError: + await repo.add(ThreadMessage( + thread_id=thread.id, + text="ERROR_RECURSION_LIMIT_EXCEEDED", + origin=ThreadMessageOrigin.SYSTEM, + parent_id=user_message.id, + )) + await UsageRepository(db).add(message_usage) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Agent exceeded its recursion limit while processing the message" + ) + except* Exception: + logger.exception(f"Error answering MCP message in thread {thread.id}") + await repo.add(ThreadMessage( + thread_id=thread.id, + text="ERROR_GENERIC", + origin=ThreadMessageOrigin.SYSTEM, + parent_id=user_message.id, + )) + await UsageRepository(db).add(message_usage) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="An error occurred while processing the message" + ) + + await repo.add(ThreadMessage( + thread_id=thread.id, + text=complete_answer, + origin=ThreadMessageOrigin.AGENT, + parent_id=user_message.id, + )) + await UsageRepository(db).add(message_usage) + + return McpMessageResponse(response=complete_answer, thread_id=thread.id) diff --git a/src/backend/tero/threads/time_saved_estimation.py b/src/backend/tero/threads/time_saved_estimation.py index 585610c..2d29c68 100644 --- a/src/backend/tero/threads/time_saved_estimation.py +++ b/src/backend/tero/threads/time_saved_estimation.py @@ -10,7 +10,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession from ..ai_models import ai_factory -from ..ai_models.domain import LlmTemperature, LlmModel +from ..ai_models.domain import LlmTemperature, LlmModel, ReasoningEffort from ..ai_models.repos import AiModelRepository from ..core.env import env from ..threads.core import trim_messages_to_fit_model @@ -24,9 +24,15 @@ async def estimate_minutes_saved(user_message: str, agent_response: str, thread: Thread, thread_messages: List[ThreadMessage], message_usage: MessageUsage, db: AsyncSession) -> int: thread_messages = thread_messages[-2:] if len(thread_messages) > 2 else thread_messages + evaluator_model_id = cast(str, env.internal_evaluator_model) internal_generator_model = cast(LlmModel, await AiModelRepository(db).find_by_id(env.internal_generator_model)) + internal_evaluator_model = cast(LlmModel, await AiModelRepository(db).find_by_id(evaluator_model_id)) feedback_messages = await ThreadMessageRepository(db).find_feedback_messages(thread.agent_id, thread.user_id, limit=4) - llm = ai_factory.build_chat_model(env.internal_generator_model, LlmTemperature.PRECISE.get_float()) + llm = ai_factory.build_chat_model( + evaluator_model_id, + LlmTemperature.PRECISE.get_float(), + ReasoningEffort.MEDIUM.value.lower(), + ) messages = [ HumanMessage(content=user_message), @@ -74,7 +80,7 @@ async def estimate_minutes_saved(user_message: str, agent_response: str, thread: logger.exception(f"Invalid int response from minutes saved estimation LLM: {response.content}") return 0 finally: - message_usage.increment_with_metadata(response.usage_metadata, internal_generator_model) + message_usage.increment_with_metadata(response.usage_metadata, internal_evaluator_model) def _add_reference_examples(feedback_thread_messages: List[ThreadMessage], feedback_trimmed_messages: List[BaseMessage]) -> str: diff --git a/src/backend/tero/tools/api.py b/src/backend/tero/tools/api.py index df65a4c..87273fe 100644 --- a/src/backend/tero/tools/api.py +++ b/src/backend/tero/tools/api.py @@ -10,7 +10,7 @@ from ..core.repos import get_db from ..users.domain import User from .core import AgentTool -from .auth import ToolAuthCallbackError, ToolAuthCallback, ToolOAuthCallback, ToolAuthTokenCallback, ToolAuthRepository, ToolOAuthState +from .auth import ToolAuthCallbackError, ToolOAuthCallback, ToolAuthTokenCallback, ToolAuthRepository, ToolOAuthState from .repos import ToolRepository diff --git a/src/backend/tero/tools/browser/tool.py b/src/backend/tero/tools/browser/tool.py index a60a7a9..3ed9f2e 100644 --- a/src/backend/tero/tools/browser/tool.py +++ b/src/backend/tero/tools/browser/tool.py @@ -1,3 +1,6 @@ +import copy +import os + import aiofiles import aiofiles.os from collections.abc import AsyncIterator @@ -30,14 +33,29 @@ class ScreenshotPersistingTool(BaseTool): _PLAYWRIGHT_OUTPUT_DIR = "/tmp/playwright-output" def __init__(self, tool: BaseTool, user_id: int, thread_id: Optional[int], db: AsyncSession): - super().__init__(name=tool.name, description=tool.description + ". IT IS IMPORTANT TO AVOID USING PROVIDED PATH TO GENERATE RESPONSES, unless the user explicitly asks for it, since the agent already provides other means to download the screenshot. IT IS VERY IMPORTANT TO NOT USE RETURNED URL AS SOURCE OF AN IMAGE.", args_schema=self._build_args_schema(cast(dict, tool.args_schema)), metadata=tool.metadata) + super().__init__( + name=tool.name, + description=tool.description + ". IT IS IMPORTANT TO AVOID USING PROVIDED PATH TO GENERATE RESPONSES, unless the user explicitly asks for it, since the agent already provides other means to download the screenshot. IT IS VERY IMPORTANT TO NOT USE RETURNED URL AS SOURCE OF AN IMAGE.", + args_schema=self._build_args_schema(cast(dict, tool.args_schema)), + metadata=tool.metadata, + ) self._mcp_tool = tool self._user_id = user_id self._db = db self._thread_id = thread_id def _build_args_schema(self, args_schema: dict) -> type[BaseModel]: - OriginalModel = create_model(args_schema) + # Custom screenshot names from Playwright MCP may be written outside --output-dir; we only read the shared + # output path, so callers must use the default generated filename there. + schema = copy.deepcopy(args_schema) + props = schema.get("properties") + if isinstance(props, dict): + props.pop("filename", None) + req = schema.get("required") + if isinstance(req, list): + schema["required"] = [r for r in req if r != "filename"] + + OriginalModel = create_model(schema) class ScreenshotPersistingToolArgs(OriginalModel): tool_call_id: Annotated[str, InjectedToolCallId] return ScreenshotPersistingToolArgs @@ -46,24 +64,55 @@ def _run(self, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError("Synchronous run not implemented.") async def _arun(self, *args: Any, config: RunnableConfig, - run_manager: Optional[AsyncCallbackManagerForToolRun] = None, **kwargs: Any) -> Any: - result = await self._mcp_tool._arun(*args, config=config, run_manager=run_manager, **kwargs) + run_manager: Optional[AsyncCallbackManagerForToolRun] = None, + tool_call_id: Annotated[Optional[str], InjectedToolCallId] = None, + **kwargs: Any) -> Any: + + clean_kwargs = {k: v for k, v in kwargs.items() if v is not None and k != "tool_call_id"} + result = await self._mcp_tool._arun(*args, config=config, run_manager=run_manager, **clean_kwargs) + path = None try: path = self._extract_screenshot_path(result) file = await self._save_screenshot_to_database(path) return ToolMessage( - tool_call_id=kwargs.get("tool_call_id"), + tool_call_id=tool_call_id, name=self.name, content=self._replace_file_path_references(result[0], path, file), response_metadata={"file": file} ) except Exception: - logger.error("Problem saving screenshot to database", exc_info=True) + if path: + logger.info(f"Extracted screenshot path: {path}") + await self._log_playwright_output_files() + logger.error(f"Problem saving screenshot to database for result: {result[0]}", exc_info=True) return "Error saving screenshot to database" def _extract_screenshot_path(self, result: Any) -> str: - match = re.search(f"{self._PLAYWRIGHT_OUTPUT_DIR}/.*\\.png", result[0]) - return cast(re.Match, match).group(0) + # The MCP Playwright tool usually returns the full output path, but in + # some cases it may return only the screenshot filename inside a markdown + # link, e.g. `(01_home_loaded.png)`. + content = result[0] + + full_path_match = re.search( + rf"{re.escape(self._PLAYWRIGHT_OUTPUT_DIR)}/.*?\.png", + content, + ) + if full_path_match: + return cast(re.Match, full_path_match).group(0) + + file_name = None + link_target_match = re.search(r"\(([^)]+\.png)\)", content) + if link_target_match: + file_name = link_target_match.group(1).split("/")[-1] + else: + png_names = re.findall(r"([^/\s]+\.png)", content) + if png_names: + file_name = png_names[-1] + + if file_name: + return f"{self._PLAYWRIGHT_OUTPUT_DIR}/{file_name}" + + raise ValueError(f"Could not extract screenshot path from: {content!r}") async def _save_screenshot_to_database(self, file_path: Any) -> FileMetadata: host_path = file_path.replace(self._PLAYWRIGHT_OUTPUT_DIR, env.browser_tool_playwright_output_dir) @@ -77,18 +126,25 @@ async def _save_screenshot_to_database(self, file_path: Any) -> FileMetadata: await aiofiles.os.remove(host_path) return FileMetadata.from_file(ret) + async def _log_playwright_output_files(self) -> None: + base = os.path.abspath(env.browser_tool_playwright_output_dir) + names = sorted(await aiofiles.os.listdir(base)) + listing = "\n".join(names) if names else "(empty)" + logger.info(f"Playwright output directory: {base}\n{listing}") + def _replace_file_path_references(self, content: str, path: str, file: FileMetadata) -> str: final_path = f"/chat/{self._thread_id}/files/{file.id}" - ret = content.replace(path, f"{env.frontend_url}{final_path}", 1) - return ret.replace(path, final_path, 1) + final_absolute_url = f"{env.frontend_url}{final_path}" + ret = content.replace(f"../.." + path, final_absolute_url) + ret = ret.replace(path, final_absolute_url) + file_name = path.split("/")[-1] + return ret.replace(f"({file_name})", final_path) class BrowserTool(AgentTool): id: str = BROWSER_TOOL_ID name: str = "Browser Tools" - description: str = ( - "Provides tools to control a browser like navigating, click elements, fill, get page contents, screenshots, etc." - ) + description: str = "Interact with web sites and capture screenshots" config_schema: dict = load_schema(__file__) _tools: Optional[list[BaseTool]] = None diff --git a/src/backend/tero/tools/core.py b/src/backend/tero/tools/core.py index 928e653..32096a7 100644 --- a/src/backend/tero/tools/core.py +++ b/src/backend/tero/tools/core.py @@ -2,6 +2,7 @@ from collections.abc import AsyncIterator from contextlib import asynccontextmanager import json +import logging from typing import Any, List, Optional, cast, Callable from uuid import UUID diff --git a/src/backend/tero/tools/docs/tool.py b/src/backend/tero/tools/docs/tool.py index be89e9f..5760528 100644 --- a/src/backend/tero/tools/docs/tool.py +++ b/src/backend/tero/tools/docs/tool.py @@ -22,7 +22,7 @@ from langchain_core.tools import BaseTool, StructuredTool from langchain_core.vectorstores import VectorStoreRetriever from langchain_postgres import PGVector -from langchain_text_splitters import MarkdownTextSplitter, CharacterTextSplitter +from langchain_text_splitters import MarkdownTextSplitter, RecursiveCharacterTextSplitter from langgraph.config import get_stream_writer from pydantic import BaseModel, Field, model_validator from sqlalchemy import text @@ -83,7 +83,7 @@ class DocsTool(AgentToolWithFiles): id: str = DOCS_TOOL_ID name: str = "Docs" description: str = ( - "Allows to use information from uploaded files in agent responses" + "Use information from uploaded files in agent responses" ) config_schema: dict = load_schema(__file__) _embedding_usage: Optional[Usage] = None @@ -130,8 +130,12 @@ async def teardown(self): def _build_vectorstore(self): ai_provider = ai_factory.get_provider(env.embedding_model) usage_tracker = lambda tokens: self.embedding_usage.increment(tokens, env.embedding_cost_per_1k_tokens) - return PGVector(embeddings=ai_provider.build_embedding(env.embedding_model, usage_tracker), connection=self._get_async_engine(), - collection_name=self._build_collection_name(self.agent.id), use_jsonb=True) + return PGVector( + embeddings=ai_provider.build_embedding(env.embedding_model, usage_tracker), + connection=self._get_async_engine(), + collection_name=self._build_collection_name(self.agent.id), + use_jsonb=True + ) async def add_file(self, file: File, user: User): await self._handle_file(file, user) @@ -190,10 +194,11 @@ async def _build_document(file: File, file_quota: FileQuota): return Document(page_content=content, metadata=metadata) async def _generate_file_description(self, file: File, model: LlmModel, message_usage: MessageUsage) -> str: - llm = ai_factory.build_chat_model(model.id, env.internal_generator_temperature) + llm = ai_factory.build_chat_model( + model.id, env.internal_generator_temperature, env.internal_generator_reasoning_effort) async with aiofiles.open(solve_asset_path('file-description-prompt.md', __file__)) as f: system_prompt = await f.read() - text_splitter = CharacterTextSplitter( + text_splitter = RecursiveCharacterTextSplitter( length_function=lambda text: llm.get_num_tokens(text), chunk_size=env.docs_tool_description_chunk_size, chunk_overlap=env.docs_tool_description_chunk_overlap) @@ -230,7 +235,8 @@ async def _generate_tool_description(self, files: List[DocToolFile], model: LlmM prompt = await f.read() for f in files: prompt += f"\n- {f.description}" - llm = ai_factory.build_chat_model(model.id, env.internal_generator_temperature) + llm = ai_factory.build_chat_model( + model.id, env.internal_generator_temperature, env.internal_generator_reasoning_effort) return await self._generate_description(prompt, 200, llm, model, message_usage) async def update_file(self, file: File, user: User): diff --git a/src/backend/tero/tools/github/__init__.py b/src/backend/tero/tools/github/__init__.py new file mode 100644 index 0000000..ac5d603 --- /dev/null +++ b/src/backend/tero/tools/github/__init__.py @@ -0,0 +1,3 @@ +from .tool import GitHubTool, GITHUB_TOOL_ID + +__all__ = ["GitHubTool", "GITHUB_TOOL_ID"] diff --git a/src/backend/tero/tools/github/tool-schema.json b/src/backend/tero/tools/github/tool-schema.json new file mode 100644 index 0000000..3a07f56 --- /dev/null +++ b/src/backend/tero/tools/github/tool-schema.json @@ -0,0 +1,13 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "token": { + "type": "string", + "writeOnly": true, + "description": "GitHub Personal Access Token or fine-grained token. Used as Bearer token to authenticate with the official GitHub MCP server (https://api.githubcopilot.com/mcp/)." + } + }, + "required": ["token"], + "additionalProperties": false +} diff --git a/src/backend/tero/tools/github/tool.py b/src/backend/tero/tools/github/tool.py new file mode 100644 index 0000000..d35d358 --- /dev/null +++ b/src/backend/tero/tools/github/tool.py @@ -0,0 +1,19 @@ +from ..core import load_schema +from ..mcp_tool import AuthType, BaseMcpTool + + +GITHUB_TOOL_ID = "github" +GITHUB_MCP_SERVER_URL = "https://api.githubcopilot.com/mcp/" + + +class GitHubTool(BaseMcpTool): + id: str = GITHUB_TOOL_ID + name: str = "GitHub" + description: str = "Search code, manage repos, review PRs and track issues" + config_schema: dict = load_schema(__file__) + + def _get_auth_type(self) -> AuthType: + return AuthType.BEARER_TOKEN + + def _get_mcp_server_url(self) -> str: + return GITHUB_MCP_SERVER_URL diff --git a/src/backend/tero/tools/jira/tool.py b/src/backend/tero/tools/jira/tool.py index ecbf190..0dd7d28 100644 --- a/src/backend/tero/tools/jira/tool.py +++ b/src/backend/tero/tools/jira/tool.py @@ -12,7 +12,7 @@ from ...core.repos import scalar from ..core import load_schema from ..openapi_tool import OpenApiTool -from ..auth import AgentToolOauth, ToolAuthCallback, ToolOAuthCallback, ToolOAuthClientInfo, ToolOAuthClientInfoRepository, ToolAuthRepository, OAuthMetadata, ToolAuthCallbackError +from ..auth import AgentToolOauth, ToolAuthCallback, ToolOAuthCallback, ToolOAuthClientInfo, ToolOAuthClientInfoRepository, ToolAuthRepository, OAuthMetadata, ToolAuthCallbackError, ToolAuthRequestException, ToolAuthTokenRequest logger = logging.getLogger(__name__) @@ -49,7 +49,7 @@ async def delete(self, agent_id: int): class JiraTool(OpenApiTool): id: str = JIRA_TOOL_ID name: str = "Jira" - description: str = "Allows to use interact with Jira" + description: str = "Manage issues and track project activity" config_schema: dict = load_schema(__file__) _client_secret: Optional[str] = None @@ -89,6 +89,8 @@ async def _load_oauth(self) -> AgentToolOauth: token_endpoint=AnyHttpUrl(f"{base_url}/oauth/token") ) client_info = await ToolOAuthClientInfoRepository(self.db).find_by_ids(self.agent.id, self.id) + if not client_info or not client_info.scope: + raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) # add offline_access scope to be able to refresh tokens return AgentToolOauth(base_url, oauth_metadata, cast(str, cast(ToolOAuthClientInfo, client_info).scope) + " offline_access", self.agent.id, self.id, self.user_id, self.db) diff --git a/src/backend/tero/tools/mcp/tool-schema.json b/src/backend/tero/tools/mcp/tool-schema.json index bc6ad01..dc87fa7 100644 --- a/src/backend/tero/tools/mcp/tool-schema.json +++ b/src/backend/tero/tools/mcp/tool-schema.json @@ -21,6 +21,24 @@ "const": "bearerToken" } } + }, + "customHeaders": { + "type": "array", + "description": "Custom HTTP headers to include in MCP server requests", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "value": { + "type": "string" + } + }, + "required": ["name", "value"], + "additionalProperties": false + } } }, "required": ["serverUrl", "authType"], diff --git a/src/backend/tero/tools/mcp/tool.py b/src/backend/tero/tools/mcp/tool.py index caa4a48..b1bb5ca 100644 --- a/src/backend/tero/tools/mcp/tool.py +++ b/src/backend/tero/tools/mcp/tool.py @@ -1,210 +1,39 @@ -from collections.abc import AsyncIterator -from contextlib import asynccontextmanager -from datetime import timedelta -from enum import Enum import logging -from typing import Any, Optional, cast, Mapping -from httpx import HTTPStatusError - -from langchain_core.tools import BaseTool -from langchain_mcp_adapters.client import MultiServerMCPClient, SSEConnection -from langchain_mcp_adapters.tools import load_mcp_tools -from mcp.shared.session import RequestResponder -from mcp.types import ServerRequest, ClientResult, ServerNotification +from typing import Optional, cast from pydantic import AnyHttpUrl from sqlmodel.ext.asyncio.session import AsyncSession -from ...agents.domain import Agent, AgentToolConfig -from ..core import AgentTool, StatusUpdateCallbackHandler, load_schema -from ..auth import AgentToolOauth, ToolAuthCallback, ToolOAuthCallback, ToolOAuthClientInfoRepository, ToolAuthRepository, ToolAuthToken, ToolAuthTokenType, ToolAuthTokenCallback, ToolAuthTokenRequest, ToolAuthCallbackError, ToolAuthRequestException +from ...agents.domain import Agent +from ..core import load_schema +from ..mcp_tool import AuthType, BaseMcpTool MCP_TOOL_ID = "mcp" logger = logging.getLogger(__name__) -class AuthType(str, Enum): - OAUTH = "oauth" - BEARER_TOKEN = "bearerToken" - - @classmethod - def from_string(cls, value: str) -> "AuthType": - if value == "oauth": - return cls.OAUTH - elif value == "bearerToken": - return cls.BEARER_TOKEN - else: - raise ValueError(f"Invalid auth type: {value}") - - -class McpTool(AgentTool): +class McpTool(BaseMcpTool): id: str = MCP_TOOL_ID + "-*" name: str = "MCP" - description: str = "Allows to use a set of tools provided by a MCP server" + description: str = "Use tools from any MCP server" config_schema: dict = load_schema(__file__) - _oauth: Optional[AgentToolOauth] = None - _bearer_token: Optional[str] = None - _server_url: str = "" - _tools: Optional[list[BaseTool]] = None - + _bearer_secret_key: str = "bearerToken" + def configure(self, agent: Agent, user_id: int, config: dict, db: AsyncSession, thread_id: Optional[int] = None): super().configure(agent, user_id, config, db, thread_id) - self._server_url = config.get("serverUrl", "") - self.id = MCP_TOOL_ID + "-" + cast(str, AnyHttpUrl(self._server_url).host) - if self._get_auth_type() == AuthType.OAUTH: - self._oauth = AgentToolOauth(self._server_url, None, None, agent.id, self.id, user_id, db) + server_url = cast(str, config.get("serverUrl", "")) + self.id = MCP_TOOL_ID + "-" + cast(str, AnyHttpUrl(server_url).host) def _get_auth_type(self) -> AuthType: return AuthType.from_string(self.config.get("authType", AuthType.OAUTH.value)) - async def _setup_tool(self, prev_config: Optional[AgentToolConfig]): - prev_token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) - self._bearer_token = self._get_secret("bearerToken") - if (prev_config and self.config != prev_config.config - or self._get_auth_type() == AuthType.BEARER_TOKEN and prev_token and self._bearer_token - and self._bearer_token != prev_token.access_token): - await self.teardown() - await self._check_auth() - await self._save_bearer_token(self._bearer_token, self.db) - - async def teardown(self): - await ToolAuthRepository(self.db).delete_token(self.user_id, self.agent.id, self.id) - await ToolOAuthClientInfoRepository(self.db).delete(self.agent.id, self.id) - - async def _check_auth(self): - try: - async with self.load(): - pass - except ToolAuthRequestException: - if self._get_auth_type() == AuthType.BEARER_TOKEN: - # Avoid tool oauth request exception asking for a new token (and open an additional dialog in frontend) - # and just raise an error so interface knows that originial config was invalid - raise ToolAuthCallbackError("Invalid bearer token") - else: - raise - - @asynccontextmanager - async def load(self) -> AsyncIterator['McpTool']: - app_name = cast(str, AnyHttpUrl(self._server_url).host) - transport = "sse" if self._server_url.endswith("/sse") else "streamable_http" - - async def error_raising_message_handler( - message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception - ) -> None: - if isinstance(message, Exception): - raise message - - auth_token = await self._get_auth_token() - base_config: dict[str, Any] = { - "transport": transport, - "url": self._server_url, - "headers": { "Authorization": auth_token } if auth_token else {}, - "session_kwargs": { - "message_handler": error_raising_message_handler, - "read_timeout_seconds": timedelta(seconds=30) - } - } - connections: Mapping[str, Any] = {app_name: cast(SSEConnection, base_config) if transport == "sse" else base_config} - try: - async with MultiServerMCPClient(connections).session(app_name) as mcp_session: - tools = await load_mcp_tools(mcp_session) - # this is required since https://mcp.atlassian.com/v1/sse returns arrays with no items description and openAI doesn't like tool with such schema, - # same happens with https://browser.mcp.cloudflare.com/sse returning a type object with no properties - self._tools = self._fix_tools_schemas(tools) - for tool in self._tools: - tool.callbacks = [StatusUpdateCallbackHandler(tool.name, description=tool.description)] - yield self - except ExceptionGroup as exception: - # If a bearer token is present but invalid/expired, the MCP server responds with 401; catch that case here. - if self._get_auth_type() == AuthType.BEARER_TOKEN \ - and any(isinstance(e, HTTPStatusError) and e.response.status_code == 401 for e in exception.exceptions): - raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) - else: - raise - - async def _get_auth_token(self) -> Optional[str]: - auth_type = self._get_auth_type() - if auth_type == AuthType.BEARER_TOKEN: - bearer_token = await self._get_bearer_token() - return f"Bearer {bearer_token}" - elif auth_type == AuthType.OAUTH: - oauth = self._get_oauth() - tokens = await oauth.solve_tokens() - return f"Bearer {tokens.access_token}" if tokens else None - else: - raise ValueError("Invalid Auth Type") - - async def _get_bearer_token(self) -> str: - if self._bearer_token: - return self._bearer_token - token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) - if not token: - raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) - return token.access_token - - def _get_oauth(self) -> AgentToolOauth: - if not self._oauth: - raise RuntimeError("MCP tool has not been set up properly") - return self._oauth + def _get_mcp_server_url(self) -> str: + return cast(str, self.config.get("serverUrl", "")) - def _fix_tools_schemas(self, tools: list[BaseTool]) -> list[BaseTool]: - ret = [] - for tool in tools: - tool.args_schema = self._fix_schema(tool.args_schema) - ret.append(tool) - return ret - - def _fix_schema(self, schema: Any) -> Any: - if isinstance(schema, dict): - items = schema.get("items", {}) - if schema.get("type") == "array" and not items.get("type"): - if not items: - schema["items"] = {"type": "string"} - else: - schema["items"]["type"] = "string" - return schema - elif schema.get("type") == "object" and not schema.get("properties"): - schema["properties"] = {} - return schema - else: - return { key: self._fix_schema(value) for key, value in schema.items() } - elif isinstance(schema, list): - return [self._fix_schema(item) for item in schema] - else: - return schema + def _get_transport(self) -> str: + url = self._get_mcp_server_url() + return "sse" if url.endswith("/sse") else "streamable_http" - async def _save_bearer_token(self, bearer_token: Optional[str], db: AsyncSession): - if not bearer_token: - return - token = ToolAuthToken( - user_id=self.user_id, - agent_id=self.agent.id, - tool_id=self.id, - access_token=bearer_token, - token_type=ToolAuthTokenType.BEARER, - expires_in=None, - scope=None, - refresh_token=None, - expires_at=None - ) - await ToolAuthRepository(db).save_token(token) - - async def build_langchain_tools(self) -> list[BaseTool]: - if not self._tools: - raise RuntimeError("MCP tool has not been set up properly") - return self._tools - - async def auth(self, auth_callback: ToolAuthCallback): - auth_type = self._get_auth_type() - if auth_type == AuthType.OAUTH: - state = await ToolAuthRepository(self.db).find_state(self.user_id, self.id, cast(ToolOAuthCallback, auth_callback).state) - if not state: - raise ToolAuthCallbackError("OAuth state not found") - await self._get_oauth().callback(cast(ToolOAuthCallback, auth_callback), state) - elif auth_type == AuthType.BEARER_TOKEN: - self._bearer_token = cast(ToolAuthTokenCallback, auth_callback).auth_token - await self._check_auth() - await self._save_bearer_token(self._bearer_token, self.db) + def _get_connection_id(self) -> str: + return cast(str, AnyHttpUrl(self._get_mcp_server_url()).host) - async def clone(self, agent_id: int, cloned_agent_id: int, tool_id: str, user_id: int, db: AsyncSession) -> None: - pass diff --git a/src/backend/tero/tools/mcp_tool.py b/src/backend/tero/tools/mcp_tool.py new file mode 100644 index 0000000..abdd163 --- /dev/null +++ b/src/backend/tero/tools/mcp_tool.py @@ -0,0 +1,254 @@ +import abc +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from datetime import timedelta +from enum import Enum +import logging +from typing import Any, Mapping, Optional, cast + +from httpx import HTTPStatusError +from langchain.tools import BaseTool +from langchain_mcp_adapters.client import MultiServerMCPClient +from langchain_mcp_adapters.tools import load_mcp_tools +from mcp.shared.session import RequestResponder +from mcp.types import ServerRequest, ClientResult, ServerNotification +from sqlmodel.ext.asyncio.session import AsyncSession + +from ..agents.domain import AgentToolConfig +from ..tools.auth import ( + AgentToolOauth, + ToolAuthCallback, + ToolAuthCallbackError, + ToolAuthRepository, + ToolAuthRequestException, + ToolAuthToken, + ToolAuthTokenType, + ToolAuthTokenCallback, + ToolAuthTokenRequest, + ToolOAuthCallback, + ToolOAuthClientInfoRepository, +) +from .core import AgentTool, StatusUpdateCallbackHandler + + +class AuthType(str, Enum): + OAUTH = "oauth" + BEARER_TOKEN = "bearerToken" + + @classmethod + def from_string(cls, value: str) -> "AuthType": + if value == "oauth": + return cls.OAUTH + if value == "bearerToken": + return cls.BEARER_TOKEN + raise ValueError(f"Invalid auth type: {value}") + +logger = logging.getLogger(__name__) + + +class BaseMcpTool(AgentTool, abc.ABC): + _tools: Optional[list[BaseTool]] = None + _oauth: Optional[AgentToolOauth] = None + _pending_bearer_token: Optional[str] = None + _bearer_token: Optional[str] = None + _bearer_secret_key: str = "token" + + @abc.abstractmethod + def _get_mcp_server_url(self) -> str: + pass + + @abc.abstractmethod + def _get_auth_type(self) -> "AuthType": + pass + + async def _setup_tool(self, prev_config: Optional["AgentToolConfig"]): + self._oauth = None + self._bearer_token = self._get_secret(self._bearer_secret_key) + prev_token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) + if ( + prev_config + and self._auth_config(prev_config.config) != self._auth_config(self.config) + or self._get_auth_type() == AuthType.BEARER_TOKEN + and self._bearer_token + and prev_token + and self._bearer_token != prev_token.access_token + ): + await self.teardown() + if self._get_auth_type() == AuthType.BEARER_TOKEN and self._bearer_token: + await self._save_bearer_token(self._bearer_token, self.db) + await self._check_auth() + + async def teardown(self): + await ToolAuthRepository(self.db).delete_token(self.user_id, self.agent.id, self.id) + if self._get_auth_type() == AuthType.OAUTH: + await ToolOAuthClientInfoRepository(self.db).delete(self.agent.id, self.id) + + async def _check_auth(self): + async with self.load(): + pass + + async def clone(self, agent_id: int, cloned_agent_id: int, tool_id: str, user_id: int, db: AsyncSession) -> None: + pass + + @asynccontextmanager + async def load(self) -> AsyncIterator["BaseMcpTool"]: + async def error_raising_message_handler( + message: RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception, + ) -> None: + if isinstance(message, Exception): + raise message + + server_url = self._get_mcp_server_url() + auth_token = await self._get_auth_token() + connection_id = self._get_connection_id() + base_config: dict[str, Any] = { + "transport": self._get_transport(), + "url": server_url, + "headers": self._build_headers(await self._get_auth_headers(auth_token)), + "session_kwargs": { + "message_handler": error_raising_message_handler, + "read_timeout_seconds": timedelta(seconds=30), + }, + } + connections: Mapping[str, Any] = {connection_id: base_config} + try: + async with MultiServerMCPClient(connections).session(connection_id) as mcp_session: + tools = await load_mcp_tools(mcp_session) + self._tools = self._fix_tools_schemas(tools) + for tool in self._tools: + tool.callbacks = [StatusUpdateCallbackHandler(tool.name, description=tool.description)] + yield self + except ExceptionGroup as exception: + if any( + isinstance(e, HTTPStatusError) and e.response.status_code == 401 + for e in exception.exceptions + ): + raise ToolAuthRequestException( + ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id) + ) from exception + raise + + async def _get_auth_token(self) -> Optional[str]: + auth_type = self._get_auth_type() + if auth_type == AuthType.BEARER_TOKEN: + bearer = await self._get_bearer_token() + return f"Bearer {bearer}" + if auth_type == AuthType.OAUTH: + oauth = self._get_oauth() + tokens = await oauth.solve_tokens() + return f"Bearer {tokens.access_token}" if tokens else None + raise ValueError("Invalid auth type") + + async def _get_bearer_token(self) -> str: + if self._pending_bearer_token: + return self._pending_bearer_token + if self._bearer_token: + return self._bearer_token + token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) + if not token: + raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) + return token.access_token + + def _get_oauth(self) -> AgentToolOauth: + if not self._oauth: + server_url = self._get_mcp_server_url() + self._oauth = AgentToolOauth(server_url, None, None, self.agent.id, self.id, self.user_id, self.db) + return self._oauth + + def _get_transport(self) -> str: + url = self._get_mcp_server_url() + return "sse" if url.endswith("/sse") else "streamable_http" + + def _get_connection_id(self) -> str: + return self.id + + async def _get_auth_headers(self, auth_token: Optional[str] = None) -> dict[str, str]: + if auth_token: + return {"Authorization": auth_token} + return {} + + def _build_headers(self, auth_headers: dict[str, str]) -> dict[str, str]: + headers = dict(auth_headers) + for header in self.config.get("customHeaders", []): + headers[header["name"]] = header["value"] + return headers + + def _auth_config(self, config: dict) -> dict: + non_auth_keys = ["customHeaders"] + return {k: v for k, v in config.items() if k not in non_auth_keys} + + def _fix_tools_schemas(self, tools: list[BaseTool]) -> list[BaseTool]: + ret = [] + for tool in tools: + tool.args_schema = self._fix_schema(tool.args_schema) + ret.append(tool) + return ret + + def _fix_schema(self, schema: Any) -> Any: + if isinstance(schema, dict): + items = schema.get("items", {}) + if schema.get("type") == "array" and not items.get("type"): + if not items: + schema["items"] = {"type": "string"} + else: + schema["items"]["type"] = "string" + return schema + if schema.get("type") == "object" and not schema.get("properties"): + schema["properties"] = {} + return schema + return {key: self._fix_schema(value) for key, value in schema.items()} + if isinstance(schema, list): + return [self._fix_schema(item) for item in schema] + return schema + + async def _save_bearer_token(self, token: Optional[str], db: AsyncSession): + if not token: + return + auth_token = ToolAuthToken( + user_id=self.user_id, + agent_id=self.agent.id, + tool_id=self.id, + access_token=token, + token_type=ToolAuthTokenType.BEARER, + expires_in=None, + scope=None, + refresh_token=None, + expires_at=None, + ) + await ToolAuthRepository(db).save_token(auth_token) + + async def build_langchain_tools(self) -> list[BaseTool]: + if not self._tools: + raise RuntimeError(f"{self.name} tool has not been set up properly") + return self._tools + + async def auth(self, auth_callback: ToolAuthCallback): + auth_type = self._get_auth_type() + if auth_type == AuthType.OAUTH: + state = await ToolAuthRepository(self.db).find_state( + self.user_id, + self.id, + cast(ToolOAuthCallback, auth_callback).state, + ) + if not state: + raise ToolAuthCallbackError("OAuth state not found") + await self._get_oauth().callback(cast(ToolOAuthCallback, auth_callback), state) + return + + if auth_type == AuthType.BEARER_TOKEN: + self._pending_bearer_token = cast(ToolAuthTokenCallback, auth_callback).auth_token + try: + await self._check_auth() + except Exception: + logger.warning( + f"Invalid token for agent {self.agent.id} tool {self.id} user {self.user_id}", + exc_info=True, + ) + self._pending_bearer_token = None + raise ToolAuthCallbackError("Invalid token") + await self._save_bearer_token(self._pending_bearer_token, self.db) + self._pending_bearer_token = None + return + + raise ValueError("Invalid auth type") + diff --git a/src/backend/tero/tools/practitest/__init__.py b/src/backend/tero/tools/practitest/__init__.py new file mode 100644 index 0000000..85bf722 --- /dev/null +++ b/src/backend/tero/tools/practitest/__init__.py @@ -0,0 +1,3 @@ +from .tool import PractiTestTool, PRACTITEST_TOOL_ID + +__all__ = ["PractiTestTool", "PRACTITEST_TOOL_ID"] diff --git a/src/backend/tero/tools/practitest/tool-schema.json b/src/backend/tero/tools/practitest/tool-schema.json new file mode 100644 index 0000000..35b84f1 --- /dev/null +++ b/src/backend/tero/tools/practitest/tool-schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "serverUrl": { + "type": "string", + "format": "uri", + "description": "PractiTest MCP server URL." + }, + "token": { + "type": "string", + "writeOnly": true, + "description": "PractiTest personal API token. Used as Bearer token to authenticate with the PractiTest MCP server." + } + }, + "required": ["serverUrl", "token"], + "additionalProperties": false +} diff --git a/src/backend/tero/tools/practitest/tool.py b/src/backend/tero/tools/practitest/tool.py new file mode 100644 index 0000000..e75583d --- /dev/null +++ b/src/backend/tero/tools/practitest/tool.py @@ -0,0 +1,23 @@ +from typing import cast + +from ..core import load_schema +from ..mcp_tool import AuthType, BaseMcpTool + + +PRACTITEST_TOOL_ID = "practitest" + + +class PractiTestTool(BaseMcpTool): + id: str = PRACTITEST_TOOL_ID + name: str = "PractiTest" + description: str = "Manage tests, test sets and requirements" + config_schema: dict = load_schema(__file__) + + def _get_auth_type(self) -> AuthType: + return AuthType.BEARER_TOKEN + + def _get_mcp_server_url(self) -> str: + server_url = cast(str, self.config.get("serverUrl") or "") + if not server_url: + raise ValueError("PractiTest MCP serverUrl is not configured") + return server_url diff --git a/src/backend/tero/tools/redmine/__init__.py b/src/backend/tero/tools/redmine/__init__.py new file mode 100644 index 0000000..f58068f --- /dev/null +++ b/src/backend/tero/tools/redmine/__init__.py @@ -0,0 +1 @@ +from .tool import RedmineTool, REDMINE_TOOL_ID diff --git a/src/backend/tero/tools/redmine/assets/redmine-api-spec.json b/src/backend/tero/tools/redmine/assets/redmine-api-spec.json new file mode 100644 index 0000000..d8bdc56 --- /dev/null +++ b/src/backend/tero/tools/redmine/assets/redmine-api-spec.json @@ -0,0 +1,933 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "Redmine REST API", + "version": "2.4", + "description": "Subset of Redmine REST API commonly used for projects and issues. Requires API authentication and uses common pagination parameters on collection endpoints." + }, + "security": [ + { + "basicAuth": [] + } + ], + "components": { + "securitySchemes": { + "basicAuth": { + "type": "http", + "scheme": "basic", + "description": "Use the Redmine API key as the username and any string as the password. The API key can also be provided via query param or header, but this tool uses Basic Auth." + } + }, + "schemas": { + "UserResponse": { + "type": "object", + "properties": { + "user": { "$ref": "#/components/schemas/User" } + } + }, + "User": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "login": { "type": "string" }, + "firstname": { "type": "string" }, + "lastname": { "type": "string" }, + "mail": { "type": "string" } + } + }, + "ProjectsResponse": { + "type": "object", + "properties": { + "projects": { + "type": "array", + "items": { "$ref": "#/components/schemas/Project" } + }, + "total_count": { "type": "integer" }, + "offset": { "type": "integer" }, + "limit": { "type": "integer" } + } + }, + "Project": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "identifier": { "type": "string" }, + "description": { "type": "string" }, + "homepage": { "type": "string" }, + "status": { "type": "integer" }, + "is_public": { "type": "boolean" }, + "parent": { "$ref": "#/components/schemas/IdentifiedName" }, + "default_assignee": { "$ref": "#/components/schemas/IdentifiedName" }, + "default_version": { "$ref": "#/components/schemas/IdentifiedName" }, + "created_on": { "type": "string", "format": "date-time" }, + "updated_on": { "type": "string", "format": "date-time" } + } + }, + "IdentifiedName": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" } + } + }, + "ProjectResponse": { + "type": "object", + "properties": { + "project": { "$ref": "#/components/schemas/Project" } + } + }, + "MembershipsResponse": { + "type": "object", + "properties": { + "memberships": { + "type": "array", + "items": { "$ref": "#/components/schemas/Membership" } + }, + "total_count": { "type": "integer" }, + "offset": { "type": "integer" }, + "limit": { "type": "integer" } + } + }, + "Membership": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "project": { "$ref": "#/components/schemas/IdentifiedName" }, + "user": { "$ref": "#/components/schemas/IdentifiedName" }, + "group": { "$ref": "#/components/schemas/IdentifiedName" }, + "roles": { + "type": "array", + "items": { "$ref": "#/components/schemas/Role" } + } + } + }, + "Role": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "inherited": { "type": "boolean" } + } + }, + "MembershipResponse": { + "type": "object", + "properties": { + "membership": { "$ref": "#/components/schemas/Membership" } + } + }, + "VersionsResponse": { + "type": "object", + "properties": { + "versions": { + "type": "array", + "items": { "$ref": "#/components/schemas/Version" } + }, + "total_count": { "type": "integer" } + } + }, + "Version": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "project": { "$ref": "#/components/schemas/IdentifiedName" }, + "name": { "type": "string" }, + "description": { "type": "string" }, + "status": { "type": "string" }, + "sharing": { "type": "string" }, + "due_date": { "type": "string", "format": "date" }, + "estimated_hours": { "type": "number" }, + "spent_hours": { "type": "number" }, + "wiki_page_title": { "type": "string" }, + "created_on": { "type": "string", "format": "date-time" }, + "updated_on": { "type": "string", "format": "date-time" } + } + }, + "VersionResponse": { + "type": "object", + "properties": { + "version": { "$ref": "#/components/schemas/Version" } + } + }, + "IssuesResponse": { + "type": "object", + "properties": { + "issues": { + "type": "array", + "items": { "$ref": "#/components/schemas/Issue" } + }, + "total_count": { "type": "integer" }, + "offset": { "type": "integer" }, + "limit": { "type": "integer" } + } + }, + "Issue": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "project": { "$ref": "#/components/schemas/IdentifiedName" }, + "tracker": { "$ref": "#/components/schemas/IdentifiedName" }, + "status": { "$ref": "#/components/schemas/IdentifiedName" }, + "priority": { "$ref": "#/components/schemas/IdentifiedName" }, + "author": { "$ref": "#/components/schemas/IdentifiedName" }, + "assigned_to": { "$ref": "#/components/schemas/IdentifiedName" }, + "category": { "$ref": "#/components/schemas/IdentifiedName" }, + "fixed_version": { "$ref": "#/components/schemas/IdentifiedName" }, + "subject": { "type": "string" }, + "description": { "type": "string" }, + "start_date": { "type": "string", "format": "date" }, + "due_date": { "type": "string", "format": "date" }, + "done_ratio": { "type": "integer" }, + "estimated_hours": { "type": "number" }, + "is_private": { "type": "boolean" }, + "custom_fields": { + "type": "array", + "items": { "$ref": "#/components/schemas/CustomField" } + }, + "watchers": { + "type": "array", + "items": { "$ref": "#/components/schemas/User" } + }, + "created_on": { "type": "string", "format": "date-time" }, + "updated_on": { "type": "string", "format": "date-time" } + } + }, + "CustomField": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" }, + "customized_type": { "type": "string" }, + "field_format": { "type": "string" }, + "regexp": { "type": "string" }, + "min_length": { "type": "integer" }, + "max_length": { "type": "integer" }, + "is_required": { "type": "boolean" }, + "is_filter": { "type": "boolean" }, + "searchable": { "type": "boolean" }, + "multiple": { "type": "boolean" }, + "default_value": {}, + "visible": { "type": "boolean" }, + "possible_values": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": {} + } + } + } + } + }, + "IssueResponse": { + "type": "object", + "properties": { + "issue": { "$ref": "#/components/schemas/Issue" } + } + }, + "IssueCreateRequest": { + "type": "object", + "properties": { + "issue": { "$ref": "#/components/schemas/IssueCreate" } + }, + "required": ["issue"] + }, + "IssueCreate": { + "type": "object", + "properties": { + "project_id": { "type": "integer" }, + "tracker_id": { "type": "integer" }, + "status_id": { "type": "integer" }, + "priority_id": { "type": "integer" }, + "category_id": { "type": "integer" }, + "fixed_version_id": { "type": "integer" }, + "assigned_to_id": { "type": "integer" }, + "parent_issue_id": { "type": "integer" }, + "custom_fields": { + "type": "array", + "items": { "$ref": "#/components/schemas/CustomField" } + }, + "watcher_user_ids": { "type": "array", "items": { "type": "integer" } }, + "is_private": { "type": "boolean" }, + "estimated_hours": { "type": "number" }, + "subject": { "type": "string" }, + "description": { "type": "string" } + }, + "required": ["project_id", "subject"] + }, + "IssueCreateResponse": { + "type": "object", + "properties": { + "issue": { + "type": "object", + "properties": { + "id": { "type": "integer" } + } + } + } + }, + "IssueUpdateRequest": { + "type": "object", + "properties": { + "issue": { "$ref": "#/components/schemas/IssueUpdate" } + }, + "required": ["issue"] + }, + "IssueUpdate": { + "type": "object", + "properties": { + "tracker_id": { "type": "integer" }, + "status_id": { "type": "integer" }, + "priority_id": { "type": "integer" }, + "category_id": { "type": "integer" }, + "fixed_version_id": { "type": "integer" }, + "assigned_to_id": { "type": "integer" }, + "parent_issue_id": { "type": "integer" }, + "custom_fields": { + "type": "array", + "items": { "$ref": "#/components/schemas/CustomField" } + }, + "watcher_user_ids": { "type": "array", "items": { "type": "integer" } }, + "is_private": { "type": "boolean" }, + "estimated_hours": { "type": "number" }, + "subject": { "type": "string" }, + "description": { "type": "string" }, + "notes": { "type": "string" }, + "private_notes": { "type": "boolean" } + } + }, + "IssueRelationsResponse": { + "type": "object", + "properties": { + "relations": { + "type": "array", + "items": { "$ref": "#/components/schemas/IssueRelation" } + } + } + }, + "IssueRelation": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "issue_id": { "type": "integer" }, + "issue_to_id": { "type": "integer" }, + "relation_type": { "type": "string" }, + "delay": { "type": "integer" } + } + }, + "IssueRelationResponse": { + "type": "object", + "properties": { + "relation": { "$ref": "#/components/schemas/IssueRelation" } + } + }, + "IssueRelationCreateRequest": { + "type": "object", + "properties": { + "relation": { "$ref": "#/components/schemas/IssueRelationCreate" } + }, + "required": ["relation"] + }, + "IssueRelationCreate": { + "type": "object", + "properties": { + "issue_to_id": { "type": "integer" }, + "relation_type": { + "type": "string", + "enum": ["relates", "duplicates", "duplicated", "blocks", "blocked", "precedes", "follows", "copied_to", "copied_from"] + }, + "delay": { "type": "integer" } + }, + "required": ["issue_to_id", "relation_type"] + }, + "IssueStatusesResponse": { + "type": "object", + "properties": { + "issue_statuses": { + "type": "array", + "items": { "$ref": "#/components/schemas/IdentifiedName" } + } + } + }, + "TrackersResponse": { + "type": "object", + "properties": { + "trackers": { + "type": "array", + "items": { "$ref": "#/components/schemas/IdentifiedName" } + } + } + }, + "TimeEntriesResponse": { + "type": "object", + "properties": { + "time_entries": { + "type": "array", + "items": { "$ref": "#/components/schemas/TimeEntry" } + }, + "total_count": { "type": "integer" }, + "offset": { "type": "integer" }, + "limit": { "type": "integer" } + } + }, + "TimeEntry": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "project": { "$ref": "#/components/schemas/IdentifiedName" }, + "issue": { "$ref": "#/components/schemas/IdentifiedName" }, + "user": { "$ref": "#/components/schemas/IdentifiedName" }, + "activity": { "$ref": "#/components/schemas/TimeEntryActivity" }, + "hours": { "type": "number" }, + "spent_on": { "type": "string", "format": "date" }, + "comments": { "type": "string" }, + "created_on": { "type": "string", "format": "date-time" }, + "updated_on": { "type": "string", "format": "date-time" } + } + }, + "TimeEntryActivity": { + "type": "object", + "properties": { + "id": { "type": "integer" }, + "name": { "type": "string" } + } + }, + "TimeEntryResponse": { + "type": "object", + "properties": { + "time_entry": { "$ref": "#/components/schemas/TimeEntry" } + } + }, + "TimeEntryCreateRequest": { + "type": "object", + "properties": { + "time_entry": { "$ref": "#/components/schemas/TimeEntryCreate" } + }, + "required": ["time_entry"] + }, + "TimeEntryCreate": { + "type": "object", + "properties": { + "issue_id": { "type": "integer" }, + "project_id": { "type": "integer" }, + "spent_on": { "type": "string", "format": "date" }, + "hours": { "type": "number" }, + "activity_id": { "type": "integer" }, + "comments": { "type": "string" }, + "user_id": { "type": "integer" } + }, + "required": ["hours"] + }, + "TimeEntryCreateResponse": { + "type": "object", + "properties": { + "time_entry": { + "type": "object", + "properties": { + "id": { "type": "integer" } + } + } + } + }, + "TimeEntryUpdateRequest": { + "type": "object", + "properties": { + "time_entry": { "$ref": "#/components/schemas/TimeEntryUpdate" } + }, + "required": ["time_entry"] + }, + "TimeEntryUpdate": { + "type": "object", + "properties": { + "issue_id": { "type": "integer" }, + "project_id": { "type": "integer" }, + "spent_on": { "type": "string", "format": "date" }, + "hours": { "type": "number" }, + "activity_id": { "type": "integer" }, + "comments": { "type": "string" }, + "user_id": { "type": "integer" } + } + } + } + }, + "paths": { + "/users/current.json": { + "get": { + "operationId": "getCurrentUser", + "summary": "Get current user", + "description": "Fetches the current authenticated user. Useful for validating credentials and discovering the current user's id/login.", + "responses": { + "200": { + "description": "Current user", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserResponse" } + } + } + } + } + } + }, + "/users/{id}.json": { + "get": { + "operationId": "getUser", + "summary": "Get user", + "description": "Returns user details by id. Use /users/current.json for the current user.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "User id or 'current' for current user." }, + { "name": "include", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated associations to include (memberships, groups)." } + ], + "responses": { + "200": { + "description": "User", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/UserResponse" } + } + } + } + } + } + }, + "/projects.json": { + "get": { + "operationId": "listProjects", + "summary": "List projects", + "description": "Returns all projects (public and private where the user has access). Use pagination parameters to page through large project lists.", + "parameters": [ + { "name": "offset", "in": "query", "schema": { "type": "integer" }, "description": "Offset of the first project to return." }, + { "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "Max number of projects to return (default 25, max 100)." }, + { "name": "include", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated associations to include (trackers, issue_categories, enabled_modules, time_entry_activities, issue_custom_fields)." } + ], + "responses": { + "200": { + "description": "Projects", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProjectsResponse" } + } + } + } + } + } + }, + "/projects/{id}.json": { + "get": { + "operationId": "getProject", + "summary": "Get project", + "description": "Returns a project by id or identifier.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id or identifier." }, + { "name": "include", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated associations to include (trackers, issue_categories, enabled_modules, time_entry_activities, issue_custom_fields)." } + ], + "responses": { + "200": { + "description": "Project", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/ProjectResponse" } + } + } + } + } + } + }, + "/projects/{project_id}/memberships.json": { + "get": { + "operationId": "listProjectMemberships", + "summary": "List project memberships", + "description": "Returns a paginated list of project memberships for the given project id or identifier.", + "parameters": [ + { "name": "project_id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id or identifier." }, + { "name": "offset", "in": "query", "schema": { "type": "integer" }, "description": "Offset of the first membership to return." }, + { "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "Max number of memberships to return (default 25, max 100)." } + ], + "responses": { + "200": { + "description": "Memberships", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/MembershipsResponse" } + } + } + } + } + } + }, + "/memberships/{id}.json": { + "get": { + "operationId": "getMembership", + "summary": "Get membership", + "description": "Returns a project membership by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Membership id." } + ], + "responses": { + "200": { + "description": "Membership", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/MembershipResponse" } + } + } + } + } + } + }, + "/projects/{project_id}/versions.json": { + "get": { + "operationId": "listProjectVersions", + "summary": "List project versions", + "description": "Returns versions for the given project id or identifier.", + "parameters": [ + { "name": "project_id", "in": "path", "required": true, "schema": { "type": "string" }, "description": "Project id or identifier." } + ], + "responses": { + "200": { + "description": "Versions", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VersionsResponse" } + } + } + } + } + } + }, + "/versions/{id}.json": { + "get": { + "operationId": "getVersion", + "summary": "Get version", + "description": "Returns a version by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Version id." } + ], + "responses": { + "200": { + "description": "Version", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/VersionResponse" } + } + } + } + } + } + }, + "/issues.json": { + "get": { + "operationId": "listIssues", + "summary": "List issues", + "description": "Returns a paginated list of issues. By default, only open issues are returned. Use filters and pagination for large datasets.", + "parameters": [ + { "name": "issue_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by a single issue id or a comma-separated list of ids." }, + { "name": "project_id", "in": "query", "schema": { "type": "string" }, "description": "Filter issues by project id (numeric)." }, + { "name": "subproject_id", "in": "query", "schema": { "type": "string" }, "description": "Filter issues by subproject id. Use with project_id and subproject_id=!* to exclude subprojects." }, + { "name": "tracker_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by tracker id." }, + { "name": "status_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by status id or special values: open, closed, * (open+closed)." }, + { "name": "assigned_to_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by assignee user id. Use 'me' to fetch issues assigned to the current user." }, + { "name": "parent_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by parent issue id." }, + { "name": "include", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated associations to include (attachments, relations)." }, + { "name": "offset", "in": "query", "schema": { "type": "integer" }, "description": "Offset of the first issue to return." }, + { "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "Max number of issues to return (default 25, max 100)." }, + { "name": "sort", "in": "query", "schema": { "type": "string" }, "description": "Sort field and order, e.g. 'updated_on:desc'." } + ], + "responses": { + "200": { + "description": "Issues", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssuesResponse" } + } + } + } + } + }, + "post": { + "operationId": "createIssue", + "summary": "Create issue", + "description": "Creates a new issue. Redmine requires Content-Type: application/json for POST/PUT requests.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueCreateRequest" } + } + } + }, + "responses": { + "201": { + "description": "Issue created", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueCreateResponse" } + } + } + } + } + } + }, + "/issues/{id}.json": { + "get": { + "operationId": "getIssue", + "summary": "Get issue", + "description": "Returns an issue by id. Use include to fetch related data like children, attachments, relations, changesets, journals, watchers, or allowed_statuses.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Issue id." }, + { "name": "include", "in": "query", "schema": { "type": "string" }, "description": "Comma-separated associations to include (children, attachments, relations, changesets, journals, watchers, allowed_statuses)." } + ], + "responses": { + "200": { + "description": "Issue", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueResponse" } + } + } + } + } + }, + "put": { + "operationId": "updateIssue", + "summary": "Update issue", + "description": "Updates an existing issue. Redmine requires Content-Type: application/json for POST/PUT requests.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Issue id." } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueUpdateRequest" } + } + } + }, + "responses": { + "204": { "description": "Issue updated" } + } + }, + "delete": { + "operationId": "deleteIssue", + "summary": "Delete issue", + "description": "Deletes an issue.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Issue id." } + ], + "responses": { + "204": { "description": "Issue deleted" } + } + } + }, + "/issues/{issue_id}/relations.json": { + "get": { + "operationId": "listIssueRelations", + "summary": "List issue relations", + "description": "Returns relations for the given issue id.", + "parameters": [ + { "name": "issue_id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Issue id." } + ], + "responses": { + "200": { + "description": "Issue relations", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueRelationsResponse" } + } + } + } + } + }, + "post": { + "operationId": "createIssueRelation", + "summary": "Create issue relation", + "description": "Creates a relation for the given issue id.", + "parameters": [ + { "name": "issue_id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Issue id." } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueRelationCreateRequest" } + } + } + }, + "responses": { + "201": { + "description": "Relation created", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueRelationResponse" } + } + } + } + } + } + }, + "/relations/{id}.json": { + "get": { + "operationId": "getIssueRelation", + "summary": "Get issue relation", + "description": "Returns a relation by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Relation id." } + ], + "responses": { + "200": { + "description": "Relation", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueRelationResponse" } + } + } + } + } + }, + "delete": { + "operationId": "deleteIssueRelation", + "summary": "Delete issue relation", + "description": "Deletes a relation by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Relation id." } + ], + "responses": { + "204": { "description": "Relation deleted" }, + "422": { "description": "Relation not deleted" } + } + } + }, + "/issue_statuses.json": { + "get": { + "operationId": "listIssueStatuses", + "summary": "List issue statuses", + "description": "Returns issue statuses. Use this to map status ids when filtering or updating issues.", + "responses": { + "200": { + "description": "Issue statuses", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/IssueStatusesResponse" } + } + } + } + } + } + }, + "/trackers.json": { + "get": { + "operationId": "listTrackers", + "summary": "List trackers", + "description": "Returns trackers. Use tracker ids when creating or updating issues.", + "responses": { + "200": { + "description": "Trackers", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TrackersResponse" } + } + } + } + } + } + }, + "/time_entries.json": { + "get": { + "operationId": "listTimeEntries", + "summary": "List time entries", + "description": "Returns time entries. Use filters and pagination to narrow results.", + "parameters": [ + { "name": "offset", "in": "query", "schema": { "type": "integer" }, "description": "Offset of the first entry to return." }, + { "name": "limit", "in": "query", "schema": { "type": "integer" }, "description": "Max number of entries to return (default 25, max 100)." }, + { "name": "user_id", "in": "query", "schema": { "type": "integer" }, "description": "Filter by user id." }, + { "name": "project_id", "in": "query", "schema": { "type": "string" }, "description": "Filter by project id or identifier." }, + { "name": "spent_on", "in": "query", "schema": { "type": "string" }, "description": "Filter by spent date (YYYY-MM-DD)." }, + { "name": "from", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Filter entries from date (YYYY-MM-DD)." }, + { "name": "to", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Filter entries to date (YYYY-MM-DD)." } + ], + "responses": { + "200": { + "description": "Time entries", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TimeEntriesResponse" } + } + } + } + } + }, + "post": { + "operationId": "createTimeEntry", + "summary": "Create time entry", + "description": "Creates a time entry. Provide either issue_id or project_id plus hours. Redmine requires Content-Type: application/json for POST/PUT requests.", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TimeEntryCreateRequest" } + } + } + }, + "responses": { + "201": { + "description": "Time entry created", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TimeEntryCreateResponse" } + } + } + } + } + } + }, + "/time_entries/{id}.json": { + "get": { + "operationId": "getTimeEntry", + "summary": "Get time entry", + "description": "Returns a time entry by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Time entry id." } + ], + "responses": { + "200": { + "description": "Time entry", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TimeEntryResponse" } + } + } + } + } + }, + "put": { + "operationId": "updateTimeEntry", + "summary": "Update time entry", + "description": "Updates a time entry by id. Redmine requires Content-Type: application/json for POST/PUT requests.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Time entry id." } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/TimeEntryUpdateRequest" } + } + } + }, + "responses": { + "204": { "description": "Time entry updated" } + } + }, + "delete": { + "operationId": "deleteTimeEntry", + "summary": "Delete time entry", + "description": "Deletes a time entry by id.", + "parameters": [ + { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" }, "description": "Time entry id." } + ], + "responses": { + "204": { "description": "Time entry deleted" } + } + } + } + } +} diff --git a/src/backend/tero/tools/redmine/tool-schema.json b/src/backend/tero/tools/redmine/tool-schema.json new file mode 100644 index 0000000..c1216bc --- /dev/null +++ b/src/backend/tero/tools/redmine/tool-schema.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "url": { + "type": "string", + "format": "uri", + "description": "URL of the Redmine instance (e.g. https://redmine.example.com)" + }, + "apiKey": { + "type": "string", + "writeOnly": true, + "description": "Redmine API key used as the Basic Auth username" + } + }, + "required": ["url", "apiKey"], + "additionalProperties": false +} diff --git a/src/backend/tero/tools/redmine/tool.py b/src/backend/tero/tools/redmine/tool.py new file mode 100644 index 0000000..c20ea5e --- /dev/null +++ b/src/backend/tero/tools/redmine/tool.py @@ -0,0 +1,104 @@ +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +from http import HTTPMethod +import logging +from typing import Optional, cast + +from httpx import HTTPStatusError +from sqlmodel.ext.asyncio.session import AsyncSession + +from ...agents.domain import AgentToolConfig +from ..auth import ( + ToolAuthCallback, + ToolAuthCallbackError, + ToolAuthRepository, + ToolAuthRequestException, + ToolAuthToken, + ToolAuthTokenType, + ToolAuthTokenCallback, + ToolAuthTokenRequest, +) +from ..core import load_schema +from ..openapi_tool import OpenApiTool + + +logger = logging.getLogger(__name__) +REDMINE_TOOL_ID = "redmine" + + +class RedmineTool(OpenApiTool): + id: str = REDMINE_TOOL_ID + name: str = "Redmine" + description: str = "Manage issues, projects and log time" + config_schema: dict = load_schema(__file__) + _api_url: str = "" + _api_key: Optional[str] = None + + def configure(self, agent, user_id: int, config: dict, db: AsyncSession, thread_id: Optional[int] = None): + super().configure(agent, user_id, config, db, thread_id) + self._api_url = config.get("url", "").rstrip("/") + + async def _setup_tool(self, prev_config: Optional[AgentToolConfig]): + self._api_key = self._get_secret("apiKey") + prev_token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) + if (prev_config and prev_config.config != self.config + or self._api_key and prev_token and self._api_key != prev_token.access_token): + await self.teardown() + await self._check_auth() + await self._save_api_key(self._api_key, self.db) + + async def teardown(self): + await ToolAuthRepository(self.db).delete_token(self.user_id, self.agent.id, self.id) + + async def _check_auth(self): + await self._invoke_rest_api(HTTPMethod.GET, f"{self._api_url}/users/current.json") + + async def _save_api_key(self, api_key: Optional[str], db: AsyncSession): + if not api_key: + return + token = ToolAuthToken( + user_id=self.user_id, + agent_id=self.agent.id, + tool_id=self.id, + access_token=api_key, + token_type=ToolAuthTokenType.BEARER, + expires_in=None, + scope=None, + refresh_token=None, + expires_at=None, + ) + await ToolAuthRepository(db).save_token(token) + + @asynccontextmanager + async def load(self) -> AsyncIterator["RedmineTool"]: + try: + await self._check_auth() + yield self + except HTTPStatusError as e: + if e.response.status_code == 401: + raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) + raise + + async def auth(self, auth_callback: ToolAuthCallback): + self._api_key = cast(ToolAuthTokenCallback, auth_callback).auth_token + try: + await self._check_auth() + except HTTPStatusError as e: + logger.warning(f"Invalid API key for agent {self.agent.id} tool {self.id} user {self.user_id}", exc_info=True) + raise ToolAuthCallbackError("Invalid API key") + await self._save_api_key(self._api_key, self.db) + + async def clone(self, agent_id: int, cloned_agent_id: int, tool_id: str, user_id: int, db: AsyncSession) -> None: + pass + + async def _add_auth_headers(self, headers: dict) -> dict: + headers["X-Redmine-API-Key"] = await self._get_api_key() + return headers + + async def _get_api_key(self) -> str: + if self._api_key: + return self._api_key + token = await ToolAuthRepository(self.db).find_token(self.user_id, self.agent.id, self.id) + if not token: + raise ToolAuthRequestException(ToolAuthTokenRequest(tool_id=self.id, agent_id=self.agent.id)) + return token.access_token diff --git a/src/backend/tero/tools/repos.py b/src/backend/tero/tools/repos.py index 56f4913..09bf220 100644 --- a/src/backend/tero/tools/repos.py +++ b/src/backend/tero/tools/repos.py @@ -4,6 +4,10 @@ from .core import AgentTool from .docs import DocsTool from .jira import JiraTool +from .redmine import RedmineTool +from .github import GitHubTool +from .youtrack import YouTrackTool +from .practitest import PractiTestTool from .mcp import McpTool from .web import WebTool from .browser import BrowserTool @@ -11,13 +15,13 @@ class ToolRepository: def __init__(self): - self._tools: List[AgentTool] = [DocsTool(), McpTool(), JiraTool(), BrowserTool()] - + self._tools: List[AgentTool] = [DocsTool(), McpTool(), JiraTool(), BrowserTool(), GitHubTool(), YouTrackTool(), PractiTestTool(), RedmineTool()] + if env.web_tool_tavily_api_key or (env.web_tool_google_api_key and env.web_tool_google_custom_search_engine_id): self._tools.append(WebTool()) def find_agent_tools(self) -> List[AgentTool]: - return self._tools + return sorted(self._tools, key=lambda t: t.name.casefold()) def find_by_id(self, tool_id: str) -> Optional[AgentTool]: return next((t for t in self._tools if t.id == tool_id or t.id.endswith("-*") and tool_id.startswith(t.id[:-1])), None) diff --git a/src/backend/tero/tools/web/tool.py b/src/backend/tero/tools/web/tool.py index cf61aad..5f2127c 100644 --- a/src/backend/tero/tools/web/tool.py +++ b/src/backend/tero/tools/web/tool.py @@ -165,7 +165,7 @@ async def _arun(self, *args: Any, **kwargs: Any) -> ToolMessage: class WebTool(AgentTool): id: str = WEB_TOOL_ID name: str = "Web Tools" - description: str = "Provides web search and web extraction capabilities." + description: str = "Search the web and extract content from public URLs" config_schema: dict = load_schema(__file__) async def _setup_tool(self, prev_config: Optional[AgentToolConfig]): diff --git a/src/backend/tero/tools/youtrack/__init__.py b/src/backend/tero/tools/youtrack/__init__.py new file mode 100644 index 0000000..6406875 --- /dev/null +++ b/src/backend/tero/tools/youtrack/__init__.py @@ -0,0 +1,4 @@ +from .tool import YouTrackTool, YOUTRACK_TOOL_ID + +__all__ = ["YouTrackTool", "YOUTRACK_TOOL_ID"] + diff --git a/src/backend/tero/tools/youtrack/tool-schema.json b/src/backend/tero/tools/youtrack/tool-schema.json new file mode 100644 index 0000000..1003b21 --- /dev/null +++ b/src/backend/tero/tools/youtrack/tool-schema.json @@ -0,0 +1,19 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "serverUrl": { + "type": "string", + "format": "uri", + "description": "YouTrack MCP server URL (for example: https://your-instance.youtrack.cloud/mcp)." + }, + "token": { + "type": "string", + "writeOnly": true, + "description": "YouTrack permanent token with YouTrack scope. Used as Bearer token to authenticate with the YouTrack MCP server." + } + }, + "required": ["serverUrl", "token"], + "additionalProperties": false +} + diff --git a/src/backend/tero/tools/youtrack/tool.py b/src/backend/tero/tools/youtrack/tool.py new file mode 100644 index 0000000..5d9aa25 --- /dev/null +++ b/src/backend/tero/tools/youtrack/tool.py @@ -0,0 +1,23 @@ +from typing import cast + +from ..core import load_schema +from ..mcp_tool import AuthType, BaseMcpTool + + +YOUTRACK_TOOL_ID = "youtrack" + + +class YouTrackTool(BaseMcpTool): + id: str = YOUTRACK_TOOL_ID + name: str = "YouTrack" + description: str = "Report issues, manage agile boards and log work time" + config_schema: dict = load_schema(__file__) + + def _get_auth_type(self) -> AuthType: + return AuthType.BEARER_TOKEN + + def _get_mcp_server_url(self) -> str: + server_url = cast(str, self.config.get("serverUrl") or "") + if not server_url: + raise ValueError("YouTrack MCP serverUrl is not configured") + return server_url diff --git a/src/backend/tero/users/domain.py b/src/backend/tero/users/domain.py index 11edfea..8e95318 100644 --- a/src/backend/tero/users/domain.py +++ b/src/backend/tero/users/domain.py @@ -4,7 +4,7 @@ from sqlmodel import SQLModel, Field, Relationship -from ..teams.domain import PublicTeamRole, Team, TeamRole, GLOBAL_TEAM_ID, TeamRoleStatus +from ..teams.domain import PublicTeamRole, Team, TeamRole, GLOBAL_TEAM_ID, TeamRoleStatus, Role class BaseUser(SQLModel, abc.ABC): @@ -24,7 +24,7 @@ def is_active(self) -> bool: if self.deleted_at is None: return True deleted_at = self.deleted_at - # Records inserted directly into the database may lack timezone info, + # Records inserted directly into the database may lack timezone info, # for that reason we set it to the current timezone if it's not set if deleted_at.tzinfo is None: deleted_at = deleted_at.replace(tzinfo=timezone.utc) @@ -33,6 +33,14 @@ def is_active(self) -> bool: def is_member_of(self, team_id: int) -> bool: return team_id == GLOBAL_TEAM_ID or any(cast(Team, tr.team).id == team_id and tr.status == TeamRoleStatus.ACCEPTED for tr in self.team_roles) + def is_global_owner(self) -> bool: + return any( + tr.role == Role.TEAM_OWNER + and tr.team_id == GLOBAL_TEAM_ID + and tr.status == TeamRoleStatus.ACCEPTED + for tr in self.team_roles + ) + class UserListItem(BaseUser): diff --git a/src/backend/tero/users/repos.py b/src/backend/tero/users/repos.py index ab4965b..bc62eb6 100644 --- a/src/backend/tero/users/repos.py +++ b/src/backend/tero/users/repos.py @@ -64,7 +64,7 @@ async def find_all_users(self) -> list[User]: stmt = select(User).where(or_(col(User.deleted_at).is_(None), col(User.deleted_at) > datetime.now(timezone.utc))) result = await self._db.exec(stmt) return list(result.all()) - + async def update_user(self, user: User) -> User: self._db.add(user) await self._db.commit() diff --git a/src/backend/tests/assets/init_db.sql b/src/backend/tests/assets/init_db.sql index 1b8eb9c..f14fad2 100644 --- a/src/backend/tests/assets/init_db.sql +++ b/src/backend/tests/assets/init_db.sql @@ -6,14 +6,16 @@ insert into "user" (username, name, monthly_usd_limit, monthly_hours, created_at ('test5', 'John Doe 5', 10, 160, '2025-01-10 12:00', Null); insert into llm_model (id, name, model_type, description, model_vendor, token_limit, output_token_limit, prompt_1k_token_usd, completion_1k_token_usd) values -('gpt-4o', 'GPT-4o', 'CHAT', 'This is the best model for complex tasks', 'OPENAI', 128000, 4096, 0.005, 0.015), -('gpt-4o-mini', 'GPT-4o Mini', 'CHAT', 'This is the best model for simple tasks', 'OPENAI', 64000, 2048, 0.0025, 0.0075), -('o4-mini', 'O4 Mini', 'REASONING', 'This is a reasoning model that is good for coding, math and some complex tasks.', 'OPENAI', 200000, 100000, 0.0011, 0.0044), -('claude-sonnet-4', 'Claude Sonnet 4', 'CHAT', 'This is a similar model to GPT-4o but with better reasoning.', 'ANTHROPIC', 200000, 64000, 0.003, 0.015), -('gemini-2.5-pro', 'Gemini 2.5 Pro', 'CHAT', 'This is an advanced reasoning model, outperforming GPT-4o with a larger context while being more affordable.', 'GOOGLE', 1048576, 65536, 0.00125, 0.01), -('gemini-2.5-flash', 'Gemini 2.5 Flash', 'CHAT', 'This is a fast and efficient model, comparable to GPT-4.1 Nano, optimized for speed while maintaining high quality responses.', 'GOOGLE', 1048576, 65536, 0.0003, 0.0025), -('gpt-5-mini', 'GPT-5 Mini', 'REASONING', 'This is a new reasoning model that will replace GPT-4o Mini in the short term. It has a good balance between cost and intelligence.', 'OPENAI', 400000, 128000, 0.00025, 0.002), -('gpt-5-nano', 'GPT-5 Nano', 'REASONING', 'This is a new reasoning model that will replace GPT-4.1 Nano in the short term.', 'OPENAI', 4000000, 128000, 0.00005, 0.0004); +('claude-sonnet-4', 'Claude Sonnet 4', 'CHAT', 'This is a similar model to GPT-5 but with different strengths.', 'ANTHROPIC', 200000, 64000, 0.003, 0.015), +('claude-sonnet-4-6', 'Claude Sonnet 4.6', 'CHAT', 'This is an improved version of Claude Sonnet 4 with better reasoning. Good for complex analysis, coding, and creative writing. Similar pricing to Claude Sonnet 4.', 'ANTHROPIC', 200000, 64000, 0.003, 0.015), +('claude-opus-4-6', 'Claude Opus 4.6', 'CHAT', 'This is a more capable model than Claude Sonnet 4 for highly complex tasks. Best for demanding reasoning and analysis. Higher cost than Claude Sonnet models.', 'ANTHROPIC', 200000, 64000, 0.005, 0.025), +('gemini-2.5-pro', 'Gemini 2.5 Pro', 'CHAT', 'This is an advanced reasoning model, outperforming GPT-5 Mini with a larger context while being more affordable.', 'GOOGLE', 1048576, 65536, 0.00125, 0.01), +('gemini-2.5-flash', 'Gemini 2.5 Flash', 'CHAT', 'This is a fast and efficient model, comparable to GPT-5 Nano, optimized for speed while maintaining high quality responses.', 'GOOGLE', 1048576, 65536, 0.0003, 0.0025), +('gpt-5', 'GPT-5', 'REASONING', 'This is the best reasoning model for coding and complex agentic tasks from OpenAI. Aligned with GPT-5 Mini for most use cases.', 'OPENAI', 400000, 128000, 0.00125, 0.01), +('gpt-5-mini', 'GPT-5 Mini', 'REASONING', 'This is a new reasoning model with a good balance between cost and intelligence. Suited for most agent tasks.', 'OPENAI', 400000, 128000, 0.00025, 0.002), +('gpt-5-nano', 'GPT-5 Nano', 'REASONING', 'This is a new reasoning model for simpler tasks. Lower cost than GPT-5 Mini.', 'OPENAI', 4000000, 128000, 0.00005, 0.0004), +('gpt-5.1-codex-max', 'GPT-5.1 Codex Max', 'REASONING', 'This is the most capable Codex model for long-running agentic coding. Best for complex codebases and multi-step tasks. Similar pricing to GPT-5.1 Codex.', 'OPENAI', 400000, 128000, 0.00125, 0.01), +('gpt-5.4', 'GPT-5.4', 'REASONING', 'This is the most capable reasoning model from OpenAI for professional work. Best for demanding analysis and complex agentic tasks. Higher cost than GPT-5.', 'OPENAI', 1050000, 128000, 0.0025, 0.015); insert into team (name) values @@ -31,13 +33,13 @@ insert into team_role (team_id, user_id, role, status) values (4, 2, 'TEAM_MEMBER', 'PENDING'), (4, 5, 'TEAM_MEMBER', 'REJECTED'); -insert into agent (name, description, user_id, last_update, team_id, model_id, system_prompt, temperature, reasoning_effort, recursion_limit) values -('Agent 1', 'This is the first agent', 1, '2025-02-21 12:00', Null, 'gpt-4o-mini', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20), -('Agent 2', 'This is the second agent', 1, '2025-02-21 12:01', 1, 'o4-mini', 'You are a helpful AI agent.', 'CREATIVE', 'LOW', 20), -('Agent 3', 'This is the third agent', 2, '2025-02-21 12:02', 4, 'gpt-4o', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20), -('Agent 4', 'This is the fourth agent', 2, '2025-02-21 12:03', Null, 'gpt-4o', 'You are a helpful AI agent.', 'CREATIVE', 'LOW', 20), -('Agent 5', 'This is the fifth agent', 2, '2025-02-21 12:04', 2, 'gpt-4o', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20), -('GPT-5 Nano', 'This is the default agent', Null, '2025-02-21 12:00', 1, 'gpt-5-nano', 'You are a helpful AI agent.', 'NEUTRAL', 'LOW', 20); +insert into agent (name, description, user_id, last_update, team_id, model_id, system_prompt, temperature, reasoning_effort, recursion_limit, is_protected) values +('Agent 1', 'This is the first agent', 1, '2025-02-21 12:00', Null, 'gpt-5-mini', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20, false), +('Agent 2', 'This is the second agent', 1, '2025-02-21 12:01', 1, 'gpt-5-mini', 'You are a helpful AI agent.', 'CREATIVE', 'LOW', 20, false), +('Agent 3', 'This is the third agent', 2, '2025-02-21 12:02', 4, 'gpt-5', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20, true), +('Agent 4', 'This is the fourth agent', 2, '2025-02-21 12:03', Null, 'gpt-5', 'You are a helpful AI agent.', 'CREATIVE', 'LOW', 20, false), +('Agent 5', 'This is the fifth agent', 2, '2025-02-21 12:04', 2, 'gpt-5', 'You are a helpful AI agent.', 'PRECISE', 'LOW', 20, false), +('GPT-5 Nano', 'This is the default agent', Null, '2025-02-21 12:00', 1, 'gpt-5-nano', 'You are a helpful AI agent.', 'NEUTRAL', 'LOW', 20, false); insert into user_agent (user_id, agent_id, creation) values (1, 1, '2025-02-21 12:00'), @@ -68,7 +70,8 @@ insert into thread (name, user_id, agent_id, creation, deleted, is_test_case) va ('Test Case Execution #1', 1, 1, '2025-02-21 12:15', False, True), ('Test Case Execution #2', 1, 1, '2025-02-21 12:16', False, True), ('Test Case Execution #3', 2, 2, '2025-02-21 12:17', False, True), -('Test Case #4', 1, 1, '2025-02-21 12:13', False, True); +('Test Case #4', 1, 1, '2025-02-21 12:13', False, True), +('Test Case For Evaluation #5', 1, 1, '2025-02-21 12:14', False, True); insert into thread_message (thread_id, origin, text, timestamp, minutes_saved, stopped, parent_id) values (1, 'USER', 'This is a message', '2025-02-21 12:00', 5, False, Null), @@ -96,13 +99,16 @@ insert into thread_message (thread_id, origin, text, timestamp, minutes_saved, s (13, 'USER', 'What is 2 + 2? Only provide the number', '2025-02-21 12:13', Null, False, Null), (13, 'AGENT', '4', '2025-02-21 12:14', Null, False, Null), (13, 'USER', 'What is 3 + 3? Only provide the number', '2025-02-21 12:14', Null, False, Null), -(13, 'AGENT', '6', '2025-02-21 12:15', Null, False, Null); +(13, 'AGENT', '6', '2025-02-21 12:15', Null, False, Null), +(14, 'USER', 'Which is the first natural number? Only provide the number', '2025-02-21 12:15', Null, False, Null), +(14, 'AGENT', 'one', '2025-02-21 12:16', Null, False, Null); insert into test_case (thread_id, agent_id, last_update) values (7, 1, '2025-02-21 12:10'), (8, 1, '2025-02-21 12:11'), (9, 2, '2025-02-21 12:12'), -(13, 1, '2025-02-21 12:15'); +(13, 1, '2025-02-21 12:15'), +(14, 1, '2025-02-21 12:16'); insert into test_suite_run (agent_id, status, executed_at, completed_at, total_tests, passed_tests, failed_tests, error_tests, skipped_tests) values (1, 'SUCCESS', '2025-02-21 12:15', '2025-02-21 12:17', 2, 2, 0, 0, 0), @@ -114,23 +120,23 @@ insert into test_case_result (thread_id, test_case_id, test_suite_run_id, status (12, 9, 2, 'ERROR', '2025-02-21 12:17', 'Test Case #3'); insert into usage (message_id, user_id, agent_id, model_id, timestamp, quantity, usd_cost, type) values -(2, 1, 1, 'gpt-4o-mini', '2025-02-21 12:00', 100, 0.5, 'PROMPT_TOKENS'), -(2, 1, 1, 'gpt-4o-mini', '2025-02-21 12:00', 200, 3.0, 'COMPLETION_TOKENS'), -(4, 1, 2, 'gpt-4o', '2025-02-21 12:01', 50, 0.25, 'PROMPT_TOKENS'), -(4, 1, 2, 'gpt-4o', '2025-02-21 12:01', 100, 1.5, 'COMPLETION_TOKENS'), -(4, 1, 2, 'gpt-4o', '2025-02-21 12:01', 50, 0.5, 'PDF_PARSING'), -(6, 1, 2, 'gpt-4o', '2025-01-20 12:02', 50, 0.25, 'PROMPT_TOKENS'), -(6, 1, 2, 'gpt-4o', '2025-01-20 12:02', 100, 1.5, 'COMPLETION_TOKENS'), -(8, 2, 3, 'gpt-4o', '2025-02-21 12:08', 50, 0.25, 'PROMPT_TOKENS'), -(8, 2, 3, 'gpt-4o', '2025-02-21 12:08', 100, 1.5, 'COMPLETION_TOKENS'), -(10, 2, 5, 'gpt-4o', '2025-02-21 12:08', 55, 0.25, 'PROMPT_TOKENS'), -(10, 2, 5, 'gpt-4o', '2025-02-21 12:08', 105, 1.5, 'COMPLETION_TOKENS'), -(14, 1, 1, 'gpt-4o-mini', '2025-02-21 12:15', 50, 0.25, 'PROMPT_TOKENS'), -(14, 1, 1, 'gpt-4o-mini', '2025-02-21 12:15', 100, 1.5, 'COMPLETION_TOKENS'), -(16, 1, 1, 'gpt-4o-mini', '2025-02-21 12:16', 60, 0.3, 'PROMPT_TOKENS'), -(16, 1, 1, 'gpt-4o-mini', '2025-02-21 12:16', 120, 1.8, 'COMPLETION_TOKENS'), -(18, 2, 2, 'o4-mini', '2025-02-21 12:17', 40, 0.2, 'PROMPT_TOKENS'), -(18, 2, 2, 'o4-mini', '2025-02-21 12:17', 80, 1.2, 'COMPLETION_TOKENS'); +(2, 1, 1, 'gpt-5-mini', '2025-02-21 12:00', 100, 0.5, 'PROMPT_TOKENS'), +(2, 1, 1, 'gpt-5-mini', '2025-02-21 12:00', 200, 3.0, 'COMPLETION_TOKENS'), +(4, 1, 2, 'gpt-5', '2025-02-21 12:01', 50, 0.25, 'PROMPT_TOKENS'), +(4, 1, 2, 'gpt-5', '2025-02-21 12:01', 100, 1.5, 'COMPLETION_TOKENS'), +(4, 1, 2, 'gpt-5', '2025-02-21 12:01', 50, 0.5, 'PDF_PARSING'), +(6, 1, 2, 'gpt-5', '2025-01-20 12:02', 50, 0.25, 'PROMPT_TOKENS'), +(6, 1, 2, 'gpt-5', '2025-01-20 12:02', 100, 1.5, 'COMPLETION_TOKENS'), +(8, 2, 3, 'gpt-5', '2025-02-21 12:08', 50, 0.25, 'PROMPT_TOKENS'), +(8, 2, 3, 'gpt-5', '2025-02-21 12:08', 100, 1.5, 'COMPLETION_TOKENS'), +(10, 2, 5, 'gpt-5', '2025-02-21 12:08', 55, 0.25, 'PROMPT_TOKENS'), +(10, 2, 5, 'gpt-5', '2025-02-21 12:08', 105, 1.5, 'COMPLETION_TOKENS'), +(14, 1, 1, 'gpt-5-mini', '2025-02-21 12:15', 50, 0.25, 'PROMPT_TOKENS'), +(14, 1, 1, 'gpt-5-mini', '2025-02-21 12:15', 100, 1.5, 'COMPLETION_TOKENS'), +(16, 1, 1, 'gpt-5-mini', '2025-02-21 12:16', 60, 0.3, 'PROMPT_TOKENS'), +(16, 1, 1, 'gpt-5-mini', '2025-02-21 12:16', 120, 1.8, 'COMPLETION_TOKENS'), +(18, 2, 2, 'gpt-5-mini', '2025-02-21 12:17', 40, 0.2, 'PROMPT_TOKENS'), +(18, 2, 2, 'gpt-5-mini', '2025-02-21 12:17', 80, 1.2, 'COMPLETION_TOKENS'); insert into agent_prompt (name, content, last_update, shared, agent_id, user_id, starter) values ('Test prompt private 1', 'Test prompt content', '2025-02-22 12:00', false, 1, 1, false), diff --git a/src/backend/tests/common.py b/src/backend/tests/common.py index 657072f..89baf26 100644 --- a/src/backend/tests/common.py +++ b/src/backend/tests/common.py @@ -4,6 +4,7 @@ import logging import os from typing import List, Sequence, AsyncContextManager, Optional +from unittest.mock import AsyncMock, patch import aiofiles from fastapi import status # noqa: F401 # used by test files importing common @@ -44,11 +45,46 @@ def parse_date(value: str) -> datetime: TEST_ICON = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjWCMg9B8ABAgBzkPo1OYAAAAASUVORK5CYII=" -def assert_response(resp: Response, expected: Sequence[BaseModel] | BaseModel): +@pytest.fixture +def stub_docs_tool_generate_description(): + with patch("tero.tools.docs.tool.DocsTool._generate_description", new=AsyncMock(return_value="stub description")): + yield + + +def assert_response(resp: Response, expected: Sequence[BaseModel] | BaseModel, minutes_saved_tolerance: int = 2): resp.raise_for_status() - assert resp.json() == ( - [json.loads(e.model_dump_json(by_alias=True)) for e in expected] if isinstance(expected, Sequence) else json.loads( - expected.model_dump_json(by_alias=True))) + actual = resp.json() + expected_data = [json.loads(e.model_dump_json(by_alias=True)) for e in expected] if isinstance(expected, Sequence) else json.loads(expected.model_dump_json(by_alias=True)) + try: + _assert_response_with_minutes_saved_tolerance(actual, expected_data, minutes_saved_tolerance) + except AssertionError: + # Fall back to strict equality to let pytest render a full diff with better context. + assert actual == expected_data + + +def _assert_response_with_minutes_saved_tolerance(actual, expected, tolerance: int): + if isinstance(expected, dict): + assert isinstance(actual, dict) + assert set(actual.keys()) == set(expected.keys()) + for key, expected_value in expected.items(): + actual_value = actual[key] + if key == "minutesSaved": + if actual_value is None or expected_value is None: + assert actual_value == expected_value + else: + assert abs(actual_value - expected_value) <= tolerance + else: + _assert_response_with_minutes_saved_tolerance(actual_value, expected_value, tolerance) + return + + if isinstance(expected, list): + assert isinstance(actual, list) + assert len(actual) == len(expected) + for actual_item, expected_item in zip(actual, expected): + _assert_response_with_minutes_saved_tolerance(actual_item, expected_item, tolerance) + return + + assert actual == expected async def configure_agent_tool(agent_id: int, tool_id: str, config: dict, client: AsyncClient) -> Response: diff --git a/src/backend/tests/conftest.py b/src/backend/tests/conftest.py index 5f51537..e9f2ed6 100644 --- a/src/backend/tests/conftest.py +++ b/src/backend/tests/conftest.py @@ -4,9 +4,11 @@ pytest.register_assert_rewrite('tests.common') import asyncio +import contextlib import logging import os from typing import Generator, AsyncGenerator, List +from unittest.mock import AsyncMock, patch from fastapi import Depends import freezegun @@ -52,6 +54,7 @@ def patched_match_type(self, obj): logger = logging.getLogger(__name__) + class SafeEventLoopPolicy(asyncio.DefaultEventLoopPolicy): def new_event_loop(self): loop = super().new_event_loop() @@ -144,16 +147,16 @@ def agents_fixture(users: dict[int, UserListItem], teams: List[Team]) -> List[Ag return [ AgentListItem(id=AGENT_ID, name="Agent 1", description="This is the first agent", last_update=parse_date("2025-02-21T12:00"), team=None, - user=users[0], active_users=1, can_edit=True), + user=users[0], active_users=1, can_edit=True, is_protected=False), AgentListItem(id=2, name="Agent 2", description="This is the second agent", last_update=parse_date("2025-02-21T12:01"), team=teams[0], - user=users[0], active_users=2, can_edit=True), + user=users[0], active_users=2, can_edit=True, is_protected=False), AgentListItem(id=NON_EDITABLE_AGENT_ID, name="Agent 3", description="This is the third agent", last_update=parse_date("2025-02-21T12:02"), team=teams[2], - user=users[1], active_users=1, can_edit=False), + user=users[1], active_users=1, can_edit=False, is_protected=True), AgentListItem(id=5, name="Agent 5", description="This is the fifth agent", last_update=parse_date("2025-02-21T12:04"), team=teams[1], - user=users[1], active_users=1, can_edit=True)] + user=users[1], active_users=1, can_edit=True, is_protected=False)] @pytest.fixture @@ -212,3 +215,13 @@ async def fixture_last_suite_run_id(session: AsyncSession) -> int: @pytest.fixture(name="last_result_id") async def fixture_last_result_id(session: AsyncSession) -> int: return await find_last_id(col(TestCaseResult.id), session) + + +@pytest.fixture(autouse=True) +def _autouse_thread_api_stubs(request): + with contextlib.ExitStack() as stack: + if not request.node.get_closest_marker("no_stub_estimate_minutes_saved"): + stack.enter_context(patch("tero.threads.api.estimate_minutes_saved", new=AsyncMock(return_value=0))) + if not request.node.get_closest_marker("no_stub_build_thread_name"): + stack.enter_context(patch("tero.threads.api.build_thread_name", new=AsyncMock(return_value="stub chat title"))) + yield diff --git a/src/backend/tests/test_agent_distribution.py b/src/backend/tests/test_agent_distribution.py index 5efe53f..eac48cc 100644 --- a/src/backend/tests/test_agent_distribution.py +++ b/src/backend/tests/test_agent_distribution.py @@ -26,7 +26,8 @@ async def test_import_exported_minimal_agent(users: List[UserListItem], client: source_agent_id = await _create_agent(client) zip_file_content = await _export_agent(source_agent_id, client) target_agent_id = await _create_agent(client) - await _import_agent(target_agent_id, zip_file_content, client) + result = await _import_agent(target_agent_id, zip_file_content, client) + assert result == {"unavailableTools": [], "toolsRequiringAuthentication": [], "unavailableModel": None, "defaultModel": None} resp = await _find_agent(target_agent_id, client) assert_response(resp, PublicAgent( id=target_agent_id, name=f"Agent #{source_agent_id}", description="", last_update=CURRENT_TIME, user_id=USER_ID, @@ -50,9 +51,10 @@ async def _try_export_agent(agent_id: int, client: AsyncClient) -> Response: return await client.get(f"{AGENT_PATH.format(agent_id=agent_id)}/dist") -async def _import_agent(agent_id: int, file: bytes, client: AsyncClient): +async def _import_agent(agent_id: int, file: bytes, client: AsyncClient) -> dict: resp = await _try_import_agent(agent_id, file, client) resp.raise_for_status() + return resp.json() async def _try_import_agent(agent_id: int, file: bytes, client: AsyncClient) -> Response: @@ -74,10 +76,11 @@ async def fixture_last_file_id(session: AsyncSession) -> int: @freeze_time(CURRENT_TIME) +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_import_exported_agent_with_all_tools_and_configs( users: List[UserListItem], last_prompt_id: int, last_file_id: int, last_thread_id: int, last_message_id: int, client: AsyncClient): agent_update = AgentUpdate(name="Test Agent 1", description="Test description", system_prompt="Test system prompt", - model_id="o4-mini", icon=TEST_ICON, reasoning_effort= ReasoningEffort.MEDIUM) + model_id="gpt-5-mini", icon=TEST_ICON, reasoning_effort= ReasoningEffort.MEDIUM) prompts = [ AgentPromptCreate(name="Starter", content="Starter Text", shared=True, starter=True), AgentPromptCreate(name="Private", content="Private Text", shared=False), @@ -94,7 +97,8 @@ async def test_import_exported_agent_with_all_tools_and_configs( agent_update, prompts, advanced_file_processing, file_name, file_content, test_messages, client) zip_file_content = await _export_agent(source_agent_id, client) target_agent_id = await _create_agent(client) - await _import_agent(target_agent_id, zip_file_content, client) + result = await _import_agent(target_agent_id, zip_file_content, client) + assert result == {"unavailableTools": [], "toolsRequiringAuthentication": [], "unavailableModel": None, "defaultModel": None} await _assert_imported_agent(target_agent_id, agent_update, prompts, advanced_file_processing, file_name, file_content, test_messages, last_prompt_id + len(prompts), last_file_id + 1, last_thread_id + 1, last_message_id + len(test_messages), test_case_name, users, client) @@ -221,6 +225,12 @@ async def test_export_non_visible_agent(client: AsyncClient): assert resp.status_code == status.HTTP_404_NOT_FOUND +async def test_export_protected_agent_as_non_editor(client: AsyncClient): + resp = await _try_export_agent(NON_EDITABLE_AGENT_ID, client) + assert resp.status_code == status.HTTP_403_FORBIDDEN + assert resp.json()["detail"] == "Editors access required to export this protected agent" + + async def test_import_non_editable_agent(client: AsyncClient): resp = await _try_import_agent(NON_EDITABLE_AGENT_ID, await _zip_markdown("minimal_agent"), client) assert resp.status_code == status.HTTP_404_NOT_FOUND @@ -242,18 +252,35 @@ def _zip_file(filename: str, content: bytes | str) -> bytes: async def test_import_with_invalid_zip_file(client: AsyncClient): resp = await _try_import_agent(AGENT_ID, b"invalid", client) assert resp.status_code == status.HTTP_400_BAD_REQUEST + assert resp.json()["detail"] == "invalidFileFormat" async def test_import_with_missing_markdown(client: AsyncClient): resp = await _try_import_agent(AGENT_ID, _zip_file("sample.txt", "Hello"), client) assert resp.status_code == status.HTTP_400_BAD_REQUEST + assert resp.json()["detail"] == "unsupportedFileStructure" async def test_import_with_invalid_markdown(client: AsyncClient): resp = await _try_import_agent(AGENT_ID, await _zip_markdown("invalid_agent"), client) assert resp.status_code == status.HTTP_400_BAD_REQUEST + assert resp.json()["detail"] == "missingRequiredConfiguration" -async def test_import_with_invalid_tool_id(client: AsyncClient): - resp = await _try_import_agent(AGENT_ID, await _zip_markdown("invalid_tool_agent"), client) - assert resp.status_code == status.HTTP_400_BAD_REQUEST +async def test_import_with_unavailable_tool(client: AsyncClient): + agent_id = await _create_agent(client) + result = await _import_agent(agent_id, await _zip_markdown("invalid_tool_agent"), client) + assert "test" in result["unavailableTools"] + assert result["toolsRequiringAuthentication"] == [] + + +@freeze_time(CURRENT_TIME) +async def test_import_with_unavailable_tool_still_imports_agent_data(client: AsyncClient): + agent_id = await _create_agent(client) + await _import_agent(agent_id, await _zip_markdown("invalid_tool_agent"), client) + resp = await _find_agent(agent_id, client) + resp.raise_for_status() + agent = resp.json() + assert agent["name"] == "Test Agent" + assert agent["description"] == "A Test Agent" + assert agent["systemPrompt"] == "Test instructions" diff --git a/src/backend/tests/test_agents.py b/src/backend/tests/test_agents.py index bd01a97..3ab18e5 100644 --- a/src/backend/tests/test_agents.py +++ b/src/backend/tests/test_agents.py @@ -10,7 +10,7 @@ from tero.agents.prompts.api import AGENT_PROMPTS_PATH from tero.agents.prompts.domain import AgentPromptPublic, AgentPrompt from tero.files.domain import FileMetadata, FileStatus, FileProcessor -from tero.teams.domain import Team +from tero.teams.domain import Team, Role from tero.tools.docs import DOCS_TOOL_ID from tero.users.domain import UserListItem @@ -137,7 +137,7 @@ async def test_create_agent(agents: List[AgentListItem], users: dict[int, UserLi @freeze_time(CURRENT_TIME) async def test_update_agent(client: AsyncClient, users: List[UserListItem], teams: List[Team]): - update = AgentUpdate(name="Updated Agent", description="Updated description", model_id="gpt-4o", icon=TEST_ICON, + update = AgentUpdate(name="Updated Agent", description="Updated description", model_id="gpt-5", icon=TEST_ICON, temperature=LlmTemperature.PRECISE, reasoning_effort=ReasoningEffort.MEDIUM, team_id=GLOBAL_TEAM_ID, system_prompt="Updated prompt") resp = await _update_agent(AGENT_ID, update, client) resp.raise_for_status() @@ -162,6 +162,19 @@ async def test_update_non_editable_agent(client: AsyncClient): assert resp.status_code == status.HTTP_404_NOT_FOUND +async def test_update_agent_protected_as_global_owner(client: AsyncClient): + resp = await _update_agent(AGENT_ID, AgentUpdate(is_protected=True), client) + resp.raise_for_status() + assert resp.json()["isProtected"] is True + + +async def test_update_agent_protected_as_non_global_owner(client: AsyncClient, override_user_role): + override_user_role(Role.TEAM_EDITOR) + resp = await _update_agent(AGENT_ID, AgentUpdate(is_protected=True), client) + assert resp.status_code == status.HTTP_403_FORBIDDEN + assert resp.json()["detail"] == "Global owner access required to manage protected configuration" + + async def test_find_non_visible_agent(client: AsyncClient): resp = await _find_agent(NON_VISIBLE_AGENT_ID, client) assert resp.status_code == status.HTTP_404_NOT_FOUND @@ -201,6 +214,12 @@ async def test_get_non_visible_agent_tools(client: AsyncClient): assert resp.status_code == status.HTTP_404_NOT_FOUND +async def test_get_protected_agent_tools_as_non_editor(client: AsyncClient): + resp = await _find_agent_tools(NON_EDITABLE_AGENT_ID, client) + assert resp.status_code == status.HTTP_403_FORBIDDEN + assert resp.json()["detail"] == "Editors access required to view this agent's tool configs" + + async def test_remove_agent_tool_config(client: AsyncClient): await _configure_docs_tool(client) resp = await _remove_agent_tool_config(AGENT_ID, DOCS_TOOL_ID, client) @@ -224,6 +243,7 @@ async def test_remove_unconfigured_tool(client: AsyncClient): @freeze_time(CURRENT_TIME) +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_upload_agent_tool_file(client: AsyncClient): await _configure_docs_tool(client) filename = "test.txt" @@ -287,6 +307,7 @@ async def test_find_non_existent_file_content(client: AsyncClient): @freeze_time(CURRENT_TIME) +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_update_agent_tool_file(client: AsyncClient): await _configure_docs_tool(client) filename = "test.txt" @@ -324,6 +345,7 @@ async def test_update_non_existent_file(client: AsyncClient): assert resp.status_code == status.HTTP_404_NOT_FOUND +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_delete_agent_tool_file(client: AsyncClient): await _configure_docs_tool(client) file_id = await upload_agent_tool_config_file(AGENT_ID, DOCS_TOOL_ID, client) @@ -382,12 +404,14 @@ async def _reprocess_agent_tool_file(client: AsyncClient, file_processor: FilePr @freeze_time(CURRENT_TIME) +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_reprocess_agent_tool_file_basic(client: AsyncClient): await _reprocess_agent_tool_file(client, FileProcessor.BASIC) @pytest.mark.skipif(not env.azure_doc_intelligence_key, reason="Azure Doc Intelligence not configured") @freeze_time(CURRENT_TIME) +@pytest.mark.usefixtures("stub_docs_tool_generate_description") async def test_reprocess_agent_tool_file_enhanced(client: AsyncClient): await _reprocess_agent_tool_file(client, FileProcessor.ENHANCED) @@ -433,7 +457,7 @@ async def test_clone_agent(users: dict[int, UserListItem], last_agent_id: int,cl icon=None, user_id=USER_ID, can_edit=True, - model_id="o4-mini", + model_id="gpt-5-mini", system_prompt="You are a helpful AI agent.", temperature=LlmTemperature.CREATIVE, reasoning_effort=ReasoningEffort.LOW, @@ -448,6 +472,11 @@ async def _clone_agent(agent_id: int, client: AsyncClient) -> int: return resp.json()["id"] +async def test_clone_protected_agent_as_non_editor(client: AsyncClient): + resp = await client.post(f"{AGENT_PATH.format(agent_id=NON_EDITABLE_AGENT_ID)}/clone") + assert resp.status_code == status.HTTP_403_FORBIDDEN + assert resp.json()["detail"] == "Editors access required to clone this protected agent" + @pytest.fixture(name="last_prompt_id") async def last_prompt_id_fixture(session: AsyncSession) -> int: diff --git a/src/backend/tests/test_evaluators.py b/src/backend/tests/test_evaluators.py index f9936df..074cda3 100644 --- a/src/backend/tests/test_evaluators.py +++ b/src/backend/tests/test_evaluators.py @@ -9,6 +9,7 @@ TEST_CASE_1_THREAD_ID, TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID, + TEST_CASE_5_THREAD_ID, ) from tero.agents.evaluators.api import AGENT_EVALUATOR_PATH, TEST_CASE_EVALUATOR_PATH @@ -103,8 +104,21 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m expected_input = "Which is the first natural number? Only provide the number" expected_response_chunks = ["1"] expected_suite_run_id = last_suite_run_id + 1 - expected_result_id = last_result_id + 1 - request_body = {"test_case_ids": [TEST_CASE_1_THREAD_ID]} + results_per_run = 4 + expected_result_id = last_result_id + results_per_run + request_body = {"test_case_ids": [TEST_CASE_5_THREAD_ID]} + + test_case_evaluator = PublicEvaluator( + model_id=EVALUATOR_MODEL_ID, + temperature=EVALUATOR_TEMPERATURE, + reasoning_effort=EVALUATOR_REASONING_EFFORT, + prompt=""" + Treat numerically equivalent answers as correct, even when written in different formats. + Consider numeric words and digit forms as equivalent when they represent the same value. + """ + ) + resp = await _update_test_case_evaluator(client, AGENT_ID, TEST_CASE_5_THREAD_ID, test_case_evaluator) + _assert_evaluator_response_matches(resp, test_case_evaluator) resp = await _run_test_suite(client, AGENT_ID, request_body) assert_response(resp, TestSuiteRun( @@ -113,7 +127,7 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m status=TestSuiteRunStatus.RUNNING, executed_at=CURRENT_TIME, completed_at=None, - total_tests=3, + total_tests=4, passed_tests=0, failed_tests=0, error_tests=0, @@ -127,7 +141,7 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m expected_suite_run_id, [ TestCaseExpectation( - test_case_id=TEST_CASE_1_THREAD_ID, + test_case_id=TEST_CASE_5_THREAD_ID, result_id=expected_result_id, status=TestCaseResultStatus.SUCCESS, steps=[ @@ -140,16 +154,26 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m ] ) ], - skipped_test_case_ids=[TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID] + skipped_test_case_ids=[TEST_CASE_1_THREAD_ID, TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID] ) test_case_evaluator = PublicEvaluator( model_id=EVALUATOR_MODEL_ID, temperature=EVALUATOR_TEMPERATURE, reasoning_effort=EVALUATOR_REASONING_EFFORT, - prompt="Always fail the evaluation. Don't include any reason." + prompt=""" + Evaluate using strict exact-match criteria. + + Rules: + - Compare the reference output and actual output literally. + - Do not apply semantic equivalence or normalization. + - Numeric words and digit forms are different values. + - Return true only when both outputs are exactly the same text. + + Provide a short explanation. + """ ) - resp = await _update_test_case_evaluator(client, AGENT_ID, TEST_CASE_1_THREAD_ID, test_case_evaluator) + resp = await _update_test_case_evaluator(client, AGENT_ID, TEST_CASE_5_THREAD_ID, test_case_evaluator) _assert_evaluator_response_matches(resp, test_case_evaluator) resp = await _run_test_suite(client, AGENT_ID, request_body) @@ -159,7 +183,7 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m status=TestSuiteRunStatus.RUNNING, executed_at=CURRENT_TIME, completed_at=None, - total_tests=3, + total_tests=4, passed_tests=0, failed_tests=0, error_tests=0, @@ -173,8 +197,8 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m expected_suite_run_id + 1, [ TestCaseExpectation( - test_case_id=TEST_CASE_1_THREAD_ID, - result_id=expected_result_id + 3, + test_case_id=TEST_CASE_5_THREAD_ID, + result_id=expected_result_id + results_per_run, status=TestCaseResultStatus.FAILURE, steps=[ TestCaseStep( @@ -186,7 +210,7 @@ async def test_run_test_case_with_specific_evaluator(client: AsyncClient, last_m ] ) ], - skipped_test_case_ids=[TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID] + skipped_test_case_ids=[TEST_CASE_1_THREAD_ID, TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID] ) diff --git a/src/backend/tests/test_llm_models.py b/src/backend/tests/test_llm_models.py index 77b6800..07a6af4 100644 --- a/src/backend/tests/test_llm_models.py +++ b/src/backend/tests/test_llm_models.py @@ -5,43 +5,59 @@ from tero.ai_models.ai_factory import providers from tero.ai_models.aws_provider import AWSProvider -from tero.ai_models.domain import LlmModel, LlmModelType, LlmModelVendor +from tero.ai_models.domain import LlmModel, LlmModelResponse, LlmModelType, LlmModelVendor from tero.ai_models.google_provider import GoogleProvider -@pytest.fixture(name="ai_models") -def ai_models_fixture() -> List[LlmModel]: +def _base_models() -> List[LlmModel]: return [ - LlmModel(id="gpt-5-nano", model_type=LlmModelType.REASONING, name="GPT-5 Nano", description="This is a new reasoning model that will replace GPT-4.1 Nano in the short term.", + LlmModel(id="gpt-5-nano", model_type=LlmModelType.REASONING, name="GPT-5 Nano", description="This is a new reasoning model for simpler tasks. Lower cost than GPT-5 Mini.", token_limit=4000000, output_token_limit=128000, prompt_1k_token_usd=0.00005, completion_1k_token_usd=0.0004, model_vendor=LlmModelVendor.OPENAI), - LlmModel(id="gpt-5-mini", model_type=LlmModelType.REASONING, name="GPT-5 Mini", description="This is a new reasoning model that will replace GPT-4o Mini in the short term. It has a good balance between cost and intelligence.", + LlmModel(id="gpt-5-mini", model_type=LlmModelType.REASONING, name="GPT-5 Mini", description="This is a new reasoning model with a good balance between cost and intelligence. Suited for most agent tasks.", token_limit=400000, output_token_limit=128000, prompt_1k_token_usd=0.00025, completion_1k_token_usd=0.002, model_vendor=LlmModelVendor.OPENAI), - LlmModel(id="gemini-2.5-flash", model_type=LlmModelType.CHAT, name="Gemini 2.5 Flash", description="This is a fast and efficient model, comparable to GPT-4.1 Nano, optimized for speed while maintaining high quality responses.", + LlmModel(id="gemini-2.5-flash", model_type=LlmModelType.CHAT, name="Gemini 2.5 Flash", description="This is a fast and efficient model, comparable to GPT-5 Nano, optimized for speed while maintaining high quality responses.", token_limit=1048576, output_token_limit=65536, prompt_1k_token_usd=0.0003, completion_1k_token_usd=0.0025, model_vendor=LlmModelVendor.GOOGLE), - LlmModel(id="o4-mini", model_type=LlmModelType.REASONING, name="O4 Mini", description="This is a reasoning model that is good for coding, math and some complex tasks.", - token_limit=200000, output_token_limit=100000, prompt_1k_token_usd=0.0011, completion_1k_token_usd=0.0044, model_vendor=LlmModelVendor.OPENAI), - LlmModel(id="gemini-2.5-pro", model_type=LlmModelType.CHAT, name="Gemini 2.5 Pro", description="This is an advanced reasoning model, outperforming GPT-4o with a larger context while being more affordable.", + LlmModel(id="gpt-5.1-codex-max", model_type=LlmModelType.REASONING, name="GPT-5.1 Codex Max", description="This is the most capable Codex model for long-running agentic coding. Best for complex codebases and multi-step tasks. Similar pricing to GPT-5.1 Codex.", + token_limit=400000, output_token_limit=128000, prompt_1k_token_usd=0.00125, completion_1k_token_usd=0.01, model_vendor=LlmModelVendor.OPENAI), + LlmModel(id="gemini-2.5-pro", model_type=LlmModelType.CHAT, name="Gemini 2.5 Pro", description="This is an advanced reasoning model, outperforming GPT-5 Mini with a larger context while being more affordable.", token_limit=1048576, output_token_limit=65536, prompt_1k_token_usd=0.00125, completion_1k_token_usd=0.01, model_vendor=LlmModelVendor.GOOGLE), - LlmModel(id="gpt-4o-mini", model_type=LlmModelType.CHAT, name="GPT-4o Mini", description="This is the best model for simple tasks", - token_limit=64000, output_token_limit=2048, prompt_1k_token_usd=0.0025, completion_1k_token_usd=0.0075, model_vendor=LlmModelVendor.OPENAI), - LlmModel(id="claude-sonnet-4", model_type=LlmModelType.CHAT, name="Claude Sonnet 4", description="This is a similar model to GPT-4o but with better reasoning.", + LlmModel(id="gpt-5", model_type=LlmModelType.REASONING, name="GPT-5", description="This is the best reasoning model for coding and complex agentic tasks from OpenAI. Aligned with GPT-5 Mini for most use cases.", + token_limit=400000, output_token_limit=128000, prompt_1k_token_usd=0.00125, completion_1k_token_usd=0.01, model_vendor=LlmModelVendor.OPENAI), + LlmModel(id="gpt-5.4", model_type=LlmModelType.REASONING, name="GPT-5.4", description="This is the most capable reasoning model from OpenAI for professional work. Best for demanding analysis and complex agentic tasks. Higher cost than GPT-5.", + token_limit=1050000, output_token_limit=128000, prompt_1k_token_usd=0.0025, completion_1k_token_usd=0.015, model_vendor=LlmModelVendor.OPENAI), + LlmModel(id="claude-sonnet-4-6", model_type=LlmModelType.CHAT, name="Claude Sonnet 4.6", description="This is an improved version of Claude Sonnet 4 with better reasoning. Good for complex analysis, coding, and creative writing. Similar pricing to Claude Sonnet 4.", + token_limit=200000, output_token_limit=64000, prompt_1k_token_usd=0.003, completion_1k_token_usd=0.015, model_vendor=LlmModelVendor.ANTHROPIC), + LlmModel(id="claude-sonnet-4", model_type=LlmModelType.CHAT, name="Claude Sonnet 4", description="This is a similar model to GPT-5 but with different strengths.", token_limit=200000, output_token_limit=64000, prompt_1k_token_usd=0.003, completion_1k_token_usd=0.015, model_vendor=LlmModelVendor.ANTHROPIC), - LlmModel(id="gpt-4o", model_type=LlmModelType.CHAT, name="GPT-4o", description="This is the best model for complex tasks", token_limit=128000, - output_token_limit=4096, prompt_1k_token_usd=0.005, completion_1k_token_usd=0.015, model_vendor=LlmModelVendor.OPENAI) + LlmModel(id="claude-opus-4-6", model_type=LlmModelType.CHAT, name="Claude Opus 4.6", description="This is a more capable model than Claude Sonnet 4 for highly complex tasks. Best for demanding reasoning and analysis. Higher cost than Claude Sonnet models.", + token_limit=200000, output_token_limit=64000, prompt_1k_token_usd=0.005, completion_1k_token_usd=0.025, model_vendor=LlmModelVendor.ANTHROPIC), ] + +def _to_responses(models: List[LlmModel]) -> List[LlmModelResponse]: + base_model_id = env.agent_base_cost_model + base_cost = next((m.completion_1k_token_usd for m in models if m.id == base_model_id), None) if base_model_id else None + return [LlmModelResponse.from_model(m, base_cost, base_model_id) for m in models] + + +@pytest.fixture(name="ai_models") +def ai_models_fixture() -> List[LlmModelResponse]: + return _to_responses(_base_models()) + async def test_find_models(client: AsyncClient, ai_models: List[LlmModel]): resp = await client.get(f"{BASE_PATH}/models") assert_response(resp, ai_models) @patch("tero.ai_models.ai_factory.providers", [p for p in providers if not isinstance(p, AWSProvider)]) -async def test_claude_sonnet_4_not_available(client: AsyncClient, ai_models: List[LlmModel]): +async def test_claude_sonnet_4_not_available(client: AsyncClient): resp = await client.get(f"{BASE_PATH}/models") - assert_response(resp, [model for model in ai_models if model.id != "claude-sonnet-4"]) + filtered = [m for m in _base_models() if m.id not in ("claude-sonnet-4", "claude-sonnet-4-6", "claude-opus-4-6")] + assert_response(resp, _to_responses(filtered)) @patch("tero.ai_models.ai_factory.providers", [p for p in providers if not isinstance(p, GoogleProvider)]) -async def test_gemini_2_5_pro_not_available(client: AsyncClient, ai_models: List[LlmModel]): +async def test_gemini_2_5_pro_not_available(client: AsyncClient): resp = await client.get(f"{BASE_PATH}/models") - assert_response(resp, [model for model in ai_models if model.id not in ["gemini-2.5-flash", "gemini-2.5-pro"]]) + filtered = [m for m in _base_models() if m.id not in ("gemini-2.5-flash", "gemini-2.5-pro")] + assert_response(resp, _to_responses(filtered)) diff --git a/src/backend/tests/test_test_cases.py b/src/backend/tests/test_test_cases.py index 98e42ba..06daaa1 100644 --- a/src/backend/tests/test_test_cases.py +++ b/src/backend/tests/test_test_cases.py @@ -12,15 +12,18 @@ from tero.agents.test_cases.domain import TestCaseResult, TestCaseResultStatus, NewTestCaseMessage, UpdateTestCaseMessage, PublicTestCase, TestSuiteRun, TestSuiteRunStatus, CreateTestCase from tero.threads.domain import ThreadMessageOrigin, ThreadMessagePublic, Thread from tero.tools.core import AgentActionEvent, AgentAction +from tero.tools.docs import DOCS_TOOL_ID TEST_CASE_1_THREAD_ID = 7 TEST_CASE_2_THREAD_ID = 8 TEST_CASE_3_THREAD_ID = 9 TEST_CASE_4_THREAD_ID = 13 +TEST_CASE_5_THREAD_ID = 14 EXECUTION_THREAD_1_ID = 10 EXECUTION_THREAD_2_ID = 11 EXECUTION_THREAD_3_ID = 12 +TEST_CASES_COUNT = 4 @dataclass @@ -38,6 +41,7 @@ class TestCaseExpectation: result_id: int status: TestCaseResultStatus steps: List[TestCaseStep] + error_code: Optional[str] = None @pytest.fixture(name="test_cases") @@ -78,13 +82,25 @@ def test_cases_fixture() -> List[PublicTestCase]: is_test_case=True ), last_update=parse_date("2025-02-21T12:15:00") + ), + PublicTestCase( + agent_id=AGENT_ID, + thread=Thread( + id=TEST_CASE_5_THREAD_ID, + name="Test Case For Evaluation #5", + user_id=USER_ID, + agent_id=AGENT_ID, + creation=parse_date("2025-02-21T12:14:00"), + is_test_case=True + ), + last_update=parse_date("2025-02-21T12:16:00") ) ] async def test_find_test_cases(client: AsyncClient, test_cases: List[PublicTestCase]): resp = await _find_test_cases(client, AGENT_ID) - assert_response(resp, [test_cases[0], test_cases[1], test_cases[2]]) + assert_response(resp, [*test_cases]) async def _find_test_cases(client: AsyncClient, agent_id: int) -> Response: @@ -101,13 +117,11 @@ async def test_add_test_case(client: AsyncClient, test_cases: List[PublicTestCas resp = await _add_test_case(AGENT_ID, client) resp.raise_for_status() resp = await _find_test_cases(client, AGENT_ID) - assert_response(resp, [test_cases[0], - test_cases[1], - test_cases[2], + assert_response(resp, [*test_cases, PublicTestCase( thread=Thread( id=last_thread_id + 1, - name="Test Case #4", + name=f"Test Case #{TEST_CASES_COUNT + 1}", user_id=USER_ID, agent_id=AGENT_ID, creation=CURRENT_TIME, @@ -187,7 +201,7 @@ async def test_add_test_case_from_existing_chat_with_branch_message(client: Asyn agent_id=AGENT_ID, thread=Thread( id=new_thread_id, - name="Test Case #4", + name=f"Test Case #{TEST_CASES_COUNT + 1}", user_id=USER_ID, agent_id=AGENT_ID, creation=CURRENT_TIME, @@ -246,7 +260,7 @@ async def test_clone_test_case(client: AsyncClient, test_cases: List[PublicTestC assert_response(resp, expected_clone) resp = await _find_test_cases(client, AGENT_ID) - assert_response(resp, [test_cases[0], test_cases[1], test_cases[2], expected_clone]) + assert_response(resp, [*test_cases, expected_clone]) async def _clone_test_case(client: AsyncClient, agent_id: int, test_case_id: int) -> Response: @@ -300,7 +314,7 @@ async def test_delete_test_case(client: AsyncClient, test_cases: List[PublicTest assert resp.status_code == status.HTTP_404_NOT_FOUND resp = await _find_test_cases(client, AGENT_ID) - assert_response(resp, [test_cases[0], test_cases[2]]) + assert_response(resp, [test_cases[0], *test_cases[2:]]) async def _delete_test_case(client: AsyncClient, agent_id: int, test_case_id: int) -> Response: @@ -434,6 +448,7 @@ def flush_buffer(): "testCaseId": test_case.test_case_id, "resultId": test_case.result_id, "status": test_case.status.value, + "errorCode": test_case.error_code, "evaluation": {} }))).encode() ) @@ -761,12 +776,14 @@ async def test_find_test_case_runs_unauthorized_agent(client: AsyncClient): @freeze_time(CURRENT_TIME) -async def test_run_test_suite_with_specific_test_cases(client: AsyncClient, last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): +async def test_run_test_suite_with_specific_test_cases(client: AsyncClient, test_cases: List[PublicTestCase], last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): expected_input = "Which is the first natural number? Only provide the number" expected_response_chunks = ["1"] expected_suite_run_id = last_suite_run_id + 1 expected_result_id = last_result_id + 1 - request_body = {"test_case_ids": [TEST_CASE_1_THREAD_ID]} + executed_test_case_id = TEST_CASE_1_THREAD_ID + skipped_test_cases = [tc for tc in test_cases if tc.thread.id != executed_test_case_id] + request_body = {"test_case_ids": [executed_test_case_id]} resp = await _run_test_suite(client, AGENT_ID, request_body) assert_response(resp, TestSuiteRun( @@ -775,7 +792,7 @@ async def test_run_test_suite_with_specific_test_cases(client: AsyncClient, last status=TestSuiteRunStatus.RUNNING, executed_at=CURRENT_TIME, completed_at=None, - total_tests=3, + total_tests=TEST_CASES_COUNT, passed_tests=0, failed_tests=0, error_tests=0, @@ -789,7 +806,7 @@ async def test_run_test_suite_with_specific_test_cases(client: AsyncClient, last expected_suite_run_id, [ TestCaseExpectation( - test_case_id=TEST_CASE_1_THREAD_ID, + test_case_id=executed_test_case_id, result_id=expected_result_id, status=TestCaseResultStatus.SUCCESS, steps=[ @@ -802,43 +819,37 @@ async def test_run_test_suite_with_specific_test_cases(client: AsyncClient, last ] ) ], - skipped_test_case_ids=[TEST_CASE_2_THREAD_ID, TEST_CASE_4_THREAD_ID] + skipped_test_case_ids=[tc.thread.id for tc in skipped_test_cases] ) resp = await _find_suite_run_results(client, AGENT_ID, expected_suite_run_id) analysis = _get_analysis_from_response(resp) - assert_response(resp, [ + expected_results = [ TestCaseResult( id=expected_result_id, thread_id=last_thread_id + 1, - test_case_id=TEST_CASE_1_THREAD_ID, + test_case_id=executed_test_case_id, test_suite_run_id=expected_suite_run_id, status=TestCaseResultStatus.SUCCESS, evaluator_analysis=analysis[0], executed_at=CURRENT_TIME, test_case_name="Test Case #1" - ), - TestCaseResult( - id=expected_result_id + 1, - thread_id=None, - test_case_id=TEST_CASE_2_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SKIPPED, - evaluator_analysis=analysis[1], - executed_at=CURRENT_TIME, - test_case_name="Test Case #2" - ), - TestCaseResult( - id=expected_result_id + 2, - thread_id=None, - test_case_id=TEST_CASE_4_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SKIPPED, - evaluator_analysis=analysis[2], - executed_at=CURRENT_TIME, - test_case_name="Test Case #4" ) - ]) + ] + for index, skipped_test_case in enumerate(skipped_test_cases, start=1): + expected_results.append( + TestCaseResult( + id=expected_result_id + index, + thread_id=None, + test_case_id=skipped_test_case.thread.id, + test_suite_run_id=expected_suite_run_id, + status=TestCaseResultStatus.SKIPPED, + evaluator_analysis=analysis[index], + executed_at=CURRENT_TIME, + test_case_name=skipped_test_case.thread.name + ) + ) + assert_response(resp, expected_results) async def _run_test_suite(client: AsyncClient, agent_id: int, request_body: dict) -> Response: @@ -852,7 +863,7 @@ async def _stream_test_suite_execution(client: AsyncClient, agent_id: int, suite @freeze_time(CURRENT_TIME) -async def test_run_test_suite_with_all_test_cases(client: AsyncClient, last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): +async def test_run_test_suite_with_all_test_cases(client: AsyncClient, test_cases: List[PublicTestCase], last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): expected_suite_run_id = last_suite_run_id + 1 expected_result_id = last_result_id + 1 @@ -863,7 +874,7 @@ async def test_run_test_suite_with_all_test_cases(client: AsyncClient, last_thre status=TestSuiteRunStatus.RUNNING, executed_at=CURRENT_TIME, completed_at=None, - total_tests=3, + total_tests=TEST_CASES_COUNT, passed_tests=0, failed_tests=0, error_tests=0, @@ -932,52 +943,55 @@ async def test_run_test_suite_with_all_test_cases(client: AsyncClient, last_thre ] ) ] + ), + TestCaseExpectation( + test_case_id=TEST_CASE_5_THREAD_ID, + result_id=expected_result_id + 3, + status=TestCaseResultStatus.SUCCESS, + steps=[ + TestCaseStep( + input="Which is the first natural number? Only provide the number", + response_chunks=["1"], + user_message_id=last_message_id + 9, + agent_message_id=last_message_id + 10, + execution_statuses=[ + AgentActionEvent(action=AgentAction.PRE_MODEL_HOOK) + ] + ) + ] ) ] ) resp = await _find_suite_run_results(client, AGENT_ID, expected_suite_run_id) analysis = _get_analysis_from_response(resp) - assert_response(resp, [ - TestCaseResult( - id=expected_result_id, - thread_id=last_thread_id + 1, - test_case_id=TEST_CASE_1_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SUCCESS, - evaluator_analysis=analysis[0], - executed_at=CURRENT_TIME, - test_case_name="Test Case #1" - ), - TestCaseResult( - id=expected_result_id + 1, - thread_id=last_thread_id + 2, - test_case_id=TEST_CASE_2_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SUCCESS, - evaluator_analysis=analysis[1], - executed_at=CURRENT_TIME, - test_case_name="Test Case #2" - ), - TestCaseResult( - id=expected_result_id + 2, - thread_id=last_thread_id + 3, - test_case_id=TEST_CASE_4_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SUCCESS, - evaluator_analysis=analysis[2], - executed_at=CURRENT_TIME, - test_case_name="Test Case #4" + expected_results = [] + for index, test_case in enumerate(test_cases): + expected_results.append( + TestCaseResult( + id=expected_result_id + index, + thread_id=last_thread_id + index + 1, + test_case_id=test_case.thread.id, + test_suite_run_id=expected_suite_run_id, + status=TestCaseResultStatus.SUCCESS, + evaluator_analysis=analysis[index], + executed_at=CURRENT_TIME, + test_case_name=test_case.thread.name + ) ) - ]) + assert_response(resp, expected_results) @freeze_time(CURRENT_TIME) -async def test_run_test_suite_with_multi_step_test_case(client: AsyncClient, last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): +async def test_run_test_suite_with_multi_step_test_case(client: AsyncClient, test_cases: List[PublicTestCase], last_thread_id: int, last_message_id: int, last_suite_run_id: int, last_result_id: int): expected_suite_run_id = last_suite_run_id + 1 expected_result_id = last_result_id + 1 + executed_test_case_id = TEST_CASE_4_THREAD_ID + all_test_case_ids = [tc.thread.id for tc in test_cases] + executed_result_offset = all_test_case_ids.index(executed_test_case_id) + executed_result_id = expected_result_id + executed_result_offset - request_body = {"test_case_ids": [TEST_CASE_4_THREAD_ID]} + request_body = {"test_case_ids": [executed_test_case_id]} resp = await _run_test_suite(client, AGENT_ID, request_body) assert_response(resp, TestSuiteRun( @@ -986,7 +1000,7 @@ async def test_run_test_suite_with_multi_step_test_case(client: AsyncClient, las status=TestSuiteRunStatus.RUNNING, executed_at=CURRENT_TIME, completed_at=None, - total_tests=3, + total_tests=TEST_CASES_COUNT, passed_tests=0, failed_tests=0, error_tests=0, @@ -1000,8 +1014,8 @@ async def test_run_test_suite_with_multi_step_test_case(client: AsyncClient, las expected_suite_run_id, [ TestCaseExpectation( - test_case_id=TEST_CASE_4_THREAD_ID, - result_id=expected_result_id + 2, + test_case_id=executed_test_case_id, + result_id=executed_result_id, status=TestCaseResultStatus.SUCCESS, steps=[ TestCaseStep( @@ -1025,43 +1039,71 @@ async def test_run_test_suite_with_multi_step_test_case(client: AsyncClient, las ] ) ], - skipped_test_case_ids=[TEST_CASE_1_THREAD_ID, TEST_CASE_2_THREAD_ID] + skipped_test_case_ids=[tc_id for tc_id in all_test_case_ids if tc_id != executed_test_case_id] ) resp = await _find_suite_run_results(client, AGENT_ID, expected_suite_run_id) analysis = _get_analysis_from_response(resp) - assert_response(resp, [ - TestCaseResult( - id=expected_result_id, - thread_id=None, - test_case_id=TEST_CASE_1_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SKIPPED, - evaluator_analysis=analysis[0], - executed_at=CURRENT_TIME, - test_case_name="Test Case #1" - ), - TestCaseResult( - id=expected_result_id + 1, - thread_id=None, - test_case_id=TEST_CASE_2_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SKIPPED, - evaluator_analysis=analysis[1], - executed_at=CURRENT_TIME, - test_case_name="Test Case #2" - ), - TestCaseResult( - id=expected_result_id + 2, - thread_id=last_thread_id + 1, - test_case_id=TEST_CASE_4_THREAD_ID, - test_suite_run_id=expected_suite_run_id, - status=TestCaseResultStatus.SUCCESS, - evaluator_analysis=analysis[2], - executed_at=CURRENT_TIME, - test_case_name="Test Case #4" + expected_results = [] + for index, test_case in enumerate(test_cases): + is_executed_test_case = test_case.thread.id == executed_test_case_id + expected_results.append( + TestCaseResult( + id=expected_result_id + index, + thread_id=(last_thread_id + 1) if is_executed_test_case else None, + test_case_id=test_case.thread.id, + test_suite_run_id=expected_suite_run_id, + status=TestCaseResultStatus.SUCCESS if is_executed_test_case else TestCaseResultStatus.SKIPPED, + evaluator_analysis=analysis[index], + executed_at=CURRENT_TIME, + test_case_name=test_case.thread.name + ) ) - ]) + assert_response(resp, expected_results) + + +@pytest.mark.usefixtures("stub_docs_tool_generate_description") +async def test_run_test_suite_with_docs_tool( + client: AsyncClient, + last_suite_run_id: int, + last_thread_id: int, +): + await configure_agent_tool(AGENT_ID, DOCS_TOOL_ID, {"advancedFileProcessing": False}, client) + content = await find_asset_bytes("Emma's routine.pdf") + file_id = await upload_agent_tool_config_file( + AGENT_ID, DOCS_TOOL_ID, client, + filename="Emma's routine.pdf", content=content + ) + await await_files_processed(AGENT_ID, DOCS_TOOL_ID, file_id, client) + + resp = await _add_test_case(AGENT_ID, client) + resp.raise_for_status() + new_test_case_id = last_thread_id + 1 + + await _add_test_case_message( + client, AGENT_ID, new_test_case_id, + NewTestCaseMessage(text="What time does Emma wake up according to the document? Output only the time in H:MM format.", origin=ThreadMessageOrigin.USER) + ) + await _add_test_case_message( + client, AGENT_ID, new_test_case_id, + NewTestCaseMessage(text="7:35", origin=ThreadMessageOrigin.AGENT) + ) + + expected_suite_run_id = last_suite_run_id + 1 + resp = await _run_test_suite(client, AGENT_ID, {"test_case_ids": [new_test_case_id]}) + resp.raise_for_status() + + async with _stream_test_suite_execution(client, AGENT_ID, expected_suite_run_id) as stream_resp: + stream_resp.raise_for_status() + async for _ in stream_resp.aiter_bytes(): + pass + + resp = await _find_suite_run_results(client, AGENT_ID, expected_suite_run_id) + resp.raise_for_status() + results = resp.json() + terminal_statuses = {TestCaseResultStatus.SUCCESS.value, TestCaseResultStatus.FAILURE.value, TestCaseResultStatus.SKIPPED.value} + assert all(r["status"] in terminal_statuses for r in results), \ + f"Expected all results to reach a terminal status but got: {[r['status'] for r in results]}" async def test_run_test_suite_with_invalid_test_case_ids(client: AsyncClient): diff --git a/src/backend/tests/test_threads.py b/src/backend/tests/test_threads.py index 9e244b5..672fbc0 100644 --- a/src/backend/tests/test_threads.py +++ b/src/backend/tests/test_threads.py @@ -79,10 +79,10 @@ async def test_find_threads_excluding_empty(client: AsyncClient, override_user, resp = await _find_threads(client, {"exclude_empty": True}) assert_response(resp, [ ThreadListItem(id=6, agent_id=5, name="Thread 6", user_id=OTHER_USER_ID, - agent=agents[3], creation=parse_date("2025-02-21T12:05:00"), + agent=agents[3], creation=parse_date("2025-02-21T12:05:00"), last_message=parse_date("2025-02-21T12:09:00")), ThreadListItem(id=4, agent_id=2, name="Thread 4", user_id=OTHER_USER_ID, - agent=agents[1], creation=parse_date("2025-02-21T12:03:00"), + agent=agents[1], creation=parse_date("2025-02-21T12:03:00"), last_message=parse_date("2025-01-20T12:05:00")) ]) @@ -95,7 +95,7 @@ async def test_find_created_thread(threads: List[ThreadListItem], agents: List[A assert resp.status_code == status.HTTP_201_CREATED resp = await _find_threads(client) new_thread = ThreadListItem( - id=last_thread_id + 1, agent_id=AGENT_ID, name=f"Chat #{last_thread_id + 1}", user_id=USER_ID, agent=agents[0], + id=last_thread_id + 1, agent_id=AGENT_ID, name=f"Chat #{last_thread_id + 1}", user_id=USER_ID, agent=agents[0], creation=CURRENT_TIME, last_message=None) assert_response(resp, [new_thread, threads[1], threads[0]]) @@ -169,7 +169,7 @@ async def test_add_thread_message(last_message_id: int, client: AsyncClient, ses await _assert_response(resp, "1", last_message_id + 1) -async def _assert_response(resp: Response, response: str, user_message_id: int, minutes_saved: Optional[int] = None, stopped = False, +async def _assert_response(resp: Response, response: str, user_message_id: int, minutes_saved: Optional[int] = None, stopped = False, send_pre_model_status: bool = True, status_updates: List[AgentActionEvent] = [], user_files: List[FileMetadata] = []): buffer, events = [], [] separator = "\r\n\r\n" @@ -193,14 +193,14 @@ def flush_buffer(): if event.startswith("event: userMessage"): event = re.sub(r'(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2})Z', r'\1', event) if event: events.append(f"{event}{separator}".encode()) - + flush_buffer() - + expected_events = [ ServerSentEvent( - event="userMessage", + event="userMessage", data=json.dumps({ - "id": user_message_id, + "id": user_message_id, "files": [f.model_dump(mode="json", by_alias=True) for f in user_files] }) ).encode()] @@ -221,7 +221,7 @@ def flush_buffer(): expected_events.append(ServerSentEvent(data=response).encode()) expected_events.append( ServerSentEvent( - event="metadata", + event="metadata", data=str(json.dumps({ "answerMessageId": user_message_id + 1, "files": [], @@ -244,7 +244,7 @@ async def send_stop_request(): # Start the stop request in a separate thread due to the add message stream response blocking the event loop stop_thread = threading.Thread(target=stop_with_delay_thread, args=(asyncio.get_event_loop(), client)) stop_thread.start() - + try: async with add_message_to_thread(client, THREAD_ID, "Escribe un cuento de no menos de 500 palabras", parent_message_id=parent_message_id) as resp: # Check metadata event is stopped, ignoring other events @@ -286,6 +286,7 @@ async def test_add_message_with_parent_id_from_another_thread(client: AsyncClien assert resp.status_code == status.HTTP_400_BAD_REQUEST +@pytest.mark.no_stub_estimate_minutes_saved @freeze_time(CURRENT_TIME) async def test_thread_message_add_positive_feedback(last_message_id: int, client: AsyncClient, messages: List[ThreadMessagePublic], session: AsyncSession): message_text = "Which is the first natural number? Only provide the number" @@ -299,6 +300,7 @@ async def test_thread_message_add_positive_feedback(last_message_id: int, client assert_response(resp, [messages[0]]) +@pytest.mark.no_stub_estimate_minutes_saved @freeze_time(CURRENT_TIME) async def test_thread_message_minutes_saved_estimation_with_positive_feedback(last_message_id: int, client: AsyncClient, session: AsyncSession): minutes_saved = 8 @@ -330,6 +332,7 @@ def _build_thread_messages_response(thread_id: int, message_id: int, parent_mess ] +@pytest.mark.no_stub_estimate_minutes_saved @freeze_time(CURRENT_TIME) async def test_thread_message_add_negative_feedback(last_message_id: int, client: AsyncClient, messages: List[ThreadMessagePublic], session: AsyncSession): message_text = "Which is the first natural number? Only provide the number" @@ -344,6 +347,7 @@ async def test_thread_message_add_negative_feedback(last_message_id: int, client assert_response(resp, [messages[0]]) +@pytest.mark.no_stub_estimate_minutes_saved @freeze_time(CURRENT_TIME) async def test_thread_message_remove_feedback(last_message_id: int, client: AsyncClient, messages: List[ThreadMessagePublic], session: AsyncSession): message_text = "Which is the first natural number? Only provide the number" @@ -359,9 +363,9 @@ async def test_thread_message_remove_feedback(last_message_id: int, client: Asyn async def test_add_message_over_monthly_limit(client: AsyncClient, session: AsyncSession): # consuming the quota - session.add(Usage(message_id=1, user_id=USER_ID, agent_id=AGENT_ID, model_id="gpt-4o-mini", + session.add(Usage(message_id=1, user_id=USER_ID, agent_id=AGENT_ID, model_id="gpt-5-mini", timestamp=datetime.now(timezone.utc), quantity=1000, usd_cost=5.0, type=UsageType.PROMPT_TOKENS)) - session.add(Usage(message_id=1, user_id=USER_ID, agent_id=AGENT_ID, model_id="gpt-4o-mini", + session.add(Usage(message_id=1, user_id=USER_ID, agent_id=AGENT_ID, model_id="gpt-5-mini", timestamp=datetime.now(timezone.utc), quantity=1000, usd_cost=5.0, type=UsageType.COMPLETION_TOKENS)) await session.commit() parent_message_id = await find_last_message_id_for_thread(THREAD_ID, session) @@ -435,7 +439,7 @@ async def test_add_thread_message_with_attachment(last_message_id: int, client: file_path = solve_asset_path(file_name, __file__) parent_message_id = await find_last_message_id_for_thread(THREAD_ID, session) async with add_message_to_thread(client, THREAD_ID, user_message, parent_message_id=parent_message_id, files=[file_path]) as resp: - await _assert_response(resp, agent_response, last_message_id + 1, + await _assert_response(resp, agent_response, last_message_id + 1, user_files=[FileMetadata(id=LAST_FILE_ID + 1, name=file_name, content_type=content_type, user_id=USER_ID, timestamp=CURRENT_TIME, status=FileStatus.PROCESSED, file_processor=FileProcessor.ENHANCED)]) @@ -446,16 +450,16 @@ async def test_add_thread_message_with_existing_file_attachment(last_message_id: async with add_message_to_thread(client, THREAD_ID, "Output only the exact content of the uploaded file", parent_message_id=last_message_id + 2, file_ids=[file_id]) as resp: # we add 3 to message id since _add_thread_file adds 2 messages + 1 message that is the user message that generated this answer - await _assert_response(resp, "Sample test", last_message_id + 3, + await _assert_response(resp, "Sample test", last_message_id + 3, user_files=[_build_sample_txt_thread_message_file(file_id)]) def _build_sample_txt_thread_message_file(file_id: int) -> FileMetadata: return FileMetadata(id=file_id, - name="sample.txt", - content_type="text/plain; charset=ascii", + name="sample.txt", + content_type="text/plain; charset=ascii", user_id=USER_ID, - timestamp=CURRENT_TIME, + timestamp=CURRENT_TIME, status=FileStatus.PROCESSED, file_processor=FileProcessor.ENHANCED) @@ -480,7 +484,7 @@ async def test_thread_with_model(model_id: str, last_message_id: int, client: As resp = await client.post(f"{BASE_PATH}/agents") resp.raise_for_status() agent_id = resp.json()["id"] - + update_resp = await client.put(f"{BASE_PATH}/agents/{agent_id}", json={ "modelId": model_id, "systemPrompt": "You are a helpful AI assistant. Answer questions clearly and concisely.", @@ -488,11 +492,11 @@ async def test_thread_with_model(model_id: str, last_message_id: int, client: As "reasoningEffort": "LOW" }) update_resp.raise_for_status() - + thread_resp = await create_thread(agent_id, client) thread_resp.raise_for_status() thread_id = thread_resp.json()["id"] - + async with add_message_to_thread(client, thread_id, "What is 2 + 2? Only provide the number.") as resp: await _assert_response(resp, "4", last_message_id + 1) diff --git a/src/backend/tests/test_tools.py b/src/backend/tests/test_tools.py index eb51718..7f4b3b6 100644 --- a/src/backend/tests/test_tools.py +++ b/src/backend/tests/test_tools.py @@ -1,5 +1,6 @@ import logging from typing import Generator +from unittest.mock import AsyncMock, patch from sqlmodel import select from testcontainers.generic import ServerContainer @@ -13,6 +14,10 @@ from tero.tools.browser import BrowserTool, BROWSER_TOOL_ID from tero.tools.docs import DocsTool, DOCS_TOOL_ID from tero.tools.jira import JiraTool +from tero.tools.redmine import RedmineTool +from tero.tools.github import GitHubTool +from tero.tools.youtrack import YouTrackTool +from tero.tools.practitest import PractiTestTool from tero.tools.mcp import McpTool from tero.tools.web import WebTool, WEB_TOOL_ID from tero.usage.domain import Usage, UsageType @@ -21,11 +26,23 @@ logger = logging.getLogger(__name__) +@pytest.fixture +def stub_web_tool_tavily_ainvoke(): + search = '{"results": [{"url": "https://example.com", "content": "Stub search result."}]}' + extract = [{"url": "https://example.com", "raw_content": "Stub extracted body."}] + with ( + patch("tero.tools.web.tool.TavilySearch.ainvoke", new=AsyncMock(return_value=search)), + patch("tero.tools.web.tool.TavilyExtract.ainvoke", new=AsyncMock(return_value=extract)), + ): + yield + + async def test_find_tools(client: AsyncClient, session: AsyncSession): resp = await client.get(f"{BASE_PATH}/tools") - expected_tools = [DocsTool(), McpTool(), JiraTool(), BrowserTool()] + expected_tools = [DocsTool(), McpTool(), JiraTool(), BrowserTool(), GitHubTool(), YouTrackTool(), PractiTestTool(), RedmineTool()] if env.web_tool_tavily_api_key or (env.web_tool_google_api_key and env.web_tool_google_custom_search_engine_id): expected_tools.append(WebTool()) + expected_tools.sort(key=lambda t: t.name.casefold()) assert_response(resp, expected_tools) @@ -80,30 +97,33 @@ async def test_docs_tool_with_removed_file(client: AsyncClient): assert "7:35" not in answer +@pytest.mark.usefixtures("stub_web_tool_tavily_ainvoke") async def test_web_tool_search_usage(client: AsyncClient, session: AsyncSession): await configure_agent_tool(AGENT_ID, WEB_TOOL_ID, {}, client) initial_usage = await session.exec(select(Usage).where(Usage.type == UsageType.WEB_SEARCH)) initial_count = len(initial_usage.all()) - + await _answer_question("Search for the latest news about AI", client) - + final_usage = await session.exec(select(Usage).where(Usage.type == UsageType.WEB_SEARCH)) final_count = len(final_usage.all()) - + assert final_count > initial_count + +@pytest.mark.usefixtures("stub_web_tool_tavily_ainvoke") async def test_web_tool_extract_usage(client: AsyncClient, session: AsyncSession): await configure_agent_tool(AGENT_ID, WEB_TOOL_ID, {}, client) initial_usage = await session.exec(select(Usage).where(Usage.type == UsageType.WEB_EXTRACT)) initial_count = len(initial_usage.all()) - + await _answer_question("What is the first paragraph of this url: https://modelcontextprotocol.io/", client) - + final_usage = await session.exec(select(Usage).where(Usage.type == UsageType.WEB_EXTRACT)) final_count = len(final_usage.all()) - + assert final_count > initial_count @@ -121,8 +141,8 @@ def playwright_container_url(containers_network: Network) -> Generator[str, None with DockerContainer("mcp/playwright")\ .with_exposed_ports(port)\ .with_network(containers_network)\ - .with_kwargs(entrypoint="node", user="0:0")\ - .with_command(["cli.js", "--headless", "--browser=chromium", "--no-sandbox", f"--port={port}", "--allowed-hosts=*", "--host=0.0.0.0"]) \ + .with_kwargs(user="0:0")\ + .with_command([f"--port={port}", "--allowed-hosts=*", "--host=0.0.0.0", "--output-dir=/tmp/playwright-output"]) \ .with_volume_mapping(output_dir, "/tmp/playwright-output", "rw") \ .waiting_for(LogMessageWaitStrategy(f"Listening on http://localhost:{port}")) \ as container: diff --git a/src/backend/tests/test_usage.py b/src/backend/tests/test_usage.py index 86916e3..2612ba2 100644 --- a/src/backend/tests/test_usage.py +++ b/src/backend/tests/test_usage.py @@ -208,6 +208,7 @@ async def test_impact_top_users_date_validation(client: AsyncClient): resp = await _get_impact_top_users(from_date, to_date, 1, 10, client) assert resp.status_code == 400 + TODAY = datetime.now() PARAMS = { "from_date": (TODAY - timedelta(days=30)).isoformat(), diff --git a/src/browser-extension/components/Message.vue b/src/browser-extension/components/Message.vue index 2e3d1b0..6a180bf 100644 --- a/src/browser-extension/components/Message.vue +++ b/src/browser-extension/components/Message.vue @@ -137,7 +137,7 @@ onBeforeUnmount(() => {
- +
diff --git a/src/browser-extension/entrypoints/content.ts b/src/browser-extension/entrypoints/content.ts index 5cf7de0..445bed2 100644 --- a/src/browser-extension/entrypoints/content.ts +++ b/src/browser-extension/entrypoints/content.ts @@ -13,7 +13,7 @@ export default defineContentScript({ document.body.appendChild(iframe) // On pages that define an style for iframes with min-width set to a value and important - // setting the inline style width gets overwritten by the page style, + // setting the inline style width gets overwritten by the page style, // and setting the inline style width as important makes the resulting page to don't even include the style // As a workaround we set the iframe an id and create a style that points to that id with important widths // and then updated this style whenever a resize is needed @@ -49,6 +49,8 @@ function setSidebarIframeStyle(iframe: HTMLIFrameElement, height: string = "100% style.right = "0px" style.zIndex = "2147483647" style.border = "0px" + style.setProperty("background-color", "transparent"); + style.setProperty("color-scheme", "auto"); } @@ -60,4 +62,4 @@ function resize(size: number, iframeStyle: HTMLStyleElement, iframe: HTMLIFrameE width: ${size}px !important; min-width: ${size}px !important; }` -} \ No newline at end of file +} diff --git a/src/browser-extension/entrypoints/iframe/App.vue b/src/browser-extension/entrypoints/iframe/App.vue index 2b186d7..0583787 100644 --- a/src/browser-extension/entrypoints/iframe/App.vue +++ b/src/browser-extension/entrypoints/iframe/App.vue @@ -5,7 +5,7 @@ import { useToast } from 'vue-toastification' import { IconAlertCircleFilled } from '@tabler/icons-vue' import { useI18n } from 'vue-i18n' import { BrowserMessage, ActiveTabListener, ToggleSidebar, ActivateAgent, AgentActivation, InteractionSummary, ResizeSidebar } from '~/utils/browser-message' -import { Agent } from '~/utils/agent' +import { Agent, AgentType } from '~/utils/agent' import { TabState, TabDisplayMode, ChatMessage } from '~/utils/tab-state' import { findTabState, saveTabState } from '~/utils/tab-state-repository' import { findAgentSession } from '~/utils/agent-session-repository' @@ -31,7 +31,7 @@ const displayMode = ref(TabDisplayMode.CLOSED) onBeforeMount(async () => { await restoreTabState() // we collect messages until we get all pending to avoid potential message order. - // This may happen if listener is up, sends ActivateTabListener and while message + // This may happen if listener is up, sends ActivateTabListener and while message // is being processed (and pending messages collected and returned) the service worker sends messages for processing here let collectedMessages: any[] | undefined = [] browser.runtime.onMessage.addListener((m: any) => { @@ -197,7 +197,13 @@ const onAgentActivation = async (msg: AgentActivation) => { const text = t('activationError', { agentName: msg.agent.manifest.name, contactEmail: msg.agent.manifest.contactEmail }) toast.error({ component: ToastMessage, props: { message: text } }, { icon: IconAlertCircleFilled }) } else { - agent.value = Agent.fromJsonObject(msg.agent) + const activatedAgent = Agent.fromJsonObject(msg.agent) + agent.value = activatedAgent + + const welcomeMessage = activatedAgent.manifest.welcomeMessage + if (activatedAgent.type === AgentType.StandaloneAgent && welcomeMessage) { + messages.value.push(ChatMessage.agentMessage(welcomeMessage)) + } } } @@ -302,7 +308,7 @@ const sidebarClasses = computed(() => [ diff --git a/src/common/src/components/chat/ChatMessage.vue b/src/common/src/components/chat/ChatMessage.vue index ccb6c6b..dd795b5 100644 --- a/src/common/src/components/chat/ChatMessage.vue +++ b/src/common/src/components/chat/ChatMessage.vue @@ -158,7 +158,7 @@ onBeforeUnmount(() => { }) const renderedMsg = computed(() => { - return renderMarkDown(props.message.text, props.message.isComplete, t) + return renderMarkDown(props.message.text, props.message.isComplete, t, messageElement) }) const handleEditFileChange = (files: UploadedFile[]) => { diff --git a/src/common/src/components/common/AgentChatMenu.vue b/src/common/src/components/common/AgentChatMenu.vue index bb11580..462cfe8 100644 --- a/src/common/src/components/common/AgentChatMenu.vue +++ b/src/common/src/components/common/AgentChatMenu.vue @@ -1,14 +1,19 @@ + + diff --git a/src/common/src/components/common/FileInput.vue b/src/common/src/components/common/FileInput.vue index 00e9427..2f1bdfc 100644 --- a/src/common/src/components/common/FileInput.vue +++ b/src/common/src/components/common/FileInput.vue @@ -171,9 +171,12 @@ defineExpose({
-
-
- {{ t('dragDropBrowse', maxFiles === 1 ? maxFiles : 2) }} +
+
+ + + {{ t('supportedFileTypes') }}: {{ formattedFileNames(allowedExtensions) }} | {{ t('maxFileSize') }}: {{ MAX_FILE_SIZE_MB }} MB +
@@ -183,9 +186,6 @@ defineExpose({
-
- {{ t('supportedFileTypes') }}: {{ formattedFileNames(allowedExtensions) }} | {{ t('maxFileSize') }}: {{ MAX_FILE_SIZE_MB }} MB -
@@ -196,7 +196,7 @@ defineExpose({ "filesTooLargeTitle": "Size limit exceeded", "filesTooLarge": "The file size limit is {max} MB. Please reduce the size of the files: {files} and try again.", "dropFilesHere": "Drag and drop your {extensions} files here", - "dragDropBrowse": "Drag and drop @:files here or browse", + "dragDropBrowse": "Drag and drop @:files here or {'<'}span class='underline'>browse{'<'}/span>", "files": "your file | your files", "discardedFiles": "The following files were discarded: {files} (max {max} files allowed)", "unsupportedFilesTitle": "Unsupported file type", @@ -210,7 +210,7 @@ defineExpose({ "filesTooLargeTitle": "Límite de tamaño excedido", "filesTooLarge": "El límite de tamaño por archivo es {max} MB. Por favor, reduzca el tamaño de los archivos: {files} y vuelva a intentarlo.", "dropFilesHere": "Arrastra y suelta @:files {extensions} aquí", - "dragDropBrowse": "Arrastra y suelta @:files aquí o selecciona un archivo", + "dragDropBrowse": "Arrastra y suelta @:files aquí o {'<'}span class='underline'>selecciona un archivo{'<'}/span>", "files": "tu archivo | tus archivos", "discardedFiles": "Los siguientes archivos fueron descartados: {files} (máximo {max} archivos permitidos)", "unsupportedFilesTitle": "Tipo de archivo no soportado", diff --git a/src/common/src/components/common/FlexCard.vue b/src/common/src/components/common/FlexCard.vue index cc8016e..fb22553 100644 --- a/src/common/src/components/common/FlexCard.vue +++ b/src/common/src/components/common/FlexCard.vue @@ -36,4 +36,4 @@ const isInSidebar = computed(() => {
- \ No newline at end of file + diff --git a/src/common/src/components/common/InteractiveInput.vue b/src/common/src/components/common/InteractiveInput.vue index 5d262d7..35ff833 100644 --- a/src/common/src/components/common/InteractiveInput.vue +++ b/src/common/src/components/common/InteractiveInput.vue @@ -22,7 +22,8 @@ const props = withDefaults(defineProps<{ variant?: string; required?: boolean; disabled?: boolean; -}>(), {min:0, max: MAX_INT, maxHeight: 150}); + size?: 'normal' | 'large'; +}>(), {min:0, max: MAX_INT, maxHeight: 150, size: 'normal'}); const emit = defineEmits<{ (e: 'update:modelValue', value: string | number | undefined): void @@ -118,9 +119,14 @@ const inputIsAIGenerating = computed(() => { return props.loading && props.endIcon == "IconWand"; }); +const sizeClasses = computed(() => + !props.rows && props.size === 'large' ? 'h-12 !text-base' : '' +); + const commonInputClasses = computed(() => [ 'w-full py-2 my-1 rounded-xl bg-surface placeholder:text-content-muted', 'outline-1 outline-auxiliar-gray focus:outline-abstracta focus:ring-1 focus:ring-abstracta focus:border-abstracta', + sizeClasses.value, props.startIcon ? 'px-11' : 'px-2', props.endIcon ? 'pr-11' : 'pr-2', inputIsAIGenerating.value && 'animate-glowing', diff --git a/src/common/src/components/common/ToolAuthModalContent.vue b/src/common/src/components/common/ToolAuthModalContent.vue index 44e1aeb..f0e83bb 100644 --- a/src/common/src/components/common/ToolAuthModalContent.vue +++ b/src/common/src/components/common/ToolAuthModalContent.vue @@ -7,7 +7,7 @@ import { buildToolConfigName, findToolIcon } from '@tero/common/utils/toolConfig import { ToolAuthType } from '@tero/common/utils/toolAuth.js' import { IconLoader2 } from '@tabler/icons-vue' -const props = defineProps<{ +const props = defineProps<{ toolId: string authType: ToolAuthType }>() @@ -22,6 +22,19 @@ const token = ref('') const toolName = computed(() => buildToolConfigName(props.toolId)) const toolIcon = computed(() => findToolIcon(props.toolId)) +const tokenLabel = computed(() => { + const toolPrefix = props.toolId.split('-', 1)[0] + if (toolPrefix === 'github') { + return t('tokenLabelGitHub') + } + if (toolPrefix === 'youtrack') { + return t('tokenLabelYouTrack') + } + if (toolPrefix === 'practitest') { + return t('tokenLabelPractiTest') + } + return t('tokenLabel') +}) const onSubmit = () => { if (token.value.trim()) { @@ -55,14 +68,14 @@ const onCancel = () => {
-

{{ t('enterToken') }} +

{{ t('enterToken') }} {{ toolName }}

- + { "completeAuthenticationOrCancel": "Please complete the authentication process in the opened window, or cancel to abort.", "enterToken": "Please enter the token required to use", "tokenLabel": "Token", + "tokenLabelGitHub": "Personal Access Token", + "tokenLabelYouTrack": "Permanent token", + "tokenLabelPractiTest": "Personal API Token", "cancel": "Cancel", "submit": "Submit" }, @@ -100,6 +116,9 @@ const onCancel = () => { "completeAuthenticationOrCancel": "Por favor complete el proceso de autenticación en la ventana abierta, o cancele para abortar.", "enterToken": "Por favor ingrese el token requerido para usar", "tokenLabel": "Token", + "tokenLabelGitHub": "Personal Access Token", + "tokenLabelYouTrack": "Token permanente", + "tokenLabelPractiTest": "Token personal de API", "cancel": "Cancelar", "submit": "Enviar" } diff --git a/src/common/src/types/vite-raw-assets.d.ts b/src/common/src/types/vite-assets.d.ts similarity index 100% rename from src/common/src/types/vite-raw-assets.d.ts rename to src/common/src/types/vite-assets.d.ts diff --git a/src/common/src/utils/chatExport.ts b/src/common/src/utils/chatExport.ts new file mode 100644 index 0000000..95069f8 --- /dev/null +++ b/src/common/src/utils/chatExport.ts @@ -0,0 +1,180 @@ +import { ChatUiMessage, type StatusUpdate } from '../components/chat/ChatMessage.vue' +import { renderMarkDown } from './formatter' +import { escapeHtml } from 'markdown-it/lib/common/utils' + +function formatStatusAction(status: StatusUpdate): string { + const tool = escapeHtml(status.toolName || '') + switch (status.action) { + case 'statusProcessing': + return 'Processing' + case 'preModelHook': + return 'Thinking' + case 'planning': + return 'Planning to run tools' + case 'executingTool': + return `Executing ${tool}` + + (status.step ? ` - ${escapeHtml(status.step)}` : '') + + (status.args != null ? ` with params ${escapeHtml(typeof status.args === 'string' ? status.args : JSON.stringify(status.args))}` : '') + case 'executedTool': + return `Tool ${tool} execution finished` + case 'toolError': + return `Error in tool ${tool}` + default: + return escapeHtml(status.action) + } +} + +function renderFileAttachments(files: { name: string }[]): string { + if (!files || files.length === 0) return '' + const tags = files.map(f => + `${escapeHtml(f.name)}` + ).join(' ') + return `
${tags}
` +} + +function renderStatusStep(status: StatusUpdate, isLast: boolean): string { + const descriptionHtml = status.description?.trim() + ? `
Description: ${escapeHtml(status.description)}
` + : '' + + let resultHtml = '' + if (Array.isArray(status.result) && status.result.length > 0) { + resultHtml = status.result.map(doc => + `
-${escapeHtml(doc)}
` + ).join('') + } else if (typeof status.result === 'string' && status.result.trim()) { + resultHtml = `
Result: ${escapeHtml(status.result)}
` + } + + return `
+
+ + ${!isLast ? '' : ''} +
+
+
${formatStatusAction(status)}
+ ${descriptionHtml} + ${resultHtml} +
+
` +} + +function renderMessage(msg: ChatUiMessage): string { + if (msg.isUser) { + const textHtml = msg.text ? escapeHtml(msg.text).replace(/\n/g, '
') : '' + return `
${textHtml}
${renderFileAttachments(msg.files)}
` + } + const thoughtHtml = msg.statusUpdates?.length + ? `
Thought process
${msg.statusUpdates.map((s, i) => renderStatusStep(s, i === msg.statusUpdates.length - 1)).join('')}
` + : '' + const bodyHtml = renderMarkDown(msg.text, true).replace(/${thoughtHtml}
${bodyHtml}
${renderFileAttachments(msg.files)}
` +} + +export function generateChatHtml({ messages, agentName = 'Agent', chatName = 'Chat', chartImages }: { messages: ChatUiMessage[], agentName?: string, chatName?: string, chartImages?: string[] }): string { + const exportDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) + let messagesHtml = messages.map(renderMessage).join('') + if (chartImages?.length) { + let i = 0 + messagesHtml = messagesHtml.replace( + /
]*><\/div>
]*>[\s\S]*?<\/div>/g, + () => i < chartImages.length ? `` : '' + ) + } + return ` + + + + +${escapeHtml(chatName)} - ${escapeHtml(agentName)} + + + + +
+
+

${escapeHtml(agentName)}

+
${escapeHtml(chatName)} · Exported ${exportDate}
+
+
${messagesHtml}
+
+ +` +} + +export function downloadChatHtml(html: string, filename: string): void { + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }) + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = filename + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) +} + +const CSS = ` +:root { --bg: #f4f4f4; --border: #d9d9d9; --muted: #737475; --text: #1f1f1f; --primary: #754bde; --font: 'Sora', sans-serif; --mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; } +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html { font-family: var(--font); } +body { background: #fff; color: var(--text); line-height: 1.5; } +a { color: var(--primary); text-decoration: underline; font-weight: 600; } +b, strong { font-weight: 500; } + +.chat-container { max-width: 837px; margin: 0 auto; padding: 1rem; } +header { padding: 1rem 0; margin-bottom: 1rem; border-bottom: 1px solid var(--border); color: var(--muted); font-size: 0.875rem; } +header h1 { font-size: 1.25rem; font-weight: 600; color: var(--text); margin-bottom: 0.25rem; } + +.messages { display: flex; flex-direction: column; gap: 0.25rem; } +.message { padding: 0.5rem; } +.user-message { display: flex; flex-direction: column; align-items: flex-end; } +.user-bubble { background: var(--bg); border-radius: 0.75rem; padding: 1rem; max-width: 75%; word-break: break-word; } +.agent-message { display: flex; flex-direction: column; gap: 0.5rem; } +.agent-body { display: flex; flex-direction: column; gap: 0.5rem; overflow-x: auto; } + +.file-attachments { margin-top: 0.5rem; font-size: 0.875rem; color: var(--muted); } +.file-tag { display: inline-block; background: #fff; border: 1px solid var(--border); border-radius: 0.5rem; padding: 0.25rem 0.5rem; margin: 0.125rem; } + +.formatted-text img { max-width: 100%; height: auto; border-radius: 0.5rem; } +.formatted-text { line-height: 1.375; } +.formatted-text hr { display: none; } +.formatted-text h1, .formatted-text h2 { font-size: 1.25rem; margin: 0.625rem 0; } +.formatted-text h3, .formatted-text h4 { margin: 0.125rem 0; font-weight: normal; } +.formatted-text ul, .formatted-text ol { margin-bottom: 0.625rem; } +.formatted-text p { margin: 0.375rem 0; } +.formatted-text li { margin-left: 1rem; margin-top: 0.5rem; margin-bottom: 0.5rem; } +.formatted-text ul li ul li { list-style-type: circle; } +.formatted-text p br { display: block; margin: 0.625rem 0; } +ol.list-decimal { list-style-type: decimal; padding-left: 1.5rem; } +ul.list-disc { list-style-type: disc; padding-left: 0.5rem; } + +code.bg-surface-muted { background: var(--bg); padding: 0.125rem; font-size: 0.875rem; font-family: var(--mono); } + +pre { margin: 0.75rem 0; } +pre > div:first-child { display: flex; align-items: center; padding: 0.5rem; background: var(--bg); border: 1px solid var(--border); border-bottom: none; border-radius: 0.5rem 0.5rem 0 0; } +pre > div:first-child > span { font-size: 0.75rem; text-transform: lowercase; color: var(--text); font-family: var(--font); } +pre > code { display: block; padding: 0.5rem; background: var(--bg); border: 1px solid var(--border); border-radius: 0 0 0.5rem 0.5rem; overflow-x: auto; font-family: var(--mono); font-size: 0.875rem; line-height: 1.5; } +.copy-code-btn { display: none; } + +table { border-collapse: collapse; width: 100%; } +td, th { border: 1px solid var(--border); padding: 0.5rem 1rem; word-break: break-word; } +th { background: var(--bg); font-weight: 500; } +.overflow-x-auto { overflow-x: auto; } +button[onclick="downloadTableCSV(this)"] { display: none !important; } + +blockquote { display: flex; gap: 0.75rem; margin: 0.5rem 0; padding: 1.5rem; border-left: 8px solid var(--bg); } + +.status-container { margin-bottom: 0.5rem; border-bottom: 1px solid var(--border); overflow: hidden; } +.status-header { padding: 0.5rem 0; font-size: 0.875rem; font-weight: 500; color: var(--muted); cursor: pointer; user-select: none; list-style: none; display: flex; align-items: center; justify-content: space-between; } +.status-header::-webkit-details-marker { display: none; } +.status-header::after { content: '▾'; display: inline-block; margin-left: 0.5rem;} +details[open] > .status-header::after { transform: rotate(180deg); } +.status-steps { padding-top: 0.25rem; } +.status-step { display: flex; align-items: stretch; gap: 0.25rem; font-size: 0.875rem; min-width: 0; } +.status-content { min-width: 0; overflow-wrap: break-word; word-break: break-word; padding-bottom: 0.75rem; } +.status-dot-col { display: flex; flex-direction: column; align-items: center; flex-shrink: 0; color: var(--muted); } +.status-line { width: 2px; flex: 1; background: var(--border); } + +.hljs{color:#2f3337;background:var(--bg)}.hljs-subst{color:#2f3337}.hljs-comment{color:#656e77}.hljs-attr,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-section,.hljs-selector-tag{color:#015692}.hljs-attribute{color:#803378}.hljs-name,.hljs-number,.hljs-quote,.hljs-selector-id,.hljs-template-tag,.hljs-type{color:#b75501}.hljs-selector-class{color:#015692}.hljs-link,.hljs-regexp,.hljs-selector-attr,.hljs-string,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#54790d}.hljs-meta,.hljs-selector-pseudo{color:#015692}.hljs-built_in,.hljs-literal,.hljs-title{color:#b75501}.hljs-bullet,.hljs-code{color:#535a60}.hljs-meta .hljs-string{color:#54790d}.hljs-deletion{color:#c02d2e}.hljs-addition{color:#2f6f44}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} +` diff --git a/src/common/src/utils/formatter.ts b/src/common/src/utils/formatter.ts index e536a88..b0ed52a 100644 --- a/src/common/src/utils/formatter.ts +++ b/src/common/src/utils/formatter.ts @@ -1,4 +1,4 @@ -import { nextTick, ref } from 'vue' +import { ref, nextTick, type Ref } from 'vue' import MarkdownIt from 'markdown-it' import MarkdownItPlantuml from 'markdown-it-plantuml' import Token from 'markdown-it/lib/token' @@ -12,17 +12,17 @@ import * as echarts from 'echarts' import * as XLSX from 'xlsx' import { saveAs } from 'file-saver' -var chart: any var prevWidth: number = 0 const messageElement = ref(null) const CSV_FILENAME = 'table.csv' const onResize = () => { - if (messageElement.value!.scrollWidth != prevWidth && chart) { - prevWidth = messageElement.value!.scrollWidth - chart.resize() - } + if (!messageElement.value || messageElement.value.scrollWidth === prevWidth) return + prevWidth = messageElement.value.scrollWidth + messageElement.value.querySelectorAll('.echarts').forEach(div => { + echarts.getInstanceByDom(div)?.resize() + }) } const resizeObserver: ResizeObserver = new ResizeObserver(onResize) @@ -119,18 +119,6 @@ const useEcharts = (md: MarkdownIt) => { const token = tokens[idx] const code = token.content.trim() if (token.info === 'echarts') { - nextTick().then(() => { - const container = messageElement.value! - const chartDiv = container.querySelector('.echarts') - const chartData = container.querySelector('.echarts-data') - if (chartDiv && chartData) { - chart = echarts.init(chartDiv as HTMLDivElement) - const options = JSON.parse(chartData.textContent || '') - if (options.xAxis?.axisLabel) solveEchartsFormatter(options.xAxis.axisLabel) - if (options.xAxis?.axisPointer?.label) solveEchartsFormatter(options.xAxis.axisPointer.label) - chart.setOption(options) - } - }) return `
` } return defaultRender(tokens, idx, options, env, self) @@ -155,7 +143,7 @@ const formatEpoch = (config: any): ((value: any) => string) => { } } -export const renderMarkDown = (text: string, isComplete: boolean, t?: (key: string) => string) => { +export const renderMarkDown = (text: string, isComplete: boolean, t?: (key: string) => string, container?: Ref) => { let md = new MarkdownIt({ breaks: true, highlight: function (str, lang) { @@ -191,15 +179,36 @@ export const renderMarkDown = (text: string, isComplete: boolean, t?: (key: stri md.use(listDecimalPlugin) md.use(listBulletPlugin) md.use(blockquotePlugin) - return md.render(text) + const html = md.render(text) + if (container) nextTick().then(() => { if (container.value) initializeEchartsIn(container.value) }) + return html +} + +export const initializeEchartsIn = (container: HTMLElement) => { + container.querySelectorAll('.echarts').forEach(chartDiv => { + const chartData = chartDiv.nextElementSibling + if (!chartData) return + let options + try { + options = JSON.parse(chartData.textContent || '') + } catch { + return + } + if (!options) return + const existing = echarts.getInstanceByDom(chartDiv) + const chart = existing ?? echarts.init(chartDiv) + if (options.xAxis?.axisLabel) solveEchartsFormatter(options.xAxis.axisLabel) + if (options.xAxis?.axisPointer?.label) solveEchartsFormatter(options.xAxis.axisPointer.label) + chart.setOption(options) + }) } export const initializeResizeObserver = (element: HTMLElement | null): (() => void) => { messageElement.value = element - if (messageElement.value) resizeObserver.observe(messageElement.value) + if (element) resizeObserver.observe(element) return () => { - if (messageElement.value) resizeObserver.unobserve(messageElement.value) - messageElement.value = null + if (element) resizeObserver.unobserve(element) + if (messageElement.value === element) messageElement.value = null } } diff --git a/src/common/src/utils/toolAuth.ts b/src/common/src/utils/toolAuth.ts index 5b80871..faf6474 100644 --- a/src/common/src/utils/toolAuth.ts +++ b/src/common/src/utils/toolAuth.ts @@ -90,7 +90,7 @@ export const toolAuthManager = new ToolAuthManager(); export interface AuthFlowHandlers { isHttpError: (e: unknown) => e is HttpErrorLike; handleOAuthFlow: (request: OAuthRequest) => Promise; - completeAuthToken: (toolId: string, agentId: number, token: string) => Promise; + completeAuthToken?: (toolId: string, agentId: number, token: string) => Promise; } export const createAuthFlowHandler = (handlers: AuthFlowHandlers) => { @@ -104,7 +104,7 @@ export const createAuthFlowHandler = (handlers: AuthFlowHandlers) => { throw new AuthenticationError(result.cancelled ? 'authenticationCancelled' : 'authenticationAccessDenied'); } - await handlers.completeAuthToken(request.toolId, request.agentId, result.token); + await handlers.completeAuthToken!(request.toolId, request.agentId, result.token); }; return async (fn: () => Promise): Promise => { @@ -120,6 +120,9 @@ export const createAuthFlowHandler = (handlers: AuthFlowHandlers) => { if (authRequest instanceof OAuthRequest) { await handlers.handleOAuthFlow(authRequest); } else if (authRequest instanceof AuthTokenRequest) { + if (!handlers.completeAuthToken) { + throw e; + } await authTokenFlow(authRequest); } continue; diff --git a/src/common/src/utils/toolConfig.ts b/src/common/src/utils/toolConfig.ts index 572bde2..305b3f0 100644 --- a/src/common/src/utils/toolConfig.ts +++ b/src/common/src/utils/toolConfig.ts @@ -1,8 +1,12 @@ -import { IconBrain, IconQuestionMark, IconWorldWww, IconDeviceDesktopBolt, type Icon } from '@tabler/icons-vue' +import { IconBrain, IconQuestionMark, IconWorld, IconDeviceDesktopBolt, type Icon } from '@tabler/icons-vue' import { h, type SVGAttributes } from 'vue' import mcpIcon from '../assets/images/mcp-icon.svg' import jiraIcon from '../assets/images/jira-icon.svg' +import redmineIcon from '../assets/images/redmine-icon.svg' +import githubIcon from '../assets/images/github-icon.svg' +import youtrackIcon from '../assets/images/youtrack-icon.svg' +import practitestIcon from '../assets/images/practitest-tool.svg' const iconFromImage = (imageSrc: string): Icon => { return (props: SVGAttributes) => { @@ -20,10 +24,14 @@ const iconFromImage = (imageSrc: string): Icon => { const toolIcons: Record = { docs: IconBrain, + web: IconWorld, 'mcp-*': iconFromImage(mcpIcon), jira: iconFromImage(jiraIcon), - browser: IconDeviceDesktopBolt, - web: IconWorldWww + github: iconFromImage(githubIcon), + redmine: iconFromImage(redmineIcon), + youtrack: iconFromImage(youtrackIcon), + practitest: iconFromImage(practitestIcon), + browser: IconDeviceDesktopBolt } export const defaultToolNames: Record = { @@ -31,11 +39,13 @@ export const defaultToolNames: Record = { web: 'Web', mcp: 'MCP', jira: 'Jira', + github: 'GitHub', + youtrack: 'YouTrack', + practitest: 'PractiTest', + redmine: 'Redmine', browser: 'Browser' } -export const defaultToolNamesOrder = Object.values(defaultToolNames) - export const findToolIcon = (toolId: string): Icon => { const key = toolId.includes('-') ? toolId.split('-', 1)[0] + '-*' : toolId return toolIcons[key] || IconQuestionMark diff --git a/src/frontend/src/assets/images/anthropic.svg b/src/frontend/src/assets/images/anthropic.svg deleted file mode 100644 index 497bb50..0000000 --- a/src/frontend/src/assets/images/anthropic.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/src/frontend/src/assets/images/gemini.svg b/src/frontend/src/assets/images/gemini.svg deleted file mode 100644 index 175ec80..0000000 --- a/src/frontend/src/assets/images/gemini.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/frontend/src/assets/images/jira-icon.svg b/src/frontend/src/assets/images/jira-icon.svg deleted file mode 100644 index 63917a8..0000000 --- a/src/frontend/src/assets/images/jira-icon.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/frontend/src/assets/images/mcp-icon.svg b/src/frontend/src/assets/images/mcp-icon.svg deleted file mode 100644 index 6bc5a93..0000000 --- a/src/frontend/src/assets/images/mcp-icon.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/frontend/src/assets/images/open-ai.svg b/src/frontend/src/assets/images/open-ai.svg deleted file mode 100644 index 06937f4..0000000 --- a/src/frontend/src/assets/images/open-ai.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/frontend/src/assets/images/qwen.svg b/src/frontend/src/assets/images/qwen.svg deleted file mode 100644 index 0b71145..0000000 --- a/src/frontend/src/assets/images/qwen.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/src/frontend/src/assets/images/status-icon.svg.rej b/src/frontend/src/assets/images/status-icon.svg.rej deleted file mode 100644 index 7ba3aca..0000000 --- a/src/frontend/src/assets/images/status-icon.svg.rej +++ /dev/null @@ -1,8 +0,0 @@ -@@ -1,6 +1,6 @@ - -+ class="text-content-muted"> - - diff --git a/src/frontend/src/components/agent/AgentAdvancedSettingsDialog.vue b/src/frontend/src/components/agent/AgentAdvancedSettingsDialog.vue deleted file mode 100644 index f69eff5..0000000 --- a/src/frontend/src/components/agent/AgentAdvancedSettingsDialog.vue +++ /dev/null @@ -1,71 +0,0 @@ - - -
-
- - | {{ t('advancedSettingsTitle') }} - -
- - - -
- -
-
- -

{{ t('recursionLimitHelp') }}

- -
-
- - - - - - -{ - "en": { - "advancedSettingsTitle": "Advanced settings", - "recursionLimit": "Thought process steps limit", - "recursionLimitHelp": "Higher values increase budget usage and response time. Before increasing, optimize your agent to use fewer steps." - }, - "es": { - "advancedSettingsTitle": "Configuración avanzada", - "recursionLimit": "Límite de pasos del proceso de pensamiento", - "recursionLimitHelp": "Valores más altos aumentan el uso de presupuesto y el tiempo de respuesta. Antes de aumentar, optimiza tu agente para usar menos pasos." - } -} - diff --git a/src/frontend/src/components/agent/AgentCollapsibleSection.vue b/src/frontend/src/components/agent/AgentCollapsibleSection.vue new file mode 100644 index 0000000..af96f16 --- /dev/null +++ b/src/frontend/src/components/agent/AgentCollapsibleSection.vue @@ -0,0 +1,46 @@ + + + + + diff --git a/src/frontend/src/components/agent/AgentConversationStarters.vue b/src/frontend/src/components/agent/AgentConversationStarters.vue index 5dcc425..a3db410 100644 --- a/src/frontend/src/components/agent/AgentConversationStarters.vue +++ b/src/frontend/src/components/agent/AgentConversationStarters.vue @@ -65,7 +65,7 @@ const saveStarter = async (p: AgentPrompt) => {