From 85175b44003c40de252fd2dcbdcd80a67485b003 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Wed, 4 Mar 2026 15:24:23 -0700 Subject: [PATCH 01/13] fix: add content for intermediate mcp tutorial --- .copywrite.hcl | 2 + .gitignore | 13 + Taskfile.yml | 37 + .../integrate-palette-mcp/.python-version | 1 + .../integrate-palette-mcp/README.md | 3 + .../integrate-palette-mcp/Taskfile.yaml | 11 + .../integrate-palette-mcp/agents/__init__.py | 3 + .../agents/active_cluster_agent.py | 103 ++ .../agents/palette_profile_agent.py | 107 ++ .../agents/reporter_agent.py | 73 + .../agents/tagging_agent.py | 93 ++ .../integrate-palette-mcp/helpers.py | 149 ++ ai/palette-mcp/integrate-palette-mcp/main.py | 152 ++ .../integrate-palette-mcp/pyproject.toml | 11 + ai/palette-mcp/integrate-palette-mcp/tools.py | 224 +++ ai/palette-mcp/integrate-palette-mcp/uv.lock | 1431 +++++++++++++++++ .../manifests/wordpress-default.yaml | 3 + .../manifests/wordpress-variables.yaml | 3 + 18 files changed, 2419 insertions(+) create mode 100644 Taskfile.yml create mode 100644 ai/palette-mcp/integrate-palette-mcp/.python-version create mode 100644 ai/palette-mcp/integrate-palette-mcp/README.md create mode 100644 ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml create mode 100644 ai/palette-mcp/integrate-palette-mcp/agents/__init__.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/helpers.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/main.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/pyproject.toml create mode 100644 ai/palette-mcp/integrate-palette-mcp/tools.py create mode 100644 ai/palette-mcp/integrate-palette-mcp/uv.lock diff --git a/.copywrite.hcl b/.copywrite.hcl index 2401352..e8676dc 100644 --- a/.copywrite.hcl +++ b/.copywrite.hcl @@ -11,5 +11,7 @@ project { header_ignore = [ # "vendors/**", # "**autogen**", + ".venv", + "__pycache__" ] } \ No newline at end of file diff --git a/.gitignore b/.gitignore index db0ecd1..50ececf 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,16 @@ my.*.tfvars *.env *.kubeconfig +## Python + +__pycache__/ +*.pyc +*.pyo +*.pyd +*.pyw +*.pyz +*.pywz +*.pyzw +*.pyzwz + +.venv/ \ No newline at end of file diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..32210aa --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,37 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +version: "3" + +tasks: + init: + desc: Install dependencies and setup the project + cmds: + - echo "initializing npm dependencies" + - npm ci + - npx husky install + + help: + desc: Display this help + cmds: + - task --list + + build-docker: + desc: Build docker image + cmds: + - echo "Building docker image" + - | + docker build --build-arg PALETTE_VERSION=$PALETTE_VERSION \ + --build-arg PALETTE_CLI_VERSION=$PALETTE_CLI_VERION \ + --build-arg PALETTE_EDGE_VERSION=$PALETTE_EDGE_VERSION \ + --build-arg PACKER_VERSION=$PACKER_VERSION \ + --build-arg ORAS_VERSION=$ORAS_VERSION \ + --build-arg TERRAFORM_VERSION=$TERRAFORM_VERSION \ + --build-arg K9S_VERSION=$K9S_VERSION \ + -t tutorials . + + license: + desc: Adds a license header to all files. Reference https://github.com/hashicorp/copywrite to learn more. + cmds: + - echo "Applying license headers..." + - copywrite headers diff --git a/ai/palette-mcp/integrate-palette-mcp/.python-version b/ai/palette-mcp/integrate-palette-mcp/.python-version new file mode 100644 index 0000000..e4fba21 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/.python-version @@ -0,0 +1 @@ +3.12 diff --git a/ai/palette-mcp/integrate-palette-mcp/README.md b/ai/palette-mcp/integrate-palette-mcp/README.md new file mode 100644 index 0000000..6c5db57 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/README.md @@ -0,0 +1,3 @@ +# Integrate Palette MCP + +This folder contains the demo code for the Integrate Palette MCP tutorial. The user will learn how to integrate Palette MCP into a LangChain agent workflow. diff --git a/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml b/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml new file mode 100644 index 0000000..d0a53fc --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml @@ -0,0 +1,11 @@ +version: "3" + +dotenv: [".env", "../../../.env"] + +tasks: + start-agent: + desc: Start the Palette MCP LangChain agent + env: + DEBUG: "info" + cmds: + - uv run python main.py {{.CLI_ARGS}} diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py b/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py new file mode 100644 index 0000000..fb97b30 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py @@ -0,0 +1,3 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py new file mode 100644 index 0000000..89dae25 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -0,0 +1,103 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Palette-focused agent for active cluster mapping.""" + +from __future__ import annotations + +import importlib +from typing import Any + +from helpers import build_palette_server_config, extract_text_response, suppress_console_output + +ACTIVE_CLUSTER_SYSTEM_PROMPT = ( + "You are a Palette active-cluster mapping specialist. " + "Use only Palette MCP tools to identify active clusters that use a provided set of cluster profile UIDs. " + "Return factual results only." +) + +async def initialize_active_cluster_agent( + model: str, + debug_level: str, + default_env_file: str, + default_kubeconfig_dir: str, + default_mcp_image: str, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient + + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=default_env_file, + default_kubeconfig_dir=default_kubeconfig_dir, + default_mcp_image=default_mcp_image, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) + + +async def run_active_cluster_agent( + agent: Any, + pack_name: str, + matched_profiles_output: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + active_cluster_prompt = ( + f"Given this profile discovery result for pack '{pack_name}':\n" + f"{matched_profiles_output}\n\n" + "Required process:\n" + "1) Extract matched profile UIDs from the input JSON.\n" + "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" + "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" + "4) Match clusters using explicit profile UID fields only.\n" + "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" + "Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + ' "target_profile_uids": ["", ""],\n' + ' "total_active_clusters_scanned": ,\n' + ' "active_clusters_using_matched_profiles": [\n' + " {\n" + ' "uid": "",\n' + ' "name": "",\n' + ' "cluster_profile_uid": "",\n' + ' "cluster_profile_name": "",\n' + ' "evidence_field_path": "",\n' + ' "evidence": ""\n' + " }\n" + " ],\n" + ' "checked_active_cluster_uids": ["", ""],\n' + ' "notes": ""\n' + "}\n" + ) + run_config = { + "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": active_cluster_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py new file mode 100644 index 0000000..ccee0eb --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -0,0 +1,107 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Palette-focused agent for cluster profile discovery.""" + +from __future__ import annotations + +import importlib +from typing import Any + +from helpers import ( + build_palette_server_config, + extract_text_response, + suppress_console_output, +) + +PROFILE_FINDER_SYSTEM_PROMPT = ( + "You are a Palette profile discovery specialist. " + "Use only Palette MCP tools to find cluster profiles that include a target pack. " + "First list cluster profiles, then inspect details when needed. " + "Always capture and return cluster profile scope. " + "Return factual results only." +) + +async def initialize_profile_finder_agent( + model: str, + debug_level: str, + default_env_file: str, + default_kubeconfig_dir: str, + default_mcp_image: str, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient + + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=default_env_file, + default_kubeconfig_dir=default_kubeconfig_dir, + default_mcp_image=default_mcp_image, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) + + +async def run_profile_finder_agent( + agent: Any, + pack_name: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + profile_finder_prompt = ( + "Find all cluster profiles in Palette that use the pack named " + f"'{pack_name}'. Use Palette MCP tools only.\n\n" + "Required process:\n" + "1) Call gather_or_delete_clusterprofiles with action='list'.\n" + "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" + "3) Match pack name case-insensitively.\n" + "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" + "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" + "Important:\n" + "- Return only profile-level results. Do not query clusters in this agent.\n\n" + "Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + ' "total_profiles_scanned": ,\n' + ' "matched_profiles": [\n' + " {\n" + ' "uid": "",\n' + ' "name": "",\n' + ' "scope": "",\n' + ' "pack_references": ["", ""],\n' + ' "evidence": ""\n' + " }\n" + " ],\n" + ' "notes": ""\n' + "}\n" + ) + run_config = { + "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": profile_finder_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py new file mode 100644 index 0000000..02d348a --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -0,0 +1,73 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Reporter agent that formats discovery output for humans.""" + +from __future__ import annotations + +import importlib +from typing import Any + +from helpers import extract_text_response + +REPORTER_SYSTEM_PROMPT = ( + "You are a reporting agent. Produce clear, structured, concise reports. " + "Do not invent data. If discovery data is uncertain, call that out clearly." +) + +async def initialize_reporter_agent(model: str) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=[], + system_prompt=REPORTER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) + + +async def run_reporter_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + tagging_output: str, + run_id: str, +) -> str: + reporter_prompt = ( + f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" + "Use these outputs as the source of truth:\n\n" + "Profile discovery output:\n" + f"{profile_discovery_output}\n\n" + "Active cluster mapping output:\n" + f"{active_cluster_output}\n\n" + "Tagging output:\n" + f"{tagging_output}\n\n" + "Output format:\n" + "1) Summary (1-2 sentences)\n" + "2) Matching cluster profiles (bullet list with uid, name, and evidence)\n" + "3) Active clusters using the matched cluster profiles (bullet list with uid, name, cluster profile uid, and cluster profile name)\n" + "4) Tagging results for clusters and cluster profiles (what was tagged, success/failure)\n" + " Include a separate list of skipped cluster profiles with reason (for example, scope=system).\n" + "5) Notes and caveats\n" + "If active cluster data appears incomplete or unchecked, explicitly say the result may be incomplete.\n" + "If there are no matching cluster profiles, return 'No matching cluster profiles found' in the summary and omit the rest of the output." + ) + run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} + result = await agent.ainvoke( + { + "messages": [ + { + "role": "user", + "content": reporter_prompt, + } + ] + }, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py new file mode 100644 index 0000000..7ec6e12 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -0,0 +1,93 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Agent that tags matched active clusters for review.""" + +from __future__ import annotations + +import importlib +from typing import Any + +from helpers import extract_text_response +from tools import tag_cluster_for_review, tag_cluster_profile_for_review + +TAGGING_SYSTEM_PROMPT = ( + "You are a cluster tagging specialist. " + "Your only job is to apply review tags to active clusters and matched cluster profiles provided in the input. " + "Never tag cluster profiles with scope='system'. " + "Use the available tagging tools and do not invent UIDs." +) + + +async def initialize_tagging_agent(model: str) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=[tag_cluster_for_review, tag_cluster_profile_for_review], + system_prompt=TAGGING_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) + + +async def run_tagging_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + run_id: str, +) -> str: + tagging_prompt = ( + f"Given this profile discovery output for pack '{pack_name}':\n" + f"{profile_discovery_output}\n\n" + f"Given this active cluster mapping output for pack '{pack_name}':\n" + f"{active_cluster_output}\n\n" + "Task:\n" + "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" + "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" + "3) For each cluster UID, call tag_cluster_for_review once.\n" + "4) For each cluster profile UID, call tag_cluster_profile_for_review once only if scope is not 'system'.\n" + "5) For scope='system' profiles, skip tagging and record skip reason.\n" + "5) Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + ' "requested_tags": ["nginx:present", "review:required"],\n' + ' "clusters_attempted": ["", ""],\n' + ' "cluster_profiles_attempted": ["", ""],\n' + ' "cluster_results": [\n' + " {\n" + ' "cluster_uid": "",\n' + ' "tool_call_status": "",\n' + ' "tool_output": ""\n' + " }\n" + " ],\n" + ' "cluster_profile_results": [\n' + " {\n" + ' "cluster_profile_uid": "",\n' + ' "scope": "",\n' + ' "tool_call_status": "",\n' + ' "tool_output": ""\n' + " }\n" + " ],\n" + ' "cluster_profile_skipped": [\n' + " {\n" + ' "cluster_profile_uid": "",\n' + ' "scope": "system",\n' + ' "reason": "scope system is not taggable"\n' + " }\n" + " ],\n" + ' "notes": ""\n' + "}\n" + "If there are no resources to tag, return empty arrays and explain in notes." + ) + run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": tagging_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py new file mode 100644 index 0000000..4752d00 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -0,0 +1,149 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Helper functions for the Palette MCP tutorial agent.""" + +from __future__ import annotations + +import asyncio +import contextlib +import os +import shutil +import sys +from collections.abc import Awaitable +from typing import Any, TextIO + +VALID_DEBUG_LEVELS = {"warn", "info", "verbose"} + + +def get_debug_level() -> str: + debug_value = os.getenv("DEBUG") or os.getenv("debug") or "warn" + debug_level = debug_value.strip().lower() + if debug_level not in VALID_DEBUG_LEVELS: + print(f"Warning: invalid DEBUG='{debug_value}'. Falling back to 'warn'.") + return "warn" + return debug_level + + +def is_debug_enabled(current_level: str, min_level: str) -> bool: + levels = {"warn": 1, "info": 2, "verbose": 3} + return levels[current_level] >= levels[min_level] + + +@contextlib.contextmanager +def suppress_console_output(enabled: bool) -> Any: + """Temporarily suppress process stdout/stderr.""" + if not enabled: + yield + return + + devnull = open(os.devnull, "w", encoding="utf-8") + old_stdout_fd = os.dup(1) + old_stderr_fd = os.dup(2) + try: + os.dup2(devnull.fileno(), 1) + os.dup2(devnull.fileno(), 2) + yield + finally: + os.dup2(old_stdout_fd, 1) + os.dup2(old_stderr_fd, 2) + os.close(old_stdout_fd) + os.close(old_stderr_fd) + devnull.close() + + +def ensure_local_prerequisites( + require_kubectl: bool = True, + require_curl: bool = False, +) -> None: + if shutil.which("docker") is None: + raise RuntimeError("Docker is not available in PATH.") + if require_kubectl and shutil.which("kubectl") is None: + raise RuntimeError("kubectl is not available in PATH.") + if require_curl and shutil.which("curl") is None: + raise RuntimeError("curl is not available in PATH.") + if not os.getenv("OPENAI_API_KEY"): + raise RuntimeError("OPENAI_API_KEY is not set.") + + +def build_palette_server_config( + default_env_file: str, + default_kubeconfig_dir: str, + default_mcp_image: str, +) -> dict[str, dict[str, Any]]: + env_file = os.getenv("PALETTE_MCP_ENV_FILE", default_env_file) + kubeconfig_dir = os.getenv("PALETTE_MCP_KUBECONFIG_DIR", default_kubeconfig_dir) + mcp_image = os.getenv("PALETTE_MCP_IMAGE", default_mcp_image) + + return { + "palette": { + "transport": "stdio", + "command": "docker", + "args": [ + "run", + "--rm", + "-i", + "--mount", + f"type=bind,source={kubeconfig_dir},target=/tmp/kubeconfig", + "--env-file", + env_file, + mcp_image, + ], + } + } + + +def extract_text_response(result: dict[str, Any]) -> str: + """Best-effort extraction of final agent text from LangChain result payload.""" + messages = result.get("messages") + if isinstance(messages, list): + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + if isinstance(content, list): + text_parts = [ + part.get("text", "") + for part in content + if isinstance(part, dict) and part.get("type") == "text" + ] + joined = "\n".join(part for part in text_parts if part.strip()).strip() + if joined: + return joined + + return str(result) + + +def _get_status_stream() -> tuple[TextIO, bool]: + """Use terminal directly so status remains visible when stdout is suppressed.""" + try: + return open("/dev/tty", "w", encoding="utf-8"), True + except OSError: + return sys.stdout, False + + +async def _thinking_indicator(stream: TextIO, interval_seconds: int = 2) -> None: + """Show a simple progress line while the agent is working.""" + frames = ["Thinking ...", "Thinking ....", "Thinking ....."] + index = 0 + while True: + message = frames[index % len(frames)] + stream.write(f"\r{message}") + stream.flush() + index += 1 + await asyncio.sleep(interval_seconds) + + +async def run_with_thinking_indicator(work: Awaitable[str]) -> str: + stream, should_close = _get_status_stream() + indicator_task = asyncio.create_task(_thinking_indicator(stream)) + try: + return await work + finally: + indicator_task.cancel() + with contextlib.suppress(asyncio.CancelledError): + await indicator_task + stream.write("\r" + (" " * 40) + "\r") + stream.flush() + if should_close: + stream.close() diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py new file mode 100644 index 0000000..7eb3076 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -0,0 +1,152 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""One-shot workflow runner for Palette finder + reporter agents.""" + +from __future__ import annotations + +import argparse +import asyncio +import os +import sys +import uuid + +from helpers import ( + ensure_local_prerequisites, + get_debug_level, + is_debug_enabled, + run_with_thinking_indicator, +) +from agents.active_cluster_agent import ( + initialize_active_cluster_agent, + run_active_cluster_agent, +) +from agents.palette_profile_agent import ( + initialize_profile_finder_agent, + run_profile_finder_agent, +) +from agents.reporter_agent import initialize_reporter_agent, run_reporter_agent +from agents.tagging_agent import initialize_tagging_agent, run_tagging_agent + +DEFAULT_MODEL = "gpt-5-nano-2025-08-07" +DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" +DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") +DEFAULT_KUBECONFIG_DIR = os.path.expanduser("~/projects/spectro-cloud/mcp-kubeconfig") +DEFAULT_PACK_NAME = "nginx" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="One-shot workflow: Palette profile finder agent + reporter agent." + ) + parser.add_argument( + "--pack", + default=os.getenv("PACK_NAME", DEFAULT_PACK_NAME), + help=f"Target pack name to search in cluster profiles (default: {DEFAULT_PACK_NAME}).", + ) + parser.add_argument( + "--model", + default=os.getenv("OPENAI_MODEL", DEFAULT_MODEL), + help=f"Model for the profile finder agent (default: {DEFAULT_MODEL}).", + ) + parser.add_argument( + "--active-cluster-model", + default=os.getenv("OPENAI_ACTIVE_CLUSTER_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), + help="Model for the active cluster finder agent (default: OPENAI_ACTIVE_CLUSTER_MODEL or OPENAI_MODEL).", + ) + parser.add_argument( + "--reporter-model", + default=os.getenv("OPENAI_REPORTER_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), + help="Model for the reporter agent (default: OPENAI_REPORTER_MODEL or OPENAI_MODEL).", + ) + parser.add_argument( + "--tagging-model", + default=os.getenv("OPENAI_TAGGING_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), + help="Model for the tagging agent (default: OPENAI_TAGGING_MODEL or OPENAI_MODEL).", + ) + return parser.parse_args() + + +async def main_async() -> None: + args = parse_args() + run_id = uuid.uuid4().hex[:8] + debug_level = get_debug_level() + ensure_local_prerequisites(require_kubectl=False, require_curl=True) + + if is_debug_enabled(debug_level, "info"): + print(f"Debug level: {debug_level}") + print(f"Run ID: {run_id}") + print("Initializing profile finder agent...") + + profile_finder_agent = await initialize_profile_finder_agent( + model=args.model, + debug_level=debug_level, + default_env_file=DEFAULT_ENV_FILE, + default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, + default_mcp_image=DEFAULT_MCP_IMAGE, + ) + active_cluster_agent = await initialize_active_cluster_agent( + model=args.active_cluster_model, + debug_level=debug_level, + default_env_file=DEFAULT_ENV_FILE, + default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, + default_mcp_image=DEFAULT_MCP_IMAGE, + ) + tagging_agent = await initialize_tagging_agent(model=args.tagging_model) + reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + + if is_debug_enabled(debug_level, "info"): + print(f"Running profile discovery for pack: {args.pack}") + + profile_discovery_output = await run_with_thinking_indicator( + run_profile_finder_agent(profile_finder_agent, args.pack, debug_level, run_id) + ) + + if is_debug_enabled(debug_level, "info"): + print("Finding active clusters using matched profiles...") + + active_cluster_output = await run_with_thinking_indicator( + run_active_cluster_agent( + active_cluster_agent, + args.pack, + profile_discovery_output, + debug_level, + run_id, + ) + ) + + if is_debug_enabled(debug_level, "info"): + print("Tagging active clusters...") + + tagging_output = await run_with_thinking_indicator( + run_tagging_agent( + tagging_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + run_id, + ) + ) + + if is_debug_enabled(debug_level, "info"): + print("Formatting report...") + + final_report = await run_with_thinking_indicator( + run_reporter_agent( + reporter_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + tagging_output, + run_id, + ) + ) + print(final_report) + +if __name__ == "__main__": + try: + asyncio.run(main_async()) + except KeyboardInterrupt: + print("\nExiting.") + except Exception as exc: # Keep tutorial errors readable. + print(f"Error: {exc}", file=sys.stderr) diff --git a/ai/palette-mcp/integrate-palette-mcp/pyproject.toml b/ai/palette-mcp/integrate-palette-mcp/pyproject.toml new file mode 100644 index 0000000..eefcbf7 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/pyproject.toml @@ -0,0 +1,11 @@ +[project] +name = "integrate-palette-mcp" +version = "0.1.0" +description = "Add your description here" +readme = "README.md" +requires-python = ">=3.12" +dependencies = [ + "langchain>=1.2.10", + "langchain-mcp-adapters>=0.2.1", + "langchain-openai>=1.1.10", +] diff --git a/ai/palette-mcp/integrate-palette-mcp/tools.py b/ai/palette-mcp/integrate-palette-mcp/tools.py new file mode 100644 index 0000000..e962272 --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/tools.py @@ -0,0 +1,224 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + +"""Local LangChain tools used by the tutorial agent.""" + +from __future__ import annotations + +import os +import json +import re +import shlex +import subprocess +from typing import Any + +from langchain.tools import tool + + +@tool +def kubectl(command: str) -> str: + """Run kubectl locally and return stdout, stderr, and return code.""" + if not command.strip(): + return "STDOUT:\n\nSTDERR:\nMissing kubectl command.\nRC: 2" + + try: + args = shlex.split(command) + except ValueError as exc: + return f"STDOUT:\n\nSTDERR:\nInvalid command syntax: {exc}\nRC: 2" + + try: + result = subprocess.run( + ["kubectl", *args], + capture_output=True, + text=True, + timeout=30, + check=False, + ) + except subprocess.TimeoutExpired: + return "STDOUT:\n\nSTDERR:\nkubectl command timed out after 30s.\nRC: 124" + except OSError as exc: + return f"STDOUT:\n\nSTDERR:\nFailed to execute kubectl: {exc}\nRC: 127" + + return ( + f"STDOUT:\n{result.stdout.rstrip()}\n\n" + f"STDERR:\n{result.stderr.rstrip()}\n" + f"RC: {result.returncode}" + ) + + +def _read_env_file(path: str) -> dict[str, str]: + values: dict[str, str] = {} + if not os.path.isfile(path): + return values + + with open(path, encoding="utf-8") as env_file: + for raw_line in env_file: + line = raw_line.strip() + if not line or line.startswith("#"): + continue + if line.startswith("export "): + line = line[len("export ") :].strip() + if "=" not in line: + continue + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip('"').strip("'") + if key: + values[key] = value + + return values + + +def _resolve_palette_env_file_path() -> str: + return os.path.expanduser("~/.palette/.env-mcp") + + +def _normalize_key(key: str) -> str: + normalized = key.strip().upper() + return "".join(ch for ch in normalized if ch.isalnum()) + + +def _first_non_empty(source: dict[str, str], keys: list[str]) -> str: + normalized_source = {_normalize_key(k): v for k, v in source.items()} + for key in keys: + value = normalized_source.get(_normalize_key(key), "") + if value.strip(): + return value.strip() + return "" + + +def _resolve_palette_credentials() -> tuple[str, str, str]: + project_uid_keys = [ + "SPECTROCLOUD_DEFAULT_PROJECT_ID", + ] + api_key_keys = [ + "SPECTROCLOUD_APIKEY", + ] + host_keys = [ + "SPECTROCLOUD_HOST", + ] + + project_uid = _first_non_empty(os.environ, project_uid_keys) + api_key = _first_non_empty(os.environ, api_key_keys) + host = _first_non_empty(os.environ, host_keys) + if not host: + host = "https://api.spectrocloud.com" + if project_uid and api_key: + if not host.startswith(("http://", "https://")): + host = f"https://{host}" + return project_uid, api_key, host + + env_file_path = _resolve_palette_env_file_path() + env_values = _read_env_file(env_file_path) + if not project_uid: + project_uid = _first_non_empty(env_values, project_uid_keys) + if not api_key: + api_key = _first_non_empty(env_values, api_key_keys) + if host == "https://api.spectrocloud.com": + parsed_host = _first_non_empty(env_values, host_keys) + if parsed_host: + host = parsed_host + + if not host.startswith(("http://", "https://")): + host = f"https://{host}" + + return project_uid, api_key, host + + +@tool +def tag_cluster_for_review(cluster_uid: str) -> str: + """Tag a Palette cluster with nginx/review labels via curl.""" + if not cluster_uid.strip(): + return "STDOUT:\n\nSTDERR:\nMissing cluster UID.\nRC: 2" + + return _patch_palette_metadata( + resource_path=f"/v1/spectroclusters/{cluster_uid}/metadata", + missing_identifier_error="Missing cluster UID.", + ) + + +@tool +def tag_cluster_profile_for_review(cluster_profile_uid: str) -> str: + """Tag a Palette cluster profile with nginx/review labels via curl.""" + if not cluster_profile_uid.strip(): + return "STDOUT:\n\nSTDERR:\nMissing cluster profile UID.\nRC: 2" + + return _patch_palette_metadata( + resource_path=f"/v1/clusterprofiles/{cluster_profile_uid}/metadata", + missing_identifier_error="Missing cluster profile UID.", + ) + + +def _patch_palette_metadata(resource_path: str, missing_identifier_error: str) -> str: + if not resource_path.strip(): + return f"STDOUT:\n\nSTDERR:\n{missing_identifier_error}\nRC: 2" + + env_file_path = _resolve_palette_env_file_path() + project_uid, api_key, host = _resolve_palette_credentials() + if not project_uid: + return ( + "STDOUT:\n\nSTDERR:\n" + "Missing project UID. Checked env and " + f"{env_file_path} for project UID keys.\nRC: 2" + ) + if not api_key: + return ( + "STDOUT:\n\nSTDERR:\n" + "Missing API key. Checked env and " + f"{env_file_path} for API key keys.\nRC: 2" + ) + + payload: dict[str, Any] = { + "metadata": { + "labels": { + "nginx": "present", + "review": "required", + } + } + } + url = f"{host.rstrip('/')}{resource_path}" + command = [ + "curl", + "--location", + "--silent", + "--show-error", + "--request", + "PATCH", + url, + "--header", + f"ProjectUid: {project_uid}", + "--header", + "Content-Type: application/json", + "--header", + f"apiKey: {api_key}", + "--data", + json.dumps(payload), + "--write-out", + "\nHTTP_STATUS:%{http_code}", + ] + + try: + result = subprocess.run( + command, + capture_output=True, + text=True, + timeout=30, + check=False, + ) + except subprocess.TimeoutExpired: + return "STDOUT:\n\nSTDERR:\nTagging request timed out after 30s.\nRC: 124" + except OSError as exc: + return f"STDOUT:\n\nSTDERR:\nFailed to execute curl: {exc}\nRC: 127" + + stdout_text = result.stdout.rstrip() + status_match = re.search(r"HTTP_STATUS:(\d{3})\s*$", stdout_text) + http_status = status_match.group(1) if status_match else "unknown" + body_output = re.sub(r"\n?HTTP_STATUS:\d{3}\s*$", "", stdout_text).rstrip() + is_success = http_status.startswith("2") + + return ( + f"STDOUT:\n{body_output}\n\n" + f"STDERR:\n{result.stderr.rstrip()}\n" + f"SUCCESS: {str(is_success).lower()}\n" + f"RC: {result.returncode}" + ) diff --git a/ai/palette-mcp/integrate-palette-mcp/uv.lock b/ai/palette-mcp/integrate-palette-mcp/uv.lock new file mode 100644 index 0000000..76b855f --- /dev/null +++ b/ai/palette-mcp/integrate-palette-mcp/uv.lock @@ -0,0 +1,1431 @@ +version = 1 +revision = 3 +requires-python = ">=3.12" + +[[package]] +name = "annotated-types" +version = "0.7.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, +] + +[[package]] +name = "anyio" +version = "4.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "idna" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" }, +] + +[[package]] +name = "attrs" +version = "25.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6b/5c/685e6633917e101e5dcb62b9dd76946cbb57c26e133bae9e0cd36033c0a9/attrs-25.4.0.tar.gz", hash = "sha256:16d5969b87f0859ef33a48b35d55ac1be6e42ae49d5e853b597db70c35c57e11", size = 934251, upload-time = "2025-10-06T13:54:44.725Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl", hash = "sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373", size = 67615, upload-time = "2025-10-06T13:54:43.17Z" }, +] + +[[package]] +name = "certifi" +version = "2026.2.25" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/af/2d/7bf41579a8986e348fa033a31cdd0e4121114f6bce2457e8876010b092dd/certifi-2026.2.25.tar.gz", hash = "sha256:e887ab5cee78ea814d3472169153c2d12cd43b14bd03329a39a9c6e2e80bfba7", size = 155029, upload-time = "2026-02-25T02:54:17.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl", hash = "sha256:027692e4402ad994f1c42e52a4997a9763c646b73e4096e4d5d6db8af1d6f0fa", size = 153684, upload-time = "2026-02-25T02:54:15.766Z" }, +] + +[[package]] +name = "cffi" +version = "2.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pycparser", marker = "implementation_name != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/eb/56/b1ba7935a17738ae8453301356628e8147c79dbb825bcbc73dc7401f9846/cffi-2.0.0.tar.gz", hash = "sha256:44d1b5909021139fe36001ae048dbdde8214afa20200eda0f64c068cac5d5529", size = 523588, upload-time = "2025-09-08T23:24:04.541Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/47/4f61023ea636104d4f16ab488e268b93008c3d0bb76893b1b31db1f96802/cffi-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6d02d6655b0e54f54c4ef0b94eb6be0607b70853c45ce98bd278dc7de718be5d", size = 185271, upload-time = "2025-09-08T23:22:44.795Z" }, + { url = "https://files.pythonhosted.org/packages/df/a2/781b623f57358e360d62cdd7a8c681f074a71d445418a776eef0aadb4ab4/cffi-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8eca2a813c1cb7ad4fb74d368c2ffbbb4789d377ee5bb8df98373c2cc0dee76c", size = 181048, upload-time = "2025-09-08T23:22:45.938Z" }, + { url = "https://files.pythonhosted.org/packages/ff/df/a4f0fbd47331ceeba3d37c2e51e9dfc9722498becbeec2bd8bc856c9538a/cffi-2.0.0-cp312-cp312-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:21d1152871b019407d8ac3985f6775c079416c282e431a4da6afe7aefd2bccbe", size = 212529, upload-time = "2025-09-08T23:22:47.349Z" }, + { url = "https://files.pythonhosted.org/packages/d5/72/12b5f8d3865bf0f87cf1404d8c374e7487dcf097a1c91c436e72e6badd83/cffi-2.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b21e08af67b8a103c71a250401c78d5e0893beff75e28c53c98f4de42f774062", size = 220097, upload-time = "2025-09-08T23:22:48.677Z" }, + { url = "https://files.pythonhosted.org/packages/c2/95/7a135d52a50dfa7c882ab0ac17e8dc11cec9d55d2c18dda414c051c5e69e/cffi-2.0.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:1e3a615586f05fc4065a8b22b8152f0c1b00cdbc60596d187c2a74f9e3036e4e", size = 207983, upload-time = "2025-09-08T23:22:50.06Z" }, + { url = "https://files.pythonhosted.org/packages/3a/c8/15cb9ada8895957ea171c62dc78ff3e99159ee7adb13c0123c001a2546c1/cffi-2.0.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:81afed14892743bbe14dacb9e36d9e0e504cd204e0b165062c488942b9718037", size = 206519, upload-time = "2025-09-08T23:22:51.364Z" }, + { url = "https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3e17ed538242334bf70832644a32a7aae3d83b57567f9fd60a26257e992b79ba", size = 219572, upload-time = "2025-09-08T23:22:52.902Z" }, + { url = "https://files.pythonhosted.org/packages/07/e0/267e57e387b4ca276b90f0434ff88b2c2241ad72b16d31836adddfd6031b/cffi-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3925dd22fa2b7699ed2617149842d2e6adde22b262fcbfada50e3d195e4b3a94", size = 222963, upload-time = "2025-09-08T23:22:54.518Z" }, + { url = "https://files.pythonhosted.org/packages/b6/75/1f2747525e06f53efbd878f4d03bac5b859cbc11c633d0fb81432d98a795/cffi-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2c8f814d84194c9ea681642fd164267891702542f028a15fc97d4674b6206187", size = 221361, upload-time = "2025-09-08T23:22:55.867Z" }, + { url = "https://files.pythonhosted.org/packages/7b/2b/2b6435f76bfeb6bbf055596976da087377ede68df465419d192acf00c437/cffi-2.0.0-cp312-cp312-win32.whl", hash = "sha256:da902562c3e9c550df360bfa53c035b2f241fed6d9aef119048073680ace4a18", size = 172932, upload-time = "2025-09-08T23:22:57.188Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:da68248800ad6320861f129cd9c1bf96ca849a2771a59e0344e88681905916f5", size = 183557, upload-time = "2025-09-08T23:22:58.351Z" }, + { url = "https://files.pythonhosted.org/packages/95/31/9f7f93ad2f8eff1dbc1c3656d7ca5bfd8fb52c9d786b4dcf19b2d02217fa/cffi-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:4671d9dd5ec934cb9a73e7ee9676f9362aba54f7f34910956b84d727b0d73fb6", size = 177762, upload-time = "2025-09-08T23:22:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/4b/8d/a0a47a0c9e413a658623d014e91e74a50cdd2c423f7ccfd44086ef767f90/cffi-2.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00bdf7acc5f795150faa6957054fbbca2439db2f775ce831222b66f192f03beb", size = 185230, upload-time = "2025-09-08T23:23:00.879Z" }, + { url = "https://files.pythonhosted.org/packages/4a/d2/a6c0296814556c68ee32009d9c2ad4f85f2707cdecfd7727951ec228005d/cffi-2.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:45d5e886156860dc35862657e1494b9bae8dfa63bf56796f2fb56e1679fc0bca", size = 181043, upload-time = "2025-09-08T23:23:02.231Z" }, + { url = "https://files.pythonhosted.org/packages/b0/1e/d22cc63332bd59b06481ceaac49d6c507598642e2230f201649058a7e704/cffi-2.0.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:07b271772c100085dd28b74fa0cd81c8fb1a3ba18b21e03d7c27f3436a10606b", size = 212446, upload-time = "2025-09-08T23:23:03.472Z" }, + { url = "https://files.pythonhosted.org/packages/a9/f5/a2c23eb03b61a0b8747f211eb716446c826ad66818ddc7810cc2cc19b3f2/cffi-2.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d48a880098c96020b02d5a1f7d9251308510ce8858940e6fa99ece33f610838b", size = 220101, upload-time = "2025-09-08T23:23:04.792Z" }, + { url = "https://files.pythonhosted.org/packages/f2/7f/e6647792fc5850d634695bc0e6ab4111ae88e89981d35ac269956605feba/cffi-2.0.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:f93fd8e5c8c0a4aa1f424d6173f14a892044054871c771f8566e4008eaa359d2", size = 207948, upload-time = "2025-09-08T23:23:06.127Z" }, + { url = "https://files.pythonhosted.org/packages/cb/1e/a5a1bd6f1fb30f22573f76533de12a00bf274abcdc55c8edab639078abb6/cffi-2.0.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:dd4f05f54a52fb558f1ba9f528228066954fee3ebe629fc1660d874d040ae5a3", size = 206422, upload-time = "2025-09-08T23:23:07.753Z" }, + { url = "https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c8d3b5532fc71b7a77c09192b4a5a200ea992702734a2e9279a37f2478236f26", size = 219499, upload-time = "2025-09-08T23:23:09.648Z" }, + { url = "https://files.pythonhosted.org/packages/50/e1/a969e687fcf9ea58e6e2a928ad5e2dd88cc12f6f0ab477e9971f2309b57c/cffi-2.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:d9b29c1f0ae438d5ee9acb31cadee00a58c46cc9c0b2f9038c6b0b3470877a8c", size = 222928, upload-time = "2025-09-08T23:23:10.928Z" }, + { url = "https://files.pythonhosted.org/packages/36/54/0362578dd2c9e557a28ac77698ed67323ed5b9775ca9d3fe73fe191bb5d8/cffi-2.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6d50360be4546678fc1b79ffe7a66265e28667840010348dd69a314145807a1b", size = 221302, upload-time = "2025-09-08T23:23:12.42Z" }, + { url = "https://files.pythonhosted.org/packages/eb/6d/bf9bda840d5f1dfdbf0feca87fbdb64a918a69bca42cfa0ba7b137c48cb8/cffi-2.0.0-cp313-cp313-win32.whl", hash = "sha256:74a03b9698e198d47562765773b4a8309919089150a0bb17d829ad7b44b60d27", size = 172909, upload-time = "2025-09-08T23:23:14.32Z" }, + { url = "https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:19f705ada2530c1167abacb171925dd886168931e0a7b78f5bffcae5c6b5be75", size = 183402, upload-time = "2025-09-08T23:23:15.535Z" }, + { url = "https://files.pythonhosted.org/packages/cb/0e/02ceeec9a7d6ee63bb596121c2c8e9b3a9e150936f4fbef6ca1943e6137c/cffi-2.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:256f80b80ca3853f90c21b23ee78cd008713787b1b1e93eae9f3d6a7134abd91", size = 177780, upload-time = "2025-09-08T23:23:16.761Z" }, + { url = "https://files.pythonhosted.org/packages/92/c4/3ce07396253a83250ee98564f8d7e9789fab8e58858f35d07a9a2c78de9f/cffi-2.0.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:fc33c5141b55ed366cfaad382df24fe7dcbc686de5be719b207bb248e3053dc5", size = 185320, upload-time = "2025-09-08T23:23:18.087Z" }, + { url = "https://files.pythonhosted.org/packages/59/dd/27e9fa567a23931c838c6b02d0764611c62290062a6d4e8ff7863daf9730/cffi-2.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c654de545946e0db659b3400168c9ad31b5d29593291482c43e3564effbcee13", size = 181487, upload-time = "2025-09-08T23:23:19.622Z" }, + { url = "https://files.pythonhosted.org/packages/d6/43/0e822876f87ea8a4ef95442c3d766a06a51fc5298823f884ef87aaad168c/cffi-2.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:24b6f81f1983e6df8db3adc38562c83f7d4a0c36162885ec7f7b77c7dcbec97b", size = 220049, upload-time = "2025-09-08T23:23:20.853Z" }, + { url = "https://files.pythonhosted.org/packages/b4/89/76799151d9c2d2d1ead63c2429da9ea9d7aac304603de0c6e8764e6e8e70/cffi-2.0.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:12873ca6cb9b0f0d3a0da705d6086fe911591737a59f28b7936bdfed27c0d47c", size = 207793, upload-time = "2025-09-08T23:23:22.08Z" }, + { url = "https://files.pythonhosted.org/packages/bb/dd/3465b14bb9e24ee24cb88c9e3730f6de63111fffe513492bf8c808a3547e/cffi-2.0.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:d9b97165e8aed9272a6bb17c01e3cc5871a594a446ebedc996e2397a1c1ea8ef", size = 206300, upload-time = "2025-09-08T23:23:23.314Z" }, + { url = "https://files.pythonhosted.org/packages/47/d9/d83e293854571c877a92da46fdec39158f8d7e68da75bf73581225d28e90/cffi-2.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:afb8db5439b81cf9c9d0c80404b60c3cc9c3add93e114dcae767f1477cb53775", size = 219244, upload-time = "2025-09-08T23:23:24.541Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0f/1f177e3683aead2bb00f7679a16451d302c436b5cbf2505f0ea8146ef59e/cffi-2.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:737fe7d37e1a1bffe70bd5754ea763a62a066dc5913ca57e957824b72a85e205", size = 222828, upload-time = "2025-09-08T23:23:26.143Z" }, + { url = "https://files.pythonhosted.org/packages/c6/0f/cafacebd4b040e3119dcb32fed8bdef8dfe94da653155f9d0b9dc660166e/cffi-2.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:38100abb9d1b1435bc4cc340bb4489635dc2f0da7456590877030c9b3d40b0c1", size = 220926, upload-time = "2025-09-08T23:23:27.873Z" }, + { url = "https://files.pythonhosted.org/packages/3e/aa/df335faa45b395396fcbc03de2dfcab242cd61a9900e914fe682a59170b1/cffi-2.0.0-cp314-cp314-win32.whl", hash = "sha256:087067fa8953339c723661eda6b54bc98c5625757ea62e95eb4898ad5e776e9f", size = 175328, upload-time = "2025-09-08T23:23:44.61Z" }, + { url = "https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:203a48d1fb583fc7d78a4c6655692963b860a417c0528492a6bc21f1aaefab25", size = 185650, upload-time = "2025-09-08T23:23:45.848Z" }, + { url = "https://files.pythonhosted.org/packages/9f/2c/98ece204b9d35a7366b5b2c6539c350313ca13932143e79dc133ba757104/cffi-2.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:dbd5c7a25a7cb98f5ca55d258b103a2054f859a46ae11aaf23134f9cc0d356ad", size = 180687, upload-time = "2025-09-08T23:23:47.105Z" }, + { url = "https://files.pythonhosted.org/packages/3e/61/c768e4d548bfa607abcda77423448df8c471f25dbe64fb2ef6d555eae006/cffi-2.0.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9a67fc9e8eb39039280526379fb3a70023d77caec1852002b4da7e8b270c4dd9", size = 188773, upload-time = "2025-09-08T23:23:29.347Z" }, + { url = "https://files.pythonhosted.org/packages/2c/ea/5f76bce7cf6fcd0ab1a1058b5af899bfbef198bea4d5686da88471ea0336/cffi-2.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7a66c7204d8869299919db4d5069a82f1561581af12b11b3c9f48c584eb8743d", size = 185013, upload-time = "2025-09-08T23:23:30.63Z" }, + { url = "https://files.pythonhosted.org/packages/be/b4/c56878d0d1755cf9caa54ba71e5d049479c52f9e4afc230f06822162ab2f/cffi-2.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7cc09976e8b56f8cebd752f7113ad07752461f48a58cbba644139015ac24954c", size = 221593, upload-time = "2025-09-08T23:23:31.91Z" }, + { url = "https://files.pythonhosted.org/packages/e0/0d/eb704606dfe8033e7128df5e90fee946bbcb64a04fcdaa97321309004000/cffi-2.0.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:92b68146a71df78564e4ef48af17551a5ddd142e5190cdf2c5624d0c3ff5b2e8", size = 209354, upload-time = "2025-09-08T23:23:33.214Z" }, + { url = "https://files.pythonhosted.org/packages/d8/19/3c435d727b368ca475fb8742ab97c9cb13a0de600ce86f62eab7fa3eea60/cffi-2.0.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:b1e74d11748e7e98e2f426ab176d4ed720a64412b6a15054378afdb71e0f37dc", size = 208480, upload-time = "2025-09-08T23:23:34.495Z" }, + { url = "https://files.pythonhosted.org/packages/d0/44/681604464ed9541673e486521497406fadcc15b5217c3e326b061696899a/cffi-2.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:28a3a209b96630bca57cce802da70c266eb08c6e97e5afd61a75611ee6c64592", size = 221584, upload-time = "2025-09-08T23:23:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/25/8e/342a504ff018a2825d395d44d63a767dd8ebc927ebda557fecdaca3ac33a/cffi-2.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:7553fb2090d71822f02c629afe6042c299edf91ba1bf94951165613553984512", size = 224443, upload-time = "2025-09-08T23:23:37.328Z" }, + { url = "https://files.pythonhosted.org/packages/e1/5e/b666bacbbc60fbf415ba9988324a132c9a7a0448a9a8f125074671c0f2c3/cffi-2.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6c6c373cfc5c83a975506110d17457138c8c63016b563cc9ed6e056a82f13ce4", size = 223437, upload-time = "2025-09-08T23:23:38.945Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/ec1a60bd1a10daa292d3cd6bb0b359a81607154fb8165f3ec95fe003b85c/cffi-2.0.0-cp314-cp314t-win32.whl", hash = "sha256:1fc9ea04857caf665289b7a75923f2c6ed559b8298a1b8c49e59f7dd95c8481e", size = 180487, upload-time = "2025-09-08T23:23:40.423Z" }, + { url = "https://files.pythonhosted.org/packages/bf/41/4c1168c74fac325c0c8156f04b6749c8b6a8f405bbf91413ba088359f60d/cffi-2.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:d68b6cef7827e8641e8ef16f4494edda8b36104d79773a334beaa1e3521430f6", size = 191726, upload-time = "2025-09-08T23:23:41.742Z" }, + { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/13/69/33ddede1939fdd074bce5434295f38fae7136463422fe4fd3e0e89b98062/charset_normalizer-3.4.4.tar.gz", hash = "sha256:94537985111c35f28720e43603b8e7b43a6ecfb2ce1d3058bbe955b73404e21a", size = 129418, upload-time = "2025-10-14T04:42:32.879Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f3/85/1637cd4af66fa687396e757dec650f28025f2a2f5a5531a3208dc0ec43f2/charset_normalizer-3.4.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0a98e6759f854bd25a58a73fa88833fba3b7c491169f86ce1180c948ab3fd394", size = 208425, upload-time = "2025-10-14T04:40:53.353Z" }, + { url = "https://files.pythonhosted.org/packages/9d/6a/04130023fef2a0d9c62d0bae2649b69f7b7d8d24ea5536feef50551029df/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b5b290ccc2a263e8d185130284f8501e3e36c5e02750fc6b6bdeb2e9e96f1e25", size = 148162, upload-time = "2025-10-14T04:40:54.558Z" }, + { url = "https://files.pythonhosted.org/packages/78/29/62328d79aa60da22c9e0b9a66539feae06ca0f5a4171ac4f7dc285b83688/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74bb723680f9f7a6234dcf67aea57e708ec1fbdf5699fb91dfd6f511b0a320ef", size = 144558, upload-time = "2025-10-14T04:40:55.677Z" }, + { url = "https://files.pythonhosted.org/packages/86/bb/b32194a4bf15b88403537c2e120b817c61cd4ecffa9b6876e941c3ee38fe/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f1e34719c6ed0b92f418c7c780480b26b5d9c50349e9a9af7d76bf757530350d", size = 161497, upload-time = "2025-10-14T04:40:57.217Z" }, + { url = "https://files.pythonhosted.org/packages/19/89/a54c82b253d5b9b111dc74aca196ba5ccfcca8242d0fb64146d4d3183ff1/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:2437418e20515acec67d86e12bf70056a33abdacb5cb1655042f6538d6b085a8", size = 159240, upload-time = "2025-10-14T04:40:58.358Z" }, + { url = "https://files.pythonhosted.org/packages/c0/10/d20b513afe03acc89ec33948320a5544d31f21b05368436d580dec4e234d/charset_normalizer-3.4.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:11d694519d7f29d6cd09f6ac70028dba10f92f6cdd059096db198c283794ac86", size = 153471, upload-time = "2025-10-14T04:40:59.468Z" }, + { url = "https://files.pythonhosted.org/packages/61/fa/fbf177b55bdd727010f9c0a3c49eefa1d10f960e5f09d1d887bf93c2e698/charset_normalizer-3.4.4-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:ac1c4a689edcc530fc9d9aa11f5774b9e2f33f9a0c6a57864e90908f5208d30a", size = 150864, upload-time = "2025-10-14T04:41:00.623Z" }, + { url = "https://files.pythonhosted.org/packages/05/12/9fbc6a4d39c0198adeebbde20b619790e9236557ca59fc40e0e3cebe6f40/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:21d142cc6c0ec30d2efee5068ca36c128a30b0f2c53c1c07bd78cb6bc1d3be5f", size = 150647, upload-time = "2025-10-14T04:41:01.754Z" }, + { url = "https://files.pythonhosted.org/packages/ad/1f/6a9a593d52e3e8c5d2b167daf8c6b968808efb57ef4c210acb907c365bc4/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:5dbe56a36425d26d6cfb40ce79c314a2e4dd6211d51d6d2191c00bed34f354cc", size = 145110, upload-time = "2025-10-14T04:41:03.231Z" }, + { url = "https://files.pythonhosted.org/packages/30/42/9a52c609e72471b0fc54386dc63c3781a387bb4fe61c20231a4ebcd58bdd/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5bfbb1b9acf3334612667b61bd3002196fe2a1eb4dd74d247e0f2a4d50ec9bbf", size = 162839, upload-time = "2025-10-14T04:41:04.715Z" }, + { url = "https://files.pythonhosted.org/packages/c4/5b/c0682bbf9f11597073052628ddd38344a3d673fda35a36773f7d19344b23/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:d055ec1e26e441f6187acf818b73564e6e6282709e9bcb5b63f5b23068356a15", size = 150667, upload-time = "2025-10-14T04:41:05.827Z" }, + { url = "https://files.pythonhosted.org/packages/e4/24/a41afeab6f990cf2daf6cb8c67419b63b48cf518e4f56022230840c9bfb2/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:af2d8c67d8e573d6de5bc30cdb27e9b95e49115cd9baad5ddbd1a6207aaa82a9", size = 160535, upload-time = "2025-10-14T04:41:06.938Z" }, + { url = "https://files.pythonhosted.org/packages/2a/e5/6a4ce77ed243c4a50a1fecca6aaaab419628c818a49434be428fe24c9957/charset_normalizer-3.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:780236ac706e66881f3b7f2f32dfe90507a09e67d1d454c762cf642e6e1586e0", size = 154816, upload-time = "2025-10-14T04:41:08.101Z" }, + { url = "https://files.pythonhosted.org/packages/a8/ef/89297262b8092b312d29cdb2517cb1237e51db8ecef2e9af5edbe7b683b1/charset_normalizer-3.4.4-cp312-cp312-win32.whl", hash = "sha256:5833d2c39d8896e4e19b689ffc198f08ea58116bee26dea51e362ecc7cd3ed26", size = 99694, upload-time = "2025-10-14T04:41:09.23Z" }, + { url = "https://files.pythonhosted.org/packages/3d/2d/1e5ed9dd3b3803994c155cd9aacb60c82c331bad84daf75bcb9c91b3295e/charset_normalizer-3.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:a79cfe37875f822425b89a82333404539ae63dbdddf97f84dcbc3d339aae9525", size = 107131, upload-time = "2025-10-14T04:41:10.467Z" }, + { url = "https://files.pythonhosted.org/packages/d0/d9/0ed4c7098a861482a7b6a95603edce4c0d9db2311af23da1fb2b75ec26fc/charset_normalizer-3.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:376bec83a63b8021bb5c8ea75e21c4ccb86e7e45ca4eb81146091b56599b80c3", size = 100390, upload-time = "2025-10-14T04:41:11.915Z" }, + { url = "https://files.pythonhosted.org/packages/97/45/4b3a1239bbacd321068ea6e7ac28875b03ab8bc0aa0966452db17cd36714/charset_normalizer-3.4.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:e1f185f86a6f3403aa2420e815904c67b2f9ebc443f045edd0de921108345794", size = 208091, upload-time = "2025-10-14T04:41:13.346Z" }, + { url = "https://files.pythonhosted.org/packages/7d/62/73a6d7450829655a35bb88a88fca7d736f9882a27eacdca2c6d505b57e2e/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6b39f987ae8ccdf0d2642338faf2abb1862340facc796048b604ef14919e55ed", size = 147936, upload-time = "2025-10-14T04:41:14.461Z" }, + { url = "https://files.pythonhosted.org/packages/89/c5/adb8c8b3d6625bef6d88b251bbb0d95f8205831b987631ab0c8bb5d937c2/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:3162d5d8ce1bb98dd51af660f2121c55d0fa541b46dff7bb9b9f86ea1d87de72", size = 144180, upload-time = "2025-10-14T04:41:15.588Z" }, + { url = "https://files.pythonhosted.org/packages/91/ed/9706e4070682d1cc219050b6048bfd293ccf67b3d4f5a4f39207453d4b99/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:81d5eb2a312700f4ecaa977a8235b634ce853200e828fbadf3a9c50bab278328", size = 161346, upload-time = "2025-10-14T04:41:16.738Z" }, + { url = "https://files.pythonhosted.org/packages/d5/0d/031f0d95e4972901a2f6f09ef055751805ff541511dc1252ba3ca1f80cf5/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5bd2293095d766545ec1a8f612559f6b40abc0eb18bb2f5d1171872d34036ede", size = 158874, upload-time = "2025-10-14T04:41:17.923Z" }, + { url = "https://files.pythonhosted.org/packages/f5/83/6ab5883f57c9c801ce5e5677242328aa45592be8a00644310a008d04f922/charset_normalizer-3.4.4-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8a8b89589086a25749f471e6a900d3f662d1d3b6e2e59dcecf787b1cc3a1894", size = 153076, upload-time = "2025-10-14T04:41:19.106Z" }, + { url = "https://files.pythonhosted.org/packages/75/1e/5ff781ddf5260e387d6419959ee89ef13878229732732ee73cdae01800f2/charset_normalizer-3.4.4-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bc7637e2f80d8530ee4a78e878bce464f70087ce73cf7c1caf142416923b98f1", size = 150601, upload-time = "2025-10-14T04:41:20.245Z" }, + { url = "https://files.pythonhosted.org/packages/d7/57/71be810965493d3510a6ca79b90c19e48696fb1ff964da319334b12677f0/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f8bf04158c6b607d747e93949aa60618b61312fe647a6369f88ce2ff16043490", size = 150376, upload-time = "2025-10-14T04:41:21.398Z" }, + { url = "https://files.pythonhosted.org/packages/e5/d5/c3d057a78c181d007014feb7e9f2e65905a6c4ef182c0ddf0de2924edd65/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:554af85e960429cf30784dd47447d5125aaa3b99a6f0683589dbd27e2f45da44", size = 144825, upload-time = "2025-10-14T04:41:22.583Z" }, + { url = "https://files.pythonhosted.org/packages/e6/8c/d0406294828d4976f275ffbe66f00266c4b3136b7506941d87c00cab5272/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:74018750915ee7ad843a774364e13a3db91682f26142baddf775342c3f5b1133", size = 162583, upload-time = "2025-10-14T04:41:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/d7/24/e2aa1f18c8f15c4c0e932d9287b8609dd30ad56dbe41d926bd846e22fb8d/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c0463276121fdee9c49b98908b3a89c39be45d86d1dbaa22957e38f6321d4ce3", size = 150366, upload-time = "2025-10-14T04:41:25.27Z" }, + { url = "https://files.pythonhosted.org/packages/e4/5b/1e6160c7739aad1e2df054300cc618b06bf784a7a164b0f238360721ab86/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:362d61fd13843997c1c446760ef36f240cf81d3ebf74ac62652aebaf7838561e", size = 160300, upload-time = "2025-10-14T04:41:26.725Z" }, + { url = "https://files.pythonhosted.org/packages/7a/10/f882167cd207fbdd743e55534d5d9620e095089d176d55cb22d5322f2afd/charset_normalizer-3.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9a26f18905b8dd5d685d6d07b0cdf98a79f3c7a918906af7cc143ea2e164c8bc", size = 154465, upload-time = "2025-10-14T04:41:28.322Z" }, + { url = "https://files.pythonhosted.org/packages/89/66/c7a9e1b7429be72123441bfdbaf2bc13faab3f90b933f664db506dea5915/charset_normalizer-3.4.4-cp313-cp313-win32.whl", hash = "sha256:9b35f4c90079ff2e2edc5b26c0c77925e5d2d255c42c74fdb70fb49b172726ac", size = 99404, upload-time = "2025-10-14T04:41:29.95Z" }, + { url = "https://files.pythonhosted.org/packages/c4/26/b9924fa27db384bdcd97ab83b4f0a8058d96ad9626ead570674d5e737d90/charset_normalizer-3.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:b435cba5f4f750aa6c0a0d92c541fb79f69a387c91e61f1795227e4ed9cece14", size = 107092, upload-time = "2025-10-14T04:41:31.188Z" }, + { url = "https://files.pythonhosted.org/packages/af/8f/3ed4bfa0c0c72a7ca17f0380cd9e4dd842b09f664e780c13cff1dcf2ef1b/charset_normalizer-3.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:542d2cee80be6f80247095cc36c418f7bddd14f4a6de45af91dfad36d817bba2", size = 100408, upload-time = "2025-10-14T04:41:32.624Z" }, + { url = "https://files.pythonhosted.org/packages/2a/35/7051599bd493e62411d6ede36fd5af83a38f37c4767b92884df7301db25d/charset_normalizer-3.4.4-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:da3326d9e65ef63a817ecbcc0df6e94463713b754fe293eaa03da99befb9a5bd", size = 207746, upload-time = "2025-10-14T04:41:33.773Z" }, + { url = "https://files.pythonhosted.org/packages/10/9a/97c8d48ef10d6cd4fcead2415523221624bf58bcf68a802721a6bc807c8f/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8af65f14dc14a79b924524b1e7fffe304517b2bff5a58bf64f30b98bbc5079eb", size = 147889, upload-time = "2025-10-14T04:41:34.897Z" }, + { url = "https://files.pythonhosted.org/packages/10/bf/979224a919a1b606c82bd2c5fa49b5c6d5727aa47b4312bb27b1734f53cd/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:74664978bb272435107de04e36db5a9735e78232b85b77d45cfb38f758efd33e", size = 143641, upload-time = "2025-10-14T04:41:36.116Z" }, + { url = "https://files.pythonhosted.org/packages/ba/33/0ad65587441fc730dc7bd90e9716b30b4702dc7b617e6ba4997dc8651495/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:752944c7ffbfdd10c074dc58ec2d5a8a4cd9493b314d367c14d24c17684ddd14", size = 160779, upload-time = "2025-10-14T04:41:37.229Z" }, + { url = "https://files.pythonhosted.org/packages/67/ed/331d6b249259ee71ddea93f6f2f0a56cfebd46938bde6fcc6f7b9a3d0e09/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d1f13550535ad8cff21b8d757a3257963e951d96e20ec82ab44bc64aeb62a191", size = 159035, upload-time = "2025-10-14T04:41:38.368Z" }, + { url = "https://files.pythonhosted.org/packages/67/ff/f6b948ca32e4f2a4576aa129d8bed61f2e0543bf9f5f2b7fc3758ed005c9/charset_normalizer-3.4.4-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ecaae4149d99b1c9e7b88bb03e3221956f68fd6d50be2ef061b2381b61d20838", size = 152542, upload-time = "2025-10-14T04:41:39.862Z" }, + { url = "https://files.pythonhosted.org/packages/16/85/276033dcbcc369eb176594de22728541a925b2632f9716428c851b149e83/charset_normalizer-3.4.4-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:cb6254dc36b47a990e59e1068afacdcd02958bdcce30bb50cc1700a8b9d624a6", size = 149524, upload-time = "2025-10-14T04:41:41.319Z" }, + { url = "https://files.pythonhosted.org/packages/9e/f2/6a2a1f722b6aba37050e626530a46a68f74e63683947a8acff92569f979a/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c8ae8a0f02f57a6e61203a31428fa1d677cbe50c93622b4149d5c0f319c1d19e", size = 150395, upload-time = "2025-10-14T04:41:42.539Z" }, + { url = "https://files.pythonhosted.org/packages/60/bb/2186cb2f2bbaea6338cad15ce23a67f9b0672929744381e28b0592676824/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:47cc91b2f4dd2833fddaedd2893006b0106129d4b94fdb6af1f4ce5a9965577c", size = 143680, upload-time = "2025-10-14T04:41:43.661Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/bf6f13b772fbb2a90360eb620d52ed8f796f3c5caee8398c3b2eb7b1c60d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:82004af6c302b5d3ab2cfc4cc5f29db16123b1a8417f2e25f9066f91d4411090", size = 162045, upload-time = "2025-10-14T04:41:44.821Z" }, + { url = "https://files.pythonhosted.org/packages/df/c5/d1be898bf0dc3ef9030c3825e5d3b83f2c528d207d246cbabe245966808d/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:2b7d8f6c26245217bd2ad053761201e9f9680f8ce52f0fcd8d0755aeae5b2152", size = 149687, upload-time = "2025-10-14T04:41:46.442Z" }, + { url = "https://files.pythonhosted.org/packages/a5/42/90c1f7b9341eef50c8a1cb3f098ac43b0508413f33affd762855f67a410e/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:799a7a5e4fb2d5898c60b640fd4981d6a25f1c11790935a44ce38c54e985f828", size = 160014, upload-time = "2025-10-14T04:41:47.631Z" }, + { url = "https://files.pythonhosted.org/packages/76/be/4d3ee471e8145d12795ab655ece37baed0929462a86e72372fd25859047c/charset_normalizer-3.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:99ae2cffebb06e6c22bdc25801d7b30f503cc87dbd283479e7b606f70aff57ec", size = 154044, upload-time = "2025-10-14T04:41:48.81Z" }, + { url = "https://files.pythonhosted.org/packages/b0/6f/8f7af07237c34a1defe7defc565a9bc1807762f672c0fde711a4b22bf9c0/charset_normalizer-3.4.4-cp314-cp314-win32.whl", hash = "sha256:f9d332f8c2a2fcbffe1378594431458ddbef721c1769d78e2cbc06280d8155f9", size = 99940, upload-time = "2025-10-14T04:41:49.946Z" }, + { url = "https://files.pythonhosted.org/packages/4b/51/8ade005e5ca5b0d80fb4aff72a3775b325bdc3d27408c8113811a7cbe640/charset_normalizer-3.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:8a6562c3700cce886c5be75ade4a5db4214fda19fede41d9792d100288d8f94c", size = 107104, upload-time = "2025-10-14T04:41:51.051Z" }, + { url = "https://files.pythonhosted.org/packages/da/5f/6b8f83a55bb8278772c5ae54a577f3099025f9ade59d0136ac24a0df4bde/charset_normalizer-3.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:de00632ca48df9daf77a2c65a484531649261ec9f25489917f09e455cb09ddb2", size = 100743, upload-time = "2025-10-14T04:41:52.122Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4c/925909008ed5a988ccbb72dcc897407e5d6d3bd72410d69e051fc0c14647/charset_normalizer-3.4.4-py3-none-any.whl", hash = "sha256:7a32c560861a02ff789ad905a2fe94e3f840803362c84fecf1851cb4cf3dc37f", size = 53402, upload-time = "2025-10-14T04:42:31.76Z" }, +] + +[[package]] +name = "click" +version = "8.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3d/fa/656b739db8587d7b5dfa22e22ed02566950fbfbcdc20311993483657a5c0/click-8.3.1.tar.gz", hash = "sha256:12ff4785d337a1bb490bb7e9c2b1ee5da3112e94a8622f26a6c77f5d2fc6842a", size = 295065, upload-time = "2025-11-15T20:45:42.706Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/98/78/01c019cdb5d6498122777c1a43056ebb3ebfeef2076d9d026bfe15583b2b/click-8.3.1-py3-none-any.whl", hash = "sha256:981153a64e25f12d547d3426c367a4857371575ee7ad18df2a6183ab0545b2a6", size = 108274, upload-time = "2025-11-15T20:45:41.139Z" }, +] + +[[package]] +name = "colorama" +version = "0.4.6" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, +] + +[[package]] +name = "cryptography" +version = "46.0.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi", marker = "platform_python_implementation != 'PyPy'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/60/04/ee2a9e8542e4fa2773b81771ff8349ff19cdd56b7258a0cc442639052edb/cryptography-46.0.5.tar.gz", hash = "sha256:abace499247268e3757271b2f1e244b36b06f8515cf27c4d49468fc9eb16e93d", size = 750064, upload-time = "2026-02-10T19:18:38.255Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f7/81/b0bb27f2ba931a65409c6b8a8b358a7f03c0e46eceacddff55f7c84b1f3b/cryptography-46.0.5-cp311-abi3-macosx_10_9_universal2.whl", hash = "sha256:351695ada9ea9618b3500b490ad54c739860883df6c1f555e088eaf25b1bbaad", size = 7176289, upload-time = "2026-02-10T19:17:08.274Z" }, + { url = "https://files.pythonhosted.org/packages/ff/9e/6b4397a3e3d15123de3b1806ef342522393d50736c13b20ec4c9ea6693a6/cryptography-46.0.5-cp311-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c18ff11e86df2e28854939acde2d003f7984f721eba450b56a200ad90eeb0e6b", size = 4275637, upload-time = "2026-02-10T19:17:10.53Z" }, + { url = "https://files.pythonhosted.org/packages/63/e7/471ab61099a3920b0c77852ea3f0ea611c9702f651600397ac567848b897/cryptography-46.0.5-cp311-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d7e3d356b8cd4ea5aff04f129d5f66ebdc7b6f8eae802b93739ed520c47c79b", size = 4424742, upload-time = "2026-02-10T19:17:12.388Z" }, + { url = "https://files.pythonhosted.org/packages/37/53/a18500f270342d66bf7e4d9f091114e31e5ee9e7375a5aba2e85a91e0044/cryptography-46.0.5-cp311-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:50bfb6925eff619c9c023b967d5b77a54e04256c4281b0e21336a130cd7fc263", size = 4277528, upload-time = "2026-02-10T19:17:13.853Z" }, + { url = "https://files.pythonhosted.org/packages/22/29/c2e812ebc38c57b40e7c583895e73c8c5adb4d1e4a0cc4c5a4fdab2b1acc/cryptography-46.0.5-cp311-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:803812e111e75d1aa73690d2facc295eaefd4439be1023fefc4995eaea2af90d", size = 4947993, upload-time = "2026-02-10T19:17:15.618Z" }, + { url = "https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed", size = 4456855, upload-time = "2026-02-10T19:17:17.221Z" }, + { url = "https://files.pythonhosted.org/packages/2d/87/fc628a7ad85b81206738abbd213b07702bcbdada1dd43f72236ef3cffbb5/cryptography-46.0.5-cp311-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:f145bba11b878005c496e93e257c1e88f154d278d2638e6450d17e0f31e558d2", size = 3984635, upload-time = "2026-02-10T19:17:18.792Z" }, + { url = "https://files.pythonhosted.org/packages/84/29/65b55622bde135aedf4565dc509d99b560ee4095e56989e815f8fd2aa910/cryptography-46.0.5-cp311-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:e9251e3be159d1020c4030bd2e5f84d6a43fe54b6c19c12f51cde9542a2817b2", size = 4277038, upload-time = "2026-02-10T19:17:20.256Z" }, + { url = "https://files.pythonhosted.org/packages/bc/36/45e76c68d7311432741faf1fbf7fac8a196a0a735ca21f504c75d37e2558/cryptography-46.0.5-cp311-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:47fb8a66058b80e509c47118ef8a75d14c455e81ac369050f20ba0d23e77fee0", size = 4912181, upload-time = "2026-02-10T19:17:21.825Z" }, + { url = "https://files.pythonhosted.org/packages/6d/1a/c1ba8fead184d6e3d5afcf03d569acac5ad063f3ac9fb7258af158f7e378/cryptography-46.0.5-cp311-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:4c3341037c136030cb46e4b1e17b7418ea4cbd9dd207e4a6f3b2b24e0d4ac731", size = 4456482, upload-time = "2026-02-10T19:17:25.133Z" }, + { url = "https://files.pythonhosted.org/packages/f9/e5/3fb22e37f66827ced3b902cf895e6a6bc1d095b5b26be26bd13c441fdf19/cryptography-46.0.5-cp311-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:890bcb4abd5a2d3f852196437129eb3667d62630333aacc13dfd470fad3aaa82", size = 4405497, upload-time = "2026-02-10T19:17:26.66Z" }, + { url = "https://files.pythonhosted.org/packages/1a/df/9d58bb32b1121a8a2f27383fabae4d63080c7ca60b9b5c88be742be04ee7/cryptography-46.0.5-cp311-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:80a8d7bfdf38f87ca30a5391c0c9ce4ed2926918e017c29ddf643d0ed2778ea1", size = 4667819, upload-time = "2026-02-10T19:17:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/ea/ed/325d2a490c5e94038cdb0117da9397ece1f11201f425c4e9c57fe5b9f08b/cryptography-46.0.5-cp311-abi3-win32.whl", hash = "sha256:60ee7e19e95104d4c03871d7d7dfb3d22ef8a9b9c6778c94e1c8fcc8365afd48", size = 3028230, upload-time = "2026-02-10T19:17:30.518Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5a/ac0f49e48063ab4255d9e3b79f5def51697fce1a95ea1370f03dc9db76f6/cryptography-46.0.5-cp311-abi3-win_amd64.whl", hash = "sha256:38946c54b16c885c72c4f59846be9743d699eee2b69b6988e0a00a01f46a61a4", size = 3480909, upload-time = "2026-02-10T19:17:32.083Z" }, + { url = "https://files.pythonhosted.org/packages/00/13/3d278bfa7a15a96b9dc22db5a12ad1e48a9eb3d40e1827ef66a5df75d0d0/cryptography-46.0.5-cp314-cp314t-macosx_10_9_universal2.whl", hash = "sha256:94a76daa32eb78d61339aff7952ea819b1734b46f73646a07decb40e5b3448e2", size = 7119287, upload-time = "2026-02-10T19:17:33.801Z" }, + { url = "https://files.pythonhosted.org/packages/67/c8/581a6702e14f0898a0848105cbefd20c058099e2c2d22ef4e476dfec75d7/cryptography-46.0.5-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5be7bf2fb40769e05739dd0046e7b26f9d4670badc7b032d6ce4db64dddc0678", size = 4265728, upload-time = "2026-02-10T19:17:35.569Z" }, + { url = "https://files.pythonhosted.org/packages/dd/4a/ba1a65ce8fc65435e5a849558379896c957870dd64fecea97b1ad5f46a37/cryptography-46.0.5-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fe346b143ff9685e40192a4960938545c699054ba11d4f9029f94751e3f71d87", size = 4408287, upload-time = "2026-02-10T19:17:36.938Z" }, + { url = "https://files.pythonhosted.org/packages/f8/67/8ffdbf7b65ed1ac224d1c2df3943553766914a8ca718747ee3871da6107e/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c69fd885df7d089548a42d5ec05be26050ebcd2283d89b3d30676eb32ff87dee", size = 4270291, upload-time = "2026-02-10T19:17:38.748Z" }, + { url = "https://files.pythonhosted.org/packages/f8/e5/f52377ee93bc2f2bba55a41a886fd208c15276ffbd2569f2ddc89d50e2c5/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_ppc64le.whl", hash = "sha256:8293f3dea7fc929ef7240796ba231413afa7b68ce38fd21da2995549f5961981", size = 4927539, upload-time = "2026-02-10T19:17:40.241Z" }, + { url = "https://files.pythonhosted.org/packages/3b/02/cfe39181b02419bbbbcf3abdd16c1c5c8541f03ca8bda240debc467d5a12/cryptography-46.0.5-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:1abfdb89b41c3be0365328a410baa9df3ff8a9110fb75e7b52e66803ddabc9a9", size = 4442199, upload-time = "2026-02-10T19:17:41.789Z" }, + { url = "https://files.pythonhosted.org/packages/c0/96/2fcaeb4873e536cf71421a388a6c11b5bc846e986b2b069c79363dc1648e/cryptography-46.0.5-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:d66e421495fdb797610a08f43b05269e0a5ea7f5e652a89bfd5a7d3c1dee3648", size = 3960131, upload-time = "2026-02-10T19:17:43.379Z" }, + { url = "https://files.pythonhosted.org/packages/d8/d2/b27631f401ddd644e94c5cf33c9a4069f72011821cf3dc7309546b0642a0/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_aarch64.whl", hash = "sha256:4e817a8920bfbcff8940ecfd60f23d01836408242b30f1a708d93198393a80b4", size = 4270072, upload-time = "2026-02-10T19:17:45.481Z" }, + { url = "https://files.pythonhosted.org/packages/f4/a7/60d32b0370dae0b4ebe55ffa10e8599a2a59935b5ece1b9f06edb73abdeb/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_ppc64le.whl", hash = "sha256:68f68d13f2e1cb95163fa3b4db4bf9a159a418f5f6e7242564fc75fcae667fd0", size = 4892170, upload-time = "2026-02-10T19:17:46.997Z" }, + { url = "https://files.pythonhosted.org/packages/d2/b9/cf73ddf8ef1164330eb0b199a589103c363afa0cf794218c24d524a58eab/cryptography-46.0.5-cp314-cp314t-manylinux_2_34_x86_64.whl", hash = "sha256:a3d1fae9863299076f05cb8a778c467578262fae09f9dc0ee9b12eb4268ce663", size = 4441741, upload-time = "2026-02-10T19:17:48.661Z" }, + { url = "https://files.pythonhosted.org/packages/5f/eb/eee00b28c84c726fe8fa0158c65afe312d9c3b78d9d01daf700f1f6e37ff/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c4143987a42a2397f2fc3b4d7e3a7d313fbe684f67ff443999e803dd75a76826", size = 4396728, upload-time = "2026-02-10T19:17:50.058Z" }, + { url = "https://files.pythonhosted.org/packages/65/f4/6bc1a9ed5aef7145045114b75b77c2a8261b4d38717bd8dea111a63c3442/cryptography-46.0.5-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:7d731d4b107030987fd61a7f8ab512b25b53cef8f233a97379ede116f30eb67d", size = 4652001, upload-time = "2026-02-10T19:17:51.54Z" }, + { url = "https://files.pythonhosted.org/packages/86/ef/5d00ef966ddd71ac2e6951d278884a84a40ffbd88948ef0e294b214ae9e4/cryptography-46.0.5-cp314-cp314t-win32.whl", hash = "sha256:c3bcce8521d785d510b2aad26ae2c966092b7daa8f45dd8f44734a104dc0bc1a", size = 3003637, upload-time = "2026-02-10T19:17:52.997Z" }, + { url = "https://files.pythonhosted.org/packages/b7/57/f3f4160123da6d098db78350fdfd9705057aad21de7388eacb2401dceab9/cryptography-46.0.5-cp314-cp314t-win_amd64.whl", hash = "sha256:4d8ae8659ab18c65ced284993c2265910f6c9e650189d4e3f68445ef82a810e4", size = 3469487, upload-time = "2026-02-10T19:17:54.549Z" }, + { url = "https://files.pythonhosted.org/packages/e2/fa/a66aa722105ad6a458bebd64086ca2b72cdd361fed31763d20390f6f1389/cryptography-46.0.5-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:4108d4c09fbbf2789d0c926eb4152ae1760d5a2d97612b92d508d96c861e4d31", size = 7170514, upload-time = "2026-02-10T19:17:56.267Z" }, + { url = "https://files.pythonhosted.org/packages/0f/04/c85bdeab78c8bc77b701bf0d9bdcf514c044e18a46dcff330df5448631b0/cryptography-46.0.5-cp38-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7d1f30a86d2757199cb2d56e48cce14deddf1f9c95f1ef1b64ee91ea43fe2e18", size = 4275349, upload-time = "2026-02-10T19:17:58.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/32/9b87132a2f91ee7f5223b091dc963055503e9b442c98fc0b8a5ca765fab0/cryptography-46.0.5-cp38-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:039917b0dc418bb9f6edce8a906572d69e74bd330b0b3fea4f79dab7f8ddd235", size = 4420667, upload-time = "2026-02-10T19:18:00.619Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a6/a7cb7010bec4b7c5692ca6f024150371b295ee1c108bdc1c400e4c44562b/cryptography-46.0.5-cp38-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ba2a27ff02f48193fc4daeadf8ad2590516fa3d0adeeb34336b96f7fa64c1e3a", size = 4276980, upload-time = "2026-02-10T19:18:02.379Z" }, + { url = "https://files.pythonhosted.org/packages/8e/7c/c4f45e0eeff9b91e3f12dbd0e165fcf2a38847288fcfd889deea99fb7b6d/cryptography-46.0.5-cp38-abi3-manylinux_2_28_ppc64le.whl", hash = "sha256:61aa400dce22cb001a98014f647dc21cda08f7915ceb95df0c9eaf84b4b6af76", size = 4939143, upload-time = "2026-02-10T19:18:03.964Z" }, + { url = "https://files.pythonhosted.org/packages/37/19/e1b8f964a834eddb44fa1b9a9976f4e414cbb7aa62809b6760c8803d22d1/cryptography-46.0.5-cp38-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3ce58ba46e1bc2aac4f7d9290223cead56743fa6ab94a5d53292ffaac6a91614", size = 4453674, upload-time = "2026-02-10T19:18:05.588Z" }, + { url = "https://files.pythonhosted.org/packages/db/ed/db15d3956f65264ca204625597c410d420e26530c4e2943e05a0d2f24d51/cryptography-46.0.5-cp38-abi3-manylinux_2_31_armv7l.whl", hash = "sha256:420d0e909050490d04359e7fdb5ed7e667ca5c3c402b809ae2563d7e66a92229", size = 3978801, upload-time = "2026-02-10T19:18:07.167Z" }, + { url = "https://files.pythonhosted.org/packages/41/e2/df40a31d82df0a70a0daf69791f91dbb70e47644c58581d654879b382d11/cryptography-46.0.5-cp38-abi3-manylinux_2_34_aarch64.whl", hash = "sha256:582f5fcd2afa31622f317f80426a027f30dc792e9c80ffee87b993200ea115f1", size = 4276755, upload-time = "2026-02-10T19:18:09.813Z" }, + { url = "https://files.pythonhosted.org/packages/33/45/726809d1176959f4a896b86907b98ff4391a8aa29c0aaaf9450a8a10630e/cryptography-46.0.5-cp38-abi3-manylinux_2_34_ppc64le.whl", hash = "sha256:bfd56bb4b37ed4f330b82402f6f435845a5f5648edf1ad497da51a8452d5d62d", size = 4901539, upload-time = "2026-02-10T19:18:11.263Z" }, + { url = "https://files.pythonhosted.org/packages/99/0f/a3076874e9c88ecb2ecc31382f6e7c21b428ede6f55aafa1aa272613e3cd/cryptography-46.0.5-cp38-abi3-manylinux_2_34_x86_64.whl", hash = "sha256:a3d507bb6a513ca96ba84443226af944b0f7f47dcc9a399d110cd6146481d24c", size = 4452794, upload-time = "2026-02-10T19:18:12.914Z" }, + { url = "https://files.pythonhosted.org/packages/02/ef/ffeb542d3683d24194a38f66ca17c0a4b8bf10631feef44a7ef64e631b1a/cryptography-46.0.5-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9f16fbdf4da055efb21c22d81b89f155f02ba420558db21288b3d0035bafd5f4", size = 4404160, upload-time = "2026-02-10T19:18:14.375Z" }, + { url = "https://files.pythonhosted.org/packages/96/93/682d2b43c1d5f1406ed048f377c0fc9fc8f7b0447a478d5c65ab3d3a66eb/cryptography-46.0.5-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ced80795227d70549a411a4ab66e8ce307899fad2220ce5ab2f296e687eacde9", size = 4667123, upload-time = "2026-02-10T19:18:15.886Z" }, + { url = "https://files.pythonhosted.org/packages/45/2d/9c5f2926cb5300a8eefc3f4f0b3f3df39db7f7ce40c8365444c49363cbda/cryptography-46.0.5-cp38-abi3-win32.whl", hash = "sha256:02f547fce831f5096c9a567fd41bc12ca8f11df260959ecc7c3202555cc47a72", size = 3010220, upload-time = "2026-02-10T19:18:17.361Z" }, + { url = "https://files.pythonhosted.org/packages/48/ef/0c2f4a8e31018a986949d34a01115dd057bf536905dca38897bacd21fac3/cryptography-46.0.5-cp38-abi3-win_amd64.whl", hash = "sha256:556e106ee01aa13484ce9b0239bca667be5004efb0aabbed28d353df86445595", size = 3467050, upload-time = "2026-02-10T19:18:18.899Z" }, +] + +[[package]] +name = "distro" +version = "1.9.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, +] + +[[package]] +name = "h11" +version = "0.16.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" }, +] + +[[package]] +name = "httpcore" +version = "1.0.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" }, +] + +[[package]] +name = "httpx" +version = "0.28.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "certifi" }, + { name = "httpcore" }, + { name = "idna" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" }, +] + +[[package]] +name = "httpx-sse" +version = "0.4.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0f/4c/751061ffa58615a32c31b2d82e8482be8dd4a89154f003147acee90f2be9/httpx_sse-0.4.3.tar.gz", hash = "sha256:9b1ed0127459a66014aec3c56bebd93da3c1bc8bb6618c8082039a44889a755d", size = 15943, upload-time = "2025-10-10T21:48:22.271Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d2/fd/6668e5aec43ab844de6fc74927e155a3b37bf40d7c3790e49fc0406b6578/httpx_sse-0.4.3-py3-none-any.whl", hash = "sha256:0ac1c9fe3c0afad2e0ebb25a934a59f4c7823b60792691f779fad2c5568830fc", size = 8960, upload-time = "2025-10-10T21:48:21.158Z" }, +] + +[[package]] +name = "idna" +version = "3.11" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, +] + +[[package]] +name = "integrate-palette-mcp" +version = "0.1.0" +source = { virtual = "." } +dependencies = [ + { name = "langchain" }, + { name = "langchain-mcp-adapters" }, + { name = "langchain-openai" }, +] + +[package.metadata] +requires-dist = [ + { name = "langchain", specifier = ">=1.2.10" }, + { name = "langchain-mcp-adapters", specifier = ">=0.2.1" }, + { name = "langchain-openai", specifier = ">=1.1.10" }, +] + +[[package]] +name = "jiter" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/5e/4ec91646aee381d01cdb9974e30882c9cd3b8c5d1079d6b5ff4af522439a/jiter-0.13.0.tar.gz", hash = "sha256:f2839f9c2c7e2dffc1bc5929a510e14ce0a946be9365fd1219e7ef342dae14f4", size = 164847, upload-time = "2026-02-02T12:37:56.441Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2e/30/7687e4f87086829955013ca12a9233523349767f69653ebc27036313def9/jiter-0.13.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:0a2bd69fc1d902e89925fc34d1da51b2128019423d7b339a45d9e99c894e0663", size = 307958, upload-time = "2026-02-02T12:35:57.165Z" }, + { url = "https://files.pythonhosted.org/packages/c3/27/e57f9a783246ed95481e6749cc5002a8a767a73177a83c63ea71f0528b90/jiter-0.13.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f917a04240ef31898182f76a332f508f2cc4b57d2b4d7ad2dbfebbfe167eb505", size = 318597, upload-time = "2026-02-02T12:35:58.591Z" }, + { url = "https://files.pythonhosted.org/packages/cf/52/e5719a60ac5d4d7c5995461a94ad5ef962a37c8bf5b088390e6fad59b2ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1e2b199f446d3e82246b4fd9236d7cb502dc2222b18698ba0d986d2fecc6152", size = 348821, upload-time = "2026-02-02T12:36:00.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/db/c1efc32b8ba4c740ab3fc2d037d8753f67685f475e26b9d6536a4322bcdd/jiter-0.13.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:04670992b576fa65bd056dbac0c39fe8bd67681c380cb2b48efa885711d9d726", size = 364163, upload-time = "2026-02-02T12:36:01.937Z" }, + { url = "https://files.pythonhosted.org/packages/55/8a/fb75556236047c8806995671a18e4a0ad646ed255276f51a20f32dceaeec/jiter-0.13.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5a1aff1fbdb803a376d4d22a8f63f8e7ccbce0b4890c26cc7af9e501ab339ef0", size = 483709, upload-time = "2026-02-02T12:36:03.41Z" }, + { url = "https://files.pythonhosted.org/packages/7e/16/43512e6ee863875693a8e6f6d532e19d650779d6ba9a81593ae40a9088ff/jiter-0.13.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b3fb8c2053acaef8580809ac1d1f7481a0a0bdc012fd7f5d8b18fb696a5a089", size = 370480, upload-time = "2026-02-02T12:36:04.791Z" }, + { url = "https://files.pythonhosted.org/packages/f8/4c/09b93e30e984a187bc8aaa3510e1ec8dcbdcd71ca05d2f56aac0492453aa/jiter-0.13.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bdaba7d87e66f26a2c45d8cbadcbfc4bf7884182317907baf39cfe9775bb4d93", size = 360735, upload-time = "2026-02-02T12:36:06.994Z" }, + { url = "https://files.pythonhosted.org/packages/1a/1b/46c5e349019874ec5dfa508c14c37e29864ea108d376ae26d90bee238cd7/jiter-0.13.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7b88d649135aca526da172e48083da915ec086b54e8e73a425ba50999468cc08", size = 391814, upload-time = "2026-02-02T12:36:08.368Z" }, + { url = "https://files.pythonhosted.org/packages/15/9e/26184760e85baee7162ad37b7912797d2077718476bf91517641c92b3639/jiter-0.13.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e404ea551d35438013c64b4f357b0474c7abf9f781c06d44fcaf7a14c69ff9e2", size = 513990, upload-time = "2026-02-02T12:36:09.993Z" }, + { url = "https://files.pythonhosted.org/packages/e9/34/2c9355247d6debad57a0a15e76ab1566ab799388042743656e566b3b7de1/jiter-0.13.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1f4748aad1b4a93c8bdd70f604d0f748cdc0e8744c5547798acfa52f10e79228", size = 548021, upload-time = "2026-02-02T12:36:11.376Z" }, + { url = "https://files.pythonhosted.org/packages/ac/4a/9f2c23255d04a834398b9c2e0e665382116911dc4d06b795710503cdad25/jiter-0.13.0-cp312-cp312-win32.whl", hash = "sha256:0bf670e3b1445fc4d31612199f1744f67f889ee1bbae703c4b54dc097e5dd394", size = 203024, upload-time = "2026-02-02T12:36:12.682Z" }, + { url = "https://files.pythonhosted.org/packages/09/ee/f0ae675a957ae5a8f160be3e87acea6b11dc7b89f6b7ab057e77b2d2b13a/jiter-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:15db60e121e11fe186c0b15236bd5d18381b9ddacdcf4e659feb96fc6c969c92", size = 205424, upload-time = "2026-02-02T12:36:13.93Z" }, + { url = "https://files.pythonhosted.org/packages/1b/02/ae611edf913d3cbf02c97cdb90374af2082c48d7190d74c1111dde08bcdd/jiter-0.13.0-cp312-cp312-win_arm64.whl", hash = "sha256:41f92313d17989102f3cb5dd533a02787cdb99454d494344b0361355da52fcb9", size = 186818, upload-time = "2026-02-02T12:36:15.308Z" }, + { url = "https://files.pythonhosted.org/packages/91/9c/7ee5a6ff4b9991e1a45263bfc46731634c4a2bde27dfda6c8251df2d958c/jiter-0.13.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:1f8a55b848cbabf97d861495cd65f1e5c590246fabca8b48e1747c4dfc8f85bf", size = 306897, upload-time = "2026-02-02T12:36:16.748Z" }, + { url = "https://files.pythonhosted.org/packages/7c/02/be5b870d1d2be5dd6a91bdfb90f248fbb7dcbd21338f092c6b89817c3dbf/jiter-0.13.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f556aa591c00f2c45eb1b89f68f52441a016034d18b65da60e2d2875bbbf344a", size = 317507, upload-time = "2026-02-02T12:36:18.351Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/b25d2ec333615f5f284f3a4024f7ce68cfa0604c322c6808b2344c7f5d2b/jiter-0.13.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7e1d61da332ec412350463891923f960c3073cf1aae93b538f0bb4c8cd46efb", size = 350560, upload-time = "2026-02-02T12:36:19.746Z" }, + { url = "https://files.pythonhosted.org/packages/be/ec/74dcb99fef0aca9fbe56b303bf79f6bd839010cb18ad41000bf6cc71eec0/jiter-0.13.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:3097d665a27bc96fd9bbf7f86178037db139f319f785e4757ce7ccbf390db6c2", size = 363232, upload-time = "2026-02-02T12:36:21.243Z" }, + { url = "https://files.pythonhosted.org/packages/1b/37/f17375e0bb2f6a812d4dd92d7616e41917f740f3e71343627da9db2824ce/jiter-0.13.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d01ecc3a8cbdb6f25a37bd500510550b64ddf9f7d64a107d92f3ccb25035d0f", size = 483727, upload-time = "2026-02-02T12:36:22.688Z" }, + { url = "https://files.pythonhosted.org/packages/77/d2/a71160a5ae1a1e66c1395b37ef77da67513b0adba73b993a27fbe47eb048/jiter-0.13.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed9bbc30f5d60a3bdf63ae76beb3f9db280d7f195dfcfa61af792d6ce912d159", size = 370799, upload-time = "2026-02-02T12:36:24.106Z" }, + { url = "https://files.pythonhosted.org/packages/01/99/ed5e478ff0eb4e8aa5fd998f9d69603c9fd3f32de3bd16c2b1194f68361c/jiter-0.13.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98fbafb6e88256f4454de33c1f40203d09fc33ed19162a68b3b257b29ca7f663", size = 359120, upload-time = "2026-02-02T12:36:25.519Z" }, + { url = "https://files.pythonhosted.org/packages/16/be/7ffd08203277a813f732ba897352797fa9493faf8dc7995b31f3d9cb9488/jiter-0.13.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5467696f6b827f1116556cb0db620440380434591e93ecee7fd14d1a491b6daa", size = 390664, upload-time = "2026-02-02T12:36:26.866Z" }, + { url = "https://files.pythonhosted.org/packages/d1/84/e0787856196d6d346264d6dcccb01f741e5f0bd014c1d9a2ebe149caf4f3/jiter-0.13.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:2d08c9475d48b92892583df9da592a0e2ac49bcd41fae1fec4f39ba6cf107820", size = 513543, upload-time = "2026-02-02T12:36:28.217Z" }, + { url = "https://files.pythonhosted.org/packages/65/50/ecbd258181c4313cf79bca6c88fb63207d04d5bf5e4f65174114d072aa55/jiter-0.13.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:aed40e099404721d7fcaf5b89bd3b4568a4666358bcac7b6b15c09fb6252ab68", size = 547262, upload-time = "2026-02-02T12:36:29.678Z" }, + { url = "https://files.pythonhosted.org/packages/27/da/68f38d12e7111d2016cd198161b36e1f042bd115c169255bcb7ec823a3bf/jiter-0.13.0-cp313-cp313-win32.whl", hash = "sha256:36ebfbcffafb146d0e6ffb3e74d51e03d9c35ce7c625c8066cdbfc7b953bdc72", size = 200630, upload-time = "2026-02-02T12:36:31.808Z" }, + { url = "https://files.pythonhosted.org/packages/25/65/3bd1a972c9a08ecd22eb3b08a95d1941ebe6938aea620c246cf426ae09c2/jiter-0.13.0-cp313-cp313-win_amd64.whl", hash = "sha256:8d76029f077379374cf0dbc78dbe45b38dec4a2eb78b08b5194ce836b2517afc", size = 202602, upload-time = "2026-02-02T12:36:33.679Z" }, + { url = "https://files.pythonhosted.org/packages/15/fe/13bd3678a311aa67686bb303654792c48206a112068f8b0b21426eb6851e/jiter-0.13.0-cp313-cp313-win_arm64.whl", hash = "sha256:bb7613e1a427cfcb6ea4544f9ac566b93d5bf67e0d48c787eca673ff9c9dff2b", size = 185939, upload-time = "2026-02-02T12:36:35.065Z" }, + { url = "https://files.pythonhosted.org/packages/49/19/a929ec002ad3228bc97ca01dbb14f7632fffdc84a95ec92ceaf4145688ae/jiter-0.13.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:fa476ab5dd49f3bf3a168e05f89358c75a17608dbabb080ef65f96b27c19ab10", size = 316616, upload-time = "2026-02-02T12:36:36.579Z" }, + { url = "https://files.pythonhosted.org/packages/52/56/d19a9a194afa37c1728831e5fb81b7722c3de18a3109e8f282bfc23e587a/jiter-0.13.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ade8cb6ff5632a62b7dbd4757d8c5573f7a2e9ae285d6b5b841707d8363205ef", size = 346850, upload-time = "2026-02-02T12:36:38.058Z" }, + { url = "https://files.pythonhosted.org/packages/36/4a/94e831c6bf287754a8a019cb966ed39ff8be6ab78cadecf08df3bb02d505/jiter-0.13.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9950290340acc1adaded363edd94baebcee7dabdfa8bee4790794cd5cfad2af6", size = 358551, upload-time = "2026-02-02T12:36:39.417Z" }, + { url = "https://files.pythonhosted.org/packages/a2/ec/a4c72c822695fa80e55d2b4142b73f0012035d9fcf90eccc56bc060db37c/jiter-0.13.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2b4972c6df33731aac0742b64fd0d18e0a69bc7d6e03108ce7d40c85fd9e3e6d", size = 201950, upload-time = "2026-02-02T12:36:40.791Z" }, + { url = "https://files.pythonhosted.org/packages/b6/00/393553ec27b824fbc29047e9c7cd4a3951d7fbe4a76743f17e44034fa4e4/jiter-0.13.0-cp313-cp313t-win_arm64.whl", hash = "sha256:701a1e77d1e593c1b435315ff625fd071f0998c5f02792038a5ca98899261b7d", size = 185852, upload-time = "2026-02-02T12:36:42.077Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f5/f1997e987211f6f9bd71b8083047b316208b4aca0b529bb5f8c96c89ef3e/jiter-0.13.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:cc5223ab19fe25e2f0bf2643204ad7318896fe3729bf12fde41b77bfc4fafff0", size = 308804, upload-time = "2026-02-02T12:36:43.496Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8f/5482a7677731fd44881f0204981ce2d7175db271f82cba2085dd2212e095/jiter-0.13.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9776ebe51713acf438fd9b4405fcd86893ae5d03487546dae7f34993217f8a91", size = 318787, upload-time = "2026-02-02T12:36:45.071Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b9/7257ac59778f1cd025b26a23c5520a36a424f7f1b068f2442a5b499b7464/jiter-0.13.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:879e768938e7b49b5e90b7e3fecc0dbec01b8cb89595861fb39a8967c5220d09", size = 353880, upload-time = "2026-02-02T12:36:47.365Z" }, + { url = "https://files.pythonhosted.org/packages/c3/87/719eec4a3f0841dad99e3d3604ee4cba36af4419a76f3cb0b8e2e691ad67/jiter-0.13.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:682161a67adea11e3aae9038c06c8b4a9a71023228767477d683f69903ebc607", size = 366702, upload-time = "2026-02-02T12:36:48.871Z" }, + { url = "https://files.pythonhosted.org/packages/d2/65/415f0a75cf6921e43365a1bc227c565cb949caca8b7532776e430cbaa530/jiter-0.13.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a13b68cd1cd8cc9de8f244ebae18ccb3e4067ad205220ef324c39181e23bbf66", size = 486319, upload-time = "2026-02-02T12:36:53.006Z" }, + { url = "https://files.pythonhosted.org/packages/54/a2/9e12b48e82c6bbc6081fd81abf915e1443add1b13d8fc586e1d90bb02bb8/jiter-0.13.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87ce0f14c6c08892b610686ae8be350bf368467b6acd5085a5b65441e2bf36d2", size = 372289, upload-time = "2026-02-02T12:36:54.593Z" }, + { url = "https://files.pythonhosted.org/packages/4e/c1/e4693f107a1789a239c759a432e9afc592366f04e901470c2af89cfd28e1/jiter-0.13.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c365005b05505a90d1c47856420980d0237adf82f70c4aff7aebd3c1cc143ad", size = 360165, upload-time = "2026-02-02T12:36:56.112Z" }, + { url = "https://files.pythonhosted.org/packages/17/08/91b9ea976c1c758240614bd88442681a87672eebc3d9a6dde476874e706b/jiter-0.13.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1317fdffd16f5873e46ce27d0e0f7f4f90f0cdf1d86bf6abeaea9f63ca2c401d", size = 389634, upload-time = "2026-02-02T12:36:57.495Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/58325ef99390d6d40427ed6005bf1ad54f2577866594bcf13ce55675f87d/jiter-0.13.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:c05b450d37ba0c9e21c77fef1f205f56bcee2330bddca68d344baebfc55ae0df", size = 514933, upload-time = "2026-02-02T12:36:58.909Z" }, + { url = "https://files.pythonhosted.org/packages/5b/25/69f1120c7c395fd276c3996bb8adefa9c6b84c12bb7111e5c6ccdcd8526d/jiter-0.13.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:775e10de3849d0631a97c603f996f518159272db00fdda0a780f81752255ee9d", size = 548842, upload-time = "2026-02-02T12:37:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/18/05/981c9669d86850c5fbb0d9e62bba144787f9fba84546ba43d624ee27ef29/jiter-0.13.0-cp314-cp314-win32.whl", hash = "sha256:632bf7c1d28421c00dd8bbb8a3bac5663e1f57d5cd5ed962bce3c73bf62608e6", size = 202108, upload-time = "2026-02-02T12:37:01.718Z" }, + { url = "https://files.pythonhosted.org/packages/8d/96/cdcf54dd0b0341db7d25413229888a346c7130bd20820530905fdb65727b/jiter-0.13.0-cp314-cp314-win_amd64.whl", hash = "sha256:f22ef501c3f87ede88f23f9b11e608581c14f04db59b6a801f354397ae13739f", size = 204027, upload-time = "2026-02-02T12:37:03.075Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f9/724bcaaab7a3cd727031fe4f6995cb86c4bd344909177c186699c8dec51a/jiter-0.13.0-cp314-cp314-win_arm64.whl", hash = "sha256:07b75fe09a4ee8e0c606200622e571e44943f47254f95e2436c8bdcaceb36d7d", size = 187199, upload-time = "2026-02-02T12:37:04.414Z" }, + { url = "https://files.pythonhosted.org/packages/62/92/1661d8b9fd6a3d7a2d89831db26fe3c1509a287d83ad7838831c7b7a5c7e/jiter-0.13.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:964538479359059a35fb400e769295d4b315ae61e4105396d355a12f7fef09f0", size = 318423, upload-time = "2026-02-02T12:37:05.806Z" }, + { url = "https://files.pythonhosted.org/packages/4f/3b/f77d342a54d4ebcd128e520fc58ec2f5b30a423b0fd26acdfc0c6fef8e26/jiter-0.13.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e104da1db1c0991b3eaed391ccd650ae8d947eab1480c733e5a3fb28d4313e40", size = 351438, upload-time = "2026-02-02T12:37:07.189Z" }, + { url = "https://files.pythonhosted.org/packages/76/b3/ba9a69f0e4209bd3331470c723c2f5509e6f0482e416b612431a5061ed71/jiter-0.13.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e3a5f0cde8ff433b8e88e41aa40131455420fb3649a3c7abdda6145f8cb7202", size = 364774, upload-time = "2026-02-02T12:37:08.579Z" }, + { url = "https://files.pythonhosted.org/packages/b3/16/6cdb31fa342932602458dbb631bfbd47f601e03d2e4950740e0b2100b570/jiter-0.13.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:57aab48f40be1db920a582b30b116fe2435d184f77f0e4226f546794cedd9cf0", size = 487238, upload-time = "2026-02-02T12:37:10.066Z" }, + { url = "https://files.pythonhosted.org/packages/ed/b1/956cc7abaca8d95c13aa8d6c9b3f3797241c246cd6e792934cc4c8b250d2/jiter-0.13.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7772115877c53f62beeb8fd853cab692dbc04374ef623b30f997959a4c0e7e95", size = 372892, upload-time = "2026-02-02T12:37:11.656Z" }, + { url = "https://files.pythonhosted.org/packages/26/c4/97ecde8b1e74f67b8598c57c6fccf6df86ea7861ed29da84629cdbba76c4/jiter-0.13.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1211427574b17b633cfceba5040de8081e5abf114f7a7602f73d2e16f9fdaa59", size = 360309, upload-time = "2026-02-02T12:37:13.244Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d7/eabe3cf46715854ccc80be2cd78dd4c36aedeb30751dbf85a1d08c14373c/jiter-0.13.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7beae3a3d3b5212d3a55d2961db3c292e02e302feb43fce6a3f7a31b90ea6dfe", size = 389607, upload-time = "2026-02-02T12:37:14.881Z" }, + { url = "https://files.pythonhosted.org/packages/df/2d/03963fc0804e6109b82decfb9974eb92df3797fe7222428cae12f8ccaa0c/jiter-0.13.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e5562a0f0e90a6223b704163ea28e831bd3a9faa3512a711f031611e6b06c939", size = 514986, upload-time = "2026-02-02T12:37:16.326Z" }, + { url = "https://files.pythonhosted.org/packages/f6/6c/8c83b45eb3eb1c1e18d841fe30b4b5bc5619d781267ca9bc03e005d8fd0a/jiter-0.13.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:6c26a424569a59140fb51160a56df13f438a2b0967365e987889186d5fc2f6f9", size = 548756, upload-time = "2026-02-02T12:37:17.736Z" }, + { url = "https://files.pythonhosted.org/packages/47/66/eea81dfff765ed66c68fd2ed8c96245109e13c896c2a5015c7839c92367e/jiter-0.13.0-cp314-cp314t-win32.whl", hash = "sha256:24dc96eca9f84da4131cdf87a95e6ce36765c3b156fc9ae33280873b1c32d5f6", size = 201196, upload-time = "2026-02-02T12:37:19.101Z" }, + { url = "https://files.pythonhosted.org/packages/ff/32/4ac9c7a76402f8f00d00842a7f6b83b284d0cf7c1e9d4227bc95aa6d17fa/jiter-0.13.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0a8d76c7524087272c8ae913f5d9d608bd839154b62c4322ef65723d2e5bb0b8", size = 204215, upload-time = "2026-02-02T12:37:20.495Z" }, + { url = "https://files.pythonhosted.org/packages/f9/8e/7def204fea9f9be8b3c21a6f2dd6c020cf56c7d5ff753e0e23ed7f9ea57e/jiter-0.13.0-cp314-cp314t-win_arm64.whl", hash = "sha256:2c26cf47e2cad140fa23b6d58d435a7c0161f5c514284802f25e87fddfe11024", size = 187152, upload-time = "2026-02-02T12:37:22.124Z" }, + { url = "https://files.pythonhosted.org/packages/80/60/e50fa45dd7e2eae049f0ce964663849e897300433921198aef94b6ffa23a/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:3d744a6061afba08dd7ae375dcde870cffb14429b7477e10f67e9e6d68772a0a", size = 305169, upload-time = "2026-02-02T12:37:50.376Z" }, + { url = "https://files.pythonhosted.org/packages/d2/73/a009f41c5eed71c49bec53036c4b33555afcdee70682a18c6f66e396c039/jiter-0.13.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:ff732bd0a0e778f43d5009840f20b935e79087b4dc65bd36f1cd0f9b04b8ff7f", size = 303808, upload-time = "2026-02-02T12:37:52.092Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/528b439290763bff3d939268085d03382471b442f212dca4ff5f12802d43/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab44b178f7981fcaea7e0a5df20e773c663d06ffda0198f1a524e91b2fde7e59", size = 337384, upload-time = "2026-02-02T12:37:53.582Z" }, + { url = "https://files.pythonhosted.org/packages/67/8a/a342b2f0251f3dac4ca17618265d93bf244a2a4d089126e81e4c1056ac50/jiter-0.13.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bb00b6d26db67a05fe3e12c76edc75f32077fb51deed13822dc648fa373bc19", size = 343768, upload-time = "2026-02-02T12:37:55.055Z" }, +] + +[[package]] +name = "jsonpatch" +version = "1.33" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpointer" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/78/18813351fe5d63acad16aec57f94ec2b70a09e53ca98145589e185423873/jsonpatch-1.33.tar.gz", hash = "sha256:9fcd4009c41e6d12348b4a0ff2563ba56a2923a7dfee731d004e212e1ee5030c", size = 21699, upload-time = "2023-06-26T12:07:29.144Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/73/07/02e16ed01e04a374e644b575638ec7987ae846d25ad97bcc9945a3ee4b0e/jsonpatch-1.33-py2.py3-none-any.whl", hash = "sha256:0ae28c0cd062bbd8b8ecc26d7d164fbbea9652a1a3693f3b956c1eae5145dade", size = 12898, upload-time = "2023-06-16T21:01:28.466Z" }, +] + +[[package]] +name = "jsonpointer" +version = "3.0.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" }, +] + +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + +[[package]] +name = "langchain" +version = "1.2.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph" }, + { name = "pydantic" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/16/22/a4d4ac98fc2e393537130bbfba0d71a8113e6f884d96f935923e247397fe/langchain-1.2.10.tar.gz", hash = "sha256:bdcd7218d9c79a413cf15e106e4eb94408ac0963df9333ccd095b9ed43bf3be7", size = 570071, upload-time = "2026-02-10T14:56:49.74Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/7c/06/c3394327f815fade875724c0f6cff529777c96a1e17fea066deb997f8cf5/langchain-1.2.10-py3-none-any.whl", hash = "sha256:e07a377204451fffaed88276b8193e894893b1003e25c5bca6539288ccca3698", size = 111738, upload-time = "2026-02-10T14:56:47.985Z" }, +] + +[[package]] +name = "langchain-core" +version = "1.2.17" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "jsonpatch" }, + { name = "langsmith" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tenacity" }, + { name = "typing-extensions" }, + { name = "uuid-utils" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1d/93/36226f593df52b871fc24d494c274f3a6b2ac76763a2806e7d35611634a1/langchain_core-1.2.17.tar.gz", hash = "sha256:54aa267f3311e347fb2e50951fe08e53761cebfb999ab80e6748d70525bbe872", size = 836130, upload-time = "2026-03-02T22:47:55.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/be/90/073f33ab383a62908eca7ea699586dfea280e77182176e33199c80ddf22a/langchain_core-1.2.17-py3-none-any.whl", hash = "sha256:bf6bd6ce503874e9c2da1669a69383e967c3de1ea808921d19a9a6bff1a9fbbe", size = 502727, upload-time = "2026-03-02T22:47:54.537Z" }, +] + +[[package]] +name = "langchain-mcp-adapters" +version = "0.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "mcp" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d9/52/cebf0ef5b1acef6cbc63d671171d43af70f12d19f55577909c7afa79fb6e/langchain_mcp_adapters-0.2.1.tar.gz", hash = "sha256:58e64c44e8df29ca7eb3b656cf8c9931ef64386534d7ca261982e3bdc63f3176", size = 36394, upload-time = "2025-12-09T16:28:38.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/81/b2479eb26861ab36be851026d004b2d391d789b7856e44c272b12828ece0/langchain_mcp_adapters-0.2.1-py3-none-any.whl", hash = "sha256:9f96ad4c64230f6757297fec06fde19d772c99dbdfbca987f7b7cfd51ff77240", size = 22708, upload-time = "2025-12-09T16:28:37.877Z" }, +] + +[[package]] +name = "langchain-openai" +version = "1.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "openai" }, + { name = "tiktoken" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d6/0f/01147f842499338ae3b0dd0a351fb83006d9ed623cf3a999bd68ba5bbe2d/langchain_openai-1.1.10.tar.gz", hash = "sha256:ca6fae7cf19425acc81814efed59c7d205ec9a1f284fd1d08aae9bda85d6501b", size = 1059755, upload-time = "2026-02-17T18:03:44.506Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/72/17/3785cbcdc81c451179247e4176d2697879cb4f45ab2c59d949ca574e072d/langchain_openai-1.1.10-py3-none-any.whl", hash = "sha256:d91b2c09e9fbc70f7af45345d3aa477744962d41c73a029beb46b4f83b824827", size = 87205, upload-time = "2026-02-17T18:03:43.502Z" }, +] + +[[package]] +name = "langgraph" +version = "1.0.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, + { name = "langgraph-prebuilt" }, + { name = "langgraph-sdk" }, + { name = "pydantic" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/92/14df6fefba28c10caf1cb05aa5b8c7bf005838fe32a86d903b6c7cc4018d/langgraph-1.0.10.tar.gz", hash = "sha256:73bd10ee14a8020f31ef07e9cd4c1a70c35cc07b9c2b9cd637509a10d9d51e29", size = 511644, upload-time = "2026-02-27T21:04:38.743Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/60/260e0c04620a37ba8916b712766c341cc5fc685dabc6948c899494bbc2ae/langgraph-1.0.10-py3-none-any.whl", hash = "sha256:7c298bef4f6ea292fcf9824d6088fe41a6727e2904ad6066f240c4095af12247", size = 160920, upload-time = "2026-02-27T21:04:35.932Z" }, +] + +[[package]] +name = "langgraph-checkpoint" +version = "4.0.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "ormsgpack" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b1/44/a8df45d1e8b4637e29789fa8bae1db022c953cc7ac80093cfc52e923547e/langgraph_checkpoint-4.0.1.tar.gz", hash = "sha256:b433123735df11ade28829e40ce25b9be614930cd50245ff2af60629234befd9", size = 158135, upload-time = "2026-02-27T21:06:16.092Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/65/4c/09a4a0c42f5d2fc38d6c4d67884788eff7fd2cfdf367fdf7033de908b4c0/langgraph_checkpoint-4.0.1-py3-none-any.whl", hash = "sha256:e3adcd7a0e0166f3b48b8cf508ce0ea366e7420b5a73aa81289888727769b034", size = 50453, upload-time = "2026-02-27T21:06:14.293Z" }, +] + +[[package]] +name = "langgraph-prebuilt" +version = "1.0.8" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "langchain-core" }, + { name = "langgraph-checkpoint" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/0d/06/dd61a5c2dce009d1b03b1d56f2a85b3127659fdddf5b3be5d8f1d60820fb/langgraph_prebuilt-1.0.8.tar.gz", hash = "sha256:0cd3cf5473ced8a6cd687cc5294e08d3de57529d8dd14fdc6ae4899549efcf69", size = 164442, upload-time = "2026-02-19T18:14:39.083Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/41/ec966424ad3f2ed3996d24079d3342c8cd6c0bd0653c12b2a917a685ec6c/langgraph_prebuilt-1.0.8-py3-none-any.whl", hash = "sha256:d16a731e591ba4470f3e313a319c7eee7dbc40895bcf15c821f985a3522a7ce0", size = 35648, upload-time = "2026-02-19T18:14:37.611Z" }, +] + +[[package]] +name = "langgraph-sdk" +version = "0.3.9" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/3a/bd/ca8ae5c6a34be6d4f7aa86016e010ff96b3a939456041565797952e3014d/langgraph_sdk-0.3.9.tar.gz", hash = "sha256:8be8958529b3f6d493ec248fdb46e539362efda75784654a42a7091d22504e0e", size = 184287, upload-time = "2026-02-24T18:39:03.276Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6a/4c/7a7510260fbda788efd13bf4650d3e7d80988118441ac811ec78e0aa03ac/langgraph_sdk-0.3.9-py3-none-any.whl", hash = "sha256:94654294250c920789b6ed0d8a70c0117fed5736b61efc24ff647157359453c5", size = 90511, upload-time = "2026-02-24T18:39:02.012Z" }, +] + +[[package]] +name = "langsmith" +version = "0.7.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "httpx" }, + { name = "orjson", marker = "platform_python_implementation != 'PyPy'" }, + { name = "packaging" }, + { name = "pydantic" }, + { name = "requests" }, + { name = "requests-toolbelt" }, + { name = "uuid-utils" }, + { name = "xxhash" }, + { name = "zstandard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/26/aa913264d81039f5ea1a396507042534f4fa848621a4a96b104cd44b8aaf/langsmith-0.7.10.tar.gz", hash = "sha256:50163f7016f182907077eb086dbfa84bf576a4b3d6b21ef2565f52169de3579d", size = 1105854, upload-time = "2026-03-03T02:31:00.823Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/28/a2d78620dbdc82119151c832b214a68dbf8962636b25ed7ac25de7820484/langsmith-0.7.10-py3-none-any.whl", hash = "sha256:90b92623c2d7b832ce081a7b3f214ac2e5ec6c5f5af1b28c14b5d32ad6726fcc", size = 344388, upload-time = "2026-03-03T02:30:58.922Z" }, +] + +[[package]] +name = "mcp" +version = "1.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "httpx" }, + { name = "httpx-sse" }, + { name = "jsonschema" }, + { name = "pydantic" }, + { name = "pydantic-settings" }, + { name = "pyjwt", extra = ["crypto"] }, + { name = "python-multipart" }, + { name = "pywin32", marker = "sys_platform == 'win32'" }, + { name = "sse-starlette" }, + { name = "starlette" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, + { name = "uvicorn", marker = "sys_platform != 'emscripten'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/fc/6d/62e76bbb8144d6ed86e202b5edd8a4cb631e7c8130f3f4893c3f90262b10/mcp-1.26.0.tar.gz", hash = "sha256:db6e2ef491eecc1a0d93711a76f28dec2e05999f93afd48795da1c1137142c66", size = 608005, upload-time = "2026-01-24T19:40:32.468Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fd/d9/eaa1f80170d2b7c5ba23f3b59f766f3a0bb41155fbc32a69adfa1adaaef9/mcp-1.26.0-py3-none-any.whl", hash = "sha256:904a21c33c25aa98ddbeb47273033c435e595bbacfdb177f4bd87f6dceebe1ca", size = 233615, upload-time = "2026-01-24T19:40:30.652Z" }, +] + +[[package]] +name = "openai" +version = "2.24.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "distro" }, + { name = "httpx" }, + { name = "jiter" }, + { name = "pydantic" }, + { name = "sniffio" }, + { name = "tqdm" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/13/17e87641b89b74552ed408a92b231283786523edddc95f3545809fab673c/openai-2.24.0.tar.gz", hash = "sha256:1e5769f540dbd01cb33bc4716a23e67b9d695161a734aff9c5f925e2bf99a673", size = 658717, upload-time = "2026-02-24T20:02:07.958Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c9/30/844dc675ee6902579b8eef01ed23917cc9319a1c9c0c14ec6e39340c96d0/openai-2.24.0-py3-none-any.whl", hash = "sha256:fed30480d7d6c884303287bde864980a4b137b60553ffbcf9ab4a233b7a73d94", size = 1120122, upload-time = "2026-02-24T20:02:05.669Z" }, +] + +[[package]] +name = "orjson" +version = "3.11.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/53/45/b268004f745ede84e5798b48ee12b05129d19235d0e15267aa57dcdb400b/orjson-3.11.7.tar.gz", hash = "sha256:9b1a67243945819ce55d24a30b59d6a168e86220452d2c96f4d1f093e71c0c49", size = 6144992, upload-time = "2026-02-02T15:38:49.29Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/80/bf/76f4f1665f6983385938f0e2a5d7efa12a58171b8456c252f3bae8a4cf75/orjson-3.11.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:bd03ea7606833655048dab1a00734a2875e3e86c276e1d772b2a02556f0d895f", size = 228545, upload-time = "2026-02-02T15:37:46.376Z" }, + { url = "https://files.pythonhosted.org/packages/79/53/6c72c002cb13b5a978a068add59b25a8bdf2800ac1c9c8ecdb26d6d97064/orjson-3.11.7-cp312-cp312-macosx_15_0_arm64.whl", hash = "sha256:89e440ebc74ce8ab5c7bc4ce6757b4a6b1041becb127df818f6997b5c71aa60b", size = 125224, upload-time = "2026-02-02T15:37:47.697Z" }, + { url = "https://files.pythonhosted.org/packages/2c/83/10e48852865e5dd151bdfe652c06f7da484578ed02c5fca938e3632cb0b8/orjson-3.11.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5ede977b5fe5ac91b1dffc0a517ca4542d2ec8a6a4ff7b2652d94f640796342a", size = 128154, upload-time = "2026-02-02T15:37:48.954Z" }, + { url = "https://files.pythonhosted.org/packages/6e/52/a66e22a2b9abaa374b4a081d410edab6d1e30024707b87eab7c734afe28d/orjson-3.11.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b7b1dae39230a393df353827c855a5f176271c23434cfd2db74e0e424e693e10", size = 123548, upload-time = "2026-02-02T15:37:50.187Z" }, + { url = "https://files.pythonhosted.org/packages/de/38/605d371417021359f4910c496f764c48ceb8997605f8c25bf1dfe58c0ebe/orjson-3.11.7-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed46f17096e28fb28d2975834836a639af7278aa87c84f68ab08fbe5b8bd75fa", size = 129000, upload-time = "2026-02-02T15:37:51.426Z" }, + { url = "https://files.pythonhosted.org/packages/44/98/af32e842b0ffd2335c89714d48ca4e3917b42f5d6ee5537832e069a4b3ac/orjson-3.11.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3726be79e36e526e3d9c1aceaadbfb4a04ee80a72ab47b3f3c17fefb9812e7b8", size = 141686, upload-time = "2026-02-02T15:37:52.607Z" }, + { url = "https://files.pythonhosted.org/packages/96/0b/fc793858dfa54be6feee940c1463370ece34b3c39c1ca0aa3845f5ba9892/orjson-3.11.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0724e265bc548af1dedebd9cb3d24b4e1c1e685a343be43e87ba922a5c5fff2f", size = 130812, upload-time = "2026-02-02T15:37:53.944Z" }, + { url = "https://files.pythonhosted.org/packages/dc/91/98a52415059db3f374757d0b7f0f16e3b5cd5976c90d1c2b56acaea039e6/orjson-3.11.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7745312efa9e11c17fbd3cb3097262d079da26930ae9ae7ba28fb738367cbad", size = 133440, upload-time = "2026-02-02T15:37:55.615Z" }, + { url = "https://files.pythonhosted.org/packages/dc/b6/cb540117bda61791f46381f8c26c8f93e802892830a6055748d3bb1925ab/orjson-3.11.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f904c24bdeabd4298f7a977ef14ca2a022ca921ed670b92ecd16ab6f3d01f867", size = 138386, upload-time = "2026-02-02T15:37:56.814Z" }, + { url = "https://files.pythonhosted.org/packages/63/1a/50a3201c334a7f17c231eee5f841342190723794e3b06293f26e7cf87d31/orjson-3.11.7-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:b9fc4d0f81f394689e0814617aadc4f2ea0e8025f38c226cbf22d3b5ddbf025d", size = 408853, upload-time = "2026-02-02T15:37:58.291Z" }, + { url = "https://files.pythonhosted.org/packages/87/cd/8de1c67d0be44fdc22701e5989c0d015a2adf391498ad42c4dc589cd3013/orjson-3.11.7-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:849e38203e5be40b776ed2718e587faf204d184fc9a008ae441f9442320c0cab", size = 144130, upload-time = "2026-02-02T15:38:00.163Z" }, + { url = "https://files.pythonhosted.org/packages/0f/fe/d605d700c35dd55f51710d159fc54516a280923cd1b7e47508982fbb387d/orjson-3.11.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4682d1db3bcebd2b64757e0ddf9e87ae5f00d29d16c5cdf3a62f561d08cc3dd2", size = 134818, upload-time = "2026-02-02T15:38:01.507Z" }, + { url = "https://files.pythonhosted.org/packages/e4/e4/15ecc67edb3ddb3e2f46ae04475f2d294e8b60c1825fbe28a428b93b3fbd/orjson-3.11.7-cp312-cp312-win32.whl", hash = "sha256:f4f7c956b5215d949a1f65334cf9d7612dde38f20a95f2315deef167def91a6f", size = 127923, upload-time = "2026-02-02T15:38:02.75Z" }, + { url = "https://files.pythonhosted.org/packages/34/70/2e0855361f76198a3965273048c8e50a9695d88cd75811a5b46444895845/orjson-3.11.7-cp312-cp312-win_amd64.whl", hash = "sha256:bf742e149121dc5648ba0a08ea0871e87b660467ef168a3a5e53bc1fbd64bb74", size = 125007, upload-time = "2026-02-02T15:38:04.032Z" }, + { url = "https://files.pythonhosted.org/packages/68/40/c2051bd19fc467610fed469dc29e43ac65891571138f476834ca192bc290/orjson-3.11.7-cp312-cp312-win_arm64.whl", hash = "sha256:26c3b9132f783b7d7903bf1efb095fed8d4a3a85ec0d334ee8beff3d7a4749d5", size = 126089, upload-time = "2026-02-02T15:38:05.297Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/6e0e52cac5aab51d7b6dcd257e855e1dec1c2060f6b28566c509b4665f62/orjson-3.11.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:1d98b30cc1313d52d4af17d9c3d307b08389752ec5f2e5febdfada70b0f8c733", size = 228390, upload-time = "2026-02-02T15:38:06.8Z" }, + { url = "https://files.pythonhosted.org/packages/a5/29/a77f48d2fc8a05bbc529e5ff481fb43d914f9e383ea2469d4f3d51df3d00/orjson-3.11.7-cp313-cp313-macosx_15_0_arm64.whl", hash = "sha256:d897e81f8d0cbd2abb82226d1860ad2e1ab3ff16d7b08c96ca00df9d45409ef4", size = 125189, upload-time = "2026-02-02T15:38:08.181Z" }, + { url = "https://files.pythonhosted.org/packages/89/25/0a16e0729a0e6a1504f9d1a13cdd365f030068aab64cec6958396b9969d7/orjson-3.11.7-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814be4b49b228cfc0b3c565acf642dd7d13538f966e3ccde61f4f55be3e20785", size = 128106, upload-time = "2026-02-02T15:38:09.41Z" }, + { url = "https://files.pythonhosted.org/packages/66/da/a2e505469d60666a05ab373f1a6322eb671cb2ba3a0ccfc7d4bc97196787/orjson-3.11.7-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d06e5c5fed5caedd2e540d62e5b1c25e8c82431b9e577c33537e5fa4aa909539", size = 123363, upload-time = "2026-02-02T15:38:10.73Z" }, + { url = "https://files.pythonhosted.org/packages/23/bf/ed73f88396ea35c71b38961734ea4a4746f7ca0768bf28fd551d37e48dd0/orjson-3.11.7-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:31c80ce534ac4ea3739c5ee751270646cbc46e45aea7576a38ffec040b4029a1", size = 129007, upload-time = "2026-02-02T15:38:12.138Z" }, + { url = "https://files.pythonhosted.org/packages/73/3c/b05d80716f0225fc9008fbf8ab22841dcc268a626aa550561743714ce3bf/orjson-3.11.7-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f50979824bde13d32b4320eedd513431c921102796d86be3eee0b58e58a3ecd1", size = 141667, upload-time = "2026-02-02T15:38:13.398Z" }, + { url = "https://files.pythonhosted.org/packages/61/e8/0be9b0addd9bf86abfc938e97441dcd0375d494594b1c8ad10fe57479617/orjson-3.11.7-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9e54f3808e2b6b945078c41aa8d9b5834b28c50843846e97807e5adb75fa9705", size = 130832, upload-time = "2026-02-02T15:38:14.698Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ec/c68e3b9021a31d9ec15a94931db1410136af862955854ed5dd7e7e4f5bff/orjson-3.11.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a12b80df61aab7b98b490fe9e4879925ba666fccdfcd175252ce4d9035865ace", size = 133373, upload-time = "2026-02-02T15:38:16.109Z" }, + { url = "https://files.pythonhosted.org/packages/d2/45/f3466739aaafa570cc8e77c6dbb853c48bf56e3b43738020e2661e08b0ac/orjson-3.11.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:996b65230271f1a97026fd0e6a753f51fbc0c335d2ad0c6201f711b0da32693b", size = 138307, upload-time = "2026-02-02T15:38:17.453Z" }, + { url = "https://files.pythonhosted.org/packages/e1/84/9f7f02288da1ffb31405c1be07657afd1eecbcb4b64ee2817b6fe0f785fa/orjson-3.11.7-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:ab49d4b2a6a1d415ddb9f37a21e02e0d5dbfe10b7870b21bf779fc21e9156157", size = 408695, upload-time = "2026-02-02T15:38:18.831Z" }, + { url = "https://files.pythonhosted.org/packages/18/07/9dd2f0c0104f1a0295ffbe912bc8d63307a539b900dd9e2c48ef7810d971/orjson-3.11.7-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:390a1dce0c055ddf8adb6aa94a73b45a4a7d7177b5c584b8d1c1947f2ba60fb3", size = 144099, upload-time = "2026-02-02T15:38:20.28Z" }, + { url = "https://files.pythonhosted.org/packages/a5/66/857a8e4a3292e1f7b1b202883bcdeb43a91566cf59a93f97c53b44bd6801/orjson-3.11.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1eb80451a9c351a71dfaf5b7ccc13ad065405217726b59fdbeadbcc544f9d223", size = 134806, upload-time = "2026-02-02T15:38:22.186Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5b/6ebcf3defc1aab3a338ca777214966851e92efb1f30dc7fc8285216e6d1b/orjson-3.11.7-cp313-cp313-win32.whl", hash = "sha256:7477aa6a6ec6139c5cb1cc7b214643592169a5494d200397c7fc95d740d5fcf3", size = 127914, upload-time = "2026-02-02T15:38:23.511Z" }, + { url = "https://files.pythonhosted.org/packages/00/04/c6f72daca5092e3117840a1b1e88dfc809cc1470cf0734890d0366b684a1/orjson-3.11.7-cp313-cp313-win_amd64.whl", hash = "sha256:b9f95dcdea9d4f805daa9ddf02617a89e484c6985fa03055459f90e87d7a0757", size = 124986, upload-time = "2026-02-02T15:38:24.836Z" }, + { url = "https://files.pythonhosted.org/packages/03/ba/077a0f6f1085d6b806937246860fafbd5b17f3919c70ee3f3d8d9c713f38/orjson-3.11.7-cp313-cp313-win_arm64.whl", hash = "sha256:800988273a014a0541483dc81021247d7eacb0c845a9d1a34a422bc718f41539", size = 126045, upload-time = "2026-02-02T15:38:26.216Z" }, + { url = "https://files.pythonhosted.org/packages/e9/1e/745565dca749813db9a093c5ebc4bac1a9475c64d54b95654336ac3ed961/orjson-3.11.7-cp314-cp314-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:de0a37f21d0d364954ad5de1970491d7fbd0fb1ef7417d4d56a36dc01ba0c0a0", size = 228391, upload-time = "2026-02-02T15:38:27.757Z" }, + { url = "https://files.pythonhosted.org/packages/46/19/e40f6225da4d3aa0c8dc6e5219c5e87c2063a560fe0d72a88deb59776794/orjson-3.11.7-cp314-cp314-macosx_15_0_arm64.whl", hash = "sha256:c2428d358d85e8da9d37cba18b8c4047c55222007a84f97156a5b22028dfbfc0", size = 125188, upload-time = "2026-02-02T15:38:29.241Z" }, + { url = "https://files.pythonhosted.org/packages/9d/7e/c4de2babef2c0817fd1f048fd176aa48c37bec8aef53d2fa932983032cce/orjson-3.11.7-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4bc6c6ac52cdaa267552544c73e486fecbd710b7ac09bc024d5a78555a22f6", size = 128097, upload-time = "2026-02-02T15:38:30.618Z" }, + { url = "https://files.pythonhosted.org/packages/eb/74/233d360632bafd2197f217eee7fb9c9d0229eac0c18128aee5b35b0014fe/orjson-3.11.7-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bd0d68edd7dfca1b2eca9361a44ac9f24b078de3481003159929a0573f21a6bf", size = 123364, upload-time = "2026-02-02T15:38:32.363Z" }, + { url = "https://files.pythonhosted.org/packages/79/51/af79504981dd31efe20a9e360eb49c15f06df2b40e7f25a0a52d9ae888e8/orjson-3.11.7-cp314-cp314-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:623ad1b9548ef63886319c16fa317848e465a21513b31a6ad7b57443c3e0dcf5", size = 129076, upload-time = "2026-02-02T15:38:33.68Z" }, + { url = "https://files.pythonhosted.org/packages/67/e2/da898eb68b72304f8de05ca6715870d09d603ee98d30a27e8a9629abc64b/orjson-3.11.7-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6e776b998ac37c0396093d10290e60283f59cfe0fc3fccbd0ccc4bd04dd19892", size = 141705, upload-time = "2026-02-02T15:38:34.989Z" }, + { url = "https://files.pythonhosted.org/packages/c5/89/15364d92acb3d903b029e28d834edb8780c2b97404cbf7929aa6b9abdb24/orjson-3.11.7-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:652c6c3af76716f4a9c290371ba2e390ede06f6603edb277b481daf37f6f464e", size = 130855, upload-time = "2026-02-02T15:38:36.379Z" }, + { url = "https://files.pythonhosted.org/packages/c2/8b/ecdad52d0b38d4b8f514be603e69ccd5eacf4e7241f972e37e79792212ec/orjson-3.11.7-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56df3239294ea5964adf074c54bcc4f0ccd21636049a2cf3ca9cf03b5d03cf1", size = 133386, upload-time = "2026-02-02T15:38:37.704Z" }, + { url = "https://files.pythonhosted.org/packages/b9/0e/45e1dcf10e17d0924b7c9162f87ec7b4ca79e28a0548acf6a71788d3e108/orjson-3.11.7-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bda117c4148e81f746655d5a3239ae9bd00cb7bc3ca178b5fc5a5997e9744183", size = 138295, upload-time = "2026-02-02T15:38:39.096Z" }, + { url = "https://files.pythonhosted.org/packages/63/d7/4d2e8b03561257af0450f2845b91fbd111d7e526ccdf737267108075e0ba/orjson-3.11.7-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:23d6c20517a97a9daf1d48b580fcdc6f0516c6f4b5038823426033690b4d2650", size = 408720, upload-time = "2026-02-02T15:38:40.634Z" }, + { url = "https://files.pythonhosted.org/packages/78/cf/d45343518282108b29c12a65892445fc51f9319dc3c552ceb51bb5905ed2/orjson-3.11.7-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:8ff206156006da5b847c9304b6308a01e8cdbc8cce824e2779a5ba71c3def141", size = 144152, upload-time = "2026-02-02T15:38:42.262Z" }, + { url = "https://files.pythonhosted.org/packages/a9/3a/d6001f51a7275aacd342e77b735c71fa04125a3f93c36fee4526bc8c654e/orjson-3.11.7-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:962d046ee1765f74a1da723f4b33e3b228fe3a48bd307acce5021dfefe0e29b2", size = 134814, upload-time = "2026-02-02T15:38:43.627Z" }, + { url = "https://files.pythonhosted.org/packages/1d/d3/f19b47ce16820cc2c480f7f1723e17f6d411b3a295c60c8ad3aa9ff1c96a/orjson-3.11.7-cp314-cp314-win32.whl", hash = "sha256:89e13dd3f89f1c38a9c9eba5fbf7cdc2d1feca82f5f290864b4b7a6aac704576", size = 127997, upload-time = "2026-02-02T15:38:45.06Z" }, + { url = "https://files.pythonhosted.org/packages/12/df/172771902943af54bf661a8d102bdf2e7f932127968080632bda6054b62c/orjson-3.11.7-cp314-cp314-win_amd64.whl", hash = "sha256:845c3e0d8ded9c9271cd79596b9b552448b885b97110f628fb687aee2eed11c1", size = 124985, upload-time = "2026-02-02T15:38:46.388Z" }, + { url = "https://files.pythonhosted.org/packages/6f/1c/f2a8d8a1b17514660a614ce5f7aac74b934e69f5abc2700cc7ced882a009/orjson-3.11.7-cp314-cp314-win_arm64.whl", hash = "sha256:4a2e9c5be347b937a2e0203866f12bba36082e89b402ddb9e927d5822e43088d", size = 126038, upload-time = "2026-02-02T15:38:47.703Z" }, +] + +[[package]] +name = "ormsgpack" +version = "1.12.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/12/0c/f1761e21486942ab9bb6feaebc610fa074f7c5e496e6962dea5873348077/ormsgpack-1.12.2.tar.gz", hash = "sha256:944a2233640273bee67521795a73cf1e959538e0dfb7ac635505010455e53b33", size = 39031, upload-time = "2026-01-18T20:55:28.023Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/4c/36/16c4b1921c308a92cef3bf6663226ae283395aa0ff6e154f925c32e91ff5/ormsgpack-1.12.2-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7a29d09b64b9694b588ff2f80e9826bdceb3a2b91523c5beae1fab27d5c940e7", size = 378618, upload-time = "2026-01-18T20:55:50.835Z" }, + { url = "https://files.pythonhosted.org/packages/c0/68/468de634079615abf66ed13bb5c34ff71da237213f29294363beeeca5306/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b39e629fd2e1c5b2f46f99778450b59454d1f901bc507963168985e79f09c5d", size = 203186, upload-time = "2026-01-18T20:56:11.163Z" }, + { url = "https://files.pythonhosted.org/packages/73/a9/d756e01961442688b7939bacd87ce13bfad7d26ce24f910f6028178b2cc8/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:958dcb270d30a7cb633a45ee62b9444433fa571a752d2ca484efdac07480876e", size = 210738, upload-time = "2026-01-18T20:56:09.181Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ba/795b1036888542c9113269a3f5690ab53dd2258c6fb17676ac4bd44fcf94/ormsgpack-1.12.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58d379d72b6c5e964851c77cfedfb386e474adee4fd39791c2c5d9efb53505cc", size = 212569, upload-time = "2026-01-18T20:56:06.135Z" }, + { url = "https://files.pythonhosted.org/packages/6c/aa/bff73c57497b9e0cba8837c7e4bcab584b1a6dbc91a5dd5526784a5030c8/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8463a3fc5f09832e67bdb0e2fda6d518dc4281b133166146a67f54c08496442e", size = 387166, upload-time = "2026-01-18T20:55:36.738Z" }, + { url = "https://files.pythonhosted.org/packages/d3/cf/f8283cba44bcb7b14f97b6274d449db276b3a86589bdb363169b51bc12de/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:eddffb77eff0bad4e67547d67a130604e7e2dfbb7b0cde0796045be4090f35c6", size = 482498, upload-time = "2026-01-18T20:55:29.626Z" }, + { url = "https://files.pythonhosted.org/packages/05/be/71e37b852d723dfcbe952ad04178c030df60d6b78eba26bfd14c9a40575e/ormsgpack-1.12.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fcd55e5f6ba0dbce624942adf9f152062135f991a0126064889f68eb850de0dd", size = 425518, upload-time = "2026-01-18T20:55:49.556Z" }, + { url = "https://files.pythonhosted.org/packages/7a/0c/9803aa883d18c7ef197213cd2cbf73ba76472a11fe100fb7dab2884edf48/ormsgpack-1.12.2-cp312-cp312-win_amd64.whl", hash = "sha256:d024b40828f1dde5654faebd0d824f9cc29ad46891f626272dd5bfd7af2333a4", size = 117462, upload-time = "2026-01-18T20:55:47.726Z" }, + { url = "https://files.pythonhosted.org/packages/c8/9e/029e898298b2cc662f10d7a15652a53e3b525b1e7f07e21fef8536a09bb8/ormsgpack-1.12.2-cp312-cp312-win_arm64.whl", hash = "sha256:da538c542bac7d1c8f3f2a937863dba36f013108ce63e55745941dda4b75dbb6", size = 111559, upload-time = "2026-01-18T20:55:54.273Z" }, + { url = "https://files.pythonhosted.org/packages/eb/29/bb0eba3288c0449efbb013e9c6f58aea79cf5cb9ee1921f8865f04c1a9d7/ormsgpack-1.12.2-cp313-cp313-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:5ea60cb5f210b1cfbad8c002948d73447508e629ec375acb82910e3efa8ff355", size = 378661, upload-time = "2026-01-18T20:55:57.765Z" }, + { url = "https://files.pythonhosted.org/packages/6e/31/5efa31346affdac489acade2926989e019e8ca98129658a183e3add7af5e/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3601f19afdbea273ed70b06495e5794606a8b690a568d6c996a90d7255e51c1", size = 203194, upload-time = "2026-01-18T20:56:08.252Z" }, + { url = "https://files.pythonhosted.org/packages/eb/56/d0087278beef833187e0167f8527235ebe6f6ffc2a143e9de12a98b1ce87/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29a9f17a3dac6054c0dce7925e0f4995c727f7c41859adf9b5572180f640d172", size = 210778, upload-time = "2026-01-18T20:55:17.694Z" }, + { url = "https://files.pythonhosted.org/packages/1c/a2/072343e1413d9443e5a252a8eb591c2d5b1bffbe5e7bfc78c069361b92eb/ormsgpack-1.12.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:39c1bd2092880e413902910388be8715f70b9f15f20779d44e673033a6146f2d", size = 212592, upload-time = "2026-01-18T20:55:32.747Z" }, + { url = "https://files.pythonhosted.org/packages/a2/8b/a0da3b98a91d41187a63b02dda14267eefc2a74fcb43cc2701066cf1510e/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:50b7249244382209877deedeee838aef1542f3d0fc28b8fe71ca9d7e1896a0d7", size = 387164, upload-time = "2026-01-18T20:55:40.853Z" }, + { url = "https://files.pythonhosted.org/packages/19/bb/6d226bc4cf9fc20d8eb1d976d027a3f7c3491e8f08289a2e76abe96a65f3/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:5af04800d844451cf102a59c74a841324868d3f1625c296a06cc655c542a6685", size = 482516, upload-time = "2026-01-18T20:55:42.033Z" }, + { url = "https://files.pythonhosted.org/packages/fb/f1/bb2c7223398543dedb3dbf8bb93aaa737b387de61c5feaad6f908841b782/ormsgpack-1.12.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:cec70477d4371cd524534cd16472d8b9cc187e0e3043a8790545a9a9b296c258", size = 425539, upload-time = "2026-01-18T20:55:24.727Z" }, + { url = "https://files.pythonhosted.org/packages/7b/e8/0fb45f57a2ada1fed374f7494c8cd55e2f88ccd0ab0a669aa3468716bf5f/ormsgpack-1.12.2-cp313-cp313-win_amd64.whl", hash = "sha256:21f4276caca5c03a818041d637e4019bc84f9d6ca8baa5ea03e5cc8bf56140e9", size = 117459, upload-time = "2026-01-18T20:55:56.876Z" }, + { url = "https://files.pythonhosted.org/packages/7a/d4/0cfeea1e960d550a131001a7f38a5132c7ae3ebde4c82af1f364ccc5d904/ormsgpack-1.12.2-cp313-cp313-win_arm64.whl", hash = "sha256:baca4b6773d20a82e36d6fd25f341064244f9f86a13dead95dd7d7f996f51709", size = 111577, upload-time = "2026-01-18T20:55:43.605Z" }, + { url = "https://files.pythonhosted.org/packages/94/16/24d18851334be09c25e87f74307c84950f18c324a4d3c0b41dabdbf19c29/ormsgpack-1.12.2-cp314-cp314-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:bc68dd5915f4acf66ff2010ee47c8906dc1cf07399b16f4089f8c71733f6e36c", size = 378717, upload-time = "2026-01-18T20:55:26.164Z" }, + { url = "https://files.pythonhosted.org/packages/b5/a2/88b9b56f83adae8032ac6a6fa7f080c65b3baf9b6b64fd3d37bd202991d4/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46d084427b4132553940070ad95107266656cb646ea9da4975f85cb1a6676553", size = 203183, upload-time = "2026-01-18T20:55:18.815Z" }, + { url = "https://files.pythonhosted.org/packages/a9/80/43e4555963bf602e5bdc79cbc8debd8b6d5456c00d2504df9775e74b450b/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c010da16235806cf1d7bc4c96bf286bfa91c686853395a299b3ddb49499a3e13", size = 210814, upload-time = "2026-01-18T20:55:33.973Z" }, + { url = "https://files.pythonhosted.org/packages/78/e1/7cfbf28de8bca6efe7e525b329c31277d1b64ce08dcba723971c241a9d60/ormsgpack-1.12.2-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:18867233df592c997154ff942a6503df274b5ac1765215bceba7a231bea2745d", size = 212634, upload-time = "2026-01-18T20:55:28.634Z" }, + { url = "https://files.pythonhosted.org/packages/95/f8/30ae5716e88d792a4e879debee195653c26ddd3964c968594ddef0a3cc7e/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b009049086ddc6b8f80c76b3955df1aa22a5fbd7673c525cd63bf91f23122ede", size = 387139, upload-time = "2026-01-18T20:56:02.013Z" }, + { url = "https://files.pythonhosted.org/packages/dc/81/aee5b18a3e3a0e52f718b37ab4b8af6fae0d9d6a65103036a90c2a8ffb5d/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:1dcc17d92b6390d4f18f937cf0b99054824a7815818012ddca925d6e01c2e49e", size = 482578, upload-time = "2026-01-18T20:55:35.117Z" }, + { url = "https://files.pythonhosted.org/packages/bd/17/71c9ba472d5d45f7546317f467a5fc941929cd68fb32796ca3d13dcbaec2/ormsgpack-1.12.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:f04b5e896d510b07c0ad733d7fce2d44b260c5e6c402d272128f8941984e4285", size = 425539, upload-time = "2026-01-18T20:56:04.009Z" }, + { url = "https://files.pythonhosted.org/packages/2e/a6/ac99cd7fe77e822fed5250ff4b86fa66dd4238937dd178d2299f10b69816/ormsgpack-1.12.2-cp314-cp314-win_amd64.whl", hash = "sha256:ae3aba7eed4ca7cb79fd3436eddd29140f17ea254b91604aa1eb19bfcedb990f", size = 117493, upload-time = "2026-01-18T20:56:07.343Z" }, + { url = "https://files.pythonhosted.org/packages/3a/67/339872846a1ae4592535385a1c1f93614138566d7af094200c9c3b45d1e5/ormsgpack-1.12.2-cp314-cp314-win_arm64.whl", hash = "sha256:118576ea6006893aea811b17429bfc561b4778fad393f5f538c84af70b01260c", size = 111579, upload-time = "2026-01-18T20:55:21.161Z" }, + { url = "https://files.pythonhosted.org/packages/49/c2/6feb972dc87285ad381749d3882d8aecbde9f6ecf908dd717d33d66df095/ormsgpack-1.12.2-cp314-cp314t-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:7121b3d355d3858781dc40dafe25a32ff8a8242b9d80c692fd548a4b1f7fd3c8", size = 378721, upload-time = "2026-01-18T20:55:52.12Z" }, + { url = "https://files.pythonhosted.org/packages/a3/9a/900a6b9b413e0f8a471cf07830f9cf65939af039a362204b36bd5b581d8b/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ee766d2e78251b7a63daf1cddfac36a73562d3ddef68cacfb41b2af64698033", size = 203170, upload-time = "2026-01-18T20:55:44.469Z" }, + { url = "https://files.pythonhosted.org/packages/87/4c/27a95466354606b256f24fad464d7c97ab62bce6cc529dd4673e1179b8fb/ormsgpack-1.12.2-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:292410a7d23de9b40444636b9b8f1e4e4b814af7f1ef476e44887e52a123f09d", size = 212816, upload-time = "2026-01-18T20:55:23.501Z" }, + { url = "https://files.pythonhosted.org/packages/73/cd/29cee6007bddf7a834e6cd6f536754c0535fcb939d384f0f37a38b1cddb8/ormsgpack-1.12.2-cp314-cp314t-win_amd64.whl", hash = "sha256:837dd316584485b72ef451d08dd3e96c4a11d12e4963aedb40e08f89685d8ec2", size = 117232, upload-time = "2026-01-18T20:55:45.448Z" }, +] + +[[package]] +name = "packaging" +version = "26.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/65/ee/299d360cdc32edc7d2cf530f3accf79c4fca01e96ffc950d8a52213bd8e4/packaging-26.0.tar.gz", hash = "sha256:00243ae351a257117b6a241061796684b084ed1c516a08c48a3f7e147a9d80b4", size = 143416, upload-time = "2026-01-21T20:50:39.064Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b7/b9/c538f279a4e237a006a2c98387d081e9eb060d203d8ed34467cc0f0b9b53/packaging-26.0-py3-none-any.whl", hash = "sha256:b36f1fef9334a5588b4166f8bcd26a14e521f2b55e6b9de3aaa80d3ff7a37529", size = 74366, upload-time = "2026-01-21T20:50:37.788Z" }, +] + +[[package]] +name = "pycparser" +version = "3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" }, +] + +[[package]] +name = "pydantic" +version = "2.12.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-types" }, + { name = "pydantic-core" }, + { name = "typing-extensions" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" }, +] + +[[package]] +name = "pydantic-core" +version = "2.41.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, + { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, + { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, + { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, + { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, + { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, + { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, + { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, + { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, + { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, + { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, + { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, + { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, + { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, + { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, + { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, + { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, + { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, + { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, + { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, + { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, + { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, + { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, + { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, + { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, + { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, + { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, + { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, + { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, + { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, + { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, + { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, + { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, + { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, + { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, + { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, + { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, + { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, + { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, + { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, + { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, + { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, + { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, + { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, + { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, + { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, + { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, + { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, + { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, + { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, + { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, + { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, + { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, + { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, + { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, + { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, + { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, +] + +[[package]] +name = "pydantic-settings" +version = "2.13.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "pydantic" }, + { name = "python-dotenv" }, + { name = "typing-inspection" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/6d/fffca34caecc4a3f97bda81b2098da5e8ab7efc9a66e819074a11955d87e/pydantic_settings-2.13.1.tar.gz", hash = "sha256:b4c11847b15237fb0171e1462bf540e294affb9b86db4d9aa5c01730bdbe4025", size = 223826, upload-time = "2026-02-19T13:45:08.055Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/4b/ccc026168948fec4f7555b9164c724cf4125eac006e176541483d2c959be/pydantic_settings-2.13.1-py3-none-any.whl", hash = "sha256:d56fd801823dbeae7f0975e1f8c8e25c258eb75d278ea7abb5d9cebb01b56237", size = 58929, upload-time = "2026-02-19T13:45:06.034Z" }, +] + +[[package]] +name = "pyjwt" +version = "2.11.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/5c/5a/b46fa56bf322901eee5b0454a34343cdbdae202cd421775a8ee4e42fd519/pyjwt-2.11.0.tar.gz", hash = "sha256:35f95c1f0fbe5d5ba6e43f00271c275f7a1a4db1dab27bf708073b75318ea623", size = 98019, upload-time = "2026-01-30T19:59:55.694Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/6f/01/c26ce75ba460d5cd503da9e13b21a33804d38c2165dec7b716d06b13010c/pyjwt-2.11.0-py3-none-any.whl", hash = "sha256:94a6bde30eb5c8e04fee991062b534071fd1439ef58d2adc9ccb823e7bcd0469", size = 28224, upload-time = "2026-01-30T19:59:54.539Z" }, +] + +[package.optional-dependencies] +crypto = [ + { name = "cryptography" }, +] + +[[package]] +name = "python-dotenv" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/82/ed/0301aeeac3e5353ef3d94b6ec08bbcabd04a72018415dcb29e588514bba8/python_dotenv-1.2.2.tar.gz", hash = "sha256:2c371a91fbd7ba082c2c1dc1f8bf89ca22564a087c2c287cd9b662adde799cf3", size = 50135, upload-time = "2026-03-01T16:00:26.196Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/0b/d7/1959b9648791274998a9c3526f6d0ec8fd2233e4d4acce81bbae76b44b2a/python_dotenv-1.2.2-py3-none-any.whl", hash = "sha256:1d8214789a24de455a8b8bd8ae6fe3c6b69a5e3d64aa8a8e5d68e694bbcb285a", size = 22101, upload-time = "2026-03-01T16:00:25.09Z" }, +] + +[[package]] +name = "python-multipart" +version = "0.0.22" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/94/01/979e98d542a70714b0cb2b6728ed0b7c46792b695e3eaec3e20711271ca3/python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58", size = 37612, upload-time = "2026-01-25T10:15:56.219Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155", size = 24579, upload-time = "2026-01-25T10:15:54.811Z" }, +] + +[[package]] +name = "pywin32" +version = "311" +source = { registry = "https://pypi.org/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/ab/01ea1943d4eba0f850c3c61e78e8dd59757ff815ff3ccd0a84de5f541f42/pywin32-311-cp312-cp312-win32.whl", hash = "sha256:750ec6e621af2b948540032557b10a2d43b0cee2ae9758c54154d711cc852d31", size = 8706543, upload-time = "2025-07-14T20:13:20.765Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/a0e8d07d4d051ec7502cd58b291ec98dcc0c3fff027caad0470b72cfcc2f/pywin32-311-cp312-cp312-win_amd64.whl", hash = "sha256:b8c095edad5c211ff31c05223658e71bf7116daa0ecf3ad85f3201ea3190d067", size = 9495040, upload-time = "2025-07-14T20:13:22.543Z" }, + { url = "https://files.pythonhosted.org/packages/ba/3a/2ae996277b4b50f17d61f0603efd8253cb2d79cc7ae159468007b586396d/pywin32-311-cp312-cp312-win_arm64.whl", hash = "sha256:e286f46a9a39c4a18b319c28f59b61de793654af2f395c102b4f819e584b5852", size = 8710102, upload-time = "2025-07-14T20:13:24.682Z" }, + { url = "https://files.pythonhosted.org/packages/a5/be/3fd5de0979fcb3994bfee0d65ed8ca9506a8a1260651b86174f6a86f52b3/pywin32-311-cp313-cp313-win32.whl", hash = "sha256:f95ba5a847cba10dd8c4d8fefa9f2a6cf283b8b88ed6178fa8a6c1ab16054d0d", size = 8705700, upload-time = "2025-07-14T20:13:26.471Z" }, + { url = "https://files.pythonhosted.org/packages/e3/28/e0a1909523c6890208295a29e05c2adb2126364e289826c0a8bc7297bd5c/pywin32-311-cp313-cp313-win_amd64.whl", hash = "sha256:718a38f7e5b058e76aee1c56ddd06908116d35147e133427e59a3983f703a20d", size = 9494700, upload-time = "2025-07-14T20:13:28.243Z" }, + { url = "https://files.pythonhosted.org/packages/04/bf/90339ac0f55726dce7d794e6d79a18a91265bdf3aa70b6b9ca52f35e022a/pywin32-311-cp313-cp313-win_arm64.whl", hash = "sha256:7b4075d959648406202d92a2310cb990fea19b535c7f4a78d3f5e10b926eeb8a", size = 8709318, upload-time = "2025-07-14T20:13:30.348Z" }, + { url = "https://files.pythonhosted.org/packages/c9/31/097f2e132c4f16d99a22bfb777e0fd88bd8e1c634304e102f313af69ace5/pywin32-311-cp314-cp314-win32.whl", hash = "sha256:b7a2c10b93f8986666d0c803ee19b5990885872a7de910fc460f9b0c2fbf92ee", size = 8840714, upload-time = "2025-07-14T20:13:32.449Z" }, + { url = "https://files.pythonhosted.org/packages/90/4b/07c77d8ba0e01349358082713400435347df8426208171ce297da32c313d/pywin32-311-cp314-cp314-win_amd64.whl", hash = "sha256:3aca44c046bd2ed8c90de9cb8427f581c479e594e99b5c0bb19b29c10fd6cb87", size = 9656800, upload-time = "2025-07-14T20:13:34.312Z" }, + { url = "https://files.pythonhosted.org/packages/c0/d2/21af5c535501a7233e734b8af901574572da66fcc254cb35d0609c9080dd/pywin32-311-cp314-cp314-win_arm64.whl", hash = "sha256:a508e2d9025764a8270f93111a970e1d0fbfc33f4153b388bb649b7eec4f9b42", size = 8932540, upload-time = "2025-07-14T20:13:36.379Z" }, +] + +[[package]] +name = "pyyaml" +version = "6.0.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, + { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, + { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, + { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, + { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, + { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, + { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, + { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, + { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, + { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, + { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, + { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, + { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, + { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, + { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, + { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, + { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, + { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, + { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, + { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, + { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, + { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, + { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, + { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, + { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, + { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, + { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, + { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, + { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, + { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, + { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, + { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, + { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, +] + +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + +[[package]] +name = "regex" +version = "2026.2.28" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8b/71/41455aa99a5a5ac1eaf311f5d8efd9ce6433c03ac1e0962de163350d0d97/regex-2026.2.28.tar.gz", hash = "sha256:a729e47d418ea11d03469f321aaf67cdee8954cde3ff2cf8403ab87951ad10f2", size = 415184, upload-time = "2026-02-28T02:19:42.792Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/07/42/9061b03cf0fc4b5fa2c3984cbbaed54324377e440a5c5a29d29a72518d62/regex-2026.2.28-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fcf26c3c6d0da98fada8ae4ef0aa1c3405a431c0a77eb17306d38a89b02adcd7", size = 489574, upload-time = "2026-02-28T02:16:50.455Z" }, + { url = "https://files.pythonhosted.org/packages/77/83/0c8a5623a233015595e3da499c5a1c13720ac63c107897a6037bb97af248/regex-2026.2.28-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:02473c954af35dd2defeb07e44182f5705b30ea3f351a7cbffa9177beb14da5d", size = 291426, upload-time = "2026-02-28T02:16:52.52Z" }, + { url = "https://files.pythonhosted.org/packages/9e/06/3ef1ac6910dc3295ebd71b1f9bfa737e82cfead211a18b319d45f85ddd09/regex-2026.2.28-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9b65d33a17101569f86d9c5966a8b1d7fbf8afdda5a8aa219301b0a80f58cf7d", size = 289200, upload-time = "2026-02-28T02:16:54.08Z" }, + { url = "https://files.pythonhosted.org/packages/dd/c9/8cc8d850b35ab5650ff6756a1cb85286e2000b66c97520b29c1587455344/regex-2026.2.28-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e71dcecaa113eebcc96622c17692672c2d104b1d71ddf7adeda90da7ddeb26fc", size = 796765, upload-time = "2026-02-28T02:16:55.905Z" }, + { url = "https://files.pythonhosted.org/packages/e9/5d/57702597627fc23278ebf36fbb497ac91c0ce7fec89ac6c81e420ca3e38c/regex-2026.2.28-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:481df4623fa4969c8b11f3433ed7d5e3dc9cec0f008356c3212b3933fb77e3d8", size = 863093, upload-time = "2026-02-28T02:16:58.094Z" }, + { url = "https://files.pythonhosted.org/packages/02/6d/f3ecad537ca2811b4d26b54ca848cf70e04fcfc138667c146a9f3157779c/regex-2026.2.28-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:64e7c6ad614573e0640f271e811a408d79a9e1fe62a46adb602f598df42a818d", size = 909455, upload-time = "2026-02-28T02:17:00.918Z" }, + { url = "https://files.pythonhosted.org/packages/9e/40/bb226f203caa22c1043c1ca79b36340156eca0f6a6742b46c3bb222a3a57/regex-2026.2.28-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d6b08a06976ff4fb0d83077022fde3eca06c55432bb997d8c0495b9a4e9872f4", size = 802037, upload-time = "2026-02-28T02:17:02.842Z" }, + { url = "https://files.pythonhosted.org/packages/44/7c/c6d91d8911ac6803b45ca968e8e500c46934e58c0903cbc6d760ee817a0a/regex-2026.2.28-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:864cdd1a2ef5716b0ab468af40139e62ede1b3a53386b375ec0786bb6783fc05", size = 775113, upload-time = "2026-02-28T02:17:04.506Z" }, + { url = "https://files.pythonhosted.org/packages/dc/8d/4a9368d168d47abd4158580b8c848709667b1cd293ff0c0c277279543bd0/regex-2026.2.28-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:511f7419f7afab475fd4d639d4aedfc54205bcb0800066753ef68a59f0f330b5", size = 784194, upload-time = "2026-02-28T02:17:06.888Z" }, + { url = "https://files.pythonhosted.org/packages/cc/bf/2c72ab5d8b7be462cb1651b5cc333da1d0068740342f350fcca3bca31947/regex-2026.2.28-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:b42f7466e32bf15a961cf09f35fa6323cc72e64d3d2c990b10de1274a5da0a59", size = 856846, upload-time = "2026-02-28T02:17:09.11Z" }, + { url = "https://files.pythonhosted.org/packages/7c/f4/6b65c979bb6d09f51bb2d2a7bc85de73c01ec73335d7ddd202dcb8cd1c8f/regex-2026.2.28-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:8710d61737b0c0ce6836b1da7109f20d495e49b3809f30e27e9560be67a257bf", size = 763516, upload-time = "2026-02-28T02:17:11.004Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/29ea5e27400ee86d2cc2b4e80aa059df04eaf78b4f0c18576ae077aeff68/regex-2026.2.28-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:4390c365fd2d45278f45afd4673cb90f7285f5701607e3ad4274df08e36140ae", size = 849278, upload-time = "2026-02-28T02:17:12.693Z" }, + { url = "https://files.pythonhosted.org/packages/1d/91/3233d03b5f865111cd517e1c95ee8b43e8b428d61fa73764a80c9bb6f537/regex-2026.2.28-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:cb3b1db8ff6c7b8bf838ab05583ea15230cb2f678e569ab0e3a24d1e8320940b", size = 790068, upload-time = "2026-02-28T02:17:14.9Z" }, + { url = "https://files.pythonhosted.org/packages/76/92/abc706c1fb03b4580a09645b206a3fc032f5a9f457bc1a8038ac555658ab/regex-2026.2.28-cp312-cp312-win32.whl", hash = "sha256:f8ed9a5d4612df9d4de15878f0bc6aa7a268afbe5af21a3fdd97fa19516e978c", size = 266416, upload-time = "2026-02-28T02:17:17.15Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/2a6f7dff190e5fa9df9fb4acf2fdf17a1aa0f7f54596cba8de608db56b3a/regex-2026.2.28-cp312-cp312-win_amd64.whl", hash = "sha256:01d65fd24206c8e1e97e2e31b286c59009636c022eb5d003f52760b0f42155d4", size = 277297, upload-time = "2026-02-28T02:17:18.723Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f0/58a2484851fadf284458fdbd728f580d55c1abac059ae9f048c63b92f427/regex-2026.2.28-cp312-cp312-win_arm64.whl", hash = "sha256:c0b5ccbb8ffb433939d248707d4a8b31993cb76ab1a0187ca886bf50e96df952", size = 270408, upload-time = "2026-02-28T02:17:20.328Z" }, + { url = "https://files.pythonhosted.org/packages/87/f6/dc9ef48c61b79c8201585bf37fa70cd781977da86e466cd94e8e95d2443b/regex-2026.2.28-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6d63a07e5ec8ce7184452cb00c41c37b49e67dc4f73b2955b5b8e782ea970784", size = 489311, upload-time = "2026-02-28T02:17:22.591Z" }, + { url = "https://files.pythonhosted.org/packages/95/c8/c20390f2232d3f7956f420f4ef1852608ad57aa26c3dd78516cb9f3dc913/regex-2026.2.28-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e59bc8f30414d283ae8ee1617b13d8112e7135cb92830f0ec3688cb29152585a", size = 291285, upload-time = "2026-02-28T02:17:24.355Z" }, + { url = "https://files.pythonhosted.org/packages/d2/a6/ba1068a631ebd71a230e7d8013fcd284b7c89c35f46f34a7da02082141b1/regex-2026.2.28-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:de0cf053139f96219ccfabb4a8dd2d217c8c82cb206c91d9f109f3f552d6b43d", size = 289051, upload-time = "2026-02-28T02:17:26.722Z" }, + { url = "https://files.pythonhosted.org/packages/1d/1b/7cc3b7af4c244c204b7a80924bd3d85aecd9ba5bc82b485c5806ee8cda9e/regex-2026.2.28-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fb4db2f17e6484904f986c5a657cec85574c76b5c5e61c7aae9ffa1bc6224f95", size = 796842, upload-time = "2026-02-28T02:17:29.064Z" }, + { url = "https://files.pythonhosted.org/packages/24/87/26bd03efc60e0d772ac1e7b60a2e6325af98d974e2358f659c507d3c76db/regex-2026.2.28-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:52b017b35ac2214d0db5f4f90e303634dc44e4aba4bd6235a27f97ecbe5b0472", size = 863083, upload-time = "2026-02-28T02:17:31.363Z" }, + { url = "https://files.pythonhosted.org/packages/ae/54/aeaf4afb1aa0a65e40de52a61dc2ac5b00a83c6cb081c8a1d0dda74f3010/regex-2026.2.28-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:69fc560ccbf08a09dc9b52ab69cacfae51e0ed80dc5693078bdc97db2f91ae96", size = 909412, upload-time = "2026-02-28T02:17:33.248Z" }, + { url = "https://files.pythonhosted.org/packages/12/2f/049901def913954e640d199bbc6a7ca2902b6aeda0e5da9d17f114100ec2/regex-2026.2.28-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e61eea47230eba62a31f3e8a0e3164d0f37ef9f40529fb2c79361bc6b53d2a92", size = 802101, upload-time = "2026-02-28T02:17:35.053Z" }, + { url = "https://files.pythonhosted.org/packages/7d/a5/512fb9ff7f5b15ea204bb1967ebb649059446decacccb201381f9fa6aad4/regex-2026.2.28-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:4f5c0b182ad4269e7381b7c27fdb0408399881f7a92a4624fd5487f2971dfc11", size = 775260, upload-time = "2026-02-28T02:17:37.692Z" }, + { url = "https://files.pythonhosted.org/packages/d1/a8/9a92935878aba19bd72706b9db5646a6f993d99b3f6ed42c02ec8beb1d61/regex-2026.2.28-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:96f6269a2882fbb0ee76967116b83679dc628e68eaea44e90884b8d53d833881", size = 784311, upload-time = "2026-02-28T02:17:39.855Z" }, + { url = "https://files.pythonhosted.org/packages/09/d3/fc51a8a738a49a6b6499626580554c9466d3ea561f2b72cfdc72e4149773/regex-2026.2.28-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b5acd4b6a95f37c3c3828e5d053a7d4edaedb85de551db0153754924cb7c83e3", size = 856876, upload-time = "2026-02-28T02:17:42.317Z" }, + { url = "https://files.pythonhosted.org/packages/08/b7/2e641f3d084b120ca4c52e8c762a78da0b32bf03ef546330db3e2635dc5f/regex-2026.2.28-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:2234059cfe33d9813a3677ef7667999caea9eeaa83fef98eb6ce15c6cf9e0215", size = 763632, upload-time = "2026-02-28T02:17:45.073Z" }, + { url = "https://files.pythonhosted.org/packages/fe/6d/0009021d97e79ee99f3d8641f0a8d001eed23479ade4c3125a5480bf3e2d/regex-2026.2.28-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:c15af43c72a7fb0c97cbc66fa36a43546eddc5c06a662b64a0cbf30d6ac40944", size = 849320, upload-time = "2026-02-28T02:17:47.192Z" }, + { url = "https://files.pythonhosted.org/packages/05/7a/51cfbad5758f8edae430cb21961a9c8d04bce1dae4d2d18d4186eec7cfa1/regex-2026.2.28-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9185cc63359862a6e80fe97f696e04b0ad9a11c4ac0a4a927f979f611bfe3768", size = 790152, upload-time = "2026-02-28T02:17:49.067Z" }, + { url = "https://files.pythonhosted.org/packages/90/3d/a83e2b6b3daa142acb8c41d51de3876186307d5cb7490087031747662500/regex-2026.2.28-cp313-cp313-win32.whl", hash = "sha256:fb66e5245db9652abd7196ace599b04d9c0e4aa7c8f0e2803938377835780081", size = 266398, upload-time = "2026-02-28T02:17:50.744Z" }, + { url = "https://files.pythonhosted.org/packages/85/4f/16e9ebb1fe5425e11b9596c8d57bf8877dcb32391da0bfd33742e3290637/regex-2026.2.28-cp313-cp313-win_amd64.whl", hash = "sha256:71a911098be38c859ceb3f9a9ce43f4ed9f4c6720ad8684a066ea246b76ad9ff", size = 277282, upload-time = "2026-02-28T02:17:53.074Z" }, + { url = "https://files.pythonhosted.org/packages/07/b4/92851335332810c5a89723bf7a7e35c7209f90b7d4160024501717b28cc9/regex-2026.2.28-cp313-cp313-win_arm64.whl", hash = "sha256:39bb5727650b9a0275c6a6690f9bb3fe693a7e6cc5c3155b1240aedf8926423e", size = 270382, upload-time = "2026-02-28T02:17:54.888Z" }, + { url = "https://files.pythonhosted.org/packages/24/07/6c7e4cec1e585959e96cbc24299d97e4437a81173217af54f1804994e911/regex-2026.2.28-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:97054c55db06ab020342cc0d35d6f62a465fa7662871190175f1ad6c655c028f", size = 492541, upload-time = "2026-02-28T02:17:56.813Z" }, + { url = "https://files.pythonhosted.org/packages/7c/13/55eb22ada7f43d4f4bb3815b6132183ebc331c81bd496e2d1f3b8d862e0d/regex-2026.2.28-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0d25a10811de831c2baa6aef3c0be91622f44dd8d31dd12e69f6398efb15e48b", size = 292984, upload-time = "2026-02-28T02:17:58.538Z" }, + { url = "https://files.pythonhosted.org/packages/5b/11/c301f8cb29ce9644a5ef85104c59244e6e7e90994a0f458da4d39baa8e17/regex-2026.2.28-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:d6cfe798d8da41bb1862ed6e0cba14003d387c3c0c4a5d45591076ae9f0ce2f8", size = 291509, upload-time = "2026-02-28T02:18:00.208Z" }, + { url = "https://files.pythonhosted.org/packages/b5/43/aabe384ec1994b91796e903582427bc2ffaed9c4103819ed3c16d8e749f3/regex-2026.2.28-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fd0ce43e71d825b7c0661f9c54d4d74bd97c56c3fd102a8985bcfea48236bacb", size = 809429, upload-time = "2026-02-28T02:18:02.328Z" }, + { url = "https://files.pythonhosted.org/packages/04/b8/8d2d987a816720c4f3109cee7c06a4b24ad0e02d4fc74919ab619e543737/regex-2026.2.28-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00945d007fd74a9084d2ab79b695b595c6b7ba3698972fadd43e23230c6979c1", size = 869422, upload-time = "2026-02-28T02:18:04.23Z" }, + { url = "https://files.pythonhosted.org/packages/fc/ad/2c004509e763c0c3719f97c03eca26473bffb3868d54c5f280b8cd4f9e3d/regex-2026.2.28-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:bec23c11cbbf09a4df32fe50d57cbdd777bc442269b6e39a1775654f1c95dee2", size = 915175, upload-time = "2026-02-28T02:18:06.791Z" }, + { url = "https://files.pythonhosted.org/packages/55/c2/fd429066da487ef555a9da73bf214894aec77fc8c66a261ee355a69871a8/regex-2026.2.28-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5cdcc17d935c8f9d3f4db5c2ebe2640c332e3822ad5d23c2f8e0228e6947943a", size = 812044, upload-time = "2026-02-28T02:18:08.736Z" }, + { url = "https://files.pythonhosted.org/packages/5b/ca/feedb7055c62a3f7f659971bf45f0e0a87544b6b0cf462884761453f97c5/regex-2026.2.28-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a448af01e3d8031c89c5d902040b124a5e921a25c4e5e07a861ca591ce429341", size = 782056, upload-time = "2026-02-28T02:18:10.777Z" }, + { url = "https://files.pythonhosted.org/packages/95/30/1aa959ed0d25c1dd7dd5047ea8ba482ceaef38ce363c401fd32a6b923e60/regex-2026.2.28-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:10d28e19bd4888e4abf43bd3925f3c134c52fdf7259219003588a42e24c2aa25", size = 798743, upload-time = "2026-02-28T02:18:13.025Z" }, + { url = "https://files.pythonhosted.org/packages/3b/1f/dadb9cf359004784051c897dcf4d5d79895f73a1bbb7b827abaa4814ae80/regex-2026.2.28-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:99985a2c277dcb9ccb63f937451af5d65177af1efdeb8173ac55b61095a0a05c", size = 864633, upload-time = "2026-02-28T02:18:16.84Z" }, + { url = "https://files.pythonhosted.org/packages/a7/f1/b9a25eb24e1cf79890f09e6ec971ee5b511519f1851de3453bc04f6c902b/regex-2026.2.28-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:e1e7b24cb3ae9953a560c563045d1ba56ee4749fbd05cf21ba571069bd7be81b", size = 770862, upload-time = "2026-02-28T02:18:18.892Z" }, + { url = "https://files.pythonhosted.org/packages/02/9a/c5cb10b7aa6f182f9247a30cc9527e326601f46f4df864ac6db588d11fcd/regex-2026.2.28-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d8511a01d0e4ee1992eb3ba19e09bc1866fe03f05129c3aec3fdc4cbc77aad3f", size = 854788, upload-time = "2026-02-28T02:18:21.475Z" }, + { url = "https://files.pythonhosted.org/packages/0a/50/414ba0731c4bd40b011fa4703b2cc86879ec060c64f2a906e65a56452589/regex-2026.2.28-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:aaffaecffcd2479ce87aa1e74076c221700b7c804e48e98e62500ee748f0f550", size = 800184, upload-time = "2026-02-28T02:18:23.492Z" }, + { url = "https://files.pythonhosted.org/packages/69/50/0c7290987f97e7e6830b0d853f69dc4dc5852c934aae63e7fdcd76b4c383/regex-2026.2.28-cp313-cp313t-win32.whl", hash = "sha256:ef77bdde9c9eba3f7fa5b58084b29bbcc74bcf55fdbeaa67c102a35b5bd7e7cc", size = 269137, upload-time = "2026-02-28T02:18:25.375Z" }, + { url = "https://files.pythonhosted.org/packages/68/80/ef26ff90e74ceb4051ad6efcbbb8a4be965184a57e879ebcbdef327d18fa/regex-2026.2.28-cp313-cp313t-win_amd64.whl", hash = "sha256:98adf340100cbe6fbaf8e6dc75e28f2c191b1be50ffefe292fb0e6f6eefdb0d8", size = 280682, upload-time = "2026-02-28T02:18:27.205Z" }, + { url = "https://files.pythonhosted.org/packages/69/8b/fbad9c52e83ffe8f97e3ed1aa0516e6dff6bb633a41da9e64645bc7efdc5/regex-2026.2.28-cp313-cp313t-win_arm64.whl", hash = "sha256:2fb950ac1d88e6b6a9414381f403797b236f9fa17e1eee07683af72b1634207b", size = 271735, upload-time = "2026-02-28T02:18:29.015Z" }, + { url = "https://files.pythonhosted.org/packages/cf/03/691015f7a7cb1ed6dacb2ea5de5682e4858e05a4c5506b2839cd533bbcd6/regex-2026.2.28-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:78454178c7df31372ea737996fb7f36b3c2c92cccc641d251e072478afb4babc", size = 489497, upload-time = "2026-02-28T02:18:30.889Z" }, + { url = "https://files.pythonhosted.org/packages/c6/ba/8db8fd19afcbfa0e1036eaa70c05f20ca8405817d4ad7a38a6b4c2f031ac/regex-2026.2.28-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:5d10303dd18cedfd4d095543998404df656088240bcfd3cd20a8f95b861f74bd", size = 291295, upload-time = "2026-02-28T02:18:33.426Z" }, + { url = "https://files.pythonhosted.org/packages/5a/79/9aa0caf089e8defef9b857b52fc53801f62ff868e19e5c83d4a96612eba1/regex-2026.2.28-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:19a9c9e0a8f24f39d575a6a854d516b48ffe4cbdcb9de55cb0570a032556ecff", size = 289275, upload-time = "2026-02-28T02:18:35.247Z" }, + { url = "https://files.pythonhosted.org/packages/eb/26/ee53117066a30ef9c883bf1127eece08308ccf8ccd45c45a966e7a665385/regex-2026.2.28-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09500be324f49b470d907b3ef8af9afe857f5cca486f853853f7945ddbf75911", size = 797176, upload-time = "2026-02-28T02:18:37.15Z" }, + { url = "https://files.pythonhosted.org/packages/05/1b/67fb0495a97259925f343ae78b5d24d4a6624356ae138b57f18bd43006e4/regex-2026.2.28-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:fb1c4ff62277d87a7335f2c1ea4e0387b8f2b3ad88a64efd9943906aafad4f33", size = 863813, upload-time = "2026-02-28T02:18:39.478Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/93ac9bbafc53618091c685c7ed40239a90bf9f2a82c983f0baa97cb7ae07/regex-2026.2.28-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b8b3f1be1738feadc69f62daa250c933e85c6f34fa378f54a7ff43807c1b9117", size = 908678, upload-time = "2026-02-28T02:18:41.619Z" }, + { url = "https://files.pythonhosted.org/packages/c7/7a/a8f5e0561702b25239846a16349feece59712ae20598ebb205580332a471/regex-2026.2.28-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dc8ed8c3f41c27acb83f7b6a9eb727a73fc6663441890c5cb3426a5f6a91ce7d", size = 801528, upload-time = "2026-02-28T02:18:43.624Z" }, + { url = "https://files.pythonhosted.org/packages/96/5d/ed6d4cbde80309854b1b9f42d9062fee38ade15f7eb4909f6ef2440403b5/regex-2026.2.28-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fa539be029844c0ce1114762d2952ab6cfdd7c7c9bd72e0db26b94c3c36dcc5a", size = 775373, upload-time = "2026-02-28T02:18:46.102Z" }, + { url = "https://files.pythonhosted.org/packages/6a/e9/6e53c34e8068b9deec3e87210086ecb5b9efebdefca6b0d3fa43d66dcecb/regex-2026.2.28-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7900157786428a79615a8264dac1f12c9b02957c473c8110c6b1f972dcecaddf", size = 784859, upload-time = "2026-02-28T02:18:48.269Z" }, + { url = "https://files.pythonhosted.org/packages/48/3c/736e1c7ca7f0dcd2ae33819888fdc69058a349b7e5e84bc3e2f296bbf794/regex-2026.2.28-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:0b1d2b07614d95fa2bf8a63fd1e98bd8fa2b4848dc91b1efbc8ba219fdd73952", size = 857813, upload-time = "2026-02-28T02:18:50.576Z" }, + { url = "https://files.pythonhosted.org/packages/6e/7c/48c4659ad9da61f58e79dbe8c05223e0006696b603c16eb6b5cbfbb52c27/regex-2026.2.28-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:b389c61aa28a79c2e0527ac36da579869c2e235a5b208a12c5b5318cda2501d8", size = 763705, upload-time = "2026-02-28T02:18:52.59Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a1/bc1c261789283128165f71b71b4b221dd1b79c77023752a6074c102f18d8/regex-2026.2.28-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:f467cb602f03fbd1ab1908f68b53c649ce393fde056628dc8c7e634dab6bfc07", size = 848734, upload-time = "2026-02-28T02:18:54.595Z" }, + { url = "https://files.pythonhosted.org/packages/10/d8/979407faf1397036e25a5ae778157366a911c0f382c62501009f4957cf86/regex-2026.2.28-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e8c8cb2deba42f5ec1ede46374e990f8adc5e6456a57ac1a261b19be6f28e4e6", size = 789871, upload-time = "2026-02-28T02:18:57.34Z" }, + { url = "https://files.pythonhosted.org/packages/03/23/da716821277115fcb1f4e3de1e5dc5023a1e6533598c486abf5448612579/regex-2026.2.28-cp314-cp314-win32.whl", hash = "sha256:9036b400b20e4858d56d117108d7813ed07bb7803e3eed766675862131135ca6", size = 271825, upload-time = "2026-02-28T02:18:59.202Z" }, + { url = "https://files.pythonhosted.org/packages/91/ff/90696f535d978d5f16a52a419be2770a8d8a0e7e0cfecdbfc31313df7fab/regex-2026.2.28-cp314-cp314-win_amd64.whl", hash = "sha256:1d367257cd86c1cbb97ea94e77b373a0bbc2224976e247f173d19e8f18b4afa7", size = 280548, upload-time = "2026-02-28T02:19:01.049Z" }, + { url = "https://files.pythonhosted.org/packages/69/f9/5e1b5652fc0af3fcdf7677e7df3ad2a0d47d669b34ac29a63bb177bb731b/regex-2026.2.28-cp314-cp314-win_arm64.whl", hash = "sha256:5e68192bb3a1d6fb2836da24aa494e413ea65853a21505e142e5b1064a595f3d", size = 273444, upload-time = "2026-02-28T02:19:03.255Z" }, + { url = "https://files.pythonhosted.org/packages/d3/eb/8389f9e940ac89bcf58d185e230a677b4fd07c5f9b917603ad5c0f8fa8fe/regex-2026.2.28-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:a5dac14d0872eeb35260a8e30bac07ddf22adc1e3a0635b52b02e180d17c9c7e", size = 492546, upload-time = "2026-02-28T02:19:05.378Z" }, + { url = "https://files.pythonhosted.org/packages/7b/c7/09441d27ce2a6fa6a61ea3150ea4639c1dcda9b31b2ea07b80d6937b24dd/regex-2026.2.28-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:ec0c608b7a7465ffadb344ed7c987ff2f11ee03f6a130b569aa74d8a70e8333c", size = 292986, upload-time = "2026-02-28T02:19:07.24Z" }, + { url = "https://files.pythonhosted.org/packages/fb/69/4144b60ed7760a6bd235e4087041f487aa4aa62b45618ce018b0c14833ea/regex-2026.2.28-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c7815afb0ca45456613fdaf60ea9c993715511c8d53a83bc468305cbc0ee23c7", size = 291518, upload-time = "2026-02-28T02:19:09.698Z" }, + { url = "https://files.pythonhosted.org/packages/2d/be/77e5426cf5948c82f98c53582009ca9e94938c71f73a8918474f2e2990bb/regex-2026.2.28-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b059e71ec363968671693a78c5053bd9cb2fe410f9b8e4657e88377ebd603a2e", size = 809464, upload-time = "2026-02-28T02:19:12.494Z" }, + { url = "https://files.pythonhosted.org/packages/45/99/2c8c5ac90dc7d05c6e7d8e72c6a3599dc08cd577ac476898e91ca787d7f1/regex-2026.2.28-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b8cf76f1a29f0e99dcfd7aef1551a9827588aae5a737fe31442021165f1920dc", size = 869553, upload-time = "2026-02-28T02:19:15.151Z" }, + { url = "https://files.pythonhosted.org/packages/53/34/daa66a342f0271e7737003abf6c3097aa0498d58c668dbd88362ef94eb5d/regex-2026.2.28-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:180e08a435a0319e6a4821c3468da18dc7001987e1c17ae1335488dfe7518dd8", size = 915289, upload-time = "2026-02-28T02:19:17.331Z" }, + { url = "https://files.pythonhosted.org/packages/c5/c7/e22c2aaf0a12e7e22ab19b004bb78d32ca1ecc7ef245949935463c5567de/regex-2026.2.28-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1e496956106fd59ba6322a8ea17141a27c5040e5ee8f9433ae92d4e5204462a0", size = 812156, upload-time = "2026-02-28T02:19:20.011Z" }, + { url = "https://files.pythonhosted.org/packages/7f/bb/2dc18c1efd9051cf389cd0d7a3a4d90f6804b9fff3a51b5dc3c85b935f71/regex-2026.2.28-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:bba2b18d70eeb7b79950f12f633beeecd923f7c9ad6f6bae28e59b4cb3ab046b", size = 782215, upload-time = "2026-02-28T02:19:22.047Z" }, + { url = "https://files.pythonhosted.org/packages/17/1e/9e4ec9b9013931faa32226ec4aa3c71fe664a6d8a2b91ac56442128b332f/regex-2026.2.28-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:6db7bfae0f8a2793ff1f7021468ea55e2699d0790eb58ee6ab36ae43aa00bc5b", size = 798925, upload-time = "2026-02-28T02:19:24.173Z" }, + { url = "https://files.pythonhosted.org/packages/71/57/a505927e449a9ccb41e2cc8d735e2abe3444b0213d1cf9cb364a8c1f2524/regex-2026.2.28-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:d0b02e8b7e5874b48ae0f077ecca61c1a6a9f9895e9c6dfb191b55b242862033", size = 864701, upload-time = "2026-02-28T02:19:26.376Z" }, + { url = "https://files.pythonhosted.org/packages/a6/ad/c62cb60cdd93e13eac5b3d9d6bd5d284225ed0e3329426f94d2552dd7cca/regex-2026.2.28-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:25b6eb660c5cf4b8c3407a1ed462abba26a926cc9965e164268a3267bcc06a43", size = 770899, upload-time = "2026-02-28T02:19:29.38Z" }, + { url = "https://files.pythonhosted.org/packages/3c/5a/874f861f5c3d5ab99633e8030dee1bc113db8e0be299d1f4b07f5b5ec349/regex-2026.2.28-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:5a932ea8ad5d0430351ff9c76c8db34db0d9f53c1d78f06022a21f4e290c5c18", size = 854727, upload-time = "2026-02-28T02:19:31.494Z" }, + { url = "https://files.pythonhosted.org/packages/6b/ca/d2c03b0efde47e13db895b975b2be6a73ed90b8ba963677927283d43bf74/regex-2026.2.28-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:1c2c95e1a2b0f89d01e821ff4de1be4b5d73d1f4b0bf679fa27c1ad8d2327f1a", size = 800366, upload-time = "2026-02-28T02:19:34.248Z" }, + { url = "https://files.pythonhosted.org/packages/14/bd/ee13b20b763b8989f7c75d592bfd5de37dc1181814a2a2747fedcf97e3ba/regex-2026.2.28-cp314-cp314t-win32.whl", hash = "sha256:bbb882061f742eb5d46f2f1bd5304055be0a66b783576de3d7eef1bed4778a6e", size = 274936, upload-time = "2026-02-28T02:19:36.313Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e7/d8020e39414c93af7f0d8688eabcecece44abfd5ce314b21dfda0eebd3d8/regex-2026.2.28-cp314-cp314t-win_amd64.whl", hash = "sha256:6591f281cb44dc13de9585b552cec6fc6cf47fb2fe7a48892295ee9bc4a612f9", size = 284779, upload-time = "2026-02-28T02:19:38.625Z" }, + { url = "https://files.pythonhosted.org/packages/13/c0/ad225f4a405827486f1955283407cf758b6d2fb966712644c5f5aef33d1b/regex-2026.2.28-cp314-cp314t-win_arm64.whl", hash = "sha256:dee50f1be42222f89767b64b283283ef963189da0dda4a515aa54a5563c62dec", size = 275010, upload-time = "2026-02-28T02:19:40.65Z" }, +] + +[[package]] +name = "requests" +version = "2.32.5" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "certifi" }, + { name = "charset-normalizer" }, + { name = "idna" }, + { name = "urllib3" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c9/74/b3ff8e6c8446842c3f5c837e9c3dfcfe2018ea6ecef224c710c85ef728f4/requests-2.32.5.tar.gz", hash = "sha256:dbba0bac56e100853db0ea71b82b4dfd5fe2bf6d3754a8893c3af500cec7d7cf", size = 134517, upload-time = "2025-08-18T20:46:02.573Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, +] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f3/61/d7545dafb7ac2230c70d38d31cbfe4cc64f7144dc41f6e4e4b78ecd9f5bb/requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", size = 206888, upload-time = "2023-05-01T04:11:33.229Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" }, +] + +[[package]] +name = "rpds-py" +version = "0.30.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/20/af/3f2f423103f1113b36230496629986e0ef7e199d2aa8392452b484b38ced/rpds_py-0.30.0.tar.gz", hash = "sha256:dd8ff7cf90014af0c0f787eea34794ebf6415242ee1d6fa91eaba725cc441e84", size = 69469, upload-time = "2025-11-30T20:24:38.837Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/03/e7/98a2f4ac921d82f33e03f3835f5bf3a4a40aa1bfdc57975e74a97b2b4bdd/rpds_py-0.30.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a161f20d9a43006833cd7068375a94d035714d73a172b681d8881820600abfad", size = 375086, upload-time = "2025-11-30T20:22:17.93Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a1/bca7fd3d452b272e13335db8d6b0b3ecde0f90ad6f16f3328c6fb150c889/rpds_py-0.30.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6abc8880d9d036ecaafe709079969f56e876fcf107f7a8e9920ba6d5a3878d05", size = 359053, upload-time = "2025-11-30T20:22:19.297Z" }, + { url = "https://files.pythonhosted.org/packages/65/1c/ae157e83a6357eceff62ba7e52113e3ec4834a84cfe07fa4b0757a7d105f/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca28829ae5f5d569bb62a79512c842a03a12576375d5ece7d2cadf8abe96ec28", size = 390763, upload-time = "2025-11-30T20:22:21.661Z" }, + { url = "https://files.pythonhosted.org/packages/d4/36/eb2eb8515e2ad24c0bd43c3ee9cd74c33f7ca6430755ccdb240fd3144c44/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a1010ed9524c73b94d15919ca4d41d8780980e1765babf85f9a2f90d247153dd", size = 408951, upload-time = "2025-11-30T20:22:23.408Z" }, + { url = "https://files.pythonhosted.org/packages/d6/65/ad8dc1784a331fabbd740ef6f71ce2198c7ed0890dab595adb9ea2d775a1/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f8d1736cfb49381ba528cd5baa46f82fdc65c06e843dab24dd70b63d09121b3f", size = 514622, upload-time = "2025-11-30T20:22:25.16Z" }, + { url = "https://files.pythonhosted.org/packages/63/8e/0cfa7ae158e15e143fe03993b5bcd743a59f541f5952e1546b1ac1b5fd45/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d948b135c4693daff7bc2dcfc4ec57237a29bd37e60c2fabf5aff2bbacf3e2f1", size = 414492, upload-time = "2025-11-30T20:22:26.505Z" }, + { url = "https://files.pythonhosted.org/packages/60/1b/6f8f29f3f995c7ffdde46a626ddccd7c63aefc0efae881dc13b6e5d5bb16/rpds_py-0.30.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47f236970bccb2233267d89173d3ad2703cd36a0e2a6e92d0560d333871a3d23", size = 394080, upload-time = "2025-11-30T20:22:27.934Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d5/a266341051a7a3ca2f4b750a3aa4abc986378431fc2da508c5034d081b70/rpds_py-0.30.0-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:2e6ecb5a5bcacf59c3f912155044479af1d0b6681280048b338b28e364aca1f6", size = 408680, upload-time = "2025-11-30T20:22:29.341Z" }, + { url = "https://files.pythonhosted.org/packages/10/3b/71b725851df9ab7a7a4e33cf36d241933da66040d195a84781f49c50490c/rpds_py-0.30.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a8fa71a2e078c527c3e9dc9fc5a98c9db40bcc8a92b4e8858e36d329f8684b51", size = 423589, upload-time = "2025-11-30T20:22:31.469Z" }, + { url = "https://files.pythonhosted.org/packages/00/2b/e59e58c544dc9bd8bd8384ecdb8ea91f6727f0e37a7131baeff8d6f51661/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73c67f2db7bc334e518d097c6d1e6fed021bbc9b7d678d6cc433478365d1d5f5", size = 573289, upload-time = "2025-11-30T20:22:32.997Z" }, + { url = "https://files.pythonhosted.org/packages/da/3e/a18e6f5b460893172a7d6a680e86d3b6bc87a54c1f0b03446a3c8c7b588f/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5ba103fb455be00f3b1c2076c9d4264bfcb037c976167a6047ed82f23153f02e", size = 599737, upload-time = "2025-11-30T20:22:34.419Z" }, + { url = "https://files.pythonhosted.org/packages/5c/e2/714694e4b87b85a18e2c243614974413c60aa107fd815b8cbc42b873d1d7/rpds_py-0.30.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:7cee9c752c0364588353e627da8a7e808a66873672bcb5f52890c33fd965b394", size = 563120, upload-time = "2025-11-30T20:22:35.903Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ab/d5d5e3bcedb0a77f4f613706b750e50a5a3ba1c15ccd3665ecc636c968fd/rpds_py-0.30.0-cp312-cp312-win32.whl", hash = "sha256:1ab5b83dbcf55acc8b08fc62b796ef672c457b17dbd7820a11d6c52c06839bdf", size = 223782, upload-time = "2025-11-30T20:22:37.271Z" }, + { url = "https://files.pythonhosted.org/packages/39/3b/f786af9957306fdc38a74cef405b7b93180f481fb48453a114bb6465744a/rpds_py-0.30.0-cp312-cp312-win_amd64.whl", hash = "sha256:a090322ca841abd453d43456ac34db46e8b05fd9b3b4ac0c78bcde8b089f959b", size = 240463, upload-time = "2025-11-30T20:22:39.021Z" }, + { url = "https://files.pythonhosted.org/packages/f3/d2/b91dc748126c1559042cfe41990deb92c4ee3e2b415f6b5234969ffaf0cc/rpds_py-0.30.0-cp312-cp312-win_arm64.whl", hash = "sha256:669b1805bd639dd2989b281be2cfd951c6121b65e729d9b843e9639ef1fd555e", size = 230868, upload-time = "2025-11-30T20:22:40.493Z" }, + { url = "https://files.pythonhosted.org/packages/ed/dc/d61221eb88ff410de3c49143407f6f3147acf2538c86f2ab7ce65ae7d5f9/rpds_py-0.30.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f83424d738204d9770830d35290ff3273fbb02b41f919870479fab14b9d303b2", size = 374887, upload-time = "2025-11-30T20:22:41.812Z" }, + { url = "https://files.pythonhosted.org/packages/fd/32/55fb50ae104061dbc564ef15cc43c013dc4a9f4527a1f4d99baddf56fe5f/rpds_py-0.30.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e7536cd91353c5273434b4e003cbda89034d67e7710eab8761fd918ec6c69cf8", size = 358904, upload-time = "2025-11-30T20:22:43.479Z" }, + { url = "https://files.pythonhosted.org/packages/58/70/faed8186300e3b9bdd138d0273109784eea2396c68458ed580f885dfe7ad/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2771c6c15973347f50fece41fc447c054b7ac2ae0502388ce3b6738cd366e3d4", size = 389945, upload-time = "2025-11-30T20:22:44.819Z" }, + { url = "https://files.pythonhosted.org/packages/bd/a8/073cac3ed2c6387df38f71296d002ab43496a96b92c823e76f46b8af0543/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0a59119fc6e3f460315fe9d08149f8102aa322299deaa5cab5b40092345c2136", size = 407783, upload-time = "2025-11-30T20:22:46.103Z" }, + { url = "https://files.pythonhosted.org/packages/77/57/5999eb8c58671f1c11eba084115e77a8899d6e694d2a18f69f0ba471ec8b/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:76fec018282b4ead0364022e3c54b60bf368b9d926877957a8624b58419169b7", size = 515021, upload-time = "2025-11-30T20:22:47.458Z" }, + { url = "https://files.pythonhosted.org/packages/e0/af/5ab4833eadc36c0a8ed2bc5c0de0493c04f6c06de223170bd0798ff98ced/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:692bef75a5525db97318e8cd061542b5a79812d711ea03dbc1f6f8dbb0c5f0d2", size = 414589, upload-time = "2025-11-30T20:22:48.872Z" }, + { url = "https://files.pythonhosted.org/packages/b7/de/f7192e12b21b9e9a68a6d0f249b4af3fdcdff8418be0767a627564afa1f1/rpds_py-0.30.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9027da1ce107104c50c81383cae773ef5c24d296dd11c99e2629dbd7967a20c6", size = 394025, upload-time = "2025-11-30T20:22:50.196Z" }, + { url = "https://files.pythonhosted.org/packages/91/c4/fc70cd0249496493500e7cc2de87504f5aa6509de1e88623431fec76d4b6/rpds_py-0.30.0-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:9cf69cdda1f5968a30a359aba2f7f9aa648a9ce4b580d6826437f2b291cfc86e", size = 408895, upload-time = "2025-11-30T20:22:51.87Z" }, + { url = "https://files.pythonhosted.org/packages/58/95/d9275b05ab96556fefff73a385813eb66032e4c99f411d0795372d9abcea/rpds_py-0.30.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a4796a717bf12b9da9d3ad002519a86063dcac8988b030e405704ef7d74d2d9d", size = 422799, upload-time = "2025-11-30T20:22:53.341Z" }, + { url = "https://files.pythonhosted.org/packages/06/c1/3088fc04b6624eb12a57eb814f0d4997a44b0d208d6cace713033ff1a6ba/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5d4c2aa7c50ad4728a094ebd5eb46c452e9cb7edbfdb18f9e1221f597a73e1e7", size = 572731, upload-time = "2025-11-30T20:22:54.778Z" }, + { url = "https://files.pythonhosted.org/packages/d8/42/c612a833183b39774e8ac8fecae81263a68b9583ee343db33ab571a7ce55/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ba81a9203d07805435eb06f536d95a266c21e5b2dfbf6517748ca40c98d19e31", size = 599027, upload-time = "2025-11-30T20:22:56.212Z" }, + { url = "https://files.pythonhosted.org/packages/5f/60/525a50f45b01d70005403ae0e25f43c0384369ad24ffe46e8d9068b50086/rpds_py-0.30.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:945dccface01af02675628334f7cf49c2af4c1c904748efc5cf7bbdf0b579f95", size = 563020, upload-time = "2025-11-30T20:22:58.2Z" }, + { url = "https://files.pythonhosted.org/packages/0b/5d/47c4655e9bcd5ca907148535c10e7d489044243cc9941c16ed7cd53be91d/rpds_py-0.30.0-cp313-cp313-win32.whl", hash = "sha256:b40fb160a2db369a194cb27943582b38f79fc4887291417685f3ad693c5a1d5d", size = 223139, upload-time = "2025-11-30T20:23:00.209Z" }, + { url = "https://files.pythonhosted.org/packages/f2/e1/485132437d20aa4d3e1d8b3fb5a5e65aa8139f1e097080c2a8443201742c/rpds_py-0.30.0-cp313-cp313-win_amd64.whl", hash = "sha256:806f36b1b605e2d6a72716f321f20036b9489d29c51c91f4dd29a3e3afb73b15", size = 240224, upload-time = "2025-11-30T20:23:02.008Z" }, + { url = "https://files.pythonhosted.org/packages/24/95/ffd128ed1146a153d928617b0ef673960130be0009c77d8fbf0abe306713/rpds_py-0.30.0-cp313-cp313-win_arm64.whl", hash = "sha256:d96c2086587c7c30d44f31f42eae4eac89b60dabbac18c7669be3700f13c3ce1", size = 230645, upload-time = "2025-11-30T20:23:03.43Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1b/b10de890a0def2a319a2626334a7f0ae388215eb60914dbac8a3bae54435/rpds_py-0.30.0-cp313-cp313t-macosx_10_12_x86_64.whl", hash = "sha256:eb0b93f2e5c2189ee831ee43f156ed34e2a89a78a66b98cadad955972548be5a", size = 364443, upload-time = "2025-11-30T20:23:04.878Z" }, + { url = "https://files.pythonhosted.org/packages/0d/bf/27e39f5971dc4f305a4fb9c672ca06f290f7c4e261c568f3dea16a410d47/rpds_py-0.30.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:922e10f31f303c7c920da8981051ff6d8c1a56207dbdf330d9047f6d30b70e5e", size = 353375, upload-time = "2025-11-30T20:23:06.342Z" }, + { url = "https://files.pythonhosted.org/packages/40/58/442ada3bba6e8e6615fc00483135c14a7538d2ffac30e2d933ccf6852232/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cdc62c8286ba9bf7f47befdcea13ea0e26bf294bda99758fd90535cbaf408000", size = 383850, upload-time = "2025-11-30T20:23:07.825Z" }, + { url = "https://files.pythonhosted.org/packages/14/14/f59b0127409a33c6ef6f5c1ebd5ad8e32d7861c9c7adfa9a624fc3889f6c/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:47f9a91efc418b54fb8190a6b4aa7813a23fb79c51f4bb84e418f5476c38b8db", size = 392812, upload-time = "2025-11-30T20:23:09.228Z" }, + { url = "https://files.pythonhosted.org/packages/b3/66/e0be3e162ac299b3a22527e8913767d869e6cc75c46bd844aa43fb81ab62/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f3587eb9b17f3789ad50824084fa6f81921bbf9a795826570bda82cb3ed91f2", size = 517841, upload-time = "2025-11-30T20:23:11.186Z" }, + { url = "https://files.pythonhosted.org/packages/3d/55/fa3b9cf31d0c963ecf1ba777f7cf4b2a2c976795ac430d24a1f43d25a6ba/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:39c02563fc592411c2c61d26b6c5fe1e51eaa44a75aa2c8735ca88b0d9599daa", size = 408149, upload-time = "2025-11-30T20:23:12.864Z" }, + { url = "https://files.pythonhosted.org/packages/60/ca/780cf3b1a32b18c0f05c441958d3758f02544f1d613abf9488cd78876378/rpds_py-0.30.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a1234d8febafdfd33a42d97da7a43f5dcb120c1060e352a3fbc0c6d36e2083", size = 383843, upload-time = "2025-11-30T20:23:14.638Z" }, + { url = "https://files.pythonhosted.org/packages/82/86/d5f2e04f2aa6247c613da0c1dd87fcd08fa17107e858193566048a1e2f0a/rpds_py-0.30.0-cp313-cp313t-manylinux_2_31_riscv64.whl", hash = "sha256:eb2c4071ab598733724c08221091e8d80e89064cd472819285a9ab0f24bcedb9", size = 396507, upload-time = "2025-11-30T20:23:16.105Z" }, + { url = "https://files.pythonhosted.org/packages/4b/9a/453255d2f769fe44e07ea9785c8347edaf867f7026872e76c1ad9f7bed92/rpds_py-0.30.0-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6bdfdb946967d816e6adf9a3d8201bfad269c67efe6cefd7093ef959683c8de0", size = 414949, upload-time = "2025-11-30T20:23:17.539Z" }, + { url = "https://files.pythonhosted.org/packages/a3/31/622a86cdc0c45d6df0e9ccb6becdba5074735e7033c20e401a6d9d0e2ca0/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:c77afbd5f5250bf27bf516c7c4a016813eb2d3e116139aed0096940c5982da94", size = 565790, upload-time = "2025-11-30T20:23:19.029Z" }, + { url = "https://files.pythonhosted.org/packages/1c/5d/15bbf0fb4a3f58a3b1c67855ec1efcc4ceaef4e86644665fff03e1b66d8d/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:61046904275472a76c8c90c9ccee9013d70a6d0f73eecefd38c1ae7c39045a08", size = 590217, upload-time = "2025-11-30T20:23:20.885Z" }, + { url = "https://files.pythonhosted.org/packages/6d/61/21b8c41f68e60c8cc3b2e25644f0e3681926020f11d06ab0b78e3c6bbff1/rpds_py-0.30.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4c5f36a861bc4b7da6516dbdf302c55313afa09b81931e8280361a4f6c9a2d27", size = 555806, upload-time = "2025-11-30T20:23:22.488Z" }, + { url = "https://files.pythonhosted.org/packages/f9/39/7e067bb06c31de48de3eb200f9fc7c58982a4d3db44b07e73963e10d3be9/rpds_py-0.30.0-cp313-cp313t-win32.whl", hash = "sha256:3d4a69de7a3e50ffc214ae16d79d8fbb0922972da0356dcf4d0fdca2878559c6", size = 211341, upload-time = "2025-11-30T20:23:24.449Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4d/222ef0b46443cf4cf46764d9c630f3fe4abaa7245be9417e56e9f52b8f65/rpds_py-0.30.0-cp313-cp313t-win_amd64.whl", hash = "sha256:f14fc5df50a716f7ece6a80b6c78bb35ea2ca47c499e422aa4463455dd96d56d", size = 225768, upload-time = "2025-11-30T20:23:25.908Z" }, + { url = "https://files.pythonhosted.org/packages/86/81/dad16382ebbd3d0e0328776d8fd7ca94220e4fa0798d1dc5e7da48cb3201/rpds_py-0.30.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:68f19c879420aa08f61203801423f6cd5ac5f0ac4ac82a2368a9fcd6a9a075e0", size = 362099, upload-time = "2025-11-30T20:23:27.316Z" }, + { url = "https://files.pythonhosted.org/packages/2b/60/19f7884db5d5603edf3c6bce35408f45ad3e97e10007df0e17dd57af18f8/rpds_py-0.30.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ec7c4490c672c1a0389d319b3a9cfcd098dcdc4783991553c332a15acf7249be", size = 353192, upload-time = "2025-11-30T20:23:29.151Z" }, + { url = "https://files.pythonhosted.org/packages/bf/c4/76eb0e1e72d1a9c4703c69607cec123c29028bff28ce41588792417098ac/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f251c812357a3fed308d684a5079ddfb9d933860fc6de89f2b7ab00da481e65f", size = 384080, upload-time = "2025-11-30T20:23:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/72/87/87ea665e92f3298d1b26d78814721dc39ed8d2c74b86e83348d6b48a6f31/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ac98b175585ecf4c0348fd7b29c3864bda53b805c773cbf7bfdaffc8070c976f", size = 394841, upload-time = "2025-11-30T20:23:32.209Z" }, + { url = "https://files.pythonhosted.org/packages/77/ad/7783a89ca0587c15dcbf139b4a8364a872a25f861bdb88ed99f9b0dec985/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3e62880792319dbeb7eb866547f2e35973289e7d5696c6e295476448f5b63c87", size = 516670, upload-time = "2025-11-30T20:23:33.742Z" }, + { url = "https://files.pythonhosted.org/packages/5b/3c/2882bdac942bd2172f3da574eab16f309ae10a3925644e969536553cb4ee/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4e7fc54e0900ab35d041b0601431b0a0eb495f0851a0639b6ef90f7741b39a18", size = 408005, upload-time = "2025-11-30T20:23:35.253Z" }, + { url = "https://files.pythonhosted.org/packages/ce/81/9a91c0111ce1758c92516a3e44776920b579d9a7c09b2b06b642d4de3f0f/rpds_py-0.30.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47e77dc9822d3ad616c3d5759ea5631a75e5809d5a28707744ef79d7a1bcfcad", size = 382112, upload-time = "2025-11-30T20:23:36.842Z" }, + { url = "https://files.pythonhosted.org/packages/cf/8e/1da49d4a107027e5fbc64daeab96a0706361a2918da10cb41769244b805d/rpds_py-0.30.0-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:b4dc1a6ff022ff85ecafef7979a2c6eb423430e05f1165d6688234e62ba99a07", size = 399049, upload-time = "2025-11-30T20:23:38.343Z" }, + { url = "https://files.pythonhosted.org/packages/df/5a/7ee239b1aa48a127570ec03becbb29c9d5a9eb092febbd1699d567cae859/rpds_py-0.30.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4559c972db3a360808309e06a74628b95eaccbf961c335c8fe0d590cf587456f", size = 415661, upload-time = "2025-11-30T20:23:40.263Z" }, + { url = "https://files.pythonhosted.org/packages/70/ea/caa143cf6b772f823bc7929a45da1fa83569ee49b11d18d0ada7f5ee6fd6/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0ed177ed9bded28f8deb6ab40c183cd1192aa0de40c12f38be4d59cd33cb5c65", size = 565606, upload-time = "2025-11-30T20:23:42.186Z" }, + { url = "https://files.pythonhosted.org/packages/64/91/ac20ba2d69303f961ad8cf55bf7dbdb4763f627291ba3d0d7d67333cced9/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ad1fa8db769b76ea911cb4e10f049d80bf518c104f15b3edb2371cc65375c46f", size = 591126, upload-time = "2025-11-30T20:23:44.086Z" }, + { url = "https://files.pythonhosted.org/packages/21/20/7ff5f3c8b00c8a95f75985128c26ba44503fb35b8e0259d812766ea966c7/rpds_py-0.30.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:46e83c697b1f1c72b50e5ee5adb4353eef7406fb3f2043d64c33f20ad1c2fc53", size = 553371, upload-time = "2025-11-30T20:23:46.004Z" }, + { url = "https://files.pythonhosted.org/packages/72/c7/81dadd7b27c8ee391c132a6b192111ca58d866577ce2d9b0ca157552cce0/rpds_py-0.30.0-cp314-cp314-win32.whl", hash = "sha256:ee454b2a007d57363c2dfd5b6ca4a5d7e2c518938f8ed3b706e37e5d470801ed", size = 215298, upload-time = "2025-11-30T20:23:47.696Z" }, + { url = "https://files.pythonhosted.org/packages/3e/d2/1aaac33287e8cfb07aab2e6b8ac1deca62f6f65411344f1433c55e6f3eb8/rpds_py-0.30.0-cp314-cp314-win_amd64.whl", hash = "sha256:95f0802447ac2d10bcc69f6dc28fe95fdf17940367b21d34e34c737870758950", size = 228604, upload-time = "2025-11-30T20:23:49.501Z" }, + { url = "https://files.pythonhosted.org/packages/e8/95/ab005315818cc519ad074cb7784dae60d939163108bd2b394e60dc7b5461/rpds_py-0.30.0-cp314-cp314-win_arm64.whl", hash = "sha256:613aa4771c99f03346e54c3f038e4cc574ac09a3ddfb0e8878487335e96dead6", size = 222391, upload-time = "2025-11-30T20:23:50.96Z" }, + { url = "https://files.pythonhosted.org/packages/9e/68/154fe0194d83b973cdedcdcc88947a2752411165930182ae41d983dcefa6/rpds_py-0.30.0-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:7e6ecfcb62edfd632e56983964e6884851786443739dbfe3582947e87274f7cb", size = 364868, upload-time = "2025-11-30T20:23:52.494Z" }, + { url = "https://files.pythonhosted.org/packages/83/69/8bbc8b07ec854d92a8b75668c24d2abcb1719ebf890f5604c61c9369a16f/rpds_py-0.30.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:a1d0bc22a7cdc173fedebb73ef81e07faef93692b8c1ad3733b67e31e1b6e1b8", size = 353747, upload-time = "2025-11-30T20:23:54.036Z" }, + { url = "https://files.pythonhosted.org/packages/ab/00/ba2e50183dbd9abcce9497fa5149c62b4ff3e22d338a30d690f9af970561/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d08f00679177226c4cb8c5265012eea897c8ca3b93f429e546600c971bcbae7", size = 383795, upload-time = "2025-11-30T20:23:55.556Z" }, + { url = "https://files.pythonhosted.org/packages/05/6f/86f0272b84926bcb0e4c972262f54223e8ecc556b3224d281e6598fc9268/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5965af57d5848192c13534f90f9dd16464f3c37aaf166cc1da1cae1fd5a34898", size = 393330, upload-time = "2025-11-30T20:23:57.033Z" }, + { url = "https://files.pythonhosted.org/packages/cb/e9/0e02bb2e6dc63d212641da45df2b0bf29699d01715913e0d0f017ee29438/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a4e86e34e9ab6b667c27f3211ca48f73dba7cd3d90f8d5b11be56e5dbc3fb4e", size = 518194, upload-time = "2025-11-30T20:23:58.637Z" }, + { url = "https://files.pythonhosted.org/packages/ee/ca/be7bca14cf21513bdf9c0606aba17d1f389ea2b6987035eb4f62bd923f25/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e5d3e6b26f2c785d65cc25ef1e5267ccbe1b069c5c21b8cc724efee290554419", size = 408340, upload-time = "2025-11-30T20:24:00.2Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c7/736e00ebf39ed81d75544c0da6ef7b0998f8201b369acf842f9a90dc8fce/rpds_py-0.30.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:626a7433c34566535b6e56a1b39a7b17ba961e97ce3b80ec62e6f1312c025551", size = 383765, upload-time = "2025-11-30T20:24:01.759Z" }, + { url = "https://files.pythonhosted.org/packages/4a/3f/da50dfde9956aaf365c4adc9533b100008ed31aea635f2b8d7b627e25b49/rpds_py-0.30.0-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:acd7eb3f4471577b9b5a41baf02a978e8bdeb08b4b355273994f8b87032000a8", size = 396834, upload-time = "2025-11-30T20:24:03.687Z" }, + { url = "https://files.pythonhosted.org/packages/4e/00/34bcc2565b6020eab2623349efbdec810676ad571995911f1abdae62a3a0/rpds_py-0.30.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:fe5fa731a1fa8a0a56b0977413f8cacac1768dad38d16b3a296712709476fbd5", size = 415470, upload-time = "2025-11-30T20:24:05.232Z" }, + { url = "https://files.pythonhosted.org/packages/8c/28/882e72b5b3e6f718d5453bd4d0d9cf8df36fddeb4ddbbab17869d5868616/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:74a3243a411126362712ee1524dfc90c650a503502f135d54d1b352bd01f2404", size = 565630, upload-time = "2025-11-30T20:24:06.878Z" }, + { url = "https://files.pythonhosted.org/packages/3b/97/04a65539c17692de5b85c6e293520fd01317fd878ea1995f0367d4532fb1/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:3e8eeb0544f2eb0d2581774be4c3410356eba189529a6b3e36bbbf9696175856", size = 591148, upload-time = "2025-11-30T20:24:08.445Z" }, + { url = "https://files.pythonhosted.org/packages/85/70/92482ccffb96f5441aab93e26c4d66489eb599efdcf96fad90c14bbfb976/rpds_py-0.30.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:dbd936cde57abfee19ab3213cf9c26be06d60750e60a8e4dd85d1ab12c8b1f40", size = 556030, upload-time = "2025-11-30T20:24:10.956Z" }, + { url = "https://files.pythonhosted.org/packages/20/53/7c7e784abfa500a2b6b583b147ee4bb5a2b3747a9166bab52fec4b5b5e7d/rpds_py-0.30.0-cp314-cp314t-win32.whl", hash = "sha256:dc824125c72246d924f7f796b4f63c1e9dc810c7d9e2355864b3c3a73d59ade0", size = 211570, upload-time = "2025-11-30T20:24:12.735Z" }, + { url = "https://files.pythonhosted.org/packages/d0/02/fa464cdfbe6b26e0600b62c528b72d8608f5cc49f96b8d6e38c95d60c676/rpds_py-0.30.0-cp314-cp314t-win_amd64.whl", hash = "sha256:27f4b0e92de5bfbc6f86e43959e6edd1425c33b5e69aab0984a72047f2bcf1e3", size = 226532, upload-time = "2025-11-30T20:24:14.634Z" }, +] + +[[package]] +name = "sniffio" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" }, +] + +[[package]] +name = "sse-starlette" +version = "3.3.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "starlette" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/9f/c3695c2d2d4ef70072c3a06992850498b01c6bc9be531950813716b426fa/sse_starlette-3.3.2.tar.gz", hash = "sha256:678fca55a1945c734d8472a6cad186a55ab02840b4f6786f5ee8770970579dcd", size = 32326, upload-time = "2026-02-28T11:24:34.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/61/28/8cb142d3fe80c4a2d8af54ca0b003f47ce0ba920974e7990fa6e016402d1/sse_starlette-3.3.2-py3-none-any.whl", hash = "sha256:5c3ea3dad425c601236726af2f27689b74494643f57017cafcb6f8c9acfbb862", size = 14270, upload-time = "2026-02-28T11:24:32.984Z" }, +] + +[[package]] +name = "starlette" +version = "0.52.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c4/68/79977123bb7be889ad680d79a40f339082c1978b5cfcf62c2d8d196873ac/starlette-0.52.1.tar.gz", hash = "sha256:834edd1b0a23167694292e94f597773bc3f89f362be6effee198165a35d62933", size = 2653702, upload-time = "2026-01-18T13:34:11.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/81/0d/13d1d239a25cbfb19e740db83143e95c772a1fe10202dda4b76792b114dd/starlette-0.52.1-py3-none-any.whl", hash = "sha256:0029d43eb3d273bc4f83a08720b4912ea4b071087a3b48db01b7c839f7954d74", size = 74272, upload-time = "2026-01-18T13:34:09.188Z" }, +] + +[[package]] +name = "tenacity" +version = "9.1.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/c6/ee486fd809e357697ee8a44d3d69222b344920433d3b6666ccd9b374630c/tenacity-9.1.4.tar.gz", hash = "sha256:adb31d4c263f2bd041081ab33b498309a57c77f9acf2db65aadf0898179cf93a", size = 49413, upload-time = "2026-02-07T10:45:33.841Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d7/c1/eb8f9debc45d3b7918a32ab756658a0904732f75e555402972246b0b8e71/tenacity-9.1.4-py3-none-any.whl", hash = "sha256:6095a360c919085f28c6527de529e76a06ad89b23659fa881ae0649b867a9d55", size = 28926, upload-time = "2026-02-07T10:45:32.24Z" }, +] + +[[package]] +name = "tiktoken" +version = "0.12.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "regex" }, + { name = "requests" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/7d/ab/4d017d0f76ec3171d469d80fc03dfbb4e48a4bcaddaa831b31d526f05edc/tiktoken-0.12.0.tar.gz", hash = "sha256:b18ba7ee2b093863978fcb14f74b3707cdc8d4d4d3836853ce7ec60772139931", size = 37806, upload-time = "2025-10-06T20:22:45.419Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/85/be65d39d6b647c79800fd9d29241d081d4eeb06271f383bb87200d74cf76/tiktoken-0.12.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b97f74aca0d78a1ff21b8cd9e9925714c15a9236d6ceacf5c7327c117e6e21e8", size = 1050728, upload-time = "2025-10-06T20:21:52.756Z" }, + { url = "https://files.pythonhosted.org/packages/4a/42/6573e9129bc55c9bf7300b3a35bef2c6b9117018acca0dc760ac2d93dffe/tiktoken-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b90f5ad190a4bb7c3eb30c5fa32e1e182ca1ca79f05e49b448438c3e225a49b", size = 994049, upload-time = "2025-10-06T20:21:53.782Z" }, + { url = "https://files.pythonhosted.org/packages/66/c5/ed88504d2f4a5fd6856990b230b56d85a777feab84e6129af0822f5d0f70/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:65b26c7a780e2139e73acc193e5c63ac754021f160df919add909c1492c0fb37", size = 1129008, upload-time = "2025-10-06T20:21:54.832Z" }, + { url = "https://files.pythonhosted.org/packages/f4/90/3dae6cc5436137ebd38944d396b5849e167896fc2073da643a49f372dc4f/tiktoken-0.12.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:edde1ec917dfd21c1f2f8046b86348b0f54a2c0547f68149d8600859598769ad", size = 1152665, upload-time = "2025-10-06T20:21:56.129Z" }, + { url = "https://files.pythonhosted.org/packages/a3/fe/26df24ce53ffde419a42f5f53d755b995c9318908288c17ec3f3448313a3/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:35a2f8ddd3824608b3d650a000c1ef71f730d0c56486845705a8248da00f9fe5", size = 1194230, upload-time = "2025-10-06T20:21:57.546Z" }, + { url = "https://files.pythonhosted.org/packages/20/cc/b064cae1a0e9fac84b0d2c46b89f4e57051a5f41324e385d10225a984c24/tiktoken-0.12.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:83d16643edb7fa2c99eff2ab7733508aae1eebb03d5dfc46f5565862810f24e3", size = 1254688, upload-time = "2025-10-06T20:21:58.619Z" }, + { url = "https://files.pythonhosted.org/packages/81/10/b8523105c590c5b8349f2587e2fdfe51a69544bd5a76295fc20f2374f470/tiktoken-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffc5288f34a8bc02e1ea7047b8d041104791d2ddbf42d1e5fa07822cbffe16bd", size = 878694, upload-time = "2025-10-06T20:21:59.876Z" }, + { url = "https://files.pythonhosted.org/packages/00/61/441588ee21e6b5cdf59d6870f86beb9789e532ee9718c251b391b70c68d6/tiktoken-0.12.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:775c2c55de2310cc1bc9a3ad8826761cbdc87770e586fd7b6da7d4589e13dab3", size = 1050802, upload-time = "2025-10-06T20:22:00.96Z" }, + { url = "https://files.pythonhosted.org/packages/1f/05/dcf94486d5c5c8d34496abe271ac76c5b785507c8eae71b3708f1ad9b45a/tiktoken-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a01b12f69052fbe4b080a2cfb867c4de12c704b56178edf1d1d7b273561db160", size = 993995, upload-time = "2025-10-06T20:22:02.788Z" }, + { url = "https://files.pythonhosted.org/packages/a0/70/5163fe5359b943f8db9946b62f19be2305de8c3d78a16f629d4165e2f40e/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:01d99484dc93b129cd0964f9d34eee953f2737301f18b3c7257bf368d7615baa", size = 1128948, upload-time = "2025-10-06T20:22:03.814Z" }, + { url = "https://files.pythonhosted.org/packages/0c/da/c028aa0babf77315e1cef357d4d768800c5f8a6de04d0eac0f377cb619fa/tiktoken-0.12.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:4a1a4fcd021f022bfc81904a911d3df0f6543b9e7627b51411da75ff2fe7a1be", size = 1151986, upload-time = "2025-10-06T20:22:05.173Z" }, + { url = "https://files.pythonhosted.org/packages/a0/5a/886b108b766aa53e295f7216b509be95eb7d60b166049ce2c58416b25f2a/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:981a81e39812d57031efdc9ec59fa32b2a5a5524d20d4776574c4b4bd2e9014a", size = 1194222, upload-time = "2025-10-06T20:22:06.265Z" }, + { url = "https://files.pythonhosted.org/packages/f4/f8/4db272048397636ac7a078d22773dd2795b1becee7bc4922fe6207288d57/tiktoken-0.12.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9baf52f84a3f42eef3ff4e754a0db79a13a27921b457ca9832cf944c6be4f8f3", size = 1255097, upload-time = "2025-10-06T20:22:07.403Z" }, + { url = "https://files.pythonhosted.org/packages/8e/32/45d02e2e0ea2be3a9ed22afc47d93741247e75018aac967b713b2941f8ea/tiktoken-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:b8a0cd0c789a61f31bf44851defbd609e8dd1e2c8589c614cc1060940ef1f697", size = 879117, upload-time = "2025-10-06T20:22:08.418Z" }, + { url = "https://files.pythonhosted.org/packages/ce/76/994fc868f88e016e6d05b0da5ac24582a14c47893f4474c3e9744283f1d5/tiktoken-0.12.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d5f89ea5680066b68bcb797ae85219c72916c922ef0fcdd3480c7d2315ffff16", size = 1050309, upload-time = "2025-10-06T20:22:10.939Z" }, + { url = "https://files.pythonhosted.org/packages/f6/b8/57ef1456504c43a849821920d582a738a461b76a047f352f18c0b26c6516/tiktoken-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b4e7ed1c6a7a8a60a3230965bdedba8cc58f68926b835e519341413370e0399a", size = 993712, upload-time = "2025-10-06T20:22:12.115Z" }, + { url = "https://files.pythonhosted.org/packages/72/90/13da56f664286ffbae9dbcfadcc625439142675845baa62715e49b87b68b/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:fc530a28591a2d74bce821d10b418b26a094bf33839e69042a6e86ddb7a7fb27", size = 1128725, upload-time = "2025-10-06T20:22:13.541Z" }, + { url = "https://files.pythonhosted.org/packages/05/df/4f80030d44682235bdaecd7346c90f67ae87ec8f3df4a3442cb53834f7e4/tiktoken-0.12.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:06a9f4f49884139013b138920a4c393aa6556b2f8f536345f11819389c703ebb", size = 1151875, upload-time = "2025-10-06T20:22:14.559Z" }, + { url = "https://files.pythonhosted.org/packages/22/1f/ae535223a8c4ef4c0c1192e3f9b82da660be9eb66b9279e95c99288e9dab/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:04f0e6a985d95913cabc96a741c5ffec525a2c72e9df086ff17ebe35985c800e", size = 1194451, upload-time = "2025-10-06T20:22:15.545Z" }, + { url = "https://files.pythonhosted.org/packages/78/a7/f8ead382fce0243cb625c4f266e66c27f65ae65ee9e77f59ea1653b6d730/tiktoken-0.12.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:0ee8f9ae00c41770b5f9b0bb1235474768884ae157de3beb5439ca0fd70f3e25", size = 1253794, upload-time = "2025-10-06T20:22:16.624Z" }, + { url = "https://files.pythonhosted.org/packages/93/e0/6cc82a562bc6365785a3ff0af27a2a092d57c47d7a81d9e2295d8c36f011/tiktoken-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:dc2dd125a62cb2b3d858484d6c614d136b5b848976794edfb63688d539b8b93f", size = 878777, upload-time = "2025-10-06T20:22:18.036Z" }, + { url = "https://files.pythonhosted.org/packages/72/05/3abc1db5d2c9aadc4d2c76fa5640134e475e58d9fbb82b5c535dc0de9b01/tiktoken-0.12.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a90388128df3b3abeb2bfd1895b0681412a8d7dc644142519e6f0a97c2111646", size = 1050188, upload-time = "2025-10-06T20:22:19.563Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7b/50c2f060412202d6c95f32b20755c7a6273543b125c0985d6fa9465105af/tiktoken-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:da900aa0ad52247d8794e307d6446bd3cdea8e192769b56276695d34d2c9aa88", size = 993978, upload-time = "2025-10-06T20:22:20.702Z" }, + { url = "https://files.pythonhosted.org/packages/14/27/bf795595a2b897e271771cd31cb847d479073497344c637966bdf2853da1/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:285ba9d73ea0d6171e7f9407039a290ca77efcdb026be7769dccc01d2c8d7fff", size = 1129271, upload-time = "2025-10-06T20:22:22.06Z" }, + { url = "https://files.pythonhosted.org/packages/f5/de/9341a6d7a8f1b448573bbf3425fa57669ac58258a667eb48a25dfe916d70/tiktoken-0.12.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:d186a5c60c6a0213f04a7a802264083dea1bbde92a2d4c7069e1a56630aef830", size = 1151216, upload-time = "2025-10-06T20:22:23.085Z" }, + { url = "https://files.pythonhosted.org/packages/75/0d/881866647b8d1be4d67cb24e50d0c26f9f807f994aa1510cb9ba2fe5f612/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:604831189bd05480f2b885ecd2d1986dc7686f609de48208ebbbddeea071fc0b", size = 1194860, upload-time = "2025-10-06T20:22:24.602Z" }, + { url = "https://files.pythonhosted.org/packages/b3/1e/b651ec3059474dab649b8d5b69f5c65cd8fcd8918568c1935bd4136c9392/tiktoken-0.12.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:8f317e8530bb3a222547b85a58583238c8f74fd7a7408305f9f63246d1a0958b", size = 1254567, upload-time = "2025-10-06T20:22:25.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/57/ce64fd16ac390fafde001268c364d559447ba09b509181b2808622420eec/tiktoken-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:399c3dd672a6406719d84442299a490420b458c44d3ae65516302a99675888f3", size = 921067, upload-time = "2025-10-06T20:22:26.753Z" }, + { url = "https://files.pythonhosted.org/packages/ac/a4/72eed53e8976a099539cdd5eb36f241987212c29629d0a52c305173e0a68/tiktoken-0.12.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c2c714c72bc00a38ca969dae79e8266ddec999c7ceccd603cc4f0d04ccd76365", size = 1050473, upload-time = "2025-10-06T20:22:27.775Z" }, + { url = "https://files.pythonhosted.org/packages/e6/d7/0110b8f54c008466b19672c615f2168896b83706a6611ba6e47313dbc6e9/tiktoken-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:cbb9a3ba275165a2cb0f9a83f5d7025afe6b9d0ab01a22b50f0e74fee2ad253e", size = 993855, upload-time = "2025-10-06T20:22:28.799Z" }, + { url = "https://files.pythonhosted.org/packages/5f/77/4f268c41a3957c418b084dd576ea2fad2e95da0d8e1ab705372892c2ca22/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:dfdfaa5ffff8993a3af94d1125870b1d27aed7cb97aa7eb8c1cefdbc87dbee63", size = 1129022, upload-time = "2025-10-06T20:22:29.981Z" }, + { url = "https://files.pythonhosted.org/packages/4e/2b/fc46c90fe5028bd094cd6ee25a7db321cb91d45dc87531e2bdbb26b4867a/tiktoken-0.12.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:584c3ad3d0c74f5269906eb8a659c8bfc6144a52895d9261cdaf90a0ae5f4de0", size = 1150736, upload-time = "2025-10-06T20:22:30.996Z" }, + { url = "https://files.pythonhosted.org/packages/28/c0/3c7a39ff68022ddfd7d93f3337ad90389a342f761c4d71de99a3ccc57857/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:54c891b416a0e36b8e2045b12b33dd66fb34a4fe7965565f1b482da50da3e86a", size = 1194908, upload-time = "2025-10-06T20:22:32.073Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0d/c1ad6f4016a3968c048545f5d9b8ffebf577774b2ede3e2e352553b685fe/tiktoken-0.12.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5edb8743b88d5be814b1a8a8854494719080c28faaa1ccbef02e87354fe71ef0", size = 1253706, upload-time = "2025-10-06T20:22:33.385Z" }, + { url = "https://files.pythonhosted.org/packages/af/df/c7891ef9d2712ad774777271d39fdef63941ffba0a9d59b7ad1fd2765e57/tiktoken-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:f61c0aea5565ac82e2ec50a05e02a6c44734e91b51c10510b084ea1b8e633a71", size = 920667, upload-time = "2025-10-06T20:22:34.444Z" }, +] + +[[package]] +name = "tqdm" +version = "4.67.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, +] + +[[package]] +name = "typing-extensions" +version = "4.15.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, +] + +[[package]] +name = "typing-inspection" +version = "0.4.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" }, +] + +[[package]] +name = "urllib3" +version = "2.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c7/24/5f1b3bdffd70275f6661c76461e25f024d5a38a46f04aaca912426a2b1d3/urllib3-2.6.3.tar.gz", hash = "sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed", size = 435556, upload-time = "2026-01-07T16:24:43.925Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/08/aaaad47bc4e9dc8c725e68f9d04865dbcb2052843ff09c97b08904852d84/urllib3-2.6.3-py3-none-any.whl", hash = "sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4", size = 131584, upload-time = "2026-01-07T16:24:42.685Z" }, +] + +[[package]] +name = "uuid-utils" +version = "0.14.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/7b/d1/38a573f0c631c062cf42fa1f5d021d4dd3c31fb23e4376e4b56b0c9fbbed/uuid_utils-0.14.1.tar.gz", hash = "sha256:9bfc95f64af80ccf129c604fb6b8ca66c6f256451e32bc4570f760e4309c9b69", size = 22195, upload-time = "2026-02-20T22:50:38.833Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/b7/add4363039a34506a58457d96d4aa2126061df3a143eb4d042aedd6a2e76/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:93a3b5dc798a54a1feb693f2d1cb4cf08258c32ff05ae4929b5f0a2ca624a4f0", size = 604679, upload-time = "2026-02-20T22:50:27.469Z" }, + { url = "https://files.pythonhosted.org/packages/dd/84/d1d0bef50d9e66d31b2019997c741b42274d53dde2e001b7a83e9511c339/uuid_utils-0.14.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:ccd65a4b8e83af23eae5e56d88034b2fe7264f465d3e830845f10d1591b81741", size = 309346, upload-time = "2026-02-20T22:50:31.857Z" }, + { url = "https://files.pythonhosted.org/packages/ef/ed/b6d6fd52a6636d7c3eddf97d68da50910bf17cd5ac221992506fb56cf12e/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b56b0cacd81583834820588378e432b0696186683b813058b707aedc1e16c4b1", size = 344714, upload-time = "2026-02-20T22:50:42.642Z" }, + { url = "https://files.pythonhosted.org/packages/a8/a7/a19a1719fb626fe0b31882db36056d44fe904dc0cf15b06fdf56b2679cf7/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb3cf14de789097320a3c56bfdfdd51b1225d11d67298afbedee7e84e3837c96", size = 350914, upload-time = "2026-02-20T22:50:36.487Z" }, + { url = "https://files.pythonhosted.org/packages/1d/fc/f6690e667fdc3bb1a73f57951f97497771c56fe23e3d302d7404be394d4f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e0854a90d67f4b0cc6e54773deb8be618f4c9bad98d3326f081423b5d14fae", size = 482609, upload-time = "2026-02-20T22:50:37.511Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/dcd3fa031320921a12ec7b4672dea3bd1dd90ddffa363a91831ba834d559/uuid_utils-0.14.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce6743ba194de3910b5feb1a62590cd2587e33a73ab6af8a01b642ceb5055862", size = 345699, upload-time = "2026-02-20T22:50:46.87Z" }, + { url = "https://files.pythonhosted.org/packages/04/28/e5220204b58b44ac0047226a9d016a113fde039280cc8732d9e6da43b39f/uuid_utils-0.14.1-cp39-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:043fb58fde6cf1620a6c066382f04f87a8e74feb0f95a585e4ed46f5d44af57b", size = 372205, upload-time = "2026-02-20T22:50:28.438Z" }, + { url = "https://files.pythonhosted.org/packages/c7/d9/3d2eb98af94b8dfffc82b6a33b4dfc87b0a5de2c68a28f6dde0db1f8681b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c915d53f22945e55fe0d3d3b0b87fd965a57f5fd15666fd92d6593a73b1dd297", size = 521836, upload-time = "2026-02-20T22:50:23.057Z" }, + { url = "https://files.pythonhosted.org/packages/a8/15/0eb106cc6fe182f7577bc0ab6e2f0a40be247f35c5e297dbf7bbc460bd02/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:0972488e3f9b449e83f006ead5a0e0a33ad4a13e4462e865b7c286ab7d7566a3", size = 625260, upload-time = "2026-02-20T22:50:25.949Z" }, + { url = "https://files.pythonhosted.org/packages/3c/17/f539507091334b109e7496830af2f093d9fc8082411eafd3ece58af1f8ba/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:1c238812ae0c8ffe77d8d447a32c6dfd058ea4631246b08b5a71df586ff08531", size = 587824, upload-time = "2026-02-20T22:50:35.225Z" }, + { url = "https://files.pythonhosted.org/packages/2e/c2/d37a7b2e41f153519367d4db01f0526e0d4b06f1a4a87f1c5dfca5d70a8b/uuid_utils-0.14.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:bec8f8ef627af86abf8298e7ec50926627e29b34fa907fcfbedb45aaa72bca43", size = 551407, upload-time = "2026-02-20T22:50:44.915Z" }, + { url = "https://files.pythonhosted.org/packages/65/36/2d24b2cbe78547c6532da33fb8613debd3126eccc33a6374ab788f5e46e9/uuid_utils-0.14.1-cp39-abi3-win32.whl", hash = "sha256:b54d6aa6252d96bac1fdbc80d26ba71bad9f220b2724d692ad2f2310c22ef523", size = 183476, upload-time = "2026-02-20T22:50:32.745Z" }, + { url = "https://files.pythonhosted.org/packages/83/92/2d7e90df8b1a69ec4cff33243ce02b7a62f926ef9e2f0eca5a026889cd73/uuid_utils-0.14.1-cp39-abi3-win_amd64.whl", hash = "sha256:fc27638c2ce267a0ce3e06828aff786f91367f093c80625ee21dad0208e0f5ba", size = 187147, upload-time = "2026-02-20T22:50:45.807Z" }, + { url = "https://files.pythonhosted.org/packages/d9/26/529f4beee17e5248e37e0bc17a2761d34c0fa3b1e5729c88adb2065bae6e/uuid_utils-0.14.1-cp39-abi3-win_arm64.whl", hash = "sha256:b04cb49b42afbc4ff8dbc60cf054930afc479d6f4dd7f1ec3bbe5dbfdde06b7a", size = 188132, upload-time = "2026-02-20T22:50:41.718Z" }, +] + +[[package]] +name = "uvicorn" +version = "0.41.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/32/ce/eeb58ae4ac36fe09e3842eb02e0eb676bf2c53ae062b98f1b2531673efdd/uvicorn-0.41.0.tar.gz", hash = "sha256:09d11cf7008da33113824ee5a1c6422d89fbc2ff476540d69a34c87fab8b571a", size = 82633, upload-time = "2026-02-16T23:07:24.1Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/83/e4/d04a086285c20886c0daad0e026f250869201013d18f81d9ff5eada73a88/uvicorn-0.41.0-py3-none-any.whl", hash = "sha256:29e35b1d2c36a04b9e180d4007ede3bcb32a85fbdfd6c6aeb3f26839de088187", size = 68783, upload-time = "2026-02-16T23:07:22.357Z" }, +] + +[[package]] +name = "xxhash" +version = "3.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/02/84/30869e01909fb37a6cc7e18688ee8bf1e42d57e7e0777636bd47524c43c7/xxhash-3.6.0.tar.gz", hash = "sha256:f0162a78b13a0d7617b2845b90c763339d1f1d82bb04a4b07f4ab535cc5e05d6", size = 85160, upload-time = "2025-10-02T14:37:08.097Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9a/07/d9412f3d7d462347e4511181dea65e47e0d0e16e26fbee2ea86a2aefb657/xxhash-3.6.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:01362c4331775398e7bb34e3ab403bc9ee9f7c497bc7dee6272114055277dd3c", size = 32744, upload-time = "2025-10-02T14:34:34.622Z" }, + { url = "https://files.pythonhosted.org/packages/79/35/0429ee11d035fc33abe32dca1b2b69e8c18d236547b9a9b72c1929189b9a/xxhash-3.6.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b7b2df81a23f8cb99656378e72501b2cb41b1827c0f5a86f87d6b06b69f9f204", size = 30816, upload-time = "2025-10-02T14:34:36.043Z" }, + { url = "https://files.pythonhosted.org/packages/b7/f2/57eb99aa0f7d98624c0932c5b9a170e1806406cdbcdb510546634a1359e0/xxhash-3.6.0-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:dc94790144e66b14f67b10ac8ed75b39ca47536bf8800eb7c24b50271ea0c490", size = 194035, upload-time = "2025-10-02T14:34:37.354Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ed/6224ba353690d73af7a3f1c7cdb1fc1b002e38f783cb991ae338e1eb3d79/xxhash-3.6.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:93f107c673bccf0d592cdba077dedaf52fe7f42dcd7676eba1f6d6f0c3efffd2", size = 212914, upload-time = "2025-10-02T14:34:38.6Z" }, + { url = "https://files.pythonhosted.org/packages/38/86/fb6b6130d8dd6b8942cc17ab4d90e223653a89aa32ad2776f8af7064ed13/xxhash-3.6.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2aa5ee3444c25b69813663c9f8067dcfaa2e126dc55e8dddf40f4d1c25d7effa", size = 212163, upload-time = "2025-10-02T14:34:39.872Z" }, + { url = "https://files.pythonhosted.org/packages/ee/dc/e84875682b0593e884ad73b2d40767b5790d417bde603cceb6878901d647/xxhash-3.6.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:f7f99123f0e1194fa59cc69ad46dbae2e07becec5df50a0509a808f90a0f03f0", size = 445411, upload-time = "2025-10-02T14:34:41.569Z" }, + { url = "https://files.pythonhosted.org/packages/11/4f/426f91b96701ec2f37bb2b8cec664eff4f658a11f3fa9d94f0a887ea6d2b/xxhash-3.6.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:49e03e6fe2cac4a1bc64952dd250cf0dbc5ef4ebb7b8d96bce82e2de163c82a2", size = 193883, upload-time = "2025-10-02T14:34:43.249Z" }, + { url = "https://files.pythonhosted.org/packages/53/5a/ddbb83eee8e28b778eacfc5a85c969673e4023cdeedcfcef61f36731610b/xxhash-3.6.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:bd17fede52a17a4f9a7bc4472a5867cb0b160deeb431795c0e4abe158bc784e9", size = 210392, upload-time = "2025-10-02T14:34:45.042Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c2/ff69efd07c8c074ccdf0a4f36fcdd3d27363665bcdf4ba399abebe643465/xxhash-3.6.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:6fb5f5476bef678f69db04f2bd1efbed3030d2aba305b0fc1773645f187d6a4e", size = 197898, upload-time = "2025-10-02T14:34:46.302Z" }, + { url = "https://files.pythonhosted.org/packages/58/ca/faa05ac19b3b622c7c9317ac3e23954187516298a091eb02c976d0d3dd45/xxhash-3.6.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:843b52f6d88071f87eba1631b684fcb4b2068cd2180a0224122fe4ef011a9374", size = 210655, upload-time = "2025-10-02T14:34:47.571Z" }, + { url = "https://files.pythonhosted.org/packages/d4/7a/06aa7482345480cc0cb597f5c875b11a82c3953f534394f620b0be2f700c/xxhash-3.6.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:7d14a6cfaf03b1b6f5f9790f76880601ccc7896aff7ab9cd8978a939c1eb7e0d", size = 414001, upload-time = "2025-10-02T14:34:49.273Z" }, + { url = "https://files.pythonhosted.org/packages/23/07/63ffb386cd47029aa2916b3d2f454e6cc5b9f5c5ada3790377d5430084e7/xxhash-3.6.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:418daf3db71e1413cfe211c2f9a528456936645c17f46b5204705581a45390ae", size = 191431, upload-time = "2025-10-02T14:34:50.798Z" }, + { url = "https://files.pythonhosted.org/packages/0f/93/14fde614cadb4ddf5e7cebf8918b7e8fac5ae7861c1875964f17e678205c/xxhash-3.6.0-cp312-cp312-win32.whl", hash = "sha256:50fc255f39428a27299c20e280d6193d8b63b8ef8028995323bf834a026b4fbb", size = 30617, upload-time = "2025-10-02T14:34:51.954Z" }, + { url = "https://files.pythonhosted.org/packages/13/5d/0d125536cbe7565a83d06e43783389ecae0c0f2ed037b48ede185de477c0/xxhash-3.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:c0f2ab8c715630565ab8991b536ecded9416d615538be8ecddce43ccf26cbc7c", size = 31534, upload-time = "2025-10-02T14:34:53.276Z" }, + { url = "https://files.pythonhosted.org/packages/54/85/6ec269b0952ec7e36ba019125982cf11d91256a778c7c3f98a4c5043d283/xxhash-3.6.0-cp312-cp312-win_arm64.whl", hash = "sha256:eae5c13f3bc455a3bbb68bdc513912dc7356de7e2280363ea235f71f54064829", size = 27876, upload-time = "2025-10-02T14:34:54.371Z" }, + { url = "https://files.pythonhosted.org/packages/33/76/35d05267ac82f53ae9b0e554da7c5e281ee61f3cad44c743f0fcd354f211/xxhash-3.6.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:599e64ba7f67472481ceb6ee80fa3bd828fd61ba59fb11475572cc5ee52b89ec", size = 32738, upload-time = "2025-10-02T14:34:55.839Z" }, + { url = "https://files.pythonhosted.org/packages/31/a8/3fbce1cd96534a95e35d5120637bf29b0d7f5d8fa2f6374e31b4156dd419/xxhash-3.6.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7d8b8aaa30fca4f16f0c84a5c8d7ddee0e25250ec2796c973775373257dde8f1", size = 30821, upload-time = "2025-10-02T14:34:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/0c/ea/d387530ca7ecfa183cb358027f1833297c6ac6098223fd14f9782cd0015c/xxhash-3.6.0-cp313-cp313-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:d597acf8506d6e7101a4a44a5e428977a51c0fadbbfd3c39650cca9253f6e5a6", size = 194127, upload-time = "2025-10-02T14:34:59.21Z" }, + { url = "https://files.pythonhosted.org/packages/ba/0c/71435dcb99874b09a43b8d7c54071e600a7481e42b3e3ce1eb5226a5711a/xxhash-3.6.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:858dc935963a33bc33490128edc1c12b0c14d9c7ebaa4e387a7869ecc4f3e263", size = 212975, upload-time = "2025-10-02T14:35:00.816Z" }, + { url = "https://files.pythonhosted.org/packages/84/7a/c2b3d071e4bb4a90b7057228a99b10d51744878f4a8a6dd643c8bd897620/xxhash-3.6.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:ba284920194615cb8edf73bf52236ce2e1664ccd4a38fdb543506413529cc546", size = 212241, upload-time = "2025-10-02T14:35:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/81/5f/640b6eac0128e215f177df99eadcd0f1b7c42c274ab6a394a05059694c5a/xxhash-3.6.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:4b54219177f6c6674d5378bd862c6aedf64725f70dd29c472eaae154df1a2e89", size = 445471, upload-time = "2025-10-02T14:35:03.61Z" }, + { url = "https://files.pythonhosted.org/packages/5e/1e/3c3d3ef071b051cc3abbe3721ffb8365033a172613c04af2da89d5548a87/xxhash-3.6.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:42c36dd7dbad2f5238950c377fcbf6811b1cdb1c444fab447960030cea60504d", size = 193936, upload-time = "2025-10-02T14:35:05.013Z" }, + { url = "https://files.pythonhosted.org/packages/2c/bd/4a5f68381939219abfe1c22a9e3a5854a4f6f6f3c4983a87d255f21f2e5d/xxhash-3.6.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f22927652cba98c44639ffdc7aaf35828dccf679b10b31c4ad72a5b530a18eb7", size = 210440, upload-time = "2025-10-02T14:35:06.239Z" }, + { url = "https://files.pythonhosted.org/packages/eb/37/b80fe3d5cfb9faff01a02121a0f4d565eb7237e9e5fc66e73017e74dcd36/xxhash-3.6.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b45fad44d9c5c119e9c6fbf2e1c656a46dc68e280275007bbfd3d572b21426db", size = 197990, upload-time = "2025-10-02T14:35:07.735Z" }, + { url = "https://files.pythonhosted.org/packages/d7/fd/2c0a00c97b9e18f72e1f240ad4e8f8a90fd9d408289ba9c7c495ed7dc05c/xxhash-3.6.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6f2580ffab1a8b68ef2b901cde7e55fa8da5e4be0977c68f78fc80f3c143de42", size = 210689, upload-time = "2025-10-02T14:35:09.438Z" }, + { url = "https://files.pythonhosted.org/packages/93/86/5dd8076a926b9a95db3206aba20d89a7fc14dd5aac16e5c4de4b56033140/xxhash-3.6.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:40c391dd3cd041ebc3ffe6f2c862f402e306eb571422e0aa918d8070ba31da11", size = 414068, upload-time = "2025-10-02T14:35:11.162Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/0bb129170ee8f3650f08e993baee550a09593462a5cddd8e44d0011102b1/xxhash-3.6.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f205badabde7aafd1a31e8ca2a3e5a763107a71c397c4481d6a804eb5063d8bd", size = 191495, upload-time = "2025-10-02T14:35:12.971Z" }, + { url = "https://files.pythonhosted.org/packages/e9/3a/6797e0114c21d1725e2577508e24006fd7ff1d8c0c502d3b52e45c1771d8/xxhash-3.6.0-cp313-cp313-win32.whl", hash = "sha256:2577b276e060b73b73a53042ea5bd5203d3e6347ce0d09f98500f418a9fcf799", size = 30620, upload-time = "2025-10-02T14:35:14.129Z" }, + { url = "https://files.pythonhosted.org/packages/86/15/9bc32671e9a38b413a76d24722a2bf8784a132c043063a8f5152d390b0f9/xxhash-3.6.0-cp313-cp313-win_amd64.whl", hash = "sha256:757320d45d2fbcce8f30c42a6b2f47862967aea7bf458b9625b4bbe7ee390392", size = 31542, upload-time = "2025-10-02T14:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/39/c5/cc01e4f6188656e56112d6a8e0dfe298a16934b8c47a247236549a3f7695/xxhash-3.6.0-cp313-cp313-win_arm64.whl", hash = "sha256:457b8f85dec5825eed7b69c11ae86834a018b8e3df5e77783c999663da2f96d6", size = 27880, upload-time = "2025-10-02T14:35:16.315Z" }, + { url = "https://files.pythonhosted.org/packages/f3/30/25e5321c8732759e930c555176d37e24ab84365482d257c3b16362235212/xxhash-3.6.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a42e633d75cdad6d625434e3468126c73f13f7584545a9cf34e883aa1710e702", size = 32956, upload-time = "2025-10-02T14:35:17.413Z" }, + { url = "https://files.pythonhosted.org/packages/9f/3c/0573299560d7d9f8ab1838f1efc021a280b5ae5ae2e849034ef3dee18810/xxhash-3.6.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:568a6d743219e717b07b4e03b0a828ce593833e498c3b64752e0f5df6bfe84db", size = 31072, upload-time = "2025-10-02T14:35:18.844Z" }, + { url = "https://files.pythonhosted.org/packages/7a/1c/52d83a06e417cd9d4137722693424885cc9878249beb3a7c829e74bf7ce9/xxhash-3.6.0-cp313-cp313t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:bec91b562d8012dae276af8025a55811b875baace6af510412a5e58e3121bc54", size = 196409, upload-time = "2025-10-02T14:35:20.31Z" }, + { url = "https://files.pythonhosted.org/packages/e3/8e/c6d158d12a79bbd0b878f8355432075fc82759e356ab5a111463422a239b/xxhash-3.6.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:78e7f2f4c521c30ad5e786fdd6bae89d47a32672a80195467b5de0480aa97b1f", size = 215736, upload-time = "2025-10-02T14:35:21.616Z" }, + { url = "https://files.pythonhosted.org/packages/bc/68/c4c80614716345d55071a396cf03d06e34b5f4917a467faf43083c995155/xxhash-3.6.0-cp313-cp313t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:3ed0df1b11a79856df5ffcab572cbd6b9627034c1c748c5566fa79df9048a7c5", size = 214833, upload-time = "2025-10-02T14:35:23.32Z" }, + { url = "https://files.pythonhosted.org/packages/7e/e9/ae27c8ffec8b953efa84c7c4a6c6802c263d587b9fc0d6e7cea64e08c3af/xxhash-3.6.0-cp313-cp313t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0e4edbfc7d420925b0dd5e792478ed393d6e75ff8fc219a6546fb446b6a417b1", size = 448348, upload-time = "2025-10-02T14:35:25.111Z" }, + { url = "https://files.pythonhosted.org/packages/d7/6b/33e21afb1b5b3f46b74b6bd1913639066af218d704cc0941404ca717fc57/xxhash-3.6.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fba27a198363a7ef87f8c0f6b171ec36b674fe9053742c58dd7e3201c1ab30ee", size = 196070, upload-time = "2025-10-02T14:35:26.586Z" }, + { url = "https://files.pythonhosted.org/packages/96/b6/fcabd337bc5fa624e7203aa0fa7d0c49eed22f72e93229431752bddc83d9/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:794fe9145fe60191c6532fa95063765529770edcdd67b3d537793e8004cabbfd", size = 212907, upload-time = "2025-10-02T14:35:28.087Z" }, + { url = "https://files.pythonhosted.org/packages/4b/d3/9ee6160e644d660fcf176c5825e61411c7f62648728f69c79ba237250143/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:6105ef7e62b5ac73a837778efc331a591d8442f8ef5c7e102376506cb4ae2729", size = 200839, upload-time = "2025-10-02T14:35:29.857Z" }, + { url = "https://files.pythonhosted.org/packages/0d/98/e8de5baa5109394baf5118f5e72ab21a86387c4f89b0e77ef3e2f6b0327b/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:f01375c0e55395b814a679b3eea205db7919ac2af213f4a6682e01220e5fe292", size = 213304, upload-time = "2025-10-02T14:35:31.222Z" }, + { url = "https://files.pythonhosted.org/packages/7b/1d/71056535dec5c3177eeb53e38e3d367dd1d16e024e63b1cee208d572a033/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:d706dca2d24d834a4661619dcacf51a75c16d65985718d6a7d73c1eeeb903ddf", size = 416930, upload-time = "2025-10-02T14:35:32.517Z" }, + { url = "https://files.pythonhosted.org/packages/dc/6c/5cbde9de2cd967c322e651c65c543700b19e7ae3e0aae8ece3469bf9683d/xxhash-3.6.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:5f059d9faeacd49c0215d66f4056e1326c80503f51a1532ca336a385edadd033", size = 193787, upload-time = "2025-10-02T14:35:33.827Z" }, + { url = "https://files.pythonhosted.org/packages/19/fa/0172e350361d61febcea941b0cc541d6e6c8d65d153e85f850a7b256ff8a/xxhash-3.6.0-cp313-cp313t-win32.whl", hash = "sha256:1244460adc3a9be84731d72b8e80625788e5815b68da3da8b83f78115a40a7ec", size = 30916, upload-time = "2025-10-02T14:35:35.107Z" }, + { url = "https://files.pythonhosted.org/packages/ad/e6/e8cf858a2b19d6d45820f072eff1bea413910592ff17157cabc5f1227a16/xxhash-3.6.0-cp313-cp313t-win_amd64.whl", hash = "sha256:b1e420ef35c503869c4064f4a2f2b08ad6431ab7b229a05cce39d74268bca6b8", size = 31799, upload-time = "2025-10-02T14:35:36.165Z" }, + { url = "https://files.pythonhosted.org/packages/56/15/064b197e855bfb7b343210e82490ae672f8bc7cdf3ddb02e92f64304ee8a/xxhash-3.6.0-cp313-cp313t-win_arm64.whl", hash = "sha256:ec44b73a4220623235f67a996c862049f375df3b1052d9899f40a6382c32d746", size = 28044, upload-time = "2025-10-02T14:35:37.195Z" }, + { url = "https://files.pythonhosted.org/packages/7e/5e/0138bc4484ea9b897864d59fce9be9086030825bc778b76cb5a33a906d37/xxhash-3.6.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:a40a3d35b204b7cc7643cbcf8c9976d818cb47befcfac8bbefec8038ac363f3e", size = 32754, upload-time = "2025-10-02T14:35:38.245Z" }, + { url = "https://files.pythonhosted.org/packages/18/d7/5dac2eb2ec75fd771957a13e5dda560efb2176d5203f39502a5fc571f899/xxhash-3.6.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:a54844be970d3fc22630b32d515e79a90d0a3ddb2644d8d7402e3c4c8da61405", size = 30846, upload-time = "2025-10-02T14:35:39.6Z" }, + { url = "https://files.pythonhosted.org/packages/fe/71/8bc5be2bb00deb5682e92e8da955ebe5fa982da13a69da5a40a4c8db12fb/xxhash-3.6.0-cp314-cp314-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:016e9190af8f0a4e3741343777710e3d5717427f175adfdc3e72508f59e2a7f3", size = 194343, upload-time = "2025-10-02T14:35:40.69Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/52badfb2aecec2c377ddf1ae75f55db3ba2d321c5e164f14461c90837ef3/xxhash-3.6.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4f6f72232f849eb9d0141e2ebe2677ece15adfd0fa599bc058aad83c714bb2c6", size = 213074, upload-time = "2025-10-02T14:35:42.29Z" }, + { url = "https://files.pythonhosted.org/packages/a2/2b/ae46b4e9b92e537fa30d03dbc19cdae57ed407e9c26d163895e968e3de85/xxhash-3.6.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:63275a8aba7865e44b1813d2177e0f5ea7eadad3dd063a21f7cf9afdc7054063", size = 212388, upload-time = "2025-10-02T14:35:43.929Z" }, + { url = "https://files.pythonhosted.org/packages/f5/80/49f88d3afc724b4ac7fbd664c8452d6db51b49915be48c6982659e0e7942/xxhash-3.6.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cd01fa2aa00d8b017c97eb46b9a794fbdca53fc14f845f5a328c71254b0abb7", size = 445614, upload-time = "2025-10-02T14:35:45.216Z" }, + { url = "https://files.pythonhosted.org/packages/ed/ba/603ce3961e339413543d8cd44f21f2c80e2a7c5cfe692a7b1f2cccf58f3c/xxhash-3.6.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0226aa89035b62b6a86d3c68df4d7c1f47a342b8683da2b60cedcddb46c4d95b", size = 194024, upload-time = "2025-10-02T14:35:46.959Z" }, + { url = "https://files.pythonhosted.org/packages/78/d1/8e225ff7113bf81545cfdcd79eef124a7b7064a0bba53605ff39590b95c2/xxhash-3.6.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c6e193e9f56e4ca4923c61238cdaced324f0feac782544eb4c6d55ad5cc99ddd", size = 210541, upload-time = "2025-10-02T14:35:48.301Z" }, + { url = "https://files.pythonhosted.org/packages/6f/58/0f89d149f0bad89def1a8dd38feb50ccdeb643d9797ec84707091d4cb494/xxhash-3.6.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:9176dcaddf4ca963d4deb93866d739a343c01c969231dbe21680e13a5d1a5bf0", size = 198305, upload-time = "2025-10-02T14:35:49.584Z" }, + { url = "https://files.pythonhosted.org/packages/11/38/5eab81580703c4df93feb5f32ff8fa7fe1e2c51c1f183ee4e48d4bb9d3d7/xxhash-3.6.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:c1ce4009c97a752e682b897aa99aef84191077a9433eb237774689f14f8ec152", size = 210848, upload-time = "2025-10-02T14:35:50.877Z" }, + { url = "https://files.pythonhosted.org/packages/5e/6b/953dc4b05c3ce678abca756416e4c130d2382f877a9c30a20d08ee6a77c0/xxhash-3.6.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:8cb2f4f679b01513b7adbb9b1b2f0f9cdc31b70007eaf9d59d0878809f385b11", size = 414142, upload-time = "2025-10-02T14:35:52.15Z" }, + { url = "https://files.pythonhosted.org/packages/08/a9/238ec0d4e81a10eb5026d4a6972677cbc898ba6c8b9dbaec12ae001b1b35/xxhash-3.6.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:653a91d7c2ab54a92c19ccf43508b6a555440b9be1bc8be553376778be7f20b5", size = 191547, upload-time = "2025-10-02T14:35:53.547Z" }, + { url = "https://files.pythonhosted.org/packages/f1/ee/3cf8589e06c2164ac77c3bf0aa127012801128f1feebf2a079272da5737c/xxhash-3.6.0-cp314-cp314-win32.whl", hash = "sha256:a756fe893389483ee8c394d06b5ab765d96e68fbbfe6fde7aa17e11f5720559f", size = 31214, upload-time = "2025-10-02T14:35:54.746Z" }, + { url = "https://files.pythonhosted.org/packages/02/5d/a19552fbc6ad4cb54ff953c3908bbc095f4a921bc569433d791f755186f1/xxhash-3.6.0-cp314-cp314-win_amd64.whl", hash = "sha256:39be8e4e142550ef69629c9cd71b88c90e9a5db703fecbcf265546d9536ca4ad", size = 32290, upload-time = "2025-10-02T14:35:55.791Z" }, + { url = "https://files.pythonhosted.org/packages/b1/11/dafa0643bc30442c887b55baf8e73353a344ee89c1901b5a5c54a6c17d39/xxhash-3.6.0-cp314-cp314-win_arm64.whl", hash = "sha256:25915e6000338999236f1eb68a02a32c3275ac338628a7eaa5a269c401995679", size = 28795, upload-time = "2025-10-02T14:35:57.162Z" }, + { url = "https://files.pythonhosted.org/packages/2c/db/0e99732ed7f64182aef4a6fb145e1a295558deec2a746265dcdec12d191e/xxhash-3.6.0-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:c5294f596a9017ca5a3e3f8884c00b91ab2ad2933cf288f4923c3fd4346cf3d4", size = 32955, upload-time = "2025-10-02T14:35:58.267Z" }, + { url = "https://files.pythonhosted.org/packages/55/f4/2a7c3c68e564a099becfa44bb3d398810cc0ff6749b0d3cb8ccb93f23c14/xxhash-3.6.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1cf9dcc4ab9cff01dfbba78544297a3a01dafd60f3bde4e2bfd016cf7e4ddc67", size = 31072, upload-time = "2025-10-02T14:35:59.382Z" }, + { url = "https://files.pythonhosted.org/packages/c6/d9/72a29cddc7250e8a5819dad5d466facb5dc4c802ce120645630149127e73/xxhash-3.6.0-cp314-cp314t-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:01262da8798422d0685f7cef03b2bd3f4f46511b02830861df548d7def4402ad", size = 196579, upload-time = "2025-10-02T14:36:00.838Z" }, + { url = "https://files.pythonhosted.org/packages/63/93/b21590e1e381040e2ca305a884d89e1c345b347404f7780f07f2cdd47ef4/xxhash-3.6.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:51a73fb7cb3a3ead9f7a8b583ffd9b8038e277cdb8cb87cf890e88b3456afa0b", size = 215854, upload-time = "2025-10-02T14:36:02.207Z" }, + { url = "https://files.pythonhosted.org/packages/ce/b8/edab8a7d4fa14e924b29be877d54155dcbd8b80be85ea00d2be3413a9ed4/xxhash-3.6.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b9c6df83594f7df8f7f708ce5ebeacfc69f72c9fbaaababf6cf4758eaada0c9b", size = 214965, upload-time = "2025-10-02T14:36:03.507Z" }, + { url = "https://files.pythonhosted.org/packages/27/67/dfa980ac7f0d509d54ea0d5a486d2bb4b80c3f1bb22b66e6a05d3efaf6c0/xxhash-3.6.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:627f0af069b0ea56f312fd5189001c24578868643203bca1abbc2c52d3a6f3ca", size = 448484, upload-time = "2025-10-02T14:36:04.828Z" }, + { url = "https://files.pythonhosted.org/packages/8c/63/8ffc2cc97e811c0ca5d00ab36604b3ea6f4254f20b7bc658ca825ce6c954/xxhash-3.6.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aa912c62f842dfd013c5f21a642c9c10cd9f4c4e943e0af83618b4a404d9091a", size = 196162, upload-time = "2025-10-02T14:36:06.182Z" }, + { url = "https://files.pythonhosted.org/packages/4b/77/07f0e7a3edd11a6097e990f6e5b815b6592459cb16dae990d967693e6ea9/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:b465afd7909db30168ab62afe40b2fcf79eedc0b89a6c0ab3123515dc0df8b99", size = 213007, upload-time = "2025-10-02T14:36:07.733Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d8/bc5fa0d152837117eb0bef6f83f956c509332ce133c91c63ce07ee7c4873/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:a881851cf38b0a70e7c4d3ce81fc7afd86fbc2a024f4cfb2a97cf49ce04b75d3", size = 200956, upload-time = "2025-10-02T14:36:09.106Z" }, + { url = "https://files.pythonhosted.org/packages/26/a5/d749334130de9411783873e9b98ecc46688dad5db64ca6e04b02acc8b473/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9b3222c686a919a0f3253cfc12bb118b8b103506612253b5baeaac10d8027cf6", size = 213401, upload-time = "2025-10-02T14:36:10.585Z" }, + { url = "https://files.pythonhosted.org/packages/89/72/abed959c956a4bfc72b58c0384bb7940663c678127538634d896b1195c10/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:c5aa639bc113e9286137cec8fadc20e9cd732b2cc385c0b7fa673b84fc1f2a93", size = 417083, upload-time = "2025-10-02T14:36:12.276Z" }, + { url = "https://files.pythonhosted.org/packages/0c/b3/62fd2b586283b7d7d665fb98e266decadf31f058f1cf6c478741f68af0cb/xxhash-3.6.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5c1343d49ac102799905e115aee590183c3921d475356cb24b4de29a4bc56518", size = 193913, upload-time = "2025-10-02T14:36:14.025Z" }, + { url = "https://files.pythonhosted.org/packages/9a/9a/c19c42c5b3f5a4aad748a6d5b4f23df3bed7ee5445accc65a0fb3ff03953/xxhash-3.6.0-cp314-cp314t-win32.whl", hash = "sha256:5851f033c3030dd95c086b4a36a2683c2ff4a799b23af60977188b057e467119", size = 31586, upload-time = "2025-10-02T14:36:15.603Z" }, + { url = "https://files.pythonhosted.org/packages/03/d6/4cc450345be9924fd5dc8c590ceda1db5b43a0a889587b0ae81a95511360/xxhash-3.6.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0444e7967dac37569052d2409b00a8860c2135cff05502df4da80267d384849f", size = 32526, upload-time = "2025-10-02T14:36:16.708Z" }, + { url = "https://files.pythonhosted.org/packages/0f/c9/7243eb3f9eaabd1a88a5a5acadf06df2d83b100c62684b7425c6a11bcaa8/xxhash-3.6.0-cp314-cp314t-win_arm64.whl", hash = "sha256:bb79b1e63f6fd84ec778a4b1916dfe0a7c3fdb986c06addd5db3a0d413819d95", size = 28898, upload-time = "2025-10-02T14:36:17.843Z" }, +] + +[[package]] +name = "zstandard" +version = "0.25.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/fd/aa/3e0508d5a5dd96529cdc5a97011299056e14c6505b678fd58938792794b1/zstandard-0.25.0.tar.gz", hash = "sha256:7713e1179d162cf5c7906da876ec2ccb9c3a9dcbdffef0cc7f70c3667a205f0b", size = 711513, upload-time = "2025-09-14T22:15:54.002Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/82/fc/f26eb6ef91ae723a03e16eddb198abcfce2bc5a42e224d44cc8b6765e57e/zstandard-0.25.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7b3c3a3ab9daa3eed242d6ecceead93aebbb8f5f84318d82cee643e019c4b73b", size = 795738, upload-time = "2025-09-14T22:16:56.237Z" }, + { url = "https://files.pythonhosted.org/packages/aa/1c/d920d64b22f8dd028a8b90e2d756e431a5d86194caa78e3819c7bf53b4b3/zstandard-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:913cbd31a400febff93b564a23e17c3ed2d56c064006f54efec210d586171c00", size = 640436, upload-time = "2025-09-14T22:16:57.774Z" }, + { url = "https://files.pythonhosted.org/packages/53/6c/288c3f0bd9fcfe9ca41e2c2fbfd17b2097f6af57b62a81161941f09afa76/zstandard-0.25.0-cp312-cp312-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:011d388c76b11a0c165374ce660ce2c8efa8e5d87f34996aa80f9c0816698b64", size = 5343019, upload-time = "2025-09-14T22:16:59.302Z" }, + { url = "https://files.pythonhosted.org/packages/1e/15/efef5a2f204a64bdb5571e6161d49f7ef0fffdbca953a615efbec045f60f/zstandard-0.25.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dffecc361d079bb48d7caef5d673c88c8988d3d33fb74ab95b7ee6da42652ea", size = 5063012, upload-time = "2025-09-14T22:17:01.156Z" }, + { url = "https://files.pythonhosted.org/packages/b7/37/a6ce629ffdb43959e92e87ebdaeebb5ac81c944b6a75c9c47e300f85abdf/zstandard-0.25.0-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7149623bba7fdf7e7f24312953bcf73cae103db8cae49f8154dd1eadc8a29ecb", size = 5394148, upload-time = "2025-09-14T22:17:03.091Z" }, + { url = "https://files.pythonhosted.org/packages/e3/79/2bf870b3abeb5c070fe2d670a5a8d1057a8270f125ef7676d29ea900f496/zstandard-0.25.0-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6a573a35693e03cf1d67799fd01b50ff578515a8aeadd4595d2a7fa9f3ec002a", size = 5451652, upload-time = "2025-09-14T22:17:04.979Z" }, + { url = "https://files.pythonhosted.org/packages/53/60/7be26e610767316c028a2cbedb9a3beabdbe33e2182c373f71a1c0b88f36/zstandard-0.25.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5a56ba0db2d244117ed744dfa8f6f5b366e14148e00de44723413b2f3938a902", size = 5546993, upload-time = "2025-09-14T22:17:06.781Z" }, + { url = "https://files.pythonhosted.org/packages/85/c7/3483ad9ff0662623f3648479b0380d2de5510abf00990468c286c6b04017/zstandard-0.25.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:10ef2a79ab8e2974e2075fb984e5b9806c64134810fac21576f0668e7ea19f8f", size = 5046806, upload-time = "2025-09-14T22:17:08.415Z" }, + { url = "https://files.pythonhosted.org/packages/08/b3/206883dd25b8d1591a1caa44b54c2aad84badccf2f1de9e2d60a446f9a25/zstandard-0.25.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aaf21ba8fb76d102b696781bddaa0954b782536446083ae3fdaa6f16b25a1c4b", size = 5576659, upload-time = "2025-09-14T22:17:10.164Z" }, + { url = "https://files.pythonhosted.org/packages/9d/31/76c0779101453e6c117b0ff22565865c54f48f8bd807df2b00c2c404b8e0/zstandard-0.25.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1869da9571d5e94a85a5e8d57e4e8807b175c9e4a6294e3b66fa4efb074d90f6", size = 4953933, upload-time = "2025-09-14T22:17:11.857Z" }, + { url = "https://files.pythonhosted.org/packages/18/e1/97680c664a1bf9a247a280a053d98e251424af51f1b196c6d52f117c9720/zstandard-0.25.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:809c5bcb2c67cd0ed81e9229d227d4ca28f82d0f778fc5fea624a9def3963f91", size = 5268008, upload-time = "2025-09-14T22:17:13.627Z" }, + { url = "https://files.pythonhosted.org/packages/1e/73/316e4010de585ac798e154e88fd81bb16afc5c5cb1a72eeb16dd37e8024a/zstandard-0.25.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f27662e4f7dbf9f9c12391cb37b4c4c3cb90ffbd3b1fb9284dadbbb8935fa708", size = 5433517, upload-time = "2025-09-14T22:17:16.103Z" }, + { url = "https://files.pythonhosted.org/packages/5b/60/dd0f8cfa8129c5a0ce3ea6b7f70be5b33d2618013a161e1ff26c2b39787c/zstandard-0.25.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:99c0c846e6e61718715a3c9437ccc625de26593fea60189567f0118dc9db7512", size = 5814292, upload-time = "2025-09-14T22:17:17.827Z" }, + { url = "https://files.pythonhosted.org/packages/fc/5f/75aafd4b9d11b5407b641b8e41a57864097663699f23e9ad4dbb91dc6bfe/zstandard-0.25.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:474d2596a2dbc241a556e965fb76002c1ce655445e4e3bf38e5477d413165ffa", size = 5360237, upload-time = "2025-09-14T22:17:19.954Z" }, + { url = "https://files.pythonhosted.org/packages/ff/8d/0309daffea4fcac7981021dbf21cdb2e3427a9e76bafbcdbdf5392ff99a4/zstandard-0.25.0-cp312-cp312-win32.whl", hash = "sha256:23ebc8f17a03133b4426bcc04aabd68f8236eb78c3760f12783385171b0fd8bd", size = 436922, upload-time = "2025-09-14T22:17:24.398Z" }, + { url = "https://files.pythonhosted.org/packages/79/3b/fa54d9015f945330510cb5d0b0501e8253c127cca7ebe8ba46a965df18c5/zstandard-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:ffef5a74088f1e09947aecf91011136665152e0b4b359c42be3373897fb39b01", size = 506276, upload-time = "2025-09-14T22:17:21.429Z" }, + { url = "https://files.pythonhosted.org/packages/ea/6b/8b51697e5319b1f9ac71087b0af9a40d8a6288ff8025c36486e0c12abcc4/zstandard-0.25.0-cp312-cp312-win_arm64.whl", hash = "sha256:181eb40e0b6a29b3cd2849f825e0fa34397f649170673d385f3598ae17cca2e9", size = 462679, upload-time = "2025-09-14T22:17:23.147Z" }, + { url = "https://files.pythonhosted.org/packages/35/0b/8df9c4ad06af91d39e94fa96cc010a24ac4ef1378d3efab9223cc8593d40/zstandard-0.25.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ec996f12524f88e151c339688c3897194821d7f03081ab35d31d1e12ec975e94", size = 795735, upload-time = "2025-09-14T22:17:26.042Z" }, + { url = "https://files.pythonhosted.org/packages/3f/06/9ae96a3e5dcfd119377ba33d4c42a7d89da1efabd5cb3e366b156c45ff4d/zstandard-0.25.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a1a4ae2dec3993a32247995bdfe367fc3266da832d82f8438c8570f989753de1", size = 640440, upload-time = "2025-09-14T22:17:27.366Z" }, + { url = "https://files.pythonhosted.org/packages/d9/14/933d27204c2bd404229c69f445862454dcc101cd69ef8c6068f15aaec12c/zstandard-0.25.0-cp313-cp313-manylinux2010_i686.manylinux2014_i686.manylinux_2_12_i686.manylinux_2_17_i686.whl", hash = "sha256:e96594a5537722fdfb79951672a2a63aec5ebfb823e7560586f7484819f2a08f", size = 5343070, upload-time = "2025-09-14T22:17:28.896Z" }, + { url = "https://files.pythonhosted.org/packages/6d/db/ddb11011826ed7db9d0e485d13df79b58586bfdec56e5c84a928a9a78c1c/zstandard-0.25.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bfc4e20784722098822e3eee42b8e576b379ed72cca4a7cb856ae733e62192ea", size = 5063001, upload-time = "2025-09-14T22:17:31.044Z" }, + { url = "https://files.pythonhosted.org/packages/db/00/87466ea3f99599d02a5238498b87bf84a6348290c19571051839ca943777/zstandard-0.25.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:457ed498fc58cdc12fc48f7950e02740d4f7ae9493dd4ab2168a47c93c31298e", size = 5394120, upload-time = "2025-09-14T22:17:32.711Z" }, + { url = "https://files.pythonhosted.org/packages/2b/95/fc5531d9c618a679a20ff6c29e2b3ef1d1f4ad66c5e161ae6ff847d102a9/zstandard-0.25.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fd7a5004eb1980d3cefe26b2685bcb0b17989901a70a1040d1ac86f1d898c551", size = 5451230, upload-time = "2025-09-14T22:17:34.41Z" }, + { url = "https://files.pythonhosted.org/packages/63/4b/e3678b4e776db00f9f7b2fe58e547e8928ef32727d7a1ff01dea010f3f13/zstandard-0.25.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8e735494da3db08694d26480f1493ad2cf86e99bdd53e8e9771b2752a5c0246a", size = 5547173, upload-time = "2025-09-14T22:17:36.084Z" }, + { url = "https://files.pythonhosted.org/packages/4e/d5/ba05ed95c6b8ec30bd468dfeab20589f2cf709b5c940483e31d991f2ca58/zstandard-0.25.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3a39c94ad7866160a4a46d772e43311a743c316942037671beb264e395bdd611", size = 5046736, upload-time = "2025-09-14T22:17:37.891Z" }, + { url = "https://files.pythonhosted.org/packages/50/d5/870aa06b3a76c73eced65c044b92286a3c4e00554005ff51962deef28e28/zstandard-0.25.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:172de1f06947577d3a3005416977cce6168f2261284c02080e7ad0185faeced3", size = 5576368, upload-time = "2025-09-14T22:17:40.206Z" }, + { url = "https://files.pythonhosted.org/packages/5d/35/398dc2ffc89d304d59bc12f0fdd931b4ce455bddf7038a0a67733a25f550/zstandard-0.25.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3c83b0188c852a47cd13ef3bf9209fb0a77fa5374958b8c53aaa699398c6bd7b", size = 4954022, upload-time = "2025-09-14T22:17:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/9a/5c/36ba1e5507d56d2213202ec2b05e8541734af5f2ce378c5d1ceaf4d88dc4/zstandard-0.25.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1673b7199bbe763365b81a4f3252b8e80f44c9e323fc42940dc8843bfeaf9851", size = 5267889, upload-time = "2025-09-14T22:17:43.577Z" }, + { url = "https://files.pythonhosted.org/packages/70/e8/2ec6b6fb7358b2ec0113ae202647ca7c0e9d15b61c005ae5225ad0995df5/zstandard-0.25.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:0be7622c37c183406f3dbf0cba104118eb16a4ea7359eeb5752f0794882fc250", size = 5433952, upload-time = "2025-09-14T22:17:45.271Z" }, + { url = "https://files.pythonhosted.org/packages/7b/01/b5f4d4dbc59ef193e870495c6f1275f5b2928e01ff5a81fecb22a06e22fb/zstandard-0.25.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:5f5e4c2a23ca271c218ac025bd7d635597048b366d6f31f420aaeb715239fc98", size = 5814054, upload-time = "2025-09-14T22:17:47.08Z" }, + { url = "https://files.pythonhosted.org/packages/b2/e5/fbd822d5c6f427cf158316d012c5a12f233473c2f9c5fe5ab1ae5d21f3d8/zstandard-0.25.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4f187a0bb61b35119d1926aee039524d1f93aaf38a9916b8c4b78ac8514a0aaf", size = 5360113, upload-time = "2025-09-14T22:17:48.893Z" }, + { url = "https://files.pythonhosted.org/packages/8e/e0/69a553d2047f9a2c7347caa225bb3a63b6d7704ad74610cb7823baa08ed7/zstandard-0.25.0-cp313-cp313-win32.whl", hash = "sha256:7030defa83eef3e51ff26f0b7bfb229f0204b66fe18e04359ce3474ac33cbc09", size = 436936, upload-time = "2025-09-14T22:17:52.658Z" }, + { url = "https://files.pythonhosted.org/packages/d9/82/b9c06c870f3bd8767c201f1edbdf9e8dc34be5b0fbc5682c4f80fe948475/zstandard-0.25.0-cp313-cp313-win_amd64.whl", hash = "sha256:1f830a0dac88719af0ae43b8b2d6aef487d437036468ef3c2ea59c51f9d55fd5", size = 506232, upload-time = "2025-09-14T22:17:50.402Z" }, + { url = "https://files.pythonhosted.org/packages/d4/57/60c3c01243bb81d381c9916e2a6d9e149ab8627c0c7d7abb2d73384b3c0c/zstandard-0.25.0-cp313-cp313-win_arm64.whl", hash = "sha256:85304a43f4d513f5464ceb938aa02c1e78c2943b29f44a750b48b25ac999a049", size = 462671, upload-time = "2025-09-14T22:17:51.533Z" }, + { url = "https://files.pythonhosted.org/packages/3d/5c/f8923b595b55fe49e30612987ad8bf053aef555c14f05bb659dd5dbe3e8a/zstandard-0.25.0-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:e29f0cf06974c899b2c188ef7f783607dbef36da4c242eb6c82dcd8b512855e3", size = 795887, upload-time = "2025-09-14T22:17:54.198Z" }, + { url = "https://files.pythonhosted.org/packages/8d/09/d0a2a14fc3439c5f874042dca72a79c70a532090b7ba0003be73fee37ae2/zstandard-0.25.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:05df5136bc5a011f33cd25bc9f506e7426c0c9b3f9954f056831ce68f3b6689f", size = 640658, upload-time = "2025-09-14T22:17:55.423Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7c/8b6b71b1ddd517f68ffb55e10834388d4f793c49c6b83effaaa05785b0b4/zstandard-0.25.0-cp314-cp314-manylinux2010_i686.manylinux_2_12_i686.manylinux_2_28_i686.whl", hash = "sha256:f604efd28f239cc21b3adb53eb061e2a205dc164be408e553b41ba2ffe0ca15c", size = 5379849, upload-time = "2025-09-14T22:17:57.372Z" }, + { url = "https://files.pythonhosted.org/packages/a4/86/a48e56320d0a17189ab7a42645387334fba2200e904ee47fc5a26c1fd8ca/zstandard-0.25.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:223415140608d0f0da010499eaa8ccdb9af210a543fac54bce15babbcfc78439", size = 5058095, upload-time = "2025-09-14T22:17:59.498Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ad/eb659984ee2c0a779f9d06dbfe45e2dc39d99ff40a319895df2d3d9a48e5/zstandard-0.25.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2e54296a283f3ab5a26fc9b8b5d4978ea0532f37b231644f367aa588930aa043", size = 5551751, upload-time = "2025-09-14T22:18:01.618Z" }, + { url = "https://files.pythonhosted.org/packages/61/b3/b637faea43677eb7bd42ab204dfb7053bd5c4582bfe6b1baefa80ac0c47b/zstandard-0.25.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:ca54090275939dc8ec5dea2d2afb400e0f83444b2fc24e07df7fdef677110859", size = 6364818, upload-time = "2025-09-14T22:18:03.769Z" }, + { url = "https://files.pythonhosted.org/packages/31/dc/cc50210e11e465c975462439a492516a73300ab8caa8f5e0902544fd748b/zstandard-0.25.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e09bb6252b6476d8d56100e8147b803befa9a12cea144bbe629dd508800d1ad0", size = 5560402, upload-time = "2025-09-14T22:18:05.954Z" }, + { url = "https://files.pythonhosted.org/packages/c9/ae/56523ae9c142f0c08efd5e868a6da613ae76614eca1305259c3bf6a0ed43/zstandard-0.25.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a9ec8c642d1ec73287ae3e726792dd86c96f5681eb8df274a757bf62b750eae7", size = 4955108, upload-time = "2025-09-14T22:18:07.68Z" }, + { url = "https://files.pythonhosted.org/packages/98/cf/c899f2d6df0840d5e384cf4c4121458c72802e8bda19691f3b16619f51e9/zstandard-0.25.0-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:a4089a10e598eae6393756b036e0f419e8c1d60f44a831520f9af41c14216cf2", size = 5269248, upload-time = "2025-09-14T22:18:09.753Z" }, + { url = "https://files.pythonhosted.org/packages/1b/c0/59e912a531d91e1c192d3085fc0f6fb2852753c301a812d856d857ea03c6/zstandard-0.25.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:f67e8f1a324a900e75b5e28ffb152bcac9fbed1cc7b43f99cd90f395c4375344", size = 5430330, upload-time = "2025-09-14T22:18:11.966Z" }, + { url = "https://files.pythonhosted.org/packages/a0/1d/7e31db1240de2df22a58e2ea9a93fc6e38cc29353e660c0272b6735d6669/zstandard-0.25.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:9654dbc012d8b06fc3d19cc825af3f7bf8ae242226df5f83936cb39f5fdc846c", size = 5811123, upload-time = "2025-09-14T22:18:13.907Z" }, + { url = "https://files.pythonhosted.org/packages/f6/49/fac46df5ad353d50535e118d6983069df68ca5908d4d65b8c466150a4ff1/zstandard-0.25.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:4203ce3b31aec23012d3a4cf4a2ed64d12fea5269c49aed5e4c3611b938e4088", size = 5359591, upload-time = "2025-09-14T22:18:16.465Z" }, + { url = "https://files.pythonhosted.org/packages/c2/38/f249a2050ad1eea0bb364046153942e34abba95dd5520af199aed86fbb49/zstandard-0.25.0-cp314-cp314-win32.whl", hash = "sha256:da469dc041701583e34de852d8634703550348d5822e66a0c827d39b05365b12", size = 444513, upload-time = "2025-09-14T22:18:20.61Z" }, + { url = "https://files.pythonhosted.org/packages/3a/43/241f9615bcf8ba8903b3f0432da069e857fc4fd1783bd26183db53c4804b/zstandard-0.25.0-cp314-cp314-win_amd64.whl", hash = "sha256:c19bcdd826e95671065f8692b5a4aa95c52dc7a02a4c5a0cac46deb879a017a2", size = 516118, upload-time = "2025-09-14T22:18:17.849Z" }, + { url = "https://files.pythonhosted.org/packages/f0/ef/da163ce2450ed4febf6467d77ccb4cd52c4c30ab45624bad26ca0a27260c/zstandard-0.25.0-cp314-cp314-win_arm64.whl", hash = "sha256:d7541afd73985c630bafcd6338d2518ae96060075f9463d7dc14cfb33514383d", size = 476940, upload-time = "2025-09-14T22:18:19.088Z" }, +] diff --git a/terraform/cluster-profile-variables-tf/manifests/wordpress-default.yaml b/terraform/cluster-profile-variables-tf/manifests/wordpress-default.yaml index 608ae77..3c7846f 100644 --- a/terraform/cluster-profile-variables-tf/manifests/wordpress-default.yaml +++ b/terraform/cluster-profile-variables-tf/manifests/wordpress-default.yaml @@ -1,3 +1,6 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + pack: #The namespace (on the target cluster) to install this chart #When not found, a new namespace will be created diff --git a/terraform/cluster-profile-variables-tf/manifests/wordpress-variables.yaml b/terraform/cluster-profile-variables-tf/manifests/wordpress-variables.yaml index aeb9844..5144081 100644 --- a/terraform/cluster-profile-variables-tf/manifests/wordpress-variables.yaml +++ b/terraform/cluster-profile-variables-tf/manifests/wordpress-variables.yaml @@ -1,3 +1,6 @@ +# Copyright (c) Spectro Cloud +# SPDX-License-Identifier: Apache-2.0 + pack: #The namespace (on the target cluster) to install this chart #When not found, a new namespace will be created From 13303158a2d38cafc0e8d0868cc9399ff5eb6351 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Wed, 4 Mar 2026 15:25:20 -0700 Subject: [PATCH 02/13] chore: fix husky warning --- .husky/commit-msg | 2 -- 1 file changed, 2 deletions(-) diff --git a/.husky/commit-msg b/.husky/commit-msg index d1f519e..4a40d38 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,3 +1 @@ -#!/usr/bin/env sh -. "$(dirname -- "$0")/_/husky.sh" npx --no -- commitlint --edit '' \ No newline at end of file From 13d555653db901bf6a6ef6e91a6e63bf5874c516 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Wed, 11 Mar 2026 10:23:38 -0700 Subject: [PATCH 03/13] chore: comment out agents --- .../agents/active_cluster_agent.py | 158 +++++++++--------- .../agents/palette_profile_agent.py | 154 ++++++++--------- .../agents/reporter_agent.py | 104 ++++++------ .../agents/tagging_agent.py | 136 +++++++-------- .../integrate-palette-mcp/helpers.py | 24 +-- ai/palette-mcp/integrate-palette-mcp/main.py | 2 +- ai/palette-mcp/integrate-palette-mcp/tools.py | 102 ++++------- 7 files changed, 321 insertions(+), 359 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index 89dae25..eb58841 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -16,88 +16,88 @@ "Return factual results only." ) -async def initialize_active_cluster_agent( - model: str, - debug_level: str, - default_env_file: str, - default_kubeconfig_dir: str, - default_mcp_image: str, -) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_active_cluster_agent( +# model: str, +# debug_level: str, +# default_env_file: str, +# default_kubeconfig_dir: str, +# default_mcp_image: str, +# ) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient +# try: +# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") +# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient +# except (ImportError, AttributeError): +# mcp_module = importlib.import_module("langchain_mcp_adapters") +# MultiServerMCPClient = mcp_module.MultiServerMCPClient - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=default_env_file, - default_kubeconfig_dir=default_kubeconfig_dir, - default_mcp_image=default_mcp_image, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() +# mcp_client = MultiServerMCPClient( +# build_palette_server_config( +# default_env_file=default_env_file, +# default_kubeconfig_dir=default_kubeconfig_dir, +# default_mcp_image=default_mcp_image, +# ) +# ) +# hide_mcp_output = debug_level != "verbose" +# with suppress_console_output(hide_mcp_output): +# mcp_tools = await mcp_client.get_tools() - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def run_active_cluster_agent( - agent: Any, - pack_name: str, - matched_profiles_output: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - active_cluster_prompt = ( - f"Given this profile discovery result for pack '{pack_name}':\n" - f"{matched_profiles_output}\n\n" - "Required process:\n" - "1) Extract matched profile UIDs from the input JSON.\n" - "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" - "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" - "4) Match clusters using explicit profile UID fields only.\n" - "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" - "Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - ' "target_profile_uids": ["", ""],\n' - ' "total_active_clusters_scanned": ,\n' - ' "active_clusters_using_matched_profiles": [\n' - " {\n" - ' "uid": "",\n' - ' "name": "",\n' - ' "cluster_profile_uid": "",\n' - ' "cluster_profile_name": "",\n' - ' "evidence_field_path": "",\n' - ' "evidence": ""\n' - " }\n" - " ],\n" - ' "checked_active_cluster_uids": ["", ""],\n' - ' "notes": ""\n' - "}\n" - ) - run_config = { - "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": active_cluster_prompt}]}, - config=run_config, - ) - return extract_text_response(result) +# async def run_active_cluster_agent( +# agent: Any, +# pack_name: str, +# matched_profiles_output: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# active_cluster_prompt = ( +# f"Given this profile discovery result for pack '{pack_name}':\n" +# f"{matched_profiles_output}\n\n" +# "Required process:\n" +# "1) Extract matched profile UIDs from the input JSON.\n" +# "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" +# "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" +# "4) Match clusters using explicit profile UID fields only.\n" +# "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" +# "Return JSON with this shape:\n" +# "{\n" +# ' "pack_name": "",\n' +# ' "target_profile_uids": ["", ""],\n' +# ' "total_active_clusters_scanned": ,\n' +# ' "active_clusters_using_matched_profiles": [\n' +# " {\n" +# ' "uid": "",\n' +# ' "name": "",\n' +# ' "cluster_profile_uid": "",\n' +# ' "cluster_profile_name": "",\n' +# ' "evidence_field_path": "",\n' +# ' "evidence": ""\n' +# " }\n" +# " ],\n" +# ' "checked_active_cluster_uids": ["", ""],\n' +# ' "notes": ""\n' +# "}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": active_cluster_prompt}]}, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index ccee0eb..441ce5c 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -22,86 +22,86 @@ "Return factual results only." ) -async def initialize_profile_finder_agent( - model: str, - debug_level: str, - default_env_file: str, - default_kubeconfig_dir: str, - default_mcp_image: str, -) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_profile_finder_agent( +# model: str, +# debug_level: str, +# default_env_file: str, +# default_kubeconfig_dir: str, +# default_mcp_image: str, +# ) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient +# try: +# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") +# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient +# except (ImportError, AttributeError): +# mcp_module = importlib.import_module("langchain_mcp_adapters") +# MultiServerMCPClient = mcp_module.MultiServerMCPClient - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=default_env_file, - default_kubeconfig_dir=default_kubeconfig_dir, - default_mcp_image=default_mcp_image, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() +# mcp_client = MultiServerMCPClient( +# build_palette_server_config( +# default_env_file=default_env_file, +# default_kubeconfig_dir=default_kubeconfig_dir, +# default_mcp_image=default_mcp_image, +# ) +# ) +# hide_mcp_output = debug_level != "verbose" +# with suppress_console_output(hide_mcp_output): +# mcp_tools = await mcp_client.get_tools() - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def run_profile_finder_agent( - agent: Any, - pack_name: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - profile_finder_prompt = ( - "Find all cluster profiles in Palette that use the pack named " - f"'{pack_name}'. Use Palette MCP tools only.\n\n" - "Required process:\n" - "1) Call gather_or_delete_clusterprofiles with action='list'.\n" - "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" - "3) Match pack name case-insensitively.\n" - "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" - "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" - "Important:\n" - "- Return only profile-level results. Do not query clusters in this agent.\n\n" - "Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - ' "total_profiles_scanned": ,\n' - ' "matched_profiles": [\n' - " {\n" - ' "uid": "",\n' - ' "name": "",\n' - ' "scope": "",\n' - ' "pack_references": ["", ""],\n' - ' "evidence": ""\n' - " }\n" - " ],\n" - ' "notes": ""\n' - "}\n" - ) - run_config = { - "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": profile_finder_prompt}]}, - config=run_config, - ) - return extract_text_response(result) +# async def run_profile_finder_agent( +# agent: Any, +# pack_name: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# profile_finder_prompt = ( +# "Find all cluster profiles in Palette that use the pack named " +# f"'{pack_name}'. Use Palette MCP tools only.\n\n" +# "Required process:\n" +# "1) Call gather_or_delete_clusterprofiles with action='list'.\n" +# "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" +# "3) Match pack name case-insensitively.\n" +# "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" +# "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" +# "Important:\n" +# "- Return only profile-level results. Do not query clusters in this agent.\n\n" +# "Return JSON with this shape:\n" +# "{\n" +# ' "pack_name": "",\n' +# ' "total_profiles_scanned": ,\n' +# ' "matched_profiles": [\n' +# " {\n" +# ' "uid": "",\n' +# ' "name": "",\n' +# ' "scope": "",\n' +# ' "pack_references": ["", ""],\n' +# ' "evidence": ""\n' +# " }\n" +# " ],\n" +# ' "notes": ""\n' +# "}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": profile_finder_prompt}]}, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index 02d348a..56f093d 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -15,59 +15,59 @@ "Do not invent data. If discovery data is uncertain, call that out clearly." ) -async def initialize_reporter_agent(model: str) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_reporter_agent(model: str) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=[], - system_prompt=REPORTER_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=[], +# system_prompt=REPORTER_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def run_reporter_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - tagging_output: str, - run_id: str, -) -> str: - reporter_prompt = ( - f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" - "Use these outputs as the source of truth:\n\n" - "Profile discovery output:\n" - f"{profile_discovery_output}\n\n" - "Active cluster mapping output:\n" - f"{active_cluster_output}\n\n" - "Tagging output:\n" - f"{tagging_output}\n\n" - "Output format:\n" - "1) Summary (1-2 sentences)\n" - "2) Matching cluster profiles (bullet list with uid, name, and evidence)\n" - "3) Active clusters using the matched cluster profiles (bullet list with uid, name, cluster profile uid, and cluster profile name)\n" - "4) Tagging results for clusters and cluster profiles (what was tagged, success/failure)\n" - " Include a separate list of skipped cluster profiles with reason (for example, scope=system).\n" - "5) Notes and caveats\n" - "If active cluster data appears incomplete or unchecked, explicitly say the result may be incomplete.\n" - "If there are no matching cluster profiles, return 'No matching cluster profiles found' in the summary and omit the rest of the output." - ) - run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} - result = await agent.ainvoke( - { - "messages": [ - { - "role": "user", - "content": reporter_prompt, - } - ] - }, - config=run_config, - ) - return extract_text_response(result) +# async def run_reporter_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# tagging_output: str, +# run_id: str, +# ) -> str: +# reporter_prompt = ( +# f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" +# "Use these outputs as the source of truth:\n\n" +# "Profile discovery output:\n" +# f"{profile_discovery_output}\n\n" +# "Active cluster mapping output:\n" +# f"{active_cluster_output}\n\n" +# "Tagging output:\n" +# f"{tagging_output}\n\n" +# "Output format:\n" +# "1) Summary (1-2 sentences)\n" +# "2) Matching cluster profiles (bullet list with uid, name, and evidence)\n" +# "3) Active clusters using the matched cluster profiles (bullet list with uid, name, cluster profile uid, and cluster profile name)\n" +# "4) Tagging results for clusters and cluster profiles (what was tagged, success/failure)\n" +# " Include a separate list of skipped cluster profiles with reason (for example, scope=system).\n" +# "5) Notes and caveats\n" +# "If active cluster data appears incomplete or unchecked, explicitly say the result may be incomplete.\n" +# "If there are no matching cluster profiles, return 'No matching cluster profiles found' in the summary and omit the rest of the output." +# ) +# run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} +# result = await agent.ainvoke( +# { +# "messages": [ +# { +# "role": "user", +# "content": reporter_prompt, +# } +# ] +# }, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index 7ec6e12..129d332 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -19,75 +19,75 @@ ) -async def initialize_tagging_agent(model: str) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_tagging_agent(model: str) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=[tag_cluster_for_review, tag_cluster_profile_for_review], - system_prompt=TAGGING_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=[tag_cluster_for_review, tag_cluster_profile_for_review], +# system_prompt=TAGGING_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def run_tagging_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - run_id: str, -) -> str: - tagging_prompt = ( - f"Given this profile discovery output for pack '{pack_name}':\n" - f"{profile_discovery_output}\n\n" - f"Given this active cluster mapping output for pack '{pack_name}':\n" - f"{active_cluster_output}\n\n" - "Task:\n" - "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" - "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" - "3) For each cluster UID, call tag_cluster_for_review once.\n" - "4) For each cluster profile UID, call tag_cluster_profile_for_review once only if scope is not 'system'.\n" - "5) For scope='system' profiles, skip tagging and record skip reason.\n" - "5) Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - ' "requested_tags": ["nginx:present", "review:required"],\n' - ' "clusters_attempted": ["", ""],\n' - ' "cluster_profiles_attempted": ["", ""],\n' - ' "cluster_results": [\n' - " {\n" - ' "cluster_uid": "",\n' - ' "tool_call_status": "",\n' - ' "tool_output": ""\n' - " }\n" - " ],\n" - ' "cluster_profile_results": [\n' - " {\n" - ' "cluster_profile_uid": "",\n' - ' "scope": "",\n' - ' "tool_call_status": "",\n' - ' "tool_output": ""\n' - " }\n" - " ],\n" - ' "cluster_profile_skipped": [\n' - " {\n" - ' "cluster_profile_uid": "",\n' - ' "scope": "system",\n' - ' "reason": "scope system is not taggable"\n' - " }\n" - " ],\n" - ' "notes": ""\n' - "}\n" - "If there are no resources to tag, return empty arrays and explain in notes." - ) - run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": tagging_prompt}]}, - config=run_config, - ) - return extract_text_response(result) +# async def run_tagging_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# run_id: str, +# ) -> str: +# tagging_prompt = ( +# f"Given this profile discovery output for pack '{pack_name}':\n" +# f"{profile_discovery_output}\n\n" +# f"Given this active cluster mapping output for pack '{pack_name}':\n" +# f"{active_cluster_output}\n\n" +# "Task:\n" +# "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" +# "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" +# "3) For each cluster UID, call tag_cluster_for_review once.\n" +# "4) For each cluster profile UID, call tag_cluster_profile_for_review once only if scope is not 'system'.\n" +# "5) For scope='system' profiles, skip tagging and record skip reason.\n" +# "5) Return JSON with this shape:\n" +# "{\n" +# ' "pack_name": "",\n' +# ' "requested_tags": ["nginx:present", "review:required"],\n' +# ' "clusters_attempted": ["", ""],\n' +# ' "cluster_profiles_attempted": ["", ""],\n' +# ' "cluster_results": [\n' +# " {\n" +# ' "cluster_uid": "",\n' +# ' "tool_call_status": "",\n' +# ' "tool_output": ""\n' +# " }\n" +# " ],\n" +# ' "cluster_profile_results": [\n' +# " {\n" +# ' "cluster_profile_uid": "",\n' +# ' "scope": "",\n' +# ' "tool_call_status": "",\n' +# ' "tool_output": ""\n' +# " }\n" +# " ],\n" +# ' "cluster_profile_skipped": [\n' +# " {\n" +# ' "cluster_profile_uid": "",\n' +# ' "scope": "system",\n' +# ' "reason": "scope system is not taggable"\n' +# " }\n" +# " ],\n" +# ' "notes": ""\n' +# "}\n" +# "If there are no resources to tag, return empty arrays and explain in notes." +# ) +# run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": tagging_prompt}]}, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py index 4752d00..5212fc7 100644 --- a/ai/palette-mcp/integrate-palette-mcp/helpers.py +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -16,6 +16,15 @@ VALID_DEBUG_LEVELS = {"warn", "info", "verbose"} +def resolve_container_runtime() -> str: + """Return the preferred local container runtime command.""" + if shutil.which("docker") is not None: + return "docker" + if shutil.which("podman") is not None: + return "podman" + raise RuntimeError("Docker or Podman is not available in PATH.") + + def get_debug_level() -> str: debug_value = os.getenv("DEBUG") or os.getenv("debug") or "warn" debug_level = debug_value.strip().lower() @@ -52,16 +61,8 @@ def suppress_console_output(enabled: bool) -> Any: devnull.close() -def ensure_local_prerequisites( - require_kubectl: bool = True, - require_curl: bool = False, -) -> None: - if shutil.which("docker") is None: - raise RuntimeError("Docker is not available in PATH.") - if require_kubectl and shutil.which("kubectl") is None: - raise RuntimeError("kubectl is not available in PATH.") - if require_curl and shutil.which("curl") is None: - raise RuntimeError("curl is not available in PATH.") +def ensure_local_prerequisites() -> None: + resolve_container_runtime() if not os.getenv("OPENAI_API_KEY"): raise RuntimeError("OPENAI_API_KEY is not set.") @@ -71,6 +72,7 @@ def build_palette_server_config( default_kubeconfig_dir: str, default_mcp_image: str, ) -> dict[str, dict[str, Any]]: + container_runtime = resolve_container_runtime() env_file = os.getenv("PALETTE_MCP_ENV_FILE", default_env_file) kubeconfig_dir = os.getenv("PALETTE_MCP_KUBECONFIG_DIR", default_kubeconfig_dir) mcp_image = os.getenv("PALETTE_MCP_IMAGE", default_mcp_image) @@ -78,7 +80,7 @@ def build_palette_server_config( return { "palette": { "transport": "stdio", - "command": "docker", + "command": container_runtime, "args": [ "run", "--rm", diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index 7eb3076..b101295 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -71,7 +71,7 @@ async def main_async() -> None: args = parse_args() run_id = uuid.uuid4().hex[:8] debug_level = get_debug_level() - ensure_local_prerequisites(require_kubectl=False, require_curl=True) + ensure_local_prerequisites() if is_debug_enabled(debug_level, "info"): print(f"Debug level: {debug_level}") diff --git a/ai/palette-mcp/integrate-palette-mcp/tools.py b/ai/palette-mcp/integrate-palette-mcp/tools.py index e962272..79faa9f 100644 --- a/ai/palette-mcp/integrate-palette-mcp/tools.py +++ b/ai/palette-mcp/integrate-palette-mcp/tools.py @@ -7,45 +7,13 @@ import os import json -import re -import shlex -import subprocess from typing import Any +import urllib.error +import urllib.request from langchain.tools import tool -@tool -def kubectl(command: str) -> str: - """Run kubectl locally and return stdout, stderr, and return code.""" - if not command.strip(): - return "STDOUT:\n\nSTDERR:\nMissing kubectl command.\nRC: 2" - - try: - args = shlex.split(command) - except ValueError as exc: - return f"STDOUT:\n\nSTDERR:\nInvalid command syntax: {exc}\nRC: 2" - - try: - result = subprocess.run( - ["kubectl", *args], - capture_output=True, - text=True, - timeout=30, - check=False, - ) - except subprocess.TimeoutExpired: - return "STDOUT:\n\nSTDERR:\nkubectl command timed out after 30s.\nRC: 124" - except OSError as exc: - return f"STDOUT:\n\nSTDERR:\nFailed to execute kubectl: {exc}\nRC: 127" - - return ( - f"STDOUT:\n{result.stdout.rstrip()}\n\n" - f"STDERR:\n{result.stderr.rstrip()}\n" - f"RC: {result.returncode}" - ) - - def _read_env_file(path: str) -> dict[str, str]: values: dict[str, str] = {} if not os.path.isfile(path): @@ -127,7 +95,7 @@ def _resolve_palette_credentials() -> tuple[str, str, str]: @tool def tag_cluster_for_review(cluster_uid: str) -> str: - """Tag a Palette cluster with nginx/review labels via curl.""" + """Tag a Palette cluster with nginx/review labels via HTTP PATCH.""" if not cluster_uid.strip(): return "STDOUT:\n\nSTDERR:\nMissing cluster UID.\nRC: 2" @@ -139,7 +107,7 @@ def tag_cluster_for_review(cluster_uid: str) -> str: @tool def tag_cluster_profile_for_review(cluster_profile_uid: str) -> str: - """Tag a Palette cluster profile with nginx/review labels via curl.""" + """Tag a Palette cluster profile with nginx/review labels via HTTP PATCH.""" if not cluster_profile_uid.strip(): return "STDOUT:\n\nSTDERR:\nMissing cluster profile UID.\nRC: 2" @@ -177,48 +145,40 @@ def _patch_palette_metadata(resource_path: str, missing_identifier_error: str) - } } url = f"{host.rstrip('/')}{resource_path}" - command = [ - "curl", - "--location", - "--silent", - "--show-error", - "--request", - "PATCH", - url, - "--header", - f"ProjectUid: {project_uid}", - "--header", - "Content-Type: application/json", - "--header", - f"apiKey: {api_key}", - "--data", - json.dumps(payload), - "--write-out", - "\nHTTP_STATUS:%{http_code}", - ] + request = urllib.request.Request( + url=url, + data=json.dumps(payload).encode("utf-8"), + headers={ + "ProjectUid": project_uid, + "Content-Type": "application/json", + "apiKey": api_key, + }, + method="PATCH", + ) try: - result = subprocess.run( - command, - capture_output=True, - text=True, - timeout=30, - check=False, - ) - except subprocess.TimeoutExpired: + with urllib.request.urlopen(request, timeout=30) as response: + http_status = response.getcode() + body_output = response.read().decode("utf-8", errors="replace").rstrip() + stderr_output = "" + rc = 0 + except TimeoutError: return "STDOUT:\n\nSTDERR:\nTagging request timed out after 30s.\nRC: 124" + except urllib.error.HTTPError as exc: + http_status = exc.code + body_output = exc.read().decode("utf-8", errors="replace").rstrip() + stderr_output = str(exc) + rc = 0 + except urllib.error.URLError as exc: + return f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc.reason}\nRC: 127" except OSError as exc: - return f"STDOUT:\n\nSTDERR:\nFailed to execute curl: {exc}\nRC: 127" + return f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc}\nRC: 127" - stdout_text = result.stdout.rstrip() - status_match = re.search(r"HTTP_STATUS:(\d{3})\s*$", stdout_text) - http_status = status_match.group(1) if status_match else "unknown" - body_output = re.sub(r"\n?HTTP_STATUS:\d{3}\s*$", "", stdout_text).rstrip() - is_success = http_status.startswith("2") + is_success = str(http_status).startswith("2") return ( f"STDOUT:\n{body_output}\n\n" - f"STDERR:\n{result.stderr.rstrip()}\n" + f"STDERR:\n{stderr_output}\n" f"SUCCESS: {str(is_success).lower()}\n" - f"RC: {result.returncode}" + f"RC: {rc}" ) From a41e5ca60079bf9df28de86445a7c6079daab76a Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Thu, 12 Mar 2026 12:03:07 -0700 Subject: [PATCH 04/13] chore: name update --- .../integrate-palette-mcp/Taskfile.yaml | 4 +- .../agents/active_cluster_agent.py | 158 +++++++------- .../agents/palette_profile_agent.py | 154 +++++++------- .../agents/reporter_agent.py | 145 ++++++++----- .../agents/tagging_agent.py | 152 +++++++------ .../integrate-palette-mcp/helpers.py | 50 +++++ ai/palette-mcp/integrate-palette-mcp/main.py | 200 ++++++++++-------- ai/palette-mcp/integrate-palette-mcp/tools.py | 40 +++- 8 files changed, 528 insertions(+), 375 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml b/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml index d0a53fc..6cb7150 100644 --- a/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml +++ b/ai/palette-mcp/integrate-palette-mcp/Taskfile.yaml @@ -4,8 +4,10 @@ dotenv: [".env", "../../../.env"] tasks: start-agent: - desc: Start the Palette MCP LangChain agent + desc: "Start the Palette MCP LangChain agent. Usage: task start-agent -- --pack [--model ]" env: DEBUG: "info" + PACK_NAME: "{{.PACK_NAME}}" + OPENAI_MODEL: "{{.OPENAI_MODEL}}" cmds: - uv run python main.py {{.CLI_ARGS}} diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index eb58841..e3802f0 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -16,88 +16,88 @@ "Return factual results only." ) -# async def initialize_active_cluster_agent( -# model: str, -# debug_level: str, -# default_env_file: str, -# default_kubeconfig_dir: str, -# default_mcp_image: str, -# ) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI +async def initialize_active_cluster_agent( + model: str, + debug_level: str, + default_env_file: str, + default_kubeconfig_dir: str, + default_mcp_image: str, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver -# try: -# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") -# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient -# except (ImportError, AttributeError): -# mcp_module = importlib.import_module("langchain_mcp_adapters") -# MultiServerMCPClient = mcp_module.MultiServerMCPClient + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient -# mcp_client = MultiServerMCPClient( -# build_palette_server_config( -# default_env_file=default_env_file, -# default_kubeconfig_dir=default_kubeconfig_dir, -# default_mcp_image=default_mcp_image, -# ) -# ) -# hide_mcp_output = debug_level != "verbose" -# with suppress_console_output(hide_mcp_output): -# mcp_tools = await mcp_client.get_tools() + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=default_env_file, + default_kubeconfig_dir=default_kubeconfig_dir, + default_mcp_image=default_mcp_image, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=mcp_tools, -# system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, -# checkpointer=InMemorySaver(), -# ) + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) -# async def run_active_cluster_agent( -# agent: Any, -# pack_name: str, -# matched_profiles_output: str, -# debug_level: str, -# run_id: str, -# ) -> str: -# hide_mcp_output = debug_level != "verbose" -# active_cluster_prompt = ( -# f"Given this profile discovery result for pack '{pack_name}':\n" -# f"{matched_profiles_output}\n\n" -# "Required process:\n" -# "1) Extract matched profile UIDs from the input JSON.\n" -# "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" -# "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" -# "4) Match clusters using explicit profile UID fields only.\n" -# "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" -# "Return JSON with this shape:\n" -# "{\n" -# ' "pack_name": "",\n' -# ' "target_profile_uids": ["", ""],\n' -# ' "total_active_clusters_scanned": ,\n' -# ' "active_clusters_using_matched_profiles": [\n' -# " {\n" -# ' "uid": "",\n' -# ' "name": "",\n' -# ' "cluster_profile_uid": "",\n' -# ' "cluster_profile_name": "",\n' -# ' "evidence_field_path": "",\n' -# ' "evidence": ""\n' -# " }\n" -# " ],\n" -# ' "checked_active_cluster_uids": ["", ""],\n' -# ' "notes": ""\n' -# "}\n" -# ) -# run_config = { -# "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} -# } -# with suppress_console_output(hide_mcp_output): -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": active_cluster_prompt}]}, -# config=run_config, -# ) -# return extract_text_response(result) +async def invoke_active_cluster_agent( + agent: Any, + pack_name: str, + matched_profiles_output: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + active_cluster_prompt = ( + f"Given this profile discovery result for pack '{pack_name}':\n" + f"{matched_profiles_output}\n\n" + "Required process:\n" + "1) Extract matched profile UIDs from the input JSON.\n" + "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" + "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" + "4) Match clusters using explicit profile UID fields only.\n" + "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" + "Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + ' "target_profile_uids": ["", ""],\n' + ' "total_active_clusters_scanned": ,\n' + ' "active_clusters_using_matched_profiles": [\n' + " {\n" + ' "uid": "",\n' + ' "name": "",\n' + ' "cluster_profile_uid": "",\n' + ' "cluster_profile_name": "",\n' + ' "evidence_field_path": "",\n' + ' "evidence": ""\n' + " }\n" + " ],\n" + ' "checked_active_cluster_uids": ["", ""],\n' + ' "notes": ""\n' + "}\n" + ) + run_config = { + "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": active_cluster_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index 441ce5c..c19153b 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -22,86 +22,86 @@ "Return factual results only." ) -# async def initialize_profile_finder_agent( -# model: str, -# debug_level: str, -# default_env_file: str, -# default_kubeconfig_dir: str, -# default_mcp_image: str, -# ) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI +async def initialize_profile_finder_agent( + model: str, + debug_level: str, + default_env_file: str, + default_kubeconfig_dir: str, + default_mcp_image: str, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver -# try: -# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") -# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient -# except (ImportError, AttributeError): -# mcp_module = importlib.import_module("langchain_mcp_adapters") -# MultiServerMCPClient = mcp_module.MultiServerMCPClient + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient -# mcp_client = MultiServerMCPClient( -# build_palette_server_config( -# default_env_file=default_env_file, -# default_kubeconfig_dir=default_kubeconfig_dir, -# default_mcp_image=default_mcp_image, -# ) -# ) -# hide_mcp_output = debug_level != "verbose" -# with suppress_console_output(hide_mcp_output): -# mcp_tools = await mcp_client.get_tools() + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=default_env_file, + default_kubeconfig_dir=default_kubeconfig_dir, + default_mcp_image=default_mcp_image, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=mcp_tools, -# system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, -# checkpointer=InMemorySaver(), -# ) + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) -# async def run_profile_finder_agent( -# agent: Any, -# pack_name: str, -# debug_level: str, -# run_id: str, -# ) -> str: -# hide_mcp_output = debug_level != "verbose" -# profile_finder_prompt = ( -# "Find all cluster profiles in Palette that use the pack named " -# f"'{pack_name}'. Use Palette MCP tools only.\n\n" -# "Required process:\n" -# "1) Call gather_or_delete_clusterprofiles with action='list'.\n" -# "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" -# "3) Match pack name case-insensitively.\n" -# "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" -# "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" -# "Important:\n" -# "- Return only profile-level results. Do not query clusters in this agent.\n\n" -# "Return JSON with this shape:\n" -# "{\n" -# ' "pack_name": "",\n' -# ' "total_profiles_scanned": ,\n' -# ' "matched_profiles": [\n' -# " {\n" -# ' "uid": "",\n' -# ' "name": "",\n' -# ' "scope": "",\n' -# ' "pack_references": ["", ""],\n' -# ' "evidence": ""\n' -# " }\n" -# " ],\n" -# ' "notes": ""\n' -# "}\n" -# ) -# run_config = { -# "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} -# } -# with suppress_console_output(hide_mcp_output): -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": profile_finder_prompt}]}, -# config=run_config, -# ) -# return extract_text_response(result) +async def invoke_profile_finder_agent( + agent: Any, + pack_name: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + profile_finder_prompt = ( + "Find all cluster profiles in Palette that use the pack named " + f"'{pack_name}'. Use Palette MCP tools only.\n\n" + "Required process:\n" + "1) Call gather_or_delete_clusterprofiles with action='list'.\n" + "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" + "3) Match pack name case-insensitively.\n" + "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" + "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" + "Important:\n" + "- Return only profile-level results. Do not query clusters in this agent.\n\n" + "Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + ' "total_profiles_scanned": ,\n' + ' "matched_profiles": [\n' + " {\n" + ' "uid": "",\n' + ' "name": "",\n' + ' "scope": "",\n' + ' "pack_references": ["", ""],\n' + ' "evidence": ""\n' + " }\n" + " ],\n" + ' "notes": ""\n' + "}\n" + ) + run_config = { + "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": profile_finder_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index 56f093d..6ead0cc 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -15,59 +15,100 @@ "Do not invent data. If discovery data is uncertain, call that out clearly." ) -# async def initialize_reporter_agent(model: str) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI +async def initialize_reporter_agent(model: str) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=[], -# system_prompt=REPORTER_SYSTEM_PROMPT, -# checkpointer=InMemorySaver(), -# ) + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=[], + system_prompt=REPORTER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) -# async def run_reporter_agent( -# agent: Any, -# pack_name: str, -# profile_discovery_output: str, -# active_cluster_output: str, -# tagging_output: str, -# run_id: str, -# ) -> str: -# reporter_prompt = ( -# f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" -# "Use these outputs as the source of truth:\n\n" -# "Profile discovery output:\n" -# f"{profile_discovery_output}\n\n" -# "Active cluster mapping output:\n" -# f"{active_cluster_output}\n\n" -# "Tagging output:\n" -# f"{tagging_output}\n\n" -# "Output format:\n" -# "1) Summary (1-2 sentences)\n" -# "2) Matching cluster profiles (bullet list with uid, name, and evidence)\n" -# "3) Active clusters using the matched cluster profiles (bullet list with uid, name, cluster profile uid, and cluster profile name)\n" -# "4) Tagging results for clusters and cluster profiles (what was tagged, success/failure)\n" -# " Include a separate list of skipped cluster profiles with reason (for example, scope=system).\n" -# "5) Notes and caveats\n" -# "If active cluster data appears incomplete or unchecked, explicitly say the result may be incomplete.\n" -# "If there are no matching cluster profiles, return 'No matching cluster profiles found' in the summary and omit the rest of the output." -# ) -# run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} -# result = await agent.ainvoke( -# { -# "messages": [ -# { -# "role": "user", -# "content": reporter_prompt, -# } -# ] -# }, -# config=run_config, -# ) -# return extract_text_response(result) +async def invoke_reporter_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + tagging_output: str, + run_id: str, +) -> str: + reporter_prompt = ( + f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" + "Use these outputs as the source of truth:\n\n" + "Profile discovery output:\n" + f"{profile_discovery_output}\n\n" + "Active cluster mapping output:\n" + f"{active_cluster_output}\n\n" + "Tagging output:\n" + f"{tagging_output}\n\n" + "Produce output that matches this exact template. " + "Do not add, remove, or rename any section. " + "Do not add extra blank lines between bullet sub-fields. " + "Replace every with the real value from the source data.\n\n" + "---\n" + "1) Summary\n\n" + "Pack `` was found in of scanned cluster profiles. " + "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " + "tagging for .\n\n" + "2) Matching cluster profiles\n\n" + "- UID: ``\n" + " - Name: ``\n" + " - Evidence: \n\n" + "(repeat for each matched profile)\n\n" + "3) Active clusters using the matched cluster profiles\n\n" + "- UID: ``\n" + " - Name: ``\n" + " - Cluster profile UID: ``\n" + " - Cluster profile name: ``\n\n" + "(repeat for each active cluster)\n\n" + "4) Tagging results for clusters and cluster profiles\n\n" + "Requested tags:\n" + "- ``\n\n" + "(repeat for each tag)\n\n" + "Clusters tagged:\n" + "- Cluster UID: ``\n" + " - Result: \n\n" + "(repeat for each cluster)\n\n" + "Cluster profiles tagged:\n" + "- Cluster profile UID: ``\n" + " - Name: ``\n" + " - Scope: ``\n" + " - Result: \n\n" + "(repeat for each tagged profile)\n\n" + "Skipped cluster profiles:\n" + "- Cluster profile UID: ``\n" + " - Name: ``\n" + " - Scope: ``\n" + " - Reason: ``\n\n" + "(repeat for each skipped profile)\n\n" + "5) Notes and caveats\n\n" + "- \n\n" + "(repeat for each note)\n" + "---\n\n" + "Rules:\n" + "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" + "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" + "- Do not invent data. Only use values present in the source outputs.\n" + "- Remove any '(repeat ...)' lines from the final output.\n" + "- Remove the '---' delimiters from the final output.\n" + ) + run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} + result = await agent.ainvoke( + { + "messages": [ + { + "role": "user", + "content": reporter_prompt, + } + ] + }, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index 129d332..749a648 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -15,79 +15,95 @@ "You are a cluster tagging specialist. " "Your only job is to apply review tags to active clusters and matched cluster profiles provided in the input. " "Never tag cluster profiles with scope='system'. " + "Never tag a cluster if does not have the pack name in the cluster profile." "Use the available tagging tools and do not invent UIDs." ) -# async def initialize_tagging_agent(model: str) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI +async def initialize_tagging_agent(model: str) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=[tag_cluster_for_review, tag_cluster_profile_for_review], -# system_prompt=TAGGING_SYSTEM_PROMPT, -# checkpointer=InMemorySaver(), -# ) + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=[tag_cluster_for_review, tag_cluster_profile_for_review], + system_prompt=TAGGING_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) -# async def run_tagging_agent( -# agent: Any, -# pack_name: str, -# profile_discovery_output: str, -# active_cluster_output: str, -# run_id: str, -# ) -> str: -# tagging_prompt = ( -# f"Given this profile discovery output for pack '{pack_name}':\n" -# f"{profile_discovery_output}\n\n" -# f"Given this active cluster mapping output for pack '{pack_name}':\n" -# f"{active_cluster_output}\n\n" -# "Task:\n" -# "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" -# "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" -# "3) For each cluster UID, call tag_cluster_for_review once.\n" -# "4) For each cluster profile UID, call tag_cluster_profile_for_review once only if scope is not 'system'.\n" -# "5) For scope='system' profiles, skip tagging and record skip reason.\n" -# "5) Return JSON with this shape:\n" -# "{\n" -# ' "pack_name": "",\n' -# ' "requested_tags": ["nginx:present", "review:required"],\n' -# ' "clusters_attempted": ["", ""],\n' -# ' "cluster_profiles_attempted": ["", ""],\n' -# ' "cluster_results": [\n' -# " {\n" -# ' "cluster_uid": "",\n' -# ' "tool_call_status": "",\n' -# ' "tool_output": ""\n' -# " }\n" -# " ],\n" -# ' "cluster_profile_results": [\n' -# " {\n" -# ' "cluster_profile_uid": "",\n' -# ' "scope": "",\n' -# ' "tool_call_status": "",\n' -# ' "tool_output": ""\n' -# " }\n" -# " ],\n" -# ' "cluster_profile_skipped": [\n' -# " {\n" -# ' "cluster_profile_uid": "",\n' -# ' "scope": "system",\n' -# ' "reason": "scope system is not taggable"\n' -# " }\n" -# " ],\n" -# ' "notes": ""\n' -# "}\n" -# "If there are no resources to tag, return empty arrays and explain in notes." -# ) -# run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": tagging_prompt}]}, -# config=run_config, -# ) -# return extract_text_response(result) +async def invoke_tagging_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + tags: list[str], + run_id: str, +) -> str: + if not tags: + return ( + '{"pack_name": "' + pack_name + '", ' + '"requested_tags": [], ' + '"clusters_attempted": [], ' + '"cluster_profiles_attempted": [], ' + '"cluster_results": [], ' + '"cluster_profile_results": [], ' + '"cluster_profile_skipped": [], ' + '"notes": "No tags provided by user. Tagging skipped."}' + ) + + tags_repr = str(tags) + tagging_prompt = ( + f"Given this profile discovery output for pack '{pack_name}':\n" + f"{profile_discovery_output}\n\n" + f"Given this active cluster mapping output for pack '{pack_name}':\n" + f"{active_cluster_output}\n\n" + f"Apply these tags: {tags_repr}\n\n" + "Task:\n" + "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" + "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" + f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags_repr}.\n" + f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags_repr}, only if scope is not 'system'.\n" + "5) For scope='system' profiles, skip tagging and record skip reason.\n" + "6) Return JSON with this shape:\n" + "{\n" + ' "pack_name": "",\n' + f' "requested_tags": {tags_repr},\n' + ' "clusters_attempted": ["", ""],\n' + ' "cluster_profiles_attempted": ["", ""],\n' + ' "cluster_results": [\n' + " {\n" + ' "cluster_uid": "",\n' + ' "tool_call_status": "",\n' + ' "tool_output": ""\n' + " }\n" + " ],\n" + ' "cluster_profile_results": [\n' + " {\n" + ' "cluster_profile_uid": "",\n' + ' "scope": "",\n' + ' "tool_call_status": "",\n' + ' "tool_output": ""\n' + " }\n" + " ],\n" + ' "cluster_profile_skipped": [\n' + " {\n" + ' "cluster_profile_uid": "",\n' + ' "scope": "system",\n' + ' "reason": "scope system is not taggable"\n' + " }\n" + " ],\n" + ' "notes": ""\n' + "}\n" + "If there are no resources to tag, return empty arrays and explain in notes." + ) + run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": tagging_prompt}]}, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py index 5212fc7..a51c8db 100644 --- a/ai/palette-mcp/integrate-palette-mcp/helpers.py +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -7,6 +7,7 @@ import asyncio import contextlib +import json import os import shutil import sys @@ -95,6 +96,55 @@ def build_palette_server_config( } +def has_matches(profile_discovery_output: str, active_cluster_output: str) -> bool: + """Return True if either agent found at least one matched profile or active cluster.""" + try: + profile_data = json.loads(profile_discovery_output) + if profile_data.get("matched_profiles"): + return True + except (json.JSONDecodeError, AttributeError): + pass + try: + cluster_data = json.loads(active_cluster_output) + if cluster_data.get("active_clusters_using_matched_profiles"): + return True + except (json.JSONDecodeError, AttributeError): + pass + return False + + +def prompt_for_tags(profile_discovery_output: str, active_cluster_output: str) -> list[str] | None: + """Prompt the user for tags if matches were found. + + Returns a list of tag strings (possibly empty if the user skips), + or None if there are no matches and tagging should be skipped entirely. + """ + if not has_matches(profile_discovery_output, active_cluster_output): + return None + + print("\nMatches found. Enter tags to apply to matched cluster profiles and active clusters.") + print("Supported formats:") + print(" key:value -> nginx:found, date:2026-03-11") + print(" single -> review") + print("Press Enter with no input to skip tagging.") + raw = input("Tags: ").strip() + + if not raw: + return [] + + tags: list[str] = [] + for token in raw.split(","): + tag = token.strip() + if not tag: + continue + if " " in tag: + print(f" Warning: skipping invalid tag (contains spaces): {tag!r}") + continue + tags.append(tag) + + return tags + + def extract_text_response(result: dict[str, Any]) -> str: """Best-effort extraction of final agent text from LangChain result payload.""" messages = result.get("messages") diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index b101295..3c5adce 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -1,7 +1,7 @@ # Copyright (c) Spectro Cloud # SPDX-License-Identifier: Apache-2.0 -"""One-shot workflow runner for Palette finder + reporter agents.""" +"""Workflow runner for Palette pack finder + active cluster finder + reporter + tagging agents.""" from __future__ import annotations @@ -15,24 +15,25 @@ ensure_local_prerequisites, get_debug_level, is_debug_enabled, + prompt_for_tags, run_with_thinking_indicator, ) from agents.active_cluster_agent import ( initialize_active_cluster_agent, - run_active_cluster_agent, + invoke_active_cluster_agent, ) from agents.palette_profile_agent import ( initialize_profile_finder_agent, - run_profile_finder_agent, + invoke_profile_finder_agent, ) -from agents.reporter_agent import initialize_reporter_agent, run_reporter_agent -from agents.tagging_agent import initialize_tagging_agent, run_tagging_agent +from agents.reporter_agent import initialize_reporter_agent, invoke_reporter_agent +from agents.tagging_agent import initialize_tagging_agent, invoke_tagging_agent DEFAULT_MODEL = "gpt-5-nano-2025-08-07" DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") DEFAULT_KUBECONFIG_DIR = os.path.expanduser("~/projects/spectro-cloud/mcp-kubeconfig") -DEFAULT_PACK_NAME = "nginx" +DEFAULT_PACK_NAME = "" def parse_args() -> argparse.Namespace: @@ -44,104 +45,125 @@ def parse_args() -> argparse.Namespace: default=os.getenv("PACK_NAME", DEFAULT_PACK_NAME), help=f"Target pack name to search in cluster profiles (default: {DEFAULT_PACK_NAME}).", ) + base_model = os.getenv("OPENAI_MODEL", DEFAULT_MODEL) parser.add_argument( "--model", - default=os.getenv("OPENAI_MODEL", DEFAULT_MODEL), - help=f"Model for the profile finder agent (default: {DEFAULT_MODEL}).", + default=base_model, + help=f"Model used for all agents unless overridden (default: {DEFAULT_MODEL}).", ) + args, _ = parser.parse_known_args() + resolved_model = args.model + parser.add_argument( "--active-cluster-model", - default=os.getenv("OPENAI_ACTIVE_CLUSTER_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), - help="Model for the active cluster finder agent (default: OPENAI_ACTIVE_CLUSTER_MODEL or OPENAI_MODEL).", + default=os.getenv("OPENAI_ACTIVE_CLUSTER_MODEL", resolved_model), + help="Model for the active cluster finder agent (default: --model).", ) parser.add_argument( "--reporter-model", - default=os.getenv("OPENAI_REPORTER_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), - help="Model for the reporter agent (default: OPENAI_REPORTER_MODEL or OPENAI_MODEL).", + default=os.getenv("OPENAI_REPORTER_MODEL", resolved_model), + help="Model for the reporter agent (default: --model).", ) parser.add_argument( "--tagging-model", - default=os.getenv("OPENAI_TAGGING_MODEL", os.getenv("OPENAI_MODEL", DEFAULT_MODEL)), - help="Model for the tagging agent (default: OPENAI_TAGGING_MODEL or OPENAI_MODEL).", + default=os.getenv("OPENAI_TAGGING_MODEL", resolved_model), + help="Model for the tagging agent (default: --model).", ) return parser.parse_args() -async def main_async() -> None: - args = parse_args() - run_id = uuid.uuid4().hex[:8] - debug_level = get_debug_level() - ensure_local_prerequisites() - - if is_debug_enabled(debug_level, "info"): - print(f"Debug level: {debug_level}") - print(f"Run ID: {run_id}") - print("Initializing profile finder agent...") - - profile_finder_agent = await initialize_profile_finder_agent( - model=args.model, - debug_level=debug_level, - default_env_file=DEFAULT_ENV_FILE, - default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, - default_mcp_image=DEFAULT_MCP_IMAGE, - ) - active_cluster_agent = await initialize_active_cluster_agent( - model=args.active_cluster_model, - debug_level=debug_level, - default_env_file=DEFAULT_ENV_FILE, - default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, - default_mcp_image=DEFAULT_MCP_IMAGE, - ) - tagging_agent = await initialize_tagging_agent(model=args.tagging_model) - reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - - if is_debug_enabled(debug_level, "info"): - print(f"Running profile discovery for pack: {args.pack}") - - profile_discovery_output = await run_with_thinking_indicator( - run_profile_finder_agent(profile_finder_agent, args.pack, debug_level, run_id) - ) - - if is_debug_enabled(debug_level, "info"): - print("Finding active clusters using matched profiles...") - - active_cluster_output = await run_with_thinking_indicator( - run_active_cluster_agent( - active_cluster_agent, - args.pack, - profile_discovery_output, - debug_level, - run_id, - ) - ) - - if is_debug_enabled(debug_level, "info"): - print("Tagging active clusters...") - - tagging_output = await run_with_thinking_indicator( - run_tagging_agent( - tagging_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - run_id, - ) - ) - - if is_debug_enabled(debug_level, "info"): - print("Formatting report...") - - final_report = await run_with_thinking_indicator( - run_reporter_agent( - reporter_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - tagging_output, - run_id, - ) - ) - print(final_report) +# async def main_async() -> None: +# args = parse_args() +# run_id = uuid.uuid4().hex[:8] +# debug_level = get_debug_level() +# ensure_local_prerequisites() + +# if not args.pack: +# print("Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", file=sys.stderr) +# sys.exit(1) + +# if is_debug_enabled(debug_level, "info"): +# print(f"Debug level: {debug_level}") +# print(f"Run ID: {run_id}") +# print("Options:") +# print(f" --pack: {args.pack!r}") +# print(f" --model: {args.model}") +# print("Initializing profile finder agent...") + +# profile_finder_agent = await initialize_profile_finder_agent( +# model=args.model, +# debug_level=debug_level, +# default_env_file=DEFAULT_ENV_FILE, +# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, +# default_mcp_image=DEFAULT_MCP_IMAGE, +# ) +# active_cluster_agent = await initialize_active_cluster_agent( +# model=args.active_cluster_model, +# debug_level=debug_level, +# default_env_file=DEFAULT_ENV_FILE, +# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, +# default_mcp_image=DEFAULT_MCP_IMAGE, +# ) +# tagging_agent = await initialize_tagging_agent(model=args.tagging_model) +# reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + +# if is_debug_enabled(debug_level, "info"): +# print(f"Running profile discovery for pack: {args.pack}") + +# profile_discovery_output = await run_with_thinking_indicator( +# invoke_profile_finder_agent(profile_finder_agent, args.pack, debug_level, run_id) +# ) + +# if is_debug_enabled(debug_level, "info"): +# print("Finding active clusters using matched profiles...") + +# active_cluster_output = await run_with_thinking_indicator( +# invoke_active_cluster_agent( +# active_cluster_agent, +# args.pack, +# profile_discovery_output, +# debug_level, +# run_id, +# ) +# ) + +# user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) + +# if user_tags is None: +# if is_debug_enabled(debug_level, "info"): +# print("No matches found. Skipping tagging.") +# tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' +# else: +# if is_debug_enabled(debug_level, "info"): +# if user_tags: +# print(f"Tagging with: {user_tags}") +# else: +# print("No tags entered. Skipping tagging.") +# tagging_output = await run_with_thinking_indicator( +# invoke_tagging_agent( +# tagging_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# user_tags, +# run_id, +# ) +# ) + +# if is_debug_enabled(debug_level, "info"): +# print("Formatting report...") + +# final_report = await run_with_thinking_indicator( +# invoke_reporter_agent( +# reporter_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# tagging_output, +# run_id, +# ) +# ) +# print(final_report) if __name__ == "__main__": try: diff --git a/ai/palette-mcp/integrate-palette-mcp/tools.py b/ai/palette-mcp/integrate-palette-mcp/tools.py index 79faa9f..46c31c1 100644 --- a/ai/palette-mcp/integrate-palette-mcp/tools.py +++ b/ai/palette-mcp/integrate-palette-mcp/tools.py @@ -93,31 +93,56 @@ def _resolve_palette_credentials() -> tuple[str, str, str]: return project_uid, api_key, host +def _build_labels(tags: list[str]) -> dict[str, str]: + """Convert a list of tag strings into a Palette labels dict. + + Supported formats: + - "key:value" -> {"key": "value"} + - "single" -> {"single": "true"} + """ + labels: dict[str, str] = {} + for tag in tags: + if ":" in tag: + key, _, value = tag.partition(":") + labels[key.strip()] = value.strip() + else: + labels[tag.strip()] = "true" + return labels + + @tool -def tag_cluster_for_review(cluster_uid: str) -> str: - """Tag a Palette cluster with nginx/review labels via HTTP PATCH.""" +def tag_cluster_for_review(cluster_uid: str, tags: list[str]) -> str: + """Tag a Palette cluster with the provided labels via HTTP PATCH. + + tags: list of strings in 'key:value' or 'single' format. + """ if not cluster_uid.strip(): return "STDOUT:\n\nSTDERR:\nMissing cluster UID.\nRC: 2" return _patch_palette_metadata( resource_path=f"/v1/spectroclusters/{cluster_uid}/metadata", missing_identifier_error="Missing cluster UID.", + tags=tags, ) @tool -def tag_cluster_profile_for_review(cluster_profile_uid: str) -> str: - """Tag a Palette cluster profile with nginx/review labels via HTTP PATCH.""" +def tag_cluster_profile_for_review(cluster_profile_uid: str, tags: list[str]) -> str: + """Tag a Palette cluster profile with the provided labels via HTTP PATCH. + + tags: list of strings in 'key:value' or 'single' format. + """ if not cluster_profile_uid.strip(): return "STDOUT:\n\nSTDERR:\nMissing cluster profile UID.\nRC: 2" return _patch_palette_metadata( resource_path=f"/v1/clusterprofiles/{cluster_profile_uid}/metadata", missing_identifier_error="Missing cluster profile UID.", + tags=tags, ) -def _patch_palette_metadata(resource_path: str, missing_identifier_error: str) -> str: +def _patch_palette_metadata(resource_path: str, missing_identifier_error: str, tags: list[str]) -> str: if not resource_path.strip(): return f"STDOUT:\n\nSTDERR:\n{missing_identifier_error}\nRC: 2" @@ -138,10 +163,7 @@ def _patch_palette_metadata(resource_path: str, missing_identifier_error: str) - payload: dict[str, Any] = { "metadata": { - "labels": { - "nginx": "present", - "review": "required", - } + "labels": _build_labels(tags), } } url = f"{host.rstrip('/')}{resource_path}" From a9f5de2edb5177f2b5341e020ccdb73cb1c4848d Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Thu, 12 Mar 2026 12:48:39 -0700 Subject: [PATCH 05/13] chore: cleanup --- .../integrate-palette-mcp/agents/__init__.py | 1 - .../agents/active_cluster_agent.py | 79 ++++--- .../agents/palette_profile_agent.py | 79 +++---- .../agents/reporter_agent.py | 5 +- .../agents/tagging_agent.py | 113 +++++----- .../integrate-palette-mcp/helpers.py | 8 +- ai/palette-mcp/integrate-palette-mcp/main.py | 208 ++++++++++-------- ai/palette-mcp/integrate-palette-mcp/tools.py | 8 +- 8 files changed, 268 insertions(+), 233 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py b/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py index fb97b30..50df7b4 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/__init__.py @@ -1,3 +1,2 @@ # Copyright (c) Spectro Cloud # SPDX-License-Identifier: Apache-2.0 - diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index e3802f0..6cb42fd 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -6,9 +6,12 @@ from __future__ import annotations import importlib +import json from typing import Any -from helpers import build_palette_server_config, extract_text_response, suppress_console_output +from pydantic import BaseModel + +from helpers import suppress_console_output ACTIVE_CLUSTER_SYSTEM_PROMPT = ( "You are a Palette active-cluster mapping specialist. " @@ -16,12 +19,28 @@ "Return factual results only." ) + +class ActiveCluster(BaseModel): + uid: str + name: str + cluster_profile_uid: str + cluster_profile_name: str + evidence_field_path: str + evidence: str + + +class ActiveClusterOutput(BaseModel): + pack_name: str + target_profile_uids: list[str] + total_active_clusters_scanned: int + active_clusters_using_matched_profiles: list[ActiveCluster] + checked_active_cluster_uids: list[str] + notes: str + + async def initialize_active_cluster_agent( model: str, - debug_level: str, - default_env_file: str, - default_kubeconfig_dir: str, - default_mcp_image: str, + mcp_tools: list, ) -> Any: from langchain.agents import create_agent from langchain_openai import ChatOpenAI @@ -29,29 +48,12 @@ async def initialize_active_cluster_agent( checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") InMemorySaver = checkpoint_module.InMemorySaver - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient - - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=default_env_file, - default_kubeconfig_dir=default_kubeconfig_dir, - default_mcp_image=default_mcp_image, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() - llm = ChatOpenAI(model=model) return create_agent( model=llm, tools=mcp_tools, system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, + response_format=ActiveClusterOutput, checkpointer=InMemorySaver(), ) @@ -64,6 +66,7 @@ async def invoke_active_cluster_agent( run_id: str, ) -> str: hide_mcp_output = debug_level != "verbose" + schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) active_cluster_prompt = ( f"Given this profile discovery result for pack '{pack_name}':\n" f"{matched_profiles_output}\n\n" @@ -73,24 +76,8 @@ async def invoke_active_cluster_agent( "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" "4) Match clusters using explicit profile UID fields only.\n" "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" - "Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - ' "target_profile_uids": ["", ""],\n' - ' "total_active_clusters_scanned": ,\n' - ' "active_clusters_using_matched_profiles": [\n' - " {\n" - ' "uid": "",\n' - ' "name": "",\n' - ' "cluster_profile_uid": "",\n' - ' "cluster_profile_name": "",\n' - ' "evidence_field_path": "",\n' - ' "evidence": ""\n' - " }\n" - " ],\n" - ' "checked_active_cluster_uids": ["", ""],\n' - ' "notes": ""\n' - "}\n" + "Return a response that conforms to this JSON schema:\n" + f"{schema}\n" ) run_config = { "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} @@ -100,4 +87,12 @@ async def invoke_active_cluster_agent( {"messages": [{"role": "user", "content": active_cluster_prompt}]}, config=run_config, ) - return extract_text_response(result) + structured = result.get("structured_response") + if isinstance(structured, ActiveClusterOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index c19153b..1e467bd 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -6,13 +6,12 @@ from __future__ import annotations import importlib -from typing import Any +import json +from typing import Any, Literal -from helpers import ( - build_palette_server_config, - extract_text_response, - suppress_console_output, -) +from pydantic import BaseModel + +from helpers import suppress_console_output PROFILE_FINDER_SYSTEM_PROMPT = ( "You are a Palette profile discovery specialist. " @@ -22,12 +21,25 @@ "Return factual results only." ) + +class MatchedProfile(BaseModel): + uid: str + name: str + scope: Literal["tenant", "project", "system", "unknown"] + pack_references: list[str] + evidence: str + + +class ProfileDiscoveryOutput(BaseModel): + pack_name: str + total_profiles_scanned: int + matched_profiles: list[MatchedProfile] + notes: str + + async def initialize_profile_finder_agent( model: str, - debug_level: str, - default_env_file: str, - default_kubeconfig_dir: str, - default_mcp_image: str, + mcp_tools: list, ) -> Any: from langchain.agents import create_agent from langchain_openai import ChatOpenAI @@ -35,29 +47,12 @@ async def initialize_profile_finder_agent( checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") InMemorySaver = checkpoint_module.InMemorySaver - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient - - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=default_env_file, - default_kubeconfig_dir=default_kubeconfig_dir, - default_mcp_image=default_mcp_image, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() - llm = ChatOpenAI(model=model) return create_agent( model=llm, tools=mcp_tools, system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, + response_format=ProfileDiscoveryOutput, checkpointer=InMemorySaver(), ) @@ -69,6 +64,7 @@ async def invoke_profile_finder_agent( run_id: str, ) -> str: hide_mcp_output = debug_level != "verbose" + schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) profile_finder_prompt = ( "Find all cluster profiles in Palette that use the pack named " f"'{pack_name}'. Use Palette MCP tools only.\n\n" @@ -80,21 +76,8 @@ async def invoke_profile_finder_agent( "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" "Important:\n" "- Return only profile-level results. Do not query clusters in this agent.\n\n" - "Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - ' "total_profiles_scanned": ,\n' - ' "matched_profiles": [\n' - " {\n" - ' "uid": "",\n' - ' "name": "",\n' - ' "scope": "",\n' - ' "pack_references": ["", ""],\n' - ' "evidence": ""\n' - " }\n" - " ],\n" - ' "notes": ""\n' - "}\n" + "Return a response that conforms to this JSON schema:\n" + f"{schema}\n" ) run_config = { "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} @@ -104,4 +87,12 @@ async def invoke_profile_finder_agent( {"messages": [{"role": "user", "content": profile_finder_prompt}]}, config=run_config, ) - return extract_text_response(result) + structured = result.get("structured_response") + if isinstance(structured, ProfileDiscoveryOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index 6ead0cc..52be529 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -15,6 +15,7 @@ "Do not invent data. If discovery data is uncertain, call that out clearly." ) + async def initialize_reporter_agent(model: str) -> Any: from langchain.agents import create_agent from langchain_openai import ChatOpenAI @@ -99,7 +100,9 @@ async def invoke_reporter_agent( "- Remove any '(repeat ...)' lines from the final output.\n" "- Remove the '---' delimiters from the final output.\n" ) - run_config = {"configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"}} + run_config = { + "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} + } result = await agent.ainvoke( { "messages": [ diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index 749a648..4673726 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -6,9 +6,11 @@ from __future__ import annotations import importlib -from typing import Any +import json +from typing import Any, Literal + +from pydantic import BaseModel -from helpers import extract_text_response from tools import tag_cluster_for_review, tag_cluster_profile_for_review TAGGING_SYSTEM_PROMPT = ( @@ -20,6 +22,36 @@ ) +class ClusterResult(BaseModel): + cluster_uid: str + tool_call_status: Literal["success", "failed"] + tool_output: str + + +class ClusterProfileResult(BaseModel): + cluster_profile_uid: str + scope: Literal["tenant", "project", "system", "unknown"] + tool_call_status: Literal["success", "failed"] + tool_output: str + + +class ClusterProfileSkipped(BaseModel): + cluster_profile_uid: str + scope: str + reason: str + + +class TaggingOutput(BaseModel): + pack_name: str + requested_tags: list[str] + clusters_attempted: list[str] + cluster_profiles_attempted: list[str] + cluster_results: list[ClusterResult] + cluster_profile_results: list[ClusterProfileResult] + cluster_profile_skipped: list[ClusterProfileSkipped] + notes: str + + async def initialize_tagging_agent(model: str) -> Any: from langchain.agents import create_agent from langchain_openai import ChatOpenAI @@ -32,6 +64,7 @@ async def initialize_tagging_agent(model: str) -> Any: model=llm, tools=[tag_cluster_for_review, tag_cluster_profile_for_review], system_prompt=TAGGING_SYSTEM_PROMPT, + response_format=TaggingOutput, checkpointer=InMemorySaver(), ) @@ -45,65 +78,47 @@ async def invoke_tagging_agent( run_id: str, ) -> str: if not tags: - return ( - '{"pack_name": "' + pack_name + '", ' - '"requested_tags": [], ' - '"clusters_attempted": [], ' - '"cluster_profiles_attempted": [], ' - '"cluster_results": [], ' - '"cluster_profile_results": [], ' - '"cluster_profile_skipped": [], ' - '"notes": "No tags provided by user. Tagging skipped."}' - ) - - tags_repr = str(tags) + return TaggingOutput( + pack_name=pack_name, + requested_tags=[], + clusters_attempted=[], + cluster_profiles_attempted=[], + cluster_results=[], + cluster_profile_results=[], + cluster_profile_skipped=[], + notes="No tags provided by user. Tagging skipped.", + ).model_dump_json() + + schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) tagging_prompt = ( f"Given this profile discovery output for pack '{pack_name}':\n" f"{profile_discovery_output}\n\n" f"Given this active cluster mapping output for pack '{pack_name}':\n" f"{active_cluster_output}\n\n" - f"Apply these tags: {tags_repr}\n\n" + f"Apply these tags: {tags}\n\n" "Task:\n" "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" - f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags_repr}.\n" - f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags_repr}, only if scope is not 'system'.\n" + f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags}.\n" + f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags}, only if scope is not 'system'.\n" "5) For scope='system' profiles, skip tagging and record skip reason.\n" - "6) Return JSON with this shape:\n" - "{\n" - ' "pack_name": "",\n' - f' "requested_tags": {tags_repr},\n' - ' "clusters_attempted": ["", ""],\n' - ' "cluster_profiles_attempted": ["", ""],\n' - ' "cluster_results": [\n' - " {\n" - ' "cluster_uid": "",\n' - ' "tool_call_status": "",\n' - ' "tool_output": ""\n' - " }\n" - " ],\n" - ' "cluster_profile_results": [\n' - " {\n" - ' "cluster_profile_uid": "",\n' - ' "scope": "",\n' - ' "tool_call_status": "",\n' - ' "tool_output": ""\n' - " }\n" - " ],\n" - ' "cluster_profile_skipped": [\n' - " {\n" - ' "cluster_profile_uid": "",\n' - ' "scope": "system",\n' - ' "reason": "scope system is not taggable"\n' - " }\n" - " ],\n" - ' "notes": ""\n' - "}\n" + "6) Return a response that conforms to this JSON schema:\n" + f"{schema}\n" "If there are no resources to tag, return empty arrays and explain in notes." ) - run_config = {"configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"}} + run_config = { + "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} + } result = await agent.ainvoke( {"messages": [{"role": "user", "content": tagging_prompt}]}, config=run_config, ) - return extract_text_response(result) + structured = result.get("structured_response") + if isinstance(structured, TaggingOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py index a51c8db..2292a56 100644 --- a/ai/palette-mcp/integrate-palette-mcp/helpers.py +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -113,7 +113,9 @@ def has_matches(profile_discovery_output: str, active_cluster_output: str) -> bo return False -def prompt_for_tags(profile_discovery_output: str, active_cluster_output: str) -> list[str] | None: +def prompt_for_tags( + profile_discovery_output: str, active_cluster_output: str +) -> list[str] | None: """Prompt the user for tags if matches were found. Returns a list of tag strings (possibly empty if the user skips), @@ -122,7 +124,9 @@ def prompt_for_tags(profile_discovery_output: str, active_cluster_output: str) - if not has_matches(profile_discovery_output, active_cluster_output): return None - print("\nMatches found. Enter tags to apply to matched cluster profiles and active clusters.") + print( + "\nMatches found. Enter tags to apply to matched cluster profiles and active clusters." + ) print("Supported formats:") print(" key:value -> nginx:found, date:2026-03-11") print(" single -> review") diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index 3c5adce..814f789 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -7,16 +7,19 @@ import argparse import asyncio +import importlib import os import sys import uuid from helpers import ( + build_palette_server_config, ensure_local_prerequisites, get_debug_level, is_debug_enabled, prompt_for_tags, run_with_thinking_indicator, + suppress_console_output, ) from agents.active_cluster_agent import ( initialize_active_cluster_agent, @@ -72,98 +75,119 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -# async def main_async() -> None: -# args = parse_args() -# run_id = uuid.uuid4().hex[:8] -# debug_level = get_debug_level() -# ensure_local_prerequisites() - -# if not args.pack: -# print("Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", file=sys.stderr) -# sys.exit(1) - -# if is_debug_enabled(debug_level, "info"): -# print(f"Debug level: {debug_level}") -# print(f"Run ID: {run_id}") -# print("Options:") -# print(f" --pack: {args.pack!r}") -# print(f" --model: {args.model}") -# print("Initializing profile finder agent...") - -# profile_finder_agent = await initialize_profile_finder_agent( -# model=args.model, -# debug_level=debug_level, -# default_env_file=DEFAULT_ENV_FILE, -# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, -# default_mcp_image=DEFAULT_MCP_IMAGE, -# ) -# active_cluster_agent = await initialize_active_cluster_agent( -# model=args.active_cluster_model, -# debug_level=debug_level, -# default_env_file=DEFAULT_ENV_FILE, -# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, -# default_mcp_image=DEFAULT_MCP_IMAGE, -# ) -# tagging_agent = await initialize_tagging_agent(model=args.tagging_model) -# reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - -# if is_debug_enabled(debug_level, "info"): -# print(f"Running profile discovery for pack: {args.pack}") - -# profile_discovery_output = await run_with_thinking_indicator( -# invoke_profile_finder_agent(profile_finder_agent, args.pack, debug_level, run_id) -# ) - -# if is_debug_enabled(debug_level, "info"): -# print("Finding active clusters using matched profiles...") - -# active_cluster_output = await run_with_thinking_indicator( -# invoke_active_cluster_agent( -# active_cluster_agent, -# args.pack, -# profile_discovery_output, -# debug_level, -# run_id, -# ) -# ) - -# user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) - -# if user_tags is None: -# if is_debug_enabled(debug_level, "info"): -# print("No matches found. Skipping tagging.") -# tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' -# else: -# if is_debug_enabled(debug_level, "info"): -# if user_tags: -# print(f"Tagging with: {user_tags}") -# else: -# print("No tags entered. Skipping tagging.") -# tagging_output = await run_with_thinking_indicator( -# invoke_tagging_agent( -# tagging_agent, -# args.pack, -# profile_discovery_output, -# active_cluster_output, -# user_tags, -# run_id, -# ) -# ) - -# if is_debug_enabled(debug_level, "info"): -# print("Formatting report...") - -# final_report = await run_with_thinking_indicator( -# invoke_reporter_agent( -# reporter_agent, -# args.pack, -# profile_discovery_output, -# active_cluster_output, -# tagging_output, -# run_id, -# ) -# ) -# print(final_report) +async def main_async() -> None: + args = parse_args() + run_id = uuid.uuid4().hex[:8] + debug_level = get_debug_level() + ensure_local_prerequisites() + + if not args.pack: + print( + "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", + file=sys.stderr, + ) + sys.exit(1) + + if is_debug_enabled(debug_level, "info"): + print(f"Debug level: {debug_level}") + print(f"Run ID: {run_id}") + print("Options:") + print(f" --pack: {args.pack!r}") + print(f" --model: {args.model}") + print("Initializing MCP client...") + + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient + + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=DEFAULT_ENV_FILE, + default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, + default_mcp_image=DEFAULT_MCP_IMAGE, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() + + if is_debug_enabled(debug_level, "info"): + print("Initializing profile finder agent...") + + profile_finder_agent = await initialize_profile_finder_agent( + model=args.model, + mcp_tools=mcp_tools, + ) + active_cluster_agent = await initialize_active_cluster_agent( + model=args.active_cluster_model, + mcp_tools=mcp_tools, + ) + tagging_agent = await initialize_tagging_agent(model=args.tagging_model) + reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + + if is_debug_enabled(debug_level, "info"): + print(f"Running profile discovery for pack: {args.pack}") + + profile_discovery_output = await run_with_thinking_indicator( + invoke_profile_finder_agent( + profile_finder_agent, args.pack, debug_level, run_id + ) + ) + + if is_debug_enabled(debug_level, "info"): + print("Finding active clusters using matched profiles...") + + active_cluster_output = await run_with_thinking_indicator( + invoke_active_cluster_agent( + active_cluster_agent, + args.pack, + profile_discovery_output, + debug_level, + run_id, + ) + ) + + user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) + + if user_tags is None: + if is_debug_enabled(debug_level, "info"): + print("No matches found. Skipping tagging.") + tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' + else: + if is_debug_enabled(debug_level, "info"): + if user_tags: + print(f"Tagging with: {user_tags}") + else: + print("No tags entered. Skipping tagging.") + tagging_output = await run_with_thinking_indicator( + invoke_tagging_agent( + tagging_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + user_tags, + run_id, + ) + ) + + if is_debug_enabled(debug_level, "info"): + print("Formatting report...") + + final_report = await run_with_thinking_indicator( + invoke_reporter_agent( + reporter_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + tagging_output, + run_id, + ) + ) + print(final_report) + if __name__ == "__main__": try: diff --git a/ai/palette-mcp/integrate-palette-mcp/tools.py b/ai/palette-mcp/integrate-palette-mcp/tools.py index 46c31c1..3daca63 100644 --- a/ai/palette-mcp/integrate-palette-mcp/tools.py +++ b/ai/palette-mcp/integrate-palette-mcp/tools.py @@ -142,7 +142,9 @@ def tag_cluster_profile_for_review(cluster_profile_uid: str, tags: list[str]) -> ) -def _patch_palette_metadata(resource_path: str, missing_identifier_error: str, tags: list[str]) -> str: +def _patch_palette_metadata( + resource_path: str, missing_identifier_error: str, tags: list[str] +) -> str: if not resource_path.strip(): return f"STDOUT:\n\nSTDERR:\n{missing_identifier_error}\nRC: 2" @@ -192,7 +194,9 @@ def _patch_palette_metadata(resource_path: str, missing_identifier_error: str, t stderr_output = str(exc) rc = 0 except urllib.error.URLError as exc: - return f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc.reason}\nRC: 127" + return ( + f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc.reason}\nRC: 127" + ) except OSError as exc: return f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc}\nRC: 127" From cdc9f75d4d81889acafdec8384b7fe1b3d2dd232 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Thu, 12 Mar 2026 13:21:06 -0700 Subject: [PATCH 06/13] chore: updates to flags --- .../integrate-palette-mcp/helpers.py | 14 ++++++----- ai/palette-mcp/integrate-palette-mcp/main.py | 24 ++++++++++++------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py index 2292a56..889a500 100644 --- a/ai/palette-mcp/integrate-palette-mcp/helpers.py +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -14,7 +14,7 @@ from collections.abc import Awaitable from typing import Any, TextIO -VALID_DEBUG_LEVELS = {"warn", "info", "verbose"} +VALID_DEBUG_LEVELS = {"warn", "info", "debug", "verbose"} def resolve_container_runtime() -> str: @@ -26,17 +26,19 @@ def resolve_container_runtime() -> str: raise RuntimeError("Docker or Podman is not available in PATH.") -def get_debug_level() -> str: - debug_value = os.getenv("DEBUG") or os.getenv("debug") or "warn" +def get_debug_level(cli_level: str | None = None) -> str: + if cli_level is not None: + return cli_level + debug_value = os.getenv("DEBUG") or os.getenv("debug") or "info" debug_level = debug_value.strip().lower() if debug_level not in VALID_DEBUG_LEVELS: - print(f"Warning: invalid DEBUG='{debug_value}'. Falling back to 'warn'.") - return "warn" + print(f"Warning: invalid DEBUG='{debug_value}'. Falling back to 'info'.") + return "info" return debug_level def is_debug_enabled(current_level: str, min_level: str) -> bool: - levels = {"warn": 1, "info": 2, "verbose": 3} + levels = {"warn": 1, "info": 2, "debug": 3, "verbose": 4} return levels[current_level] >= levels[min_level] diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index 814f789..a60f44d 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -32,7 +32,7 @@ from agents.reporter_agent import initialize_reporter_agent, invoke_reporter_agent from agents.tagging_agent import initialize_tagging_agent, invoke_tagging_agent -DEFAULT_MODEL = "gpt-5-nano-2025-08-07" +DEFAULT_MODEL = "gpt-5.4" DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") DEFAULT_KUBECONFIG_DIR = os.path.expanduser("~/projects/spectro-cloud/mcp-kubeconfig") @@ -72,13 +72,19 @@ def parse_args() -> argparse.Namespace: default=os.getenv("OPENAI_TAGGING_MODEL", resolved_model), help="Model for the tagging agent (default: --model).", ) + parser.add_argument( + "--log-level", + default=None, + choices=["warn", "info", "debug", "verbose"], + help="Set the log level (warn, info, debug, verbose). Overrides the DEBUG env var.", + ) return parser.parse_args() async def main_async() -> None: args = parse_args() run_id = uuid.uuid4().hex[:8] - debug_level = get_debug_level() + debug_level = get_debug_level(cli_level=args.log_level) ensure_local_prerequisites() if not args.pack: @@ -88,7 +94,7 @@ async def main_async() -> None: ) sys.exit(1) - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print(f"Debug level: {debug_level}") print(f"Run ID: {run_id}") print("Options:") @@ -114,7 +120,7 @@ async def main_async() -> None: with suppress_console_output(hide_mcp_output): mcp_tools = await mcp_client.get_tools() - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print("Initializing profile finder agent...") profile_finder_agent = await initialize_profile_finder_agent( @@ -128,7 +134,7 @@ async def main_async() -> None: tagging_agent = await initialize_tagging_agent(model=args.tagging_model) reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print(f"Running profile discovery for pack: {args.pack}") profile_discovery_output = await run_with_thinking_indicator( @@ -137,7 +143,7 @@ async def main_async() -> None: ) ) - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print("Finding active clusters using matched profiles...") active_cluster_output = await run_with_thinking_indicator( @@ -153,11 +159,11 @@ async def main_async() -> None: user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) if user_tags is None: - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print("No matches found. Skipping tagging.") tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' else: - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): if user_tags: print(f"Tagging with: {user_tags}") else: @@ -173,7 +179,7 @@ async def main_async() -> None: ) ) - if is_debug_enabled(debug_level, "info"): + if is_debug_enabled(debug_level, "debug"): print("Formatting report...") final_report = await run_with_thinking_indicator( From 242072f9f1a856f7e73496b6f23fe38798c47c0f Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 13 Mar 2026 07:59:10 -0700 Subject: [PATCH 07/13] chore: commented out pieces ready --- .../integrate-palette-mcp/README.md | 15 ++ .../agents/active_cluster_agent.py | 116 ++++----- .../agents/palette_profile_agent.py | 118 ++++----- .../agents/reporter_agent.py | 190 +++++++-------- .../agents/tagging_agent.py | 140 +++++------ .../integrate-palette-mcp/helpers.py | 20 +- ai/palette-mcp/integrate-palette-mcp/main.py | 226 +++++++++--------- 7 files changed, 418 insertions(+), 407 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/README.md b/ai/palette-mcp/integrate-palette-mcp/README.md index 6c5db57..528eb8a 100644 --- a/ai/palette-mcp/integrate-palette-mcp/README.md +++ b/ai/palette-mcp/integrate-palette-mcp/README.md @@ -1,3 +1,18 @@ # Integrate Palette MCP This folder contains the demo code for the Integrate Palette MCP tutorial. The user will learn how to integrate Palette MCP into a LangChain agent workflow. + +## Environment Variables + +| Variable | Description | Example | +| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | +| `OPENAI_API_KEY` | OpenAI API key. Required. | `sk-...` | +| `OPENAI_MODEL` | Default model used for all agents. Overridden per-agent by the model-specific variables below. | `gpt-4o` | +| `OPENAI_ACTIVE_CLUSTER_MODEL` | Model for the active cluster finder agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | +| `OPENAI_REPORTER_MODEL` | Model for the reporter agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | +| `OPENAI_TAGGING_MODEL` | Model for the tagging agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | +| `PACK_NAME` | Target pack name to search for in cluster profiles. Can also be set via `--pack`. | `nginx` | +| `DEBUG` | Log level. Accepted values: `warn`, `info`, `debug`, `verbose`. Defaults to `info`. Can also be set via `--log-level`. | `debug` | +| `PALETTE_MCP_ENV_FILE` | Path to the env file passed to the Palette MCP container. Defaults to `~/.palette/.env-mcp`. | `/home/user/.palette/.env-mcp` | +| `PALETTE_MCP_KUBECONFIG_DIR` | Path to a local directory to bind-mount into the container as `/tmp/kubeconfig`. Omitted if not set. | `/home/user/.kube` | +| `PALETTE_MCP_IMAGE` | Palette MCP container image to use. Defaults to `public.ecr.aws/palette-ai/palette-mcp-server:latest`. | `public.ecr.aws/palette-ai/palette-mcp-server:v1.2.0` | diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index 6cb42fd..1d39af5 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -38,61 +38,61 @@ class ActiveClusterOutput(BaseModel): notes: str -async def initialize_active_cluster_agent( - model: str, - mcp_tools: list, -) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI - - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver - - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, - response_format=ActiveClusterOutput, - checkpointer=InMemorySaver(), - ) - - -async def invoke_active_cluster_agent( - agent: Any, - pack_name: str, - matched_profiles_output: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) - active_cluster_prompt = ( - f"Given this profile discovery result for pack '{pack_name}':\n" - f"{matched_profiles_output}\n\n" - "Required process:\n" - "1) Extract matched profile UIDs from the input JSON.\n" - "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" - "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" - "4) Match clusters using explicit profile UID fields only.\n" - "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" - "Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - ) - run_config = { - "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": active_cluster_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, ActiveClusterOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) +# async def initialize_active_cluster_agent( +# model: str, +# mcp_tools: list, +# ) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI + +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver + +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, +# response_format=ActiveClusterOutput, +# checkpointer=InMemorySaver(), +# ) + + +# async def invoke_active_cluster_agent( +# agent: Any, +# pack_name: str, +# matched_profiles_output: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) +# active_cluster_prompt = ( +# f"Given this profile discovery result for pack '{pack_name}':\n" +# f"{matched_profiles_output}\n\n" +# "Required process:\n" +# "1) Extract matched profile UIDs from the input JSON.\n" +# "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" +# "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" +# "4) Match clusters using explicit profile UID fields only.\n" +# "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" +# "Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": active_cluster_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, ActiveClusterOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index 1e467bd..2f19314 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -37,62 +37,62 @@ class ProfileDiscoveryOutput(BaseModel): notes: str -async def initialize_profile_finder_agent( - model: str, - mcp_tools: list, -) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI - - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver - - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, - response_format=ProfileDiscoveryOutput, - checkpointer=InMemorySaver(), - ) - - -async def invoke_profile_finder_agent( - agent: Any, - pack_name: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) - profile_finder_prompt = ( - "Find all cluster profiles in Palette that use the pack named " - f"'{pack_name}'. Use Palette MCP tools only.\n\n" - "Required process:\n" - "1) Call gather_or_delete_clusterprofiles with action='list'.\n" - "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" - "3) Match pack name case-insensitively.\n" - "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" - "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" - "Important:\n" - "- Return only profile-level results. Do not query clusters in this agent.\n\n" - "Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - ) - run_config = { - "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": profile_finder_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, ProfileDiscoveryOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) +# async def initialize_profile_finder_agent( +# model: str, +# mcp_tools: list, +# ) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI + +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver + +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, +# response_format=ProfileDiscoveryOutput, +# checkpointer=InMemorySaver(), +# ) + + +# async def invoke_profile_finder_agent( +# agent: Any, +# pack_name: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) +# profile_finder_prompt = ( +# "Find all cluster profiles in Palette that use the pack named " +# f"'{pack_name}'. Use Palette MCP tools only.\n\n" +# "Required process:\n" +# "1) Call gather_or_delete_clusterprofiles with action='list'.\n" +# "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" +# "3) Match pack name case-insensitively.\n" +# "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" +# "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" +# "Important:\n" +# "- Return only profile-level results. Do not query clusters in this agent.\n\n" +# "Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": profile_finder_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, ProfileDiscoveryOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index 52be529..cb03940 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -16,102 +16,102 @@ ) -async def initialize_reporter_agent(model: str) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_reporter_agent(model: str) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=[], - system_prompt=REPORTER_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=[], +# system_prompt=REPORTER_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def invoke_reporter_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - tagging_output: str, - run_id: str, -) -> str: - reporter_prompt = ( - f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" - "Use these outputs as the source of truth:\n\n" - "Profile discovery output:\n" - f"{profile_discovery_output}\n\n" - "Active cluster mapping output:\n" - f"{active_cluster_output}\n\n" - "Tagging output:\n" - f"{tagging_output}\n\n" - "Produce output that matches this exact template. " - "Do not add, remove, or rename any section. " - "Do not add extra blank lines between bullet sub-fields. " - "Replace every with the real value from the source data.\n\n" - "---\n" - "1) Summary\n\n" - "Pack `` was found in of scanned cluster profiles. " - "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " - "tagging for .\n\n" - "2) Matching cluster profiles\n\n" - "- UID: ``\n" - " - Name: ``\n" - " - Evidence: \n\n" - "(repeat for each matched profile)\n\n" - "3) Active clusters using the matched cluster profiles\n\n" - "- UID: ``\n" - " - Name: ``\n" - " - Cluster profile UID: ``\n" - " - Cluster profile name: ``\n\n" - "(repeat for each active cluster)\n\n" - "4) Tagging results for clusters and cluster profiles\n\n" - "Requested tags:\n" - "- ``\n\n" - "(repeat for each tag)\n\n" - "Clusters tagged:\n" - "- Cluster UID: ``\n" - " - Result: \n\n" - "(repeat for each cluster)\n\n" - "Cluster profiles tagged:\n" - "- Cluster profile UID: ``\n" - " - Name: ``\n" - " - Scope: ``\n" - " - Result: \n\n" - "(repeat for each tagged profile)\n\n" - "Skipped cluster profiles:\n" - "- Cluster profile UID: ``\n" - " - Name: ``\n" - " - Scope: ``\n" - " - Reason: ``\n\n" - "(repeat for each skipped profile)\n\n" - "5) Notes and caveats\n\n" - "- \n\n" - "(repeat for each note)\n" - "---\n\n" - "Rules:\n" - "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" - "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" - "- Do not invent data. Only use values present in the source outputs.\n" - "- Remove any '(repeat ...)' lines from the final output.\n" - "- Remove the '---' delimiters from the final output.\n" - ) - run_config = { - "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} - } - result = await agent.ainvoke( - { - "messages": [ - { - "role": "user", - "content": reporter_prompt, - } - ] - }, - config=run_config, - ) - return extract_text_response(result) +# async def invoke_reporter_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# tagging_output: str, +# run_id: str, +# ) -> str: +# reporter_prompt = ( +# f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" +# "Use these outputs as the source of truth:\n\n" +# "Profile discovery output:\n" +# f"{profile_discovery_output}\n\n" +# "Active cluster mapping output:\n" +# f"{active_cluster_output}\n\n" +# "Tagging output:\n" +# f"{tagging_output}\n\n" +# "Produce output that matches this exact template. " +# "Do not add, remove, or rename any section. " +# "Do not add extra blank lines between bullet sub-fields. " +# "Replace every with the real value from the source data.\n\n" +# "---\n" +# "1) Summary\n\n" +# "Pack `` was found in of scanned cluster profiles. " +# "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " +# "tagging for .\n\n" +# "2) Matching cluster profiles\n\n" +# "- UID: ``\n" +# " - Name: ``\n" +# " - Evidence: \n\n" +# "(repeat for each matched profile)\n\n" +# "3) Active clusters using the matched cluster profiles\n\n" +# "- UID: ``\n" +# " - Name: ``\n" +# " - Cluster profile UID: ``\n" +# " - Cluster profile name: ``\n\n" +# "(repeat for each active cluster)\n\n" +# "4) Tagging results for clusters and cluster profiles\n\n" +# "Requested tags:\n" +# "- ``\n\n" +# "(repeat for each tag)\n\n" +# "Clusters tagged:\n" +# "- Cluster UID: ``\n" +# " - Result: \n\n" +# "(repeat for each cluster)\n\n" +# "Cluster profiles tagged:\n" +# "- Cluster profile UID: ``\n" +# " - Name: ``\n" +# " - Scope: ``\n" +# " - Result: \n\n" +# "(repeat for each tagged profile)\n\n" +# "Skipped cluster profiles:\n" +# "- Cluster profile UID: ``\n" +# " - Name: ``\n" +# " - Scope: ``\n" +# " - Reason: ``\n\n" +# "(repeat for each skipped profile)\n\n" +# "5) Notes and caveats\n\n" +# "- \n\n" +# "(repeat for each note)\n" +# "---\n\n" +# "Rules:\n" +# "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" +# "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" +# "- Do not invent data. Only use values present in the source outputs.\n" +# "- Remove any '(repeat ...)' lines from the final output.\n" +# "- Remove the '---' delimiters from the final output.\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} +# } +# result = await agent.ainvoke( +# { +# "messages": [ +# { +# "role": "user", +# "content": reporter_prompt, +# } +# ] +# }, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index 4673726..b2c60e5 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -52,73 +52,73 @@ class TaggingOutput(BaseModel): notes: str -async def initialize_tagging_agent(model: str) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI - - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver - - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=[tag_cluster_for_review, tag_cluster_profile_for_review], - system_prompt=TAGGING_SYSTEM_PROMPT, - response_format=TaggingOutput, - checkpointer=InMemorySaver(), - ) - - -async def invoke_tagging_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - tags: list[str], - run_id: str, -) -> str: - if not tags: - return TaggingOutput( - pack_name=pack_name, - requested_tags=[], - clusters_attempted=[], - cluster_profiles_attempted=[], - cluster_results=[], - cluster_profile_results=[], - cluster_profile_skipped=[], - notes="No tags provided by user. Tagging skipped.", - ).model_dump_json() - - schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) - tagging_prompt = ( - f"Given this profile discovery output for pack '{pack_name}':\n" - f"{profile_discovery_output}\n\n" - f"Given this active cluster mapping output for pack '{pack_name}':\n" - f"{active_cluster_output}\n\n" - f"Apply these tags: {tags}\n\n" - "Task:\n" - "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" - "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" - f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags}.\n" - f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags}, only if scope is not 'system'.\n" - "5) For scope='system' profiles, skip tagging and record skip reason.\n" - "6) Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - "If there are no resources to tag, return empty arrays and explain in notes." - ) - run_config = { - "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} - } - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": tagging_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, TaggingOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) +# async def initialize_tagging_agent(model: str) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI + +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver + +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=[tag_cluster_for_review, tag_cluster_profile_for_review], +# system_prompt=TAGGING_SYSTEM_PROMPT, +# response_format=TaggingOutput, +# checkpointer=InMemorySaver(), +# ) + + +# async def invoke_tagging_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# tags: list[str], +# run_id: str, +# ) -> str: +# if not tags: +# return TaggingOutput( +# pack_name=pack_name, +# requested_tags=[], +# clusters_attempted=[], +# cluster_profiles_attempted=[], +# cluster_results=[], +# cluster_profile_results=[], +# cluster_profile_skipped=[], +# notes="No tags provided by user. Tagging skipped.", +# ).model_dump_json() + +# schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) +# tagging_prompt = ( +# f"Given this profile discovery output for pack '{pack_name}':\n" +# f"{profile_discovery_output}\n\n" +# f"Given this active cluster mapping output for pack '{pack_name}':\n" +# f"{active_cluster_output}\n\n" +# f"Apply these tags: {tags}\n\n" +# "Task:\n" +# "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" +# "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" +# f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags}.\n" +# f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags}, only if scope is not 'system'.\n" +# "5) For scope='system' profiles, skip tagging and record skip reason.\n" +# "6) Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# "If there are no resources to tag, return empty arrays and explain in notes." +# ) +# run_config = { +# "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} +# } +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": tagging_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, TaggingOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) \ No newline at end of file diff --git a/ai/palette-mcp/integrate-palette-mcp/helpers.py b/ai/palette-mcp/integrate-palette-mcp/helpers.py index 889a500..cae09a2 100644 --- a/ai/palette-mcp/integrate-palette-mcp/helpers.py +++ b/ai/palette-mcp/integrate-palette-mcp/helpers.py @@ -72,28 +72,24 @@ def ensure_local_prerequisites() -> None: def build_palette_server_config( default_env_file: str, - default_kubeconfig_dir: str, + default_kubeconfig_dir: str | None, default_mcp_image: str, ) -> dict[str, dict[str, Any]]: container_runtime = resolve_container_runtime() env_file = os.getenv("PALETTE_MCP_ENV_FILE", default_env_file) - kubeconfig_dir = os.getenv("PALETTE_MCP_KUBECONFIG_DIR", default_kubeconfig_dir) + kubeconfig_dir = os.getenv("PALETTE_MCP_KUBECONFIG_DIR") or default_kubeconfig_dir mcp_image = os.getenv("PALETTE_MCP_IMAGE", default_mcp_image) + args: list[str] = ["run", "--rm", "-i"] + if kubeconfig_dir: + args += ["--mount", f"type=bind,source={kubeconfig_dir},target=/tmp/kubeconfig"] + args += ["--env-file", env_file, mcp_image] + return { "palette": { "transport": "stdio", "command": container_runtime, - "args": [ - "run", - "--rm", - "-i", - "--mount", - f"type=bind,source={kubeconfig_dir},target=/tmp/kubeconfig", - "--env-file", - env_file, - mcp_image, - ], + "args": args, } } diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index a60f44d..e9801a4 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -35,7 +35,7 @@ DEFAULT_MODEL = "gpt-5.4" DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") -DEFAULT_KUBECONFIG_DIR = os.path.expanduser("~/projects/spectro-cloud/mcp-kubeconfig") +DEFAULT_KUBECONFIG_DIR = None DEFAULT_PACK_NAME = "" @@ -81,118 +81,118 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -async def main_async() -> None: - args = parse_args() - run_id = uuid.uuid4().hex[:8] - debug_level = get_debug_level(cli_level=args.log_level) - ensure_local_prerequisites() - - if not args.pack: - print( - "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", - file=sys.stderr, - ) - sys.exit(1) - - if is_debug_enabled(debug_level, "debug"): - print(f"Debug level: {debug_level}") - print(f"Run ID: {run_id}") - print("Options:") - print(f" --pack: {args.pack!r}") - print(f" --model: {args.model}") - print("Initializing MCP client...") - - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient - - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=DEFAULT_ENV_FILE, - default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, - default_mcp_image=DEFAULT_MCP_IMAGE, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() - - if is_debug_enabled(debug_level, "debug"): - print("Initializing profile finder agent...") - - profile_finder_agent = await initialize_profile_finder_agent( - model=args.model, - mcp_tools=mcp_tools, - ) - active_cluster_agent = await initialize_active_cluster_agent( - model=args.active_cluster_model, - mcp_tools=mcp_tools, - ) - tagging_agent = await initialize_tagging_agent(model=args.tagging_model) - reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - - if is_debug_enabled(debug_level, "debug"): - print(f"Running profile discovery for pack: {args.pack}") - - profile_discovery_output = await run_with_thinking_indicator( - invoke_profile_finder_agent( - profile_finder_agent, args.pack, debug_level, run_id - ) - ) - - if is_debug_enabled(debug_level, "debug"): - print("Finding active clusters using matched profiles...") - - active_cluster_output = await run_with_thinking_indicator( - invoke_active_cluster_agent( - active_cluster_agent, - args.pack, - profile_discovery_output, - debug_level, - run_id, - ) - ) - - user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) - - if user_tags is None: - if is_debug_enabled(debug_level, "debug"): - print("No matches found. Skipping tagging.") - tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' - else: - if is_debug_enabled(debug_level, "debug"): - if user_tags: - print(f"Tagging with: {user_tags}") - else: - print("No tags entered. Skipping tagging.") - tagging_output = await run_with_thinking_indicator( - invoke_tagging_agent( - tagging_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - user_tags, - run_id, - ) - ) - - if is_debug_enabled(debug_level, "debug"): - print("Formatting report...") - - final_report = await run_with_thinking_indicator( - invoke_reporter_agent( - reporter_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - tagging_output, - run_id, - ) - ) - print(final_report) +# async def main_async() -> None: +# args = parse_args() +# run_id = uuid.uuid4().hex[:8] +# debug_level = get_debug_level(cli_level=args.log_level) +# ensure_local_prerequisites() + +# if not args.pack: +# print( +# "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", +# file=sys.stderr, +# ) +# sys.exit(1) + +# if is_debug_enabled(debug_level, "debug"): +# print(f"Debug level: {debug_level}") +# print(f"Run ID: {run_id}") +# print("Options:") +# print(f" --pack: {args.pack!r}") +# print(f" --model: {args.model}") +# print("Initializing MCP client...") + +# try: +# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") +# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient +# except (ImportError, AttributeError): +# mcp_module = importlib.import_module("langchain_mcp_adapters") +# MultiServerMCPClient = mcp_module.MultiServerMCPClient + +# mcp_client = MultiServerMCPClient( +# build_palette_server_config( +# default_env_file=DEFAULT_ENV_FILE, +# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, +# default_mcp_image=DEFAULT_MCP_IMAGE, +# ) +# ) +# hide_mcp_output = debug_level != "verbose" +# with suppress_console_output(hide_mcp_output): +# mcp_tools = await mcp_client.get_tools() + +# if is_debug_enabled(debug_level, "debug"): +# print("Initializing profile finder agent...") + +# profile_finder_agent = await initialize_profile_finder_agent( +# model=args.model, +# mcp_tools=mcp_tools, +# ) +# active_cluster_agent = await initialize_active_cluster_agent( +# model=args.active_cluster_model, +# mcp_tools=mcp_tools, +# ) +# tagging_agent = await initialize_tagging_agent(model=args.tagging_model) +# reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + +# if is_debug_enabled(debug_level, "debug"): +# print(f"Running profile discovery for pack: {args.pack}") + +# profile_discovery_output = await run_with_thinking_indicator( +# invoke_profile_finder_agent( +# profile_finder_agent, args.pack, debug_level, run_id +# ) +# ) + +# if is_debug_enabled(debug_level, "debug"): +# print("Finding active clusters using matched profiles...") + +# active_cluster_output = await run_with_thinking_indicator( +# invoke_active_cluster_agent( +# active_cluster_agent, +# args.pack, +# profile_discovery_output, +# debug_level, +# run_id, +# ) +# ) + +# user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) + +# if user_tags is None: +# if is_debug_enabled(debug_level, "debug"): +# print("No matches found. Skipping tagging.") +# tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' +# else: +# if is_debug_enabled(debug_level, "debug"): +# if user_tags: +# print(f"Tagging with: {user_tags}") +# else: +# print("No tags entered. Skipping tagging.") +# tagging_output = await run_with_thinking_indicator( +# invoke_tagging_agent( +# tagging_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# user_tags, +# run_id, +# ) +# ) + +# if is_debug_enabled(debug_level, "debug"): +# print("Formatting report...") + +# final_report = await run_with_thinking_indicator( +# invoke_reporter_agent( +# reporter_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# tagging_output, +# run_id, +# ) +# ) +# print(final_report) if __name__ == "__main__": From 6e9b557dfc449a61478950e2e1a958b563ba403f Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 13 Mar 2026 08:09:52 -0700 Subject: [PATCH 08/13] chore: updated README --- ai/palette-mcp/integrate-palette-mcp/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/README.md b/ai/palette-mcp/integrate-palette-mcp/README.md index 528eb8a..9745079 100644 --- a/ai/palette-mcp/integrate-palette-mcp/README.md +++ b/ai/palette-mcp/integrate-palette-mcp/README.md @@ -1,6 +1,14 @@ # Integrate Palette MCP -This folder contains the demo code for the Integrate Palette MCP tutorial. The user will learn how to integrate Palette MCP into a LangChain agent workflow. +This folder contains the demo code for the Integrate Palette MCP in an Agentic Workflow tutorial. The user will learn how to integrate Palette MCP into a LangChain agent workflow. + +The workflow is as follows: it identifies if a specific pack is present in your environment's cluster profiles and deployed clusters. If the pack is present, the workflow will ask you what tags you want to apply to the cluster profiles containing the pack and any active clusters using cluster +profiles containing the pack. This will allow to more readily identify the cluster profiles and active clusters that are +using the pack. + +## Get Started + +Follow the instructions in the tutorial to get started. ## Environment Variables From d4ba6c054fae880066cbc70e939f2639c51ef7aa Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 13 Mar 2026 08:17:01 -0700 Subject: [PATCH 09/13] ci: updated taskfile --- Taskfile.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Taskfile.yml b/Taskfile.yml index 32210aa..393c637 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,6 +3,8 @@ version: "3" +dotenv: [".env"] + tasks: init: desc: Install dependencies and setup the project From 704a72755e383d6345c0108c59821fb1aa0f9a95 Mon Sep 17 00:00:00 2001 From: Karl Cardenas <29551334+karl-cardenas-coding@users.noreply.github.com> Date: Wed, 18 Mar 2026 16:48:09 -0700 Subject: [PATCH 10/13] chore: update to not tag manually (#90) --- .../agents/tagging_agent.py | 21 +- ai/palette-mcp/integrate-palette-mcp/main.py | 3 +- ai/palette-mcp/integrate-palette-mcp/tools.py | 210 ------------------ 3 files changed, 14 insertions(+), 220 deletions(-) delete mode 100644 ai/palette-mcp/integrate-palette-mcp/tools.py diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index b2c60e5..3f94fe5 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -11,7 +11,7 @@ from pydantic import BaseModel -from tools import tag_cluster_for_review, tag_cluster_profile_for_review +from helpers import suppress_console_output TAGGING_SYSTEM_PROMPT = ( "You are a cluster tagging specialist. " @@ -52,7 +52,7 @@ class TaggingOutput(BaseModel): notes: str -# async def initialize_tagging_agent(model: str) -> Any: +# async def initialize_tagging_agent(model: str, mcp_tools: list) -> Any: # from langchain.agents import create_agent # from langchain_openai import ChatOpenAI @@ -62,7 +62,7 @@ class TaggingOutput(BaseModel): # llm = ChatOpenAI(model=model) # return create_agent( # model=llm, -# tools=[tag_cluster_for_review, tag_cluster_profile_for_review], +# tools=mcp_tools, # system_prompt=TAGGING_SYSTEM_PROMPT, # response_format=TaggingOutput, # checkpointer=InMemorySaver(), @@ -75,6 +75,7 @@ class TaggingOutput(BaseModel): # profile_discovery_output: str, # active_cluster_output: str, # tags: list[str], +# debug_level: str, # run_id: str, # ) -> str: # if not tags: @@ -99,20 +100,22 @@ class TaggingOutput(BaseModel): # "Task:\n" # "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" # "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" -# f"3) For each cluster UID, call tag_cluster_for_review with cluster_uid= and tags={tags}.\n" -# f"4) For each cluster profile UID, call tag_cluster_profile_for_review with cluster_profile_uid= and tags={tags}, only if scope is not 'system'.\n" +# f"3) For each cluster UID, call manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" +# f"4) For each cluster profile UID, call manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" # "5) For scope='system' profiles, skip tagging and record skip reason.\n" # "6) Return a response that conforms to this JSON schema:\n" # f"{schema}\n" # "If there are no resources to tag, return empty arrays and explain in notes." # ) +# hide_mcp_output = debug_level != "verbose" # run_config = { # "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} # } -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": tagging_prompt}]}, -# config=run_config, -# ) +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": tagging_prompt}]}, +# config=run_config, +# ) # structured = result.get("structured_response") # if isinstance(structured, TaggingOutput): # return structured.model_dump_json() diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index e9801a4..f902ea7 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -131,7 +131,7 @@ def parse_args() -> argparse.Namespace: # model=args.active_cluster_model, # mcp_tools=mcp_tools, # ) -# tagging_agent = await initialize_tagging_agent(model=args.tagging_model) +# tagging_agent = await initialize_tagging_agent(model=args.tagging_model, mcp_tools=mcp_tools) # reporter_agent = await initialize_reporter_agent(model=args.reporter_model) # if is_debug_enabled(debug_level, "debug"): @@ -175,6 +175,7 @@ def parse_args() -> argparse.Namespace: # profile_discovery_output, # active_cluster_output, # user_tags, +# debug_level, # run_id, # ) # ) diff --git a/ai/palette-mcp/integrate-palette-mcp/tools.py b/ai/palette-mcp/integrate-palette-mcp/tools.py deleted file mode 100644 index 3daca63..0000000 --- a/ai/palette-mcp/integrate-palette-mcp/tools.py +++ /dev/null @@ -1,210 +0,0 @@ -# Copyright (c) Spectro Cloud -# SPDX-License-Identifier: Apache-2.0 - -"""Local LangChain tools used by the tutorial agent.""" - -from __future__ import annotations - -import os -import json -from typing import Any -import urllib.error -import urllib.request - -from langchain.tools import tool - - -def _read_env_file(path: str) -> dict[str, str]: - values: dict[str, str] = {} - if not os.path.isfile(path): - return values - - with open(path, encoding="utf-8") as env_file: - for raw_line in env_file: - line = raw_line.strip() - if not line or line.startswith("#"): - continue - if line.startswith("export "): - line = line[len("export ") :].strip() - if "=" not in line: - continue - key, value = line.split("=", 1) - key = key.strip() - value = value.strip().strip('"').strip("'") - if key: - values[key] = value - - return values - - -def _resolve_palette_env_file_path() -> str: - return os.path.expanduser("~/.palette/.env-mcp") - - -def _normalize_key(key: str) -> str: - normalized = key.strip().upper() - return "".join(ch for ch in normalized if ch.isalnum()) - - -def _first_non_empty(source: dict[str, str], keys: list[str]) -> str: - normalized_source = {_normalize_key(k): v for k, v in source.items()} - for key in keys: - value = normalized_source.get(_normalize_key(key), "") - if value.strip(): - return value.strip() - return "" - - -def _resolve_palette_credentials() -> tuple[str, str, str]: - project_uid_keys = [ - "SPECTROCLOUD_DEFAULT_PROJECT_ID", - ] - api_key_keys = [ - "SPECTROCLOUD_APIKEY", - ] - host_keys = [ - "SPECTROCLOUD_HOST", - ] - - project_uid = _first_non_empty(os.environ, project_uid_keys) - api_key = _first_non_empty(os.environ, api_key_keys) - host = _first_non_empty(os.environ, host_keys) - if not host: - host = "https://api.spectrocloud.com" - if project_uid and api_key: - if not host.startswith(("http://", "https://")): - host = f"https://{host}" - return project_uid, api_key, host - - env_file_path = _resolve_palette_env_file_path() - env_values = _read_env_file(env_file_path) - if not project_uid: - project_uid = _first_non_empty(env_values, project_uid_keys) - if not api_key: - api_key = _first_non_empty(env_values, api_key_keys) - if host == "https://api.spectrocloud.com": - parsed_host = _first_non_empty(env_values, host_keys) - if parsed_host: - host = parsed_host - - if not host.startswith(("http://", "https://")): - host = f"https://{host}" - - return project_uid, api_key, host - - -def _build_labels(tags: list[str]) -> dict[str, str]: - """Convert a list of tag strings into a Palette labels dict. - - Supported formats: - - "key:value" -> {"key": "value"} - - "single" -> {"single": "true"} - """ - labels: dict[str, str] = {} - for tag in tags: - if ":" in tag: - key, _, value = tag.partition(":") - labels[key.strip()] = value.strip() - else: - labels[tag.strip()] = "true" - return labels - - -@tool -def tag_cluster_for_review(cluster_uid: str, tags: list[str]) -> str: - """Tag a Palette cluster with the provided labels via HTTP PATCH. - - tags: list of strings in 'key:value' or 'single' format. - """ - if not cluster_uid.strip(): - return "STDOUT:\n\nSTDERR:\nMissing cluster UID.\nRC: 2" - - return _patch_palette_metadata( - resource_path=f"/v1/spectroclusters/{cluster_uid}/metadata", - missing_identifier_error="Missing cluster UID.", - tags=tags, - ) - - -@tool -def tag_cluster_profile_for_review(cluster_profile_uid: str, tags: list[str]) -> str: - """Tag a Palette cluster profile with the provided labels via HTTP PATCH. - - tags: list of strings in 'key:value' or 'single' format. - """ - if not cluster_profile_uid.strip(): - return "STDOUT:\n\nSTDERR:\nMissing cluster profile UID.\nRC: 2" - - return _patch_palette_metadata( - resource_path=f"/v1/clusterprofiles/{cluster_profile_uid}/metadata", - missing_identifier_error="Missing cluster profile UID.", - tags=tags, - ) - - -def _patch_palette_metadata( - resource_path: str, missing_identifier_error: str, tags: list[str] -) -> str: - if not resource_path.strip(): - return f"STDOUT:\n\nSTDERR:\n{missing_identifier_error}\nRC: 2" - - env_file_path = _resolve_palette_env_file_path() - project_uid, api_key, host = _resolve_palette_credentials() - if not project_uid: - return ( - "STDOUT:\n\nSTDERR:\n" - "Missing project UID. Checked env and " - f"{env_file_path} for project UID keys.\nRC: 2" - ) - if not api_key: - return ( - "STDOUT:\n\nSTDERR:\n" - "Missing API key. Checked env and " - f"{env_file_path} for API key keys.\nRC: 2" - ) - - payload: dict[str, Any] = { - "metadata": { - "labels": _build_labels(tags), - } - } - url = f"{host.rstrip('/')}{resource_path}" - request = urllib.request.Request( - url=url, - data=json.dumps(payload).encode("utf-8"), - headers={ - "ProjectUid": project_uid, - "Content-Type": "application/json", - "apiKey": api_key, - }, - method="PATCH", - ) - - try: - with urllib.request.urlopen(request, timeout=30) as response: - http_status = response.getcode() - body_output = response.read().decode("utf-8", errors="replace").rstrip() - stderr_output = "" - rc = 0 - except TimeoutError: - return "STDOUT:\n\nSTDERR:\nTagging request timed out after 30s.\nRC: 124" - except urllib.error.HTTPError as exc: - http_status = exc.code - body_output = exc.read().decode("utf-8", errors="replace").rstrip() - stderr_output = str(exc) - rc = 0 - except urllib.error.URLError as exc: - return ( - f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc.reason}\nRC: 127" - ) - except OSError as exc: - return f"STDOUT:\n\nSTDERR:\nFailed to execute HTTP request: {exc}\nRC: 127" - - is_success = str(http_status).startswith("2") - - return ( - f"STDOUT:\n{body_output}\n\n" - f"STDERR:\n{stderr_output}\n" - f"SUCCESS: {str(is_success).lower()}\n" - f"RC: {rc}" - ) From 21ff2a8ee5ff5cda4ebb87a68aef717f9c46681b Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 27 Mar 2026 08:22:42 -0700 Subject: [PATCH 11/13] chore: update function name --- ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index 3f94fe5..f1dfb46 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -100,8 +100,8 @@ class TaggingOutput(BaseModel): # "Task:\n" # "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" # "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" -# f"3) For each cluster UID, call manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" -# f"4) For each cluster profile UID, call manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" +# f"3) For each cluster UID, call search_and_manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" +# f"4) For each cluster profile UID, call search_and_manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" # "5) For scope='system' profiles, skip tagging and record skip reason.\n" # "6) Return a response that conforms to this JSON schema:\n" # f"{schema}\n" From c0d51e13661a90eb877c58bbe5348a0ea1e034d9 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 27 Mar 2026 14:24:22 -0700 Subject: [PATCH 12/13] chore: feedback --- .../agents/active_cluster_agent.py | 116 ++++----- .../agents/palette_profile_agent.py | 119 ++++----- .../agents/reporter_agent.py | 190 +++++++------- .../agents/tagging_agent.py | 146 +++++------ ai/palette-mcp/integrate-palette-mcp/main.py | 231 +++++++++--------- 5 files changed, 402 insertions(+), 400 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index 1d39af5..6cb42fd 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -38,61 +38,61 @@ class ActiveClusterOutput(BaseModel): notes: str -# async def initialize_active_cluster_agent( -# model: str, -# mcp_tools: list, -# ) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI - -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver - -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=mcp_tools, -# system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, -# response_format=ActiveClusterOutput, -# checkpointer=InMemorySaver(), -# ) - - -# async def invoke_active_cluster_agent( -# agent: Any, -# pack_name: str, -# matched_profiles_output: str, -# debug_level: str, -# run_id: str, -# ) -> str: -# hide_mcp_output = debug_level != "verbose" -# schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) -# active_cluster_prompt = ( -# f"Given this profile discovery result for pack '{pack_name}':\n" -# f"{matched_profiles_output}\n\n" -# "Required process:\n" -# "1) Extract matched profile UIDs from the input JSON.\n" -# "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" -# "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" -# "4) Match clusters using explicit profile UID fields only.\n" -# "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" -# "Return a response that conforms to this JSON schema:\n" -# f"{schema}\n" -# ) -# run_config = { -# "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} -# } -# with suppress_console_output(hide_mcp_output): -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": active_cluster_prompt}]}, -# config=run_config, -# ) -# structured = result.get("structured_response") -# if isinstance(structured, ActiveClusterOutput): -# return structured.model_dump_json() -# messages = result.get("messages", []) -# for message in reversed(messages): -# content = getattr(message, "content", None) -# if isinstance(content, str) and content.strip(): -# return content -# return str(result) +async def initialize_active_cluster_agent( + model: str, + mcp_tools: list, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=ACTIVE_CLUSTER_SYSTEM_PROMPT, + response_format=ActiveClusterOutput, + checkpointer=InMemorySaver(), + ) + + +async def invoke_active_cluster_agent( + agent: Any, + pack_name: str, + matched_profiles_output: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) + active_cluster_prompt = ( + f"Given this profile discovery result for pack '{pack_name}':\n" + f"{matched_profiles_output}\n\n" + "Required process:\n" + "1) Extract matched profile UIDs from the input JSON.\n" + "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" + "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" + "4) Match clusters using explicit profile UID fields only.\n" + "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" + "Return a response that conforms to this JSON schema:\n" + f"{schema}\n" + ) + run_config = { + "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": active_cluster_prompt}]}, + config=run_config, + ) + structured = result.get("structured_response") + if isinstance(structured, ActiveClusterOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index 2f19314..10ddf0b 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -37,62 +37,63 @@ class ProfileDiscoveryOutput(BaseModel): notes: str -# async def initialize_profile_finder_agent( -# model: str, -# mcp_tools: list, -# ) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI - -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver - -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=mcp_tools, -# system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, -# response_format=ProfileDiscoveryOutput, -# checkpointer=InMemorySaver(), -# ) - - -# async def invoke_profile_finder_agent( -# agent: Any, -# pack_name: str, -# debug_level: str, -# run_id: str, -# ) -> str: -# hide_mcp_output = debug_level != "verbose" -# schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) -# profile_finder_prompt = ( -# "Find all cluster profiles in Palette that use the pack named " -# f"'{pack_name}'. Use Palette MCP tools only.\n\n" -# "Required process:\n" -# "1) Call gather_or_delete_clusterprofiles with action='list'.\n" -# "2) If list output lacks pack details, call action='get' for relevant cluster profile uids.\n" -# "3) Match pack name case-insensitively.\n" -# "4) For each matched profile, include scope from metadata.annotations.scope when available.\n" -# "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" -# "Important:\n" -# "- Return only profile-level results. Do not query clusters in this agent.\n\n" -# "Return a response that conforms to this JSON schema:\n" -# f"{schema}\n" -# ) -# run_config = { -# "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} -# } -# with suppress_console_output(hide_mcp_output): -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": profile_finder_prompt}]}, -# config=run_config, -# ) -# structured = result.get("structured_response") -# if isinstance(structured, ProfileDiscoveryOutput): -# return structured.model_dump_json() -# messages = result.get("messages", []) -# for message in reversed(messages): -# content = getattr(message, "content", None) -# if isinstance(content, str) and content.strip(): -# return content -# return str(result) +async def initialize_profile_finder_agent( + model: str, + mcp_tools: list, +) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, + response_format=ProfileDiscoveryOutput, + checkpointer=InMemorySaver(), + ) + + +async def invoke_profile_finder_agent( + agent: Any, + pack_name: str, + debug_level: str, + run_id: str, +) -> str: + hide_mcp_output = debug_level != "verbose" + schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) + profile_finder_prompt = ( + "Find all cluster profiles in Palette that use the pack named " + f"'{pack_name}'. Use Palette MCP tools only.\n\n" + "Required process:\n" + "1) Call gather_or_delete_clusterprofiles with action='list', limit=50, and compact=false.\n" + "2) Review the output and check if the pack name is in the list. Match pack name case-insensitively. If the pack present, add it to the list of cluster profiles to review. If it is not, do not add it to the list.\n" + "3) For each matched profile, include scope from metadata.annotations.scope when available.\n" + "4) If there is a continue token, use it to retrieve the next page of results. Do not stop until you have gathered all cluster profiles and there is no continue token. Repeat the process until you have gathered all cluster profiles.\n" + "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" + "6) If the pack name is not in the list, return an empty list and mention in notes.\n" + "Important:\n" + "Make sure you reviewed all cluster profiles. There should be no continue token at the end of the process." + "Return a response that conforms to this JSON schema:\n" + f"{schema}\n" + ) + run_config = { + "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": profile_finder_prompt}]}, + config=run_config, + ) + structured = result.get("structured_response") + if isinstance(structured, ProfileDiscoveryOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index cb03940..52be529 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -16,102 +16,102 @@ ) -# async def initialize_reporter_agent(model: str) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI +async def initialize_reporter_agent(model: str) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=[], -# system_prompt=REPORTER_SYSTEM_PROMPT, -# checkpointer=InMemorySaver(), -# ) + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=[], + system_prompt=REPORTER_SYSTEM_PROMPT, + checkpointer=InMemorySaver(), + ) -# async def invoke_reporter_agent( -# agent: Any, -# pack_name: str, -# profile_discovery_output: str, -# active_cluster_output: str, -# tagging_output: str, -# run_id: str, -# ) -> str: -# reporter_prompt = ( -# f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" -# "Use these outputs as the source of truth:\n\n" -# "Profile discovery output:\n" -# f"{profile_discovery_output}\n\n" -# "Active cluster mapping output:\n" -# f"{active_cluster_output}\n\n" -# "Tagging output:\n" -# f"{tagging_output}\n\n" -# "Produce output that matches this exact template. " -# "Do not add, remove, or rename any section. " -# "Do not add extra blank lines between bullet sub-fields. " -# "Replace every with the real value from the source data.\n\n" -# "---\n" -# "1) Summary\n\n" -# "Pack `` was found in of scanned cluster profiles. " -# "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " -# "tagging for .\n\n" -# "2) Matching cluster profiles\n\n" -# "- UID: ``\n" -# " - Name: ``\n" -# " - Evidence: \n\n" -# "(repeat for each matched profile)\n\n" -# "3) Active clusters using the matched cluster profiles\n\n" -# "- UID: ``\n" -# " - Name: ``\n" -# " - Cluster profile UID: ``\n" -# " - Cluster profile name: ``\n\n" -# "(repeat for each active cluster)\n\n" -# "4) Tagging results for clusters and cluster profiles\n\n" -# "Requested tags:\n" -# "- ``\n\n" -# "(repeat for each tag)\n\n" -# "Clusters tagged:\n" -# "- Cluster UID: ``\n" -# " - Result: \n\n" -# "(repeat for each cluster)\n\n" -# "Cluster profiles tagged:\n" -# "- Cluster profile UID: ``\n" -# " - Name: ``\n" -# " - Scope: ``\n" -# " - Result: \n\n" -# "(repeat for each tagged profile)\n\n" -# "Skipped cluster profiles:\n" -# "- Cluster profile UID: ``\n" -# " - Name: ``\n" -# " - Scope: ``\n" -# " - Reason: ``\n\n" -# "(repeat for each skipped profile)\n\n" -# "5) Notes and caveats\n\n" -# "- \n\n" -# "(repeat for each note)\n" -# "---\n\n" -# "Rules:\n" -# "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" -# "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" -# "- Do not invent data. Only use values present in the source outputs.\n" -# "- Remove any '(repeat ...)' lines from the final output.\n" -# "- Remove the '---' delimiters from the final output.\n" -# ) -# run_config = { -# "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} -# } -# result = await agent.ainvoke( -# { -# "messages": [ -# { -# "role": "user", -# "content": reporter_prompt, -# } -# ] -# }, -# config=run_config, -# ) -# return extract_text_response(result) +async def invoke_reporter_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + tagging_output: str, + run_id: str, +) -> str: + reporter_prompt = ( + f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" + "Use these outputs as the source of truth:\n\n" + "Profile discovery output:\n" + f"{profile_discovery_output}\n\n" + "Active cluster mapping output:\n" + f"{active_cluster_output}\n\n" + "Tagging output:\n" + f"{tagging_output}\n\n" + "Produce output that matches this exact template. " + "Do not add, remove, or rename any section. " + "Do not add extra blank lines between bullet sub-fields. " + "Replace every with the real value from the source data.\n\n" + "---\n" + "1) Summary\n\n" + "Pack `` was found in of scanned cluster profiles. " + "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " + "tagging for .\n\n" + "2) Matching cluster profiles\n\n" + "- UID: ``\n" + " - Name: ``\n" + " - Evidence: \n\n" + "(repeat for each matched profile)\n\n" + "3) Active clusters using the matched cluster profiles\n\n" + "- UID: ``\n" + " - Name: ``\n" + " - Cluster profile UID: ``\n" + " - Cluster profile name: ``\n\n" + "(repeat for each active cluster)\n\n" + "4) Tagging results for clusters and cluster profiles\n\n" + "Requested tags:\n" + "- ``\n\n" + "(repeat for each tag)\n\n" + "Clusters tagged:\n" + "- Cluster UID: ``\n" + " - Result: \n\n" + "(repeat for each cluster)\n\n" + "Cluster profiles tagged:\n" + "- Cluster profile UID: ``\n" + " - Name: ``\n" + " - Scope: ``\n" + " - Result: \n\n" + "(repeat for each tagged profile)\n\n" + "Skipped cluster profiles:\n" + "- Cluster profile UID: ``\n" + " - Name: ``\n" + " - Scope: ``\n" + " - Reason: ``\n\n" + "(repeat for each skipped profile)\n\n" + "5) Notes and caveats\n\n" + "- \n\n" + "(repeat for each note)\n" + "---\n\n" + "Rules:\n" + "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" + "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" + "- Do not invent data. Only use values present in the source outputs.\n" + "- Remove any '(repeat ...)' lines from the final output.\n" + "- Remove the '---' delimiters from the final output.\n" + ) + run_config = { + "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} + } + result = await agent.ainvoke( + { + "messages": [ + { + "role": "user", + "content": reporter_prompt, + } + ] + }, + config=run_config, + ) + return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index f1dfb46..ecf2b2e 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -52,76 +52,76 @@ class TaggingOutput(BaseModel): notes: str -# async def initialize_tagging_agent(model: str, mcp_tools: list) -> Any: -# from langchain.agents import create_agent -# from langchain_openai import ChatOpenAI - -# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") -# InMemorySaver = checkpoint_module.InMemorySaver - -# llm = ChatOpenAI(model=model) -# return create_agent( -# model=llm, -# tools=mcp_tools, -# system_prompt=TAGGING_SYSTEM_PROMPT, -# response_format=TaggingOutput, -# checkpointer=InMemorySaver(), -# ) - - -# async def invoke_tagging_agent( -# agent: Any, -# pack_name: str, -# profile_discovery_output: str, -# active_cluster_output: str, -# tags: list[str], -# debug_level: str, -# run_id: str, -# ) -> str: -# if not tags: -# return TaggingOutput( -# pack_name=pack_name, -# requested_tags=[], -# clusters_attempted=[], -# cluster_profiles_attempted=[], -# cluster_results=[], -# cluster_profile_results=[], -# cluster_profile_skipped=[], -# notes="No tags provided by user. Tagging skipped.", -# ).model_dump_json() - -# schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) -# tagging_prompt = ( -# f"Given this profile discovery output for pack '{pack_name}':\n" -# f"{profile_discovery_output}\n\n" -# f"Given this active cluster mapping output for pack '{pack_name}':\n" -# f"{active_cluster_output}\n\n" -# f"Apply these tags: {tags}\n\n" -# "Task:\n" -# "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" -# "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" -# f"3) For each cluster UID, call search_and_manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" -# f"4) For each cluster profile UID, call search_and_manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" -# "5) For scope='system' profiles, skip tagging and record skip reason.\n" -# "6) Return a response that conforms to this JSON schema:\n" -# f"{schema}\n" -# "If there are no resources to tag, return empty arrays and explain in notes." -# ) -# hide_mcp_output = debug_level != "verbose" -# run_config = { -# "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} -# } -# with suppress_console_output(hide_mcp_output): -# result = await agent.ainvoke( -# {"messages": [{"role": "user", "content": tagging_prompt}]}, -# config=run_config, -# ) -# structured = result.get("structured_response") -# if isinstance(structured, TaggingOutput): -# return structured.model_dump_json() -# messages = result.get("messages", []) -# for message in reversed(messages): -# content = getattr(message, "content", None) -# if isinstance(content, str) and content.strip(): -# return content -# return str(result) \ No newline at end of file +async def initialize_tagging_agent(model: str, mcp_tools: list) -> Any: + from langchain.agents import create_agent + from langchain_openai import ChatOpenAI + + checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") + InMemorySaver = checkpoint_module.InMemorySaver + + llm = ChatOpenAI(model=model) + return create_agent( + model=llm, + tools=mcp_tools, + system_prompt=TAGGING_SYSTEM_PROMPT, + response_format=TaggingOutput, + checkpointer=InMemorySaver(), + ) + + +async def invoke_tagging_agent( + agent: Any, + pack_name: str, + profile_discovery_output: str, + active_cluster_output: str, + tags: list[str], + debug_level: str, + run_id: str, +) -> str: + if not tags: + return TaggingOutput( + pack_name=pack_name, + requested_tags=[], + clusters_attempted=[], + cluster_profiles_attempted=[], + cluster_results=[], + cluster_profile_results=[], + cluster_profile_skipped=[], + notes="No tags provided by user. Tagging skipped.", + ).model_dump_json() + + schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) + tagging_prompt = ( + f"Given this profile discovery output for pack '{pack_name}':\n" + f"{profile_discovery_output}\n\n" + f"Given this active cluster mapping output for pack '{pack_name}':\n" + f"{active_cluster_output}\n\n" + f"Apply these tags: {tags}\n\n" + "Task:\n" + "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" + "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" + f"3) For each cluster UID, call search_and_manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" + f"4) For each cluster profile UID, call search_and_manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" + "5) For scope='system' profiles, skip tagging and record skip reason.\n" + "6) Return a response that conforms to this JSON schema:\n" + f"{schema}\n" + "If there are no resources to tag, return empty arrays and explain in notes." + ) + hide_mcp_output = debug_level != "verbose" + run_config = { + "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} + } + with suppress_console_output(hide_mcp_output): + result = await agent.ainvoke( + {"messages": [{"role": "user", "content": tagging_prompt}]}, + config=run_config, + ) + structured = result.get("structured_response") + if isinstance(structured, TaggingOutput): + return structured.model_dump_json() + messages = result.get("messages", []) + for message in reversed(messages): + content = getattr(message, "content", None) + if isinstance(content, str) and content.strip(): + return content + return str(result) \ No newline at end of file diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index f902ea7..1b715cd 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -32,8 +32,9 @@ from agents.reporter_agent import initialize_reporter_agent, invoke_reporter_agent from agents.tagging_agent import initialize_tagging_agent, invoke_tagging_agent -DEFAULT_MODEL = "gpt-5.4" -DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" +DEFAULT_MODEL = "gpt-5.4-nano" +# DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:dev" +DEFAULT_MCP_IMAGE = "karl5" DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") DEFAULT_KUBECONFIG_DIR = None DEFAULT_PACK_NAME = "" @@ -81,119 +82,119 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -# async def main_async() -> None: -# args = parse_args() -# run_id = uuid.uuid4().hex[:8] -# debug_level = get_debug_level(cli_level=args.log_level) -# ensure_local_prerequisites() - -# if not args.pack: -# print( -# "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", -# file=sys.stderr, -# ) -# sys.exit(1) - -# if is_debug_enabled(debug_level, "debug"): -# print(f"Debug level: {debug_level}") -# print(f"Run ID: {run_id}") -# print("Options:") -# print(f" --pack: {args.pack!r}") -# print(f" --model: {args.model}") -# print("Initializing MCP client...") - -# try: -# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") -# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient -# except (ImportError, AttributeError): -# mcp_module = importlib.import_module("langchain_mcp_adapters") -# MultiServerMCPClient = mcp_module.MultiServerMCPClient - -# mcp_client = MultiServerMCPClient( -# build_palette_server_config( -# default_env_file=DEFAULT_ENV_FILE, -# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, -# default_mcp_image=DEFAULT_MCP_IMAGE, -# ) -# ) -# hide_mcp_output = debug_level != "verbose" -# with suppress_console_output(hide_mcp_output): -# mcp_tools = await mcp_client.get_tools() - -# if is_debug_enabled(debug_level, "debug"): -# print("Initializing profile finder agent...") - -# profile_finder_agent = await initialize_profile_finder_agent( -# model=args.model, -# mcp_tools=mcp_tools, -# ) -# active_cluster_agent = await initialize_active_cluster_agent( -# model=args.active_cluster_model, -# mcp_tools=mcp_tools, -# ) -# tagging_agent = await initialize_tagging_agent(model=args.tagging_model, mcp_tools=mcp_tools) -# reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - -# if is_debug_enabled(debug_level, "debug"): -# print(f"Running profile discovery for pack: {args.pack}") - -# profile_discovery_output = await run_with_thinking_indicator( -# invoke_profile_finder_agent( -# profile_finder_agent, args.pack, debug_level, run_id -# ) -# ) - -# if is_debug_enabled(debug_level, "debug"): -# print("Finding active clusters using matched profiles...") - -# active_cluster_output = await run_with_thinking_indicator( -# invoke_active_cluster_agent( -# active_cluster_agent, -# args.pack, -# profile_discovery_output, -# debug_level, -# run_id, -# ) -# ) - -# user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) - -# if user_tags is None: -# if is_debug_enabled(debug_level, "debug"): -# print("No matches found. Skipping tagging.") -# tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' -# else: -# if is_debug_enabled(debug_level, "debug"): -# if user_tags: -# print(f"Tagging with: {user_tags}") -# else: -# print("No tags entered. Skipping tagging.") -# tagging_output = await run_with_thinking_indicator( -# invoke_tagging_agent( -# tagging_agent, -# args.pack, -# profile_discovery_output, -# active_cluster_output, -# user_tags, -# debug_level, -# run_id, -# ) -# ) - -# if is_debug_enabled(debug_level, "debug"): -# print("Formatting report...") - -# final_report = await run_with_thinking_indicator( -# invoke_reporter_agent( -# reporter_agent, -# args.pack, -# profile_discovery_output, -# active_cluster_output, -# tagging_output, -# run_id, -# ) -# ) -# print(final_report) +async def main_async() -> None: + args = parse_args() + run_id = uuid.uuid4().hex[:8] + debug_level = get_debug_level(cli_level=args.log_level) + ensure_local_prerequisites() + + if not args.pack: + print( + "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", + file=sys.stderr, + ) + sys.exit(1) + + if is_debug_enabled(debug_level, "debug"): + print(f"Debug level: {debug_level}") + print(f"Run ID: {run_id}") + print("Options:") + print(f" --pack: {args.pack!r}") + print(f" --model: {args.model}") + print("Initializing MCP client...") + + try: + mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") + MultiServerMCPClient = mcp_client_module.MultiServerMCPClient + except (ImportError, AttributeError): + mcp_module = importlib.import_module("langchain_mcp_adapters") + MultiServerMCPClient = mcp_module.MultiServerMCPClient + + mcp_client = MultiServerMCPClient( + build_palette_server_config( + default_env_file=DEFAULT_ENV_FILE, + default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, + default_mcp_image=DEFAULT_MCP_IMAGE, + ) + ) + hide_mcp_output = debug_level != "verbose" + with suppress_console_output(hide_mcp_output): + mcp_tools = await mcp_client.get_tools() + + if is_debug_enabled(debug_level, "debug"): + print("Initializing profile finder agent...") + + profile_finder_agent = await initialize_profile_finder_agent( + model=args.model, + mcp_tools=mcp_tools, + ) + active_cluster_agent = await initialize_active_cluster_agent( + model=args.active_cluster_model, + mcp_tools=mcp_tools, + ) + tagging_agent = await initialize_tagging_agent(model=args.tagging_model, mcp_tools=mcp_tools) + reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + + if is_debug_enabled(debug_level, "debug"): + print(f"Running profile discovery for pack: {args.pack}") + + profile_discovery_output = await run_with_thinking_indicator( + invoke_profile_finder_agent( + profile_finder_agent, args.pack, debug_level, run_id + ) + ) + + if is_debug_enabled(debug_level, "debug"): + print("Finding active clusters using matched profiles...") + + active_cluster_output = await run_with_thinking_indicator( + invoke_active_cluster_agent( + active_cluster_agent, + args.pack, + profile_discovery_output, + debug_level, + run_id, + ) + ) + + user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) + + if user_tags is None: + if is_debug_enabled(debug_level, "debug"): + print("No matches found. Skipping tagging.") + tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' + else: + if is_debug_enabled(debug_level, "debug"): + if user_tags: + print(f"Tagging with: {user_tags}") + else: + print("No tags entered. Skipping tagging.") + tagging_output = await run_with_thinking_indicator( + invoke_tagging_agent( + tagging_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + user_tags, + debug_level, + run_id, + ) + ) + + if is_debug_enabled(debug_level, "debug"): + print("Formatting report...") + + final_report = await run_with_thinking_indicator( + invoke_reporter_agent( + reporter_agent, + args.pack, + profile_discovery_output, + active_cluster_output, + tagging_output, + run_id, + ) + ) + print(final_report) if __name__ == "__main__": From 27d291a8948fa0bbd355248f663f54331a1d2e64 Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Fri, 27 Mar 2026 14:53:55 -0700 Subject: [PATCH 13/13] chore: minor updates --- .../integrate-palette-mcp/README.md | 4 +- .../agents/active_cluster_agent.py | 76 +++--- .../agents/palette_profile_agent.py | 120 ++++----- .../agents/reporter_agent.py | 190 +++++++------- .../agents/tagging_agent.py | 146 +++++------ ai/palette-mcp/integrate-palette-mcp/main.py | 234 +++++++++--------- 6 files changed, 385 insertions(+), 385 deletions(-) diff --git a/ai/palette-mcp/integrate-palette-mcp/README.md b/ai/palette-mcp/integrate-palette-mcp/README.md index 9745079..a57c19e 100644 --- a/ai/palette-mcp/integrate-palette-mcp/README.md +++ b/ai/palette-mcp/integrate-palette-mcp/README.md @@ -16,8 +16,8 @@ Follow the instructions in the tutorial to get started. | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | | `OPENAI_API_KEY` | OpenAI API key. Required. | `sk-...` | | `OPENAI_MODEL` | Default model used for all agents. Overridden per-agent by the model-specific variables below. | `gpt-4o` | -| `OPENAI_ACTIVE_CLUSTER_MODEL` | Model for the active cluster finder agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | -| `OPENAI_REPORTER_MODEL` | Model for the reporter agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | +| `OPENAI_ACTIVE_CLUSTER_MODEL` | Model for the active cluster finder agent. Defaults to `gpt-5.4-nano`. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | +| `OPENAI_REPORTER_MODEL` | Model for the reporter agent. Defaults to `gpt-5.4-mini`. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | | `OPENAI_TAGGING_MODEL` | Model for the tagging agent. Falls back to `OPENAI_MODEL`. | `gpt-4o-mini` | | `PACK_NAME` | Target pack name to search for in cluster profiles. Can also be set via `--pack`. | `nginx` | | `DEBUG` | Log level. Accepted values: `warn`, `info`, `debug`, `verbose`. Defaults to `info`. Can also be set via `--log-level`. | `debug` | diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py index 6cb42fd..9f0a6ef 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/active_cluster_agent.py @@ -58,41 +58,41 @@ async def initialize_active_cluster_agent( ) -async def invoke_active_cluster_agent( - agent: Any, - pack_name: str, - matched_profiles_output: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) - active_cluster_prompt = ( - f"Given this profile discovery result for pack '{pack_name}':\n" - f"{matched_profiles_output}\n\n" - "Required process:\n" - "1) Extract matched profile UIDs from the input JSON.\n" - "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" - "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" - "4) Match clusters using explicit profile UID fields only.\n" - "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" - "Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - ) - run_config = { - "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": active_cluster_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, ActiveClusterOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) +# async def invoke_active_cluster_agent( +# agent: Any, +# pack_name: str, +# matched_profiles_output: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# schema = json.dumps(ActiveClusterOutput.model_json_schema(), indent=2) +# active_cluster_prompt = ( +# f"Given this profile discovery result for pack '{pack_name}':\n" +# f"{matched_profiles_output}\n\n" +# "Required process:\n" +# "1) Extract matched profile UIDs from the input JSON.\n" +# "2) Call gather_or_delete_clusters with action='list' and active_only=true.\n" +# "3) For each active cluster uid from step 2, call gather_or_delete_clusters with action='get'.\n" +# "4) Match clusters using explicit profile UID fields only.\n" +# "5) If no clusters match, return an empty list and include every checked active cluster uid.\n\n" +# "Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"active-cluster:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": active_cluster_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, ActiveClusterOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py index 10ddf0b..4758f9e 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/palette_profile_agent.py @@ -37,63 +37,63 @@ class ProfileDiscoveryOutput(BaseModel): notes: str -async def initialize_profile_finder_agent( - model: str, - mcp_tools: list, -) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI - - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver - - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, - response_format=ProfileDiscoveryOutput, - checkpointer=InMemorySaver(), - ) - - -async def invoke_profile_finder_agent( - agent: Any, - pack_name: str, - debug_level: str, - run_id: str, -) -> str: - hide_mcp_output = debug_level != "verbose" - schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) - profile_finder_prompt = ( - "Find all cluster profiles in Palette that use the pack named " - f"'{pack_name}'. Use Palette MCP tools only.\n\n" - "Required process:\n" - "1) Call gather_or_delete_clusterprofiles with action='list', limit=50, and compact=false.\n" - "2) Review the output and check if the pack name is in the list. Match pack name case-insensitively. If the pack present, add it to the list of cluster profiles to review. If it is not, do not add it to the list.\n" - "3) For each matched profile, include scope from metadata.annotations.scope when available.\n" - "4) If there is a continue token, use it to retrieve the next page of results. Do not stop until you have gathered all cluster profiles and there is no continue token. Repeat the process until you have gathered all cluster profiles.\n" - "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" - "6) If the pack name is not in the list, return an empty list and mention in notes.\n" - "Important:\n" - "Make sure you reviewed all cluster profiles. There should be no continue token at the end of the process." - "Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - ) - run_config = { - "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": profile_finder_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, ProfileDiscoveryOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) +# async def initialize_profile_finder_agent( +# model: str, +# mcp_tools: list, +# ) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI + +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver + +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=PROFILE_FINDER_SYSTEM_PROMPT, +# response_format=ProfileDiscoveryOutput, +# checkpointer=InMemorySaver(), +# ) + + +# async def invoke_profile_finder_agent( +# agent: Any, +# pack_name: str, +# debug_level: str, +# run_id: str, +# ) -> str: +# hide_mcp_output = debug_level != "verbose" +# schema = json.dumps(ProfileDiscoveryOutput.model_json_schema(), indent=2) +# profile_finder_prompt = ( +# "Find all cluster profiles in Palette that use the pack named " +# f"'{pack_name}'. Use Palette MCP tools only.\n\n" +# "Required process:\n" +# "1) Call gather_or_delete_clusterprofiles with action='list', limit=50, and compact=false.\n" +# "2) Review the output and check if the pack name is in the list. Match pack name case-insensitively. If the pack present, add it to the list of cluster profiles to review. If it is not, do not add it to the list.\n" +# "3) For each matched profile, include scope from metadata.annotations.scope when available.\n" +# "4) If there is a continue token, use it to retrieve the next page of results. Do not stop until you have gathered all cluster profiles and there is no continue token. Repeat the process until you have gathered all cluster profiles.\n" +# "5) If scope is missing, set scope to 'unknown' and mention in notes.\n\n" +# "6) If the pack name is not in the list, return an empty list and mention in notes.\n" +# "Important:\n" +# "Make sure you reviewed all cluster profiles. There should be no continue token at the end of the process." +# "Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"profile-finder:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": profile_finder_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, ProfileDiscoveryOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py index 52be529..cb03940 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/reporter_agent.py @@ -16,102 +16,102 @@ ) -async def initialize_reporter_agent(model: str) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI +# async def initialize_reporter_agent(model: str) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=[], - system_prompt=REPORTER_SYSTEM_PROMPT, - checkpointer=InMemorySaver(), - ) +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=[], +# system_prompt=REPORTER_SYSTEM_PROMPT, +# checkpointer=InMemorySaver(), +# ) -async def invoke_reporter_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - tagging_output: str, - run_id: str, -) -> str: - reporter_prompt = ( - f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" - "Use these outputs as the source of truth:\n\n" - "Profile discovery output:\n" - f"{profile_discovery_output}\n\n" - "Active cluster mapping output:\n" - f"{active_cluster_output}\n\n" - "Tagging output:\n" - f"{tagging_output}\n\n" - "Produce output that matches this exact template. " - "Do not add, remove, or rename any section. " - "Do not add extra blank lines between bullet sub-fields. " - "Replace every with the real value from the source data.\n\n" - "---\n" - "1) Summary\n\n" - "Pack `` was found in of scanned cluster profiles. " - "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " - "tagging for .\n\n" - "2) Matching cluster profiles\n\n" - "- UID: ``\n" - " - Name: ``\n" - " - Evidence: \n\n" - "(repeat for each matched profile)\n\n" - "3) Active clusters using the matched cluster profiles\n\n" - "- UID: ``\n" - " - Name: ``\n" - " - Cluster profile UID: ``\n" - " - Cluster profile name: ``\n\n" - "(repeat for each active cluster)\n\n" - "4) Tagging results for clusters and cluster profiles\n\n" - "Requested tags:\n" - "- ``\n\n" - "(repeat for each tag)\n\n" - "Clusters tagged:\n" - "- Cluster UID: ``\n" - " - Result: \n\n" - "(repeat for each cluster)\n\n" - "Cluster profiles tagged:\n" - "- Cluster profile UID: ``\n" - " - Name: ``\n" - " - Scope: ``\n" - " - Result: \n\n" - "(repeat for each tagged profile)\n\n" - "Skipped cluster profiles:\n" - "- Cluster profile UID: ``\n" - " - Name: ``\n" - " - Scope: ``\n" - " - Reason: ``\n\n" - "(repeat for each skipped profile)\n\n" - "5) Notes and caveats\n\n" - "- \n\n" - "(repeat for each note)\n" - "---\n\n" - "Rules:\n" - "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" - "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" - "- Do not invent data. Only use values present in the source outputs.\n" - "- Remove any '(repeat ...)' lines from the final output.\n" - "- Remove the '---' delimiters from the final output.\n" - ) - run_config = { - "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} - } - result = await agent.ainvoke( - { - "messages": [ - { - "role": "user", - "content": reporter_prompt, - } - ] - }, - config=run_config, - ) - return extract_text_response(result) +# async def invoke_reporter_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# tagging_output: str, +# run_id: str, +# ) -> str: +# reporter_prompt = ( +# f"Create a report for cluster profiles using pack '{pack_name}'.\n\n" +# "Use these outputs as the source of truth:\n\n" +# "Profile discovery output:\n" +# f"{profile_discovery_output}\n\n" +# "Active cluster mapping output:\n" +# f"{active_cluster_output}\n\n" +# "Tagging output:\n" +# f"{tagging_output}\n\n" +# "Produce output that matches this exact template. " +# "Do not add, remove, or rename any section. " +# "Do not add extra blank lines between bullet sub-fields. " +# "Replace every with the real value from the source data.\n\n" +# "---\n" +# "1) Summary\n\n" +# "Pack `` was found in of scanned cluster profiles. " +# "Among active cluster(s) scanned, active cluster(s) were confirmed to be using one of the matched profiles; " +# "tagging for .\n\n" +# "2) Matching cluster profiles\n\n" +# "- UID: ``\n" +# " - Name: ``\n" +# " - Evidence: \n\n" +# "(repeat for each matched profile)\n\n" +# "3) Active clusters using the matched cluster profiles\n\n" +# "- UID: ``\n" +# " - Name: ``\n" +# " - Cluster profile UID: ``\n" +# " - Cluster profile name: ``\n\n" +# "(repeat for each active cluster)\n\n" +# "4) Tagging results for clusters and cluster profiles\n\n" +# "Requested tags:\n" +# "- ``\n\n" +# "(repeat for each tag)\n\n" +# "Clusters tagged:\n" +# "- Cluster UID: ``\n" +# " - Result: \n\n" +# "(repeat for each cluster)\n\n" +# "Cluster profiles tagged:\n" +# "- Cluster profile UID: ``\n" +# " - Name: ``\n" +# " - Scope: ``\n" +# " - Result: \n\n" +# "(repeat for each tagged profile)\n\n" +# "Skipped cluster profiles:\n" +# "- Cluster profile UID: ``\n" +# " - Name: ``\n" +# " - Scope: ``\n" +# " - Reason: ``\n\n" +# "(repeat for each skipped profile)\n\n" +# "5) Notes and caveats\n\n" +# "- \n\n" +# "(repeat for each note)\n" +# "---\n\n" +# "Rules:\n" +# "- If there are no matching cluster profiles, write 'No matching cluster profiles found.' in the summary and omit sections 2-4.\n" +# "- If active cluster data appears incomplete, add a note in section 5 stating the result may be incomplete.\n" +# "- Do not invent data. Only use values present in the source outputs.\n" +# "- Remove any '(repeat ...)' lines from the final output.\n" +# "- Remove the '---' delimiters from the final output.\n" +# ) +# run_config = { +# "configurable": {"thread_id": f"reporter:{pack_name.lower()}:{run_id}"} +# } +# result = await agent.ainvoke( +# { +# "messages": [ +# { +# "role": "user", +# "content": reporter_prompt, +# } +# ] +# }, +# config=run_config, +# ) +# return extract_text_response(result) diff --git a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py index ecf2b2e..f1dfb46 100644 --- a/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py +++ b/ai/palette-mcp/integrate-palette-mcp/agents/tagging_agent.py @@ -52,76 +52,76 @@ class TaggingOutput(BaseModel): notes: str -async def initialize_tagging_agent(model: str, mcp_tools: list) -> Any: - from langchain.agents import create_agent - from langchain_openai import ChatOpenAI - - checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") - InMemorySaver = checkpoint_module.InMemorySaver - - llm = ChatOpenAI(model=model) - return create_agent( - model=llm, - tools=mcp_tools, - system_prompt=TAGGING_SYSTEM_PROMPT, - response_format=TaggingOutput, - checkpointer=InMemorySaver(), - ) - - -async def invoke_tagging_agent( - agent: Any, - pack_name: str, - profile_discovery_output: str, - active_cluster_output: str, - tags: list[str], - debug_level: str, - run_id: str, -) -> str: - if not tags: - return TaggingOutput( - pack_name=pack_name, - requested_tags=[], - clusters_attempted=[], - cluster_profiles_attempted=[], - cluster_results=[], - cluster_profile_results=[], - cluster_profile_skipped=[], - notes="No tags provided by user. Tagging skipped.", - ).model_dump_json() - - schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) - tagging_prompt = ( - f"Given this profile discovery output for pack '{pack_name}':\n" - f"{profile_discovery_output}\n\n" - f"Given this active cluster mapping output for pack '{pack_name}':\n" - f"{active_cluster_output}\n\n" - f"Apply these tags: {tags}\n\n" - "Task:\n" - "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" - "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" - f"3) For each cluster UID, call search_and_manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" - f"4) For each cluster profile UID, call search_and_manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" - "5) For scope='system' profiles, skip tagging and record skip reason.\n" - "6) Return a response that conforms to this JSON schema:\n" - f"{schema}\n" - "If there are no resources to tag, return empty arrays and explain in notes." - ) - hide_mcp_output = debug_level != "verbose" - run_config = { - "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} - } - with suppress_console_output(hide_mcp_output): - result = await agent.ainvoke( - {"messages": [{"role": "user", "content": tagging_prompt}]}, - config=run_config, - ) - structured = result.get("structured_response") - if isinstance(structured, TaggingOutput): - return structured.model_dump_json() - messages = result.get("messages", []) - for message in reversed(messages): - content = getattr(message, "content", None) - if isinstance(content, str) and content.strip(): - return content - return str(result) \ No newline at end of file +# async def initialize_tagging_agent(model: str, mcp_tools: list) -> Any: +# from langchain.agents import create_agent +# from langchain_openai import ChatOpenAI + +# checkpoint_module = importlib.import_module("langgraph.checkpoint.memory") +# InMemorySaver = checkpoint_module.InMemorySaver + +# llm = ChatOpenAI(model=model) +# return create_agent( +# model=llm, +# tools=mcp_tools, +# system_prompt=TAGGING_SYSTEM_PROMPT, +# response_format=TaggingOutput, +# checkpointer=InMemorySaver(), +# ) + + +# async def invoke_tagging_agent( +# agent: Any, +# pack_name: str, +# profile_discovery_output: str, +# active_cluster_output: str, +# tags: list[str], +# debug_level: str, +# run_id: str, +# ) -> str: +# if not tags: +# return TaggingOutput( +# pack_name=pack_name, +# requested_tags=[], +# clusters_attempted=[], +# cluster_profiles_attempted=[], +# cluster_results=[], +# cluster_profile_results=[], +# cluster_profile_skipped=[], +# notes="No tags provided by user. Tagging skipped.", +# ).model_dump_json() + +# schema = json.dumps(TaggingOutput.model_json_schema(), indent=2) +# tagging_prompt = ( +# f"Given this profile discovery output for pack '{pack_name}':\n" +# f"{profile_discovery_output}\n\n" +# f"Given this active cluster mapping output for pack '{pack_name}':\n" +# f"{active_cluster_output}\n\n" +# f"Apply these tags: {tags}\n\n" +# "Task:\n" +# "1) Extract unique cluster UIDs from active_clusters_using_matched_profiles.\n" +# "2) Extract unique cluster profile UIDs and scope values from matched_profiles.\n" +# f"3) For each cluster UID, call search_and_manage_resource_tags with action='create', resource_type='spectroclusters', uid=, tags={tags}.\n" +# f"4) For each cluster profile UID, call search_and_manage_resource_tags with action='create', resource_type='clusterprofiles', uid=, tags={tags}, only if scope is not 'system'.\n" +# "5) For scope='system' profiles, skip tagging and record skip reason.\n" +# "6) Return a response that conforms to this JSON schema:\n" +# f"{schema}\n" +# "If there are no resources to tag, return empty arrays and explain in notes." +# ) +# hide_mcp_output = debug_level != "verbose" +# run_config = { +# "configurable": {"thread_id": f"tagging:{pack_name.lower()}:{run_id}"} +# } +# with suppress_console_output(hide_mcp_output): +# result = await agent.ainvoke( +# {"messages": [{"role": "user", "content": tagging_prompt}]}, +# config=run_config, +# ) +# structured = result.get("structured_response") +# if isinstance(structured, TaggingOutput): +# return structured.model_dump_json() +# messages = result.get("messages", []) +# for message in reversed(messages): +# content = getattr(message, "content", None) +# if isinstance(content, str) and content.strip(): +# return content +# return str(result) \ No newline at end of file diff --git a/ai/palette-mcp/integrate-palette-mcp/main.py b/ai/palette-mcp/integrate-palette-mcp/main.py index 1b715cd..3678690 100644 --- a/ai/palette-mcp/integrate-palette-mcp/main.py +++ b/ai/palette-mcp/integrate-palette-mcp/main.py @@ -33,8 +33,8 @@ from agents.tagging_agent import initialize_tagging_agent, invoke_tagging_agent DEFAULT_MODEL = "gpt-5.4-nano" -# DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:dev" -DEFAULT_MCP_IMAGE = "karl5" +REPORT_MODEL = "gpt-5.4-mini" +DEFAULT_MCP_IMAGE = "public.ecr.aws/palette-ai/palette-mcp-server:latest" DEFAULT_ENV_FILE = os.path.expanduser("~/.palette/.env-mcp") DEFAULT_KUBECONFIG_DIR = None DEFAULT_PACK_NAME = "" @@ -42,7 +42,7 @@ def parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser( - description="One-shot workflow: Palette profile finder agent + reporter agent." + description="An example Agentic workflow designed to showcase how to integrate Palette MCP into a LangChain agent workflow: Palette profile finder agent + active cluster finder agent + tagging agent + reporter agent." ) parser.add_argument( "--pack", @@ -65,7 +65,7 @@ def parse_args() -> argparse.Namespace: ) parser.add_argument( "--reporter-model", - default=os.getenv("OPENAI_REPORTER_MODEL", resolved_model), + default=os.getenv("OPENAI_REPORTER_MODEL", REPORT_MODEL), help="Model for the reporter agent (default: --model).", ) parser.add_argument( @@ -82,119 +82,119 @@ def parse_args() -> argparse.Namespace: return parser.parse_args() -async def main_async() -> None: - args = parse_args() - run_id = uuid.uuid4().hex[:8] - debug_level = get_debug_level(cli_level=args.log_level) - ensure_local_prerequisites() - - if not args.pack: - print( - "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", - file=sys.stderr, - ) - sys.exit(1) - - if is_debug_enabled(debug_level, "debug"): - print(f"Debug level: {debug_level}") - print(f"Run ID: {run_id}") - print("Options:") - print(f" --pack: {args.pack!r}") - print(f" --model: {args.model}") - print("Initializing MCP client...") - - try: - mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") - MultiServerMCPClient = mcp_client_module.MultiServerMCPClient - except (ImportError, AttributeError): - mcp_module = importlib.import_module("langchain_mcp_adapters") - MultiServerMCPClient = mcp_module.MultiServerMCPClient - - mcp_client = MultiServerMCPClient( - build_palette_server_config( - default_env_file=DEFAULT_ENV_FILE, - default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, - default_mcp_image=DEFAULT_MCP_IMAGE, - ) - ) - hide_mcp_output = debug_level != "verbose" - with suppress_console_output(hide_mcp_output): - mcp_tools = await mcp_client.get_tools() - - if is_debug_enabled(debug_level, "debug"): - print("Initializing profile finder agent...") - - profile_finder_agent = await initialize_profile_finder_agent( - model=args.model, - mcp_tools=mcp_tools, - ) - active_cluster_agent = await initialize_active_cluster_agent( - model=args.active_cluster_model, - mcp_tools=mcp_tools, - ) - tagging_agent = await initialize_tagging_agent(model=args.tagging_model, mcp_tools=mcp_tools) - reporter_agent = await initialize_reporter_agent(model=args.reporter_model) - - if is_debug_enabled(debug_level, "debug"): - print(f"Running profile discovery for pack: {args.pack}") - - profile_discovery_output = await run_with_thinking_indicator( - invoke_profile_finder_agent( - profile_finder_agent, args.pack, debug_level, run_id - ) - ) - - if is_debug_enabled(debug_level, "debug"): - print("Finding active clusters using matched profiles...") - - active_cluster_output = await run_with_thinking_indicator( - invoke_active_cluster_agent( - active_cluster_agent, - args.pack, - profile_discovery_output, - debug_level, - run_id, - ) - ) - - user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) - - if user_tags is None: - if is_debug_enabled(debug_level, "debug"): - print("No matches found. Skipping tagging.") - tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' - else: - if is_debug_enabled(debug_level, "debug"): - if user_tags: - print(f"Tagging with: {user_tags}") - else: - print("No tags entered. Skipping tagging.") - tagging_output = await run_with_thinking_indicator( - invoke_tagging_agent( - tagging_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - user_tags, - debug_level, - run_id, - ) - ) - - if is_debug_enabled(debug_level, "debug"): - print("Formatting report...") - - final_report = await run_with_thinking_indicator( - invoke_reporter_agent( - reporter_agent, - args.pack, - profile_discovery_output, - active_cluster_output, - tagging_output, - run_id, - ) - ) - print(final_report) +# async def main_async() -> None: +# args = parse_args() +# run_id = uuid.uuid4().hex[:8] +# debug_level = get_debug_level(cli_level=args.log_level) +# ensure_local_prerequisites() + +# if not args.pack: +# print( +# "Error: no pack name provided. Use --pack or set the PACK_NAME environment variable.", +# file=sys.stderr, +# ) +# sys.exit(1) + +# if is_debug_enabled(debug_level, "debug"): +# print(f"Debug level: {debug_level}") +# print(f"Run ID: {run_id}") +# print("Options:") +# print(f" --pack: {args.pack!r}") +# print(f" --model: {args.model}") +# print("Initializing MCP client...") + +# try: +# mcp_client_module = importlib.import_module("langchain_mcp_adapters.client") +# MultiServerMCPClient = mcp_client_module.MultiServerMCPClient +# except (ImportError, AttributeError): +# mcp_module = importlib.import_module("langchain_mcp_adapters") +# MultiServerMCPClient = mcp_module.MultiServerMCPClient + +# mcp_client = MultiServerMCPClient( +# build_palette_server_config( +# default_env_file=DEFAULT_ENV_FILE, +# default_kubeconfig_dir=DEFAULT_KUBECONFIG_DIR, +# default_mcp_image=DEFAULT_MCP_IMAGE, +# ) +# ) +# hide_mcp_output = debug_level != "verbose" +# with suppress_console_output(hide_mcp_output): +# mcp_tools = await mcp_client.get_tools() + +# if is_debug_enabled(debug_level, "debug"): +# print("Initializing profile finder agent...") + +# profile_finder_agent = await initialize_profile_finder_agent( +# model=args.model, +# mcp_tools=mcp_tools, +# ) +# active_cluster_agent = await initialize_active_cluster_agent( +# model=args.active_cluster_model, +# mcp_tools=mcp_tools, +# ) +# tagging_agent = await initialize_tagging_agent(model=args.tagging_model, mcp_tools=mcp_tools) +# reporter_agent = await initialize_reporter_agent(model=args.reporter_model) + +# if is_debug_enabled(debug_level, "debug"): +# print(f"Running profile discovery for pack: {args.pack}") + +# profile_discovery_output = await run_with_thinking_indicator( +# invoke_profile_finder_agent( +# profile_finder_agent, args.pack, debug_level, run_id +# ) +# ) + +# if is_debug_enabled(debug_level, "debug"): +# print("Finding active clusters using matched profiles...") + +# active_cluster_output = await run_with_thinking_indicator( +# invoke_active_cluster_agent( +# active_cluster_agent, +# args.pack, +# profile_discovery_output, +# debug_level, +# run_id, +# ) +# ) + +# user_tags = prompt_for_tags(profile_discovery_output, active_cluster_output) + +# if user_tags is None: +# if is_debug_enabled(debug_level, "debug"): +# print("No matches found. Skipping tagging.") +# tagging_output = '{"skipped": true, "reason": "no matched profiles or active clusters found"}' +# else: +# if is_debug_enabled(debug_level, "debug"): +# if user_tags: +# print(f"Tagging with: {user_tags}") +# else: +# print("No tags entered. Skipping tagging.") +# tagging_output = await run_with_thinking_indicator( +# invoke_tagging_agent( +# tagging_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# user_tags, +# debug_level, +# run_id, +# ) +# ) + +# if is_debug_enabled(debug_level, "debug"): +# print("Formatting report...") + +# final_report = await run_with_thinking_indicator( +# invoke_reporter_agent( +# reporter_agent, +# args.pack, +# profile_discovery_output, +# active_cluster_output, +# tagging_output, +# run_id, +# ) +# ) +# print(final_report) if __name__ == "__main__":